From 7e00099480ab4d2c9353b8b5ed8d516e33abdd24 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Mon, 27 Jan 2025 18:28:53 +0000 Subject: [PATCH 001/313] vnet: add assert for offload flags in debug mode Type: improvement Signed-off-by: Mohsin Kazmi Change-Id: I7ac5ba0e2dc1fe09a00124b661d6c853a010736a --- src/plugins/lisp/lisp-cp/packets.c | 4 +++- src/vnet/buffer.h | 17 +++++++++++++++++ src/vnet/dpo/mpls_label_dpo.c | 6 ++++++ src/vnet/ethernet/node.c | 1 + src/vnet/gso/gro_func.h | 10 ++++++++-- src/vnet/ip/ip4_forward.c | 3 +++ src/vnet/ip/ip6_forward.c | 7 +++++++ 7 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/plugins/lisp/lisp-cp/packets.c b/src/plugins/lisp/lisp-cp/packets.c index 6c36a550ab..6aaa5167e4 100644 --- a/src/plugins/lisp/lisp-cp/packets.c +++ b/src/plugins/lisp/lisp-cp/packets.c @@ -182,9 +182,11 @@ pkt_push_udp_and_ip (vlib_main_t * vm, vlib_buffer_t * b, u16 sp, u16 dp, if (csum_offload) { ih = pkt_push_ip (vm, b, sip, dip, IP_PROTOCOL_UDP, 1); - vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_UDP_CKSUM); vnet_buffer (b)->l3_hdr_offset = (u8 *) ih - b->data; vnet_buffer (b)->l4_hdr_offset = (u8 *) uh - b->data; + b->flags |= + VNET_BUFFER_F_L3_HDR_OFFSET_VALID | VNET_BUFFER_F_L4_HDR_OFFSET_VALID; + vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_UDP_CKSUM); uh->checksum = 0; } else diff --git a/src/vnet/buffer.h b/src/vnet/buffer.h index 276cb1115f..5620f995c9 100644 --- a/src/vnet/buffer.h +++ b/src/vnet/buffer.h @@ -547,6 +547,23 @@ vnet_buffer_offload_flags_set (vlib_buffer_t *b, vnet_buffer_oflags_t oflags) vnet_buffer (b)->oflags = oflags; b->flags |= VNET_BUFFER_F_OFFLOAD; } +#if CLIB_DEBUG > 0 + if (VNET_BUFFER_OFFLOAD_F_IP_CKSUM & oflags) + { + ASSERT (b->flags & VNET_BUFFER_F_L3_HDR_OFFSET_VALID); + ASSERT (b->flags & VNET_BUFFER_F_IS_IP4); + } + + if ((VNET_BUFFER_OFFLOAD_F_TCP_CKSUM | VNET_BUFFER_OFFLOAD_F_UDP_CKSUM) & + oflags) + ASSERT (b->flags & VNET_BUFFER_F_L4_HDR_OFFSET_VALID); + + if (VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM & oflags) + ASSERT (VNET_BUFFER_OFFLOAD_F_TNL_VXLAN & oflags); + if (VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM & oflags) + ASSERT ((VNET_BUFFER_OFFLOAD_F_TNL_IPIP & oflags) || + (VNET_BUFFER_OFFLOAD_F_TNL_VXLAN & oflags)); +#endif } static_always_inline void diff --git a/src/vnet/dpo/mpls_label_dpo.c b/src/vnet/dpo/mpls_label_dpo.c index 872577dfbe..bccedfbfaf 100644 --- a/src/vnet/dpo/mpls_label_dpo.c +++ b/src/vnet/dpo/mpls_label_dpo.c @@ -447,6 +447,11 @@ mpls_label_imposition_inline (vlib_main_t * vm, vnet_buffer (b2)->l3_hdr_offset = b2->current_data; vnet_buffer (b3)->l3_hdr_offset = b3->current_data; + b0->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + b1->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + b2->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + b3->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + if (DPO_PROTO_IP4 == dproto) { ip4_header_t * ip0 = vlib_buffer_get_current(b0); @@ -792,6 +797,7 @@ mpls_label_imposition_inline (vlib_main_t * vm, if (DPO_PROTO_MPLS != dproto) { vnet_buffer (b0)->l3_hdr_offset = b0->current_data; + b0->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; if (DPO_PROTO_IP4 == dproto) { diff --git a/src/vnet/ethernet/node.c b/src/vnet/ethernet/node.c index 2d7f091399..aaf06a96e7 100644 --- a/src/vnet/ethernet/node.c +++ b/src/vnet/ethernet/node.c @@ -610,6 +610,7 @@ eth_input_tag_lookup (vlib_main_t * vm, vnet_main_t * vnm, vlib_buffer_advance (b, l->adv); vnet_buffer (b)->l2.l2_len = l->len; vnet_buffer (b)->l3_hdr_offset = vnet_buffer (b)->l2_hdr_offset + l->len; + b->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; if (l->err == ETHERNET_ERROR_NONE) { diff --git a/src/vnet/gso/gro_func.h b/src/vnet/gso/gro_func.h index e2e4e93850..ebda0f78c1 100644 --- a/src/vnet/gso/gro_func.h +++ b/src/vnet/gso/gro_func.h @@ -394,7 +394,10 @@ gro_fixup_header (vlib_main_t *vm, vlib_buffer_t *b0, u32 ack_number, u8 is_l2) clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0) - gho0.l3_hdr_offset); vnet_buffer (b0)->l3_hdr_offset = (u8 *) ip4 - b0->data; - b0->flags |= (VNET_BUFFER_F_GSO | VNET_BUFFER_F_IS_IP4); + b0->flags |= (VNET_BUFFER_F_GSO | VNET_BUFFER_F_IS_IP4 | + VNET_BUFFER_F_L2_HDR_OFFSET_VALID | + VNET_BUFFER_F_L3_HDR_OFFSET_VALID | + VNET_BUFFER_F_L4_HDR_OFFSET_VALID); vnet_buffer_offload_flags_set (b0, (VNET_BUFFER_OFFLOAD_F_TCP_CKSUM | VNET_BUFFER_OFFLOAD_F_IP_CKSUM)); } @@ -406,7 +409,10 @@ gro_fixup_header (vlib_main_t *vm, vlib_buffer_t *b0, u32 ack_number, u8 is_l2) clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0) - gho0.l4_hdr_offset); vnet_buffer (b0)->l3_hdr_offset = (u8 *) ip6 - b0->data; - b0->flags |= (VNET_BUFFER_F_GSO | VNET_BUFFER_F_IS_IP6); + b0->flags |= (VNET_BUFFER_F_GSO | VNET_BUFFER_F_IS_IP6 | + VNET_BUFFER_F_L2_HDR_OFFSET_VALID | + VNET_BUFFER_F_L3_HDR_OFFSET_VALID | + VNET_BUFFER_F_L4_HDR_OFFSET_VALID); vnet_buffer_offload_flags_set (b0, VNET_BUFFER_OFFLOAD_F_TCP_CKSUM); } diff --git a/src/vnet/ip/ip4_forward.c b/src/vnet/ip/ip4_forward.c index cabefd8123..4760e29a6f 100644 --- a/src/vnet/ip/ip4_forward.c +++ b/src/vnet/ip/ip4_forward.c @@ -1786,6 +1786,8 @@ ip4_local_inline (vlib_main_t *vm, vlib_node_runtime_t *node, vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data; vnet_buffer (b[1])->l3_hdr_offset = b[1]->current_data; + b[0]->flags |= VNET_BUFFER_F_IS_IP4 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + b[1]->flags |= VNET_BUFFER_F_IS_IP4 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID; pt[0] = ip4_local_classify (b[0], ip[0], &next[0]); pt[1] = ip4_local_classify (b[1], ip[1], &next[1]); @@ -1834,6 +1836,7 @@ ip4_local_inline (vlib_main_t *vm, vlib_node_runtime_t *node, ip[0] = vlib_buffer_get_current (b[0]); vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data; + b[0]->flags |= VNET_BUFFER_F_IS_IP4 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID; pt[0] = ip4_local_classify (b[0], ip[0], &next[0]); if (head_of_feature_arc == 0 || pt[0]) diff --git a/src/vnet/ip/ip6_forward.c b/src/vnet/ip/ip6_forward.c index 3c1f40beff..803396f583 100644 --- a/src/vnet/ip/ip6_forward.c +++ b/src/vnet/ip/ip6_forward.c @@ -1311,6 +1311,10 @@ ip6_local_inline (vlib_main_t *vm, vlib_node_runtime_t *node, { vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data; vnet_buffer (b[1])->l3_hdr_offset = b[1]->current_data; + b[0]->flags |= + VNET_BUFFER_F_IS_IP6 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + b[1]->flags |= + VNET_BUFFER_F_IS_IP6 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID; u8 type[2]; type[0] = lm->builtin_protocol_by_ip_protocol[ip[0]->protocol]; @@ -1524,6 +1528,9 @@ ip6_local_inline (vlib_main_t *vm, vlib_node_runtime_t *node, if (head_of_feature_arc) { vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data; + b[0]->flags |= + VNET_BUFFER_F_IS_IP6 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID; + u8 type = lm->builtin_protocol_by_ip_protocol[ip->protocol]; u32 flags = b[0]->flags; From 3dd0aa6f53eee42d74eb3699cae93d20323cded7 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Wed, 29 Jan 2025 11:26:25 +0000 Subject: [PATCH 002/313] tap: enable IPv4 checksum offload on interface Type: improvement Signed-off-by: Mohsin Kazmi Change-Id: I6e671afd3e7d09ea2158659c1c70726e7791fc28 --- src/vnet/devices/tap/tap.c | 17 +++++++++-------- src/vnet/devices/virtio/pci.c | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vnet/devices/tap/tap.c b/src/vnet/devices/tap/tap.c index a987400dc6..8f39204a1a 100644 --- a/src/vnet/devices/tap/tap.c +++ b/src/vnet/devices/tap/tap.c @@ -678,15 +678,16 @@ tap_create_if (vlib_main_t * vm, tap_create_if_args_t * args) args->rv = 0; hw = vnet_get_hw_interface (vnm, vif->hw_if_index); cc.mask = VNET_HW_IF_CAP_INT_MODE | VNET_HW_IF_CAP_TCP_GSO | - VNET_HW_IF_CAP_TX_TCP_CKSUM | VNET_HW_IF_CAP_TX_UDP_CKSUM | - VNET_HW_IF_CAP_TX_FIXED_OFFSET; + VNET_HW_IF_CAP_TX_IP4_CKSUM | VNET_HW_IF_CAP_TX_TCP_CKSUM | + VNET_HW_IF_CAP_TX_UDP_CKSUM | VNET_HW_IF_CAP_TX_FIXED_OFFSET; cc.val = VNET_HW_IF_CAP_INT_MODE | VNET_HW_IF_CAP_TX_FIXED_OFFSET; if (args->tap_flags & TAP_FLAG_GSO) - cc.val |= VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_TCP_CKSUM | - VNET_HW_IF_CAP_TX_UDP_CKSUM; + cc.val |= VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_IP4_CKSUM | + VNET_HW_IF_CAP_TX_TCP_CKSUM | VNET_HW_IF_CAP_TX_UDP_CKSUM; else if (args->tap_flags & TAP_FLAG_CSUM_OFFLOAD) - cc.val |= VNET_HW_IF_CAP_TX_TCP_CKSUM | VNET_HW_IF_CAP_TX_UDP_CKSUM; + cc.val |= VNET_HW_IF_CAP_TX_IP4_CKSUM | VNET_HW_IF_CAP_TX_TCP_CKSUM | + VNET_HW_IF_CAP_TX_UDP_CKSUM; if ((args->tap_flags & TAP_FLAG_GSO) && (args->tap_flags & TAP_FLAG_GRO_COALESCE)) @@ -800,10 +801,10 @@ tap_csum_offload_enable_disable (vlib_main_t * vm, u32 sw_if_index, vif->gso_enabled = 0; vif->packet_coalesce = 0; - cc.mask = VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_L4_TX_CKSUM; + cc.mask = VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_CKSUM; if (enable_disable) { - cc.val = VNET_HW_IF_CAP_L4_TX_CKSUM; + cc.val = VNET_HW_IF_CAP_TX_CKSUM; vif->csum_offload_enabled = 1; } else @@ -848,7 +849,7 @@ tap_gso_enable_disable (vlib_main_t * vm, u32 sw_if_index, int enable_disable, vec_foreach_index (i, vif->tap_fds) _IOCTL (vif->tap_fds[i], TUNSETOFFLOAD, offload); - cc.mask = VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_L4_TX_CKSUM; + cc.mask = VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_CKSUM; if (enable_disable) { diff --git a/src/vnet/devices/virtio/pci.c b/src/vnet/devices/virtio/pci.c index 3a56732c3e..c85e316b1f 100644 --- a/src/vnet/devices/virtio/pci.c +++ b/src/vnet/devices/virtio/pci.c @@ -565,7 +565,7 @@ virtio_pci_offloads (vlib_main_t * vm, virtio_if_t * vif, int gso_enabled, vif->csum_offload_enabled = 0; vif->gso_enabled = 0; cc.val = 0; - cc.mask = VNET_HW_IF_CAP_L4_TX_CKSUM | VNET_HW_IF_CAP_TCP_GSO | + cc.mask = VNET_HW_IF_CAP_TX_CKSUM | VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_FIXED_OFFSET; } } From 306c57e8ba443f5c966c7b7fc21f69e8ce9d4d68 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Thu, 6 Feb 2025 19:29:02 +0000 Subject: [PATCH 003/313] dpdk: fix the outer flags Type: fix Signed-off-by: Mohsin Kazmi Change-Id: If2a02e8907c2e6b419893aa19f081d39e5f791ef --- src/plugins/dpdk/device/common.c | 5 +++-- src/plugins/dpdk/device/dpdk_priv.h | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugins/dpdk/device/common.c b/src/plugins/dpdk/device/common.c index 7671fc2639..0049ee8445 100644 --- a/src/plugins/dpdk/device/common.c +++ b/src/plugins/dpdk/device/common.c @@ -100,8 +100,9 @@ dpdk_device_setup (dpdk_device_t * xd) RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM | RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM; if (xd->conf.disable_tx_checksum_offload == 0) - txo |= RTE_ETH_TX_OFFLOAD_IPV4_CKSUM | RTE_ETH_TX_OFFLOAD_TCP_CKSUM | - RTE_ETH_TX_OFFLOAD_UDP_CKSUM; + txo |= RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM | + RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM | RTE_ETH_TX_OFFLOAD_IPV4_CKSUM | + RTE_ETH_TX_OFFLOAD_TCP_CKSUM | RTE_ETH_TX_OFFLOAD_UDP_CKSUM; if (xd->conf.disable_multi_seg == 0) { diff --git a/src/plugins/dpdk/device/dpdk_priv.h b/src/plugins/dpdk/device/dpdk_priv.h index 2067b11853..288b1d96e3 100644 --- a/src/plugins/dpdk/device/dpdk_priv.h +++ b/src/plugins/dpdk/device/dpdk_priv.h @@ -202,6 +202,9 @@ dpdk_update_counters (dpdk_device_t * xd, f64 now) #define RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM DEV_TX_OFFLOAD_OUTER_UDP_CKSUM #define RTE_ETH_TX_OFFLOAD_TCP_TSO DEV_TX_OFFLOAD_TCP_TSO #define RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO DEV_TX_OFFLOAD_VXLAN_TNL_TSO +#define RTE_ETH_TX_OFFLOAD_GRE_TNL_TSO DEV_TX_OFFLOAD_GRE_TNL_TSO +#define RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO DEV_TX_OFFLOAD_IPIP_TNL_TSO +#define RTE_ETH_TX_OFFLOAD_GENEVE_TNL_TSO DEV_TX_OFFLOAD_GENEVE_TNL_TSO #define RTE_ETH_TX_OFFLOAD_MULTI_SEGS DEV_TX_OFFLOAD_MULTI_SEGS #define RTE_ETH_RX_OFFLOAD_IPV4_CKSUM DEV_RX_OFFLOAD_IPV4_CKSUM #define RTE_ETH_RX_OFFLOAD_SCATTER DEV_RX_OFFLOAD_SCATTER From dac32494fab76d770f9df12cec63719c44eab3cf Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Tue, 27 May 2025 08:49:08 -0400 Subject: [PATCH 004/313] hs-test: include http2 tests in gcov run reports Type: make Change-Id: I038aa549eb2a4757d880c75aa8eefd91ef154edb Signed-off-by: Semir Sionek --- test/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/Makefile b/test/Makefile index 56e8c1891b..7f9d06bb48 100644 --- a/test/Makefile +++ b/test/Makefile @@ -389,7 +389,7 @@ COV_REM_TODO_NO_TEST="*/vpp-api/client/*" "*/plugins/prom/*" \ "*/plugins/ikev2/ikev2_format.c" "*/vnet/bier/bier_types.c" \ "*/plugins/ioam/*" "*/plugins/vxlan-gpe/*" "*/plugins/ioam/*" \ "*/plugins/hsi/*" "*/api/api_format.c" "*/*/api.c" "*/*/*_api.c" \ - "*/plugins/http/http2/*" "*/plugins/http_static/*" "*/plugins/hs_apps/*" + "*/plugins/http_static/*" "*/plugins/hs_apps/*" ifeq ($(HS_TEST),1) COV_REM_HST_UNUSED_FEAT= "*/plugins/ping/*" "*/plugins/unittest/mpcap_node.c" "*/vnet/bfd/*" \ "*/vnet/bier/*" "*/vnet/bonding/*" "*/vnet/classify/*" \ @@ -399,6 +399,8 @@ COV_REM_HST_UNUSED_FEAT= "*/plugins/ping/*" "*/plugins/unittest/mpcap_node.c" "* "*/vnet/span/*" "*/vnet/srv6/*" "*/vnet/teib/*" \ "*/vnet/tunnel/*" "*/vpp-api/vapi/*" "*/vpp/app/vpe_cli.c" \ "*/vppinfra/pcap.c" "*/vppinfra/pcap_funcs.h" +else +COV_REM_TODO_NO_TEST := $(COV_REM_TODO_NO_TEST) "*/plugins/http/http2/*" endif LCOV_VERSION=$(shell lcov --version | sed -E 's/^lcov: LCOV version ([0-9]+)[.].*/\1/') From ecf8d3e1df64ac9ff08b961e29c402b60fc7e3b7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 21 May 2025 16:49:20 +0000 Subject: [PATCH 005/313] http_static: add http1-only option to cli This option enable only HTTP/1.1 in TLS ALPN list Type: improvement Change-Id: If39e50b25b727533477a80182f96cd9876505762 Signed-off-by: Matus Fabian --- extras/hs-test/http_test.go | 21 ++++++++++++++++++++- src/plugins/http_static/http_static.h | 2 ++ src/plugins/http_static/static_server.c | 18 +++++++++++++----- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 4f3e852ed6..f0a6f07cb6 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -37,7 +37,7 @@ func init() { HttpClientGetTlsNoRespBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, - HttpClientInvalidHeaderNameTest) + HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest, HttpClientGetRepeatMTTest, HttpClientPtrGetRepeatMTTest) @@ -1167,6 +1167,25 @@ func HttpCliBadRequestTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } +func HttpStaticHttp1OnlyTest(s *NoTopoSuite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/80 url-handlers http1-only debug")) + + client := NewHttpClient(defaultHttpTimeout, true) + req, err := http.NewRequest("GET", "https://"+serverAddress+":80/version.json", nil) + s.AssertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.AssertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + s.Log(DumpHttpResp(resp, true)) + s.AssertHttpStatus(resp, 200) + s.AssertEqual(1, resp.ProtoMajor) + data, err := io.ReadAll(resp.Body) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(string(data), "version") +} + func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() diff --git a/src/plugins/http_static/http_static.h b/src/plugins/http_static/http_static.h index 2b5c065e28..2c65df0c9d 100644 --- a/src/plugins/http_static/http_static.h +++ b/src/plugins/http_static/http_static.h @@ -156,6 +156,8 @@ typedef struct hss_listener_ u32 l_index; /** Listener session handle */ session_handle_t session_handle; + /** Enable only HTTP/1.1 in TLS ALPN list */ + u8 http1_only; } hss_listener_t; /** \brief Main data structure diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index 692cb53abe..f1cfbeec1d 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -991,6 +992,8 @@ hss_listen (hss_listener_t *l, session_handle_t *lh) &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, sizeof (transport_endpt_crypto_cfg_t)); ext_cfg->crypto.ckpair_index = hsm->ckpair_index; + if (l->http1_only) + ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; } if (!(rv = vnet_listen (a))) @@ -1132,6 +1135,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE; l->rx_buff_thresh = HSS_DEFAULT_RX_BUFFER_THRESH; l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT; + l->http1_only = 0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) @@ -1177,6 +1181,8 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size, &l->use_ptr_thresh)) ; + else if (unformat (line_input, "http1-only")) + l->http1_only = 1; else { error = clib_error_return (0, "unknown input `%U'", @@ -1235,9 +1241,10 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, * @clistart * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m * @cliend - * @cliexcmd{http static server www-root [prealloc-fios ] - * [private-segment-size ] [fifo-size ] [uri ] - * [keepalive-timeout ]} + * @cliexcmd{http static server www-root [url-handlers] + * [private-segment-size ] [fifo-size ] [max-age ] + * [uri ] [ptr-thresh ] [prealloc-fifos ] [debug [nn]] + * [keepalive-timeout ] [max-body-size ] [http1-only]} ?*/ VLIB_CLI_COMMAND (hss_create_command, static) = { .path = "http static server", @@ -1245,7 +1252,7 @@ VLIB_CLI_COMMAND (hss_create_command, static) = { "http static server [www-root ] [url-handlers]\n" "[private-segment-size ] [fifo-size ] [max-age ]\n" "[uri ] [ptr-thresh ] [prealloc-fifos ] [debug [nn]]\n" - "[keepalive-timeout ] [max-body-size ]\n", + "[keepalive-timeout ] [max-body-size ] [http1-only]\n", .function = hss_create_command_fn, }; @@ -1271,6 +1278,7 @@ hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input, l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE; l->rx_buff_thresh = HSS_DEFAULT_RX_BUFFER_THRESH; l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT; + l->http1_only = 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { @@ -1355,7 +1363,7 @@ hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input, VLIB_CLI_COMMAND (hss_add_del_listener_command, static) = { .path = "http static listener", .short_help = "http static listener [add|del] uri \n" - "[www-root ] [url-handlers] \n", + "[www-root ] [url-handlers] [http1-only]\n", .function = hss_add_del_listener_command_fn, }; From 1456745fce2281be8f93020091b07b122c798489 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Mon, 26 May 2025 11:48:28 +0200 Subject: [PATCH 006/313] hs-test: fix parallel test runs - every suite has its own generated ports - registered every perf test as solo to avoid issues - fixed teardown skipping when PERSIST or DRYRUN is enabled Type: test Change-Id: Ie4b85c8000a2158d45e906949d15ae1cefb27d1b Signed-off-by: Adrian Villin --- extras/hs-test/echo_test.go | 22 +- extras/hs-test/http2_test.go | 41 +- extras/hs-test/http_test.go | 402 +++++++++--------- extras/hs-test/infra/common/suite_common.go | 4 +- extras/hs-test/infra/hst_suite.go | 15 +- extras/hs-test/infra/suite_envoy_proxy.go | 39 +- extras/hs-test/infra/suite_h2.go | 47 +- extras/hs-test/infra/suite_iperf_linux.go | 4 + extras/hs-test/infra/suite_ldp.go | 4 + extras/hs-test/infra/suite_nginx_proxy.go | 28 +- extras/hs-test/infra/suite_no_topo.go | 18 +- extras/hs-test/infra/suite_no_topo6.go | 12 +- extras/hs-test/infra/suite_veth.go | 6 + extras/hs-test/infra/suite_veth6.go | 4 + extras/hs-test/infra/suite_vpp_proxy.go | 18 +- extras/hs-test/infra/suite_vpp_udp_proxy.go | 25 +- extras/hs-test/infra/utils.go | 4 +- extras/hs-test/iperf_linux_test.go | 6 +- extras/hs-test/ldp_test.go | 9 +- extras/hs-test/nginx_test.go | 23 +- extras/hs-test/proxy_test.go | 129 +++--- extras/hs-test/raw_session_test.go | 2 +- extras/hs-test/resources/envoy/proxy.yaml | 6 +- extras/hs-test/resources/nginx/nginx.conf | 4 +- .../hs-test/resources/nginx/nginx_http3.conf | 2 +- .../nginx/nginx_proxy_mirroring.conf | 6 +- .../nginx/nginx_server_mirroring.conf | 6 +- extras/hs-test/tls_test.go | 24 +- extras/hs-test/vcl_test.go | 19 +- 29 files changed, 512 insertions(+), 417 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 3c59f95fe5..b753121f40 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -17,13 +17,13 @@ func EchoBuiltinTest(s *VethsSuite) { serverVpp := s.Containers.ServerVpp.VppInstance serverVpp.Vppctl("test echo server " + - " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/1234") + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) clientVpp := s.Containers.ClientVpp.VppInstance o := clientVpp.Vppctl("test echo client nclients 100 bytes 1 verbose" + " syn-timeout 100 test-timeout 100" + - " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/1234") + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) s.Log(o) s.AssertNotContains(o, "failed:") } @@ -33,12 +33,12 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { serverVpp := s.Containers.ServerVpp.VppInstance serverVpp.Vppctl("test echo server " + - " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/1234") + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) clientVpp := s.Containers.ClientVpp.VppInstance o := clientVpp.Vppctl("test echo client nclients 4 bytes 8m throughput 16m" + - " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/1234") + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) s.Log(o) s.AssertContains(o, "Test started") s.AssertContains(o, "Test finished") @@ -60,7 +60,7 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { func TcpWithLossTest(s *VethsSuite) { serverVpp := s.Containers.ServerVpp.VppInstance - serverVpp.Vppctl("test echo server uri tcp://%s/20022", + serverVpp.Vppctl("test echo server uri tcp://%s/"+s.Ports.Port1, s.Interfaces.Server.Ip4AddressString()) clientVpp := s.Containers.ClientVpp.VppInstance @@ -72,8 +72,8 @@ func TcpWithLossTest(s *VethsSuite) { clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Server.Name()) // Do echo test from client-vpp container - output := clientVpp.Vppctl("test echo client uri tcp://%s/20022 verbose echo-bytes bytes 50m", - s.Interfaces.Server.Ip4AddressString()) + output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes bytes 50m", + s.Interfaces.Server.Ip4AddressString(), s.Ports.Port1) s.Log(output) s.AssertNotEqual(len(output), 0) s.AssertNotContains(output, "failed", output) @@ -82,8 +82,8 @@ func TcpWithLossTest(s *VethsSuite) { func TcpWithLoss6Test(s *Veths6Suite) { serverVpp := s.Containers.ServerVpp.VppInstance - serverVpp.Vppctl("test echo server uri tcp://%s/20022", - s.Interfaces.Server.Ip6AddressString()) + serverVpp.Vppctl("test echo server uri tcp://%s/%s", + s.Interfaces.Server.Ip6AddressString(), s.Ports.Port1) clientVpp := s.Containers.ClientVpp.VppInstance @@ -94,8 +94,8 @@ func TcpWithLoss6Test(s *Veths6Suite) { clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Server.Name()) // Do echo test from client-vpp container - output := clientVpp.Vppctl("test echo client uri tcp://%s/20022 verbose echo-bytes bytes 50m", - s.Interfaces.Server.Ip6AddressString()) + output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes bytes 50m", + s.Interfaces.Server.Ip6AddressString(), s.Ports.Port1) s.Log(output) s.AssertNotEqual(len(output), 0) s.AssertNotContains(output, "failed", output) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 8bcca1611a..3ee4e9f06d 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -9,15 +9,16 @@ import ( ) func init() { - RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2MultiplexingMTTest, Http2TlsTest) + RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest) + RegisterH2SoloTests(Http2MultiplexingMTTest) } func Http2TcpGetTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + vpp.Vppctl("http cli server listener add uri tcp://" + serverAddress) s.Log(vpp.Vppctl("show session verbose 2")) - args := fmt.Sprintf("--max-time 10 --noproxy '*' --http2-prior-knowledge http://%s:80/show/version", serverAddress) + args := fmt.Sprintf("--max-time 10 --noproxy '*' --http2-prior-knowledge http://%s/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") @@ -29,7 +30,7 @@ func Http2TcpGetTest(s *H2Suite) { tcpSessionCleanupDone := false for nTries := 0; nTries < 30; nTries++ { o := vpp.Vppctl("show session verbose 2") - if !strings.Contains(o, "[T] "+serverAddress+":80->") { + if !strings.Contains(o, "[T] "+serverAddress+"->10.") { tcpSessionCleanupDone = true } if !strings.Contains(o, "[H2]") { @@ -40,31 +41,31 @@ func Http2TcpGetTest(s *H2Suite) { } time.Sleep(1 * time.Second) } - s.AssertEqual(true, tcpSessionCleanupDone, "TCP session not cleanup") - s.AssertEqual(true, httpStreamCleanupDone, "HTTP/2 stream not cleanup") + s.AssertEqual(true, tcpSessionCleanupDone, "TCP session not cleaned up") + s.AssertEqual(true, httpStreamCleanupDone, "HTTP/2 stream not cleaned up") /* test server app stop listen */ - vpp.Vppctl("http cli server listener del") + vpp.Vppctl("http cli server listener del uri tcp://" + serverAddress) o := vpp.Vppctl("show session verbose proto http") s.AssertNotContains(o, "LISTEN") } func Http2TcpPostTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers max-body-size 20m rx-buff-thresh 20m fifo-size 65k debug 2")) + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers max-body-size 20m rx-buff-thresh 20m fifo-size 65k debug 2")) s.Log(vpp.Vppctl("test-url-handler enable")) - args := fmt.Sprintf("--max-time 10 --noproxy '*' --data-binary @%s --http2-prior-knowledge http://%s:80/test3", CurlContainerTestFile, serverAddress) + args := fmt.Sprintf("--max-time 10 --noproxy '*' --data-binary @%s --http2-prior-knowledge http://%s/test3", CurlContainerTestFile, serverAddress) _, log := s.RunCurlContainer(s.Containers.Curl, args) s.AssertContains(log, "HTTP/2 200") } func Http2MultiplexingTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http tps uri tcp://0.0.0.0/80 no-zc") + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Port1 + " no-zc") - args := fmt.Sprintf("--log-file=%s -T10 -n21 -c1 -m100 http://%s:80/test_file_20M", s.H2loadLogFileName(s.Containers.H2load), serverAddress) + args := fmt.Sprintf("--log-file=%s -T10 -n21 -c1 -m100 http://%s/test_file_20M", s.H2loadLogFileName(s.Containers.H2load), serverAddress) s.Containers.H2load.ExtraRunningArgs = args s.Containers.H2load.Run() @@ -79,10 +80,10 @@ func Http2MultiplexingTest(s *H2Suite) { func Http2MultiplexingMTTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http tps uri tcp://0.0.0.0/80 no-zc") + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Port1 + " no-zc") - args := fmt.Sprintf("-T10 -n100 -c4 -r1 -m10 http://%s:80/test_file_20M", serverAddress) + args := fmt.Sprintf("-T10 -n100 -c4 -r1 -m10 http://%s/test_file_20M", serverAddress) s.Containers.H2load.ExtraRunningArgs = args s.Containers.H2load.Run() @@ -95,10 +96,10 @@ func Http2MultiplexingMTTest(s *H2Suite) { func Http2TlsTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/443 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers debug")) - args := fmt.Sprintf("--max-time 10 --noproxy '*' -k https://%s:443/version.json", serverAddress) + args := fmt.Sprintf("--max-time 10 --noproxy '*' -k https://%s/version.json", serverAddress) writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) s.Log(vpp.Vppctl("show session verbose 2")) s.AssertContains(log, "HTTP/2 200") diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index f0a6f07cb6..f85804e165 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -41,7 +41,7 @@ func init() { RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest, HttpClientGetRepeatMTTest, HttpClientPtrGetRepeatMTTest) - RegisterNoTopo6Tests(HttpClientGetResponseBody6Test, HttpClientGetTlsResponseBody6Test) + RegisterNoTopo6SoloTests(HttpClientGetResponseBody6Test, HttpClientGetTlsResponseBody6Test) } const wwwRootPath = "/tmp/www_root" @@ -71,9 +71,9 @@ func HttpGetTpsInterruptModeTest(s *NoTopoSuite) { func HttpGetTpsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":8080/test_file_10M" + url := "http://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" - vpp.Vppctl("http tps uri tcp://0.0.0.0/8080") + vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Http) s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url) } @@ -81,9 +81,9 @@ func HttpGetTpsTest(s *NoTopoSuite) { func HttpGetTpsTlsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - url := "https://" + serverAddress + ":8080/test_file_10M" + url := "https://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" - vpp.Vppctl("http tps uri tls://0.0.0.0/8080") + vpp.Vppctl("http tps uri tls://0.0.0.0/" + s.Ports.Http) s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url) } @@ -114,9 +114,9 @@ func HttpPostTpsInterruptModeTest(s *NoTopoSuite) { func HttpPostTpsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":8080/test_file_10M" + url := "http://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" - vpp.Vppctl("http tps uri tcp://0.0.0.0/8080") + vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Http) s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } @@ -124,9 +124,9 @@ func HttpPostTpsTest(s *NoTopoSuite) { func HttpPostTpsTlsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - url := "https://" + serverAddress + ":8080/test_file_10M" + url := "https://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" - vpp.Vppctl("http tps uri tls://0.0.0.0/8080") + vpp.Vppctl("http tps uri tls://0.0.0.0/" + s.Ports.Http) s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } @@ -135,8 +135,8 @@ func HttpPersistentConnectionTest(s *NoTopoSuite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) s.Log(vpp.Vppctl("test-url-handler enable")) transport := http.DefaultTransport @@ -150,7 +150,7 @@ func HttpPersistentConnectionTest(s *NoTopoSuite) { }} body := []byte("{\"sandwich\": {\"spam\": 2, \"eggs\": 1}}") - req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test3", bytes.NewBuffer(body)) + req, err := http.NewRequest("POST", "http://"+serverAddress+"/test3", bytes.NewBuffer(body)) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -162,7 +162,7 @@ func HttpPersistentConnectionTest(s *NoTopoSuite) { s.Log(o1) s.AssertContains(o1, "established") - req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test1", nil) + req, err = http.NewRequest("GET", "http://"+serverAddress+"/test1", nil) s.AssertNil(err, fmt.Sprint(err)) clientTrace := &httptrace.ClientTrace{ GotConn: func(info httptrace.GotConnInfo) { @@ -181,7 +181,7 @@ func HttpPersistentConnectionTest(s *NoTopoSuite) { s.AssertContains(o2, "established") s.AssertEqual(o1, o2) - req, err = http.NewRequest("GET", "http://"+serverAddress+":80/test2", nil) + req, err = http.NewRequest("GET", "http://"+serverAddress+"/test2", nil) s.AssertNil(err, fmt.Sprint(err)) req = req.WithContext(httptrace.WithClientTrace(req.Context(), clientTrace)) resp, err = client.Do(req) @@ -200,14 +200,14 @@ func HttpPipeliningTest(s *NoTopoSuite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) s.Log(vpp.Vppctl("test-url-handler enable")) - req1 := "GET /test_delayed HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n" - req2 := "GET /test1 HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n" + req1 := "GET /test_delayed HTTP/1.1\r\nHost:" + serverAddress + "\r\nUser-Agent:test\r\n\r\n" + req2 := "GET /test1 HTTP/1.1\r\nHost:" + serverAddress + "\r\nUser-Agent:test\r\n\r\n" - conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30) + conn, err := net.DialTimeout("tcp", serverAddress, time.Second*30) s.AssertNil(err, fmt.Sprint(err)) defer conn.Close() err = conn.SetDeadline(time.Now().Add(time.Second * 15)) @@ -235,14 +235,14 @@ func HttpStaticPostTest(s *NoTopoSuite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug max-body-size 1m")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug max-body-size 1m")) s.Log(vpp.Vppctl("test-url-handler enable")) body := make([]byte, 131072) _, err := rand.Read(body) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test3", bytes.NewBuffer(body)) + req, err := http.NewRequest("POST", "http://"+serverAddress+"/test3", bytes.NewBuffer(body)) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -253,12 +253,13 @@ func HttpStaticPostTest(s *NoTopoSuite) { } func HttpCliTest(s *VethsSuite) { - s.Containers.ServerVpp.VppInstance.Vppctl("http cli server") + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 - uri := "http://" + s.Interfaces.Server.Ip4AddressString() + "/80" + cliServerCmd := "http cli server uri http://" + serverAddress + s.Containers.ServerVpp.VppInstance.Vppctl(cliServerCmd) o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + - " uri " + uri + " query /show/vlib/graph") + " uri http://" + serverAddress + " query /show/vlib/graph") s.Log(o) s.AssertContains(o, "", " not found in the result!") @@ -268,7 +269,7 @@ func HttpCliTest(s *VethsSuite) { clientCleanupDone := false for nTries := 0; nTries < 30; nTries++ { o := s.Containers.ClientVpp.VppInstance.Vppctl("show session verbose 2") - if !strings.Contains(o, "->"+s.Interfaces.Server.Ip4AddressString()+":80") { + if !strings.Contains(o, "->"+serverAddress) { clientCleanupDone = true break } @@ -277,13 +278,13 @@ func HttpCliTest(s *VethsSuite) { s.AssertEqual(true, clientCleanupDone) /* test server app stop listen */ - s.Containers.ServerVpp.VppInstance.Vppctl("http cli server listener del") + s.Containers.ServerVpp.VppInstance.Vppctl(cliServerCmd + " listener del") o = s.Containers.ServerVpp.VppInstance.Vppctl("show session verbose proto http") s.AssertNotContains(o, "LISTEN") } func HttpCliTlsTest(s *VethsSuite) { - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + "/443" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1 s.Containers.ServerVpp.VppInstance.Vppctl("http cli server uri " + uri) @@ -312,9 +313,9 @@ func HttpCliConnectErrorTest(s *VethsSuite) { } func HttpClientTest(s *NoTopoSuite) { - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() - l, err := net.Listen("tcp", serverAddress+":80") + l, err := net.Listen("tcp", serverAddress) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( @@ -327,7 +328,7 @@ func HttpClientTest(s *NoTopoSuite) { )) server.Start() defer server.Close() - uri := "http://" + serverAddress + "/80" + uri := "http://" + serverAddress vpp := s.Containers.Vpp.VppInstance o := vpp.Vppctl("http cli client uri " + uri + " query /test") @@ -384,9 +385,9 @@ func HttpClientInvalidHeaderNameTest(s *NoTopoSuite) { } func HttpClientErrRespTest(s *NoTopoSuite) { - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() - l, err := net.Listen("tcp", serverAddress+":80") + l, err := net.Listen("tcp", serverAddress) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( @@ -397,7 +398,7 @@ func HttpClientErrRespTest(s *NoTopoSuite) { )) server.Start() defer server.Close() - uri := "http://" + serverAddress + "/80" + uri := "http://" + serverAddress vpp := s.Containers.Vpp.VppInstance o := vpp.Vppctl("http cli client uri " + uri + " query /test") @@ -406,11 +407,11 @@ func HttpClientErrRespTest(s *NoTopoSuite) { } func HttpClientPostFormTest(s *NoTopoSuite) { - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http body := "field1=value1&field2=value2" server := ghttp.NewUnstartedServer() - l, err := net.Listen("tcp", serverAddress+":80") + l, err := net.Listen("tcp", serverAddress) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( @@ -453,10 +454,9 @@ func HttpClientGetTlsNoRespBodyTest(s *NoTopoSuite) { func httpClientGet(s *NoTopoSuite, response string, size int, proto string) { var l net.Listener var err error - var port string vpp := s.Containers.Vpp.VppInstance server := ghttp.NewUnstartedServer() - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http if proto == "https" { certFile := "resources/cert/localhost.crt" @@ -465,11 +465,9 @@ func httpClientGet(s *NoTopoSuite, response string, size int, proto string) { s.AssertNil(err) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cer}} server.HTTPTestServer.TLS = tlsConfig - port = "443" - l, err = tls.Listen("tcp", serverAddress+":443", tlsConfig) + l, err = tls.Listen("tcp", serverAddress, tlsConfig) } else { - port = "80" - l, err = net.Listen("tcp", serverAddress+":80") + l, err = net.Listen("tcp", serverAddress) } s.AssertNil(err, fmt.Sprint(err)) @@ -485,7 +483,7 @@ func httpClientGet(s *NoTopoSuite, response string, size int, proto string) { server.Start() defer server.Close() - uri := proto + "://" + serverAddress + ":" + port + uri := proto + "://" + serverAddress cmd := "http client use-ptr verbose header Hello:World header Test-H2:Test-K2 save-to response.txt uri " + uri o := vpp.Vppctl(cmd) @@ -505,12 +503,14 @@ func httpClientGet(s *NoTopoSuite, response string, size int, proto string) { s.AssertContains(file_contents, response) } +// registered as a solo test and not using generated ports func HttpClientGetResponseBody6Test(s *NoTopo6Suite) { response := "hello world" size := len(response) httpClientGet6(s, response, size, "http") } +// registered as a solo test and not using generated ports func HttpClientGetTlsResponseBody6Test(s *NoTopo6Suite) { response := "hello world" size := len(response) @@ -586,7 +586,7 @@ func HttpClientPostRepeatTest(s *NoTopoSuite) { func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { vpp := s.Containers.Vpp.VppInstance logPath := s.Containers.NginxServer.GetContainerWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log" - serverAddress := s.Interfaces.Tap.Ip4AddressString() + serverAddress := s.Interfaces.Tap.Ip4AddressString() + ":" + s.Ports.NginxServer replyCountInt := 0 repeatAmount := 10000 durationInSec := 10 @@ -606,7 +606,7 @@ func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { requestMethod += " file /tmp/test_file.txt" } - uri := "http://" + serverAddress + ":" + s.GetPortFromPpid() + "/index" + uri := "http://" + serverAddress + "/index" cmd := fmt.Sprintf("http client %s %s duration %d header Hello:World uri %s", requestMethod, clientArgs, durationInSec, uri) @@ -650,11 +650,11 @@ func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { } func HttpClientGetTimeout(s *NoTopoSuite) { - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http vpp := s.Containers.Vpp.VppInstance server := ghttp.NewUnstartedServer() - l, err := net.Listen("tcp", serverAddress+":"+s.GetPortFromPpid()) + l, err := net.Listen("tcp", serverAddress) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( @@ -668,7 +668,7 @@ func HttpClientGetTimeout(s *NoTopoSuite) { )) server.Start() defer server.Close() - uri := "http://" + serverAddress + ":" + s.GetPortFromPpid() + "/timeout" + uri := "http://" + serverAddress + "/timeout" cmd := "http client verbose timeout 1 uri " + uri o := vpp.Vppctl(cmd) @@ -677,14 +677,14 @@ func HttpClientGetTimeout(s *NoTopoSuite) { } func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) { - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http vpp := s.Containers.Vpp.VppInstance fileName := "/tmp/test_file.txt" s.Log(vpp.Container.Exec(false, "fallocate -l "+strconv.Itoa(fileSize)+" "+fileName)) s.Log(vpp.Container.Exec(false, "ls -la "+fileName)) server := ghttp.NewUnstartedServer() - l, err := net.Listen("tcp", serverAddress+":80") + l, err := net.Listen("tcp", serverAddress) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( @@ -720,12 +720,12 @@ func HttpClientPostFilePtrTest(s *NoTopoSuite) { func HttpStaticPromTest(s *NoTopoSuite) { query := "stats.prom" vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 5) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/"+query, nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/"+query, nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -758,10 +758,10 @@ func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) { func PromConcurrentConnectionsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":80/stats.prom" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "http://" + serverAddress + "/stats.prom" - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 5) @@ -776,10 +776,10 @@ func PromConcurrentConnectionsTest(s *NoTopoSuite) { func PromConsecutiveConnectionsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":80/stats.prom" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "http://" + serverAddress + "/stats.prom" - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 5) @@ -792,13 +792,13 @@ func PromMemLeakTest(s *NoTopoSuite) { s.SkipUnlessLeakCheck() vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":80/stats.prom" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "http://" + serverAddress + "/stats.prom" /* no goVPP less noise */ vpp.Disconnect() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 3) @@ -832,9 +832,9 @@ func HttpClientGetMemLeakTest(s *VethsSuite) { /* no goVPP less noise */ clientVpp.Disconnect() - serverVpp.Vppctl("http cli server") + serverVpp.Vppctl("http cli server uri " + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) - uri := "http://" + s.Interfaces.Server.Ip4AddressString() + "/80" + uri := "http://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1 /* warmup request (FIB) */ clientVpp.Vppctl("http cli client uri " + uri + " query /show/version") @@ -859,17 +859,17 @@ func HttpClientGetMemLeakTest(s *VethsSuite) { func HttpClientPostMemLeakTest(s *NoTopoSuite) { s.SkipUnlessLeakCheck() - serverAddress := s.HostAddr() + serverAddress := s.HostAddr() + ":" + s.Ports.Http body := "field1=value1&field2=value2" - uri := "http://" + serverAddress + "/80" + uri := "http://" + serverAddress vpp := s.Containers.Vpp.VppInstance /* no goVPP less noise */ vpp.Disconnect() server := ghttp.NewUnstartedServer() - l, err := net.Listen("tcp", serverAddress+":80") + l, err := net.Listen("tcp", serverAddress) s.AssertNil(err, fmt.Sprint(err)) server.HTTPTestServer.Listener = l server.AppendHandlers( @@ -909,7 +909,7 @@ func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { s.SkipUnlessLeakCheck() vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() + serverAddress := s.VppAddr() + ":" + s.Ports.Http /* no goVPP less noise */ vpp.Disconnect() @@ -917,7 +917,7 @@ func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { vpp.Vppctl("http cli server") /* warmup request (FIB) */ - _, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n") + _, err := TcpSendReceive(serverAddress, "GET / HTTP/1.1\r\n") s.AssertNil(err, fmt.Sprint(err)) /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */ @@ -927,7 +927,7 @@ func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { traces1, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) - _, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n") + _, err = TcpSendReceive(serverAddress, "GET / HTTP/1.1\r\n") s.AssertNil(err, fmt.Sprint(err)) /* let's give it some time to clean up sessions */ @@ -941,9 +941,9 @@ func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { func runWrkPerf(s *NoTopoSuite) { nConnections := 1000 - serverAddress := s.VppAddr() + serverAddress := s.VppAddr() + ":" + s.Ports.Http - args := fmt.Sprintf("-c %d -t 2 -d 30s http://%s:80/64B", nConnections, serverAddress) + args := fmt.Sprintf("-c %d -t 2 -d 30s http://%s/64B", nConnections, serverAddress) s.Containers.Wrk.ExtraRunningArgs = args s.Containers.Wrk.Run() s.Log("Please wait for 30s, test is running.") @@ -954,19 +954,19 @@ func runWrkPerf(s *NoTopoSuite) { func HttpStaticFileHandlerWrkTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() + serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) content := "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" err := vpp.Container.CreateFile(wwwRootPath+"/64B", content) s.AssertNil(err, fmt.Sprint(err)) - s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 private-segment-size 256m")) + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + " private-segment-size 256m")) runWrkPerf(s) } func HttpStaticUrlHandlerWrkTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers private-segment-size 256m")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers private-segment-size 256m")) s.Log(vpp.Vppctl("test-url-handler enable")) runWrkPerf(s) } @@ -997,11 +997,11 @@ func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) { s.AssertNil(err, fmt.Sprint(err)) err = vpp.Container.CreateFile(wwwRootPath+"/page.html", content2) s.AssertNil(err, fmt.Sprint(err)) - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m " + maxAgeFormatted)) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + " debug cache-size 2m " + maxAgeFormatted)) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/index.html", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1030,7 +1030,7 @@ func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) { s.AssertHttpContentLength(resp, int64(len([]rune(content)))) s.AssertHttpBody(resp, content) - req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil) + req, err = http.NewRequest("GET", "http://"+serverAddress+"/page.html", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1055,11 +1055,11 @@ func HttpStaticPathSanitizationTest(s *NoTopoSuite) { indexContent := "index" err = vpp.Container.CreateFile(wwwRootPath+"/index.html", indexContent) s.AssertNil(err, fmt.Sprint(err)) - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + " debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/../secret_folder/secret_file.txt", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/../secret_folder/secret_file.txt", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1070,7 +1070,7 @@ func HttpStaticPathSanitizationTest(s *NoTopoSuite) { s.AssertHttpHeaderNotPresent(resp, "Cache-Control") s.AssertHttpContentLength(resp, int64(0)) - req, err = http.NewRequest("GET", "http://"+serverAddress+":80//////fake/directory///../././//../../secret_folder/secret_file.txt", nil) + req, err = http.NewRequest("GET", "http://"+serverAddress+"//////fake/directory///../././//../../secret_folder/secret_file.txt", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1081,14 +1081,14 @@ func HttpStaticPathSanitizationTest(s *NoTopoSuite) { s.AssertHttpHeaderNotPresent(resp, "Cache-Control") s.AssertHttpContentLength(resp, int64(0)) - req, err = http.NewRequest("GET", "http://"+serverAddress+":80/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////", nil) + req, err = http.NewRequest("GET", "http://"+serverAddress+"/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err = client.Do(req) s.AssertNil(err, fmt.Sprint(err)) defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 301) - s.AssertHttpHeaderWithValue(resp, "Location", "http://"+serverAddress+"/index.html") + s.AssertHttpHeaderWithValue(resp, "Location", "http://"+s.VppAddr()+"/index.html") } func HttpStaticMovedTest(s *NoTopoSuite) { @@ -1097,10 +1097,10 @@ func HttpStaticMovedTest(s *NoTopoSuite) { err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "

Hello

") s.AssertNil(err, fmt.Sprint(err)) serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/" + s.Ports.Http + " debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/tmp.aaa", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+":"+s.Ports.Http+"/tmp.aaa", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1116,11 +1116,11 @@ func HttpStaticMovedTest(s *NoTopoSuite) { func HttpStaticNotFoundTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + " debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/notfound.html", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/notfound.html", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1134,11 +1134,11 @@ func HttpStaticNotFoundTest(s *NoTopoSuite) { func HttpCliMethodNotAllowedTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("POST", "http://"+serverAddress+":80/test", nil) + req, err := http.NewRequest("POST", "http://"+serverAddress+"/test", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1152,11 +1152,11 @@ func HttpCliMethodNotAllowedTest(s *NoTopoSuite) { func HttpCliBadRequestTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress, nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1188,11 +1188,11 @@ func HttpStaticHttp1OnlyTest(s *NoTopoSuite) { func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "https://"+serverAddress+":80/version.json", nil) + req, err := http.NewRequest("GET", "https://"+serverAddress+"/version.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1213,11 +1213,11 @@ func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) { func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json?verbose=true", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/version.json?verbose=true", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1237,11 +1237,11 @@ func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) { func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/interface_list.json", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/interface_list.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1257,11 +1257,11 @@ func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) { func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/interface_stats.json", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/interface_stats.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1285,13 +1285,13 @@ func validatePostInterfaceStats(s *NoTopoSuite, data string) { func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) body := []byte(s.VppIfName()) client := NewHttpClient(defaultHttpTimeout, false) req, err := http.NewRequest("POST", - "http://"+serverAddress+":80/interface_stats.json", bytes.NewBuffer(body)) + "http://"+serverAddress+"/interface_stats.json", bytes.NewBuffer(body)) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1306,12 +1306,12 @@ func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) { func HttpStaticMacTimeTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) s.Log(vpp.Vppctl("mactime enable-disable " + s.VppIfName())) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/mactime.json", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/mactime.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1332,56 +1332,56 @@ func HttpStaticMacTimeTest(s *NoTopoSuite) { func HttpInvalidRequestLineTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) - resp, err := TcpSendReceive(serverAddress+":80", " GET / HTTP/1.1") + resp, err := TcpSendReceive(serverAddress, " GET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "\rGET / HTTP/1.1") + resp, err = TcpSendReceive(serverAddress, "\rGET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "\nGET / HTTP/1.1") + resp, err = TcpSendReceive(serverAddress, "\nGET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid request line start not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1") + resp, err = TcpSendReceive(serverAddress, "GET / HTTP/1.1") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/1.1\r\n") + resp, err = TcpSendReceive(serverAddress, "GET / HTTP/1.1\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid framing not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET /\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "HTTP-version must be present") - resp, err = TcpSendReceive(serverAddress+":80", "GET HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "request-target must be present") - resp, err = TcpSendReceive(serverAddress+":80", "GET HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "request-target must be present") - resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP/x\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET / HTTP/x\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP/x' invalid http version not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET / HTTP1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET / HTTP1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP1.1' invalid http version not allowed") } func HttpRequestLineTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) - resp, err := TcpSendReceive(serverAddress+":80", "\r\nGET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "\r\nGET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\r\nUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 200 OK") s.AssertContains(resp, "", "html content not found") @@ -1389,78 +1389,78 @@ func HttpRequestLineTest(s *NoTopoSuite) { func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) - resp, err := TcpSendReceive(serverAddress+":80", "GET /interface|stats.json HTTP/1.1\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "GET /interface|stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'|' not allowed in target path") - resp, err = TcpSendReceive(serverAddress+":80", "GET /interface#stats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface#stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'#' not allowed in target path") - resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%stats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%1stats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%1stats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%Bstats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%Bstats.json HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress+":80", "GET /interface%stats.json%B HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%stats.json%B HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose>true HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose>true HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'>' not allowed in target query") - resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose%true HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose%true HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") - resp, err = TcpSendReceive(serverAddress+":80", "GET /version.json?verbose=%1 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose=%1 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") - resp, err = TcpSendReceive(serverAddress+":80", "GET * HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET * HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "asterisk-form is only used for a server-wide OPTIONS request") - resp, err = TcpSendReceive(serverAddress+":80", "GET www.example.com:80 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET www.example.com:80 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "authority-form is only used for CONNECT requests") - resp, err = TcpSendReceive(serverAddress+":80", "CONNECT https://www.example.com/tunnel HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT https://www.example.com/tunnel HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "CONNECT requests must use authority-form only") } func HttpInvalidContentLengthTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) - resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length:\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nContent-Length:\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value must be present") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length: \r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nContent-Length: \r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value must be present") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nContent-Length: a\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nContent-Length: a\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value other than digit not allowed") @@ -1469,7 +1469,7 @@ func HttpInvalidContentLengthTest(s *NoTopoSuite) { func HttpContentLengthTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug max-body-size 12")) + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + ":80 url-handlers debug max-body-size 12")) ifName := s.VppIfName() resp, err := TcpSendReceive(serverAddress+":80", @@ -1490,10 +1490,10 @@ func HttpContentLengthTest(s *NoTopoSuite) { func HttpHeaderErrorConnectionDropTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug max-body-size 12")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug max-body-size 12")) request := "POST /interface_stats.json HTTP/1.1\r\nContent-Length: 18234234\r\n\r\n" + s.VppIfName() - conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30) + conn, err := net.DialTimeout("tcp", serverAddress, time.Second*30) s.AssertNil(err, fmt.Sprint(err)) err = conn.SetDeadline(time.Now().Add(time.Second * 10)) s.AssertNil(err, fmt.Sprint(err)) @@ -1509,11 +1509,11 @@ func HttpHeaderErrorConnectionDropTest(s *NoTopoSuite) { } func HttpMethodNotImplementedTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("OPTIONS", "http://"+serverAddress+":80/show/version", nil) + req, err := http.NewRequest("OPTIONS", "http://"+serverAddress+"/show/version", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1526,21 +1526,21 @@ func HttpMethodNotImplementedTest(s *NoTopoSuite) { func HttpVersionNotSupportedTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) - resp, err := TcpSendReceive(serverAddress+":80", "GET / HTTP/2\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "GET / HTTP/2\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 505 HTTP Version Not Supported") } func HttpUriDecodeTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/sh%6fw%20versio%6E%20verbose", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/sh%6fw%20versio%6E%20verbose", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1556,60 +1556,60 @@ func HttpUriDecodeTest(s *NoTopoSuite) { func HttpAbsoluteFormUriTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) - resp, err := TcpSendReceive(serverAddress+":80", "GET http://"+serverAddress+"/show/version HTTP/1.1\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "GET http://"+serverAddress+"/show/version HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 200 OK") - resp, err = TcpSendReceive(serverAddress+":80", "GET http://"+serverAddress+":80/show/version HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET http://"+serverAddress+":80/show/version HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 200 OK") } func HttpInvalidAuthorityFormUriTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("test proxy server fifo-size 512k server-uri http://%s/8080", serverAddress) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("test proxy server fifo-size 512k server-uri http://%s", serverAddress) - resp, err := TcpSendReceive(serverAddress+":8080", "CONNECT 1.2.3.4:80a HTTP/1.1\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "CONNECT 1.2.3.4:80a HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT 1.2.3.4:80000000 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT 1.2.3.4:80000000 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT 1.2a3.4:80 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT 1.2a3.4:80 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT 1.2.4:80 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT 1.2.4:80 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT [dead:beef::1234:443 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT [dead:beef::1234:443 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT [zyx:beef::1234]:443 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT [zyx:beef::1234]:443 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT dead:beef::1234:443 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT dead:beef::1234:443 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request") - resp, err = TcpSendReceive(serverAddress+":8080", "CONNECT example.org:443 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT example.org:443 HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "name resolution not supported") } func HttpHeadersTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) transport := http.DefaultTransport transport.(*http.Transport).Proxy = nil @@ -1619,7 +1619,7 @@ func HttpHeadersTest(s *NoTopoSuite) { Timeout: time.Second * 30, } - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/show/version", nil) s.AssertNil(err, fmt.Sprint(err)) req.Header.Add("Accept", "text/xml") req.Header.Add("Accept-Language", "*") @@ -1635,7 +1635,7 @@ func HttpHeadersTest(s *NoTopoSuite) { s.AssertNil(err, fmt.Sprint(err)) s.AssertNotContains(string(data), "", "html content received instead of plain text") - req2, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil) + req2, err := http.NewRequest("GET", "http://"+serverAddress+"/show/version", nil) s.AssertNil(err, fmt.Sprint(err)) req2.Header.Add("Accept", "text/html") resp2, err := client.Do(req2) @@ -1652,7 +1652,7 @@ func HttpHeadersTest(s *NoTopoSuite) { client.CloseIdleConnections() for nTries := 0; nTries < 10; nTries++ { o := vpp.Vppctl("show session verbose 2") - if !strings.Contains(o, serverAddress+":80->"+s.HostAddr()) { + if !strings.Contains(o, serverAddress+"->"+s.HostAddr()) { break } time.Sleep(1 * time.Second) @@ -1661,49 +1661,49 @@ func HttpHeadersTest(s *NoTopoSuite) { func HttpInvalidHeadersTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) - resp, err := TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nUser-Agent: test\r\n") + resp, err := TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nUser-Agent: test\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Header section must end with CRLF CRLF") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser@Agent:test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser@Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'@' not allowed in field name") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "incomplete field line not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\n: test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\n: test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field name not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\rUser-Agent:test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\rUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\nUser-Agent:test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\nUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") - resp, err = TcpSendReceive(serverAddress+":80", "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent: \r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent: \r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") } func HeaderServerTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - vpp.Vppctl("http cli server") + serverAddress := s.VppAddr() + ":" + s.Ports.Http + vpp.Vppctl("http cli server uri http://" + serverAddress) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/show/version", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1716,11 +1716,11 @@ func HeaderServerTest(s *NoTopoSuite) { func HttpConnTimeoutTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers debug keepalive-timeout 2")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug keepalive-timeout 2")) - req := "GET /version.json HTTP/1.1\r\nHost:" + serverAddress + ":80\r\nUser-Agent:test\r\n\r\n" - conn, err := net.DialTimeout("tcp", serverAddress+":80", time.Second*30) + req := "GET /version.json HTTP/1.1\r\nHost:" + serverAddress + "\r\nUser-Agent:test\r\n\r\n" + conn, err := net.DialTimeout("tcp", serverAddress, time.Second*30) s.AssertNil(err, fmt.Sprint(err)) defer conn.Close() err = conn.SetDeadline(time.Now().Add(time.Second * 30)) @@ -1746,8 +1746,8 @@ func HttpConnTimeoutTest(s *NoTopoSuite) { func HttpIgnoreH2UpgradeTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) transport := http.DefaultTransport transport.(*http.Transport).Proxy = nil @@ -1757,7 +1757,7 @@ func HttpIgnoreH2UpgradeTest(s *NoTopoSuite) { Timeout: time.Second * 30, } - req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/version.json", nil) s.AssertNil(err, fmt.Sprint(err)) req.Header.Add("Connection", "Upgrade") req.Header.Add("Upgrade", "HTTP/2.0") diff --git a/extras/hs-test/infra/common/suite_common.go b/extras/hs-test/infra/common/suite_common.go index 72173c6261..163faa9611 100644 --- a/extras/hs-test/infra/common/suite_common.go +++ b/extras/hs-test/infra/common/suite_common.go @@ -59,14 +59,14 @@ func (s *HstCommon) SetupSuite() { func (s *HstCommon) TeardownTest() { if *IsPersistent || *DryRun { - return + s.Skip("Skipping test teardown") } s.Log("[* TEST TEARDOWN]") } func (s *HstCommon) TeardownSuite() { if *IsPersistent || *DryRun { - return + s.Skip("Skipping suite teardown") } s.Log("[* SUITE TEARDOWN]") } diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index 03eaff58d8..2fb05a48d8 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -50,6 +50,7 @@ type HstSuite struct { CpuCount int Docker *client.Client CoverageRun bool + numOfNewPorts int } type colors struct { @@ -181,6 +182,8 @@ func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) { func (s *HstSuite) TeardownSuite() { s.HstCommon.TeardownSuite() + // allow ports to be reused by removing them from reservedPorts slice + reservedPorts = reservedPorts[:len(reservedPorts)-s.numOfNewPorts] defer s.LogFile.Close() defer s.Docker.Close() s.UnconfigureNetworkTopology() @@ -584,8 +587,8 @@ func (s *HstSuite) GetCurrentSuiteName() string { } // Returns last 3 digits of PID + Ginkgo process index as the 4th digit. If the port is in the 'reservedPorts' slice, -// increment port number by ten and check again. -func (s *HstSuite) GetPortFromPpid() string { +// increment port number by ten and check again. Generates a new port after each use. +func (s *HstSuite) GeneratePort() string { port := s.Ppid var err error var portInt int @@ -599,9 +602,17 @@ func (s *HstSuite) GetPortFromPpid() string { portInt += 10 port = fmt.Sprintf("%d", portInt) } + reservedPorts = append(reservedPorts, port) + s.numOfNewPorts++ return port } +func (s *HstSuite) GeneratePortAsInt() uint16 { + port, err := strconv.Atoi(s.GeneratePort()) + s.AssertNil(err) + return uint16(port) +} + /* RunBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times), passing in suite context, experiment and your data. diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go index 79d9372588..7119b0ff1d 100644 --- a/extras/hs-test/infra/suite_envoy_proxy.go +++ b/extras/hs-test/infra/suite_envoy_proxy.go @@ -18,8 +18,6 @@ import ( type EnvoyProxySuite struct { HstSuite - nginxPort uint16 - proxyPort uint16 maxTimeout int Interfaces struct { Server *NetInterface @@ -31,6 +29,11 @@ type EnvoyProxySuite struct { Vpp *Container Curl *Container } + Ports struct { + Nginx uint16 + Proxy uint16 + EnvoyAdmin uint16 + } } var envoyProxyTests = map[string][]func(s *EnvoyProxySuite){} @@ -60,6 +63,8 @@ func (s *EnvoyProxySuite) SetupSuite() { s.Containers.Vpp = s.GetContainerByName("vpp") s.Containers.EnvoyProxy = s.GetContainerByName("envoy-vcl") s.Containers.Curl = s.GetContainerByName("curl") + s.Ports.Nginx = s.GeneratePortAsInt() + s.Ports.Proxy = s.GeneratePortAsInt() } func (s *EnvoyProxySuite) SetupTest() { @@ -79,7 +84,6 @@ func (s *EnvoyProxySuite) SetupTest() { // nginx HTTP server s.AssertNil(s.Containers.NginxServerTransient.Create()) - s.nginxPort = 80 nginxSettings := struct { LogPrefix string Address string @@ -88,7 +92,7 @@ func (s *EnvoyProxySuite) SetupTest() { }{ LogPrefix: s.Containers.NginxServerTransient.Name, Address: s.Interfaces.Server.Ip4AddressString(), - Port: s.nginxPort, + Port: s.Ports.Nginx, Timeout: s.maxTimeout, } s.Containers.NginxServerTransient.CreateConfigFromTemplate( @@ -100,17 +104,20 @@ func (s *EnvoyProxySuite) SetupTest() { // Envoy s.AssertNil(s.Containers.EnvoyProxy.Create()) - s.proxyPort = 8080 envoySettings := struct { - LogPrefix string - ServerAddress string - ServerPort uint16 - ProxyPort uint16 + LogPrefix string + ServerAddress string + ServerPort uint16 + ProxyPort uint16 + ProxyAddr string + EnvoyAdminPort uint16 }{ - LogPrefix: s.Containers.EnvoyProxy.Name, - ServerAddress: s.Interfaces.Server.Ip4AddressString(), - ServerPort: s.nginxPort, - ProxyPort: s.proxyPort, + LogPrefix: s.Containers.EnvoyProxy.Name, + ServerAddress: s.Interfaces.Server.Ip4AddressString(), + ServerPort: s.Ports.Nginx, + ProxyPort: s.Ports.Proxy, + ProxyAddr: s.ProxyAddr(), + EnvoyAdminPort: s.Ports.EnvoyAdmin, } s.Containers.EnvoyProxy.CreateConfigFromTemplate( "/etc/envoy/envoy.yaml", @@ -134,7 +141,7 @@ func (s *EnvoyProxySuite) SetupTest() { if *DryRun { vpp.AppendToCliConfig(arp) s.LogStartedContainers() - s.Log("%s* Proxy IP used in tests: %s:%d%s", Colors.pur, s.ProxyAddr(), s.ProxyPort(), Colors.rst) + s.Log("%s* Proxy IP used in tests: %s:%d%s", Colors.pur, s.ProxyAddr(), s.Ports.Proxy, Colors.rst) s.Skip("Dry run mode = true") } @@ -151,10 +158,6 @@ func (s *EnvoyProxySuite) TeardownTest() { s.HstSuite.TeardownTest() } -func (s *EnvoyProxySuite) ProxyPort() uint16 { - return s.proxyPort -} - func (s *EnvoyProxySuite) ProxyAddr() string { return s.Interfaces.Client.Peer.Ip4AddressString() } diff --git a/extras/hs-test/infra/suite_h2.go b/extras/hs-test/infra/suite_h2.go index 04919ad784..36fc29493c 100644 --- a/extras/hs-test/infra/suite_h2.go +++ b/extras/hs-test/infra/suite_h2.go @@ -6,6 +6,7 @@ import ( "os" "reflect" "runtime" + "strconv" "strings" "time" @@ -20,6 +21,7 @@ import ( ) var h2Tests = map[string][]func(s *H2Suite){} +var h2SoloTests = map[string][]func(s *H2Suite){} type H2Suite struct { HstSuite @@ -31,11 +33,18 @@ type H2Suite struct { Curl *Container H2load *Container } + Ports struct { + Port1 string + Port1AsInt int + } } func RegisterH2Tests(tests ...func(s *H2Suite)) { h2Tests[GetTestFilename()] = tests } +func RegisterH2SoloTests(tests ...func(s *H2Suite)) { + h2SoloTests[GetTestFilename()] = tests +} func (s *H2Suite) SetupSuite() { s.HstSuite.SetupSuite() @@ -45,6 +54,10 @@ func (s *H2Suite) SetupSuite() { s.Containers.Vpp = s.GetContainerByName("vpp") s.Containers.Curl = s.GetContainerByName("curl") s.Containers.H2load = s.GetContainerByName("h2load") + s.Ports.Port1 = s.GeneratePort() + var err error + s.Ports.Port1AsInt, err = strconv.Atoi(s.Ports.Port1) + s.AssertNil(err) } func (s *H2Suite) SetupTest() { @@ -105,6 +118,36 @@ var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { } }) +// Marked as pending since http plugin is not build with http/2 enabled by default +var _ = Describe("Http2SoloSuite", Ordered, ContinueOnFailure, Serial, func() { + var s H2Suite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range h2SoloTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) + type h2specTest struct { desc string } @@ -320,11 +363,11 @@ var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { s.Log(testName + ": BEGIN") vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/443 url-handlers debug 2")) + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/" + s.Ports.Port1 + " url-handlers debug 2")) s.Log(vpp.Vppctl("test-url-handler enable")) conf := &config.Config{ Host: serverAddress, - Port: 443, + Port: s.Ports.Port1AsInt, Path: "/test1", Timeout: time.Second * 5, MaxHeaderLen: 1024, diff --git a/extras/hs-test/infra/suite_iperf_linux.go b/extras/hs-test/infra/suite_iperf_linux.go index 10cedf7337..be41e5082a 100644 --- a/extras/hs-test/infra/suite_iperf_linux.go +++ b/extras/hs-test/infra/suite_iperf_linux.go @@ -20,6 +20,9 @@ type IperfSuite struct { Server *Container Client *Container } + Ports struct { + Port1 string + } } var iperfTests = map[string][]func(s *IperfSuite){} @@ -41,6 +44,7 @@ func (s *IperfSuite) SetupSuite() { s.Interfaces.Server = s.GetInterfaceByName("hstsrv") s.Containers.Server = s.GetContainerByName("server") s.Containers.Client = s.GetContainerByName("client") + s.Ports.Port1 = s.GeneratePort() } var _ = Describe("IperfSuite", Ordered, ContinueOnFailure, func() { diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index bdc718e897..1127f13fbc 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -26,6 +26,9 @@ type LdpSuite struct { ServerApp *Container ClientApp *Container } + Ports struct { + Port1 string + } } func RegisterLdpTests(tests ...func(s *LdpSuite)) { @@ -46,6 +49,7 @@ func (s *LdpSuite) SetupSuite() { s.Containers.ClientVpp = s.GetContainerByName("client-vpp") s.Containers.ServerApp = s.GetContainerByName("server-app") s.Containers.ClientApp = s.GetContainerByName("client-app") + s.Ports.Port1 = s.GeneratePort() } func (s *LdpSuite) SetupTest() { diff --git a/extras/hs-test/infra/suite_nginx_proxy.go b/extras/hs-test/infra/suite_nginx_proxy.go index a259db36b5..8006e6fd4d 100644 --- a/extras/hs-test/infra/suite_nginx_proxy.go +++ b/extras/hs-test/infra/suite_nginx_proxy.go @@ -15,7 +15,6 @@ var nginxProxySoloTests = map[string][]func(s *NginxProxySuite){} type NginxProxySuite struct { HstSuite - proxyPort uint16 maxTimeout int Interfaces struct { Server *NetInterface @@ -27,6 +26,12 @@ type NginxProxySuite struct { Vpp *Container Curl *Container } + Ports struct { + Proxy uint16 + Upstream1 string + Upstream2 string + Upstream3 string + } } func RegisterNginxProxyTests(tests ...func(s *NginxProxySuite)) { @@ -52,6 +57,10 @@ func (s *NginxProxySuite) SetupSuite() { s.Containers.NginxServerTransient = s.GetTransientContainerByName("nginx-server") s.Containers.Vpp = s.GetContainerByName("vpp") s.Containers.Curl = s.GetContainerByName("curl") + s.Ports.Proxy = s.GeneratePortAsInt() + s.Ports.Upstream1 = s.GeneratePort() + s.Ports.Upstream2 = s.GeneratePort() + s.Ports.Upstream3 = s.GeneratePort() } func (s *NginxProxySuite) SetupTest() { @@ -69,7 +78,6 @@ func (s *NginxProxySuite) SetupTest() { // nginx proxy s.AssertNil(s.Containers.NginxProxy.Create()) - s.proxyPort = 80 // nginx HTTP server s.AssertNil(s.Containers.NginxServerTransient.Create()) @@ -77,10 +85,16 @@ func (s *NginxProxySuite) SetupTest() { LogPrefix string Address string Timeout int + Upstream1 string + Upstream2 string + Upstream3 string }{ LogPrefix: s.Containers.NginxServerTransient.Name, Address: s.Interfaces.Server.Ip4AddressString(), Timeout: s.maxTimeout, + Upstream1: s.Ports.Upstream1, + Upstream2: s.Ports.Upstream2, + Upstream3: s.Ports.Upstream3, } s.Containers.NginxServerTransient.CreateConfigFromTemplate( "/nginx.conf", @@ -123,12 +137,18 @@ func (s *NginxProxySuite) CreateNginxProxyConfig(container *Container, multiThre Proxy string Server string Port uint16 + Upstream1 string + Upstream2 string + Upstream3 string }{ Workers: workers, LogPrefix: container.Name, Proxy: s.Interfaces.Client.Peer.Ip4AddressString(), Server: s.Interfaces.Server.Ip4AddressString(), - Port: s.proxyPort, + Port: s.Ports.Proxy, + Upstream1: s.Ports.Upstream1, + Upstream2: s.Ports.Upstream2, + Upstream3: s.Ports.Upstream3, } container.CreateConfigFromTemplate( "/nginx.conf", @@ -138,7 +158,7 @@ func (s *NginxProxySuite) CreateNginxProxyConfig(container *Container, multiThre } func (s *NginxProxySuite) ProxyPort() uint16 { - return s.proxyPort + return s.Ports.Proxy } func (s *NginxProxySuite) ProxyAddr() string { diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go index dd77c157e6..2d96ac9cd2 100644 --- a/extras/hs-test/infra/suite_no_topo.go +++ b/extras/hs-test/infra/suite_no_topo.go @@ -27,7 +27,11 @@ type NoTopoSuite struct { Curl *Container Ab *Container } - NginxServerPort string + Ports struct { + NginxServer string + NginxHttp3 string + Http string + } } func RegisterNoTopoTests(tests ...func(s *NoTopoSuite)) { @@ -49,6 +53,9 @@ func (s *NoTopoSuite) SetupSuite() { s.Containers.Wrk = s.GetContainerByName("wrk") s.Containers.Curl = s.GetContainerByName("curl") s.Containers.Ab = s.GetContainerByName("ab") + s.Ports.Http = s.GeneratePort() + s.Ports.NginxServer = s.GeneratePort() + s.Ports.NginxHttp3 = s.GeneratePort() } func (s *NoTopoSuite) SetupTest() { @@ -95,8 +102,10 @@ func (s *NoTopoSuite) CreateNginxConfig(container *Container, multiThreadWorkers } values := struct { Workers uint8 + Port string }{ Workers: workers, + Port: s.Ports.NginxServer, } container.CreateConfigFromTemplate( "/nginx.conf", @@ -108,7 +117,6 @@ func (s *NoTopoSuite) CreateNginxConfig(container *Container, multiThreadWorkers // Creates container and config. func (s *NoTopoSuite) CreateNginxServer() { s.AssertNil(s.Containers.NginxServer.Create()) - s.NginxServerPort = s.GetPortFromPpid() nginxSettings := struct { LogPrefix string Address string @@ -117,7 +125,7 @@ func (s *NoTopoSuite) CreateNginxServer() { }{ LogPrefix: s.Containers.NginxServer.Name, Address: s.Interfaces.Tap.Ip4AddressString(), - Port: s.NginxServerPort, + Port: s.Ports.NginxServer, Timeout: 600, } s.Containers.NginxServer.CreateConfigFromTemplate( @@ -166,8 +174,12 @@ func (s *NoTopoSuite) HostAddr() string { func (s *NoTopoSuite) CreateNginxHttp3Config(container *Container) { nginxSettings := struct { LogPrefix string + Address string + Port string }{ LogPrefix: container.Name, + Address: s.VppAddr(), + Port: s.Ports.NginxHttp3, } container.CreateConfigFromTemplate( "/nginx.conf", diff --git a/extras/hs-test/infra/suite_no_topo6.go b/extras/hs-test/infra/suite_no_topo6.go index ac617c8fcf..8b67791597 100644 --- a/extras/hs-test/infra/suite_no_topo6.go +++ b/extras/hs-test/infra/suite_no_topo6.go @@ -27,7 +27,10 @@ type NoTopo6Suite struct { Curl *Container Ab *Container } - NginxServerPort string + Ports struct { + NginxServer string + Http string + } } func RegisterNoTopo6Tests(tests ...func(s *NoTopo6Suite)) { @@ -49,6 +52,8 @@ func (s *NoTopo6Suite) SetupSuite() { s.Containers.Wrk = s.GetContainerByName("wrk") s.Containers.Curl = s.GetContainerByName("curl") s.Containers.Ab = s.GetContainerByName("ab") + s.Ports.Http = s.GeneratePort() + s.Ports.NginxServer = s.GeneratePort() } func (s *NoTopo6Suite) SetupTest() { @@ -95,8 +100,10 @@ func (s *NoTopo6Suite) CreateNginxConfig(container *Container, multiThreadWorker } values := struct { Workers uint8 + Port string }{ Workers: workers, + Port: s.Ports.NginxServer, } container.CreateConfigFromTemplate( "/nginx.conf", @@ -108,7 +115,6 @@ func (s *NoTopo6Suite) CreateNginxConfig(container *Container, multiThreadWorker // Creates container and config. func (s *NoTopo6Suite) CreateNginxServer() { s.AssertNil(s.Containers.NginxServer.Create()) - s.NginxServerPort = s.GetPortFromPpid() nginxSettings := struct { LogPrefix string Address string @@ -117,7 +123,7 @@ func (s *NoTopo6Suite) CreateNginxServer() { }{ LogPrefix: s.Containers.NginxServer.Name, Address: "[" + s.Interfaces.Tap.Ip6AddressString() + "]", - Port: s.NginxServerPort, + Port: s.Ports.NginxServer, Timeout: 600, } s.Containers.NginxServer.CreateConfigFromTemplate( diff --git a/extras/hs-test/infra/suite_veth.go b/extras/hs-test/infra/suite_veth.go index 4dc6b5993b..2b7832e288 100644 --- a/extras/hs-test/infra/suite_veth.go +++ b/extras/hs-test/infra/suite_veth.go @@ -26,6 +26,10 @@ type VethsSuite struct { ServerApp *Container ClientApp *Container } + Ports struct { + Port1 string + Port2 string + } } func RegisterVethTests(tests ...func(s *VethsSuite)) { @@ -46,6 +50,8 @@ func (s *VethsSuite) SetupSuite() { s.Containers.ClientVpp = s.GetContainerByName("client-vpp") s.Containers.ServerApp = s.GetContainerByName("server-app") s.Containers.ClientApp = s.GetContainerByName("client-app") + s.Ports.Port1 = s.GeneratePort() + s.Ports.Port2 = s.GeneratePort() } func (s *VethsSuite) SetupTest() { diff --git a/extras/hs-test/infra/suite_veth6.go b/extras/hs-test/infra/suite_veth6.go index 694972c406..2e3fe98c3b 100644 --- a/extras/hs-test/infra/suite_veth6.go +++ b/extras/hs-test/infra/suite_veth6.go @@ -26,6 +26,9 @@ type Veths6Suite struct { ServerApp *Container ClientApp *Container } + Ports struct { + Port1 string + } } func RegisterVeth6Tests(tests ...func(s *Veths6Suite)) { @@ -46,6 +49,7 @@ func (s *Veths6Suite) SetupSuite() { s.Containers.ClientVpp = s.GetContainerByName("client-vpp") s.Containers.ServerApp = s.GetContainerByName("server-app") s.Containers.ClientApp = s.GetContainerByName("client-app") + s.Ports.Port1 = s.GeneratePort() } func (s *Veths6Suite) SetupTest() { diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index b0881993d8..ec08a630d2 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -23,7 +23,6 @@ const ( type VppProxySuite struct { HstSuite - serverPort uint16 maxTimeout int Interfaces struct { Client *NetInterface @@ -36,6 +35,10 @@ type VppProxySuite struct { IperfS *Container IperfC *Container } + Ports struct { + Server uint16 + Proxy uint16 + } } var vppProxyTests = map[string][]func(s *VppProxySuite){} @@ -53,8 +56,9 @@ func (s *VppProxySuite) SetupSuite() { s.HstSuite.SetupSuite() s.LoadNetworkTopology("2taps") s.LoadContainerTopology("vppProxy") + s.Ports.Server = s.GeneratePortAsInt() + s.Ports.Proxy = s.GeneratePortAsInt() - s.serverPort = 80 if *IsVppDebug { s.maxTimeout = 600 } else { @@ -109,7 +113,7 @@ func (s *VppProxySuite) SetupNginxServer() { }{ LogPrefix: s.Containers.NginxServerTransient.Name, Address: s.Interfaces.Server.Ip4AddressString(), - Port: s.serverPort, + Port: s.Ports.Server, Timeout: s.maxTimeout, } s.Containers.NginxServerTransient.CreateConfigFromTemplate( @@ -120,10 +124,6 @@ func (s *VppProxySuite) SetupNginxServer() { s.AssertNil(s.Containers.NginxServerTransient.Start()) } -func (s *VppProxySuite) ServerPort() uint16 { - return s.serverPort -} - func (s *VppProxySuite) ServerAddr() string { return s.Interfaces.Server.Ip4AddressString() } @@ -198,7 +198,7 @@ func handleConn(conn net.Conn) { } func (s *VppProxySuite) StartEchoServer() *net.TCPListener { - listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP(s.ServerAddr()), Port: int(s.ServerPort())}) + listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP(s.ServerAddr()), Port: int(s.Ports.Server)}) s.AssertNil(err, fmt.Sprint(err)) go func() { for { @@ -209,7 +209,7 @@ func (s *VppProxySuite) StartEchoServer() *net.TCPListener { go handleConn(conn) } }() - s.Log("* started tcp echo server " + s.ServerAddr() + ":" + strconv.Itoa(int(s.ServerPort()))) + s.Log("* started tcp echo server " + s.ServerAddr() + ":" + strconv.Itoa(int(s.Ports.Server))) return listener } diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index b215775708..d6f58ac86c 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -15,8 +15,6 @@ import ( type VppUdpProxySuite struct { HstSuite - proxyPort int - serverPort int MaxTimeout time.Duration Interfaces struct { Client *NetInterface @@ -25,6 +23,10 @@ type VppUdpProxySuite struct { Containers struct { VppProxy *Container } + Ports struct { + Proxy int + Server int + } } var vppUdpProxyTests = map[string][]func(s *VppUdpProxySuite){} @@ -45,6 +47,8 @@ func (s *VppUdpProxySuite) SetupSuite() { s.Interfaces.Client = s.GetInterfaceByName("hstcln") s.Interfaces.Server = s.GetInterfaceByName("hstsrv") s.Containers.VppProxy = s.GetContainerByName("vpp") + s.Ports.Proxy = int(s.GeneratePortAsInt()) + s.Ports.Server = int(s.GeneratePortAsInt()) if *IsVppDebug { s.MaxTimeout = time.Second * 600 @@ -66,9 +70,6 @@ func (s *VppUdpProxySuite) SetupTest() { s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1, 1)) s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 1, 2)) - s.proxyPort = 8080 - s.serverPort = 80 - arp := fmt.Sprintf("set ip neighbor %s %s %s", s.Interfaces.Server.Peer.Name(), s.Interfaces.Server.Ip4AddressString(), @@ -99,24 +100,16 @@ func (s *VppUdpProxySuite) VppProxyAddr() string { return s.Interfaces.Client.Peer.Ip4AddressString() } -func (s *VppUdpProxySuite) ProxyPort() int { - return s.proxyPort -} - func (s *VppUdpProxySuite) ServerAddr() string { return s.Interfaces.Server.Ip4AddressString() } -func (s *VppUdpProxySuite) ServerPort() int { - return s.serverPort -} - func (s *VppUdpProxySuite) ClientAddr() string { return s.Interfaces.Client.Ip4AddressString() } func (s *VppUdpProxySuite) StartEchoServer() *net.UDPConn { - conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(s.ServerAddr()), Port: s.ServerPort()}) + conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(s.ServerAddr()), Port: s.Ports.Server}) s.AssertNil(err, fmt.Sprint(err)) go func() { for { @@ -130,14 +123,14 @@ func (s *VppUdpProxySuite) StartEchoServer() *net.UDPConn { } } }() - s.Log("* started udp echo server " + s.ServerAddr() + ":" + strconv.Itoa(s.ServerPort())) + s.Log("* started udp echo server " + s.ServerAddr() + ":" + strconv.Itoa(s.Ports.Server)) return conn } func (s *VppUdpProxySuite) ClientSendReceive(toSend []byte, rcvBuffer []byte) (int, error) { proxiedConn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(s.ClientAddr()), Port: 0}, - &net.UDPAddr{IP: net.ParseIP(s.VppProxyAddr()), Port: s.ProxyPort()}) + &net.UDPAddr{IP: net.ParseIP(s.VppProxyAddr()), Port: s.Ports.Proxy}) if err != nil { return 0, err } diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index 5285ba8b58..a4dec989e9 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -235,7 +235,7 @@ func (s *HstSuite) CollectH2loadLogs(h2loadContainer *Container) { } func (s *HstSuite) StartIperfServerApp(running chan error, done chan struct{}, env []string) { - cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GetPortFromPpid()) + cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GeneratePort()) if env != nil { cmd.Env = env } @@ -259,7 +259,7 @@ func (s *HstSuite) StartIperfClientApp(ipAddress string, env []string, clnCh cha nTries := 0 for { - cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GetPortFromPpid()) + cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GeneratePort()) if env != nil { cmd.Env = env } diff --git a/extras/hs-test/iperf_linux_test.go b/extras/hs-test/iperf_linux_test.go index 2723572732..0505f644f3 100644 --- a/extras/hs-test/iperf_linux_test.go +++ b/extras/hs-test/iperf_linux_test.go @@ -9,7 +9,7 @@ import ( ) func init() { - RegisterIperfTests(IperfUdpLinuxTest) + RegisterIperfSoloTests(IperfUdpLinuxTest) } func IperfUdpLinuxTest(s *IperfSuite) { @@ -27,7 +27,7 @@ func IperfUdpLinuxTest(s *IperfSuite) { go func() { defer GinkgoRecover() - cmd := "iperf3 -4 -s -B " + serverIpAddress + " -p " + s.GetPortFromPpid() + cmd := "iperf3 -4 -s -B " + serverIpAddress + " -p " + s.Ports.Port1 s.StartServerApp(s.Containers.Server, "iperf3", cmd, srvCh, stopServerCh) }() err := <-srvCh @@ -37,7 +37,7 @@ func IperfUdpLinuxTest(s *IperfSuite) { go func() { defer GinkgoRecover() cmd := "iperf3 -c " + serverIpAddress + " -B " + clientIpAddress + - " -u -l 1460 -b 10g -J -p " + s.GetPortFromPpid() + " -u -l 1460 -b 10g -J -p " + s.Ports.Port1 s.StartClientApp(s.Containers.Client, cmd, clnCh, clnRes) }() s.AssertChannelClosed(time.Minute*3, clnCh) diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 0943a16d0e..b42edabc64 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -11,8 +11,9 @@ import ( ) func init() { - RegisterLdpTests(LdpIperfUdpTest, LdpIperfUdpVppInterruptModeTest, RedisBenchmarkTest, LdpIperfTlsTcpTest, LdpIperfTcpTest, LdpIperfTcpReorderTest, - LdpIperfReverseTcpReorderTest, LdpIperfUdpReorderTest, LdpIperfReverseUdpReorderTest) + RegisterSoloLdpTests(LdpIperfUdpTest, LdpIperfUdpVppInterruptModeTest, RedisBenchmarkTest, + LdpIperfTlsTcpTest, LdpIperfTcpTest, LdpIperfTcpReorderTest, LdpIperfReverseTcpReorderTest, + LdpIperfUdpReorderTest, LdpIperfReverseUdpReorderTest) } func LdpIperfUdpVppInterruptModeTest(s *LdpSuite) { @@ -96,7 +97,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() - cmd := "iperf3 -4 -s -p " + s.GetPortFromPpid() + " --logfile " + s.IperfLogFileName(s.Containers.ServerVpp) + cmd := "iperf3 -4 -s -p " + s.Ports.Port1 + " --logfile " + s.IperfLogFileName(s.Containers.ServerVpp) s.StartServerApp(s.Containers.ServerVpp, "iperf3", cmd, srvCh, stopServerCh) }() @@ -105,7 +106,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() - cmd := "iperf3 -c " + serverVethAddress + " -l 1460 -b 10g -J -p " + s.GetPortFromPpid() + " " + extraClientArgs + cmd := "iperf3 -c " + serverVethAddress + " -l 1460 -b 10g -J -p " + s.Ports.Port1 + " " + extraClientArgs s.StartClientApp(s.Containers.ClientVpp, cmd, clnCh, clnRes) }() diff --git a/extras/hs-test/nginx_test.go b/extras/hs-test/nginx_test.go index f5f10cc49d..6a219e9568 100644 --- a/extras/hs-test/nginx_test.go +++ b/extras/hs-test/nginx_test.go @@ -10,10 +10,10 @@ import ( ) func init() { - RegisterNoTopoTests(NginxHttp3Test, NginxAsServerTest, NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest, + RegisterNoTopoTests(NginxHttp3Test, NginxAsServerTest) + RegisterNoTopoSoloTests(NginxPerfRpsMultiThreadTest, NginxPerfCpsMultiThreadTest, NginxPerfCpsTest, NginxPerfRpsTest, NginxPerfWrkTest, NginxPerfCpsInterruptModeTest, NginxPerfRpsInterruptModeTest, NginxPerfWrkInterruptModeTest) - RegisterNoTopoSoloTests(NginxPerfRpsMultiThreadTest, NginxPerfCpsMultiThreadTest) - RegisterNoTopo6Tests(NginxPerfRps6Test) + RegisterNoTopo6SoloTests(NginxPerfRps6Test) } func NginxHttp3Test(s *NoTopoSuite) { @@ -28,7 +28,8 @@ func NginxHttp3Test(s *NoTopoSuite) { serverAddress := s.VppAddr() defer func() { os.Remove(query) }() - args := fmt.Sprintf("curl --noproxy '*' --local-port 55444 --http3-only -k https://%s:8443/%s", serverAddress, query) + args := fmt.Sprintf("curl --noproxy '*' --http3-only -k https://%s:%s/%s", + serverAddress, s.Ports.NginxHttp3, query) s.Containers.Curl.ExtraRunningArgs = args s.Containers.Curl.Run() body, stats := s.Containers.Curl.GetOutput() @@ -56,7 +57,7 @@ func NginxAsServerTest(s *NoTopoSuite) { defer func() { os.Remove(query) }() go func() { defer GinkgoRecover() - s.StartWget(finished, serverAddress, "80", query, "") + s.StartWget(finished, serverAddress, s.Ports.NginxServer, query, "") }() s.AssertNil(<-finished) } @@ -75,7 +76,7 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string, multiThreadWorkers boo nRequests := 1000000 nClients := 1000 - serverAddress := s.VppAddr() + serverAddress := s.VppAddr() + ":" + s.Ports.NginxServer vpp := s.Containers.Vpp.VppInstance @@ -94,7 +95,7 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string, multiThreadWorkers boo } // don't exit on socket receive errors args += " -r" - args += " http://" + serverAddress + ":80/64B.json" + args += " http://" + serverAddress + "/64B.json" s.Containers.Ab.ExtraRunningArgs = args s.Log("Test might take up to 2 minutes to finish. Please wait") s.Containers.Ab.Run() @@ -103,7 +104,7 @@ func runNginxPerf(s *NoTopoSuite, mode, ab_or_wrk string, multiThreadWorkers boo s.Log(rps) s.AssertContains(err, "Finished "+fmt.Sprint(nRequests)) } else { - args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s:80/64B.json", nClients, + args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s/64B.json", nClients, serverAddress) s.Containers.Wrk.ExtraRunningArgs = args s.Containers.Wrk.Run() @@ -153,7 +154,7 @@ func runNginxPerf6(s *NoTopo6Suite, mode, ab_or_wrk string, multiThreadWorkers b nRequests := 1000000 nClients := 1000 - serverAddress := "[" + s.VppAddr() + "]" + serverAddress := "[" + s.VppAddr() + "]:" + s.Ports.NginxServer vpp := s.Containers.Vpp.VppInstance s.Containers.Nginx.Create() @@ -171,7 +172,7 @@ func runNginxPerf6(s *NoTopo6Suite, mode, ab_or_wrk string, multiThreadWorkers b } // don't exit on socket receive errors args += " -r" - args += " http://" + serverAddress + ":80/64B.json" + args += " http://" + serverAddress + "/64B.json" s.Containers.Ab.ExtraRunningArgs = args s.Log("Test might take up to 2 minutes to finish. Please wait") s.Containers.Ab.Run() @@ -180,7 +181,7 @@ func runNginxPerf6(s *NoTopo6Suite, mode, ab_or_wrk string, multiThreadWorkers b s.Log(rps) s.AssertContains(err, "Finished "+fmt.Sprint(nRequests)) } else { - args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s:80/64B.json", nClients, + args := fmt.Sprintf("-c %d -t 2 -d 30 http://%s/64B.json", nClients, serverAddress) s.Containers.Wrk.ExtraRunningArgs = args s.Containers.Wrk.Run() diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index e12b38e408..0e82bf3117 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -29,8 +29,7 @@ func init() { VppConnectUdpUnknownCapsuleTest, VppConnectUdpClientCloseTest, VppConnectUdpInvalidTargetTest) RegisterVppUdpProxySoloTests(VppProxyUdpMigrationMTTest, VppConnectUdpStressMTTest, VppConnectUdpStressTest) RegisterEnvoyProxyTests(EnvoyProxyHttpGetTcpTest, EnvoyProxyHttpPutTcpTest) - RegisterNginxProxyTests(NginxMirroringTest) - RegisterNginxProxySoloTests(MirrorMultiThreadTest) + RegisterNginxProxySoloTests(NginxMirroringTest, MirrorMultiThreadTest) } func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) { @@ -40,7 +39,7 @@ func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) { proto = "tcp" } if proto != "http" { - cmd += fmt.Sprintf(" client-uri %s://%s/%d", proto, s.ServerAddr(), s.ServerPort()) + cmd += fmt.Sprintf(" client-uri %s://%s/%d", proto, s.ServerAddr(), s.Ports.Server) } output := vppProxy.Vppctl(cmd) @@ -63,8 +62,6 @@ func vppProxyIperfMTTest(s *VppProxySuite, proto string) { s.Containers.IperfC.Run() s.Containers.IperfS.Run() vppProxy := s.Containers.VppProxy.VppInstance - proxyPort, err := strconv.Atoi(s.GetPortFromPpid()) - s.AssertNil(err) // tap interfaces are created on test setup with 1 rx-queue, // need to recreate them with 2 + consistent-qp @@ -74,9 +71,9 @@ func vppProxyIperfMTTest(s *VppProxySuite, proto string) { s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Client)) s.AssertNil(vppProxy.CreateTap(s.Interfaces.Client, false, 2, uint32(s.Interfaces.Client.Peer.Index), Consistent_qp)) - configureVppProxy(s, "tcp", uint16(proxyPort)) + configureVppProxy(s, "tcp", s.Ports.Proxy) if proto == "udp" { - configureVppProxy(s, "udp", uint16(proxyPort)) + configureVppProxy(s, "udp", s.Ports.Proxy) proto = "-u" } else { proto = "" @@ -93,16 +90,16 @@ func vppProxyIperfMTTest(s *VppProxySuite, proto string) { go func() { defer GinkgoRecover() - cmd := fmt.Sprintf("iperf3 -4 -s -B %s -p %s --logfile %s", s.ServerAddr(), fmt.Sprint(s.ServerPort()), s.IperfLogFileName(s.Containers.IperfS)) + cmd := fmt.Sprintf("iperf3 -4 -s -B %s -p %s --logfile %s", s.ServerAddr(), fmt.Sprint(s.Ports.Server), s.IperfLogFileName(s.Containers.IperfS)) s.StartServerApp(s.Containers.IperfS, "iperf3", cmd, srvCh, stopServerCh) }() - err = <-srvCh + err := <-srvCh s.AssertNil(err, fmt.Sprint(err)) go func() { defer GinkgoRecover() - cmd := fmt.Sprintf("iperf3 -c %s -P 4 -l 1460 -b 10g -J -p %d -B %s %s", s.VppProxyAddr(), proxyPort, s.ClientAddr(), proto) + cmd := fmt.Sprintf("iperf3 -c %s -P 4 -l 1460 -b 10g -J -p %d -B %s %s", s.VppProxyAddr(), s.Ports.Proxy, s.ClientAddr(), proto) s.StartClientApp(s.Containers.IperfC, cmd, clnCh, clnRes) }() @@ -113,18 +110,16 @@ func vppProxyIperfMTTest(s *VppProxySuite, proto string) { } func VppProxyHttpGetTcpTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "tcp", proxyPort) - uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort) + configureVppProxy(s, "tcp", s.Ports.Proxy) + uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } func VppProxyHttpGetTlsTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "tls", proxyPort) - uri := fmt.Sprintf("https://%s:%d/httpTestFile", s.VppProxyAddr(), proxyPort) + configureVppProxy(s, "tls", s.Ports.Proxy) + uri := fmt.Sprintf("https://%s:%d/httpTestFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } @@ -133,28 +128,26 @@ func VppProxyHttpPutTcpMTTest(s *VppProxySuite) { } func VppProxyHttpPutTcpTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "tcp", proxyPort) - uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.VppProxyAddr(), proxyPort) + configureVppProxy(s, "tcp", s.Ports.Proxy) + uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlUploadResource(uri, CurlContainerTestFile) } func VppProxyHttpPutTlsTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "tls", proxyPort) - uri := fmt.Sprintf("https://%s:%d/upload/testFile", s.VppProxyAddr(), proxyPort) + configureVppProxy(s, "tls", s.Ports.Proxy) + uri := fmt.Sprintf("https://%s:%d/upload/testFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlUploadResource(uri, CurlContainerTestFile) } func EnvoyProxyHttpGetTcpTest(s *EnvoyProxySuite) { - uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort()) + uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } func EnvoyProxyHttpPutTcpTest(s *EnvoyProxySuite) { - uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ProxyAddr(), s.ProxyPort()) + uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ProxyAddr(), s.Ports.Proxy) s.CurlUploadResource(uri, CurlContainerTestFile) } @@ -162,6 +155,7 @@ func MirrorMultiThreadTest(s *NginxProxySuite) { nginxMirroring(s, true) } +// unstable, registered as solo func NginxMirroringTest(s *NginxProxySuite) { nginxMirroring(s, false) } @@ -173,38 +167,35 @@ func nginxMirroring(s *NginxProxySuite, multiThreadWorkers bool) { s.CreateNginxProxyConfig(s.Containers.NginxProxy, multiThreadWorkers) s.Containers.NginxProxy.Start() vpp.WaitForApp("nginx-", 5) - uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.ProxyPort()) + uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } func VppConnectProxyGetTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "http", proxyPort) + configureVppProxy(s, "http", s.Ports.Proxy) - targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.ServerPort()) - proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), proxyPort) + targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server) + proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) s.CurlDownloadResourceViaTunnel(targetUri, proxyUri) } func VppConnectProxyConnectionFailedMTTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "http", proxyPort) + configureVppProxy(s, "http", s.Ports.Proxy) - targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.ServerPort()+1) - proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), proxyPort) + targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server+1) + proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) _, log := s.CurlRequestViaTunnel(targetUri, proxyUri) s.AssertContains(log, "HTTP/1.1 502 Bad Gateway") } func VppConnectProxyPutTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 s.SetupNginxServer() - configureVppProxy(s, "http", proxyPort) + configureVppProxy(s, "http", s.Ports.Proxy) - proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), proxyPort) - targetUri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ServerAddr(), s.ServerPort()) + proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ServerAddr(), s.Ports.Server) s.CurlUploadResourceViaTunnel(targetUri, proxyUri, CurlContainerTestFile) } @@ -214,7 +205,7 @@ func vppConnectProxyStressLoad(s *VppProxySuite, proxyPort string) { wg sync.WaitGroup ) stop := make(chan struct{}) - targetUri := fmt.Sprintf("%s:%d", s.ServerAddr(), s.ServerPort()) + targetUri := fmt.Sprintf("%s:%d", s.ServerAddr(), s.Ports.Server) s.Log("Running 30s test @ " + targetUri) for i := 0; i < 1000; i++ { @@ -317,20 +308,18 @@ func vppConnectProxyStressLoad(s *VppProxySuite, proxyPort string) { } func VppConnectProxyStressTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() - configureVppProxy(s, "http", proxyPort) + configureVppProxy(s, "http", s.Ports.Proxy) // no goVPP less noise s.Containers.VppProxy.VppInstance.Disconnect() - vppConnectProxyStressLoad(s, strconv.Itoa(int(proxyPort))) + vppConnectProxyStressLoad(s, strconv.Itoa(int(s.Ports.Proxy))) } func VppConnectProxyStressMTTest(s *VppProxySuite) { - var proxyPort uint16 = 8080 remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() @@ -342,12 +331,12 @@ func VppConnectProxyStressMTTest(s *VppProxySuite) { s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Client)) s.AssertNil(vppProxy.CreateTap(s.Interfaces.Client, false, 2, uint32(s.Interfaces.Client.Peer.Index), Consistent_qp)) - configureVppProxy(s, "http", proxyPort) + configureVppProxy(s, "http", s.Ports.Proxy) // no goVPP less noise vppProxy.Disconnect() - vppConnectProxyStressLoad(s, strconv.Itoa(int(proxyPort))) + vppConnectProxyStressLoad(s, strconv.Itoa(int(s.Ports.Proxy))) } func VppProxyUdpTest(s *VppUdpProxySuite) { @@ -355,8 +344,8 @@ func VppProxyUdpTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri udp://%s/%d", s.VppProxyAddr(), s.ProxyPort()) - cmd += fmt.Sprintf(" client-uri udp://%s/%d", s.ServerAddr(), s.ServerPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri udp://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) + cmd += fmt.Sprintf(" client-uri udp://%s/%d", s.ServerAddr(), s.Ports.Server) s.Log(vppProxy.Vppctl(cmd)) b := make([]byte, 1500) @@ -370,8 +359,8 @@ func VppProxyUdpMigrationMTTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri udp://%s/%d", s.VppProxyAddr(), s.ProxyPort()) - cmd += fmt.Sprintf(" client-uri udp://%s/%d", s.ServerAddr(), s.ServerPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri udp://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) + cmd += fmt.Sprintf(" client-uri udp://%s/%d", s.ServerAddr(), s.Ports.Server) s.Log(vppProxy.Vppctl(cmd)) b := make([]byte, 1500) @@ -392,11 +381,11 @@ func VppConnectUdpProxyTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) - proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) - targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.Ports.Proxy, s.ServerAddr(), s.Ports.Server) c := s.NewConnectUdpClient(s.MaxTimeout, true) err := c.Dial(proxyAddress, targetUri) s.AssertNil(err, fmt.Sprint(err)) @@ -413,22 +402,22 @@ func VppConnectUdpProxyTest(s *VppUdpProxySuite) { func VppConnectUdpInvalidTargetTest(s *VppUdpProxySuite) { vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) - proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.Ports.Proxy) - targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/example.com/80/", s.VppProxyAddr(), s.ProxyPort()) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/example.com/80/", s.VppProxyAddr(), s.Ports.Proxy) c := s.NewConnectUdpClient(s.MaxTimeout, true) err := c.Dial(proxyAddress, targetUri) s.AssertNotNil(err, "name resolution not supported") - targetUri = fmt.Sprintf("http://%s:%d/.well-known/masque/udp/1.2.3.4/800000000/", s.VppProxyAddr(), s.ProxyPort()) + targetUri = fmt.Sprintf("http://%s:%d/.well-known/masque/udp/1.2.3.4/800000000/", s.VppProxyAddr(), s.Ports.Proxy) c = s.NewConnectUdpClient(s.MaxTimeout, true) err = c.Dial(proxyAddress, targetUri) s.AssertNotNil(err, "invalid port number") - targetUri = fmt.Sprintf("http://%s:%d/masque/udp/1.2.3.4/80/", s.VppProxyAddr(), s.ProxyPort()) + targetUri = fmt.Sprintf("http://%s:%d/masque/udp/1.2.3.4/80/", s.VppProxyAddr(), s.Ports.Proxy) c = s.NewConnectUdpClient(s.MaxTimeout, true) err = c.Dial(proxyAddress, targetUri) s.AssertNotNil(err, "invalid prefix") @@ -439,11 +428,11 @@ func VppConnectUdpInvalidCapsuleTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) - proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) - targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.Ports.Proxy, s.ServerAddr(), s.Ports.Server) c := s.NewConnectUdpClient(s.MaxTimeout, true) err := c.Dial(proxyAddress, targetUri) s.AssertNil(err, fmt.Sprint(err)) @@ -469,11 +458,11 @@ func VppConnectUdpUnknownCapsuleTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) - proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) - targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.Ports.Proxy, s.ServerAddr(), s.Ports.Server) c := s.NewConnectUdpClient(s.MaxTimeout, true) err := c.Dial(proxyAddress, targetUri) s.AssertNil(err, fmt.Sprint(err)) @@ -498,18 +487,18 @@ func VppConnectUdpClientCloseTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) - proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) - targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.ProxyPort(), s.ServerAddr(), s.ServerPort()) + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s:%d/.well-known/masque/udp/%s/%d/", s.VppProxyAddr(), s.Ports.Proxy, s.ServerAddr(), s.Ports.Server) c := s.NewConnectUdpClient(s.MaxTimeout, true) err := c.Dial(proxyAddress, targetUri) s.AssertNil(err, fmt.Sprint(err)) err = c.Close() s.AssertNil(err, fmt.Sprint(err)) - proxyClientConn := fmt.Sprintf("[T] %s:%d->%s", s.VppProxyAddr(), s.ProxyPort(), s.ClientAddr()) + proxyClientConn := fmt.Sprintf("[T] %s:%d->%s", s.VppProxyAddr(), s.Ports.Proxy, s.ClientAddr()) proxyTargetConn := fmt.Sprintf("[U] %s:", s.Interfaces.Server.Peer.Ip4AddressString()) for nTries := 0; nTries < 10; nTries++ { o := vppProxy.Vppctl("show session verbose 2") @@ -530,8 +519,8 @@ func vppConnectUdpStressLoad(s *VppUdpProxySuite) { wg sync.WaitGroup ) - proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.ProxyPort()) - targetUri := fmt.Sprintf("http://%s/.well-known/masque/udp/%s/%d/", proxyAddress, s.ServerAddr(), s.ServerPort()) + proxyAddress := fmt.Sprintf("%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s/.well-known/masque/udp/%s/%d/", proxyAddress, s.ServerAddr(), s.Ports.Server) // warm-up warmUp := s.NewConnectUdpClient(s.MaxTimeout, false) @@ -629,7 +618,7 @@ func VppConnectUdpStressTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) // no goVPP less noise @@ -644,7 +633,7 @@ func VppConnectUdpStressMTTest(s *VppUdpProxySuite) { vppProxy := s.Containers.VppProxy.VppInstance vppProxy.Disconnect() - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.ProxyPort()) + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) // no goVPP less noise diff --git a/extras/hs-test/raw_session_test.go b/extras/hs-test/raw_session_test.go index b93b27ab56..196e1837b2 100644 --- a/extras/hs-test/raw_session_test.go +++ b/extras/hs-test/raw_session_test.go @@ -22,7 +22,7 @@ func VppEchoTcpTest(s *VethsSuite) { func testVppEcho(s *VethsSuite, proto string) { serverVethAddress := s.Interfaces.Server.Ip4AddressString() - uri := proto + "://" + serverVethAddress + "/12344" + uri := proto + "://" + serverVethAddress + "/" + s.Ports.Port1 serverCommand := "vpp_echo server TX=RX" + " socket-name " + s.Containers.ServerApp.GetContainerWorkDir() + "/var/run/app_ns_sockets/default" + diff --git a/extras/hs-test/resources/envoy/proxy.yaml b/extras/hs-test/resources/envoy/proxy.yaml index 67eb6909ed..62e728b2d4 100644 --- a/extras/hs-test/resources/envoy/proxy.yaml +++ b/extras/hs-test/resources/envoy/proxy.yaml @@ -1,14 +1,14 @@ admin: address: socket_address: - address: 0.0.0.0 - port_value: 8081 + address: {{.ServerAddress}} + port_value: {{.EnvoyAdminPort}} static_resources: listeners: - address: socket_address: protocol: TCP - address: 0.0.0.0 + address: {{.ProxyAddr}} port_value: {{.ProxyPort}} filter_chains: - filters: diff --git a/extras/hs-test/resources/nginx/nginx.conf b/extras/hs-test/resources/nginx/nginx.conf index 04f5f2bd1f..7eac45c927 100644 --- a/extras/hs-test/resources/nginx/nginx.conf +++ b/extras/hs-test/resources/nginx/nginx.conf @@ -15,8 +15,8 @@ http { keepalive_requests 1000000; sendfile on; server { - listen 80; - listen [::]:80; + listen {{.Port}}; + listen [::]:{{.Port}}; root /usr/share/nginx; index index.html index.htm; location /return_ok diff --git a/extras/hs-test/resources/nginx/nginx_http3.conf b/extras/hs-test/resources/nginx/nginx_http3.conf index e048b17044..814568842b 100644 --- a/extras/hs-test/resources/nginx/nginx_http3.conf +++ b/extras/hs-test/resources/nginx/nginx_http3.conf @@ -18,7 +18,7 @@ http { keepalive_timeout 300s; sendfile on; server { - listen 0.0.0.0:8443 quic; + listen {{.Address}}:{{.Port}} quic; root /usr/share/nginx; ssl_certificate /etc/nginx/ssl/localhost.crt; ssl_certificate_key /etc/nginx/ssl/localhost.key; diff --git a/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf b/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf index d834a27288..9e0f276b02 100644 --- a/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf +++ b/extras/hs-test/resources/nginx/nginx_proxy_mirroring.conf @@ -31,15 +31,15 @@ http { gzip on; upstream bk { - server {{.Server}}:8091; + server {{.Server}}:{{.Upstream1}}; keepalive 30000; } upstream bk1 { - server {{.Server}}:8092; + server {{.Server}}:{{.Upstream2}}; keepalive 30000; } upstream bk2 { - server {{.Server}}:8093; + server {{.Server}}:{{.Upstream3}}; keepalive 30000; } diff --git a/extras/hs-test/resources/nginx/nginx_server_mirroring.conf b/extras/hs-test/resources/nginx/nginx_server_mirroring.conf index 921eb2eefd..3c28dbfeec 100644 --- a/extras/hs-test/resources/nginx/nginx_server_mirroring.conf +++ b/extras/hs-test/resources/nginx/nginx_server_mirroring.conf @@ -18,9 +18,9 @@ http { sendfile on; server { access_log /tmp/nginx/{{.LogPrefix}}-access.log; - listen 8091; - listen 8092; - listen 8093; + listen {{.Address}}:{{.Upstream1}}; + listen {{.Address}}:{{.Upstream2}}; + listen {{.Address}}:{{.Upstream3}}; root /usr/share/nginx; index index.html index.htm; location /64B { diff --git a/extras/hs-test/tls_test.go b/extras/hs-test/tls_test.go index 4265f3612e..7689ef67c0 100644 --- a/extras/hs-test/tls_test.go +++ b/extras/hs-test/tls_test.go @@ -9,9 +9,9 @@ func init() { } func TlsAlpMatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 uri tls://0.0.0.0:123")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 uri tls://0.0.0.0:" + s.Ports.Port1)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -21,9 +21,9 @@ func TlsAlpMatchTest(s *VethsSuite) { } func TlsAlpnOverlapMatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 alpn-proto2 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -33,9 +33,9 @@ func TlsAlpnOverlapMatchTest(s *VethsSuite) { } func TlsAlpnServerPriorityMatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -45,9 +45,9 @@ func TlsAlpnServerPriorityMatchTest(s *VethsSuite) { } func TlsAlpnMismatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 alpn-proto2 4 uri " + uri) s.Log(o) s.AssertNotContains(o, "timeout") @@ -57,9 +57,9 @@ func TlsAlpnMismatchTest(s *VethsSuite) { } func TlsAlpnEmptyServerListTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server uri tls://0.0.0.0:123")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server uri tls://0.0.0.0:" + s.Ports.Port1)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -69,9 +69,9 @@ func TlsAlpnEmptyServerListTest(s *VethsSuite) { } func TlsAlpnEmptyClientListTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go index bc557a9050..5fa331288b 100644 --- a/extras/hs-test/vcl_test.go +++ b/extras/hs-test/vcl_test.go @@ -39,15 +39,14 @@ func XEchoVclClientTcpTest(s *VethsSuite) { } func testXEchoVclClient(s *VethsSuite, proto string) { - port := "12345" serverVpp := s.Containers.ServerVpp.VppInstance - serverVpp.Vppctl("test echo server uri %s://%s/%s fifo-size 64k", proto, s.Interfaces.Server.Ip4AddressString(), port) + serverVpp.Vppctl("test echo server uri %s://%s/%s fifo-size 64k", proto, s.Interfaces.Server.Ip4AddressString(), s.Ports.Port1) echoClnContainer := s.GetTransientContainerByName("client-app") echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer)) - testClientCommand := "vcl_test_client -N 100 -p " + proto + " " + s.Interfaces.Server.Ip4AddressString() + " " + port + testClientCommand := "vcl_test_client -N 100 -p " + proto + " " + s.Interfaces.Server.Ip4AddressString() + " " + s.Ports.Port1 s.Log(testClientCommand) echoClnContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf") o, err := echoClnContainer.Exec(true, testClientCommand) @@ -65,38 +64,36 @@ func XEchoVclServerTcpTest(s *VethsSuite) { } func testXEchoVclServer(s *VethsSuite, proto string) { - port := "12345" srvVppCont := s.Containers.ServerVpp srvAppCont := s.Containers.ServerApp srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont)) srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") - vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s %s", proto, port) + vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s %s", proto, s.Ports.Port1) srvAppCont.ExecServer(true, vclSrvCmd) serverVethAddress := s.Interfaces.Server.Ip4AddressString() clientVpp := s.Containers.ClientVpp.VppInstance - o := clientVpp.Vppctl("test echo client uri %s://%s/%s fifo-size 64k verbose bytes 2m", proto, serverVethAddress, port) + o := clientVpp.Vppctl("test echo client uri %s://%s/%s fifo-size 64k verbose bytes 2m", proto, serverVethAddress, s.Ports.Port1) s.Log(o) s.AssertContains(o, "Test finished at") } func testVclEcho(s *VethsSuite, proto string) { - port := "12345" srvVppCont := s.Containers.ServerVpp srvAppCont := s.Containers.ServerApp srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont)) srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") - srvAppCont.ExecServer(true, "vcl_test_server -p "+proto+" "+port) + srvAppCont.ExecServer(true, "vcl_test_server -p "+proto+" "+s.Ports.Port1) serverVethAddress := s.Interfaces.Server.Ip4AddressString() echoClnContainer := s.GetTransientContainerByName("client-app") echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer)) - testClientCommand := "vcl_test_client -p " + proto + " " + serverVethAddress + " " + port + testClientCommand := "vcl_test_client -p " + proto + " " + serverVethAddress + " " + s.Ports.Port1 echoClnContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf") o, err := echoClnContainer.Exec(true, testClientCommand) s.AssertNil(err) @@ -127,7 +124,7 @@ func testRetryAttach(s *VethsSuite, proto string) { echoSrvContainer.CreateFile("/vcl.conf", getVclConfig(echoSrvContainer)) echoSrvContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf") - echoSrvContainer.ExecServer(true, "vcl_test_server -p "+proto+" 12346") + echoSrvContainer.ExecServer(true, "vcl_test_server -p "+proto+" "+s.Ports.Port1) s.Log("This whole test case can take around 3 minutes to run. Please be patient.") s.Log("... Running first echo client test, before disconnect.") @@ -137,7 +134,7 @@ func testRetryAttach(s *VethsSuite, proto string) { echoClnContainer := s.GetTransientContainerByName("client-app") echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer)) - testClientCommand := "vcl_test_client -U -p " + proto + " " + serverVethAddress + " 12346" + testClientCommand := "vcl_test_client -U -p " + proto + " " + serverVethAddress + " " + s.Ports.Port1 echoClnContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf") o, err := echoClnContainer.Exec(true, testClientCommand) s.AssertNil(err) From 032468d8ddf4bd6a0a509334d2bcfdaa7ea395d7 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Wed, 28 May 2025 13:20:08 +0000 Subject: [PATCH 007/313] misc: remove unnecessary genhtml cmd_line for cov-merge Type: make Change-Id: Ie5165c38df176d9ec0c8b9ce27c3ecebd25547ec Signed-off-by: Semir Sionek --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index d7fce89a16..dece402136 100644 --- a/Makefile +++ b/Makefile @@ -592,6 +592,7 @@ cov-merge: -a $(BR)/test-coverage-merged/coverage-filtered1.info -o $(BR)/test-coverage-merged/coverage-merged.info @genhtml $(BR)/test-coverage-merged/coverage-merged.info \ --output-directory $(BR)/test-coverage-merged/html + -@rm -f $(BR)/test-coverage-merged/html/cmd_line @echo "Code coverage report is in $(BR)/test-coverage-merged/html/index.html" .PHONY: test-all From 856705da3f26e7a2929549d9981ad1ee5a0a45bf Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Thu, 22 May 2025 13:14:59 +0000 Subject: [PATCH 008/313] http: disable timer node if session disabled Type: improvement Change-Id: Ie6d343c7712edfd644b6d133007ae10b40359894 Signed-off-by: Semir Sionek --- extras/hs-test/http_test.go | 19 ++++++++++++++++- src/plugins/http/http.c | 6 +++++- src/plugins/http/http_timer.c | 39 ++++++++++++++++++++++++----------- src/plugins/http/http_timer.h | 1 + 4 files changed, 51 insertions(+), 14 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index f85804e165..e572826a19 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -37,7 +37,7 @@ func init() { HttpClientGetTlsNoRespBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, - HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest) + HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest, HttpClientGetRepeatMTTest, HttpClientPtrGetRepeatMTTest) @@ -1376,6 +1376,23 @@ func HttpInvalidRequestLineTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP1.1' invalid http version not allowed") } +func HttpTimerSessionDisable(s *NoTopoSuite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress)) + time.Sleep(250 * time.Millisecond) + resp := vpp.Vppctl("show node http-timer-process") + s.AssertContains(resp, "node http-timer-process, type process, state \"any wait\"") + vpp.Vppctl("session disable") + time.Sleep(1 * time.Second) + resp = vpp.Vppctl("show node http-timer-process") + s.AssertContains(resp, "node http-timer-process, type process, state \"not started\"") + vpp.Vppctl("session enable") + time.Sleep(100 * time.Millisecond) + resp = vpp.Vppctl("show node http-timer-process") + s.AssertContains(resp, "node http-timer-process, type process, state \"any wait\"") +} + func HttpRequestLineTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 1aed2fd358..2c923c69ee 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -792,6 +792,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) da->app_index = hm->app_index; da->api_client_index = APP_INVALID_INDEX; vnet_application_detach (da); + http_timers_set_state (vm, false); return 0; } @@ -819,7 +820,10 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) vec_free (a->name); if (hm->is_init) - return 0; + { + http_timers_set_state (vm, true); + return 0; + } vec_validate (hm->wrk, num_threads - 1); vec_validate (hm->rx_bufs, num_threads - 1); diff --git a/src/plugins/http/http_timer.c b/src/plugins/http/http_timer.c index 580f31657a..5cb4daa52d 100644 --- a/src/plugins/http/http_timer.c +++ b/src/plugins/http/http_timer.c @@ -18,6 +18,9 @@ http_tw_ctx_t http_tw_ctx; +static uword http_timer_process (vlib_main_t *vm, vlib_node_runtime_t *rt, + vlib_frame_t *f); + static void http_timer_process_expired_cb (u32 *expired_timers) { @@ -42,6 +45,13 @@ http_timer_process_expired_cb (u32 *expired_timers) } } +VLIB_REGISTER_NODE (http_timer_process_node) = { + .function = http_timer_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "http-timer-process", + .state = VLIB_NODE_STATE_DISABLED, +}; + static uword http_timer_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) { @@ -50,7 +60,8 @@ http_timer_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) uword *event_data = 0; uword __clib_unused event_type; - while (1) + while (vlib_node_get_state (vm, http_timer_process_node.index) != + VLIB_NODE_STATE_DISABLED) { vlib_process_wait_for_event_or_clock (vm, timeout); now = vlib_time_now (vm); @@ -66,19 +77,26 @@ http_timer_process (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) return 0; } -VLIB_REGISTER_NODE (http_timer_process_node) = { - .function = http_timer_process, - .type = VLIB_NODE_TYPE_PROCESS, - .name = "http-timer-process", - .state = VLIB_NODE_STATE_DISABLED, -}; +void +http_timers_set_state (vlib_main_t *vm, bool enabled) +{ + vlib_node_t *n; + + vlib_node_set_state ( + vm, http_timer_process_node.index, + (enabled ? VLIB_NODE_STATE_POLLING : VLIB_NODE_STATE_DISABLED)); + if (enabled) + { + n = vlib_get_node (vm, http_timer_process_node.index); + vlib_start_process (vm, n->runtime_index); + } +} void http_timers_init (vlib_main_t *vm, http_conn_timeout_fn *rpc_cb, http_conn_invalidate_timer_fn *invalidate_cb) { http_tw_ctx_t *twc = &http_tw_ctx; - vlib_node_t *n; ASSERT (twc->tw.timers == 0); @@ -88,10 +106,7 @@ http_timers_init (vlib_main_t *vm, http_conn_timeout_fn *rpc_cb, twc->rpc_cb = rpc_cb; twc->invalidate_cb = invalidate_cb; - vlib_node_set_state (vm, http_timer_process_node.index, - VLIB_NODE_STATE_POLLING); - n = vlib_get_node (vm, http_timer_process_node.index); - vlib_start_process (vm, n->runtime_index); + http_timers_set_state (vm, true); } /* diff --git a/src/plugins/http/http_timer.h b/src/plugins/http/http_timer.h index 5ce42032f2..ac4f71afab 100644 --- a/src/plugins/http/http_timer.h +++ b/src/plugins/http/http_timer.h @@ -37,6 +37,7 @@ extern http_tw_ctx_t http_tw_ctx; void http_timers_init (vlib_main_t *vm, http_conn_timeout_fn *rpc_cb, http_conn_invalidate_timer_fn *invalidate_cb); +void http_timers_set_state (vlib_main_t *vm, bool enabled); static inline void http_conn_timer_start (http_conn_t *hc) From f3d6656538497e28ea895b76fc64393c1bf0114e Mon Sep 17 00:00:00 2001 From: Alexander Chernavin Date: Wed, 28 May 2025 18:49:35 +0300 Subject: [PATCH 009/313] api: fix inversion of barrier marker in elog Currently, the barrier marker for an API message is shown inverted in the event log. Unsafe API messages are marked as "mp-safe" while safe ones are marked as "barrier": 1.332140489: api-msg: sw_interface_dump 1.332218564: api-msg-done(barrier): sw_interface_dump 1.332222732: api-msg: control_ping 1.332241081: api-msg-done(barrier): control_ping 1.334262103: api-msg: sw_interface_set_flags 1.334365558: api-msg-done(mp-safe): sw_interface_set_flags With this fix: 1.369092258: api-msg: sw_interface_dump 1.369123551: api-msg-done(mp-safe): sw_interface_dump 1.369125154: api-msg: control_ping 1.369132839: api-msg-done(mp-safe): control_ping 1.370686609: api-msg: sw_interface_set_flags 1.370735169: api-msg-done(barrier): sw_interface_set_flags Type: fix Change-Id: Ib892a83cf801da5fea722eebffd220fabfaa1537 Signed-off-by: Alexander Chernavin --- src/vlibapi/api_shared.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vlibapi/api_shared.c b/src/vlibapi/api_shared.c index 1f02aefe88..112b0631c4 100644 --- a/src/vlibapi/api_shared.c +++ b/src/vlibapi/api_shared.c @@ -608,7 +608,7 @@ msg_handler_internal (api_main_t *am, void *the_msg, uword msg_len, if (m && m->name) { ed->c = elog_string (am->elog_main, (char *) m->name); - ed->barrier = !m->is_mp_safe; + ed->barrier = m->is_mp_safe; } else { From cf53eb7c0ce6fa24d9d23554fe327ba402400180 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 28 May 2025 09:58:59 -0400 Subject: [PATCH 010/313] hsa: https support in proxy app Type: improvement Change-Id: Id777b4e9b30c496d702ae31b5d628815f6f3b59d Signed-off-by: Matus Fabian --- extras/hs-test/infra/suite_vpp_proxy.go | 4 ++-- extras/hs-test/proxy_test.go | 19 +++++++++++---- src/plugins/hs_apps/proxy.c | 32 ++++++++++++++++++------- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index ec08a630d2..92ca24c9d9 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -164,7 +164,7 @@ func (s *VppProxySuite) CurlUploadResource(uri, file string) { } func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri string) { - args := fmt.Sprintf("-w @/tmp/write_out_download_connect --max-time %d --insecure -p -x %s --remote-name --output-dir /tmp %s", s.maxTimeout, proxyUri, uri) + args := fmt.Sprintf("-w @/tmp/write_out_download_connect --max-time %d --insecure --proxy-insecure -p -x %s --remote-name --output-dir /tmp %s", s.maxTimeout, proxyUri, uri) writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) s.AssertContains(writeOut, "CONNECT response code: 200") s.AssertContains(writeOut, "GET response code: 200") @@ -174,7 +174,7 @@ func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri strin } func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string) { - args := fmt.Sprintf("-w @/tmp/write_out_upload_connect --max-time %d --insecure -p -x %s -T %s %s", s.maxTimeout, proxyUri, file, uri) + args := fmt.Sprintf("-w @/tmp/write_out_upload_connect --max-time %d --insecure --proxy-insecure -p -x %s -T %s %s", s.maxTimeout, proxyUri, file, uri) writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) s.AssertContains(writeOut, "CONNECT response code: 200") s.AssertContains(writeOut, "PUT response code: 201") diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index 0e82bf3117..9e107b5127 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -22,7 +22,7 @@ import ( func init() { RegisterVppProxyTests(VppProxyHttpGetTcpTest, VppProxyHttpGetTlsTest, VppProxyHttpPutTcpTest, VppProxyHttpPutTlsTest, - VppConnectProxyGetTest, VppConnectProxyPutTest) + VppConnectProxyGetTest, VppConnectProxyPutTest, VppHttpsConnectProxyGetTest) RegisterVppProxySoloTests(VppProxyHttpGetTcpMTTest, VppProxyHttpPutTcpMTTest, VppProxyTcpIperfMTTest, VppProxyUdpIperfMTTest, VppConnectProxyStressTest, VppConnectProxyStressMTTest, VppConnectProxyConnectionFailedMTTest) RegisterVppUdpProxyTests(VppProxyUdpTest, VppConnectUdpProxyTest, VppConnectUdpInvalidCapsuleTest, @@ -34,12 +34,12 @@ func init() { func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) { vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri %s://%s/%d", proto, s.VppProxyAddr(), proxyPort) - if proto != "http" && proto != "udp" { + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri %s://%s:%d", proto, s.VppProxyAddr(), proxyPort) + if proto != "http" && proto != "https" && proto != "udp" { proto = "tcp" } - if proto != "http" { - cmd += fmt.Sprintf(" client-uri %s://%s/%d", proto, s.ServerAddr(), s.Ports.Server) + if proto != "http" && proto != "https" { + cmd += fmt.Sprintf(" client-uri %s://%s:%d", proto, s.ServerAddr(), s.Ports.Server) } output := vppProxy.Vppctl(cmd) @@ -180,6 +180,15 @@ func VppConnectProxyGetTest(s *VppProxySuite) { s.CurlDownloadResourceViaTunnel(targetUri, proxyUri) } +func VppHttpsConnectProxyGetTest(s *VppProxySuite) { + s.SetupNginxServer() + configureVppProxy(s, "https", s.Ports.Proxy) + + targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server) + proxyUri := fmt.Sprintf("https://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + s.CurlDownloadResourceViaTunnel(targetUri, proxyUri) +} + func VppConnectProxyConnectionFailedMTTest(s *VppProxySuite) { s.SetupNginxServer() configureVppProxy(s, "http", s.Ports.Proxy) diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index 140183d5f5..c8bdc73a41 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -20,6 +20,7 @@ #include #include #include +#include proxy_main_t proxy_main; @@ -1266,22 +1267,35 @@ proxy_server_listen () clib_memcpy (&a->sep_ext, &pm->server_sep, sizeof (pm->server_sep)); /* Make sure listener is marked connected for transports like udp */ a->sep_ext.transport_flags = TRANSPORT_CFG_F_CONNECTED; - need_crypto = proxy_transport_needs_crypto (a->sep.transport_proto); - if (need_crypto) - { - transport_endpt_ext_cfg_t *ext_cfg = session_endpoint_add_ext_cfg ( - &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, - sizeof (transport_endpt_crypto_cfg_t)); - ext_cfg->crypto.ckpair_index = pm->ckpair_index; - } - /* set http timeout for connect-proxy */ + if (pm->server_sep.transport_proto == TRANSPORT_PROTO_HTTP) { + /* set http timeout for connect-proxy */ transport_endpt_cfg_http_t http_cfg = { pm->idle_timeout, HTTP_UDP_TUNNEL_DGRAM }; transport_endpt_ext_cfg_t *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 (pm->server_sep.flags & SESSION_ENDPT_CFG_F_SECURE) + { + transport_endpt_ext_cfg_t *ext_cfg = session_endpoint_add_ext_cfg ( + &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, + sizeof (transport_endpt_crypto_cfg_t)); + ext_cfg->crypto.ckpair_index = pm->ckpair_index; + /* TODO: remove when http/2 connect done */ + ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; + } + } + else + { + need_crypto = proxy_transport_needs_crypto (a->sep.transport_proto); + if (need_crypto) + { + transport_endpt_ext_cfg_t *ext_cfg = session_endpoint_add_ext_cfg ( + &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, + sizeof (transport_endpt_crypto_cfg_t)); + ext_cfg->crypto.ckpair_index = pm->ckpair_index; + } } rv = vnet_listen (a); From 59346b0aa233e864fd27617bd24c507ba6873c50 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Thu, 29 May 2025 12:31:10 +0000 Subject: [PATCH 011/313] misc: include http_static and prom in cov reports Type: make Change-Id: Ibcc7306f29647a963bdbc6ed1b1990a9220d8e3c Signed-off-by: Semir Sionek --- test/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Makefile b/test/Makefile index 7f9d06bb48..445087d7ff 100644 --- a/test/Makefile +++ b/test/Makefile @@ -366,7 +366,7 @@ COV_REM_UNUSED_FEAT="*/vnet/srp/*" \ "*/lawful-intercept/*" "*/lisp/*" "*/plugins/osi/*" \ "*/plugins/nsh/*" -COV_REM_TODO_NO_TEST="*/vpp-api/client/*" "*/plugins/prom/*" \ +COV_REM_TODO_NO_TEST="*/vpp-api/client/*" \ "*/plugins/tlspicotls/*" "*/plugins/tlsmbedtls/*" \ "*/vppinfra/perfmon/*" "*/plugins/ila/*" \ "*/vlib/linux/*" "*/vnet/util/radix.c" "*/vapi/vapi.hpp" \ @@ -389,7 +389,7 @@ COV_REM_TODO_NO_TEST="*/vpp-api/client/*" "*/plugins/prom/*" \ "*/plugins/ikev2/ikev2_format.c" "*/vnet/bier/bier_types.c" \ "*/plugins/ioam/*" "*/plugins/vxlan-gpe/*" "*/plugins/ioam/*" \ "*/plugins/hsi/*" "*/api/api_format.c" "*/*/api.c" "*/*/*_api.c" \ - "*/plugins/http_static/*" "*/plugins/hs_apps/*" + "*/plugins/hs_apps/*" ifeq ($(HS_TEST),1) COV_REM_HST_UNUSED_FEAT= "*/plugins/ping/*" "*/plugins/unittest/mpcap_node.c" "*/vnet/bfd/*" \ "*/vnet/bier/*" "*/vnet/bonding/*" "*/vnet/classify/*" \ @@ -400,7 +400,7 @@ COV_REM_HST_UNUSED_FEAT= "*/plugins/ping/*" "*/plugins/unittest/mpcap_node.c" "* "*/vnet/tunnel/*" "*/vpp-api/vapi/*" "*/vpp/app/vpe_cli.c" \ "*/vppinfra/pcap.c" "*/vppinfra/pcap_funcs.h" else -COV_REM_TODO_NO_TEST := $(COV_REM_TODO_NO_TEST) "*/plugins/http/http2/*" +COV_REM_TODO_NO_TEST := $(COV_REM_TODO_NO_TEST) "*/plugins/http/http2/*" "*/plugins/prom/*" endif LCOV_VERSION=$(shell lcov --version | sed -E 's/^lcov: LCOV version ([0-9]+)[.].*/\1/') From 4a54e0a9bdaaa01c4a87dc62d4392c0fc1c1bdb0 Mon Sep 17 00:00:00 2001 From: Joel Godfrey-Smith Date: Thu, 3 Apr 2025 13:34:10 -0400 Subject: [PATCH 012/313] build: updated to build on RHEL-8 Type: improvement Makefile: updated ability to detect os version id for rhel distros and added section for rhel-8 packages names in rpm dependencies. extras/depricated/vom/vom.mk: updated path for rhel8 toolset Change-Id: If760e64a06ff9d8cbe354dc64f13f271a644263e Signed-off-by: Joel Godfrey-Smith Change-Id: I8dae5fceb6b9f370f2c53066550afb608e95e087 Signed-off-by: Joel Godfrey-Smith --- Makefile | 26 ++++++++++++++++++++++++-- extras/deprecated/vom/vom.mk | 25 ++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index dece402136..792d5c4bf8 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,11 @@ GDB_ARGS= -ex "handle SIGUSR1 noprint nostop" # We allow Darwin (MacOS) for docs generation; VPP build will still fail. ifneq ($(shell uname),Darwin) OS_ID = $(shell grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') +ifeq ($(OS_ID),rhel) +OS_VERSION_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g' | sed -e 's/\..*//') +else OS_VERSION_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') +endif OS_CODENAME = $(shell grep '^VERSION_CODENAME=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') endif @@ -202,6 +206,20 @@ else ifeq ($(OS_ID)-$(OS_VERSION_ID),centos-8) RPM_DEPENDS += infiniband-diags libibumad RPM_DEPENDS += libpcap-devel llvm-toolset RPM_DEPENDS_GROUPS = 'Development Tools' +else ifeq ($(OS_ID)-$(OS_VERSION_ID),rhel-8) + RPM_DEPENDS += yum-utils + RPM_DEPENDS += openssl-devel + RPM_DEPENDS += python3-ply # for vppapigen + RPM_DEPENDS += python36-devel + RPM_DEPENDS += python3-pip + RPM_DEPENDS += python3-virtualenv + RPM_DEPENDS += python3-jsonschema + RPM_DEPENDS += gcc-toolset-9 + RPM_DEPENDS += gcc-toolset-9-libasan-devel + RPM_DEPENDS += cmake + RPM_DEPENDS += llvm-toolset + RPM_DEPENDS += infiniband-diags + RPM_DEPENDS += autoconf automake bison byacc libtool else ifeq ($(OS_ID)-$(OS_VERSION_ID),anolis-8) RPM_DEPENDS += yum-utils RPM_DEPENDS += compat-openssl10 openssl-devel @@ -382,7 +400,7 @@ ifeq ($(filter ubuntu debian linuxmint,$(OS_ID)),$(OS_ID)) exit 0 else ifneq ("$(wildcard /etc/redhat-release)","") @for i in $(RPM_DEPENDS) ; do \ - RPM=$$(basename -s .rpm "$${i##*/}" | cut -d- -f1,2,3,4) ; \ + RPM=$$(basename -s .rpm "$${i##*/}") ; \ MISSING+=$$(rpm -q $$RPM | grep "^package") ; \ done ; \ if [ -n "$$MISSING" ] ; then \ @@ -404,7 +422,11 @@ ifeq ($(filter ubuntu debian linuxmint,$(OS_ID)),$(OS_ID)) @sudo -E apt-get update @sudo -E apt-get $(APT_ARGS) $(CONFIRM) $(FORCE) install $(DEB_DEPENDS) else ifneq ("$(wildcard /etc/redhat-release)","") -ifeq ($(OS_ID),rhel) +ifeq ($(OS_ID)-$(OS_VERSION_ID),rhel-8) + @sudo -E dnf install $(CONFIRM) https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm + @sudo -E dnf install $(CONFIRM) $(RPM_DEPENDS) + @sudo -E debuginfo-install $(CONFIRM) glibc openssl-libs zlib +else ifeq ($(OS_ID)-$(OS_VERSION_ID),rhel-7) @sudo -E yum-config-manager --enable rhel-server-rhscl-7-rpms @sudo -E yum groupinstall $(CONFIRM) $(RPM_DEPENDS_GROUPS) @sudo -E yum install $(CONFIRM) $(RPM_DEPENDS) diff --git a/extras/deprecated/vom/vom.mk b/extras/deprecated/vom/vom.mk index 97fa900b84..10f7f9668f 100644 --- a/extras/deprecated/vom/vom.mk +++ b/extras/deprecated/vom/vom.mk @@ -15,6 +15,18 @@ vom_configure_depend = vpp-install vom_source = extras vom_configure_subdir = vom +# OS Detection +# +# We allow Darwin (MacOS) for docs generation; VPP build will still fail. +ifneq ($(shell uname),Darwin) +OS_ID = $(shell grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') + ifeq ($(OS_ID),rhel) + OS_VERSION_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g' | sed -e 's/\..*//') + else + OS_VERSION_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') + endif +endif + ifneq ($(shell which cmake3 2>/dev/null),) CMAKE?=cmake3 else @@ -27,9 +39,16 @@ vom_cmake_args += -DCMAKE_CXX_FLAGS="$($(TAG)_TAG_CPPFLAGS)" vom_cmake_args += -DCMAKE_SHARED_LINKER_FLAGS="$($(TAG)_TAG_LDFLAGS)" vom_cmake_args += -DCMAKE_PREFIX_PATH:PATH="$(PACKAGE_INSTALL_DIR)/../vpp" -# Use devtoolset on centos 7 -ifneq ($(wildcard /opt/rh/devtoolset-9/enable),) -vom_cmake_args += -DCMAKE_PROGRAM_PATH:PATH="/opt/rh/devtoolset-9/root/bin" +# Use devtoolset +ifeq ($(OS_ID)-$(OS_VERSION_ID),rhel-8) + ifneq ($(wildcard /opt/rh/gcc-toolset-9/enable),) + vom_cmake_args += -DCMAKE_PROGRAM_PATH:PATH="/opt/rh/gcc-toolset-9/root/bin" + endif +else + # Use devtoolset on centos 7 + ifneq ($(wildcard /opt/rh/devtoolset-9/enable),) + vom_cmake_args += -DCMAKE_PROGRAM_PATH:PATH="/opt/rh/devtoolset-9/root/bin" + endif endif vom_configure = \ From e73a2d60be7af8ebeb9bdda1e310a82a71d9e4c7 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 29 May 2025 14:18:40 -0400 Subject: [PATCH 013/313] hs-test: add show error to failed ldp test logs Type: test Change-Id: I9bb2f0b574c2cdccaa68a69b2b2c5b89afc3f86f Signed-off-by: Florin Coras --- extras/hs-test/infra/suite_ldp.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index 1127f13fbc..d1df322814 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -105,6 +105,8 @@ func (s *LdpSuite) TeardownTest() { if CurrentSpecReport().Failed() { s.CollectIperfLogs(s.Containers.ServerVpp) s.CollectRedisServerLogs(s.Containers.ServerVpp) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("show error")) + s.Log(s.Containers.ClientVpp.VppInstance.Vppctl("show error")) } for _, container := range s.StartedContainers { From 894f4f064d4d03b72638d1ba85ae9fab95b519ac Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Fri, 30 May 2025 11:07:29 +0200 Subject: [PATCH 014/313] hs-test: run LDP iperf in app containers Type: test Change-Id: I24b0556a8d530dbd487370b7113660a6ed455846 Signed-off-by: Adrian Villin --- extras/hs-test/infra/suite_ldp.go | 18 ++++++++---------- extras/hs-test/ldp_test.go | 14 +++++++------- .../hs-test/topo-containers/2peerVethLdp.yaml | 18 ------------------ 3 files changed, 15 insertions(+), 35 deletions(-) delete mode 100644 extras/hs-test/topo-containers/2peerVethLdp.yaml diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index d1df322814..f97a79c1d5 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -42,7 +42,7 @@ func (s *LdpSuite) SetupSuite() { time.Sleep(1 * time.Second) s.HstSuite.SetupSuite() s.ConfigureNetworkTopology("2peerVeth") - s.LoadContainerTopology("2peerVethLdp") + s.LoadContainerTopology("2peerVeth") s.Interfaces.Client = s.GetInterfaceByName("cln") s.Interfaces.Server = s.GetInterfaceByName("srv") s.Containers.ServerVpp = s.GetContainerByName("server-vpp") @@ -77,17 +77,15 @@ func (s *LdpSuite) SetupTest() { clientVpp, err := s.Containers.ClientVpp.newVppInstance(s.Containers.ClientVpp.AllocatedCpus, sessionConfig) s.AssertNotNil(clientVpp, fmt.Sprint(err)) - s.Containers.ServerVpp.AddEnvVar("VCL_CONFIG", s.Containers.ServerVpp.GetContainerWorkDir()+"/vcl.conf") - s.Containers.ClientVpp.AddEnvVar("VCL_CONFIG", s.Containers.ClientVpp.GetContainerWorkDir()+"/vcl.conf") - for _, container := range s.StartedContainers { + container.AddEnvVar("VCL_CONFIG", container.GetContainerWorkDir()+"/vcl.conf") container.AddEnvVar("LD_PRELOAD", "/usr/lib/libvcl_ldpreload.so") container.AddEnvVar("LDP_DEBUG", "0") container.AddEnvVar("VCL_DEBUG", "0") } - s.CreateVclConfig(s.Containers.ServerVpp) - s.CreateVclConfig(s.Containers.ClientVpp) + s.CreateVclConfig(s.Containers.ServerApp) + s.CreateVclConfig(s.Containers.ClientApp) s.SetupServerVpp(s.Containers.ServerVpp) s.setupClientVpp(s.Containers.ClientVpp) @@ -103,10 +101,10 @@ func (s *LdpSuite) SetupTest() { func (s *LdpSuite) TeardownTest() { if CurrentSpecReport().Failed() { - s.CollectIperfLogs(s.Containers.ServerVpp) - s.CollectRedisServerLogs(s.Containers.ServerVpp) - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("show error")) - s.Log(s.Containers.ClientVpp.VppInstance.Vppctl("show error")) + s.CollectIperfLogs(s.Containers.ServerApp) + s.CollectRedisServerLogs(s.Containers.ServerApp) + s.Log(s.Containers.ServerApp.VppInstance.Vppctl("show error")) + s.Log(s.Containers.ServerApp.VppInstance.Vppctl("show error")) } for _, container := range s.StartedContainers { diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index b42edabc64..8f9982a2aa 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -36,10 +36,10 @@ func ldpIperfTcpReorder(s *LdpSuite, netInterface *NetInterface, extraIperfArgs o, err := cmd.CombinedOutput() s.AssertNil(err, string(o)) - delete(s.Containers.ClientVpp.EnvVars, "VCL_CONFIG") - delete(s.Containers.ClientVpp.EnvVars, "LD_PRELOAD") - delete(s.Containers.ClientVpp.EnvVars, "VCL_DEBUG") - delete(s.Containers.ClientVpp.EnvVars, "LDP_DEBUG") + delete(s.Containers.ClientApp.EnvVars, "VCL_CONFIG") + delete(s.Containers.ClientApp.EnvVars, "LD_PRELOAD") + delete(s.Containers.ClientApp.EnvVars, "VCL_DEBUG") + delete(s.Containers.ClientApp.EnvVars, "LDP_DEBUG") s.Containers.ClientVpp.VppInstance.Disconnect() s.Containers.ClientVpp.VppInstance.Stop() s.Containers.ClientVpp.Exec(false, "ip addr add dev %s %s", s.Interfaces.Client.Name(), s.Interfaces.Client.Ip4Address) @@ -97,8 +97,8 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() - cmd := "iperf3 -4 -s -p " + s.Ports.Port1 + " --logfile " + s.IperfLogFileName(s.Containers.ServerVpp) - s.StartServerApp(s.Containers.ServerVpp, "iperf3", cmd, srvCh, stopServerCh) + cmd := "iperf3 -4 -s -p " + s.Ports.Port1 + " --logfile " + s.IperfLogFileName(s.Containers.ServerApp) + s.StartServerApp(s.Containers.ServerApp, "iperf3", cmd, srvCh, stopServerCh) }() err := <-srvCh @@ -107,7 +107,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() cmd := "iperf3 -c " + serverVethAddress + " -l 1460 -b 10g -J -p " + s.Ports.Port1 + " " + extraClientArgs - s.StartClientApp(s.Containers.ClientVpp, cmd, clnCh, clnRes) + s.StartClientApp(s.Containers.ClientApp, cmd, clnCh, clnRes) }() s.AssertChannelClosed(time.Minute*4, clnCh) diff --git a/extras/hs-test/topo-containers/2peerVethLdp.yaml b/extras/hs-test/topo-containers/2peerVethLdp.yaml deleted file mode 100644 index bd6e63a945..0000000000 --- a/extras/hs-test/topo-containers/2peerVethLdp.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -volumes: - - volume: &server-vol - host-dir: "$HST_VOLUME_DIR/server-share" - container-dir: "/tmp/server-share" - is-default-work-dir: true - - volume: &client-vol - host-dir: "$HST_VOLUME_DIR/client-share" - container-dir: "/tmp/client-share" - is-default-work-dir: true - -containers: - - name: "server-vpp" - volumes: - - <<: *server-vol - - name: "client-vpp" - volumes: - - <<: *client-vol From e82ba2f5ef80d43f3b154bd555ffe44bc2b9311d Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 31 May 2025 17:13:44 -0400 Subject: [PATCH 015/313] vcl: improve select handling of vpp detachment Type: improvement Change-Id: I85df1da32e3d9a06051175385a75818d8ec5b29d Signed-off-by: Florin Coras --- src/vcl/vppcom.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 6f84178de7..35af7f7304 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -2813,8 +2813,13 @@ vppcom_select_eventfd (vcl_worker_t * wrk, int n_bits, for (i = 0; i < n_mq_evts; i++) { - if (PREDICT_FALSE (wrk->mq_events[i].data.u32 == ~0)) + if (PREDICT_FALSE (wrk->mq_events[i].data.u32 >= VCL_EP_PIPEFD_EVT)) { + if (wrk->mq_events[i].data.u32 == VCL_EP_PIPEFD_EVT) + { + vcl_api_retry_attach (wrk); + continue; + } vcl_api_handle_disconnect (wrk); continue; } From d536ed0c34378c61daec1ef32fe8d93333df2dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Ganne?= Date: Wed, 28 May 2025 14:09:45 +0200 Subject: [PATCH 016/313] ipsec: fix debug assert with ipv6 checksum offload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since adding ASSERT() for checksum offload flags we need to make sure the relevant flags are set before calling vnet_buffer_offload_flags_set() Type: fix Fixes: 7e00099480ab4d2c9353b8b5ed8d516e33abdd24 Change-Id: I521ae77f1d2e6a73deef5168473dd3e857257101 Signed-off-by: Benoît Ganne --- src/vnet/ipsec/esp_encrypt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vnet/ipsec/esp_encrypt.c b/src/vnet/ipsec/esp_encrypt.c index 1f2cc24307..a56f2a22cc 100644 --- a/src/vnet/ipsec/esp_encrypt.c +++ b/src/vnet/ipsec/esp_encrypt.c @@ -592,9 +592,9 @@ set_ip6_udp_cksum_offload (vlib_buffer_t *b, i16 l3_hdr_offset, { vnet_buffer (b)->l3_hdr_offset = l3_hdr_offset; vnet_buffer (b)->l4_hdr_offset = l4_hdr_offset; - vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_UDP_CKSUM); b->flags |= (VNET_BUFFER_F_IS_IP6 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID | VNET_BUFFER_F_L4_HDR_OFFSET_VALID); + vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_UDP_CKSUM); } always_inline uword From 62f81cda7d2cd1d79e831677535b7b9fa89983d9 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 2 Jun 2025 03:35:49 -0400 Subject: [PATCH 017/313] hs-test: fix show error in ldp test Type: test Change-Id: I5c690c374d8089b231c0029d25bd352aba113893 Signed-off-by: Matus Fabian --- extras/hs-test/infra/suite_ldp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index f97a79c1d5..0baeacd505 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -103,8 +103,8 @@ func (s *LdpSuite) TeardownTest() { if CurrentSpecReport().Failed() { s.CollectIperfLogs(s.Containers.ServerApp) s.CollectRedisServerLogs(s.Containers.ServerApp) - s.Log(s.Containers.ServerApp.VppInstance.Vppctl("show error")) - s.Log(s.Containers.ServerApp.VppInstance.Vppctl("show error")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("show error")) + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("show error")) } for _, container := range s.StartedContainers { From 459cdbd19b7a91f931c1cc59790c496ff1c9c2cc Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 31 May 2025 17:42:38 -0400 Subject: [PATCH 018/313] hs-test: ldp test server logging improvements - dump stderr and stdout to a file instead of asking iperf to dump to file - handle vcl/ldp debug logs when parsing iperf json output Type: improvement Change-Id: I535915315416e83c569f668f984ea9204d744174 Signed-off-by: Florin Coras --- extras/hs-test/ldp_test.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 8f9982a2aa..c30bc27055 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -97,7 +97,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() - cmd := "iperf3 -4 -s -p " + s.Ports.Port1 + " --logfile " + s.IperfLogFileName(s.Containers.ServerApp) + cmd := "sh -c \"iperf3 -4 -s -p " + s.Ports.Port1 + " > " + s.IperfLogFileName(s.Containers.ServerApp) + " 2>&1\"" s.StartServerApp(s.Containers.ServerApp, "iperf3", cmd, srvCh, stopServerCh) }() @@ -112,7 +112,25 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { s.AssertChannelClosed(time.Minute*4, clnCh) output := <-clnRes - result := s.ParseJsonIperfOutput(output) + // VCL/LDP debugging can pollute output so find the first occurrence of a curly brace to locate the start of JSON data + jsonStart := -1 + jsonEnd := len(output) + braceCount := 0 + for i := 0; i < len(output); i++ { + if output[i] == '{' { + if jsonStart == -1 { + jsonStart = i + } + braceCount++ + } else if output[i] == '}' { + braceCount-- + if braceCount == 0 { + jsonEnd = i + 1 + break + } + } + } + result := s.ParseJsonIperfOutput(output[jsonStart:jsonEnd]) s.LogJsonIperfOutput(result) return result From 2685c09e50f977e65910d8b047106c9a1eef0b3d Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 2 Jun 2025 00:31:22 -0400 Subject: [PATCH 019/313] vcl: handle pthread cleanups in vls For historic reasons vcl registers for atexit and pthread exit cleanup callback functions. Since vls is the one handling thread detection, move thread cleanup logic there as well. This also avoids potential issues if main/first pthreads exists before the subsequent spawned ones as it may lead to premature vcl worker cleanup. Type: improvement Change-Id: Id7a5c186b48f1e4c60ced635d918b5d4b4143fa6 Signed-off-by: Florin Coras --- src/vcl/vcl_locked.c | 41 +++++++++++++++++++++++++++++++++++++++++ src/vcl/vcl_private.c | 22 ---------------------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index 7ba9fab25f..aaf47c8f9e 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -156,6 +156,8 @@ typedef struct vls_main_ vls_main_t *vlsm; +static pthread_key_t vls_mt_pthread_stop_key; + typedef enum { VLS_RPC_STATE_INIT, @@ -323,6 +325,37 @@ vls_mt_add (void) /* Only allow new pthread to be cancled in vls_mt_mq_lock */ if (vlsl->vls_mt_n_threads >= 2) pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); + + if (pthread_setspecific (vls_mt_pthread_stop_key, vcl_worker_get_current ())) + VDBG (0, "failed to setup key value"); +} + +static void +vls_mt_del (void *arg) +{ + vcl_worker_t *wrk = (vcl_worker_t *) arg; + + VDBG (0, "vls worker %u vcl worker %u nthreads %u cleaning up pthread", + vlsl->vls_wrk_index, vcl_get_worker_index (), vlsl->vls_mt_n_threads); + + if (wrk != vcl_worker_get_current ()) + { + VDBG (0, "vls_mt_del called with wrong worker %u != %u", wrk->wrk_index, + vcl_get_worker_index ()); + return; + } + + vlsl->vls_mt_n_threads -= 1; + + if (vls_mt_wrk_supported ()) + { + vppcom_worker_unregister (); + } + else + { + if (!vlsl->vls_mt_n_threads) + vppcom_worker_unregister (); + } } static inline void @@ -2138,6 +2171,12 @@ vls_app_create (char *app_name) { int rv; + if (pthread_key_create (&vls_mt_pthread_stop_key, vls_mt_del)) + { + VDBG (0, "failed to add pthread cleanup function"); + return -1; + } + if ((rv = vppcom_app_create (app_name))) return rv; @@ -2153,6 +2192,8 @@ vls_app_create (char *app_name) vls_worker_alloc (); vlsl->vls_wrk_index = vcl_get_worker_index (); vlsl->vls_mt_n_threads = 1; + if (pthread_setspecific (vls_mt_pthread_stop_key, vcl_worker_get_current ())) + VDBG (0, "failed to setup key value"); clib_rwlock_init (&vlsl->vls_pool_lock); vls_mt_locks_init (); vcm->wrk_rpc_fn = vls_rpc_handler; diff --git a/src/vcl/vcl_private.c b/src/vcl/vcl_private.c index 33ce7eb6c7..ea82c26803 100644 --- a/src/vcl/vcl_private.c +++ b/src/vcl/vcl_private.c @@ -15,8 +15,6 @@ #include -static pthread_key_t vcl_worker_stop_key; - vcl_mq_evt_conn_t * vcl_mq_evt_conn_alloc (vcl_worker_t * wrk) { @@ -172,22 +170,6 @@ vcl_worker_cleanup (vcl_worker_t * wrk, u8 notify_vpp) clib_spinlock_unlock (&vcm->workers_lock); } -static void -vcl_worker_cleanup_cb (void *arg) -{ - vcl_worker_t *wrk; - u32 wrk_index; - - wrk_index = vcl_get_worker_index (); - wrk = vcl_worker_get_if_valid (wrk_index); - if (!wrk) - return; - - vcl_worker_cleanup (wrk, 1 /* notify vpp */ ); - vcl_set_worker_index (~0); - VDBG (0, "cleaned up worker %u", wrk_index); -} - void vcl_worker_detached_start_signal_mq (vcl_worker_t *wrk) { @@ -379,10 +361,6 @@ vcl_worker_register_with_vpp (void) clib_spinlock_unlock (&vcm->workers_lock); return -1; } - if (pthread_key_create (&vcl_worker_stop_key, vcl_worker_cleanup_cb)) - VDBG (0, "failed to add pthread cleanup function"); - if (pthread_setspecific (vcl_worker_stop_key, &wrk->thread_id)) - VDBG (0, "failed to setup key value"); clib_spinlock_unlock (&vcm->workers_lock); From 87a2643014942412574abdf33b3c070c0d65d287 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 21 May 2025 11:23:41 +0200 Subject: [PATCH 020/313] hash: add ipv4 and ipv6 only hash Type: improvement Change-Id: Ia210a7a128521d4d81dba0fe3f09c87e0bec7dd8 Signed-off-by: Damjan Marion --- src/vnet/hash/crc32_5tuple.c | 43 +++++++++++++++++++++++++++--------- src/vnet/hash/hash.c | 10 +++++---- src/vnet/hash/hash.h | 4 +++- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/vnet/hash/crc32_5tuple.c b/src/vnet/hash/crc32_5tuple.c index 2cdb19440c..dd0e54c3e7 100644 --- a/src/vnet/hash/crc32_5tuple.c +++ b/src/vnet/hash/crc32_5tuple.c @@ -48,17 +48,18 @@ compute_ip4_key (ip4_header_t *ip) return clib_crc32c_u64 (hash, ((u64) pr << 32) | l4hdr); } static_always_inline u32 -compute_ip_key (void *p) +compute_ip_key (void *p, int ip4, int ip6) { - if ((((u8 *) p)[0] & 0xf0) == 0x40) + if (ip4 && (((u8 *) p)[0] & 0xf0) == 0x40) return compute_ip4_key (p); - else if ((((u8 *) p)[0] & 0xf0) == 0x60) + if (ip6 && (((u8 *) p)[0] & 0xf0) == 0x60) return compute_ip6_key (p); return 0; } -void -vnet_crc32c_5tuple_ip_func (void **p, u32 *hash, u32 n_packets) +static_always_inline void +vnet_crc32c_5tuple_ip_func_inline (void **p, u32 *hash, u32 n_packets, int ip4, + int ip6) { u32 n_left_from = n_packets; @@ -69,10 +70,10 @@ vnet_crc32c_5tuple_ip_func (void **p, u32 *hash, u32 n_packets) clib_prefetch_load (p[6]); clib_prefetch_load (p[7]); - hash[0] = compute_ip_key (p[0]); - hash[1] = compute_ip_key (p[1]); - hash[2] = compute_ip_key (p[2]); - hash[3] = compute_ip_key (p[3]); + hash[0] = compute_ip_key (p[0], ip4, ip6); + hash[1] = compute_ip_key (p[1], ip4, ip6); + hash[2] = compute_ip_key (p[2], ip4, ip6); + hash[3] = compute_ip_key (p[3], ip4, ip6); hash += 4; n_left_from -= 4; @@ -81,7 +82,7 @@ vnet_crc32c_5tuple_ip_func (void **p, u32 *hash, u32 n_packets) while (n_left_from > 0) { - hash[0] = compute_ip_key (p[0]); + hash[0] = compute_ip_key (p[0], ip4, ip6); hash += 1; n_left_from -= 1; @@ -157,12 +158,32 @@ vnet_crc32c_5tuple_ethernet_func (void **p, u32 *hash, u32 n_packets) } } +static void +vnet_crc32c_5tuple_ip46_func (void **p, u32 *hash, u32 n_packets) +{ + vnet_crc32c_5tuple_ip_func_inline (p, hash, n_packets, 1, 1); +} + +static void +vnet_crc32c_5tuple_ip4_func (void **p, u32 *hash, u32 n_packets) +{ + vnet_crc32c_5tuple_ip_func_inline (p, hash, n_packets, 1, 0); +} + +static void +vnet_crc32c_5tuple_ip6_func (void **p, u32 *hash, u32 n_packets) +{ + vnet_crc32c_5tuple_ip_func_inline (p, hash, n_packets, 0, 1); +} + VNET_REGISTER_HASH_FUNCTION (crc32c_5tuple, static) = { .name = "crc32c-5tuple", .description = "IPv4/IPv6 header and TCP/UDP ports", .priority = 50, .function[VNET_HASH_FN_TYPE_ETHERNET] = vnet_crc32c_5tuple_ethernet_func, - .function[VNET_HASH_FN_TYPE_IP] = vnet_crc32c_5tuple_ip_func, + .function[VNET_HASH_FN_TYPE_IP] = vnet_crc32c_5tuple_ip46_func, + .function[VNET_HASH_FN_TYPE_IP4] = vnet_crc32c_5tuple_ip4_func, + .function[VNET_HASH_FN_TYPE_IP6] = vnet_crc32c_5tuple_ip6_func, }; #endif diff --git a/src/vnet/hash/hash.c b/src/vnet/hash/hash.c index 31693c9889..d2942e14db 100644 --- a/src/vnet/hash/hash.c +++ b/src/vnet/hash/hash.c @@ -29,14 +29,16 @@ vnet_hash_fn_t vnet_hash_default_function (vnet_hash_fn_type_t ftype) { vnet_hash_function_registration_t *hash = vnet_hash_main.hash_registrations; - vnet_hash_function_registration_t *tmp_hash = hash; + vnet_hash_function_registration_t *selected = 0; + while (hash) { - if (hash->priority > tmp_hash->priority) - tmp_hash = hash; + if (hash->function[ftype] && + (!selected || hash->priority > selected->priority)) + selected = hash; hash = hash->next; } - return tmp_hash->function[ftype]; + return selected ? selected->function[ftype] : 0; } vnet_hash_fn_t diff --git a/src/vnet/hash/hash.h b/src/vnet/hash/hash.h index c1eb9475e2..d1168648ee 100644 --- a/src/vnet/hash/hash.h +++ b/src/vnet/hash/hash.h @@ -10,7 +10,9 @@ #define foreach_vnet_hash_fn_types \ _ (ETHERNET, 0, "hash-fn-ethernet") \ - _ (IP, 1, "hash-fn-ip") + _ (IP4, 1, "hash-fn-ip4") \ + _ (IP6, 2, "hash-fn-ip6") \ + _ (IP, 3, "hash-fn-ip") typedef enum { From d63737c1b53c1157fed4a7278ea39d253b7ca4a8 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 2 Jun 2025 13:58:46 -0400 Subject: [PATCH 021/313] hs-test: use base image container for container builds Avoid multiple updates/downloads of dependencies. Type: improvement Change-Id: Ic770dc00a42cb5aa8c780aebb72beef390200363 Signed-off-by: Florin Coras Signed-off-by: Matus Fabian --- extras/hs-test/docker/Dockerfile.ab | 8 +- extras/hs-test/docker/Dockerfile.base | 65 +++++++++ extras/hs-test/docker/Dockerfile.curl | 12 +- extras/hs-test/docker/Dockerfile.h2load | 8 +- extras/hs-test/docker/Dockerfile.nginx | 8 +- extras/hs-test/docker/Dockerfile.nginx-http3 | 14 +- extras/hs-test/docker/Dockerfile.nginx-server | 8 +- extras/hs-test/docker/Dockerfile.vpp | 13 +- extras/hs-test/docker/Dockerfile.wrk | 8 +- extras/hs-test/docker/setup-local-registry.sh | 63 +++++++++ extras/hs-test/infra/hst_suite.go | 1 + extras/hs-test/script/build-images.sh | 128 ++++++++++++++++++ extras/hs-test/script/build_curl.sh | 2 +- extras/hs-test/script/build_hst.sh | 75 ++++------ 14 files changed, 312 insertions(+), 101 deletions(-) create mode 100644 extras/hs-test/docker/Dockerfile.base create mode 100755 extras/hs-test/docker/setup-local-registry.sh create mode 100755 extras/hs-test/script/build-images.sh diff --git a/extras/hs-test/docker/Dockerfile.ab b/extras/hs-test/docker/Dockerfile.ab index 3ed1528c8a..5e975af3b9 100644 --- a/extras/hs-test/docker/Dockerfile.ab +++ b/extras/hs-test/docker/Dockerfile.ab @@ -1,9 +1,7 @@ +# Apache Bench container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y apache2-utils \ - && rm -rf /var/lib/apt/lists/* +# apache2-utils is now installed in the base image ENTRYPOINT ["ab"] diff --git a/extras/hs-test/docker/Dockerfile.base b/extras/hs-test/docker/Dockerfile.base new file mode 100644 index 0000000000..12b7c0588a --- /dev/null +++ b/extras/hs-test/docker/Dockerfile.base @@ -0,0 +1,65 @@ +ARG UBUNTU_VERSION=22.04 +FROM ubuntu:${UBUNTU_VERSION} AS base + +# Set environment variables +ENV DEBIAN_FRONTEND=noninteractive + +# Install common dependencies needed across multiple containers +RUN apt-get update && apt-get install -y --no-install-recommends \ + # Basic utilities + ca-certificates \ + wget \ + gnupg \ + gnupg2 \ + git \ + iproute2 \ + iputils-ping \ + less \ + lsb-release \ + ubuntu-keyring \ + vim \ + # Development & debugging + gdb \ + libunwind-dev \ + # Libraries frequently needed + libapr1 \ + libnl-3-dev \ + libnl-route-3-dev \ + libnuma1 \ + libsubunit0 \ + openssl \ + python3 \ + # Tools used in tests + iperf3 \ + redis \ + redis-tools \ + xz-utils \ + # Tools moved from derived images + apache2-utils \ + nghttp2 \ + wrk + +# Because of http/3 we can't use stock curl in ubuntu 24.04 +ARG TARGETARCH +COPY script/build_curl.sh /build_curl.sh +RUN /build_curl.sh + +# Because of http/3 support we can't use stock nginx in ubuntu 24.04 +RUN curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ +| tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null +RUN echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ + http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \ + | tee /etc/apt/sources.list.d/nginx.list +RUN bash -c 'echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ +| tee /etc/apt/preferences.d/99nginx' + +RUN apt update && apt install -y nginx=1.26.2* \ + # Clean up + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Add a non-root user (useful for containers that need it) +RUN groupadd -r vpp && useradd -r -g vpp -s /bin/bash vpp + +# Set default command +CMD ["/bin/bash"] diff --git a/extras/hs-test/docker/Dockerfile.curl b/extras/hs-test/docker/Dockerfile.curl index 8e9b579aad..b76a9d8da6 100644 --- a/extras/hs-test/docker/Dockerfile.curl +++ b/extras/hs-test/docker/Dockerfile.curl @@ -1,16 +1,10 @@ +# curl container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} +# Note: wget and xz-utils are already in the base image -ARG TARGETARCH - -RUN apt-get update \ - && apt-get install -y xz-utils wget \ - && rm -rf /var/lib/apt/lists/* - -COPY script/build_curl.sh /build_curl.sh COPY resources/curl/* /tmp/ RUN fallocate -l 10MB /tmp/testFile -RUN /build_curl.sh CMD ["/bin/sh"] diff --git a/extras/hs-test/docker/Dockerfile.h2load b/extras/hs-test/docker/Dockerfile.h2load index de9d083c00..40bfc72aae 100644 --- a/extras/hs-test/docker/Dockerfile.h2load +++ b/extras/hs-test/docker/Dockerfile.h2load @@ -1,9 +1,7 @@ +# h2load container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y nghttp2 \ - && rm -rf /var/lib/apt/lists/* +# nghttp2 is now installed in the base image ENTRYPOINT ["h2load"] diff --git a/extras/hs-test/docker/Dockerfile.nginx b/extras/hs-test/docker/Dockerfile.nginx index fc85f00aaa..bc392d5f0c 100644 --- a/extras/hs-test/docker/Dockerfile.nginx +++ b/extras/hs-test/docker/Dockerfile.nginx @@ -1,10 +1,8 @@ +# nginx container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y nginx gdb less libunwind-dev \ - && rm -rf /var/lib/apt/lists/* +# nginx is now installed in the base image COPY resources/nginx/nginx.conf /nginx.conf COPY script/nginx_ldp.sh /usr/bin/nginx_ldp.sh diff --git a/extras/hs-test/docker/Dockerfile.nginx-http3 b/extras/hs-test/docker/Dockerfile.nginx-http3 index bde73e32da..568bd9baab 100644 --- a/extras/hs-test/docker/Dockerfile.nginx-http3 +++ b/extras/hs-test/docker/Dockerfile.nginx-http3 @@ -1,18 +1,6 @@ ARG UBUNTU_VERSION=22.04 -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring libunwind-dev -RUN curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \ -| tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null -RUN echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ - http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \ - | tee /etc/apt/sources.list.d/nginx.list -RUN bash -c 'echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ -| tee /etc/apt/preferences.d/99nginx' - -RUN apt update && apt install -y nginx=1.26.2* +FROM localhost:5000/vpp-test-base:latest COPY resources/nginx/vcl.conf /vcl.conf COPY resources/nginx/nginx_http3.conf /nginx.conf diff --git a/extras/hs-test/docker/Dockerfile.nginx-server b/extras/hs-test/docker/Dockerfile.nginx-server index b245b41e1b..33c004a48b 100644 --- a/extras/hs-test/docker/Dockerfile.nginx-server +++ b/extras/hs-test/docker/Dockerfile.nginx-server @@ -1,10 +1,8 @@ +# nginx server container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y nginx \ - && rm -rf /var/lib/apt/lists/* +# nginx is now installed in the base image COPY resources/nginx/nginx_server_mirroring.conf /nginx.conf COPY script/nginx_server_entrypoint.sh /usr/bin/nginx_server_entrypoint.sh diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp index 0f897c8a0f..69414c80d1 100644 --- a/extras/hs-test/docker/Dockerfile.vpp +++ b/extras/hs-test/docker/Dockerfile.vpp @@ -1,12 +1,9 @@ +# VPP container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y openssl libapr1 libnuma1 libsubunit0 \ - iproute2 libnl-3-dev libnl-route-3-dev python3 iputils-ping \ - vim gdb libunwind-dev redis redis-tools iperf3 \ - && rm -rf /var/lib/apt/lists/* +# We don't need to install these packages as they're in the base image +# Just install anything specific to VPP that isn't in the base ARG OS_ARCH RUN echo "I'm building for $OS_ARCH" @@ -34,6 +31,6 @@ COPY vpp-data/bin/vpp_echo /usr/bin/ COPY vpp-data/bin/vcl_* /usr/bin/ COPY vpp-data/lib/*.so /usr/lib/ -RUN addgroup vpp +# Group already created in base image ENTRYPOINT ["tail", "-f", "/dev/null"] diff --git a/extras/hs-test/docker/Dockerfile.wrk b/extras/hs-test/docker/Dockerfile.wrk index b410873255..ae376c5af8 100644 --- a/extras/hs-test/docker/Dockerfile.wrk +++ b/extras/hs-test/docker/Dockerfile.wrk @@ -1,9 +1,7 @@ +# wrk HTTP benchmarking container that uses the base image ARG UBUNTU_VERSION=22.04 +FROM localhost:5000/vpp-test-base:latest -FROM ubuntu:${UBUNTU_VERSION} - -RUN apt-get update \ - && apt-get install -y wrk \ - && rm -rf /var/lib/apt/lists/* +# wrk is installed in the base image ENTRYPOINT ["wrk"] diff --git a/extras/hs-test/docker/setup-local-registry.sh b/extras/hs-test/docker/setup-local-registry.sh new file mode 100755 index 0000000000..684f858584 --- /dev/null +++ b/extras/hs-test/docker/setup-local-registry.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Script to set up a local Docker registry + +set -e + +# Check if Docker is running +if ! docker info &>/dev/null; then + echo "Error: Docker is not running. Please start Docker and try again." + exit 1 +fi + +# Registry container name +REGISTRY_NAME="local-registry" +REGISTRY_PORT=5000 + +# Check if registry container is already running +if docker container inspect "$REGISTRY_NAME" &>/dev/null; then + echo "=== Local registry '$REGISTRY_NAME' is already running ===" + REGISTRY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$REGISTRY_NAME") + echo "Registry is available at: localhost:$REGISTRY_PORT or $REGISTRY_IP:$REGISTRY_PORT" +else + echo "=== Setting up local Docker registry ===" + + # Create a new registry container + docker run -d \ + --name "$REGISTRY_NAME" \ + --restart=always \ + -p "$REGISTRY_PORT:5000" \ + -v /var/lib/registry:/var/lib/registry \ + registry:2 + + REGISTRY_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$REGISTRY_NAME") + echo "Registry container created successfully!" + echo "Registry is available at: localhost:$REGISTRY_PORT or $REGISTRY_IP:$REGISTRY_PORT" + + # Configure Docker to trust this insecure registry + echo "=== Configuring Docker to trust insecure registry ===" + if [ -f /etc/docker/daemon.json ]; then + # Check if the file already has an insecure-registries entry + if grep -q "insecure-registries" /etc/docker/daemon.json; then + echo "Insecure registries already configured. Please make sure 'localhost:$REGISTRY_PORT' is included." + else + echo "Adding 'localhost:$REGISTRY_PORT' to insecure-registries in /etc/docker/daemon.json" + echo "You may need to restart Docker for changes to take effect" + echo "Please add the following to /etc/docker/daemon.json:" + echo '{ + "insecure-registries": ["localhost:5000"] +}' + fi + else + echo "Creating /etc/docker/daemon.json with insecure-registries configuration" + echo "You may need to restart Docker for changes to take effect" + echo "Please create /etc/docker/daemon.json with the following content:" + echo '{ + "insecure-registries": ["localhost:5000"] +}' + fi +fi + +echo "" +echo "=== Local Registry Setup Complete ===" +echo "To use the local registry, prefix your image tags with 'localhost:$REGISTRY_PORT/'" +echo "For example: localhost:$REGISTRY_PORT/hs-test/vpp:latest" diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index 2fb05a48d8..6190e444d8 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -83,6 +83,7 @@ var reservedPorts = []string{ "4789", "48879", "4790", + "5000", "6633", "6081", "53053", diff --git a/extras/hs-test/script/build-images.sh b/extras/hs-test/script/build-images.sh new file mode 100755 index 0000000000..86398f881b --- /dev/null +++ b/extras/hs-test/script/build-images.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# Build script for all Docker images based on the common base image + +set -e + +# Get default architecture for multi-arch builds +ARCH=${OS_ARCH:-$(dpkg --print-architecture)} + +# Set up buildx configuration +DOCKER_BUILD_DIR="/scratch/docker-build" +DOCKER_CACHE_DIR="${DOCKER_BUILD_DIR}/docker_cache" +DOCKER_HST_BUILDER="hst_builder" + +if [ -d "${DOCKER_BUILD_DIR}" ] ; then + mkdir -p "${DOCKER_CACHE_DIR}" + + # Create buildx builder if it doesn't exist + if ! docker buildx ls --format "{{.Name}}" | grep -q "${DOCKER_HST_BUILDER}"; then + docker buildx create --use \ + --driver-opt env.http_proxy="$HTTP_PROXY" \ + --driver-opt env.https_proxy="$HTTP_PROXY" \ + --driver-opt '"env.no_proxy='"$NO_PROXY"'"' \ + --name=${DOCKER_HST_BUILDER} \ + --driver=docker-container \ + --use --bootstrap || true + fi + + DOCKER_CACHE_ARGS="--builder=${DOCKER_HST_BUILDER} --load --cache-to type=local,dest=${DOCKER_CACHE_DIR},mode=max --cache-from type=local,src=${DOCKER_CACHE_DIR}" +fi + +# Set the tag for the base image +BASE_TAG=${BASE_TAG:-"localhost:5000/vpp-test-base:latest"} + +echo "=== Building base image ===" +# shellcheck disable=2086 +docker buildx build ${DOCKER_CACHE_ARGS} \ + --build-arg UBUNTU_VERSION="${UBUNTU_VERSION:-22.04}" \ + --build-arg http_proxy="$HTTP_PROXY" \ + --build-arg https_proxy="$HTTP_PROXY" \ + --build-arg HTTP_PROXY="$HTTP_PROXY" \ + --build-arg HTTPS_PROXY="$HTTP_PROXY" \ + -t $BASE_TAG -f docker/Dockerfile.base . || { + echo "Error: Failed to build base image" + exit 1 +} + +# Push the base image to the local registry +docker push $BASE_TAG || { + echo "Error: Failed to push base image to local registry" + exit 1 +} + +# Function to build each image +build_image() { + local dockerfile="docker/$1" + local tag=$2 + local add_args="${3:-}" + + if [ ! -f "$dockerfile" ]; then + echo "Warning: Dockerfile $dockerfile doesn't exist, skipping" + return 0 + fi + + echo "=== Building $tag from $dockerfile ===" + echo "Building with architecture: $ARCH" + + # Check if the necessary files for VPP-based images are available + if [[ "$dockerfile" == *"vpp"* || "$dockerfile" == *"nginx"* || "$dockerfile" == *"vcl"* ]]; then + # Check for essential VPP files + for file in vpp-data/bin/vpp vpp-data/lib/*.so; do + if [ ! -e "$file" ]; then + echo "Warning: Required VPP file $file doesn't exist." + fi + done + fi + + # Build the image + # shellcheck disable=2086 + docker build \ + --build-arg UBUNTU_VERSION="${UBUNTU_VERSION:-22.04}" \ + --build-arg OS_ARCH="$ARCH" \ + --build-arg http_proxy="$HTTP_PROXY" \ + --build-arg https_proxy="$HTTP_PROXY" \ + --build-arg HTTP_PROXY="$HTTP_PROXY" \ + --build-arg HTTPS_PROXY="$HTTP_PROXY" \ + $add_args \ + -t "$tag" \ + -f "$dockerfile" . || { + echo "Error: Failed to build $tag" + return 1 + } + + echo "=== Successfully built and pushed $tag ===" +} + +# Build all standard images +echo "=== Building standard images ===" +build_image "Dockerfile.vpp" "hs-test/vpp" +build_image "Dockerfile.nginx" "hs-test/nginx-ldp" +build_image "Dockerfile.nginx-server" "hs-test/nginx-server" +build_image "Dockerfile.h2load" "hs-test/h2load" +build_image "Dockerfile.curl" "hs-test/curl" +build_image "Dockerfile.ab" "hs-test/ab" +build_image "Dockerfile.wrk" "hs-test/wrk" + +# Build HTTP/3 nginx if available +echo "=== Building HTTP/3 nginx image ===" +build_image "Dockerfile.nginx-http3" "hs-test/nginx-http3" + +# Build envoy separately since it doesn't use our base image +echo "=== Building envoy-test ===" +build_image "Dockerfile.envoy" "hs-test/envoy" + +# make cache directory multi-user friendly if it exists +if [ -d "${DOCKER_CACHE_DIR}" ] ; then + chgrp -R docker "${DOCKER_CACHE_DIR}" 2>/dev/null || true + chmod -R g+rwx "${DOCKER_CACHE_DIR}" 2>/dev/null || true +fi + +# cleanup detached images +images=$(docker images --filter "dangling=true" -q --no-trunc) +if [ -n "$images" ]; then + echo "=== Cleaning up dangling images ===" + # shellcheck disable=SC2086 + docker rmi $images || true +fi + +echo "=== All container images built successfully ===" diff --git a/extras/hs-test/script/build_curl.sh b/extras/hs-test/script/build_curl.sh index 1a61b9fd70..ec89379605 100755 --- a/extras/hs-test/script/build_curl.sh +++ b/extras/hs-test/script/build_curl.sh @@ -1,5 +1,5 @@ #!/bin/bash - +set -x wget https://github.com/stunnel/static-curl/releases/download/8.5.0/curl-static-"$TARGETARCH"-8.5.0.tar.xz tar -xvf ./curl-static-"$TARGETARCH"-8.5.0.tar.xz cp curl /usr/bin/curl \ No newline at end of file diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh index ab482b09f6..0e7656354f 100755 --- a/extras/hs-test/script/build_hst.sh +++ b/extras/hs-test/script/build_hst.sh @@ -39,15 +39,16 @@ OS_ARCH="$(uname -m)" DOCKER_BUILD_DIR="/scratch/docker-build" DOCKER_CACHE_DIR="${DOCKER_BUILD_DIR}/docker_cache" -if [ -d "${DOCKER_BUILD_DIR}" ] ; then - mkdir -p "${DOCKER_CACHE_DIR}" - DOCKER_HST_BUILDER="hst_builder" - set -x - if ! docker buildx ls --format "{{.Name}}" | grep -q "${DOCKER_HST_BUILDER}"; then - docker buildx create --use --driver-opt env.http_proxy="$HTTP_PROXY" --driver-opt env.https_proxy="$HTTP_PROXY" --driver-opt '"env.no_proxy='"$NO_PROXY"'"' --name=${DOCKER_HST_BUILDER} --driver=docker-container --use --bootstrap || true +# Set up the local registry before creating containers +echo "=== Setting up local registry ===" +if [ -x "$(dirname "$0")/../docker/setup-local-registry.sh" ]; then + "$(dirname "$0")/../docker/setup-local-registry.sh" +else + echo "Warning: setup-local-registry.sh not found or not executable" + echo "Attempting to create and use local registry at localhost:5000" + if ! docker ps | grep -q "local-registry"; then + docker run -d --restart=always -p 5000:5000 --name local-registry registry:2 fi - set -x - DOCKER_CACHE_ARGS="--builder=${DOCKER_HST_BUILDER} --load --cache-to type=local,dest=${DOCKER_CACHE_DIR},mode=max --cache-from type=local,src=${DOCKER_CACHE_DIR}" fi echo "Taking build objects from ${VPP_BUILD_ROOT}" @@ -74,43 +75,27 @@ if [ "$res" -ne 0 ]; then exit 1 fi -docker_build () { - tag=$1 - dockername=$2 - set -ex - # shellcheck disable=2086 - docker buildx build ${DOCKER_CACHE_ARGS} \ - --build-arg UBUNTU_VERSION \ - --build-arg OS_ARCH="$OS_ARCH" \ - --build-arg http_proxy="$HTTP_PROXY" \ - --build-arg https_proxy="$HTTP_PROXY" \ - --build-arg HTTP_PROXY="$HTTP_PROXY" \ - --build-arg HTTPS_PROXY="$HTTP_PROXY" \ - -t "$tag" -f docker/Dockerfile."$dockername" . - set +ex -} - -docker_build hs-test/vpp vpp -docker_build hs-test/nginx-ldp nginx -docker_build hs-test/nginx-server nginx-server -docker_build hs-test/curl curl -docker_build hs-test/envoy envoy -docker_build hs-test/nginx-http3 nginx-http3 -docker_build hs-test/ab ab -docker_build hs-test/wrk wrk -docker_build hs-test/h2load h2load - -# make it multi-user friendly -if [ -d "${DOCKER_CACHE_DIR}" ] ; then - chgrp -R docker "${DOCKER_CACHE_DIR}" - chmod -R g+rwx "${DOCKER_CACHE_DIR}" -fi - -# cleanup detached images -images=$(docker images --filter "dangling=true" -q --no-trunc) -if [ "$images" != "" ]; then - # shellcheck disable=SC2086 - docker rmi $images +# Use the build-images.sh script to build all containers +echo "=== Building all containers using build-images.sh ===" +( + # Export necessary environment variables for build-images.sh + export BASE_TAG="localhost:5000/vpp-test-base:latest" + export OS_ARCH + export UBUNTU_VERSION + export HTTP_PROXY + export HTTPS_PROXY + export NO_PROXY + export DOCKER_CACHE_DIR="${DOCKER_CACHE_DIR}" + export DOCKER_HST_BUILDER="${DOCKER_HST_BUILDER}" + + # Run the build script + ./script/build-images.sh +) + +# Check if the build was successful +if [ $? -ne 0 ]; then + echo "Failed to build Docker images. Check the output above for errors." + exit 1 fi echo "$current_state_hash" > "$LAST_STATE_FILE" From 8d68794032bf2ea425941a177d6948796d174dab Mon Sep 17 00:00:00 2001 From: Neil McKee Date: Sun, 23 Mar 2025 15:57:13 -0700 Subject: [PATCH 022/313] sflow: add feature-arc at error-drop, drop-monitoring, egress-sampling (First submitted as two separate commits, but is now one). This turns the "error-drop" --> "drop" arc into a feature-arc so that any plugin can insert a step and examine condemned packets before they are freed. The immediate goal is for the sflow plugin to be able export the headers of dropped packets to the sflow collector, as per the sflow standard. However it seems clear that the existing pcap-drop code could also be moved to a plugin, which would help to simplify the core vnet/interface_output.c. The sflow agent is rounded out to support egress-sampling and packet-drop monitoring. The egress-sampling is achieved by inserting a node on the interface-output feature arc. Packet samples taken here are forwarded on the same FIFO to the main thread, but marked as egress samples so that the samples are written to a separate netlink PSAMPLE group number, which indicated to hsflowd that these are egress samples. Note that when you random-sample both ingress and egress packets at 1:N it is statistically the same as if you were sampling ingress packets at 1:N and egress packets at 1:N independently. The sflow standard does not allow a different value of N for ingress and egress on the same interface, so we are free to take advantage of this. The new node on the newly introduced "error-drop" feature-arc is responsible for passing the headers of dropped packets to the main thread too. These are not sampled. Instead we use a deliberately shallow FIFO to ensure that under chronic conditions of high packet loss these discard events will have negligible impact if the main thread is not servicing the FIFO. The sflow standard sets a rate-limit for the export of discard events which will almost certainly be much lower (typically around 100 per second) so even if we can only sustain a peak of, say, 1000 per second being written to netlink DROPMON that is more than enough. The hsflowd mod_dropmon will apply the configured sflow rate-limit and throw away the excess messages before they are actually sent to the sflow collector. For this reason it is not considered necessary for the vpp CLI to set an explicit rate-limit. The netlink PSAMPLE, DROPMON and USERSOCK code has been factored and corrected for style, but the new implementation behaves the same way in that there is no heap allocation, and iovectors are used to assemble the netlink messages from their headers and attributes. New tests are added to confirm that (1) a single dropped packet is delivered to the sflow_drop node, send to the DROPMON netlink channel, and counted correctly when sflow drop-monitoring is enabled, and that (2) when bidirectional packet-sampling is enabled samples are taken both at ingress and at egress. The terms "rx" for ingress, "tx" for egress and "both" for bidrectional were settled on for brevity and because they appear elsewhere in VPP, however the code still uses terms like "ingress", "egress" and "bidirectional" to be consistent with sFlow standard documents. The new CLI options are: vpp> sflow drop-monitoring enable|disable vpp> sflow direction rx|tx|both And the "show sflow" output has been enhanced to reflect this. The defaults are as follows: vpp> show sflow show sflow sflow sampling-rate 10000 sflow direction rx sflow polling-interval 20 sflow header-bytes 128 sflow drop-monitoring disable Status interfaces enabled: 0 packet samples sent: 0 packet samples dropped: 0 counter samples sent: 0 counter samples dropped: 0 drop samples sent: 0 drop samples dropped: 0 (rebased on 5/12/2025) Type: improvement Change-Id: I831e803fa41874965bc9c32516f655b7ae837719 Signed-off-by: Neil McKee --- src/plugins/sflow/CMakeLists.txt | 4 + src/plugins/sflow/node.c | 197 ++++++++- src/plugins/sflow/sflow.api | 92 ++++- src/plugins/sflow/sflow.c | 560 ++++++++++++++++++++++---- src/plugins/sflow/sflow.h | 79 +++- src/plugins/sflow/sflow_common.h | 9 + src/plugins/sflow/sflow_dropmon.c | 164 ++++++++ src/plugins/sflow/sflow_dropmon.h | 78 ++++ src/plugins/sflow/sflow_netlink.c | 480 ++++++++++++++++++++++ src/plugins/sflow/sflow_netlink.h | 130 ++++++ src/plugins/sflow/sflow_psample.c | 619 +++++++---------------------- src/plugins/sflow/sflow_psample.h | 65 ++- src/plugins/sflow/sflow_test.c | 131 +++++- src/plugins/sflow/sflow_usersock.c | 320 ++++++--------- src/plugins/sflow/sflow_usersock.h | 51 ++- src/vnet/interface.h | 1 + src/vnet/interface_output.c | 20 +- test/test_sflow_drop.py | 149 +++++++ test/test_sflow_egress.py | 145 +++++++ 19 files changed, 2449 insertions(+), 845 deletions(-) create mode 100644 src/plugins/sflow/sflow_dropmon.c create mode 100644 src/plugins/sflow/sflow_dropmon.h create mode 100644 src/plugins/sflow/sflow_netlink.c create mode 100644 src/plugins/sflow/sflow_netlink.h create mode 100644 test/test_sflow_drop.py create mode 100644 test/test_sflow_egress.py diff --git a/src/plugins/sflow/CMakeLists.txt b/src/plugins/sflow/CMakeLists.txt index c966fcc448..37f626142b 100644 --- a/src/plugins/sflow/CMakeLists.txt +++ b/src/plugins/sflow/CMakeLists.txt @@ -29,6 +29,10 @@ add_vpp_plugin(sflow sflow_psample_fields.h sflow_usersock.c sflow_usersock.h + sflow_dropmon.h + sflow_dropmon.c + sflow_netlink.c + sflow_netlink.h MULTIARCH_SOURCES node.c diff --git a/src/plugins/sflow/node.c b/src/plugins/sflow/node.c index 5182643883..47da22cf24 100644 --- a/src/plugins/sflow/node.c +++ b/src/plugins/sflow/node.c @@ -52,6 +52,8 @@ format_sflow_trace (u8 *s, va_list *args) } vlib_node_registration_t sflow_node; +vlib_node_registration_t sflow_egress_node; +vlib_node_registration_t sflow_drop_node; #endif /* CLIB_MARCH_VARIANT */ @@ -65,12 +67,14 @@ static char *sflow_error_strings[] = { typedef enum { - SFLOW_NEXT_ETHERNET_INPUT, + SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT, SFLOW_N_NEXT, } sflow_next_t; -VLIB_NODE_FN (sflow_node) -(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +static_always_inline uword +sflow_node_ingress_egress (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_frame_t *frame, + sflow_enum_sample_t sample_type) { u32 n_left_from, *from, *to_next; sflow_next_t next_index; @@ -107,6 +111,7 @@ VLIB_NODE_FN (sflow_node) ethernet_header_t *en = vlib_buffer_get_current (bN); u32 if_index = vnet_buffer (bN)->sw_if_index[VLIB_RX]; + u32 if_index_out = 0; vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (smp->vnet_main, if_index); if (hw) @@ -117,9 +122,20 @@ VLIB_NODE_FN (sflow_node) // If so, should we ignore the sample? } + if (sample_type == SFLOW_SAMPLETYPE_EGRESS) + { + if_index_out = vnet_buffer (bN)->sw_if_index[VLIB_TX]; + vnet_hw_interface_t *hw_out = + vnet_get_sup_hw_interface (smp->vnet_main, if_index_out); + if (hw_out) + if_index_out = hw_out->hw_if_index; + } + sflow_sample_t sample = { + .sample_type = sample_type, .samplingN = sfwk->smpN, .input_if_index = if_index, + .output_if_index = if_index_out, .sampled_packet_size = bN->current_length + bN->total_length_not_including_first_buffer, .header_bytes = hdr @@ -173,10 +189,10 @@ VLIB_NODE_FN (sflow_node) while (n_left_from >= 8 && n_left_to_next >= 4) { - u32 next0 = SFLOW_NEXT_ETHERNET_INPUT; - u32 next1 = SFLOW_NEXT_ETHERNET_INPUT; - u32 next2 = SFLOW_NEXT_ETHERNET_INPUT; - u32 next3 = SFLOW_NEXT_ETHERNET_INPUT; + u32 next0 = SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT; + u32 next1 = SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT; + u32 next2 = SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT; + u32 next3 = SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT; ethernet_header_t *en0, *en1, *en2, *en3; u32 bi0, bi1, bi2, bi3; vlib_buffer_t *b0, *b1, *b2, *b3; @@ -286,7 +302,7 @@ VLIB_NODE_FN (sflow_node) { u32 bi0; vlib_buffer_t *b0; - u32 next0 = SFLOW_NEXT_ETHERNET_INPUT; + u32 next0 = SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT; ethernet_header_t *en0; /* speculatively enqueue b0 to the current next frame */ @@ -331,6 +347,138 @@ VLIB_NODE_FN (sflow_node) return frame->n_vectors; } +VLIB_NODE_FN (sflow_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return sflow_node_ingress_egress (vm, node, frame, SFLOW_SAMPLETYPE_INGRESS); +} + +VLIB_NODE_FN (sflow_egress_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return sflow_node_ingress_egress (vm, node, frame, SFLOW_SAMPLETYPE_EGRESS); +} + +typedef enum +{ + SFLOW_DROP_NEXT_DROP, + SFLOW_DROP_N_NEXT, +} sflow_drop_next_t; + +static_always_inline void +buffer_rewind_current (vlib_buffer_t *bN) +{ + /* + * Typically, we'll need to rewind the buffer + * if l2_hdr_offset is valid, make sure to rewind to the start of + * the L2 header. This may not be the buffer start in case we pop-ed + * vlan tags. + * Otherwise, rewind to buffer start and hope for the best. + */ + /* + * If the packet was rewritten the start may be somewhere + * in buffer->pre_data, which comes before buffer->data. In + * other words, the buffer->current_data index can be negative. + */ + if (bN->flags & VNET_BUFFER_F_L2_HDR_OFFSET_VALID) + { + if (bN->current_data > vnet_buffer (bN)->l2_hdr_offset) + vlib_buffer_advance (bN, vnet_buffer (bN)->l2_hdr_offset - + bN->current_data); + } + else if (bN->current_data > 0) + { + vlib_buffer_advance (bN, (word) -bN->current_data); + } +} + +VLIB_NODE_FN (sflow_drop_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + u32 n_left_from, *from, *to_next, n_left_to_next; + sflow_drop_next_t next_index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + + sflow_main_t *smp = &sflow_main; + uword thread_index = os_get_thread_index (); + sflow_per_thread_data_t *sfwk = + vec_elt_at_index (smp->per_thread_data, thread_index); + + for (u32 pkt = n_left_from; pkt > 0; --pkt) + { + vlib_buffer_t *bN = vlib_get_buffer (vm, from[pkt - 1]); + buffer_rewind_current (bN); + // drops are subject to header_bytes limit too + u32 hdr = bN->current_length; + if (hdr > smp->headerB) + hdr = smp->headerB; + ethernet_header_t *en = vlib_buffer_get_current (bN); + // Where did this packet come in originally? + // (Doesn't have to be known) + u32 if_index = vnet_buffer (bN)->sw_if_index[VLIB_RX]; + if (if_index) + { + vnet_hw_interface_t *hw = + vnet_get_sup_hw_interface (smp->vnet_main, if_index); + if (hw) + if_index = hw->hw_if_index; + } + // queue the discard sample for the main thread + sflow_sample_t discard = { .sample_type = SFLOW_SAMPLETYPE_DISCARD, + .input_if_index = if_index, + .sampled_packet_size = + bN->current_length + + bN->total_length_not_including_first_buffer, + .header_bytes = hdr, + // .header_protocol = 0, + .drop_reason = bN->error }; + sfwk->dsmp++; // drop-samples + memcpy (discard.header, en, hdr); + if (PREDICT_FALSE ( + !sflow_drop_fifo_enqueue (&sfwk->drop_fifo, &discard))) + sfwk->ddrp++; // drop-sample drops + } + + /* the rest of this is boilerplate code to pass packets on - typically to + "drop" */ + /* TODO: put back tracing code? */ + /* TODO: process 2 or 4 at a time? */ + /* TODO: by using this variant of the pipeline are we assuming that + we are in a feature arc where frames are not converging or dividing? Just + processing through a linear list of nodes that will each pass the whole + frame of buffers on unchanged ("lighting fools the way to dusty death"). + And if so, how do we make that assumption explicit? + To improve the flexibility would we have to go back and change the way + that interface_output.c (error-drop) launches the frame along the arc + in the first place? + */ + next_index = node->cached_next_index; + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 next0 = SFLOW_DROP_NEXT_DROP; + /* enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + b0 = vlib_get_buffer (vm, bi0); + /* do this to always pass on to the next node on feature arc */ + vnet_feature_next (&next0, b0); + } + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + return frame->n_vectors; +} + #ifndef CLIB_MARCH_VARIANT VLIB_REGISTER_NODE (sflow_node) = { @@ -343,7 +491,38 @@ VLIB_REGISTER_NODE (sflow_node) = .n_next_nodes = SFLOW_N_NEXT, /* edit / add dispositions here */ .next_nodes = { - [SFLOW_NEXT_ETHERNET_INPUT] = "ethernet-input", + [SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT] = "ethernet-input", + }, +}; + +VLIB_REGISTER_NODE (sflow_egress_node) = +{ + .name = "sflow-egress", + .vector_size = sizeof (u32), + .format_trace = format_sflow_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN(sflow_error_strings), + .error_strings = sflow_error_strings, + .n_next_nodes = SFLOW_N_NEXT, + /* edit / add dispositions here */ + .next_nodes = { + [SFLOW_NEXT_ETHERNET_INPUT_OR_INTERFACE_OUTPUT] = "interface-output", + }, +}; + +VLIB_REGISTER_NODE (sflow_drop_node) = +{ + .name = "sflow-drop", + .vector_size = sizeof (u32), + .format_trace = format_sflow_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN(sflow_error_strings), + .error_strings = sflow_error_strings, + .n_next_nodes = SFLOW_DROP_N_NEXT, + /* edit / add dispositions here */ + .next_nodes = { + //[SFLOW_DROP_NEXT_DROP] = "error-drop", + [SFLOW_DROP_NEXT_DROP] = "drop", }, }; #endif /* CLIB_MARCH_VARIANT */ diff --git a/src/plugins/sflow/sflow.api b/src/plugins/sflow/sflow.api index e5f33001e6..57a478b62c 100644 --- a/src/plugins/sflow/sflow.api +++ b/src/plugins/sflow/sflow.api @@ -61,7 +61,7 @@ define sflow_sampling_rate_get { u32 context; }; -/** \brief API go the sflow sampling-rate +/** \brief reply to get the sflow sampling-rate @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @param sampling_N - the current 1-in-N sampling rate @@ -121,7 +121,7 @@ define sflow_polling_interval_get { u32 context; }; -/** \brief API go the sflow polling-interval +/** \brief reply to get the sflow polling-interval @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @param polling_S - current polling interval in seconds @@ -164,7 +164,7 @@ define sflow_header_bytes_get { u32 context; }; -/** \brief API go the sflow header-bytes +/** \brief reply to get the sflow header-bytes @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @param header_B - current maximum header length in bytes @@ -177,6 +177,92 @@ define sflow_header_bytes_get_reply option in_progress; }; +/** @brief API to set sflow direction + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sampling_D - direction +*/ + +autoreply define sflow_direction_set { + /* Client identifier, set from api_main.my_client_index */ + u32 client_index; + + /* Arbitrary context, so client can match reply to request */ + u32 context; + + /* sampling_D */ + u32 sampling_D; +}; + +/** @brief API to get sflow direction + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ + +define sflow_direction_get { + /* Client identifier, set from api_main.my_client_index */ + u32 client_index; + + /* Arbitrary context, so client can match reply to request */ + u32 context; +}; + +/** \brief reply to get the sflow direction + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sampling_D - direction +*/ + +define sflow_direction_get_reply +{ + u32 context; + u32 sampling_D; + option in_progress; +}; + +/** @brief API to set sflow drop-monitoring + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param drop_M - enable drop monitoring +*/ + +autoreply define sflow_drop_monitoring_set { + /* Client identifier, set from api_main.my_client_index */ + u32 client_index; + + /* Arbitrary context, so client can match reply to request */ + u32 context; + + /* drop_M */ + u32 drop_M; +}; + +/** @brief API to get sflow drop-monitoring + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ + +define sflow_drop_monitoring_get { + /* Client identifier, set from api_main.my_client_index */ + u32 client_index; + + /* Arbitrary context, so client can match reply to request */ + u32 context; +}; + +/** \brief reply to get the sflow drop-monitoring + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param drop_M - is drop monitoring enabled +*/ + +define sflow_drop_monitoring_get_reply +{ + u32 context; + u32 drop_M; + option in_progress; +}; + /** \brief Dump sflow enabled interface(s) @param client_index - opaque cookie to identify the sender @param hw_if_index - hw_if_index of a specific interface, or -1 (default) diff --git a/src/plugins/sflow/sflow.c b/src/plugins/sflow/sflow.c index 14d07d6923..3568114d19 100644 --- a/src/plugins/sflow/sflow.c +++ b/src/plugins/sflow/sflow.c @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -178,11 +177,11 @@ update_counters (sflow_main_t *smp, sflow_per_interface_data_t *sfif) stat_segment_data_free (res); vec_free (stats); // send the structure via netlink - SFLOWUSSpec spec = {}; - SFLOWUSSpec_setMsgType (&spec, SFLOW_VPP_MSG_IF_COUNTERS); - SFLOWUSSpec_setAttr (&spec, SFLOW_VPP_ATTR_PORTNAME, hw->name, - vec_len (hw->name)); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_IFINDEX, sfif->sw_if_index); + SFLOWUS *ust = &smp->sflow_usersock; + SFLOWUS_set_msg_type (ust, SFLOW_VPP_MSG_IF_COUNTERS); + SFLOWUS_set_attr (ust, SFLOW_VPP_ATTR_PORTNAME, hw->name, + vec_len (hw->name)); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_IFINDEX, sfif->sw_if_index); if (smp->lcp_itf_pair_get_vif_index_by_phy) { @@ -194,12 +193,11 @@ update_counters (sflow_main_t *smp, sflow_per_interface_data_t *sfif) { // We know the corresponding Linux ifIndex for this interface, so include // that here. - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_OSINDEX, - sfif->linux_if_index); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_OSINDEX, sfif->linux_if_index); } // Report consistent with vpp-snmp-agent - u64 ifSpeed = (hw->link_speed == ~0) ? 0 : (hw->link_speed * 1000); + u64 ifSpeed = (hw->link_speed == ~0) ? 0 : ((u64) hw->link_speed * 1000); if (startsWith (hw->name, "loop") || startsWith (hw->name, "tap")) ifSpeed = 1e9; @@ -215,28 +213,28 @@ update_counters (sflow_main_t *smp, sflow_per_interface_data_t *sfif) u32 operUp = (hw->flags & VNET_HW_INTERFACE_FLAG_LINK_UP) ? 1 : 0; u32 adminUp = (sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) ? 1 : 0; - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_IFSPEED, ifSpeed); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_IFTYPE, ifType); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_IFDIRECTION, ifDirection); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_OPER_UP, operUp); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_ADMIN_UP, adminUp); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_RX_OCTETS, ifCtrs.rx.byts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_TX_OCTETS, ifCtrs.tx.byts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_RX_PKTS, ifCtrs.rx.pkts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_TX_PKTS, ifCtrs.tx.pkts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_RX_MCASTS, ifCtrs.rx.m_pkts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_TX_MCASTS, ifCtrs.tx.m_pkts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_RX_BCASTS, ifCtrs.rx.b_pkts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_TX_BCASTS, ifCtrs.tx.b_pkts); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_RX_ERRORS, ifCtrs.rx.errs); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_TX_ERRORS, ifCtrs.tx.errs); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_RX_DISCARDS, ifCtrs.rx.drps); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_TX_DISCARDS, ifCtrs.tx.drps); - SFLOWUSSpec_setAttr (&spec, SFLOW_VPP_ATTR_HW_ADDRESS, hw->hw_address, - vec_len (hw->hw_address)); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_IFSPEED, ifSpeed); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_IFTYPE, ifType); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_IFDIRECTION, ifDirection); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_OPER_UP, operUp); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_ADMIN_UP, adminUp); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_RX_OCTETS, ifCtrs.rx.byts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_TX_OCTETS, ifCtrs.tx.byts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_RX_PKTS, ifCtrs.rx.pkts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_TX_PKTS, ifCtrs.tx.pkts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_RX_MCASTS, ifCtrs.rx.m_pkts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_TX_MCASTS, ifCtrs.tx.m_pkts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_RX_BCASTS, ifCtrs.rx.b_pkts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_TX_BCASTS, ifCtrs.tx.b_pkts); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_RX_ERRORS, ifCtrs.rx.errs); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_TX_ERRORS, ifCtrs.tx.errs); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_RX_DISCARDS, ifCtrs.rx.drps); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_TX_DISCARDS, ifCtrs.tx.drps); + SFLOWUS_set_attr (ust, SFLOW_VPP_ATTR_HW_ADDRESS, hw->hw_address, + vec_len (hw->hw_address)); smp->unixsock_seq++; - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_SEQ, smp->unixsock_seq); - if (SFLOWUSSpec_send (&smp->sflow_usersock, &spec) < 0) + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_SEQ, smp->unixsock_seq); + if (SFLOWUS_send (ust) < 0) smp->csample_send_drops++; smp->csample_send++; } @@ -259,14 +257,14 @@ total_drops (sflow_main_t *smp) static void send_sampling_status_info (sflow_main_t *smp) { - SFLOWUSSpec spec = {}; + SFLOWUS *ust = &smp->sflow_usersock; u32 all_pipeline_drops = total_drops (smp); - SFLOWUSSpec_setMsgType (&spec, SFLOW_VPP_MSG_STATUS); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_UPTIME_S, smp->now_mono_S); - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_DROPS, all_pipeline_drops); + SFLOWUS_set_msg_type (ust, SFLOW_VPP_MSG_STATUS); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_UPTIME_S, smp->now_mono_S); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_DROPS, all_pipeline_drops); ++smp->unixsock_seq; - SFLOWUSSpec_setAttrInt (&spec, SFLOW_VPP_ATTR_SEQ, smp->unixsock_seq); - SFLOWUSSpec_send (&smp->sflow_usersock, &spec); + SFLOWUS_set_attr_int (ust, SFLOW_VPP_ATTR_SEQ, smp->unixsock_seq); + SFLOWUS_send (ust); } static int @@ -291,8 +289,137 @@ counter_polling_check (sflow_main_t *smp) return polled; } +static void +lowercase_and_replace_white (char *str, int len, char replace) +{ + if (str) + for (int ii = 0; ii < len; ii++) + { + if (isspace (str[ii])) + str[ii] = replace; + else + str[ii] = tolower (str[ii]); + } +} + +static int +compose_trap_str (char *buf, int buf_len, char *str, int str_len) +{ + int prefix_len = strlen (SFLOW_TRAP_PREFIX); + int max_cont_len = buf_len - prefix_len - 1; + int cont_len = (str_len > max_cont_len) ? max_cont_len : str_len; + clib_memcpy_fast (buf, SFLOW_TRAP_PREFIX, prefix_len); + clib_memcpy_fast (buf + prefix_len, str, cont_len); + lowercase_and_replace_white (buf + prefix_len, cont_len, SFLOW_TRAP_WHITE); + buf[prefix_len + cont_len] = '\0'; + return prefix_len + cont_len; +} + +static int +send_packet_sample (vlib_main_t *vm, sflow_main_t *smp, sflow_sample_t *sample) +{ + if (sample->header_bytes > smp->headerB) + { + // We get here if header-bytes setting is reduced dynamically + // and a sample that was in the FIFO appears with a larger + // header. + return 0; + } + SFLOWPS *pst = &smp->sflow_psample; + u32 ps_group, seqNo; + switch (sample->sample_type) + { + case SFLOW_SAMPLETYPE_INGRESS: + ps_group = SFLOW_VPP_PSAMPLE_GROUP_INGRESS; + seqNo = ++smp->psample_seq_ingress; + break; + case SFLOW_SAMPLETYPE_EGRESS: + ps_group = SFLOW_VPP_PSAMPLE_GROUP_EGRESS; + seqNo = ++smp->psample_seq_egress; + break; + default: + return 0; + } + // TODO: is it always ethernet? (affects ifType counter as well) + u16 header_protocol = 1; /* ethernet */ + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_SAMPLE_GROUP, ps_group); + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_IIFINDEX, + sample->input_if_index); + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_OIFINDEX, + sample->output_if_index); + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_ORIGSIZE, + sample->sampled_packet_size); + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_GROUP_SEQ, seqNo); + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_SAMPLE_RATE, + sample->samplingN); + SFLOWPS_set_attr (pst, SFLOWPS_PSAMPLE_ATTR_DATA, sample->header, + sample->header_bytes); + SFLOWPS_set_attr_int (pst, SFLOWPS_PSAMPLE_ATTR_PROTO, header_protocol); + if (SFLOWPS_send (pst) < 0) + return -1; + return 1; +} + +static int +send_discard_sample (vlib_main_t *vm, sflow_main_t *smp, + sflow_sample_t *sample) +{ + SFLOWDM *dmt = &smp->sflow_dropmon; + if (sample->header_bytes > smp->headerB) + { + // We get here if header-bytes setting is reduced dynamically + // and a sample that was in the FIFO appears with a larger + // header. + return 0; + } + if (sample->sample_type != SFLOW_SAMPLETYPE_DISCARD) + { + SFLOW_ERR ("send_discard_sample sample-sample_type=%u", + sample->sample_type); + return 0; + } + vlib_error_main_t *em = &vm->error_main; + if (sample->drop_reason >= vec_len (em->counters_heap)) + return 0; + if (sample->drop_reason >= vec_len (vm->node_main.node_by_error)) + return 0; + u32 err_node_idx = vm->node_main.node_by_error[sample->drop_reason]; + // Are all the ones we want classed as errors, or might some be WARN or INFO? + // if (err->severity == VL_COUNTER_SEVERITY_ERROR) + // set TRAP_GROUP_NAME to "vpp_" + char trap_grp[SFLOW_MAX_TRAP_LEN]; + char trap[SFLOW_MAX_TRAP_LEN]; + vlib_node_t *n = vlib_get_node (vm, err_node_idx); + int trap_grp_len = compose_trap_str (trap_grp, SFLOW_MAX_TRAP_LEN, + (char *) n->name, vec_len (n->name)); + // set TRAP_NAME to "vpp_" + vlib_error_desc_t *err = &em->counters_heap[sample->drop_reason]; + int err_name_len = clib_strnlen (err->name, SFLOW_MAX_TRAP_LEN); + int trap_len = + compose_trap_str (trap, SFLOW_MAX_TRAP_LEN, err->name, err_name_len); + // populate the netlink attributes + u16 origin = NET_DM_ORIGIN_SW; + SFLOWDM_set_attr_int (dmt, NET_DM_ATTR_ORIGIN, origin); + // include NUL termination char in netlink strings. + SFLOWDM_set_attr (dmt, NET_DM_ATTR_HW_TRAP_GROUP_NAME, trap_grp, + trap_grp_len + 1); + SFLOWDM_set_attr (dmt, NET_DM_ATTR_HW_TRAP_NAME, trap, trap_len + 1); + SFLOWDM_set_attr_int (dmt, NET_DM_ATTR_ORIG_LEN, + sample->sampled_packet_size); + SFLOWDM_set_attr_int (dmt, NET_DM_ATTR_TRUNC_LEN, sample->header_bytes); + // TODO: read from header? (really just needs to be non-zero for hsflowd) + u16 proto = 0x0800; + SFLOWDM_set_attr_int (dmt, NET_DM_ATTR_PROTO, proto); + SFLOWDM_set_attr_int (dmt, NET_DM_ATTR_IN_PORT, sample->input_if_index); + SFLOWDM_set_attr (dmt, NET_DM_ATTR_PAYLOAD, sample->header, + sample->header_bytes); + if (SFLOWDM_send (dmt) < 0) + return -1; + return 1; +} + static u32 -read_worker_fifos (sflow_main_t *smp) +read_worker_fifos (vlib_main_t *vm, sflow_main_t *smp) { // Our maximum samples/sec is approximately: // (SFLOW_READ_BATCH * smp->total_threads) / SFLOW_POLL_WAIT_S @@ -321,7 +448,9 @@ read_worker_fifos (sflow_main_t *smp) u32 batch = 0; for (; batch < SFLOW_READ_BATCH; batch++) { + u32 psample_found = 0, dropmon_found = 0; u32 psample_send = 0, psample_send_fail = 0; + u32 dropmon_send = 0, dropmon_send_fail = 0; for (clib_thread_index_t thread_index = 0; thread_index < smp->total_threads; thread_index++) { @@ -330,48 +459,37 @@ read_worker_fifos (sflow_main_t *smp) sflow_sample_t sample; if (sflow_fifo_dequeue (&sfwk->fifo, &sample)) { - if (sample.header_bytes > smp->headerB) - { - // We get here if header-bytes setting is reduced dynamically - // and a sample that was in the FIFO appears with a larger - // header. - continue; - } - SFLOWPSSpec spec = {}; - u32 ps_group = SFLOW_VPP_PSAMPLE_GROUP_INGRESS; - u32 seqNo = ++smp->psample_seq_ingress; - // TODO: is it always ethernet? (affects ifType counter as well) - u16 header_protocol = 1; /* ethernet */ - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_SAMPLE_GROUP, - ps_group); - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_IIFINDEX, - sample.input_if_index); - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_OIFINDEX, - sample.output_if_index); - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_ORIGSIZE, - sample.sampled_packet_size); - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_GROUP_SEQ, - seqNo); - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_SAMPLE_RATE, - sample.samplingN); - SFLOWPSSpec_setAttr (&spec, SFLOWPS_PSAMPLE_ATTR_DATA, - sample.header, sample.header_bytes); - SFLOWPSSpec_setAttrInt (&spec, SFLOWPS_PSAMPLE_ATTR_PROTO, - header_protocol); - psample_send++; - if (SFLOWPSSpec_send (&smp->sflow_psample, &spec) < 0) + psample_found++; + int sent = send_packet_sample (vm, smp, &sample); + if (sent == 1) + psample_send++; + if (sent == -1) psample_send_fail++; } + if (sflow_drop_fifo_dequeue (&sfwk->drop_fifo, &sample)) + { + dropmon_found++; + int sent = send_discard_sample (vm, smp, &sample); + if (sent == 1) + dropmon_send++; + if (sent == -1) + dropmon_send_fail++; + } } - if (psample_send == 0) + if (psample_found == 0 && dropmon_found == 0) { // nothing found on FIFOs this time through, so terminate batch early break; } else { - vlib_node_increment_counter (smp->vlib_main, sflow_node.index, - SFLOW_ERROR_PSAMPLE_SEND, psample_send); + if (psample_send > 0) + { + vlib_node_increment_counter (smp->vlib_main, sflow_node.index, + SFLOW_ERROR_PSAMPLE_SEND, + psample_send); + smp->psample_send += psample_send; + } if (psample_send_fail > 0) { vlib_node_increment_counter (smp->vlib_main, sflow_node.index, @@ -379,6 +497,20 @@ read_worker_fifos (sflow_main_t *smp) psample_send_fail); smp->psample_send_drops += psample_send_fail; } + if (dropmon_send > 0) + { + vlib_node_increment_counter (smp->vlib_main, sflow_node.index, + SFLOW_ERROR_DROPMON_SEND, + dropmon_send); + smp->dropmon_send += dropmon_send; + } + if (dropmon_send_fail > 0) + { + vlib_node_increment_counter (smp->vlib_main, sflow_node.index, + SFLOW_ERROR_DROPMON_SEND_FAIL, + dropmon_send_fail); + smp->dropmon_send_drops += dropmon_send_fail; + } } } return batch; @@ -397,6 +529,8 @@ read_node_counters (sflow_main_t *smp, sflow_err_ctrs_t *ctrs) ctrs->counters[SFLOW_ERROR_PROCESSED] += sfwk->pool; ctrs->counters[SFLOW_ERROR_SAMPLED] += sfwk->smpl; ctrs->counters[SFLOW_ERROR_DROPPED] += sfwk->drop; + ctrs->counters[SFLOW_ERROR_DIPROCESSED] += sfwk->dsmp; + ctrs->counters[SFLOW_ERROR_DIDROPPED] += sfwk->ddrp; } } @@ -412,9 +546,13 @@ static void update_node_counters (sflow_main_t *smp, sflow_err_ctrs_t *prev, sflow_err_ctrs_t *latest) { + // TODO: is it OK to assess all counters against sflow_node or do we + // need to distinguish sflow_drop_node and sflow_egress_node? update_node_cntr (smp, prev, latest, SFLOW_ERROR_PROCESSED); update_node_cntr (smp, prev, latest, SFLOW_ERROR_SAMPLED); update_node_cntr (smp, prev, latest, SFLOW_ERROR_DROPPED); + update_node_cntr (smp, prev, latest, SFLOW_ERROR_DIPROCESSED); + update_node_cntr (smp, prev, latest, SFLOW_ERROR_DIDROPPED); *prev = *latest; // latch for next time } @@ -445,12 +583,20 @@ sflow_process_samples (vlib_main_t *vm, vlib_node_runtime_t *node, // PSAMPLE channel may need extra step (e.g. to learn family_id) // before it is ready to send - EnumSFLOWPSState psState = SFLOWPS_state (&smp->sflow_psample); - if (psState != SFLOWPS_STATE_READY) + EnumSFLOWNLState psState = SFLOWPS_state (&smp->sflow_psample); + if (psState != SFLOWNL_STATE_READY) { SFLOWPS_open_step (&smp->sflow_psample); } + // DROPMON channel may need extra step (e.g. to learn family_id) + // before it is ready to send + EnumSFLOWNLState dmState = SFLOWDM_state (&smp->sflow_dropmon); + if (dmState != SFLOWNL_STATE_READY) + { + SFLOWDM_open_step (&smp->sflow_dropmon); + } + // What we want is a monotonic, per-second clock. This seems to do it // because it is based on the CPU clock. f64 tnow = clib_time_now (&ctm); @@ -465,7 +611,7 @@ sflow_process_samples (vlib_main_t *vm, vlib_node_runtime_t *node, counter_polling_check (smp); } // process samples from workers - read_worker_fifos (smp); + read_worker_fifos (vm, smp); // and sync the global counters sflow_err_ctrs_t latest = {}; @@ -511,7 +657,7 @@ sflow_sampling_start (sflow_main_t *smp) { SFLOW_INFO ("sflow_sampling_start"); - smp->running = 1; + smp->running = true; // Reset this clock so that the per-second netlink status updates // will communicate a restart to hsflowd. This helps to distinguish: // (1) vpp restarted with sFlow off => no status updates (went quiet) @@ -522,13 +668,22 @@ sflow_sampling_start (sflow_main_t *smp) // reset sequence numbers to indicated discontinuity smp->psample_seq_ingress = 0; smp->psample_seq_egress = 0; + smp->psample_send = 0; smp->psample_send_drops = 0; + smp->csample_send = 0; + smp->csample_send_drops = 0; + smp->dropmon_send = 0; + smp->dropmon_send_drops = 0; /* open PSAMPLE netlink channel for writing packet samples */ + SFLOWPS_init (&smp->sflow_psample); SFLOWPS_open (&smp->sflow_psample); /* open USERSOCK netlink channel for writing counters */ + SFLOWUS_init (&smp->sflow_usersock); SFLOWUS_open (&smp->sflow_usersock); - smp->sflow_usersock.group_id = SFLOW_NETLINK_USERSOCK_MULTICAST; + /* open DROPMON netlink channel for writing discard events */ + SFLOWDM_init (&smp->sflow_dropmon); + SFLOWDM_open (&smp->sflow_dropmon); /* set up (or reset) sampling context for each thread */ sflow_set_worker_sampling_state (smp); } @@ -537,15 +692,17 @@ static void sflow_sampling_stop (sflow_main_t *smp) { SFLOW_INFO ("sflow_sampling_stop"); - smp->running = 0; + smp->running = false; SFLOWPS_close (&smp->sflow_psample); SFLOWUS_close (&smp->sflow_usersock); + SFLOWDM_close (&smp->sflow_dropmon); } static void sflow_sampling_start_stop (sflow_main_t *smp) { - int run = (smp->samplingN != 0 && smp->interfacesEnabled != 0); + int run = + ((smp->samplingN != 0 && smp->interfacesEnabled != 0) || smp->dropM); if (run != smp->running) { if (run) @@ -603,8 +760,75 @@ sflow_header_bytes (sflow_main_t *smp, u32 headerB) return 0; } +void +sflow_enable_disable_interface (sflow_main_t *smp, + sflow_per_interface_data_t *sfif) +{ + bool ingress_on = + sfif->sflow_enabled && (smp->samplingD == SFLOW_DIRN_INGRESS || + smp->samplingD == SFLOW_DIRN_BOTH); + bool egress_on = + sfif->sflow_enabled && + (smp->samplingD == SFLOW_DIRN_EGRESS || smp->samplingD == SFLOW_DIRN_BOTH); + bool drop_on = sfif->sflow_enabled && smp->dropM; + bool ingress_enabled = (vnet_feature_is_enabled ("device-input", "sflow", + sfif->sw_if_index) == 1); + bool egress_enabled = + (vnet_feature_is_enabled ("interface-output", "sflow-egress", + sfif->sw_if_index) == 1); + bool drop_enabled = (vnet_feature_is_enabled ("error-drop", "sflow-drop", + sfif->sw_if_index) == 1); + + if (ingress_on != ingress_enabled) + vnet_feature_enable_disable ("device-input", "sflow", sfif->sw_if_index, + ingress_on, 0, 0); + if (egress_on != egress_enabled) + vnet_feature_enable_disable ("interface-output", "sflow-egress", + sfif->sw_if_index, egress_on, 0, 0); + if (drop_on != drop_enabled) + vnet_feature_enable_disable ("error-drop", "sflow-drop", sfif->sw_if_index, + smp->dropM, 0, 0); +} + +void +sflow_enable_disable_all (sflow_main_t *smp) +{ + for (int ii = 0; ii < vec_len (smp->per_interface_data); ii++) + { + sflow_per_interface_data_t *sfif = + vec_elt_at_index (smp->per_interface_data, ii); + if (sfif && sfif->sflow_enabled) + sflow_enable_disable_interface (smp, sfif); + } +} + int -sflow_enable_disable (sflow_main_t *smp, u32 sw_if_index, int enable_disable) +sflow_direction (sflow_main_t *smp, sflow_direction_t samplingD) +{ + if (samplingD != smp->samplingD) + { + // direction changed - tell all active interfaces. + smp->samplingD = samplingD; + sflow_enable_disable_all (smp); + } + return 0; +} + +int +sflow_drop_monitoring (sflow_main_t *smp, bool dropM) +{ + if (dropM != smp->dropM) + { + // drop-monitoring changed. + smp->dropM = dropM; + // Tell all active interfaces. + sflow_enable_disable_all (smp); + } + return 0; +} + +int +sflow_enable_disable (sflow_main_t *smp, u32 sw_if_index, bool enable_disable) { vnet_sw_interface_t *sw; @@ -641,8 +865,7 @@ sflow_enable_disable (sflow_main_t *smp, u32 sw_if_index, int enable_disable) sfif->hw_if_index = sw->hw_if_index; sfif->polled = 0; sfif->sflow_enabled = enable_disable; - vnet_feature_enable_disable ("device-input", "sflow", sw_if_index, - enable_disable, 0, 0); + sflow_enable_disable_interface (smp, sfif); smp->interfacesEnabled += (enable_disable) ? 1 : -1; } @@ -746,20 +969,90 @@ sflow_header_bytes_command_fn (vlib_main_t *vm, unformat_input_t *input, return 0; } +static clib_error_t * +sflow_direction_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + sflow_main_t *smp = &sflow_main; + u32 sampling_D = SFLOW_DIRN_UNDEFINED; + + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "rx")) + sampling_D = SFLOW_DIRN_INGRESS; + else if (unformat (input, "tx")) + sampling_D = SFLOW_DIRN_EGRESS; + else if (unformat (input, "both")) + sampling_D = SFLOW_DIRN_BOTH; + else + break; + } + + if (sampling_D == SFLOW_DIRN_UNDEFINED) + return clib_error_return ( + 0, "Please specify a sampling direction (rx|tx|both)..."); + + rv = sflow_direction (smp, sampling_D); + + switch (rv) + { + case 0: + break; + default: + return clib_error_return (0, "sflow_direction returned %d", rv); + } + return 0; +} + +static clib_error_t * +sflow_drop_monitoring_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + sflow_main_t *smp = &sflow_main; + bool drop_M = true; + + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "disable")) + drop_M = false; + else if (unformat (input, "enable")) + drop_M = true; + else + break; + } + + rv = sflow_drop_monitoring (smp, drop_M); + + switch (rv) + { + case 0: + break; + default: + return clib_error_return (0, "sflow_drop_monitoring returned %d", rv); + } + return 0; +} + static clib_error_t * sflow_enable_disable_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) { sflow_main_t *smp = &sflow_main; u32 sw_if_index = ~0; - int enable_disable = 1; + int enable_disable = true; int rv; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { if (unformat (input, "disable")) - enable_disable = 0; + enable_disable = false; + else if (unformat (input, "enable")) + enable_disable = true; else if (unformat (input, "%U", unformat_vnet_sw_interface, smp->vnet_main, &sw_if_index)) ; @@ -793,6 +1086,23 @@ sflow_enable_disable_command_fn (vlib_main_t *vm, unformat_input_t *input, return 0; } +static const char * +sflow_direction_str (sflow_direction_t direction) +{ + switch (direction) + { + case SFLOW_DIRN_UNDEFINED: + return "undefined"; + case SFLOW_DIRN_INGRESS: + return "rx"; + case SFLOW_DIRN_EGRESS: + return "tx"; + case SFLOW_DIRN_BOTH: + return "both"; + } + return "none"; +} + static clib_error_t * show_sflow_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) @@ -800,9 +1110,12 @@ show_sflow_command_fn (vlib_main_t *vm, unformat_input_t *input, sflow_main_t *smp = &sflow_main; clib_error_t *error = NULL; vlib_cli_output (vm, "sflow sampling-rate %u\n", smp->samplingN); - vlib_cli_output (vm, "sflow sampling-direction ingress\n"); + vlib_cli_output (vm, "sflow direction %s\n", + sflow_direction_str (smp->samplingD)); vlib_cli_output (vm, "sflow polling-interval %u\n", smp->pollingS); vlib_cli_output (vm, "sflow header-bytes %u\n", smp->headerB); + vlib_cli_output (vm, "sflow drop-monitoring %s\n", + smp->dropM ? "enable" : "disable"); u32 itfs_enabled = 0; for (int ii = 0; ii < vec_len (smp->per_interface_data); ii++) { @@ -818,12 +1131,14 @@ show_sflow_command_fn (vlib_main_t *vm, unformat_input_t *input, } vlib_cli_output (vm, "Status\n"); vlib_cli_output (vm, " interfaces enabled: %u\n", itfs_enabled); - vlib_cli_output (vm, " packet samples sent: %u\n", - smp->psample_seq_ingress + smp->psample_seq_egress); + vlib_cli_output (vm, " packet samples sent: %u\n", smp->psample_send); vlib_cli_output (vm, " packet samples dropped: %u\n", total_drops (smp)); vlib_cli_output (vm, " counter samples sent: %u\n", smp->csample_send); vlib_cli_output (vm, " counter samples dropped: %u\n", smp->csample_send_drops); + vlib_cli_output (vm, " drop samples sent: %u\n", smp->dropmon_send); + vlib_cli_output (vm, " drop samples dropped: %u\n", + smp->dropmon_send_drops); return error; } @@ -851,6 +1166,18 @@ VLIB_CLI_COMMAND (sflow_header_bytes_command, static) = { .function = sflow_header_bytes_command_fn, }; +VLIB_CLI_COMMAND (sflow_direction_command, static) = { + .path = "sflow direction", + .short_help = "sflow direction ", + .function = sflow_direction_command_fn, +}; + +VLIB_CLI_COMMAND (sflow_drop_monitoring_command, static) = { + .path = "sflow drop-monitoring", + .short_help = "sflow drop-monitoring ", + .function = sflow_drop_monitoring_command_fn, +}; + VLIB_CLI_COMMAND (show_sflow_command, static) = { .path = "show sflow", .short_help = "show sflow", @@ -865,8 +1192,7 @@ vl_api_sflow_enable_disable_t_handler (vl_api_sflow_enable_disable_t *mp) sflow_main_t *smp = &sflow_main; int rv; - rv = sflow_enable_disable (smp, ntohl (mp->hw_if_index), - (int) (mp->enable_disable)); + rv = sflow_enable_disable (smp, ntohl (mp->hw_if_index), mp->enable_disable); REPLY_MACRO (VL_API_SFLOW_ENABLE_DISABLE_REPLY); } @@ -939,6 +1265,51 @@ vl_api_sflow_header_bytes_get_t_handler (vl_api_sflow_header_bytes_get_t *mp) ({ rmp->header_B = ntohl (smp->headerB); })); } +static void +vl_api_sflow_direction_set_t_handler (vl_api_sflow_direction_set_t *mp) +{ + vl_api_sflow_direction_set_reply_t *rmp; + sflow_main_t *smp = &sflow_main; + int rv; + + rv = sflow_direction (smp, ntohl (mp->sampling_D)); + + REPLY_MACRO (VL_API_SFLOW_DIRECTION_SET_REPLY); +} + +static void +vl_api_sflow_direction_get_t_handler (vl_api_sflow_direction_get_t *mp) +{ + vl_api_sflow_direction_get_reply_t *rmp; + sflow_main_t *smp = &sflow_main; + + REPLY_MACRO_DETAILS2 (VL_API_SFLOW_DIRECTION_GET_REPLY, + ({ rmp->sampling_D = ntohl (smp->samplingD); })); +} + +static void +vl_api_sflow_drop_monitoring_set_t_handler ( + vl_api_sflow_drop_monitoring_set_t *mp) +{ + vl_api_sflow_drop_monitoring_set_reply_t *rmp; + sflow_main_t *smp = &sflow_main; + int rv; + rv = sflow_drop_monitoring (smp, ntohl (mp->drop_M)); + + REPLY_MACRO (VL_API_SFLOW_DROP_MONITORING_SET_REPLY); +} + +static void +vl_api_sflow_drop_monitoring_get_t_handler ( + vl_api_sflow_drop_monitoring_get_t *mp) +{ + vl_api_sflow_drop_monitoring_get_reply_t *rmp; + sflow_main_t *smp = &sflow_main; + + REPLY_MACRO_DETAILS2 (VL_API_SFLOW_DROP_MONITORING_GET_REPLY, + ({ rmp->drop_M = ntohl (smp->dropM); })); +} + static void send_sflow_interface_details (vpe_api_main_t *am, vl_api_registration_t *reg, u32 context, const u32 hw_if_index) @@ -1001,6 +1372,8 @@ sflow_init (vlib_main_t *vm) smp->samplingN = SFLOW_DEFAULT_SAMPLING_N; smp->pollingS = SFLOW_DEFAULT_POLLING_S; smp->headerB = SFLOW_DEFAULT_HEADER_BYTES; + smp->samplingD = SFLOW_DIRN_INGRESS; + smp->dropM = false; /* Add our API messages to the global name_crc hash table */ smp->msg_id_base = setup_message_id_table (); @@ -1030,6 +1403,19 @@ VNET_FEATURE_INIT (sflow, static) = { .runs_before = VNET_FEATURES ("ethernet-input"), }; +VNET_FEATURE_INIT (sflow_egress, static) = { + .arc_name = "interface-output", + .node_name = "sflow-egress", + .runs_before = VNET_FEATURES ("interface-output-arc-end"), +}; + +/* Add myself to the feature arc */ +VNET_FEATURE_INIT (sflow_drop, static) = { + .arc_name = "error-drop", + .node_name = "sflow-drop", + .runs_before = VNET_FEATURES ("drop"), +}; + VLIB_PLUGIN_REGISTER () = { .version = VPP_BUILD_VER, .description = "sFlow random packet sampling", diff --git a/src/plugins/sflow/sflow.h b/src/plugins/sflow/sflow.h index 0ec5ac9068..ca087e58e5 100644 --- a/src/plugins/sflow/sflow.h +++ b/src/plugins/sflow/sflow.h @@ -22,8 +22,10 @@ #include #include #include +#include #include #include +#include #define SFLOW_DEFAULT_SAMPLING_N 10000 #define SFLOW_DEFAULT_POLLING_S 20 @@ -33,6 +35,7 @@ #define SFLOW_HEADER_BYTES_STEP 32 #define SFLOW_FIFO_DEPTH 2048 // must be power of 2 +#define SFLOW_DROP_FIFO_DEPTH 4 // must be power of 2 #define SFLOW_POLL_WAIT_S 0.001 #define SFLOW_READ_BATCH 100 @@ -45,8 +48,12 @@ _ (PROCESSED, "sflow packets processed") \ _ (SAMPLED, "sflow packets sampled") \ _ (DROPPED, "sflow packets dropped") \ + _ (DIPROCESSED, "sflow discards processed") \ + _ (DIDROPPED, "sflow discards dropped") \ _ (PSAMPLE_SEND, "sflow PSAMPLE sent") \ - _ (PSAMPLE_SEND_FAIL, "sflow PSAMPLE send failed") + _ (PSAMPLE_SEND_FAIL, "sflow PSAMPLE send failed") \ + _ (DROPMON_SEND, "sflow DROPMON sent") \ + _ (DROPMON_SEND_FAIL, "sflow DROPMON send failed") typedef enum { @@ -64,15 +71,29 @@ typedef struct /* packet sample */ typedef struct { + u32 sample_type; u32 samplingN; u32 input_if_index; u32 output_if_index; u32 header_protocol; u32 sampled_packet_size; u32 header_bytes; + u32 drop_reason; u8 header[SFLOW_MAX_HEADER_BYTES]; } sflow_sample_t; +typedef enum +{ + SFLOW_SAMPLETYPE_UNDEFINED = 0, + SFLOW_SAMPLETYPE_INGRESS, + SFLOW_SAMPLETYPE_EGRESS, + SFLOW_SAMPLETYPE_DISCARD +} sflow_enum_sample_t; + +#define SFLOW_MAX_TRAP_LEN 64 +#define SFLOW_TRAP_WHITE '_' +#define SFLOW_TRAP_PREFIX "vpp_" + // Define SPSC FIFO for sending samples worker-to-main. // (I did try to use VPP svm FIFO, but couldn't // understand why it was sometimes going wrong). @@ -104,8 +125,49 @@ sflow_fifo_dequeue (sflow_fifo_t *fifo, sflow_sample_t *sample) u32 curr_tx = clib_atomic_load_acq_n (&fifo->tx); if (curr_rx == curr_tx) return false; // empty - memcpy (sample, &fifo->samples[curr_rx], sizeof (*sample)); u32 next_rx = SFLOW_FIFO_NEXT (curr_rx); + memcpy (sample, &fifo->samples[next_rx], sizeof (*sample)); + clib_atomic_store_rel_n (&fifo->rx, next_rx); + return true; +} + +// Define SPSC DROP_FIFO for sending discard events worker-to-main. +// For now the only difference from the FIFO above is the max depth, +// but it proved awkward to make depth a variable and this way gives +// us more freedom to experiment, e.g. with rate-limiting. +// We also might decide to separate sflow_sample_t into +// sflow_sample_t and sflow_drop_t if their fields diverge, +// and doing this keeps that option open. +typedef struct +{ + volatile u32 tx; // can change under consumer's feet + volatile u32 rx; // can change under producer's feet + sflow_sample_t samples[SFLOW_DROP_FIFO_DEPTH]; +} sflow_drop_fifo_t; + +#define SFLOW_DROP_FIFO_NEXT(slot) ((slot + 1) & (SFLOW_DROP_FIFO_DEPTH - 1)) +static inline int +sflow_drop_fifo_enqueue (sflow_drop_fifo_t *fifo, sflow_sample_t *sample) +{ + u32 curr_rx = clib_atomic_load_acq_n (&fifo->rx); + u32 curr_tx = fifo->tx; // clib_atomic_load_acq_n(&fifo->tx); + u32 next_tx = SFLOW_DROP_FIFO_NEXT (curr_tx); + if (next_tx == curr_rx) + return false; // full + memcpy (&fifo->samples[next_tx], sample, sizeof (*sample)); + clib_atomic_store_rel_n (&fifo->tx, next_tx); + return true; +} + +static inline int +sflow_drop_fifo_dequeue (sflow_drop_fifo_t *fifo, sflow_sample_t *sample) +{ + u32 curr_rx = fifo->rx; // clib_atomic_load_acq_n(&fifo->rx); + u32 curr_tx = clib_atomic_load_acq_n (&fifo->tx); + if (curr_rx == curr_tx) + return false; // empty + u32 next_rx = SFLOW_DROP_FIFO_NEXT (curr_rx); + memcpy (sample, &fifo->samples[next_rx], sizeof (*sample)); clib_atomic_store_rel_n (&fifo->rx, next_rx); return true; } @@ -119,8 +181,12 @@ typedef struct u32 seed; u32 smpl; u32 drop; + u32 dsmp; + u32 ddrp; CLIB_CACHE_LINE_ALIGN_MARK (_fifo); sflow_fifo_t fifo; + CLIB_CACHE_LINE_ALIGN_MARK (_drop_fifo); + sflow_drop_fifo_t drop_fifo; } sflow_per_thread_data_t; typedef u32 (*IfIndexLookupFn) (u32); @@ -139,6 +205,8 @@ typedef struct u32 samplingN; u32 pollingS; u32 headerB; + sflow_direction_t samplingD; + bool dropM; u32 total_threads; sflow_per_interface_data_t *per_interface_data; sflow_per_thread_data_t *per_thread_data; @@ -147,6 +215,8 @@ typedef struct SFLOWPS sflow_psample; /* usersock channel (periodic counters) */ SFLOWUS sflow_usersock; + /* dropmon channel (rate-limited discards) */ + SFLOWDM sflow_dropmon; #define SFLOW_NETLINK_USERSOCK_MULTICAST 29 /* dropmon channel (packet drops) */ // SFLOWDM sflow_dropmon; @@ -155,13 +225,16 @@ typedef struct u32 now_mono_S; /* running control */ - int running; + bool running; u32 interfacesEnabled; /* main-thread counters */ u32 psample_seq_ingress; u32 psample_seq_egress; + u32 psample_send; u32 psample_send_drops; + u32 dropmon_send; + u32 dropmon_send_drops; u32 csample_send; u32 csample_send_drops; u32 unixsock_seq; diff --git a/src/plugins/sflow/sflow_common.h b/src/plugins/sflow/sflow_common.h index 26f306b574..766033d57f 100644 --- a/src/plugins/sflow/sflow_common.h +++ b/src/plugins/sflow/sflow_common.h @@ -31,6 +31,15 @@ typedef struct int sflow_enabled; } sflow_per_interface_data_t; +/* mirror sflow_direction enum in sflow.api */ +typedef enum +{ + SFLOW_DIRN_UNDEFINED = 0, + SFLOW_DIRN_INGRESS, + SFLOW_DIRN_EGRESS, + SFLOW_DIRN_BOTH +} sflow_direction_t; + #endif /* __included_sflow_common_h__ */ /* diff --git a/src/plugins/sflow/sflow_dropmon.c b/src/plugins/sflow/sflow_dropmon.c new file mode 100644 index 0000000000..d953c6b819 --- /dev/null +++ b/src/plugins/sflow/sflow_dropmon.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2025 InMon Corp. + * Licensed 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/*_________________---------------------------__________________ + _________________ SFLOWDM_init __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWDM_init (SFLOWDM *dmt) +{ + dmt->nl.id = SFLOWNL_DROPMON; + memset (dmt->fam_name, 0, SFLOWDM_FAM_FOOTPRINT); + memcpy (dmt->fam_name, SFLOWDM_FAM, SFLOWDM_FAM_LEN); + dmt->nl.family_name = dmt->fam_name; + dmt->nl.family_len = SFLOWDM_FAM_LEN; + dmt->nl.join_group_id = NET_DM_GRP_ALERT; + dmt->nl.attr = dmt->attr; + dmt->nl.attr_max = SFLOWDM_ATTRS - 1; + dmt->nl.iov = dmt->iov; + dmt->nl.iov_max = SFLOWDM_IOV_FRAGS - 1; + dmt->nl.state = SFLOWNL_STATE_INIT; + return dmt->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWDM_open __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWDM_open (SFLOWDM *dmt) +{ + if (dmt->nl.state == SFLOWNL_STATE_UNDEFINED) + SFLOWDM_init (dmt); + if (dmt->nl.nl_sock == 0) + { + dmt->nl.nl_sock = sflow_netlink_generic_open (&dmt->nl); + if (dmt->nl.nl_sock > 0) + sflow_netlink_generic_get_family (&dmt->nl); + } + return (dmt->nl.nl_sock > 0); +} + +/*_________________---------------------------__________________ + _________________ SFLOWDM_close __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWDM_close (SFLOWDM *dmt) +{ + return (sflow_netlink_close (&dmt->nl) == 0); +} + +/*_________________---------------------------__________________ + _________________ SFLOWDM_state __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWDM_state (SFLOWDM *dmt) +{ + return dmt->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWDM_open_step __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWDM_open_step (SFLOWDM *dmt) +{ + switch (dmt->nl.state) + { + case SFLOWNL_STATE_UNDEFINED: + SFLOWDM_init (dmt); + break; + case SFLOWNL_STATE_INIT: + SFLOWDM_open (dmt); + break; + case SFLOWNL_STATE_OPEN: + sflow_netlink_generic_get_family (&dmt->nl); + break; + case SFLOWNL_STATE_WAIT_FAMILY: + sflow_netlink_read (&dmt->nl); + break; + case SFLOWNL_STATE_READY: + break; + } + return dmt->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWDMSpec_setAttr __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWDM_set_attr (SFLOWDM *dmt, int field, void *val, int len) +{ + return sflow_netlink_set_attr (&dmt->nl, field, val, len); +} + +/*_________________---------------------------__________________ + _________________ SFLOWDMSpec_send __________________ + -----------------___________________________------------------ +*/ + +int +SFLOWDM_send (SFLOWDM *dmt) +{ + dmt->nl.ge.cmd = NET_DM_CMD_PACKET_ALERT; + dmt->nl.ge.version = 0; // NET_DM_CFG_VERSION==0 but no NET_DM_CMD_VERSION + int status = sflow_netlink_send_attrs (&dmt->nl, true); + sflow_netlink_reset_attrs (&dmt->nl); + if (status <= 0) + { + SFLOW_ERR ("DROPMON strerror(errno) = %s; errno = %d\n", + strerror (errno), errno); + } + return status; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_dropmon.h b/src/plugins/sflow/sflow_dropmon.h new file mode 100644 index 0000000000..06ff890cda --- /dev/null +++ b/src/plugins/sflow/sflow_dropmon.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 InMon Corp. + * Licensed 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. + */ + +#ifndef __included_sflow_dropmon_h__ +#define __included_sflow_dropmon_h__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SFLOWDM_DROPMON_READNL_RCV_BUF 8192 +#define SFLOWDM_DROPMON_READNL_SND_BUF 1000000 + +#ifndef NET_DM_GENL_NAME +#define NET_DM_GENL_NAME "NET_DM" +#endif + +#define SFLOWDM_FAM NET_DM_GENL_NAME +#define SFLOWDM_FAM_LEN sizeof (SFLOWDM_FAM) +#define SFLOWDM_FAM_FOOTPRINT NLMSG_ALIGN (SFLOWDM_FAM_LEN) +#define SFLOWDM_ATTRS NET_DM_ATTR_MAX + 1 +#define SFLOWDM_IOV_FRAGS ((2 * SFLOWDM_ATTRS) + 2) + +typedef struct _SFLOWDM +{ + SFLOWNL nl; + char fam_name[SFLOWDM_FAM_FOOTPRINT]; + SFLOWNLAttr attr[SFLOWDM_ATTRS]; + struct iovec iov[SFLOWDM_IOV_FRAGS]; +} SFLOWDM; + +EnumSFLOWNLState SFLOWDM_init (SFLOWDM *dmt); +bool SFLOWDM_open (SFLOWDM *dmt); +bool SFLOWDM_close (SFLOWDM *dmt); +EnumSFLOWNLState SFLOWDM_state (SFLOWDM *dmt); +EnumSFLOWNLState SFLOWDM_open_step (SFLOWDM *dmt); + +bool SFLOWDM_set_attr (SFLOWDM *dmt, int field, void *buf, int len); +#define SFLOWDM_set_attr_int(dmt, field, val) \ + SFLOWDM_set_attr ((dmt), (field), &(val), sizeof (val)) + +int SFLOWDM_send (SFLOWDM *dmt); + +#endif /* __included_sflow_dropmon_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_netlink.c b/src/plugins/sflow/sflow_netlink.c new file mode 100644 index 0000000000..2c8949f13a --- /dev/null +++ b/src/plugins/sflow/sflow_netlink.c @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2025 InMon Corp. + * Licensed 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/*_________________---------------------------__________________ + _________________ fcntl utils __________________ + -----------------___________________________------------------ +*/ + +void +sflow_netlink_set_nonblocking (int fd) +{ + // set the socket to non-blocking + int fdFlags = fcntl (fd, F_GETFL); + fdFlags |= O_NONBLOCK; + if (fcntl (fd, F_SETFL, fdFlags) < 0) + { + SFLOW_ERR ("fcntl(O_NONBLOCK) failed: %s\n", strerror (errno)); + } +} + +void +sflow_netlink_set_close_on_exec (int fd) +{ + // make sure it doesn't get inherited, e.g. when we fork a script + int fdFlags = fcntl (fd, F_GETFD); + fdFlags |= FD_CLOEXEC; + if (fcntl (fd, F_SETFD, fdFlags) < 0) + { + SFLOW_ERR ("fcntl(F_SETFD=FD_CLOEXEC) failed: %s\n", strerror (errno)); + } +} + +int +sflow_netlink_set_send_buffer (int fd, int requested) +{ + int txbuf = 0; + socklen_t txbufsiz = sizeof (txbuf); + if (getsockopt (fd, SOL_SOCKET, SO_SNDBUF, &txbuf, &txbufsiz) < 0) + { + SFLOW_ERR ("getsockopt(SO_SNDBUF) failed: %s", strerror (errno)); + } + if (txbuf < requested) + { + txbuf = requested; + if (setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &txbuf, sizeof (txbuf)) < 0) + { + SFLOW_WARN ("setsockopt(SO_TXBUF=%d) failed: %s", requested, + strerror (errno)); + } + // see what we actually got + txbufsiz = sizeof (txbuf); + if (getsockopt (fd, SOL_SOCKET, SO_SNDBUF, &txbuf, &txbufsiz) < 0) + { + SFLOW_ERR ("getsockopt(SO_SNDBUF) failed: %s", strerror (errno)); + } + } + return txbuf; +} + +/*_________________---------------------------__________________ + _________________ generic_pid __________________ + -----------------___________________________------------------ + choose a 32-bit id that is likely to be unique even if more + than one module in this process wants to bind a netlink socket +*/ + +u32 +sflow_netlink_generic_pid (u32 mod_id) +{ + return ((mod_id << 16) + getpid ()); +} + +/*_________________---------------------------__________________ + _________________ generic_open __________________ + -----------------___________________________------------------ +*/ + +int +sflow_netlink_generic_open (SFLOWNL *nl) +{ + nl->nl_sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); + if (nl->nl_sock < 0) + { + SFLOW_ERR ("nl_sock open failed: %s\n", strerror (errno)); + return -1; + } + // bind to a suitable id + struct sockaddr_nl sa = { .nl_family = AF_NETLINK, + .nl_pid = sflow_netlink_generic_pid (nl->id) }; + if (bind (nl->nl_sock, (struct sockaddr *) &sa, sizeof (sa)) < 0) + { + SFLOW_ERR ("sflow_netlink_generic_open: bind failed: sa.nl_pid=%u " + "sock=%d id=%d: %s\n", + sa.nl_pid, nl->nl_sock, nl->id, strerror (errno)); + } + sflow_netlink_set_nonblocking (nl->nl_sock); + sflow_netlink_set_close_on_exec (nl->nl_sock); + sflow_netlink_set_send_buffer (nl->nl_sock, SFLOWNL_SND_BUF); + nl->state = SFLOWNL_STATE_OPEN; + return nl->nl_sock; +} + +/*_________________---------------------------__________________ + _________________ usersock_open __________________ + -----------------___________________________------------------ +*/ + +int +sflow_netlink_usersock_open (SFLOWNL *nl) +{ + nl->nl_sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK); + if (nl->nl_sock < 0) + { + SFLOW_ERR ("nl_sock open failed: %s\n", strerror (errno)); + return -1; + } + sflow_netlink_set_nonblocking (nl->nl_sock); + sflow_netlink_set_close_on_exec (nl->nl_sock); + nl->state = SFLOWNL_STATE_OPEN; + return nl->nl_sock; +} + +/*_________________---------------------------__________________ + _________________ close __________________ + -----------------___________________________------------------ +*/ + +int +sflow_netlink_close (SFLOWNL *nl) +{ + int err = 0; + if (nl->nl_sock > 0) + { + err = close (nl->nl_sock); + if (err == 0) + { + nl->nl_sock = 0; + } + else + { + SFLOW_ERR ("sflow_netlink_close: returned %d : %s\n", err, + strerror (errno)); + } + } + nl->state = SFLOWNL_STATE_INIT; + return err; +} + +/*_________________---------------------------__________________ + _________________ set_attr __________________ + -----------------___________________________------------------ +*/ + +bool +sflow_netlink_set_attr (SFLOWNL *nl, int field, void *val, int len) +{ + SFLOWNLAttr *psa = &nl->attr[field]; + if (psa->included) + return false; + psa->included = true; + psa->attr.nla_type = field; + psa->attr.nla_len = sizeof (psa->attr) + len; + int len_w_pad = NLMSG_ALIGN (len); + psa->val.iov_len = len_w_pad; + psa->val.iov_base = val; + nl->n_attrs++; + nl->attrs_len += sizeof (psa->attr); + nl->attrs_len += len_w_pad; + return true; +} + +/*_________________---------------------------__________________ + _________________ generic_send_cmd __________________ + -----------------___________________________------------------ +*/ + +int +sflow_netlink_generic_send_cmd (int sockfd, u32 mod_id, int type, int cmd, + int req_type, void *req, int req_len, + int req_footprint, u32 seqNo) +{ + struct nlmsghdr nlh = {}; + struct genlmsghdr ge = {}; + struct nlattr attr = {}; + + attr.nla_len = sizeof (attr) + req_len; + attr.nla_type = req_type; + + ge.cmd = cmd; + ge.version = 1; + + nlh.nlmsg_len = NLMSG_LENGTH (req_footprint + sizeof (attr) + sizeof (ge)); + nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh.nlmsg_type = type; + nlh.nlmsg_seq = seqNo; + nlh.nlmsg_pid = sflow_netlink_generic_pid (mod_id); + + struct iovec iov[4] = { { .iov_base = &nlh, .iov_len = sizeof (nlh) }, + { .iov_base = &ge, .iov_len = sizeof (ge) }, + { .iov_base = &attr, .iov_len = sizeof (attr) }, + { .iov_base = req, .iov_len = req_footprint } }; + + struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; + struct msghdr msg = { .msg_name = &sa, + .msg_namelen = sizeof (sa), + .msg_iov = iov, + .msg_iovlen = 4 }; + return sendmsg (sockfd, &msg, 0); +} + +/*_________________---------------------------__________________ + _________________ send_attrs __________________ + -----------------___________________________------------------ +*/ + +int +sflow_netlink_send_attrs (SFLOWNL *nl, bool ge) +{ + if (ge) + { + nl->nlh.nlmsg_len = NLMSG_LENGTH (sizeof (nl->ge) + nl->attrs_len); + nl->nlh.nlmsg_type = nl->family_id; + nl->nlh.nlmsg_pid = sflow_netlink_generic_pid (nl->id); + } + else + { + nl->nlh.nlmsg_len = NLMSG_LENGTH (nl->attrs_len); + nl->nlh.nlmsg_pid = getpid (); + } + + nl->nlh.nlmsg_flags = 0; + nl->nlh.nlmsg_seq = ++nl->nl_seq; + + struct iovec *iov = nl->iov; + u32 frag = 0; + iov[frag].iov_base = &nl->nlh; + iov[frag].iov_len = sizeof (nl->nlh); + frag++; + if (ge) + { + iov[frag].iov_base = &nl->ge; + iov[frag].iov_len = sizeof (nl->ge); + frag++; + } + int nn = 0; + for (u32 ii = 0; ii <= nl->attr_max; ii++) + { + SFLOWNLAttr *psa = &nl->attr[ii]; + if (psa->included) + { + nn++; + iov[frag].iov_base = &psa->attr; + iov[frag].iov_len = sizeof (psa->attr); + frag++; + iov[frag] = psa->val; // struct copy + frag++; + } + } + ASSERT (nn == nl->n_attrs); + + struct sockaddr_nl da = { .nl_family = AF_NETLINK, + .nl_groups = (1 << (nl->group_id - 1)) }; + + struct msghdr msg = { .msg_name = &da, + .msg_namelen = sizeof (da), + .msg_iov = iov, + .msg_iovlen = frag }; + + return sendmsg (nl->nl_sock, &msg, 0); +} + +/*_________________---------------------------__________________ + _________________ reset_attrs __________________ + -----------------___________________________------------------ +*/ + +void +sflow_netlink_reset_attrs (SFLOWNL *nl) +{ + for (u32 ii = 0; ii <= nl->attr_max; ii++) + nl->attr[ii].included = false; + nl->n_attrs = 0; + nl->attrs_len = 0; +} + +/*_________________---------------------------__________________ + _________________ generic_get_family __________________ + -----------------___________________________------------------ +*/ + +void +sflow_netlink_generic_get_family (SFLOWNL *nl) +{ + int status = sflow_netlink_generic_send_cmd ( + nl->nl_sock, nl->id, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, + CTRL_ATTR_FAMILY_NAME, nl->family_name, nl->family_len, + NLMSG_ALIGN (nl->family_len), ++nl->nl_seq); + if (status >= 0) + nl->state = SFLOWNL_STATE_WAIT_FAMILY; +} + +/*_________________---------------------------__________________ + _________________ generic_read __________________ + -----------------___________________________------------------ +*/ + +void +sflow_netlink_generic_read (SFLOWNL *nl, struct nlmsghdr *nlh, int numbytes) +{ + int msglen = nlh->nlmsg_len; + if (msglen > numbytes) + { + SFLOW_ERR ("generic read msglen too long\n"); + return; + } + if (msglen < (NLMSG_HDRLEN + GENL_HDRLEN + NLA_HDRLEN)) + { + SFLOW_ERR ("generic read msglen too short\n"); + return; + } + char *msg = (char *) NLMSG_DATA (nlh); + msglen -= NLMSG_HDRLEN; + struct genlmsghdr *genl = (struct genlmsghdr *) msg; + SFLOW_DBG ("generic netlink CMD = %u\n", genl->cmd); + msglen -= GENL_HDRLEN; + + struct nlattr *attr0 = (struct nlattr *) (msg + GENL_HDRLEN); + for (int attrs_len = msglen; SFNLA_OK (attr0, attrs_len); + attr0 = SFNLA_NEXT (attr0, attrs_len)) + { + switch (attr0->nla_type) + { + case CTRL_ATTR_VERSION: + nl->genetlink_version = *(u32 *) SFNLA_DATA (attr0); + break; + case CTRL_ATTR_FAMILY_ID: + nl->family_id = *(u16 *) SFNLA_DATA (attr0); + SFLOW_DBG ("generic family id: %u\n", nl->family_id); + break; + case CTRL_ATTR_FAMILY_NAME: + SFLOW_DBG ("generic family name: %s\n", (char *) SFNLA_DATA (attr0)); + break; + case CTRL_ATTR_MCAST_GROUPS: + { + struct nlattr *attr1 = (struct nlattr *) SFNLA_DATA (attr0); + for (int attr0_len = SFNLA_PAYLOAD (attr0); + SFNLA_OK (attr1, attr0_len); + attr1 = SFNLA_NEXT (attr1, attr0_len)) + { + char *grp_name = NULL; + u32 grp_id = 0; + struct nlattr *attr2 = SFNLA_DATA (attr1); + for (int attr1_len = SFNLA_PAYLOAD (attr1); + SFNLA_OK (attr2, attr1_len); + attr2 = SFNLA_NEXT (attr2, attr1_len)) + { + + switch (attr2->nla_type) + { + case CTRL_ATTR_MCAST_GRP_NAME: + grp_name = SFNLA_DATA (attr2); + SFLOW_DBG ("netlink multicast group: %s\n", grp_name); + break; + case CTRL_ATTR_MCAST_GRP_ID: + grp_id = *(u32 *) SFNLA_DATA (attr2); + SFLOW_DBG ("netlink multicast group id: %u\n", grp_id); + break; + } + } + if (nl->group_id == 0 && grp_name && + (((nl->join_group_id != 0) && + grp_id == nl->join_group_id) || + ((nl->join_group_name != NULL) && + !strcmp (grp_name, nl->join_group_name)))) + { + SFLOW_DBG ("netlink found group %s=%u\n", grp_name, + grp_id); + nl->group_id = grp_id; + // We don't need to actually join the group if we + // are only sending to it. + } + } + } + break; + default: + SFLOW_DBG ("netlink attr type: %u (nested=%u) len: %u\n", + attr0->nla_type, attr0->nla_type & NLA_F_NESTED, + attr0->nla_len); + break; + } + } + if (nl->family_id && nl->group_id) + { + SFLOW_DBG ("netlink state->READY\n"); + nl->state = SFLOWNL_STATE_READY; + } +} + +/*_________________---------------------------__________________ + _________________ sflow_netlink_read __________________ + -----------------___________________________------------------ +*/ + +void +sflow_netlink_read (SFLOWNL *nl) +{ + uint8_t recv_buf[SFLOWNL_RCV_BUF]; + memset (recv_buf, 0, SFLOWNL_RCV_BUF); // for coverity + int numbytes = recv (nl->nl_sock, recv_buf, sizeof (recv_buf), 0); + if (numbytes <= sizeof (struct nlmsghdr)) + { + SFLOW_ERR ("sflow_netlink_read returned %d : %s\n", numbytes, + strerror (errno)); + return; + } + for (struct nlmsghdr *nlh = (struct nlmsghdr *) recv_buf; + NLMSG_OK (nlh, numbytes); nlh = NLMSG_NEXT (nlh, numbytes)) + { + if (nlh->nlmsg_type == NLMSG_DONE) + break; + if (nlh->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err_msg = (struct nlmsgerr *) NLMSG_DATA (nlh); + if (err_msg->error == 0) + { + SFLOW_DBG ("received Netlink ACK\n"); + } + else + { + SFLOW_ERR ("error in netlink message: %d : %s\n", err_msg->error, + strerror (-err_msg->error)); + } + return; + } + if (nlh->nlmsg_type == NETLINK_GENERIC) + { + sflow_netlink_generic_read (nl, nlh, numbytes); + } + else if (nlh->nlmsg_type == nl->family_id) + { + // We are write-only, don't need to read these. + } + } +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_netlink.h b/src/plugins/sflow/sflow_netlink.h new file mode 100644 index 0000000000..03306584f5 --- /dev/null +++ b/src/plugins/sflow/sflow_netlink.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 InMon Corp. + * Licensed 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. + */ + +#ifndef __included_sflow_netlink_h__ +#define __included_sflow_netlink_h__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SFLOWNL_RCV_BUF 8192 +#define SFLOWNL_SND_BUF 1000000 + +typedef enum +{ + SFLOWNL_USERSOCK = 1, + SFLOWNL_PSAMPLE, + SFLOWNL_DROPMON, +} EnumSFLOWNLMod; + +typedef enum +{ + SFLOWNL_STATE_UNDEFINED = 0, + SFLOWNL_STATE_INIT, + SFLOWNL_STATE_OPEN, + SFLOWNL_STATE_WAIT_FAMILY, + SFLOWNL_STATE_READY +} EnumSFLOWNLState; + +typedef struct _SFLOWNLAttr +{ + bool included : 1; + struct nlattr attr; + struct iovec val; +} SFLOWNLAttr; + +typedef struct _SFLOWNL +{ + // connect + EnumSFLOWNLState state; + EnumSFLOWNLMod id; + int nl_sock; + u32 nl_seq; + u32 genetlink_version; + u16 family_id; + u32 group_id; + // setup + char *family_name; + u32 family_len; + u32 join_group_id; + char *join_group_name; + // msg + struct nlmsghdr nlh; + struct genlmsghdr ge; + SFLOWNLAttr *attr; + u32 attr_max; + u32 n_attrs; + u32 attrs_len; + u32 iov_max; + struct iovec *iov; +} SFLOWNL; + +void sflow_netlink_set_nonblocking (int fd); +void sflow_netlink_set_close_on_exec (int fd); +int sflow_netlink_set_send_buffer (int fd, int requested); +u32 sflow_netlink_generic_pid (u32 mod_id); +int sflow_netlink_generic_open (SFLOWNL *nl); +int sflow_netlink_usersock_open (SFLOWNL *nl); +int sflow_netlink_close (SFLOWNL *nl); +bool sflow_netlink_set_attr (SFLOWNL *nl, int field, void *val, int len); + +#define sflow_netlink_set_attr_int(nl, field, val) \ + sflow_netlink_set_attr ((nl), (field), &(val), sizeof (val)) + +int sflow_netlink_generic_send_cmd (int sockfd, u32 mod_id, int type, int cmd, + int req_type, void *req, int req_len, + int req_footprint, u32 seqNo); +int sflow_netlink_send_attrs (SFLOWNL *nl, bool ge); +void sflow_netlink_reset_attrs (SFLOWNL *nl); +void sflow_netlink_generic_get_family (SFLOWNL *nl); +void sflow_netlink_generic_read (SFLOWNL *nl, struct nlmsghdr *nlh, + int numbytes); +void sflow_netlink_read (SFLOWNL *nl); + +/* Provide the netlink attribute-walking macros that are strangely + * missing from netlink.h, so we can walk attributes the same way + * as we walk messages (and satisfy static-analysis algorithms that + * are wary of looping over "tainted" input). + */ +#define SFNLA_OK(nla, len) \ + ((len) > 0 && (nla)->nla_len >= sizeof (struct nlattr) && \ + (nla)->nla_len <= (len)) +#define SFNLA_NEXT(nla, attrlen) \ + ((attrlen) -= NLA_ALIGN ((nla)->nla_len), \ + (struct nlattr *) (((char *) (nla)) + NLA_ALIGN ((nla)->nla_len))) +#define SFNLA_LENGTH(len) (NLA_ALIGN (sizeof (struct nlattr)) + (len)) +#define SFNLA_SPACE(len) (NLA_ALIGN (SFNLA_LENGTH (len))) +#define SFNLA_DATA(nla) ((void *) (((char *) (nla)) + SFNLA_LENGTH (0))) +#define SFNLA_PAYLOAD(nla) ((int) ((nla)->nla_len) - SFNLA_LENGTH (0)) +#endif /* __included_sflow_netlink_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_psample.c b/src/plugins/sflow/sflow_psample.c index 41df454d99..468433871e 100644 --- a/src/plugins/sflow/sflow_psample.c +++ b/src/plugins/sflow/sflow_psample.c @@ -29,490 +29,143 @@ #include #include +#include #include - /*_________________---------------------------__________________ - _________________ fcntl utils __________________ - -----------------___________________________------------------ - */ - - static void - setNonBlocking (int fd) - { - // set the socket to non-blocking - int fdFlags = fcntl (fd, F_GETFL); - fdFlags |= O_NONBLOCK; - if (fcntl (fd, F_SETFL, fdFlags) < 0) - { - SFLOW_ERR ("fcntl(O_NONBLOCK) failed: %s\n", strerror (errno)); - } - } - - static void - setCloseOnExec (int fd) - { - // make sure it doesn't get inherited, e.g. when we fork a script - int fdFlags = fcntl (fd, F_GETFD); - fdFlags |= FD_CLOEXEC; - if (fcntl (fd, F_SETFD, fdFlags) < 0) - { - SFLOW_ERR ("fcntl(F_SETFD=FD_CLOEXEC) failed: %s\n", strerror (errno)); - } - } - - static int - setSendBuffer (int fd, int requested) - { - int txbuf = 0; - socklen_t txbufsiz = sizeof (txbuf); - if (getsockopt (fd, SOL_SOCKET, SO_SNDBUF, &txbuf, &txbufsiz) < 0) - { - SFLOW_ERR ("getsockopt(SO_SNDBUF) failed: %s", strerror (errno)); - } - if (txbuf < requested) - { - txbuf = requested; - if (setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &txbuf, sizeof (txbuf)) < 0) - { - SFLOW_WARN ("setsockopt(SO_TXBUF=%d) failed: %s", requested, - strerror (errno)); - } - // see what we actually got - txbufsiz = sizeof (txbuf); - if (getsockopt (fd, SOL_SOCKET, SO_SNDBUF, &txbuf, &txbufsiz) < 0) - { - SFLOW_ERR ("getsockopt(SO_SNDBUF) failed: %s", strerror (errno)); - } - } - return txbuf; - } - - /*_________________---------------------------__________________ - _________________ generic_pid __________________ - -----------------___________________________------------------ - choose a 32-bit id that is likely to be unique even if more - than one module in this process wants to bind a netlink socket - */ - - static u32 - generic_pid (u32 mod_id) - { - return (mod_id << 16) | getpid (); - } - - /*_________________---------------------------__________________ - _________________ generic_open __________________ - -----------------___________________________------------------ - */ - - static int - generic_open (u32 mod_id) - { - int nl_sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); - if (nl_sock < 0) - { - SFLOW_ERR ("nl_sock open failed: %s\n", strerror (errno)); - return -1; - } - // bind to a suitable id - struct sockaddr_nl sa = { .nl_family = AF_NETLINK, - .nl_pid = generic_pid (mod_id) }; - if (bind (nl_sock, (struct sockaddr *) &sa, sizeof (sa)) < 0) - SFLOW_ERR ("generic_open: bind failed: %s\n", strerror (errno)); - setNonBlocking (nl_sock); - setCloseOnExec (nl_sock); - return nl_sock; - } - - /*_________________---------------------------__________________ - _________________ generic_send __________________ - -----------------___________________________------------------ - */ - - static int - generic_send (int sockfd, u32 mod_id, int type, int cmd, int req_type, - void *req, int req_len, int req_footprint, u32 seqNo) - { - struct nlmsghdr nlh = {}; - struct genlmsghdr ge = {}; - struct nlattr attr = {}; - - attr.nla_len = sizeof (attr) + req_len; - attr.nla_type = req_type; - - ge.cmd = cmd; - ge.version = 1; - - nlh.nlmsg_len = NLMSG_LENGTH (req_footprint + sizeof (attr) + sizeof (ge)); - nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - nlh.nlmsg_type = type; - nlh.nlmsg_seq = seqNo; - nlh.nlmsg_pid = generic_pid (mod_id); - - struct iovec iov[4] = { { .iov_base = &nlh, .iov_len = sizeof (nlh) }, - { .iov_base = &ge, .iov_len = sizeof (ge) }, - { .iov_base = &attr, .iov_len = sizeof (attr) }, - { .iov_base = req, .iov_len = req_footprint } }; - - struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; - struct msghdr msg = { .msg_name = &sa, - .msg_namelen = sizeof (sa), - .msg_iov = iov, - .msg_iovlen = 4 }; - return sendmsg (sockfd, &msg, 0); - } - - /*_________________---------------------------__________________ - _________________ getFamily_PSAMPLE __________________ - -----------------___________________________------------------ - */ - - static void - getFamily_PSAMPLE (SFLOWPS *pst) - { -#define SFLOWPS_FAM_LEN sizeof (PSAMPLE_GENL_NAME) -#define SFLOWPS_FAM_FOOTPRINT NLMSG_ALIGN (SFLOWPS_FAM_LEN) - char fam_name[SFLOWPS_FAM_FOOTPRINT] = {}; - memcpy (fam_name, PSAMPLE_GENL_NAME, SFLOWPS_FAM_LEN); - generic_send (pst->nl_sock, pst->id, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, - CTRL_ATTR_FAMILY_NAME, fam_name, SFLOWPS_FAM_LEN, - SFLOWPS_FAM_FOOTPRINT, ++pst->nl_seq); - pst->state = SFLOWPS_STATE_WAIT_FAMILY; - } - - /*_________________---------------------------__________________ - _________________ processNetlink_GENERIC __________________ - -----------------___________________________------------------ - */ - - static void - processNetlink_GENERIC (SFLOWPS *pst, struct nlmsghdr *nlh) - { - char *msg = (char *) NLMSG_DATA (nlh); - int msglen = nlh->nlmsg_len - NLMSG_HDRLEN; - struct genlmsghdr *genl = (struct genlmsghdr *) msg; - SFLOW_DBG ("generic netlink CMD = %u\n", genl->cmd); - - for (int offset = GENL_HDRLEN; offset < msglen;) - { - struct nlattr *attr = (struct nlattr *) (msg + offset); - if (attr->nla_len == 0 || (attr->nla_len + offset) > msglen) - { - SFLOW_ERR ("processNetlink_GENERIC attr parse error\n"); - break; // attr parse error - } - char *attr_datap = (char *) attr + NLA_HDRLEN; - switch (attr->nla_type) - { - case CTRL_ATTR_VERSION: - pst->genetlink_version = *(u32 *) attr_datap; - break; - case CTRL_ATTR_FAMILY_ID: - pst->family_id = *(u16 *) attr_datap; - SFLOW_DBG ("generic family id: %u\n", pst->family_id); - break; - case CTRL_ATTR_FAMILY_NAME: - SFLOW_DBG ("generic family name: %s\n", attr_datap); - break; - case CTRL_ATTR_MCAST_GROUPS: - for (int grp_offset = NLA_HDRLEN; grp_offset < attr->nla_len;) - { - struct nlattr *grp_attr = - (struct nlattr *) (msg + offset + grp_offset); - if (grp_attr->nla_len == 0 || - (grp_attr->nla_len + grp_offset) > attr->nla_len) - { - SFLOW_ERR ( - "processNetlink_GENERIC grp_attr parse error\n"); - break; - } - char *grp_name = NULL; - u32 grp_id = 0; - for (int gf_offset = NLA_HDRLEN; - gf_offset < grp_attr->nla_len;) - { - struct nlattr *gf_attr = - (struct nlattr *) (msg + offset + grp_offset + - gf_offset); - if (gf_attr->nla_len == 0 || - (gf_attr->nla_len + gf_offset) > grp_attr->nla_len) - { - SFLOW_ERR ( - "processNetlink_GENERIC gf_attr parse error\n"); - break; - } - char *grp_attr_datap = (char *) gf_attr + NLA_HDRLEN; - switch (gf_attr->nla_type) - { - case CTRL_ATTR_MCAST_GRP_NAME: - grp_name = grp_attr_datap; - SFLOW_DBG ("psample multicast group: %s\n", grp_name); - break; - case CTRL_ATTR_MCAST_GRP_ID: - grp_id = *(u32 *) grp_attr_datap; - SFLOW_DBG ("psample multicast group id: %u\n", grp_id); - break; - } - gf_offset += NLMSG_ALIGN (gf_attr->nla_len); - } - if (pst->group_id == 0 && grp_name && grp_id && - !strcmp (grp_name, PSAMPLE_NL_MCGRP_SAMPLE_NAME)) - { - SFLOW_DBG ("psample found group %s=%u\n", grp_name, - grp_id); - pst->group_id = grp_id; - // We don't need to join the group if we are only sending - // to it. - } - - grp_offset += NLMSG_ALIGN (grp_attr->nla_len); - } - break; - default: - SFLOW_DBG ("psample attr type: %u (nested=%u) len: %u\n", - attr->nla_type, attr->nla_type & NLA_F_NESTED, - attr->nla_len); - break; - } - offset += NLMSG_ALIGN (attr->nla_len); - } - if (pst->family_id && pst->group_id) - { - SFLOW_DBG ("psample state->READY\n"); - pst->state = SFLOWPS_STATE_READY; - } - } - - // TODO: we can take out the fns for reading PSAMPLE here - - /*_________________---------------------------__________________ - _________________ processNetlink __________________ - -----------------___________________________------------------ - */ - - static void - processNetlink (SFLOWPS *pst, struct nlmsghdr *nlh) - { - if (nlh->nlmsg_type == NETLINK_GENERIC) - { - processNetlink_GENERIC (pst, nlh); - } - else if (nlh->nlmsg_type == pst->family_id) - { - // We are write-only, don't need to read these. - } - } - - /*_________________---------------------------__________________ - _________________ readNetlink_PSAMPLE __________________ - -----------------___________________________------------------ - */ - - static void - readNetlink_PSAMPLE (SFLOWPS *pst, int fd) - { - uint8_t recv_buf[SFLOWPS_PSAMPLE_READNL_RCV_BUF]; - int numbytes = recv (fd, recv_buf, sizeof (recv_buf), 0); - if (numbytes <= 0) - { - SFLOW_ERR ("readNetlink_PSAMPLE returned %d : %s\n", numbytes, - strerror (errno)); - return; - } - struct nlmsghdr *nlh = (struct nlmsghdr *) recv_buf; - while (NLMSG_OK (nlh, numbytes)) - { - if (nlh->nlmsg_type == NLMSG_DONE) - break; - if (nlh->nlmsg_type == NLMSG_ERROR) - { - struct nlmsgerr *err_msg = (struct nlmsgerr *) NLMSG_DATA (nlh); - if (err_msg->error == 0) - { - SFLOW_DBG ("received Netlink ACK\n"); - } - else - { - SFLOW_ERR ("error in netlink message: %d : %s\n", - err_msg->error, strerror (-err_msg->error)); - } - return; - } - processNetlink (pst, nlh); - nlh = NLMSG_NEXT (nlh, numbytes); - } - } - - /*_________________---------------------------__________________ - _________________ SFLOWPS_open __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWPS_open (SFLOWPS *pst) - { - if (pst->nl_sock == 0) - { - pst->nl_sock = generic_open (pst->id); - if (pst->nl_sock > 0) - { - pst->state = SFLOWPS_STATE_OPEN; - setSendBuffer (pst->nl_sock, SFLOWPS_PSAMPLE_READNL_SND_BUF); - getFamily_PSAMPLE (pst); - } - } - return (pst->nl_sock > 0); - } - - /*_________________---------------------------__________________ - _________________ SFLOWPS_close __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWPS_close (SFLOWPS *pst) - { - if (pst->nl_sock > 0) - { - int err = close (pst->nl_sock); - if (err == 0) - { - pst->nl_sock = 0; - return true; - } - else - { - SFLOW_ERR ("SFLOWPS_close: returned %d : %s\n", err, - strerror (errno)); - } - } - return false; - } - - /*_________________---------------------------__________________ - _________________ SFLOWPS_state __________________ - -----------------___________________________------------------ - */ - - EnumSFLOWPSState - SFLOWPS_state (SFLOWPS *pst) - { - return pst->state; - } - - /*_________________---------------------------__________________ - _________________ SFLOWPS_open_step __________________ - -----------------___________________________------------------ - */ - - EnumSFLOWPSState - SFLOWPS_open_step (SFLOWPS *pst) - { - switch (pst->state) - { - case SFLOWPS_STATE_INIT: - SFLOWPS_open (pst); - break; - case SFLOWPS_STATE_OPEN: - getFamily_PSAMPLE (pst); - break; - case SFLOWPS_STATE_WAIT_FAMILY: - readNetlink_PSAMPLE (pst, pst->nl_sock); - break; - case SFLOWPS_STATE_READY: - break; - } - return pst->state; - } - - /*_________________---------------------------__________________ - _________________ SFLOWPSSpec_setAttr __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWPSSpec_setAttr (SFLOWPSSpec *spec, EnumSFLOWPSAttributes field, - void *val, int len) - { - SFLOWPSAttr *psa = &spec->attr[field]; - if (psa->included) +/*_________________---------------------------__________________ + _________________ SFLOWPS_init __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWPS_init (SFLOWPS *pst) +{ + pst->nl.id = SFLOWNL_PSAMPLE; + memset (pst->fam_name, 0, SFLOWPS_FAM_FOOTPRINT); + memcpy (pst->fam_name, SFLOWPS_FAM, SFLOWPS_FAM_LEN); + pst->nl.family_name = pst->fam_name; + pst->nl.family_len = SFLOWPS_FAM_LEN; + pst->nl.join_group_name = PSAMPLE_NL_MCGRP_SAMPLE_NAME; + pst->nl.attr = pst->attr; + pst->nl.attr_max = __SFLOWPS_PSAMPLE_ATTRS - 1; + pst->nl.iov = pst->iov; + pst->nl.iov_max = SFLOWPS_IOV_FRAGS - 1; + pst->nl.state = SFLOWNL_STATE_INIT; + return pst->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWPS_open __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWPS_open (SFLOWPS *pst) +{ + if (pst->nl.state == SFLOWNL_STATE_UNDEFINED) + SFLOWPS_init (pst); + if (pst->nl.nl_sock == 0) + { + pst->nl.nl_sock = sflow_netlink_generic_open (&pst->nl); + if (pst->nl.nl_sock > 0) + sflow_netlink_generic_get_family (&pst->nl); + } + return (pst->nl.nl_sock > 0); +} + +/*_________________---------------------------__________________ + _________________ SFLOWPS_close __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWPS_close (SFLOWPS *pst) +{ + return (sflow_netlink_close (&pst->nl) == 0); +} + +/*_________________---------------------------__________________ + _________________ SFLOWPS_state __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWPS_state (SFLOWPS *pst) +{ + return pst->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWPS_open_step __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWPS_open_step (SFLOWPS *pst) +{ + switch (pst->nl.state) + { + case SFLOWNL_STATE_UNDEFINED: + SFLOWPS_init (pst); + break; + case SFLOWNL_STATE_INIT: + SFLOWPS_open (pst); + break; + case SFLOWNL_STATE_OPEN: + sflow_netlink_generic_get_family (&pst->nl); + break; + case SFLOWNL_STATE_WAIT_FAMILY: + sflow_netlink_read (&pst->nl); + break; + case SFLOWNL_STATE_READY: + break; + } + return pst->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWPS_set_attr __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWPS_set_attr (SFLOWPS *pst, EnumSFLOWPSAttributes field, void *val, + int len) +{ + int expected_len = SFLOWPS_Fields[field].len; + if (expected_len && expected_len != len) + { + SFLOW_ERR ("SFLOWPS_set_attr(%s) length=%u != expected: %u\n", + SFLOWPS_Fields[field].descr, len, expected_len); return false; - psa->included = true; - int expected_len = SFLOWPS_Fields[field].len; - if (expected_len && expected_len != len) - { - SFLOW_ERR ("SFLOWPSSpec_setAttr(%s) length=%u != expected: %u\n", - SFLOWPS_Fields[field].descr, len, expected_len); - return false; - } - psa->attr.nla_type = field; - psa->attr.nla_len = sizeof (psa->attr) + len; - int len_w_pad = NLMSG_ALIGN (len); - psa->val.iov_len = len_w_pad; - psa->val.iov_base = val; - spec->n_attrs++; - spec->attrs_len += sizeof (psa->attr); - spec->attrs_len += len_w_pad; - return true; - } - - /*_________________---------------------------__________________ - _________________ SFLOWPSSpec_send __________________ - -----------------___________________________------------------ - */ - - int - SFLOWPSSpec_send (SFLOWPS *pst, SFLOWPSSpec *spec) - { - spec->nlh.nlmsg_len = NLMSG_LENGTH (sizeof (spec->ge) + spec->attrs_len); - spec->nlh.nlmsg_flags = 0; - spec->nlh.nlmsg_type = pst->family_id; - spec->nlh.nlmsg_seq = ++pst->nl_seq; - spec->nlh.nlmsg_pid = generic_pid (pst->id); - - spec->ge.cmd = PSAMPLE_CMD_SAMPLE; - spec->ge.version = PSAMPLE_GENL_VERSION; - -#define MAX_IOV_FRAGMENTS (2 * __SFLOWPS_PSAMPLE_ATTR_MAX) + 2 - - struct iovec iov[MAX_IOV_FRAGMENTS]; - u32 frag = 0; - iov[frag].iov_base = &spec->nlh; - iov[frag].iov_len = sizeof (spec->nlh); - frag++; - iov[frag].iov_base = &spec->ge; - iov[frag].iov_len = sizeof (spec->ge); - frag++; - int nn = 0; - for (u32 ii = 0; ii < __SFLOWPS_PSAMPLE_ATTR_MAX; ii++) - { - SFLOWPSAttr *psa = &spec->attr[ii]; - if (psa->included) - { - nn++; - iov[frag].iov_base = &psa->attr; - iov[frag].iov_len = sizeof (psa->attr); - frag++; - iov[frag] = psa->val; // struct copy - frag++; - } - } - ASSERT (nn == spec->n_attrs); + } + return sflow_netlink_set_attr (&pst->nl, field, val, len); +} + +/*_________________---------------------------__________________ + _________________ SFLOWPS_send __________________ + -----------------___________________________------------------ +*/ + +int +SFLOWPS_send (SFLOWPS *pst) +{ + pst->nl.ge.cmd = PSAMPLE_CMD_SAMPLE; + pst->nl.ge.version = PSAMPLE_GENL_VERSION; + int status = sflow_netlink_send_attrs (&pst->nl, true); + sflow_netlink_reset_attrs (&pst->nl); + if (status <= 0) + { + SFLOW_ERR ("PSAMPLE strerror(errno) = %s; errno = %d\n", + strerror (errno), errno); + } + return status; +} - struct sockaddr_nl da = { .nl_family = AF_NETLINK, - .nl_groups = (1 << (pst->group_id - 1)) }; - - struct msghdr msg = { .msg_name = &da, - .msg_namelen = sizeof (da), - .msg_iov = iov, - .msg_iovlen = frag }; - - int status = sendmsg (pst->nl_sock, &msg, 0); - if (status <= 0) - { - SFLOW_ERR ("strerror(errno) = %s; errno = %d\n", strerror (errno), - errno); - return -1; - } - return 0; - } +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_psample.h b/src/plugins/sflow/sflow_psample.h index 5d4944231f..5b6e7c21a0 100644 --- a/src/plugins/sflow/sflow_psample.h +++ b/src/plugins/sflow/sflow_psample.h @@ -31,6 +31,8 @@ #include #include +#include + // #define SFLOWPS_DEBUG #define SFLOWPS_PSAMPLE_READNL_RCV_BUF 8192 @@ -45,7 +47,7 @@ typedef enum #define SFLOWPS_FIELDDATA(field, len, descr) field, #include "sflow/sflow_psample_fields.h" #undef SFLOWPS_FIELDDATA - __SFLOWPS_PSAMPLE_ATTR_MAX + __SFLOWPS_PSAMPLE_ATTRS } EnumSFLOWPSAttributes; typedef struct _SFLOWPS_field_t @@ -61,51 +63,38 @@ static const SFLOWPS_field_t SFLOWPS_Fields[] = { #undef SFLOWPS_FIELDDATA }; -typedef enum -{ - SFLOWPS_STATE_INIT, - SFLOWPS_STATE_OPEN, - SFLOWPS_STATE_WAIT_FAMILY, - SFLOWPS_STATE_READY -} EnumSFLOWPSState; +#define SFLOWPS_FAM PSAMPLE_GENL_NAME +#define SFLOWPS_FAM_LEN sizeof (SFLOWPS_FAM) +#define SFLOWPS_FAM_FOOTPRINT NLMSG_ALIGN (SFLOWPS_FAM_LEN) +#define SFLOWPS_IOV_FRAGS ((2 * __SFLOWPS_PSAMPLE_ATTRS) + 2) typedef struct _SFLOWPS { - EnumSFLOWPSState state; - u32 id; - int nl_sock; - u32 nl_seq; - u32 genetlink_version; - u16 family_id; - u32 group_id; + SFLOWNL nl; + char fam_name[SFLOWPS_FAM_FOOTPRINT]; + SFLOWNLAttr attr[__SFLOWPS_PSAMPLE_ATTRS]; + struct iovec iov[SFLOWPS_IOV_FRAGS]; } SFLOWPS; -typedef struct _SFLOWPSAttr -{ - bool included : 1; - struct nlattr attr; - struct iovec val; -} SFLOWPSAttr; - -typedef struct _SFLOWPSSpec -{ - struct nlmsghdr nlh; - struct genlmsghdr ge; - SFLOWPSAttr attr[__SFLOWPS_PSAMPLE_ATTR_MAX]; - int n_attrs; - int attrs_len; -} SFLOWPSSpec; - +EnumSFLOWNLState SFLOWPS_init (SFLOWPS *pst); bool SFLOWPS_open (SFLOWPS *pst); bool SFLOWPS_close (SFLOWPS *pst); -EnumSFLOWPSState SFLOWPS_state (SFLOWPS *pst); -EnumSFLOWPSState SFLOWPS_open_step (SFLOWPS *pst); +EnumSFLOWNLState SFLOWPS_state (SFLOWPS *pst); +EnumSFLOWNLState SFLOWPS_open_step (SFLOWPS *pst); -bool SFLOWPSSpec_setAttr (SFLOWPSSpec *spec, EnumSFLOWPSAttributes field, - void *buf, int len); -#define SFLOWPSSpec_setAttrInt(spec, field, val) \ - SFLOWPSSpec_setAttr ((spec), (field), &(val), sizeof (val)) +bool SFLOWPS_set_attr (SFLOWPS *pst, EnumSFLOWPSAttributes field, void *buf, + int len); +#define SFLOWPS_set_attr_int(pst, field, val) \ + SFLOWPS_set_attr ((pst), (field), &(val), sizeof (val)) -int SFLOWPSSpec_send (SFLOWPS *pst, SFLOWPSSpec *spec); +int SFLOWPS_send (SFLOWPS *pst); #endif /* __included_sflow_psample_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_test.c b/src/plugins/sflow/sflow_test.c index 554806640e..cdf5a7f6dd 100644 --- a/src/plugins/sflow/sflow_test.c +++ b/src/plugins/sflow/sflow_test.c @@ -27,6 +27,9 @@ uword unformat_sw_if_index (unformat_input_t *input, va_list *args); #include #include +/* for token names */ +#include + typedef struct { /* API message ID base */ @@ -40,7 +43,7 @@ static int api_sflow_enable_disable (vat_main_t *vam) { unformat_input_t *i = vam->input; - int enable_disable = 1; + int enable_disable = true; u32 hw_if_index = ~0; vl_api_sflow_enable_disable_t *mp; int ret; @@ -51,7 +54,9 @@ api_sflow_enable_disable (vat_main_t *vam) if (unformat (i, "%U", unformat_sw_if_index, vam, &hw_if_index)) ; else if (unformat (i, "disable")) - enable_disable = 0; + enable_disable = false; + else if (unformat (i, "enable")) + enable_disable = true; else break; } @@ -258,6 +263,128 @@ api_sflow_header_bytes_set (vat_main_t *vam) return ret; } +static void +vl_api_sflow_direction_get_reply_t_handler ( + vl_api_sflow_direction_get_reply_t *mp) +{ + vat_main_t *vam = sflow_test_main.vat_main; + clib_warning ("sflow direction: %d", ntohl (mp->sampling_D)); + vam->result_ready = 1; +} + +static int +api_sflow_direction_get (vat_main_t *vam) +{ + vl_api_sflow_direction_get_t *mp; + int ret; + + /* Construct the API message */ + M (SFLOW_DIRECTION_GET, mp); + + /* send it... */ + S (mp); + + /* Wait for a reply... */ + W (ret); + return ret; +} + +static int +api_sflow_direction_set (vat_main_t *vam) +{ + unformat_input_t *i = vam->input; + sflow_direction_t sampling_D = SFLOW_DIRN_UNDEFINED; + vl_api_sflow_direction_set_t *mp; + int ret; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "sampling_D rx")) + sampling_D = SFLOW_DIRN_INGRESS; + else if (unformat (i, "sampling_D tx")) + sampling_D = SFLOW_DIRN_INGRESS; + else if (unformat (i, "sampling_D both")) + sampling_D = SFLOW_DIRN_BOTH; + else + break; + } + + if (sampling_D == SFLOW_DIRN_UNDEFINED) + { + errmsg ("missing sampling_D direction\n"); + return -99; + } + + /* Construct the API message */ + M (SFLOW_DIRECTION_SET, mp); + mp->sampling_D = ntohl (sampling_D); + + /* send it... */ + S (mp); + + /* Wait for a reply... */ + W (ret); + return ret; +} + +static void +vl_api_sflow_drop_monitoring_get_reply_t_handler ( + vl_api_sflow_drop_monitoring_get_reply_t *mp) +{ + vat_main_t *vam = sflow_test_main.vat_main; + clib_warning ("sflow drop_M: %d", mp->drop_M); + vam->result_ready = 1; +} + +static int +api_sflow_drop_monitoring_get (vat_main_t *vam) +{ + vl_api_sflow_drop_monitoring_get_t *mp; + int ret; + + /* Construct the API message */ + M (SFLOW_DROP_MONITORING_GET, mp); + + /* send it... */ + S (mp); + + /* Wait for a reply... */ + W (ret); + return ret; +} + +static int +api_sflow_drop_monitoring_set (vat_main_t *vam) +{ + unformat_input_t *i = vam->input; + u32 drop_M = 1; + vl_api_sflow_drop_monitoring_set_t *mp; + int ret; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "disable")) + drop_M = 0; + else if (unformat (i, "enable")) + drop_M = 1; + else + break; + } + + /* Construct the API message */ + M (SFLOW_DROP_MONITORING_SET, mp); + mp->drop_M = ntohl (drop_M); + + /* send it... */ + S (mp); + + /* Wait for a reply... */ + W (ret); + return ret; +} + static void vl_api_sflow_interface_details_t_handler (vl_api_sflow_interface_details_t *mp) { diff --git a/src/plugins/sflow/sflow_usersock.c b/src/plugins/sflow/sflow_usersock.c index 0ccb947709..a81fd025d9 100644 --- a/src/plugins/sflow/sflow_usersock.c +++ b/src/plugins/sflow/sflow_usersock.c @@ -13,11 +13,6 @@ * limitations under the License. */ -#if defined(__cplusplus) -extern "C" -{ -#endif - #include #include #include @@ -32,191 +27,134 @@ extern "C" #include #include +#include #include - /*_________________---------------------------__________________ - _________________ fcntl utils __________________ - -----------------___________________________------------------ - */ - - static void - setNonBlocking (int fd) - { - // set the socket to non-blocking - int fdFlags = fcntl (fd, F_GETFL); - fdFlags |= O_NONBLOCK; - if (fcntl (fd, F_SETFL, fdFlags) < 0) - { - SFLOW_ERR ("fcntl(O_NONBLOCK) failed: %s\n", strerror (errno)); - } - } - - static void - setCloseOnExec (int fd) - { - // make sure it doesn't get inherited, e.g. when we fork a script - int fdFlags = fcntl (fd, F_GETFD); - fdFlags |= FD_CLOEXEC; - if (fcntl (fd, F_SETFD, fdFlags) < 0) - { - SFLOW_ERR ("fcntl(F_SETFD=FD_CLOEXEC) failed: %s\n", strerror (errno)); - } - } - - /*_________________---------------------------__________________ - _________________ usersock_open __________________ - -----------------___________________________------------------ - */ - - static int - usersock_open (void) - { - int nl_sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_USERSOCK); - if (nl_sock < 0) - { - SFLOW_ERR ("nl_sock open failed: %s\n", strerror (errno)); - return -1; - } - setNonBlocking (nl_sock); - setCloseOnExec (nl_sock); - return nl_sock; - } - - /*_________________---------------------------__________________ - _________________ SFLOWUS_open __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWUS_open (SFLOWUS *ust) - { - if (ust->nl_sock == 0) - { - ust->nl_sock = usersock_open (); - } - return true; - } - - /*_________________---------------------------__________________ - _________________ SFLOWUS_close __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWUS_close (SFLOWUS *ust) - { - if (ust->nl_sock != 0) - { - int err = close (ust->nl_sock); - if (err == 0) - { - ust->nl_sock = 0; - return true; - } - else - { - SFLOW_WARN ("SFLOWUS_close: returned %d : %s\n", err, - strerror (errno)); - } - } - return false; - } - - /*_________________---------------------------__________________ - _________________ SFLOWUSSpec_setMsgType __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWUSSpec_setMsgType (SFLOWUSSpec *spec, EnumSFlowVppMsgType msgType) - { - spec->nlh.nlmsg_type = msgType; - return true; - } - - /*_________________---------------------------__________________ - _________________ SFLOWUSSpec_setAttr __________________ - -----------------___________________________------------------ - */ - - bool - SFLOWUSSpec_setAttr (SFLOWUSSpec *spec, EnumSFlowVppAttributes field, - void *val, int len) - { - SFLOWUSAttr *usa = &spec->attr[field]; - if (usa->included) - return false; - usa->included = true; - usa->attr.nla_type = field; - usa->attr.nla_len = sizeof (usa->attr) + len; - int len_w_pad = NLMSG_ALIGN (len); - usa->val.iov_len = len_w_pad; - usa->val.iov_base = val; - spec->n_attrs++; - spec->attrs_len += sizeof (usa->attr); - spec->attrs_len += len_w_pad; - return true; - } - - /*_________________---------------------------__________________ - _________________ SFLOWUSSpec_send __________________ - -----------------___________________________------------------ - */ - - int - SFLOWUSSpec_send (SFLOWUS *ust, SFLOWUSSpec *spec) - { - spec->nlh.nlmsg_len = NLMSG_LENGTH (spec->attrs_len); - spec->nlh.nlmsg_flags = 0; - spec->nlh.nlmsg_seq = ++ust->nl_seq; - spec->nlh.nlmsg_pid = getpid (); - -#define MAX_IOV_FRAGMENTS (2 * __SFLOW_VPP_ATTR_MAX) + 2 - - struct iovec iov[MAX_IOV_FRAGMENTS]; - u32 frag = 0; - iov[frag].iov_base = &spec->nlh; - iov[frag].iov_len = sizeof (spec->nlh); - frag++; - int nn = 0; - for (u32 ii = 0; ii < __SFLOW_VPP_ATTR_MAX; ii++) - { - SFLOWUSAttr *usa = &spec->attr[ii]; - if (usa->included) - { - nn++; - iov[frag].iov_base = &usa->attr; - iov[frag].iov_len = sizeof (usa->attr); - frag++; - iov[frag] = usa->val; // struct copy - frag++; - } - } - ASSERT (nn == spec->n_attrs); - - struct sockaddr_nl da = { - .nl_family = AF_NETLINK, - .nl_groups = (1 << (ust->group_id - 1)) // for multicast to the group - // .nl_pid = 1e9+6343 // for unicast to receiver bound to netlink socket - // with that "pid" - }; - - struct msghdr msg = { .msg_name = &da, - .msg_namelen = sizeof (da), - .msg_iov = iov, - .msg_iovlen = frag }; - - int status = sendmsg (ust->nl_sock, &msg, 0); - if (status <= 0) - { - // Linux replies with ECONNREFUSED when - // a multicast is sent via NETLINK_USERSOCK, but - // it's not an error so we can just ignore it here. - if (errno != ECONNREFUSED) - { - SFLOW_DBG ("USERSOCK strerror(errno) = %s\n", strerror (errno)); - return -1; - } - } - return 0; - } +/*_________________---------------------------__________________ + _________________ SFLOWUS_init __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWUS_init (SFLOWUS *ust) +{ + ust->nl.id = SFLOWNL_USERSOCK; + ust->nl.group_id = SFLOW_NETLINK_USERSOCK_MULTICAST; + ust->nl.attr = ust->attr; + ust->nl.attr_max = SFLOWUS_ATTRS - 1; + ust->nl.iov = ust->iov; + ust->nl.iov_max = SFLOWUS_IOV_FRAGS - 1; + ust->nl.state = SFLOWNL_STATE_INIT; + return ust->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWUS_open __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWUS_open (SFLOWUS *ust) +{ + if (ust->nl.state == SFLOWNL_STATE_UNDEFINED) + SFLOWUS_init (ust); + if (ust->nl.nl_sock == 0) + sflow_netlink_usersock_open (&ust->nl); + if (ust->nl.nl_sock > 0) + { + ust->nl.state = SFLOWNL_STATE_READY; + return true; + } + return false; +} + +/*_________________---------------------------__________________ + _________________ SFLOWUS_close __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWUS_close (SFLOWUS *ust) +{ + return (sflow_netlink_close (&ust->nl) == 0); +} + +/*_________________---------------------------__________________ + _________________ SFLOWUS_set_msg_type __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWUS_set_msg_type (SFLOWUS *ust, EnumSFlowVppMsgType msgType) +{ + ust->nl.nlh.nlmsg_type = msgType; + return true; +} + +/*_________________---------------------------__________________ + _________________ SFLOWUS_open_step __________________ + -----------------___________________________------------------ +*/ + +EnumSFLOWNLState +SFLOWUS_open_step (SFLOWUS *ust) +{ + switch (ust->nl.state) + { + case SFLOWNL_STATE_UNDEFINED: + SFLOWUS_init (ust); + break; + case SFLOWNL_STATE_INIT: + SFLOWUS_open (ust); + break; + case SFLOWNL_STATE_OPEN: + case SFLOWNL_STATE_WAIT_FAMILY: + case SFLOWNL_STATE_READY: + break; + } + return ust->nl.state; +} + +/*_________________---------------------------__________________ + _________________ SFLOWUS_set_attr __________________ + -----------------___________________________------------------ +*/ + +bool +SFLOWUS_set_attr (SFLOWUS *ust, EnumSFlowVppAttributes field, void *val, + int len) +{ + return sflow_netlink_set_attr (&ust->nl, field, val, len); +} + +/*_________________---------------------------__________________ + _________________ SFLOWUS_send __________________ + -----------------___________________________------------------ +*/ + +int +SFLOWUS_send (SFLOWUS *ust) +{ + int status = sflow_netlink_send_attrs (&ust->nl, false); + sflow_netlink_reset_attrs (&ust->nl); + if (status <= 0) + { + // Linux replies with ECONNREFUSED when + // a multicast is sent via NETLINK_USERSOCK, but + // it's not an error so we can just ignore it here. + if (errno != ECONNREFUSED) + { + SFLOW_DBG ("USERSOCK strerror(errno) = %s\n", strerror (errno)); + return -1; + } + } + return 0; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/sflow/sflow_usersock.h b/src/plugins/sflow/sflow_usersock.h index d66389941a..607d087b6a 100644 --- a/src/plugins/sflow/sflow_usersock.h +++ b/src/plugins/sflow/sflow_usersock.h @@ -29,6 +29,8 @@ #include #include +#include + // ==================== shared with hsflowd mod_vpp ========================= // See https://github.com/sflow/host-sflow @@ -67,7 +69,7 @@ typedef enum SFLOW_VPP_ATTR_DROPS, /* u32 all FIFO and netlink sendmsg drops */ SFLOW_VPP_ATTR_SEQ, /* u32 send seq no */ /* enum shared with hsflowd, so only add here */ - __SFLOW_VPP_ATTR_MAX + __SFLOW_VPP_ATTRS } EnumSFlowVppAttributes; #define SFLOW_VPP_PSAMPLE_GROUP_INGRESS 3 @@ -96,38 +98,35 @@ typedef struct _SFLOWUS_field_t int len; } SFLOWUS_field_t; +#define SFLOWUS_ATTRS __SFLOW_VPP_ATTRS +#define SFLOWUS_IOV_FRAGS \ + ((2 * SFLOWUS_ATTRS) + 2) // TODO: may only be +1 -- no ge header? + typedef struct _SFLOWUS { - u32 id; - int nl_sock; - u32 nl_seq; - u32 group_id; + SFLOWNL nl; + SFLOWNLAttr attr[__SFLOW_VPP_ATTRS]; + struct iovec iov[SFLOWUS_IOV_FRAGS]; } SFLOWUS; -typedef struct _SFLOWUSAttr -{ - bool included : 1; - struct nlattr attr; - struct iovec val; -} SFLOWUSAttr; - -typedef struct _SFLOWUSSpec -{ - struct nlmsghdr nlh; - SFLOWUSAttr attr[__SFLOW_VPP_ATTR_MAX]; - int n_attrs; - int attrs_len; -} SFLOWUSSpec; - +EnumSFLOWNLState SFLOWUS_init (SFLOWUS *ust); bool SFLOWUS_open (SFLOWUS *ust); bool SFLOWUS_close (SFLOWUS *ust); -bool SFLOWUSSpec_setMsgType (SFLOWUSSpec *spec, EnumSFlowVppMsgType type); -bool SFLOWUSSpec_setAttr (SFLOWUSSpec *spec, EnumSFlowVppAttributes field, - void *buf, int len); -#define SFLOWUSSpec_setAttrInt(spec, field, val) \ - SFLOWUSSpec_setAttr ((spec), (field), &(val), sizeof (val)) +bool SFLOWUS_set_msg_type (SFLOWUS *ust, EnumSFlowVppMsgType type); +bool SFLOWUS_set_attr (SFLOWUS *ust, EnumSFlowVppAttributes field, void *buf, + int len); +#define SFLOWUS_set_attr_int(ust, field, val) \ + SFLOWUS_set_attr ((ust), (field), &(val), sizeof (val)) -int SFLOWUSSpec_send (SFLOWUS *ust, SFLOWUSSpec *spec); +int SFLOWUS_send (SFLOWUS *ust); #endif /* __included_sflow_usersock_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/interface.h b/src/vnet/interface.h index 0cb4b53ca1..8c5c5b177b 100644 --- a/src/vnet/interface.h +++ b/src/vnet/interface.h @@ -1070,6 +1070,7 @@ typedef struct /* feature_arc_index */ u8 output_feature_arc_index; + u8 drop_feature_arc_index; /* fast lookup tables */ u32 *hw_if_index_by_sw_if_index; diff --git a/src/vnet/interface_output.c b/src/vnet/interface_output.c index 47844dcd68..8f023a84bf 100644 --- a/src/vnet/interface_output.c +++ b/src/vnet/interface_output.c @@ -950,7 +950,6 @@ interface_drop_punt (vlib_main_t * vm, vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; u32 sw_if_indices[VLIB_FRAME_SIZE]; vlib_simple_counter_main_t *cm; - u16 nexts[VLIB_FRAME_SIZE]; u32 n_trace; vnet_main_t *vnm; @@ -1002,7 +1001,6 @@ interface_drop_punt (vlib_main_t * vm, interface_trace_buffers (vm, node, frame); /* All going to drop regardless, this is just a counting exercise */ - clib_memset (nexts, 0, sizeof (nexts)); cm = vec_elt_at_index (vnm->interface_main.sw_if_counters, (disposition == VNET_ERROR_DISPOSITION_PUNT @@ -1063,7 +1061,12 @@ interface_drop_punt (vlib_main_t * vm, (cm, thread_index, sw_if0->sup_sw_if_index, count); } - vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + vnet_interface_main_t *im = &vnm->interface_main; + u8 arc_index = im->drop_feature_arc_index; + u32 nextAll = 0; + vnet_feature_arc_start (arc_index, sw_if_index[0], &nextAll, bufs[0]); + vlib_buffer_enqueue_to_single_next (vm, node, from, nextAll, + frame->n_vectors); return frame->n_vectors; } @@ -1430,6 +1433,17 @@ vnet_set_interface_output_node (vnet_main_t * vnm, } #endif /* CLIB_MARCH_VARIANT */ +VNET_FEATURE_ARC_INIT (interface_drop, static) = { + .arc_name = "error-drop", + .start_nodes = VNET_FEATURES ("error-drop"), + .last_in_arc = "drop", + .arc_index_ptr = &vnet_main.interface_main.drop_feature_arc_index, +}; + +VNET_FEATURE_INIT (drop, static) = { .arc_name = "error-drop", + .node_name = "drop", + .runs_before = 0 }; + /* * fd.io coding-style-patch-verification: ON * diff --git a/test/test_sflow_drop.py b/test/test_sflow_drop.py new file mode 100644 index 0000000000..d60e2f0962 --- /dev/null +++ b/test/test_sflow_drop.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +import unittest +from framework import VppTestCase +from asfframework import VppTestRunner +from scapy.layers.l2 import Ether +from scapy.packet import Raw +from scapy.layers.inet import IP, UDP +from random import randint +import re # for finding counters in "sh errors" output + + +class SFlowDropTestCase(VppTestCase): + """sFlow test case""" + + @classmethod + def setUpClass(self): + super(SFlowDropTestCase, self).setUpClass() + + @classmethod + def teadDownClass(cls): + super(SFlowDropTestCase, cls).tearDownClass() + + def setUp(self): + self.create_pg_interfaces(range(2)) # create pg0 and pg1 + for i in self.pg_interfaces: + i.admin_up() # put the interface up + i.config_ip4() # configure IPv4 address on the interface + i.resolve_arp() # resolve ARP, so that we know VPP MAC + + def tearDown(self): + for i in self.pg_interfaces: + i.admin_down() + i.unconfig() + i.set_table_ip4(0) + i.set_table_ip6(0) + + def enable_sflow_via_api(self): + ## TEST: Enable both interfaces + ret = self.vapi.sflow_enable_disable(hw_if_index=1, enable_disable=True) + self.assertEqual(ret.retval, 0) + ret = self.vapi.sflow_enable_disable(hw_if_index=2, enable_disable=True) + self.assertEqual(ret.retval, 0) + + ## TEST: sflow_sampling_rate_set() + self.vapi.sflow_sampling_rate_set(sampling_N=1) + ret = self.vapi.sflow_sampling_rate_get() + self.assertEqual(ret.sampling_N, 1) + + ## TEST: sflow_drop_monitoring() + self.vapi.sflow_drop_monitoring_set(drop_M=1) + ret = self.vapi.sflow_drop_monitoring_get() + self.assertEqual(ret.drop_M, 1) + + def create_stream(self, src_if, dst_if, count): + packets = [] + for i in range(count): + # create packet info stored in the test case instance + info = self.create_packet_info(src_if, dst_if) + # convert the info into packet payload + payload = self.info_to_payload(info) + # create the packet itself + p = ( + Ether(dst=src_if.local_mac, src=src_if.remote_mac) + / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4, ttl=i + 1) + / UDP(sport=randint(49152, 65535), dport=5678) + / Raw(payload) + ) + # store a copy of the packet in the packet info + info.data = p.copy() + # append the packet to the list + packets.append(p) + # return the created packet list + return packets + + def get_sflow_counter(self, counter): + counters = self.vapi.cli("sh errors").split("\n") + for i in range(1, len(counters) - 1): + results = counters[i].split() + if results[1] == "sflow": + if re.search(counter, counters[i]) is not None: + return int(results[0]) + return None + + def verify_sflow(self, count): + ctr_pk_proc = "sflow packets processed" + ctr_pk_samp = "sflow packets sampled" + ctr_pk_drop = "sflow packets dropped" + ctr_di_proc = "sflow discards processed" + ctr_di_drop = "sflow discards dropped" + ctr_ps_sent = "sflow PSAMPLE sent" + ctr_ps_fail = "sflow PSAMPLE send failed" + ctr_dm_sent = "sflow DROPMON sent" + ctr_dm_fail = "sflow DROPMON send failed" + pk_proc = self.get_sflow_counter(ctr_pk_proc) + pk_samp = self.get_sflow_counter(ctr_pk_samp) + pk_drop = self.get_sflow_counter(ctr_pk_drop) + di_proc = self.get_sflow_counter(ctr_di_proc) + di_drop = self.get_sflow_counter(ctr_di_drop) + ps_sent = self.get_sflow_counter(ctr_ps_sent) + ps_fail = self.get_sflow_counter(ctr_ps_fail) + dm_sent = self.get_sflow_counter(ctr_dm_sent) + dm_fail = self.get_sflow_counter(ctr_dm_fail) + self.logger.info(ctr_pk_proc + "=" + str(pk_proc)) + self.logger.info(ctr_pk_samp + "=" + str(pk_samp)) + self.logger.info(ctr_pk_drop + "=" + str(pk_drop)) + self.logger.info(ctr_di_proc + "=" + str(di_proc)) + self.logger.info(ctr_di_drop + "=" + str(di_drop)) + self.logger.info(ctr_ps_sent + "=" + str(ps_sent)) + self.logger.info(ctr_ps_fail + "=" + str(ps_fail)) + self.logger.info(ctr_dm_sent + "=" + str(dm_sent)) + self.logger.info(ctr_dm_fail + "=" + str(dm_fail)) + self.assert_equal(pk_proc, count, ctr_pk_proc) + self.assert_equal(pk_samp, count, ctr_pk_samp) + self.assert_equal(pk_drop, None, ctr_pk_drop) + self.assert_equal(di_proc, 1, ctr_di_proc) + self.assert_equal(di_drop, None, ctr_di_drop) + # sending to PSAMPLE or DROPMON will fail if not + # running with root privileges. So do not insist + # on these numbers: + # self.assert_equal(ps_sent, count, ctr_ps_sent) + # self.assert_equal(ps_fail, None, ctr_ps_fail) + # self.assert_equal(dm_sent, 1, ctr_dm_sent) + # self.assert_equal(dm_fail, None, ctr_dm_fail) + + def test_basic(self): + self.enable_sflow_via_api() + count = 7 + # create the packet stream, with ttl decrementing so + # that just 1 packet will be dropped + packets = self.create_stream(self.pg0, self.pg1, count) + # add the stream to the source interface + self.pg0.add_stream(packets) + # enable capture on both interfaces + self.pg0.enable_capture() + self.pg1.enable_capture() + # start the packet generator + self.pg_start() + # get capture - the proper count of packets was saved by + # create_packet_info() based on dst_if parameter + capture = self.pg1.get_capture(count - 1, timeout=2) + # expect an ICMP TTL exceeded message back on pg0 + # and a dropped packet that sflow will write to DROPMON + capture0 = self.pg0.get_capture(1, timeout=2) + # allow time for the dropped packet to be fully + # processed, and for the counters to be updated + self.sleep(1.0) + # verify sflow counters + self.verify_sflow(count) diff --git a/test/test_sflow_egress.py b/test/test_sflow_egress.py new file mode 100644 index 0000000000..4e2613a4eb --- /dev/null +++ b/test/test_sflow_egress.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 + +import unittest +from framework import VppTestCase +from asfframework import VppTestRunner +from scapy.layers.l2 import Ether +from scapy.packet import Raw +from scapy.layers.inet import IP, UDP +from random import randint +import re # for finding counters in "sh errors" output + + +class SFlowEgressTestCase(VppTestCase): + """sFlow test case""" + + @classmethod + def setUpClass(self): + super(SFlowEgressTestCase, self).setUpClass() + + @classmethod + def teadDownClass(cls): + super(SFlowEgressTestCase, cls).tearDownClass() + + def setUp(self): + self.create_pg_interfaces(range(2)) # create pg0 and pg1 + for i in self.pg_interfaces: + i.admin_up() # put the interface up + i.config_ip4() # configure IPv4 address on the interface + i.resolve_arp() # resolve ARP, so that we know VPP MAC + + def tearDown(self): + for i in self.pg_interfaces: + i.admin_down() + i.unconfig() + i.set_table_ip4(0) + i.set_table_ip6(0) + + def enable_sflow_via_api(self): + ## TEST: Enable both interfaces + ret = self.vapi.sflow_enable_disable(hw_if_index=1, enable_disable=True) + self.assertEqual(ret.retval, 0) + ret = self.vapi.sflow_enable_disable(hw_if_index=2, enable_disable=True) + self.assertEqual(ret.retval, 0) + + ## TEST: sflow_sampling_rate_set() + self.vapi.sflow_sampling_rate_set(sampling_N=1) + ret = self.vapi.sflow_sampling_rate_get() + self.assert_equal(ret.sampling_N, 1) + + ## TEST: sflow_direction_set() + self.vapi.sflow_direction_set(sampling_D=3) + ret = self.vapi.sflow_direction_get() + self.assert_equal(ret.sampling_D, 3) + + def create_stream(self, src_if, dst_if, count): + packets = [] + for i in range(count): + # create packet info stored in the test case instance + info = self.create_packet_info(src_if, dst_if) + # convert the info into packet payload + payload = self.info_to_payload(info) + # create the packet itself + p = ( + Ether(dst=src_if.local_mac, src=src_if.remote_mac) + / IP(src=src_if.remote_ip4, dst=dst_if.remote_ip4) + / UDP(sport=randint(49152, 65535), dport=5678) + / Raw(payload) + ) + # store a copy of the packet in the packet info + info.data = p.copy() + # append the packet to the list + packets.append(p) + # return the created packet list + return packets + + def get_sflow_counter(self, counter): + counters = self.vapi.cli("sh errors").split("\n") + for i in range(1, len(counters) - 1): + results = counters[i].split() + if results[1] == "sflow": + if re.search(counter, counters[i]) is not None: + return int(results[0]) + return None + + def verify_sflow(self, count): + ctr_pk_proc = "sflow packets processed" + ctr_pk_samp = "sflow packets sampled" + ctr_pk_drop = "sflow packets dropped" + ctr_di_proc = "sflow discards processed" + ctr_di_drop = "sflow discards dropped" + ctr_ps_sent = "sflow PSAMPLE sent" + ctr_ps_fail = "sflow PSAMPLE send failed" + ctr_dm_sent = "sflow DROPMON sent" + ctr_dm_fail = "sflow DROPMON send failed" + pk_proc = self.get_sflow_counter(ctr_pk_proc) + pk_samp = self.get_sflow_counter(ctr_pk_samp) + pk_drop = self.get_sflow_counter(ctr_pk_drop) + di_proc = self.get_sflow_counter(ctr_di_proc) + di_drop = self.get_sflow_counter(ctr_di_drop) + ps_sent = self.get_sflow_counter(ctr_ps_sent) + ps_fail = self.get_sflow_counter(ctr_ps_fail) + dm_sent = self.get_sflow_counter(ctr_dm_sent) + dm_fail = self.get_sflow_counter(ctr_dm_fail) + self.logger.info(ctr_pk_proc + "=" + str(pk_proc)) + self.logger.info(ctr_pk_samp + "=" + str(pk_samp)) + self.logger.info(ctr_pk_drop + "=" + str(pk_drop)) + self.logger.info(ctr_di_proc + "=" + str(di_proc)) + self.logger.info(ctr_di_drop + "=" + str(di_drop)) + self.logger.info(ctr_ps_sent + "=" + str(ps_sent)) + self.logger.info(ctr_ps_fail + "=" + str(ps_fail)) + self.logger.info(ctr_dm_sent + "=" + str(dm_sent)) + self.logger.info(ctr_dm_fail + "=" + str(dm_fail)) + self.assert_equal(pk_proc, count * 2, ctr_pk_proc) + self.assert_equal(pk_samp, count * 2, ctr_pk_samp) + self.assert_equal(pk_drop, None, ctr_pk_drop) + self.assert_equal(di_proc, None, ctr_di_proc) + self.assert_equal(di_drop, None, ctr_di_drop) + # sending to PSAMPLE or DROPMON will fail if not + # running with root privileges. So do not insist + # on these numbers: + # self.assert_equal(ps_sent, count * 2, ctr_ps_sent) + # self.assert_equal(ps_fail, None, ctr_ps_fail) + # self.assert_equal(dm_sent, None, ctr_dm_sent) + # self.assert_equal(dm_fail, None, ctr_dm_fail) + + def test_basic(self): + self.enable_sflow_via_api() + count = 7 + # create the packet stream, with ttl decrementing so + # that just 1 packet will be dropped + packets = self.create_stream(self.pg0, self.pg1, count) + # add the stream to the source interface + self.pg0.add_stream(packets) + # enable capture on both interfaces + self.pg0.enable_capture() + self.pg1.enable_capture() + # start the packet generator + self.pg_start() + # get capture - the proper count of packets was saved by + # create_packet_info() based on dst_if parameter + capture = self.pg1.get_capture(count, timeout=2) + # allow time for the counters to be updated + self.sleep(1.0) + # verify sflow counters + self.verify_sflow(count) From 4b3de95d29ed9e1a3e3d7a353bd3b3ea922dfbb8 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 4 Jun 2025 08:08:44 +0200 Subject: [PATCH 023/313] hs-test: adjust timeouts when testing COV build - TestTimeout is set to 30 minutes unless overridden with TIMEOUT arg - AssertChannelClosed() timeout is set to TestTimeout-30s Type: test Change-Id: Ic321ad5baf3bf2c52ed872c58abc8ad577d82397 Signed-off-by: Adrian Villin --- extras/hs-test/framework_test.go | 6 ++++-- extras/hs-test/infra/common/suite_common.go | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/framework_test.go b/extras/hs-test/framework_test.go index 962d6d5fcd..f75f949e5e 100644 --- a/extras/hs-test/framework_test.go +++ b/extras/hs-test/framework_test.go @@ -26,9 +26,11 @@ var _ = ReportAfterSuite("VPP version under test", func(report Report) { }) func TestHst(t *testing.T) { - if *IsVppDebug { - // 30 minute timeout so that the framework won't timeout while debugging + // if we're debugging/running a coverage build and timeout isn't overridden, + // set test timeout to 30 minutes. Also impacts AssertChannelClosed() + if (*IsVppDebug || *IsCoverage) && *Timeout == 5 { TestTimeout = time.Minute * 30 + fmt.Printf("[Debugging or coverage build, TestTimeout is set to %s]\n", TestTimeout.String()) } else { TestTimeout = time.Minute * time.Duration(*Timeout) } diff --git a/extras/hs-test/infra/common/suite_common.go b/extras/hs-test/infra/common/suite_common.go index 163faa9611..32fa02b41c 100644 --- a/extras/hs-test/infra/common/suite_common.go +++ b/extras/hs-test/infra/common/suite_common.go @@ -172,7 +172,12 @@ func (s *HstCommon) AssertHttpBody(resp *http.Response, expectedBody string, msg ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...) } +// Coverage builds take longer to finish -> assert timeout is set to 'TestTimeout - 30 seconds' to let the test finish properly func (s *HstCommon) AssertChannelClosed(timeout time.Duration, channel chan error) { + if *IsCoverage && timeout > time.Second*30 { + s.Log("Coverage build, assert timeout is set to %s", timeout.String()) + timeout = TestTimeout - time.Second*30 + } EventuallyWithOffset(2, channel).WithTimeout(timeout).Should(BeClosed()) } From 7af03a0d880ed9d5a09e633e779f7abd72e6fdc2 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Mon, 2 Jun 2025 16:53:41 +0200 Subject: [PATCH 024/313] hs-test: improve teardown reliability - added defer to some teardown functions - framework now recovers when Vppctl() is used before vppinstance is created Type: test Change-Id: Ia37cca0ef24f8936cbf350636e249efbf78b13ec Signed-off-by: Adrian Villin --- extras/hs-test/infra/suite_cpu_pinning.go | 3 +-- extras/hs-test/infra/suite_envoy_proxy.go | 2 +- extras/hs-test/infra/suite_ldp.go | 3 +-- extras/hs-test/infra/suite_nginx_proxy.go | 2 +- extras/hs-test/infra/suite_no_topo.go | 2 +- extras/hs-test/infra/suite_no_topo6.go | 2 +- extras/hs-test/infra/suite_vpp_proxy.go | 2 +- extras/hs-test/infra/suite_vpp_udp_proxy.go | 2 +- extras/hs-test/infra/vppinstance.go | 8 ++++++++ 9 files changed, 16 insertions(+), 10 deletions(-) diff --git a/extras/hs-test/infra/suite_cpu_pinning.go b/extras/hs-test/infra/suite_cpu_pinning.go index 6808768d49..e80682dcb2 100644 --- a/extras/hs-test/infra/suite_cpu_pinning.go +++ b/extras/hs-test/infra/suite_cpu_pinning.go @@ -58,11 +58,10 @@ func (s *CpuPinningSuite) SetupTest() { } func (s *CpuPinningSuite) TeardownTest() { + defer s.HstSuite.TeardownTest() // reset vars s.CpuCount = *NConfiguredCpus s.CpuAllocator.maxContainerCount = s.previousMaxContainerCount - s.HstSuite.TeardownTest() - } var _ = Describe("CpuPinningSuite", Ordered, ContinueOnFailure, func() { diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go index 7119b0ff1d..2d184270b8 100644 --- a/extras/hs-test/infra/suite_envoy_proxy.go +++ b/extras/hs-test/infra/suite_envoy_proxy.go @@ -151,11 +151,11 @@ func (s *EnvoyProxySuite) SetupTest() { } func (s *EnvoyProxySuite) TeardownTest() { + defer s.HstSuite.TeardownTest() if CurrentSpecReport().Failed() { s.CollectNginxLogs(s.Containers.NginxServerTransient) s.CollectEnvoyLogs(s.Containers.EnvoyProxy) } - s.HstSuite.TeardownTest() } func (s *EnvoyProxySuite) ProxyAddr() string { diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index 0baeacd505..78c9e14892 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -100,6 +100,7 @@ func (s *LdpSuite) SetupTest() { } func (s *LdpSuite) TeardownTest() { + defer s.HstSuite.TeardownTest() if CurrentSpecReport().Failed() { s.CollectIperfLogs(s.Containers.ServerApp) s.CollectRedisServerLogs(s.Containers.ServerApp) @@ -111,8 +112,6 @@ func (s *LdpSuite) TeardownTest() { delete(container.EnvVars, "LD_PRELOAD") delete(container.EnvVars, "VCL_CONFIG") } - s.HstSuite.TeardownTest() - } func (s *LdpSuite) CreateVclConfig(container *Container) { diff --git a/extras/hs-test/infra/suite_nginx_proxy.go b/extras/hs-test/infra/suite_nginx_proxy.go index 8006e6fd4d..05b0960eed 100644 --- a/extras/hs-test/infra/suite_nginx_proxy.go +++ b/extras/hs-test/infra/suite_nginx_proxy.go @@ -117,11 +117,11 @@ func (s *NginxProxySuite) SetupTest() { } func (s *NginxProxySuite) TeardownTest() { + defer s.HstSuite.TeardownTest() if CurrentSpecReport().Failed() { s.CollectNginxLogs(s.Containers.NginxProxy) s.CollectNginxLogs(s.Containers.NginxServerTransient) } - s.HstSuite.TeardownTest() } func (s *NginxProxySuite) CreateNginxProxyConfig(container *Container, multiThreadWorkers bool) { diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go index 2d96ac9cd2..e464ef7f95 100644 --- a/extras/hs-test/infra/suite_no_topo.go +++ b/extras/hs-test/infra/suite_no_topo.go @@ -87,10 +87,10 @@ func (s *NoTopoSuite) SetupTest() { } func (s *NoTopoSuite) TeardownTest() { + defer s.HstSuite.TeardownTest() if CurrentSpecReport().Failed() { s.CollectNginxLogs(s.Containers.NginxHttp3) } - s.HstSuite.TeardownTest() } func (s *NoTopoSuite) CreateNginxConfig(container *Container, multiThreadWorkers bool) { diff --git a/extras/hs-test/infra/suite_no_topo6.go b/extras/hs-test/infra/suite_no_topo6.go index 8b67791597..1b21469d1a 100644 --- a/extras/hs-test/infra/suite_no_topo6.go +++ b/extras/hs-test/infra/suite_no_topo6.go @@ -85,10 +85,10 @@ func (s *NoTopo6Suite) SetupTest() { } func (s *NoTopo6Suite) TeardownTest() { + defer s.HstSuite.TeardownTest() if CurrentSpecReport().Failed() { s.CollectNginxLogs(s.Containers.NginxHttp3) } - s.HstSuite.TeardownTest() } func (s *NoTopo6Suite) CreateNginxConfig(container *Container, multiThreadWorkers bool) { diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index 92ca24c9d9..2226358c77 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -93,6 +93,7 @@ func (s *VppProxySuite) SetupTest() { } func (s *VppProxySuite) TeardownTest() { + defer s.HstSuite.TeardownTest() vpp := s.Containers.VppProxy.VppInstance if CurrentSpecReport().Failed() { s.Log(vpp.Vppctl("show session verbose 2")) @@ -100,7 +101,6 @@ func (s *VppProxySuite) TeardownTest() { s.CollectNginxLogs(s.Containers.NginxServerTransient) s.CollectIperfLogs(s.Containers.IperfS) } - s.HstSuite.TeardownTest() } func (s *VppProxySuite) SetupNginxServer() { diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index d6f58ac86c..aad821fad1 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -88,12 +88,12 @@ func (s *VppUdpProxySuite) SetupTest() { } func (s *VppUdpProxySuite) TeardownTest() { + defer s.HstSuite.TeardownTest() vpp := s.Containers.VppProxy.VppInstance if CurrentSpecReport().Failed() { s.Log(vpp.Vppctl("show session verbose 2")) s.Log(vpp.Vppctl("show error")) } - s.HstSuite.TeardownTest() } func (s *VppUdpProxySuite) VppProxyAddr() string { diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go index 501eb417c6..ec32f85bc4 100644 --- a/extras/hs-test/infra/vppinstance.go +++ b/extras/hs-test/infra/vppinstance.go @@ -258,6 +258,14 @@ func (vpp *VppInstance) Stop() { } func (vpp *VppInstance) Vppctl(command string, arguments ...any) string { + defer func() { + if r := recover(); r != nil { + fmt.Printf("\n*******************************************************************************\n"+ + "[%v]\nyou probably used Vppctl() without creating a vppinstance first or used Vppctl() on the wrong container\n"+ + "*******************************************************************************\n", r) + } + }() + vppCliCommand := fmt.Sprintf(command, arguments...) containerExecCommand := fmt.Sprintf("docker exec --detach=false %[1]s vppctl -s %[2]s %[3]s", vpp.Container.Name, vpp.getCliSocket(), vppCliCommand) From 2474f132fdb123f2874a41595c5edd14826c95d3 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Mon, 2 Jun 2025 12:12:03 +0200 Subject: [PATCH 025/313] hs-test: fix incorrect volume mounts - shortened volume names to avoid hitting unix socket character limit - volume paths are updated upon container creation Type: fix Change-Id: I601060a902cbba930c324653a600a98c603986c2 Signed-off-by: Adrian Villin --- extras/hs-test/http_test.go | 2 +- extras/hs-test/infra/common/suite_common.go | 2 +- extras/hs-test/infra/container.go | 54 +++++++++++++------ extras/hs-test/infra/hst_suite.go | 12 +---- extras/hs-test/infra/suite_envoy_proxy.go | 1 + extras/hs-test/proxy_test.go | 6 +-- extras/hs-test/topo-containers/2peerVeth.yaml | 8 +-- .../hs-test/topo-containers/envoyProxy.yaml | 2 +- .../hs-test/topo-containers/nginxProxy.yaml | 2 +- extras/hs-test/topo-containers/single.yaml | 2 +- .../topo-containers/singleCpuPinning.yaml | 2 +- extras/hs-test/topo-containers/vppProxy.yaml | 2 +- 12 files changed, 53 insertions(+), 42 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index e572826a19..bbc14ed111 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -585,7 +585,6 @@ func HttpClientPostRepeatTest(s *NoTopoSuite) { func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { vpp := s.Containers.Vpp.VppInstance - logPath := s.Containers.NginxServer.GetContainerWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log" serverAddress := s.Interfaces.Tap.Ip4AddressString() + ":" + s.Ports.NginxServer replyCountInt := 0 repeatAmount := 10000 @@ -598,6 +597,7 @@ func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { s.CreateNginxServer() s.AssertNil(s.Containers.NginxServer.Start()) + logPath := s.Containers.NginxServer.GetContainerWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log" if requestMethod == "post" { fileName := "/tmp/test_file.txt" diff --git a/extras/hs-test/infra/common/suite_common.go b/extras/hs-test/infra/common/suite_common.go index 32fa02b41c..c15b016805 100644 --- a/extras/hs-test/infra/common/suite_common.go +++ b/extras/hs-test/infra/common/suite_common.go @@ -29,7 +29,7 @@ var RunningInCi bool const ( LogDir string = "/tmp/hs-test/" - VolumeDir string = "/volumes" + VolumeDir string = "/vol" ) type HstCommon struct { diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go index 5bb8040005..0426516ae1 100644 --- a/extras/hs-test/infra/container.go +++ b/extras/hs-test/infra/container.go @@ -47,6 +47,7 @@ type Container struct { VppInstance *VppInstance AllocatedCpus []int ctx context.Context + volumeYamlConf []any } func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) { @@ -88,21 +89,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error } if _, ok := yamlInput["volumes"]; ok { - workingVolumeDir := LogDir + suite.GetCurrentTestName() + VolumeDir - workDirReplacer := strings.NewReplacer("$HST_DIR", workDir) - volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir) - for _, volu := range yamlInput["volumes"].([]interface{}) { - volumeMap := volu.(ContainerConfig) - hostDir := workDirReplacer.Replace(volumeMap["host-dir"].(string)) - hostDir = volDirReplacer.Replace(hostDir) - containerDir := volumeMap["container-dir"].(string) - isDefaultWorkDir := false - - if isDefault, ok := volumeMap["is-default-work-dir"]; ok { - isDefaultWorkDir = isDefault.(bool) - } - container.addVolume(hostDir, containerDir, isDefaultWorkDir) - } + container.volumeYamlConf = yamlInput["volumes"].([]any) } if _, ok := yamlInput["vars"]; ok { @@ -162,6 +149,8 @@ func (c *Container) PullDockerImage(name string, ctx context.Context) { // Creates a container func (c *Container) Create() error { + c.createVolumePaths() + var sliceOfImageNames []string images, err := c.Suite.Docker.ImageList(c.ctx, image.ListOptions{}) c.Suite.AssertNil(err) @@ -310,12 +299,36 @@ func (c *Container) Run() { c.Suite.AssertNil(c.Start()) } +func (c *Container) createVolumePaths() { + workingVolumeDir := LogDir + c.Suite.GetCurrentTestName() + VolumeDir + workDirReplacer := strings.NewReplacer("$HST_DIR", workDir) + volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir) + + for _, volu := range c.volumeYamlConf { + volumeMap := volu.(ContainerConfig) + hostDir := workDirReplacer.Replace(volumeMap["host-dir"].(string)) + hostDir = volDirReplacer.Replace(hostDir) + containerDir := volumeMap["container-dir"].(string) + isDefaultWorkDir := false + + if isDefault, ok := volumeMap["is-default-work-dir"]; ok { + isDefaultWorkDir = isDefault.(bool) + } + c.addVolume(hostDir, containerDir, isDefaultWorkDir) + } +} + func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) { var volume Volume - volume.HostDir = strings.Replace(hostDir, "volumes", c.Suite.GetTestId()+"/"+"volumes", 1) + volume.HostDir = strings.Replace(hostDir, "vol", c.Suite.GetTestId()+"/"+"vol", 1) volume.ContainerDir = containerDir volume.IsDefaultWorkDir = isDefaultWorkDir c.Volumes[hostDir] = volume + if volume.IsDefaultWorkDir && len(volume.HostDir)+len(defaultApiSocketFilePath) > 108 { + c.Suite.Log("**************************************************************\n" + + "Default api socket file path exceeds 108 bytes. Test may fail.\n" + + "**************************************************************") + } } func (c *Container) getVolumesAsSlice() []string { @@ -542,6 +555,13 @@ func (c *Container) stop() error { if err := c.Suite.Docker.ContainerStop(c.ctx, c.ID, containerTypes.StopOptions{Timeout: &timeout}); err != nil { return err } + + for n, v := range c.Volumes { + if v.IsDefaultWorkDir { + delete(c.Volumes, n) + } + } + return nil } @@ -558,7 +578,7 @@ func (c *Container) CreateConfigFromTemplate(targetConfigName string, templateNa err = f.Close() c.Suite.AssertNil(err, err) - c.copy(f.Name(), targetConfigName) + c.Suite.AssertNil(c.copy(f.Name(), targetConfigName)) } func init() { diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index 6190e444d8..4e822723a3 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -39,7 +39,6 @@ type HstSuite struct { HstCommon AllContainers map[string]*Container StartedContainers []*Container - Volumes []string NetConfigs []NetConfig NetInterfaces map[string]*NetInterface Ip4AddrAllocator *Ip4AddressAllocator @@ -424,15 +423,6 @@ func (s *HstSuite) LoadContainerTopology(topologyName string) { Fail("unmarshal error: " + fmt.Sprint(err)) } - for _, elem := range yamlTopo.Volumes { - volumeMap := elem["volume"].(VolumeConfig) - hostDir := volumeMap["host-dir"].(string) - workingVolumeDir := LogDir + s.GetCurrentTestName() + VolumeDir - volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir) - hostDir = volDirReplacer.Replace(hostDir) - s.Volumes = append(s.Volumes, hostDir) - } - s.AllContainers = make(map[string]*Container) for _, elem := range yamlTopo.Containers { newContainer, err := newContainer(s, elem) @@ -573,7 +563,7 @@ func (s *HstSuite) GetTestId() string { } if _, ok := s.TestIds[testName]; !ok { - s.TestIds[testName] = time.Now().Format("2006-01-02_15-04-05") + s.TestIds[testName] = time.Now().Format("060102_150405") } return s.TestIds[testName] diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go index 2d184270b8..aac4588e02 100644 --- a/extras/hs-test/infra/suite_envoy_proxy.go +++ b/extras/hs-test/infra/suite_envoy_proxy.go @@ -65,6 +65,7 @@ func (s *EnvoyProxySuite) SetupSuite() { s.Containers.Curl = s.GetContainerByName("curl") s.Ports.Nginx = s.GeneratePortAsInt() s.Ports.Proxy = s.GeneratePortAsInt() + s.Ports.EnvoyAdmin = s.GeneratePortAsInt() } func (s *EnvoyProxySuite) SetupTest() { diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index 9e107b5127..875d79f757 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -28,7 +28,7 @@ func init() { RegisterVppUdpProxyTests(VppProxyUdpTest, VppConnectUdpProxyTest, VppConnectUdpInvalidCapsuleTest, VppConnectUdpUnknownCapsuleTest, VppConnectUdpClientCloseTest, VppConnectUdpInvalidTargetTest) RegisterVppUdpProxySoloTests(VppProxyUdpMigrationMTTest, VppConnectUdpStressMTTest, VppConnectUdpStressTest) - RegisterEnvoyProxyTests(EnvoyProxyHttpGetTcpTest, EnvoyProxyHttpPutTcpTest) + RegisterEnvoyProxyTests(EnvoyHttpGetTcpTest, EnvoyHttpPutTcpTest) RegisterNginxProxySoloTests(NginxMirroringTest, MirrorMultiThreadTest) } @@ -141,12 +141,12 @@ func VppProxyHttpPutTlsTest(s *VppProxySuite) { s.CurlUploadResource(uri, CurlContainerTestFile) } -func EnvoyProxyHttpGetTcpTest(s *EnvoyProxySuite) { +func EnvoyHttpGetTcpTest(s *EnvoyProxySuite) { uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } -func EnvoyProxyHttpPutTcpTest(s *EnvoyProxySuite) { +func EnvoyHttpPutTcpTest(s *EnvoyProxySuite) { uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ProxyAddr(), s.Ports.Proxy) s.CurlUploadResource(uri, CurlContainerTestFile) } diff --git a/extras/hs-test/topo-containers/2peerVeth.yaml b/extras/hs-test/topo-containers/2peerVeth.yaml index e1591fb901..790739bd3c 100644 --- a/extras/hs-test/topo-containers/2peerVeth.yaml +++ b/extras/hs-test/topo-containers/2peerVeth.yaml @@ -1,12 +1,12 @@ --- volumes: - volume: &server-vol - host-dir: "$HST_VOLUME_DIR/server-share" - container-dir: "/tmp/server-share" + host-dir: "$HST_VOLUME_DIR/server" + container-dir: "/tmp/server" is-default-work-dir: true - volume: &client-vol - host-dir: "$HST_VOLUME_DIR/client-share" - container-dir: "/tmp/client-share" + host-dir: "$HST_VOLUME_DIR/client" + container-dir: "/tmp/client" is-default-work-dir: true containers: diff --git a/extras/hs-test/topo-containers/envoyProxy.yaml b/extras/hs-test/topo-containers/envoyProxy.yaml index cbb00d868d..cb2d673b78 100644 --- a/extras/hs-test/topo-containers/envoyProxy.yaml +++ b/extras/hs-test/topo-containers/envoyProxy.yaml @@ -1,7 +1,7 @@ --- volumes: - volume: &shared-vol - host-dir: "$HST_VOLUME_DIR/shared-vol" + host-dir: "$HST_VOLUME_DIR/shared" containers: - name: "vpp" diff --git a/extras/hs-test/topo-containers/nginxProxy.yaml b/extras/hs-test/topo-containers/nginxProxy.yaml index d9ddc14590..5290186578 100644 --- a/extras/hs-test/topo-containers/nginxProxy.yaml +++ b/extras/hs-test/topo-containers/nginxProxy.yaml @@ -1,7 +1,7 @@ --- volumes: - volume: &shared-vol-nginx-proxy - host-dir: "$HST_VOLUME_DIR/shared-vol-nginx-proxy" + host-dir: "$HST_VOLUME_DIR/shared" containers: - name: "vpp" diff --git a/extras/hs-test/topo-containers/single.yaml b/extras/hs-test/topo-containers/single.yaml index 7b2f91ebc7..d1f43ad07b 100644 --- a/extras/hs-test/topo-containers/single.yaml +++ b/extras/hs-test/topo-containers/single.yaml @@ -1,7 +1,7 @@ --- volumes: - volume: &shared-vol - host-dir: "$HST_VOLUME_DIR/shared-vol" + host-dir: "$HST_VOLUME_DIR/shared" containers: - name: "vpp" diff --git a/extras/hs-test/topo-containers/singleCpuPinning.yaml b/extras/hs-test/topo-containers/singleCpuPinning.yaml index 6e673aa85b..0a41ac8469 100644 --- a/extras/hs-test/topo-containers/singleCpuPinning.yaml +++ b/extras/hs-test/topo-containers/singleCpuPinning.yaml @@ -1,7 +1,7 @@ --- volumes: - volume: &shared-vol - host-dir: "$HST_VOLUME_DIR/shared-vol" + host-dir: "$HST_VOLUME_DIR/shared" containers: - name: "vpp" diff --git a/extras/hs-test/topo-containers/vppProxy.yaml b/extras/hs-test/topo-containers/vppProxy.yaml index 557d65d644..5f0248027a 100644 --- a/extras/hs-test/topo-containers/vppProxy.yaml +++ b/extras/hs-test/topo-containers/vppProxy.yaml @@ -1,7 +1,7 @@ --- volumes: - volume: &shared-vol - host-dir: "$HST_VOLUME_DIR/shared-vol" + host-dir: "$HST_VOLUME_DIR/shared" containers: - name: "vpp-proxy" From cc325c726a1338771f53050ed9b42410597ee514 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 28 May 2025 16:47:49 +0200 Subject: [PATCH 026/313] hs-test: improvements and cleanup - removed remaining 0.0.0.0 binds - ip address files are now removed on suite teardown (parallel testing should now be more reliable) - removed unused Iperf helper functions Type: test Change-Id: I3af630b11a2713b3d5d2828ba6efbabfad571e1e Signed-off-by: Adrian Villin --- extras/hs-test/http2_test.go | 4 +- extras/hs-test/http_test.go | 24 ++++++------ extras/hs-test/infra/hst_suite.go | 17 ++++---- extras/hs-test/infra/suite_envoy_proxy.go | 2 + extras/hs-test/infra/utils.go | 47 ----------------------- extras/hs-test/ldp_test.go | 4 +- extras/hs-test/tls_test.go | 30 +++++++++------ extras/hs-test/vcl_test.go | 29 +++++++------- 8 files changed, 59 insertions(+), 98 deletions(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 3ee4e9f06d..dc2d210000 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -63,7 +63,7 @@ func Http2TcpPostTest(s *H2Suite) { func Http2MultiplexingTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 - vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Port1 + " no-zc") + vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") args := fmt.Sprintf("--log-file=%s -T10 -n21 -c1 -m100 http://%s/test_file_20M", s.H2loadLogFileName(s.Containers.H2load), serverAddress) s.Containers.H2load.ExtraRunningArgs = args @@ -81,7 +81,7 @@ func Http2MultiplexingTest(s *H2Suite) { func Http2MultiplexingMTTest(s *H2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 - vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Port1 + " no-zc") + vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") args := fmt.Sprintf("-T10 -n100 -c4 -r1 -m10 http://%s/test_file_20M", serverAddress) s.Containers.H2load.ExtraRunningArgs = args diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index bbc14ed111..84f0b79787 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -70,20 +70,20 @@ func HttpGetTpsInterruptModeTest(s *NoTopoSuite) { func HttpGetTpsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "http://" + serverAddress + "/test_file_10M" - vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Http) + vpp.Vppctl("http tps uri tcp://%s", serverAddress) s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url) } func HttpGetTpsTlsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "https://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "https://" + serverAddress + "/test_file_10M" - vpp.Vppctl("http tps uri tls://0.0.0.0/" + s.Ports.Http) + vpp.Vppctl("http tps uri tls://%s", serverAddress) s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url) } @@ -113,20 +113,20 @@ func HttpPostTpsInterruptModeTest(s *NoTopoSuite) { func HttpPostTpsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "http://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "http://" + serverAddress + "/test_file_10M" - vpp.Vppctl("http tps uri tcp://0.0.0.0/" + s.Ports.Http) + vpp.Vppctl("http tps uri tcp://%s", serverAddress) s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } func HttpPostTpsTlsTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - url := "https://" + serverAddress + ":" + s.Ports.Http + "/test_file_10M" + serverAddress := s.VppAddr() + ":" + s.Ports.Http + url := "https://" + serverAddress + "/test_file_10M" - vpp.Vppctl("http tps uri tls://0.0.0.0/" + s.Ports.Http) + vpp.Vppctl("http tps uri tls://%s", serverAddress) s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index 4e822723a3..e9d0dacce0 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -66,6 +66,7 @@ var Colors = colors{ // ../../src/vnet/udp/udp_local.h:foreach_udp4_dst_port var reservedPorts = []string{ + "22", "53", "67", "68", @@ -184,6 +185,13 @@ func (s *HstSuite) TeardownSuite() { s.HstCommon.TeardownSuite() // allow ports to be reused by removing them from reservedPorts slice reservedPorts = reservedPorts[:len(reservedPorts)-s.numOfNewPorts] + if s.Ip4AddrAllocator != nil { + s.Ip4AddrAllocator.DeleteIpAddresses() + } + + if s.Ip6AddrAllocator != nil { + s.Ip6AddrAllocator.DeleteIpAddresses() + } defer s.LogFile.Close() defer s.Docker.Close() s.UnconfigureNetworkTopology() @@ -194,14 +202,6 @@ func (s *HstSuite) TeardownTest() { coreDump := s.WaitForCoreDump() s.ResetContainers() - if s.Ip4AddrAllocator != nil { - s.Ip4AddrAllocator.DeleteIpAddresses() - } - - if s.Ip6AddrAllocator != nil { - s.Ip6AddrAllocator.DeleteIpAddresses() - } - if coreDump { Fail("VPP crashed") } @@ -587,6 +587,7 @@ func (s *HstSuite) GeneratePort() string { port += "0" } port = port[len(port)-3:] + s.ProcessIndex + port = strings.TrimLeft(port, "0") for slices.Contains(reservedPorts, port) { portInt, err = strconv.Atoi(port) s.AssertNil(err) diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go index aac4588e02..a099d2ca40 100644 --- a/extras/hs-test/infra/suite_envoy_proxy.go +++ b/extras/hs-test/infra/suite_envoy_proxy.go @@ -149,6 +149,8 @@ func (s *EnvoyProxySuite) SetupTest() { s.Containers.Vpp.VppInstance.Vppctl(arp) s.AssertNil(s.Containers.NginxServerTransient.Start()) s.AssertNil(s.Containers.EnvoyProxy.Start()) + // give envoy some time to start + time.Sleep(time.Second * 2) } func (s *EnvoyProxySuite) TeardownTest() { diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index a4dec989e9..8be7fb510c 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -234,53 +234,6 @@ func (s *HstSuite) CollectH2loadLogs(h2loadContainer *Container) { } } -func (s *HstSuite) StartIperfServerApp(running chan error, done chan struct{}, env []string) { - cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GeneratePort()) - if env != nil { - cmd.Env = env - } - s.Log(cmd) - err := cmd.Start() - if err != nil { - msg := fmt.Errorf("failed to start iperf server: %v", err) - running <- msg - return - } - running <- nil - <-done - cmd.Process.Kill() -} - -func (s *HstSuite) StartIperfClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) { - defer func() { - clnCh <- nil - }() - - nTries := 0 - - for { - cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GeneratePort()) - if env != nil { - cmd.Env = env - } - s.Log(cmd) - o, err := cmd.CombinedOutput() - if err != nil { - if nTries > 5 { - clnRes <- "" - clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o) - return - } - time.Sleep(1 * time.Second) - nTries++ - continue - } else { - clnRes <- fmt.Sprintf("Client output: %s", o) - } - break - } -} - func (s *HstSuite) StartHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) { cmd := newCommand([]string{"./http_server", addressPort, s.Ppid, s.ProcessIndex}, netNs) err := cmd.Start() diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index c30bc27055..951d58ca9c 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -97,7 +97,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() - cmd := "sh -c \"iperf3 -4 -s -p " + s.Ports.Port1 + " > " + s.IperfLogFileName(s.Containers.ServerApp) + " 2>&1\"" + cmd := fmt.Sprintf("sh -c \"iperf3 -4 -s -B %s -p %s > %s 2>&1\"", serverVethAddress, s.Ports.Port1, s.IperfLogFileName(s.Containers.ServerApp)) s.StartServerApp(s.Containers.ServerApp, "iperf3", cmd, srvCh, stopServerCh) }() @@ -106,7 +106,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { go func() { defer GinkgoRecover() - cmd := "iperf3 -c " + serverVethAddress + " -l 1460 -b 10g -J -p " + s.Ports.Port1 + " " + extraClientArgs + cmd := fmt.Sprintf("iperf3 -c %s -B %s -l 1460 -b 10g -J -p %s %s", serverVethAddress, s.Interfaces.Client.Ip4AddressString(), s.Ports.Port1, extraClientArgs) s.StartClientApp(s.Containers.ClientApp, cmd, clnCh, clnRes) }() diff --git a/extras/hs-test/tls_test.go b/extras/hs-test/tls_test.go index 7689ef67c0..050bd3be91 100644 --- a/extras/hs-test/tls_test.go +++ b/extras/hs-test/tls_test.go @@ -9,9 +9,10 @@ func init() { } func TlsAlpMatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 uri tls://0.0.0.0:" + s.Ports.Port1)) + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 uri tls://" + serverAddress)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + uri := "tls://" + serverAddress o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -21,9 +22,10 @@ func TlsAlpMatchTest(s *VethsSuite) { } func TlsAlpnOverlapMatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://" + serverAddress)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + uri := "tls://" + serverAddress o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 alpn-proto2 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -33,9 +35,10 @@ func TlsAlpnOverlapMatchTest(s *VethsSuite) { } func TlsAlpnServerPriorityMatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://" + serverAddress)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + uri := "tls://" + serverAddress o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -45,9 +48,10 @@ func TlsAlpnServerPriorityMatchTest(s *VethsSuite) { } func TlsAlpnMismatchTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://" + serverAddress)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + uri := "tls://" + serverAddress o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 alpn-proto2 4 uri " + uri) s.Log(o) s.AssertNotContains(o, "timeout") @@ -57,9 +61,10 @@ func TlsAlpnMismatchTest(s *VethsSuite) { } func TlsAlpnEmptyServerListTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server uri tls://0.0.0.0:" + s.Ports.Port1)) + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server uri tls://" + serverAddress)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + uri := "tls://" + serverAddress o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 2 uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") @@ -69,9 +74,10 @@ func TlsAlpnEmptyServerListTest(s *VethsSuite) { } func TlsAlpnEmptyClientListTest(s *VethsSuite) { - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:" + s.Ports.Port1)) + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://" + serverAddress)) - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + uri := "tls://" + serverAddress o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client uri " + uri) s.Log(o) s.AssertNotContains(o, "connect failed") diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go index 5fa331288b..e00937b2a4 100644 --- a/extras/hs-test/vcl_test.go +++ b/extras/hs-test/vcl_test.go @@ -9,7 +9,8 @@ import ( func init() { RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest, - XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest, VclRetryAttachTest) + XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest) + RegisterSoloVethTests(VclRetryAttachTest) } func getVclConfig(c *Container, ns_id_optional ...string) string { @@ -66,14 +67,13 @@ func XEchoVclServerTcpTest(s *VethsSuite) { func testXEchoVclServer(s *VethsSuite, proto string) { srvVppCont := s.Containers.ServerVpp srvAppCont := s.Containers.ServerApp + serverVethAddress := s.Interfaces.Server.Ip4AddressString() srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont)) srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") - vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s %s", proto, s.Ports.Port1) + vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s -B %s %s", proto, serverVethAddress, s.Ports.Port1) srvAppCont.ExecServer(true, vclSrvCmd) - serverVethAddress := s.Interfaces.Server.Ip4AddressString() - clientVpp := s.Containers.ClientVpp.VppInstance o := clientVpp.Vppctl("test echo client uri %s://%s/%s fifo-size 64k verbose bytes 2m", proto, serverVethAddress, s.Ports.Port1) s.Log(o) @@ -83,12 +83,12 @@ func testXEchoVclServer(s *VethsSuite, proto string) { func testVclEcho(s *VethsSuite, proto string) { srvVppCont := s.Containers.ServerVpp srvAppCont := s.Containers.ServerApp + serverVethAddress := s.Interfaces.Server.Ip4AddressString() srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont)) srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") - srvAppCont.ExecServer(true, "vcl_test_server -p "+proto+" "+s.Ports.Port1) - - serverVethAddress := s.Interfaces.Server.Ip4AddressString() + vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s -B %s %s", proto, serverVethAddress, s.Ports.Port1) + srvAppCont.ExecServer(true, vclSrvCmd) echoClnContainer := s.GetTransientContainerByName("client-app") echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer)) @@ -112,25 +112,25 @@ func VclHttpPostTest(s *VethsSuite) { testVclEcho(s, "http") } +// solo because binding server to an IP makes the test fail in the CI func VclRetryAttachTest(s *VethsSuite) { testRetryAttach(s, "tcp") } func testRetryAttach(s *VethsSuite, proto string) { srvVppContainer := s.GetTransientContainerByName("server-vpp") - echoSrvContainer := s.Containers.ServerApp + serverVethAddress := s.Interfaces.Server.Ip4AddressString() echoSrvContainer.CreateFile("/vcl.conf", getVclConfig(echoSrvContainer)) - echoSrvContainer.AddEnvVar("VCL_CONFIG", "/vcl.conf") - echoSrvContainer.ExecServer(true, "vcl_test_server -p "+proto+" "+s.Ports.Port1) + + vclSrvCmd := fmt.Sprintf("vcl_test_server -p %s %s", proto, s.Ports.Port1) + echoSrvContainer.ExecServer(true, vclSrvCmd) s.Log("This whole test case can take around 3 minutes to run. Please be patient.") s.Log("... Running first echo client test, before disconnect.") - serverVethAddress := s.Interfaces.Server.Ip4AddressString() - echoClnContainer := s.GetTransientContainerByName("client-app") echoClnContainer.CreateFile("/vcl.conf", getVclConfig(echoClnContainer)) @@ -143,8 +143,7 @@ func testRetryAttach(s *VethsSuite, proto string) { // Stop server-vpp-instance, start it again and then run vcl-test-client once more srvVppContainer.VppInstance.Disconnect() - stopVppCommand := "/bin/bash -c 'ps -C vpp_main -o pid= | xargs kill -9'" - srvVppContainer.Exec(false, stopVppCommand) + srvVppContainer.VppInstance.Stop() s.SetupServerVpp() @@ -153,7 +152,7 @@ func testRetryAttach(s *VethsSuite, proto string) { s.Log("... Running second echo client test, after disconnect and re-attachment.") o, err = echoClnContainer.Exec(true, testClientCommand) - s.AssertNil(err) s.Log(o) + s.AssertNil(err, o) s.Log("Done.") } From 47303ea67d08e98fe1fc404764bff93d578bd9a5 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Mon, 2 Jun 2025 11:34:15 +0000 Subject: [PATCH 027/313] hsa: introduce a configurable body limit for http client Added a limit for returned body sizes, to make sure we're not allocating too much memory. Configurable with the max-body-size cli parameter. Type: improvement Change-Id: I732d2cfbc8c02ec85c052505b98177554960da88 Signed-off-by: Semir Sionek --- extras/hs-test/http_test.go | 25 +++++++++++++++++++- src/plugins/hs_apps/http_client.c | 39 +++++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 84f0b79787..953898d509 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -37,7 +37,7 @@ func init() { HttpClientGetTlsNoRespBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, - HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable) + HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest, HttpClientGetRepeatMTTest, HttpClientPtrGetRepeatMTTest) @@ -337,6 +337,29 @@ func HttpClientTest(s *NoTopoSuite) { s.AssertContains(o, "", " not found in the result!") } +func HttpClientBodySizeTest(s *NoTopoSuite) { + serverAddress := s.HostAddr() + ":" + s.Ports.Http + server := ghttp.NewUnstartedServer() + l, err := net.Listen("tcp", serverAddress) + s.AssertNil(err, fmt.Sprint(err)) + server.HTTPTestServer.Listener = l + server.AppendHandlers( + ghttp.CombineHandlers( + s.LogHttpReq(true), + ghttp.VerifyRequest("GET", "/test"), + ghttp.RespondWith(http.StatusOK, "

Hello

"), + )) + server.Start() + defer server.Close() + uri := "http://" + serverAddress + "/test" + vpp := s.Containers.Vpp.VppInstance + o := vpp.Vppctl("http client max-body-size 5 verbose uri " + uri) + + s.Log(o) + s.AssertContains(o, "* message body over limit", "message body size info not found in result!") + s.AssertContains(o, ", read total 38 bytes", "client retrieved invalid amount of bytes!") +} + func HttpClientInvalidHeaderNameTest(s *NoTopoSuite) { serverAddress := s.HostAddr() l, err := net.Listen("tcp", serverAddress+":80") diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 3f72dccfea..7b86278d44 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -25,8 +25,10 @@ typedef struct clib_thread_index_t thread_index; u64 to_recv; u8 is_closed; + u8 body_over_limit; hc_stats_t stats; u64 data_offset; + u64 body_recv; u8 *resp_headers; u8 *http_response; u8 *response_status; @@ -78,11 +80,13 @@ typedef struct u32 private_segment_size; u32 prealloc_fifos; u32 fifo_size; + u32 rx_fifo_size; u8 *appns_id; u64 appns_secret; clib_spinlock_t lock; bool was_transport_closed; u32 ckpair_index; + u64 max_body_size; } hc_main_t; typedef enum @@ -213,6 +217,7 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, clib_spinlock_unlock_if_init (&hcm->lock); hc_session->thread_index = s->thread_index; + hc_session->body_recv = 0; s->opaque = hc_session->session_index; wrk->session_index = hc_session->session_index; @@ -422,11 +427,17 @@ hc_rx_callback (session_t *s) { goto done; } - vec_validate (hc_session->http_response, msg.data.body_len - 1); + if (msg.data.body_len > hcm->max_body_size) + hc_session->body_over_limit = true; + vec_validate (hc_session->http_response, + (hc_session->body_over_limit ? hcm->rx_fifo_size - 1 : + msg.data.body_len - 1)); vec_reset_length (hc_session->http_response); } - max_deq = svm_fifo_max_dequeue (s->rx_fifo); + max_deq = (svm_fifo_max_dequeue (s->rx_fifo) > hcm->max_body_size ? + hcm->rx_fifo_size : + svm_fifo_max_dequeue (s->rx_fifo)); if (!max_deq) { goto done; @@ -443,9 +454,11 @@ hc_rx_callback (session_t *s) } ASSERT (rv == n_deq); - vec_set_len (hc_session->http_response, curr + n_deq); + if (!hc_session->body_over_limit) + vec_set_len (hc_session->http_response, curr + n_deq); ASSERT (hc_session->to_recv >= rv); hc_session->to_recv -= rv; + hc_session->body_recv += rv; done: if (hc_session->to_recv == 0) @@ -547,6 +560,7 @@ hc_attach () a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = hcm->prealloc_fifos; a->options[APP_OPTIONS_TLS_ENGINE] = CRYPTO_ENGINE_OPENSSL; + hcm->rx_fifo_size = a->options[APP_OPTIONS_RX_FIFO_SIZE]; if (hcm->appns_id) { a->namespace_id = hcm->appns_id; @@ -718,9 +732,14 @@ hc_get_event (vlib_main_t *vm) { wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); - vlib_cli_output (vm, "< %v\n< %v\n%v", hc_session->response_status, - hc_session->resp_headers, - hc_session->http_response); + vlib_cli_output (vm, "< %v\n< %v\n", hc_session->response_status, + hc_session->resp_headers); + if (hc_session->body_over_limit) + vlib_cli_output ( + vm, "* message body over limit, read total %llu bytes", + hc_session->body_recv); + else + vlib_cli_output (vm, "%v", hc_session->http_response); } break; case HC_REPEAT_DONE: @@ -853,6 +872,8 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hcm->private_segment_size = 0; hcm->fifo_size = 0; hcm->was_transport_closed = false; + /* default max - 64MB */ + hcm->max_body_size = 64 << 20; hc_stats.request_count = 0; hc_stats.elapsed_time = 0; @@ -917,6 +938,9 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "prealloc-fifos %d", &hcm->prealloc_fifos)) ; + else if (unformat (line_input, "max-body-size %U", unformat_memory_size, + &hcm->max_body_size)) + ; else if (unformat (line_input, "private-segment-size %U", unformat_memory_size, &mem_size)) hcm->private_segment_size = mem_size; @@ -1031,7 +1055,8 @@ VLIB_CLI_COMMAND (hc_command, static) = { "[save-to ] [header ] [verbose] " "[timeout (default = 10)] [repeat | duration ] " "[sessions <# of sessions>] [appns secret ] " - "[fifo-size ] [private-segment-size ] [prealloc-fifos ]", + "[fifo-size ] [private-segment-size ] [prealloc-fifos ]" + "[max-body-size ]", .function = hc_command_fn, .is_mp_safe = 1, }; From 68efdff8e3ebfeee14401a628889801ab5bfe44b Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Wed, 4 Jun 2025 10:32:20 +0000 Subject: [PATCH 028/313] interface: clear flags after checksum computation Type: fix Signed-off-by: Mohsin Kazmi Change-Id: I8f193f03176fa6c8a4f426c009d2978bb8f142fe --- src/vnet/interface_output.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vnet/interface_output.h b/src/vnet/interface_output.h index b512d9a04a..6464c0ef42 100644 --- a/src/vnet/interface_output.h +++ b/src/vnet/interface_output.h @@ -144,6 +144,7 @@ vnet_calc_outer_checksums_inline (vlib_main_t *vm, vlib_buffer_t *b) vnet_buffer_offload_flags_clear (b, VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM); } + vnet_buffer_offload_flags_clear (b, VNET_BUFFER_OFFLOAD_F_TNL_MASK); } #endif From f8da64e60e9f536b62a82a415f04b277a35b3224 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Thu, 22 May 2025 19:34:42 +0000 Subject: [PATCH 029/313] ip: compute checksums before fragmentation if offloaded Type: fix When a packet with checksum offloading enabled is fragmented into multiple IP fragments, due to the egress interface's MTU being smaller than the original packet size, it gets dropped after reassembly because the reconstructed packet contains an invalid checksum. This patch addresses the issue by computing the checksum in the IP fragmentation logic before the packet is fragmented, ensuring the resulting fragments carry a valid checksum upon reassembly. Signed-off-by: Mohsin Kazmi Change-Id: I202f169887ae9594f9580a4c7901d7b4483ef5f9 --- src/vnet/ip/ip_frag.c | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/vnet/ip/ip_frag.c b/src/vnet/ip/ip_frag.c index 934e40a5d1..729aa677d4 100644 --- a/src/vnet/ip/ip_frag.c +++ b/src/vnet/ip/ip_frag.c @@ -22,6 +22,7 @@ #include "ip_frag.h" #include +#include typedef struct { @@ -99,6 +100,14 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, u8 *org_from_packet, more; from_b = vlib_get_buffer (vm, from_bi); + + if (from_b->flags & VNET_BUFFER_F_OFFLOAD) + { + ASSERT ((from_b->flags & VNET_BUFFER_F_GSO) == 0); + vnet_calc_checksums_inline (vm, from_b, 1 /* is_v4 */, 0 /* is_v6 */); + vnet_calc_outer_checksums_inline (vm, from_b); + } + org_from_packet = vlib_buffer_get_current (from_b); ip4 = vlib_buffer_get_current (from_b) + l2unfragmentablesize; @@ -172,15 +181,6 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, vlib_buffer_copy_trace_flag (vm, from_b, to_bi); to_b->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; - if (from_b->flags & VNET_BUFFER_F_L4_HDR_OFFSET_VALID) - { - vnet_buffer (to_b)->l4_hdr_offset = - (vnet_buffer (to_b)->l3_hdr_offset + - (vnet_buffer (from_b)->l4_hdr_offset - - vnet_buffer (from_b)->l3_hdr_offset)); - to_b->flags |= VNET_BUFFER_F_L4_HDR_OFFSET_VALID; - } - /* Spin through from buffers filling up the to buffer */ u16 left_in_to_buffer = len, to_ptr = 0; while (1) @@ -221,9 +221,6 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, to_ip4->length = clib_host_to_net_u16 (len + sizeof (ip4_header_t)); to_ip4->checksum = ip4_header_checksum (to_ip4); - /* we've just done the IP checksum .. */ - vnet_buffer_offload_flags_clear (to_b, VNET_BUFFER_OFFLOAD_F_IP_CKSUM); - rem -= len; fo += len; } @@ -385,6 +382,14 @@ ip6_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, u16 head_bytes; from_b = vlib_get_buffer (vm, from_bi); + + if (from_b->flags & VNET_BUFFER_F_OFFLOAD) + { + ASSERT ((from_b->flags & VNET_BUFFER_F_GSO) == 0); + vnet_calc_checksums_inline (vm, from_b, 0 /* is_v4 */, 1 /* is_v6 */); + vnet_calc_outer_checksums_inline (vm, from_b); + } + org_from_packet = vlib_buffer_get_current (from_b); ip6 = vlib_buffer_get_current (from_b) + l2unfragmentablesize; @@ -443,15 +448,6 @@ ip6_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, vnet_buffer (to_b)->l3_hdr_offset = to_b->current_data; to_b->flags |= VNET_BUFFER_F_L3_HDR_OFFSET_VALID; - - if (from_b->flags & VNET_BUFFER_F_L4_HDR_OFFSET_VALID) - { - vnet_buffer (to_b)->l4_hdr_offset = - (vnet_buffer (to_b)->l3_hdr_offset + - (vnet_buffer (from_b)->l4_hdr_offset - - vnet_buffer (from_b)->l3_hdr_offset)); - to_b->flags |= VNET_BUFFER_F_L4_HDR_OFFSET_VALID; - } to_b->flags |= VNET_BUFFER_F_IS_IP6; /* Spin through from buffers filling up the to buffer */ From 8e871515b090367e49fbed7dc8ed82fae775c3fe Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 21 May 2025 11:28:32 +0200 Subject: [PATCH 030/313] vnet: add vnet_buffer_get_opaque() inline gives back pointer to vnet opaque area... Type: improvement Change-Id: Iab15278ee85f1cfda89522c72cf6e012e0b21dc1 Signed-off-by: Damjan Marion --- src/vnet/buffer.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vnet/buffer.h b/src/vnet/buffer.h index 5620f995c9..ae89915863 100644 --- a/src/vnet/buffer.h +++ b/src/vnet/buffer.h @@ -450,6 +450,15 @@ STATIC_ASSERT (sizeof (vnet_buffer_opaque_t) <= #define vnet_buffer(b) ((vnet_buffer_opaque_t *) (b)->opaque) +static_always_inline void * +vnet_buffer_get_opaque (vlib_buffer_t *b) +{ + return vnet_buffer (b)->unused; +} + +#define VNET_BUFFER_OPAQUE_SIZE \ + (sizeof (vnet_buffer ((vlib_buffer_t *) 0)->unused)) + /* Full cache line (64 bytes) of additional space */ typedef struct { From 499edced885da754e77cb5970625db338e6ca3f6 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Thu, 5 Jun 2025 14:10:10 +0200 Subject: [PATCH 031/313] hs-test: run redis in app containers - set redis output to quiet mode - 'vol' directory is no longer copied to CI archives Type: fix Change-Id: I307e60e0789af740f245e2c6729cd844fab47e21 Signed-off-by: Adrian Villin --- extras/hs-test/ldp_test.go | 12 ++++++------ extras/hs-test/script/compress.sh | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 951d58ca9c..43b44a73b8 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -153,11 +153,11 @@ func RedisBenchmarkTest(s *LdpSuite) { go func() { defer GinkgoRecover() // Avoid redis warning during startup - s.Containers.ServerVpp.Exec(false, "sysctl vm.overcommit_memory=1") + s.Containers.ServerApp.Exec(false, "sysctl vm.overcommit_memory=1") // Note: --save "" disables snapshotting which during upgrade to ubuntu 24.04 was // observed to corrupt vcl memory / heap. Needs more debugging. - cmd := "redis-server --daemonize yes --protected-mode no --save \"\" --bind " + serverVethAddress + " --loglevel notice --logfile " + s.RedisServerLogFileName(s.Containers.ServerVpp) - s.StartServerApp(s.Containers.ServerVpp, "redis-server", cmd, runningSrv, doneSrv) + cmd := "redis-server --daemonize yes --protected-mode no --save \"\" --bind " + serverVethAddress + " --loglevel notice --logfile " + s.RedisServerLogFileName(s.Containers.ServerApp) + s.StartServerApp(s.Containers.ServerApp, "redis-server", cmd, runningSrv, doneSrv) }() err := <-runningSrv @@ -167,11 +167,11 @@ func RedisBenchmarkTest(s *LdpSuite) { defer GinkgoRecover() var cmd string if *NConfiguredCpus == 1 { - cmd = "redis-benchmark --threads 1 -h " + serverVethAddress + cmd = "redis-benchmark -q --threads 1 -h " + serverVethAddress } else { - cmd = "redis-benchmark --threads " + fmt.Sprint(*NConfiguredCpus) + "-h " + serverVethAddress + cmd = "redis-benchmark -q --threads " + fmt.Sprint(*NConfiguredCpus) + "-h " + serverVethAddress } - s.StartClientApp(s.Containers.ClientVpp, cmd, clnCh, clnRes) + s.StartClientApp(s.Containers.ClientApp, cmd, clnCh, clnRes) }() diff --git a/extras/hs-test/script/compress.sh b/extras/hs-test/script/compress.sh index ebc60d2577..cb3f641660 100755 --- a/extras/hs-test/script/compress.sh +++ b/extras/hs-test/script/compress.sh @@ -16,7 +16,7 @@ then logDir=/tmp/hs-test/$dirName if [ -d "$logDir" ]; then mkdir -p ${WORKSPACE}/archives/summary - rsync -a --exclude 'volumes' $logDir ${WORKSPACE}/archives/summary/ + rsync -a --exclude 'vol' $logDir ${WORKSPACE}/archives/summary/ fi done echo "Done." From 5a3f776e078df7143991b0930d2aa602bc736146 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Thu, 5 Jun 2025 11:06:42 +0200 Subject: [PATCH 032/313] hs-test: simplify KinD deps installation Type: test Change-Id: Icc4eb31fc04dbc2675c4e93b4e99290afb862f36 Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 34 ++++++++++++++- extras/hs-test/framework_test.go | 2 +- extras/hs-test/hs_test.sh | 1 + extras/hs-test/infra/common/suite_common.go | 3 +- extras/hs-test/infra/kind/suite_kind.go | 32 ++++++++++++++ extras/hs-test/kind_test.go | 46 +++++---------------- extras/hs-test/kubernetes/setupCluster.sh | 10 ----- 7 files changed, 79 insertions(+), 49 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index bc72171a93..24026fc247 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -172,11 +172,11 @@ test-perf: FORCE_BUILD=false test-perf: .deps.ok .build.ok @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --test=$(TEST) --vppsrc=$(VPPSRC) --repeat=$(REPEAT) \ - --skip=$(SKIP) --no_color=$(NO_COLOR) --perf=true; \ + --skip=$(SKIP) --no_color=$(NO_COLOR) --perf=true --timeout=$(TIMEOUT); \ ./script/compress.sh $$? .PHONY: setup-cluster -setup-cluster: +setup-cluster: .kind_deps.ok @bash ./kubernetes/setupCluster.sh .PHONY: build-go @@ -204,9 +204,39 @@ build-debug: .deps.ok build-vpp-debug build-go .deps.ok: @$(MAKE) install-deps +.kind_deps.ok: + @$(MAKE) install-kind-deps + +.PHONY: install-kind-deps +install-kind-deps: .deps.ok + @go install sigs.k8s.io/kind@v0.29.0 + -@if which kind > /dev/null 2>&1; then \ + sudo ln -s $$(which kind) /usr/bin/kind; \ + fi + @if ! command -v kubectl >/dev/null 2>&1; then \ + echo "kubectl not found. Installing kubectl..."; \ + sudo -E apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl gnupg; \ + curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg; \ + sudo -E chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg; \ + echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list; \ + sudo -E chmod 644 /etc/apt/sources.list.d/kubernetes.list; \ + sudo apt-get update && sudo apt-get install -y kubectl; \ + else \ + echo "kubectl is already installed. Skipping installation."; \ + fi + @touch .kind_deps.ok + .PHONY: install-deps install-deps: @rm -f .deps.ok + @if [ -d "/usr/local/go" ]; then \ + echo "Go is already installed. You may have to update it manually if version < 1.22"; \ + go version; \ + else \ + echo "Installing Go 1.22"; \ + wget https://go.dev/dl/go1.22.12.linux-amd64.tar.gz -O /tmp/go1.22.12.linux-amd64.tar.gz && sudo tar -C /usr/local -xzf /tmp/go1.22.12.linux-amd64.tar.gz; \ + sudo ln -s /usr/local/go/bin/go /usr/bin/go ; \ + fi @sudo -E apt-get update @sudo -E apt-get install -y apt-transport-https ca-certificates curl software-properties-common \ bridge-utils gpg diff --git a/extras/hs-test/framework_test.go b/extras/hs-test/framework_test.go index f75f949e5e..67aab3f403 100644 --- a/extras/hs-test/framework_test.go +++ b/extras/hs-test/framework_test.go @@ -28,7 +28,7 @@ var _ = ReportAfterSuite("VPP version under test", func(report Report) { func TestHst(t *testing.T) { // if we're debugging/running a coverage build and timeout isn't overridden, // set test timeout to 30 minutes. Also impacts AssertChannelClosed() - if (*IsVppDebug || *IsCoverage) && *Timeout == 5 { + if (*IsVppDebug || *IsCoverage || *PerfTesting) && *Timeout == 5 { TestTimeout = time.Minute * 30 fmt.Printf("[Debugging or coverage build, TestTimeout is set to %s]\n", TestTimeout.String()) } else { diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index 37556fe756..8be73a25c4 100644 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -114,6 +114,7 @@ case "${i}" in ;; --perf=*) perf="${i#*=}" + args="$args -perf" ;; --timeout=*) args="$args -timeout ${i#*=}" diff --git a/extras/hs-test/infra/common/suite_common.go b/extras/hs-test/infra/common/suite_common.go index c15b016805..97dd691767 100644 --- a/extras/hs-test/infra/common/suite_common.go +++ b/extras/hs-test/infra/common/suite_common.go @@ -23,6 +23,7 @@ var ParallelTotal = flag.Lookup("ginkgo.parallel.total") var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp") var DryRun = flag.Bool("dryrun", false, "set up containers but don't run tests") var Timeout = flag.Int("timeout", 5, "test timeout override (in minutes)") +var PerfTesting = flag.Bool("perf", false, "perf test flag") var NumaAwareCpuAlloc bool var TestTimeout time.Duration var RunningInCi bool @@ -175,8 +176,8 @@ func (s *HstCommon) AssertHttpBody(resp *http.Response, expectedBody string, msg // Coverage builds take longer to finish -> assert timeout is set to 'TestTimeout - 30 seconds' to let the test finish properly func (s *HstCommon) AssertChannelClosed(timeout time.Duration, channel chan error) { if *IsCoverage && timeout > time.Second*30 { - s.Log("Coverage build, assert timeout is set to %s", timeout.String()) timeout = TestTimeout - time.Second*30 + s.Log("Coverage build, assert timeout is set to %s", timeout.String()) } EventuallyWithOffset(2, channel).WithTimeout(timeout).Should(BeClosed()) } diff --git a/extras/hs-test/infra/kind/suite_kind.go b/extras/hs-test/infra/kind/suite_kind.go index ffbd830ac4..2095b7f4e2 100644 --- a/extras/hs-test/infra/kind/suite_kind.go +++ b/extras/hs-test/infra/kind/suite_kind.go @@ -1,8 +1,10 @@ package hst_kind import ( + "fmt" "os" "reflect" + "regexp" "runtime" "strings" "text/template" @@ -78,10 +80,38 @@ func (s *KindSuite) TeardownTest() { } func (s *KindSuite) TeardownSuite() { + s.HstCommon.TeardownSuite() s.Log(" %s", s.Namespace) s.AssertNil(s.deleteNamespace(s.Namespace)) } +// Quick and dirty fix for now. Runs 'ldd /usr/lib/libvcl_ldpreload.so' +// and searches for the first version string, then creates symlinks. +func (s *KindSuite) FixVersionNumber(pods ...*Pod) { + regex := regexp.MustCompile(`lib.*\.so\.([0-9]+\.[0-9]+)`) + o, _ := s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", + "ldd /usr/lib/libvcl_ldpreload.so"}) + match := regex.FindStringSubmatch(o) + + if len(match) > 1 { + version := match[1] + s.Log("Found version: %s", version) + cmd := fmt.Sprintf("for file in /usr/lib/*.so; do\n"+ + "if [ -e \"$file\" ]; then\n"+ + "base=$(basename \"$file\")\n"+ + "newlink=\"/usr/lib/${base}.%s\"\n"+ + "ln -s \"$file\" \"$newlink\"\n"+ + "fi\n"+ + "done", version) + for _, pod := range pods { + s.Exec(pod, []string{"/bin/bash", "-c", cmd}) + } + + } else { + s.Log("Couldn't find version.") + } +} + func (s *KindSuite) CreateConfigFromTemplate(targetConfigName string, templateName string, values any) { template := template.Must(template.ParseFiles(templateName)) @@ -101,8 +131,10 @@ func (s *KindSuite) CreateConfigFromTemplate(targetConfigName string, templateNa func (s *KindSuite) CreateNginxConfig() { values := struct { Workers uint8 + Port uint16 }{ Workers: 1, + Port: 8081, } s.CreateConfigFromTemplate( "/nginx.conf", diff --git a/extras/hs-test/kind_test.go b/extras/hs-test/kind_test.go index b1e2d23b6e..05841b6093 100644 --- a/extras/hs-test/kind_test.go +++ b/extras/hs-test/kind_test.go @@ -11,22 +11,13 @@ func init() { RegisterKindTests(KindIperfVclTest, NginxRpsTest) } +const vcl string = "VCL_CONFIG=/vcl.conf" +const ldp string = "LD_PRELOAD=/usr/lib/libvcl_ldpreload.so" + func KindIperfVclTest(s *KindSuite) { s.DeployPod(s.Pods.ClientGeneric) s.DeployPod(s.Pods.ServerGeneric) - vclPath := "/vcl.conf" - ldpPath := "/usr/lib/libvcl_ldpreload.so" - - // temporary workaround - symLink := "for file in /usr/lib/*.so; do\n" + - "if [ -e \"$file\" ]; then\n" + - "base=$(basename \"$file\")\n" + - "newlink=\"/usr/lib/${base}.25.06\"\n" + - "ln -s \"$file\" \"$newlink\"\n" + - "fi\n" + - "done" - vclConf := "echo \"vcl {\n" + "rx-fifo-size 4000000\n" + "tx-fifo-size 4000000\n" + @@ -35,19 +26,18 @@ func KindIperfVclTest(s *KindSuite) { "app-socket-api abstract:vpp/session\n" + "}\" > /vcl.conf" - s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", symLink}) - s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", symLink}) - _, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", vclConf}) s.AssertNil(err) _, err = s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", vclConf}) s.AssertNil(err) - _, err = s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", - "VCL_CONFIG=" + vclPath + " LD_PRELOAD=" + ldpPath + " iperf3 -s -D -4"}) - s.AssertNil(err) + s.FixVersionNumber(s.Pods.ClientGeneric, s.Pods.ServerGeneric) + + o, err := s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", + vcl + " " + ldp + " iperf3 -s -D -4"}) + s.AssertNil(err, o) output, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", - "VCL_CONFIG=" + vclPath + " LD_PRELOAD=" + ldpPath + " iperf3 -c " + s.Pods.ServerGeneric.IpAddress}) + vcl + " " + ldp + " iperf3 -c " + s.Pods.ServerGeneric.IpAddress}) s.Log(output) s.AssertNil(err) } @@ -56,17 +46,6 @@ func NginxRpsTest(s *KindSuite) { s.DeployPod(s.Pods.Nginx) s.DeployPod(s.Pods.Ab) s.CreateNginxConfig() - vcl := "VCL_CONFIG=/vcl.conf" - ldp := "LD_PRELOAD=/usr/lib/libvcl_ldpreload.so" - - // temporary workaround - symLink := "for file in /usr/lib/*.so; do\n" + - "if [ -e \"$file\" ]; then\n" + - "base=$(basename \"$file\")\n" + - "newlink=\"/usr/lib/${base}.25.06\"\n" + - "ln -s \"$file\" \"$newlink\"\n" + - "fi\n" + - "done" vclConf := "echo \"vcl {\n" + "heapsize 64M\n" + @@ -79,10 +58,7 @@ func NginxRpsTest(s *KindSuite) { "app-socket-api abstract:vpp/session\n" + "}\" > /vcl.conf" - out, err := s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", symLink}) - s.AssertNil(err, out) - - out, err = s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", vclConf}) + out, err := s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", vclConf}) s.AssertNil(err, out) go func() { @@ -93,7 +69,7 @@ func NginxRpsTest(s *KindSuite) { // wait for nginx to start up time.Sleep(time.Second * 2) - out, err = s.Exec(s.Pods.Ab, []string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":80/64B.json"}) + out, err = s.Exec(s.Pods.Ab, []string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":8081/64B.json"}) s.Log(out) s.AssertNil(err) } diff --git a/extras/hs-test/kubernetes/setupCluster.sh b/extras/hs-test/kubernetes/setupCluster.sh index 9cd92a1f6b..4cbc9914e4 100755 --- a/extras/hs-test/kubernetes/setupCluster.sh +++ b/extras/hs-test/kubernetes/setupCluster.sh @@ -1,18 +1,8 @@ #!/usr/bin/env bash set -e -if [ "$EUID" -eq 0 ]; then - echo "********" - echo "Do not run as root. Exiting." - echo "********" - exit 1 -fi - echo "********" echo "Performance tests only work on Ubuntu 22.04 for now." -echo "Do not run as root (untested) and make sure you have KinD and Kubectl installed!" -echo "https://kind.sigs.k8s.io/" -echo "https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/#install-using-native-package-management" echo "********" kind create cluster --config kubernetes/kind-config.yaml From d08b7de9e05f3e22f8c23d35347a3c77879c5407 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Wed, 4 Jun 2025 10:34:16 +0000 Subject: [PATCH 033/313] ipip: fix the offload flags Type: fix Packets with checksum offloading are not correctly handled in the IPIP tunnel path under the new checksum offload support. Only GSO packets were being processed correctly. Signed-off-by: Mohsin Kazmi Change-Id: Iaca00a68708bd11cb81951768d7437dc63523d8d --- src/vnet/adj/adj_dp.h | 4 ++-- src/vnet/ipip/ipip.c | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vnet/adj/adj_dp.h b/src/vnet/adj/adj_dp.h index 186044b90a..f35e905b44 100644 --- a/src/vnet/adj/adj_dp.h +++ b/src/vnet/adj/adj_dp.h @@ -37,7 +37,7 @@ adj_midchain_ipip44_fixup (vlib_main_t * vm, if (PREDICT_TRUE(TUNNEL_ENCAP_DECAP_FLAG_NONE == flags)) { - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip4 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_TNL_IPIP | @@ -57,7 +57,7 @@ adj_midchain_ipip44_fixup (vlib_main_t * vm, else { tunnel_encap_fixup_4o4 (flags, ip4 + 1, ip4); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip4 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_TNL_IPIP | diff --git a/src/vnet/ipip/ipip.c b/src/vnet/ipip/ipip.c index aaf21468d1..29ac9ddabd 100644 --- a/src/vnet/ipip/ipip.c +++ b/src/vnet/ipip/ipip.c @@ -148,7 +148,7 @@ ipip64_fixup (vlib_main_t * vm, const ip_adjacency_t * adj, vlib_buffer_t * b, ip4->length = clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b)); tunnel_encap_fixup_6o4 (flags, ((ip6_header_t *) (ip4 + 1)), ip4); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip4 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM | @@ -171,7 +171,7 @@ ipip44_fixup (vlib_main_t * vm, const ip_adjacency_t * adj, vlib_buffer_t * b, ip4->length = clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b)); tunnel_encap_fixup_4o4 (flags, ip4 + 1, ip4); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip4 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM | @@ -200,7 +200,7 @@ ipip46_fixup (vlib_main_t * vm, const ip_adjacency_t * adj, vlib_buffer_t * b, sizeof (*ip6)); tunnel_encap_fixup_4o6 (flags, b, ((ip4_header_t *) (ip6 + 1)), ip6); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip6 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_TNL_IPIP); @@ -226,7 +226,7 @@ ipip66_fixup (vlib_main_t * vm, sizeof (*ip6)); tunnel_encap_fixup_6o6 (flags, ip6 + 1, ip6); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip6 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_TNL_IPIP); @@ -253,7 +253,7 @@ ipipm6_fixup (vlib_main_t *vm, const ip_adjacency_t *adj, vlib_buffer_t *b, tunnel_encap_fixup_mplso6 (flags, b, (mpls_unicast_header_t *) (ip6 + 1), ip6); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip6 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_TNL_IPIP); @@ -278,7 +278,7 @@ ipipm4_fixup (vlib_main_t *vm, const ip_adjacency_t *adj, vlib_buffer_t *b, clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b) - sizeof (*ip4)); tunnel_encap_fixup_mplso4 (flags, (mpls_unicast_header_t *) (ip4 + 1), ip4); - if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) { vnet_buffer2 (b)->outer_l3_hdr_offset = (u8 *) ip4 - b->data; vnet_buffer_offload_flags_set (b, VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM | From cdfdbebe38dd27c72fd17af81d8203e5e27dff07 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Wed, 4 Jun 2025 11:14:05 +0000 Subject: [PATCH 034/313] af_packet: conditionally set checksum offload based on TCP/UDP offload flags Type: fix Previously, the af_packet device node unconditionally set the checksum offload flag and the checksum start offset, regardless of whether TCP/UDP checksum offload flags were set. This patch updates the logic to apply these settings only when the TCP/UDP offload flags are explicitly set. Signed-off-by: Mohsin Kazmi Change-Id: If0f98db5b654a22995e5c7a82b5f793aad83bb88 --- src/plugins/af_packet/device.c | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/plugins/af_packet/device.c b/src/plugins/af_packet/device.c index 797666a147..8f5ba1c568 100644 --- a/src/plugins/af_packet/device.c +++ b/src/plugins/af_packet/device.c @@ -333,11 +333,11 @@ fill_gso_offload (vlib_buffer_t *b0, vnet_virtio_net_hdr_t *vnet_hdr) if (b0->flags & VNET_BUFFER_F_IS_IP4) { ip4_header_t *ip4; + vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; vnet_hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; - vnet_hdr->gso_size = vnet_buffer2 (b0)->gso_size; vnet_hdr->hdr_len = l4_hdr_offset + vnet_buffer2 (b0)->gso_l4_hdr_sz; - vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - vnet_hdr->csum_start = l4_hdr_offset; // 0x22; + vnet_hdr->gso_size = vnet_buffer2 (b0)->gso_size; + vnet_hdr->csum_start = l4_hdr_offset; vnet_hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); ip4 = (ip4_header_t *) (b0->data + vnet_buffer (b0)->l3_hdr_offset); if (oflags & VNET_BUFFER_OFFLOAD_F_IP_CKSUM) @@ -349,11 +349,11 @@ fill_gso_offload (vlib_buffer_t *b0, vnet_virtio_net_hdr_t *vnet_hdr) else if (b0->flags & VNET_BUFFER_F_IS_IP6) { ip6_header_t *ip6; + vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; vnet_hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6; - vnet_hdr->gso_size = vnet_buffer2 (b0)->gso_size; vnet_hdr->hdr_len = l4_hdr_offset + vnet_buffer2 (b0)->gso_l4_hdr_sz; - vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - vnet_hdr->csum_start = l4_hdr_offset; // 0x36; + vnet_hdr->gso_size = vnet_buffer2 (b0)->gso_size; + vnet_hdr->csum_start = l4_hdr_offset; vnet_hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); ip6 = (ip6_header_t *) (b0->data + vnet_buffer (b0)->l3_hdr_offset); tcp_header_t *tcp = @@ -373,46 +373,50 @@ fill_cksum_offload (vlib_buffer_t *b0, vnet_virtio_net_hdr_t *vnet_hdr) ip4 = (ip4_header_t *) (b0->data + vnet_buffer (b0)->l3_hdr_offset); if (oflags & VNET_BUFFER_OFFLOAD_F_IP_CKSUM) ip4->checksum = ip4_header_checksum (ip4); - vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - vnet_hdr->csum_start = l4_hdr_offset; if (oflags & VNET_BUFFER_OFFLOAD_F_TCP_CKSUM) { tcp_header_t *tcp = (tcp_header_t *) (b0->data + vnet_buffer (b0)->l4_hdr_offset); tcp->checksum = ip4_pseudo_header_cksum (ip4); - vnet_hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); + vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; vnet_hdr->hdr_len = l4_hdr_offset + tcp_header_bytes (tcp); + vnet_hdr->csum_start = l4_hdr_offset; + vnet_hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); } else if (oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM) { udp_header_t *udp = (udp_header_t *) (b0->data + vnet_buffer (b0)->l4_hdr_offset); udp->checksum = ip4_pseudo_header_cksum (ip4); - vnet_hdr->csum_offset = STRUCT_OFFSET_OF (udp_header_t, checksum); + vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; vnet_hdr->hdr_len = l4_hdr_offset + sizeof (udp_header_t); + vnet_hdr->csum_start = l4_hdr_offset; + vnet_hdr->csum_offset = STRUCT_OFFSET_OF (udp_header_t, checksum); } } else if (b0->flags & VNET_BUFFER_F_IS_IP6) { ip6_header_t *ip6; - vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - vnet_hdr->csum_start = l4_hdr_offset; ip6 = (ip6_header_t *) (b0->data + vnet_buffer (b0)->l3_hdr_offset); if (oflags & VNET_BUFFER_OFFLOAD_F_TCP_CKSUM) { tcp_header_t *tcp = (tcp_header_t *) (b0->data + vnet_buffer (b0)->l4_hdr_offset); tcp->checksum = ip6_pseudo_header_cksum (ip6); - vnet_hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); + vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; vnet_hdr->hdr_len = l4_hdr_offset + tcp_header_bytes (tcp); + vnet_hdr->csum_start = l4_hdr_offset; + vnet_hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); } else if (oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM) { udp_header_t *udp = (udp_header_t *) (b0->data + vnet_buffer (b0)->l4_hdr_offset); udp->checksum = ip6_pseudo_header_cksum (ip6); - vnet_hdr->csum_offset = STRUCT_OFFSET_OF (udp_header_t, checksum); + vnet_hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; vnet_hdr->hdr_len = l4_hdr_offset + sizeof (udp_header_t); + vnet_hdr->csum_start = l4_hdr_offset; + vnet_hdr->csum_offset = STRUCT_OFFSET_OF (udp_header_t, checksum); } } } From cfec155f97dfc4745ccf9d618b788dafe5e17460 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 22 May 2025 17:45:08 +0000 Subject: [PATCH 035/313] http: http/2 CONTINUATION frame support We can now receive and send headers split into multiple frames. Type: improvement Change-Id: I3a2a21c67dbc7bbd0bc19b8c6a0ff1516bcfc6fd Signed-off-by: Matus Fabian --- extras/hs-test/http2_test.go | 16 +- extras/hs-test/http_test.go | 6 +- extras/hs-test/infra/suite_h2.go | 34 ++-- src/plugins/hs_apps/http_tps.c | 36 +++- src/plugins/http/http.c | 3 +- src/plugins/http/http2/frame.c | 22 ++- src/plugins/http/http2/frame.h | 18 +- src/plugins/http/http2/http2.c | 274 +++++++++++++++++++++++++------ 8 files changed, 326 insertions(+), 83 deletions(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index dc2d210000..0d170eb121 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "strconv" "strings" "time" @@ -9,7 +10,7 @@ import ( ) func init() { - RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest) + RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest) RegisterH2SoloTests(Http2MultiplexingMTTest) } @@ -106,3 +107,16 @@ func Http2TlsTest(s *H2Suite) { s.AssertContains(log, "ALPN: server accepted h2") s.AssertContains(writeOut, "version") } + +func Http2ContinuationTxTest(s *H2Suite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") + args := fmt.Sprintf("-w %%{size_header} --max-time 10 --noproxy '*' --http2-prior-knowledge http://%s/test_file_64?test_header=32k", serverAddress) + writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) + s.AssertContains(log, "HTTP/2 200") + s.AssertContains(log, "[64 bytes data]") + sizeHeader, err := strconv.Atoi(strings.ReplaceAll(writeOut, "\x00", "")) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertGreaterThan(sizeHeader, 32768) +} diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 953898d509..19f54d1700 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -1192,11 +1192,11 @@ func HttpCliBadRequestTest(s *NoTopoSuite) { func HttpStaticHttp1OnlyTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/80 url-handlers http1-only debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers http1-only debug")) client := NewHttpClient(defaultHttpTimeout, true) - req, err := http.NewRequest("GET", "https://"+serverAddress+":80/version.json", nil) + req, err := http.NewRequest("GET", "https://"+serverAddress+"/version.json", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) diff --git a/extras/hs-test/infra/suite_h2.go b/extras/hs-test/infra/suite_h2.go index 36fc29493c..b00708645d 100644 --- a/extras/hs-test/infra/suite_h2.go +++ b/extras/hs-test/infra/suite_h2.go @@ -88,7 +88,6 @@ func (s *H2Suite) VppAddr() string { return s.Interfaces.Tap.Peer.Ip4AddressString() } -// Marked as pending since http plugin is not build with http/2 enabled by default var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { var s H2Suite BeforeAll(func() { @@ -118,7 +117,6 @@ var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { } }) -// Marked as pending since http plugin is not build with http/2 enabled by default var _ = Describe("Http2SoloSuite", Ordered, ContinueOnFailure, Serial, func() { var s H2Suite BeforeAll(func() { @@ -230,26 +228,21 @@ var http2Tests = []h2specTest{ {desc: "http2/5.1/1"}, {desc: "http2/5.1/2"}, {desc: "http2/5.1/3"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/4"}, + {desc: "http2/5.1/4"}, {desc: "http2/5.1/5"}, {desc: "http2/5.1/6"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/7"}, + {desc: "http2/5.1/7"}, {desc: "http2/5.1/8"}, {desc: "http2/5.1/9"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/10"}, + {desc: "http2/5.1/10"}, {desc: "http2/5.1/11"}, {desc: "http2/5.1/12"}, - // TODO: CONTINUATION - // {desc: "http2/5.1/13"}, + {desc: "http2/5.1/13"}, // http2/5.3.1/* PRIORITY is deprecated {desc: "http2/5.4.1/1"}, {desc: "http2/5.4.1/2"}, {desc: "http2/5.5/1"}, - // TODO: CONTINUATION - // {desc: "http2/5.5/2"}, + {desc: "http2/5.5/2"}, {desc: "http2/6.1/1"}, {desc: "http2/6.1/2"}, {desc: "http2/6.1/3"}, @@ -287,13 +280,12 @@ var http2Tests = []h2specTest{ // TODO: message framing without content length using END_STREAM flag // {desc: "http2/6.9/2"}, {desc: "http2/6.9/3"}, - // TODO: CONTINUATION - // {desc: "http2/6.10/1"}, - // {desc: "http2/6.10/2"}, - // {desc: "http2/6.10/3"}, - // {desc: "http2/6.10/4"}, - // {desc: "http2/6.10/5"}, - // {desc: "http2/6.10/6"}, + {desc: "http2/6.10/1"}, + {desc: "http2/6.10/2"}, + {desc: "http2/6.10/3"}, + {desc: "http2/6.10/4"}, + {desc: "http2/6.10/5"}, + {desc: "http2/6.10/6"}, {desc: "http2/7/1"}, // TODO: message framing without content length using END_STREAM flag // {desc: "http2/7/2"}, @@ -363,14 +355,14 @@ var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { s.Log(testName + ": BEGIN") vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/" + s.Ports.Port1 + " url-handlers debug 2")) + s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + "/" + s.Ports.Port1 + " url-handlers debug 2 fifo-size 16k")) s.Log(vpp.Vppctl("test-url-handler enable")) conf := &config.Config{ Host: serverAddress, Port: s.Ports.Port1AsInt, Path: "/test1", Timeout: time.Second * 5, - MaxHeaderLen: 1024, + MaxHeaderLen: 4096, TLS: true, Insecure: true, Sections: []string{test.desc}, diff --git a/src/plugins/hs_apps/http_tps.c b/src/plugins/hs_apps/http_tps.c index 486d4a525e..617ced57ac 100644 --- a/src/plugins/hs_apps/http_tps.c +++ b/src/plugins/hs_apps/http_tps.c @@ -73,6 +73,7 @@ typedef struct hs_main_ u8 no_zc; u8 *default_uri; u32 seed; + u8 *test_header_value; } hts_main_t; static hts_main_t hts_main; @@ -370,8 +371,10 @@ hts_ts_rx_callback (session_t *ts) { hts_main_t *htm = &hts_main; hts_session_t *hs; - u8 *target = 0; + u8 *target = 0, *query = 0; http_msg_t msg; + unformat_input_t input; + u64 test_header_len; int rv; hs = hts_session_get (ts->thread_index, ts->opaque); @@ -415,6 +418,35 @@ hts_ts_rx_callback (session_t *ts) msg.method_type == HTTP_REQ_GET ? "GET" : "POST", target); + if (msg.data.target_query_len != 0) + { + vec_validate (query, msg.data.target_query_len - 1); + rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_query_offset, + msg.data.target_query_len, query); + ASSERT (rv == msg.data.target_query_len); + if (htm->debug_level) + clib_warning ("query: %v", query); + unformat_init_vector (&input, query); + if (unformat (&input, "test_header=%U", unformat_memory_size, + &test_header_len)) + { + if (test_header_len > vec_len (htm->test_header_value)) + { + test_header_len = vec_len (htm->test_header_value); + clib_warning ("test_header_len too big, truncated to %U", + format_memory_size, test_header_len); + } + vec_resize (hs->resp_headers_buf, + sizeof (http_app_header_t) + test_header_len); + hs->resp_headers.len = vec_len (hs->resp_headers_buf); + hs->resp_headers.buf = hs->resp_headers_buf; + http_add_custom_header ( + &hs->resp_headers, http_token_lit ("x-test"), + (const char *) htm->test_header_value, test_header_len); + } + vec_free (query); + } + if (msg.method_type == HTTP_REQ_GET) { if (try_test_file (hs, target)) @@ -766,6 +798,8 @@ hts_create (vlib_main_t *vm) if (htm->no_zc) vec_validate (htm->test_data, (64 << 10) - 1); + vec_validate_init_empty (htm->test_header_value, htm->fifo_size - 1024, 'x'); + if (hts_attach (htm)) { clib_warning ("failed to attach server"); diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 2c923c69ee..951bf3ad96 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -317,6 +317,7 @@ http_get_app_header_list (http_req_t *req, http_msg_t *msg) else { app_headers = hm->app_header_lists[as->thread_index]; + vec_validate (app_headers, msg->data.headers_len - 1); rv = svm_fifo_dequeue (as->tx_fifo, msg->data.headers_len, app_headers); ASSERT (rv == msg->data.headers_len); } @@ -837,7 +838,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) vec_validate (hm->tx_bufs[i], HTTP_UDP_PAYLOAD_MAX_LEN + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD); - vec_validate (hm->app_header_lists[i], 32 << 10); + vec_validate (hm->app_header_lists[i], 64 << 10); } clib_timebase_init (&hm->timebase, 0 /* GMT */, CLIB_TIMEBASE_DAYLIGHT_NONE, diff --git a/src/plugins/http/http2/frame.c b/src/plugins/http/http2/frame.c index 580ffff22c..07821de3be 100644 --- a/src/plugins/http/http2/frame.c +++ b/src/plugins/http/http2/frame.c @@ -204,15 +204,13 @@ http2_frame_write_rst_stream (http2_error_t error_code, u32 stream_id, clib_memcpy_fast (p, &value, RST_STREAM_LENGTH); } -#define GOAWAY_MIN_SIZE 8 - __clib_export http2_error_t http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload, u32 payload_len) { u32 *value; - if (payload_len < GOAWAY_MIN_SIZE) + if (payload_len < HTTP2_GOAWAY_MIN_SIZE) return HTTP2_ERROR_FRAME_SIZE_ERROR; value = (u32 *) payload; @@ -222,7 +220,6 @@ http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload, value = (u32 *) payload; *error_code = clib_net_to_host_u32 (*value); - /* TODO: Additional Debug Data */ return HTTP2_ERROR_NO_ERROR; } @@ -236,11 +233,11 @@ http2_frame_write_goaway (http2_error_t error_code, u32 last_stream_id, ASSERT (last_stream_id <= 0x7FFFFFFF); http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_GOAWAY, - .length = GOAWAY_MIN_SIZE }; + .length = HTTP2_GOAWAY_MIN_SIZE }; p = http2_frame_header_alloc (dst); http2_frame_header_write (&fh, p); - vec_add2 (*dst, p, GOAWAY_MIN_SIZE); + vec_add2 (*dst, p, HTTP2_GOAWAY_MIN_SIZE); value = clib_host_to_net_u32 (last_stream_id); clib_memcpy_fast (p, &value, 4); p += 4; @@ -308,6 +305,19 @@ http2_frame_write_headers_header (u32 headers_len, u32 stream_id, u8 flags, http2_frame_header_write (&fh, dst); } +void +http2_frame_write_continuation_header (u32 headers_len, u32 stream_id, + u8 flags, u8 *dst) +{ + ASSERT (stream_id > 0 && stream_id <= 0x7FFFFFFF); + + http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_CONTINUATION, + .length = headers_len, + .flags = flags, + .stream_id = stream_id }; + http2_frame_header_write (&fh, dst); +} + __clib_export http2_error_t http2_frame_read_data (u8 **data, u32 *data_len, u8 *payload, u32 payload_len, u8 flags) diff --git a/src/plugins/http/http2/frame.h b/src/plugins/http/http2/frame.h index 53a37c1aa0..e19cfa7a69 100644 --- a/src/plugins/http/http2/frame.h +++ b/src/plugins/http/http2/frame.h @@ -11,6 +11,7 @@ #define HTTP2_FRAME_HEADER_SIZE 9 #define HTTP2_PING_PAYLOAD_LEN 8 +#define HTTP2_GOAWAY_MIN_SIZE 8 #define foreach_http2_frame_type \ _ (0x00, DATA, "DATA") \ @@ -161,14 +162,14 @@ void http2_frame_write_rst_stream (http2_error_t error_code, u32 stream_id, /** * Parse GOAWAY frame payload * - * @param last_stream_id Parsed last stream ID * @param error_code Parsed error code + * @param last_stream_id Parsed last stream ID * @param payload Payload to parse * @param payload_len Payload length * * @return @c HTTP2_ERROR_NO_ERROR on success, error otherwise */ -http2_error_t http2_frame_read_goaway (u32 *last_stream_id, u32 *error_code, +http2_error_t http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload, u32 payload_len); /** @@ -218,6 +219,19 @@ http2_error_t http2_frame_read_headers (u8 **headers, u32 *headers_len, void http2_frame_write_headers_header (u32 headers_len, u32 stream_id, u8 flags, u8 *dst); +/** + * Write CONTINUATION frame header + * + * @param headers_len Header block fragment length + * @param stream_id Stream ID, except 0 + * @param flags Frame header flags + * @param dst Pointer where frame header will be written + * + * @note Use @c http2_frame_header_alloc before + */ +void http2_frame_write_continuation_header (u32 headers_len, u32 stream_id, + u8 flags, u8 *dst); + /** * Parse DATA frame payload * diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 38a3cca4f8..8d47083554 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -56,10 +56,13 @@ typedef struct http2_req_ u8 *payload; u32 payload_len; clib_llist_anchor_t sched_list; + void (*dispatch_headers_cb) (struct http2_req_ *req, http_conn_t *hc, + u8 *n_emissions, clib_llist_index_t *next_ri); } http2_req_t; #define foreach_http2_conn_flags \ _ (EXPECT_PREFACE, "expect-preface") \ + _ (EXPECT_CONTINUATION, "expect-continuation") \ _ (PREFACE_VERIFIED, "preface-verified") \ _ (TS_DESCHED, "ts-descheduled") @@ -92,6 +95,9 @@ typedef struct http2_conn_ctx_ clib_llist_index_t old_tx_streams; /* data */ http2_conn_settings_t settings; clib_llist_anchor_t sched_list; + u8 *unparsed_headers; /* temporary storing rx fragmented headers */ + u8 *unsent_headers; /* temporary storing tx fragmented headers */ + u32 unsent_headers_offset; } http2_conn_ctx_t; typedef struct http2_worker_ctx_ @@ -99,6 +105,7 @@ typedef struct http2_worker_ctx_ http2_conn_ctx_t *conn_pool; http2_req_t *req_pool; clib_llist_index_t sched_head; + u8 *header_list; /* buffer for headers decompression */ } http2_worker_ctx_t; typedef struct http2_main_ @@ -111,6 +118,7 @@ typedef struct http2_main_ typedef enum { HTTP2_SCHED_WEIGHT_DATA_PTR = 1, + HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION = 1, HTTP2_SCHED_WEIGHT_DATA_INLINE = 2, HTTP2_SCHED_WEIGHT_HEADERS_PTR = 3, HTTP2_SCHED_WEIGHT_HEADERS_INLINE = 4, @@ -449,17 +457,77 @@ http2_send_server_preface (http_conn_t *hc) /* stream TX scheduler */ /***********************/ +static void +http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, + u8 *n_emissions, + clib_llist_index_t *next_ri) +{ + u8 fh[HTTP2_FRAME_HEADER_SIZE]; + u8 flags = 0; + u32 n_written, stream_id, max_write, headers_len, headers_left; + http2_conn_ctx_t *h2c; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); + + *n_emissions += HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION; + + h2c = http2_conn_ctx_get_w_thread (hc); + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); + + stream_id = req->stream_id; + + ASSERT (vec_len (h2c->unsent_headers) > h2c->unsent_headers_offset); + headers_left = vec_len (h2c->unsent_headers) - h2c->unsent_headers_offset; + headers_len = clib_min (max_write, headers_left); + flags |= (headers_len == headers_left) ? HTTP2_FRAME_FLAG_END_HEADERS : 0; + http2_frame_write_continuation_header (headers_len, stream_id, flags, fh); + svm_fifo_seg_t segs[2] = { + { fh, HTTP2_FRAME_HEADER_SIZE }, + { h2c->unsent_headers + h2c->unsent_headers_offset, headers_len } + }; + n_written = http_io_ts_write_segs (hc, segs, 2, 0); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len)); + http_io_ts_after_write (hc, 0); + + if (headers_len == headers_left) + { + HTTP_DBG (1, "sent last headers fragment"); + vec_free (h2c->unsent_headers); + *next_ri = clib_llist_next_index (req, sched_list); + clib_llist_remove (wrk->req_pool, sched_list, req); + flags |= HTTP2_FRAME_FLAG_END_HEADERS; + if (http_buffer_bytes_left (&req->base.tx_buf)) + { + /* start sending the actual data */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + http2_stream_close (req, hc); + } + else + { + HTTP_DBG (1, "need another headers fragment"); + *next_ri = clib_llist_entry_index (wrk->req_pool, req); + h2c->unsent_headers_offset += headers_len; + } +} + static void http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, - u8 *n_emissions) + u8 *n_emissions, clib_llist_index_t *next_ri) { http_msg_t msg; u8 *response, *date, *app_headers = 0; u8 fh[HTTP2_FRAME_HEADER_SIZE]; hpack_response_control_data_t control_data; - u8 flags = HTTP2_FRAME_FLAG_END_HEADERS; - u32 n_written, stream_id, n_deq; + u8 flags = 0; + u32 n_written, stream_id, n_deq, max_write, headers_len, headers_left; http2_conn_ctx_t *h2c; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); http_get_app_msg (&req->base, &msg); ASSERT (msg.type == HTTP_MSG_REPLY); @@ -488,38 +556,62 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, hpack_serialize_response (app_headers, msg.data.headers_len, &control_data, &response); vec_free (date); + headers_len = vec_len (response); h2c = http2_conn_ctx_get_w_thread (hc); - if (vec_len (response) > h2c->peer_settings.max_frame_size) - { - /* TODO: CONTINUATION (headers fragmentation) */ - clib_warning ("resp headers greater than SETTINGS_MAX_FRAME_SIZE"); - http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, 0); - return; - } + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); stream_id = req->stream_id; + + /* END_STREAM flag need to be set in HEADERS frame */ if (msg.data.body_len) { - /* start sending the actual data */ http_req_tx_buffer_init (&req->base, &msg); - HTTP_DBG (1, "adding to data queue req_index %x", - ((http_req_handle_t) req->base.hr_req_handle).req_index); - http2_req_schedule_data_tx (hc, req); http_io_as_dequeue_notify (&req->base, n_deq); } else + flags |= HTTP2_FRAME_FLAG_END_STREAM; + + if (headers_len <= max_write) { - /* no response body, we are done */ - flags |= HTTP2_FRAME_FLAG_END_STREAM; - http2_stream_close (req, hc); + *next_ri = clib_llist_next_index (req, sched_list); + clib_llist_remove (wrk->req_pool, sched_list, req); + flags |= HTTP2_FRAME_FLAG_END_HEADERS; + if (msg.data.body_len) + { + /* start sending the actual data */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + http2_stream_close (req, hc); } - - http2_frame_write_headers_header (vec_len (response), stream_id, flags, fh); + else + { + /* we need to send CONTINUATION frame as next */ + HTTP_DBG (1, "response headers need to be fragmented"); + *next_ri = clib_llist_entry_index (wrk->req_pool, req); + headers_len = max_write; + headers_left = vec_len (response) - headers_len; + req->dispatch_headers_cb = http2_sched_dispatch_continuation; + /* move unsend portion of headers to connection ctx */ + ASSERT (h2c->unsent_headers == 0); + vec_validate (h2c->unsent_headers, headers_left - 1); + clib_memcpy_fast (h2c->unsent_headers, response + headers_len, + headers_left); + h2c->unsent_headers_offset = 0; + *n_emissions += HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION; + } + + http2_frame_write_headers_header (headers_len, stream_id, flags, fh); svm_fifo_seg_t segs[2] = { { fh, HTTP2_FRAME_HEADER_SIZE }, - { response, vec_len (response) } }; + { response, headers_len } }; n_written = http_io_ts_write_segs (hc, segs, 2, 0); - ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + vec_len (response))); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len)); http_io_ts_after_write (hc, 0); } @@ -641,11 +733,9 @@ http2_update_time_callback (f64 now, u8 thread_index) n_emissions < HTTP2_SCHED_MAX_EMISSIONS) { req = clib_llist_elt (wrk->req_pool, ri); - ri = clib_llist_next_index (req, sched_list); HTTP_DBG (1, "sending headers req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); - clib_llist_remove (wrk->req_pool, sched_list, req); - http2_sched_dispatch_headers (req, hc, &n_emissions); + req->dispatch_headers_cb (req, hc, &n_emissions, &ri); } /* handle old responses (data frames), if we had any prior to processing @@ -699,24 +789,26 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, { http2_conn_ctx_t *h2c; hpack_request_control_data_t control_data; - u8 *buf = 0; http_msg_t msg; int rv; http_req_state_t new_state = HTTP_REQ_STATE_WAIT_APP_REPLY; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); h2c = http2_conn_ctx_get_w_thread (hc); - /* TODO: configurable buf size with bigger default value */ - vec_validate_init_empty (buf, 1023, 0); - *error = hpack_parse_request (req->payload, req->payload_len, buf, 1023, - &control_data, &req->base.headers, - &h2c->decoder_dynamic_table); + *error = + hpack_parse_request (req->payload, req->payload_len, wrk->header_list, + vec_len (wrk->header_list), &control_data, + &req->base.headers, &h2c->decoder_dynamic_table); if (*error != HTTP2_ERROR_NO_ERROR) { HTTP_DBG (1, "hpack_parse_request failed"); return HTTP_SM_ERROR; } + HTTP_DBG (1, "decompressed headers size %u", control_data.headers_len); + HTTP_DBG (1, "dynamic table size %u", h2c->decoder_dynamic_table.used); + if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_METHOD_PARSED)) { HTTP_DBG (1, ":method pseudo-header missing in request"); @@ -759,13 +851,13 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, } req->base.control_data_len = control_data.control_data_len; - req->base.headers_offset = control_data.headers - buf; + req->base.headers_offset = control_data.headers - wrk->header_list; req->base.headers_len = control_data.headers_len; if (control_data.content_len_header_index != ~0) { req->base.content_len_header_index = control_data.content_len_header_index; - rv = http_parse_content_length (&req->base, buf); + rv = http_parse_content_length (&req->base, wrk->header_list); if (rv) { http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); @@ -784,20 +876,20 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, req->base.to_recv = req->base.body_len; req->base.target_path_len = control_data.path_len; - req->base.target_path_offset = control_data.path - buf; + req->base.target_path_offset = control_data.path - wrk->header_list; /* drop leading slash */ req->base.target_path_offset++; req->base.target_path_len--; req->base.target_query_offset = 0; req->base.target_query_len = 0; - http_identify_optional_query (&req->base, buf); + http_identify_optional_query (&req->base, wrk->header_list); msg.type = HTTP_MSG_REQUEST; msg.method_type = control_data.method; msg.data.type = HTTP_MSG_DATA_INLINE; msg.data.len = req->base.connection_header_index; msg.data.scheme = control_data.scheme; - msg.data.target_authority_offset = control_data.authority - buf; + msg.data.target_authority_offset = control_data.authority - wrk->header_list; msg.data.target_authority_len = control_data.authority_len; msg.data.target_path_offset = req->base.target_path_offset; msg.data.target_path_len = req->base.target_path_len; @@ -811,8 +903,10 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, msg.data.body_len = req->base.body_len; svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, - { buf, req->base.control_data_len } }; - HTTP_DBG (3, "%U", format_http_bytes, buf, req->base.control_data_len); + { wrk->header_list, + req->base.control_data_len } }; + HTTP_DBG (3, "%U", format_http_bytes, wrk->header_list, + req->base.control_data_len); http_io_as_write_segs (&req->base, segs, 2); http_req_state_change (&req->base, new_state); http_app_worker_rx_notify (&req->base); @@ -982,16 +1076,12 @@ static http2_error_t http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) { http2_req_t *req; - u8 *rx_buf; + u8 *rx_buf, *headers_start; + u32 headers_len; + uword n_del, n_dec; http2_error_t rv; http2_conn_ctx_t *h2c; - if (!(fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)) - { - /* TODO: fragmented headers */ - return HTTP2_ERROR_INTERNAL_ERROR; - } - if (hc->flags & HTTP_CONN_F_IS_SERVER) { h2c = http2_conn_ctx_get_w_thread (hc); @@ -1018,6 +1108,7 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) return HTTP2_ERROR_NO_ERROR; } req = http2_conn_alloc_req (hc, fh->stream_id); + req->dispatch_headers_cb = http2_sched_dispatch_headers; http_conn_accept_request (hc, &req->base); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); req->stream_state = HTTP2_STREAM_STATE_OPEN; @@ -1031,6 +1122,31 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) } if (fh->flags & HTTP2_FRAME_FLAG_END_STREAM) req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + + if (!(fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)) + { + HTTP_DBG (1, "fragmented headers stream id %u", fh->stream_id); + h2c->flags |= HTTP2_CONN_F_EXPECT_CONTINUATION; + vec_validate (h2c->unparsed_headers, fh->length - 1); + http_io_ts_read (hc, h2c->unparsed_headers, fh->length, 0); + rv = http2_frame_read_headers (&headers_start, &headers_len, + h2c->unparsed_headers, fh->length, + fh->flags); + if (rv != HTTP2_ERROR_NO_ERROR) + return rv; + + /* in case frame has padding */ + if (PREDICT_FALSE (headers_start != h2c->unparsed_headers)) + { + n_dec = fh->length - headers_len; + n_del = headers_start - h2c->unparsed_headers; + n_dec -= n_del; + vec_delete (h2c->unparsed_headers, n_del, 0); + vec_dec_len (h2c->unparsed_headers, n_dec); + } + + return HTTP2_ERROR_NO_ERROR; + } } else { @@ -1051,6 +1167,53 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) return http2_req_run_state_machine (hc, req, 0, 0); } +static http2_error_t +http2_handle_continuation_frame (http_conn_t *hc, http2_frame_header_t *fh) +{ + http2_req_t *req; + http2_conn_ctx_t *h2c; + u8 *p; + http2_error_t rv = HTTP2_ERROR_NO_ERROR; + + if (hc->flags & HTTP_CONN_F_IS_SERVER) + { + h2c = http2_conn_ctx_get_w_thread (hc); + + if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION)) + { + HTTP_DBG (1, "unexpected CONTINUATION frame"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + if (fh->stream_id != h2c->last_opened_stream_id) + { + HTTP_DBG (1, "invalid stream id %u", fh->stream_id); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + vec_add2 (h2c->unparsed_headers, p, fh->length); + http_io_ts_read (hc, p, fh->length, 0); + + if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS) + { + req = http2_conn_get_req (hc, fh->stream_id); + h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION; + req->payload = h2c->unparsed_headers; + req->payload_len = vec_len (h2c->unparsed_headers); + HTTP_DBG (1, "run state machine"); + rv = http2_req_run_state_machine (hc, req, 0, 0); + vec_free (h2c->unparsed_headers); + } + } + else + { + /* TODO: client */ + return HTTP2_ERROR_INTERNAL_ERROR; + } + + return rv; +} + static http2_error_t http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) { @@ -1325,7 +1488,8 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) if (rv != HTTP2_ERROR_NO_ERROR) return rv; - HTTP_DBG (1, "received GOAWAY %U", format_http2_error, error_code); + HTTP_DBG (1, "received GOAWAY %U, last stream id %u", format_http2_error, + error_code, last_stream_id); if (error_code == HTTP2_ERROR_NO_ERROR) { @@ -1333,6 +1497,10 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) } else { + if (fh->length > HTTP2_GOAWAY_MIN_SIZE) + clib_warning ("additional debug data: %U", format_http_bytes, + rx_buf + HTTP2_GOAWAY_MIN_SIZE, + fh->length - HTTP2_GOAWAY_MIN_SIZE); /* connection error */ h2c = http2_conn_ctx_get_w_thread (hc); hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ @@ -1669,7 +1837,16 @@ http2_transport_rx_callback (http_conn_t *hc) http_io_ts_drain (hc, HTTP2_FRAME_HEADER_SIZE); to_deq -= fh.length; - HTTP_DBG (1, "frame type 0x%02x", fh.type); + HTTP_DBG (1, "frame type 0x%02x len %u", fh.type, fh.length); + + if ((h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION) && + fh.type != HTTP2_FRAME_TYPE_CONTINUATION) + { + HTTP_DBG (1, "expected CONTINUATION frame"); + http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); + return; + } + switch (fh.type) { case HTTP2_FRAME_TYPE_HEADERS: @@ -1694,8 +1871,7 @@ http2_transport_rx_callback (http_conn_t *hc) rv = http2_handle_ping_frame (hc, &fh); break; case HTTP2_FRAME_TYPE_CONTINUATION: - /* TODO */ - rv = HTTP2_ERROR_INTERNAL_ERROR; + rv = http2_handle_continuation_frame (hc, &fh); break; case HTTP2_FRAME_TYPE_PUSH_PROMISE: rv = http2_handle_push_promise (hc, &fh); @@ -1867,6 +2043,7 @@ http2_enable_callback (void) { wrk = &h2m->wrk_ctx[i]; wrk->sched_head = clib_llist_make_head (wrk->conn_pool, sched_list); + vec_validate (wrk->header_list, h2m->settings.max_header_list_size - 1); } } @@ -1957,6 +2134,7 @@ http2_init (vlib_main_t *vm) clib_warning ("http/2 enabled"); h2m->settings = http2_default_conn_settings; h2m->settings.max_concurrent_streams = 100; /* by default unlimited */ + h2m->settings.max_header_list_size = 1 << 14; /* by default unlimited */ http_register_engine (&http2_engine, HTTP_VERSION_2); return 0; From 90d92196211ca3c489da200619585b09a49d1f76 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 6 Jun 2025 13:46:34 -0700 Subject: [PATCH 036/313] http: mark req/streams as no lookup sessions Type: fix Change-Id: If5572fa4a9ac1a5d61867228cbd2a7e6ef3546d3 Signed-off-by: Florin Coras --- src/plugins/http/http2/http2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 8d47083554..e8d6dff88d 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -219,6 +219,7 @@ http2_conn_alloc_req (http_conn_t *hc, u32 stream_id) req->base.hr_req_handle = hr_handle.as_u32; req->base.hr_hc_index = hc->hc_hc_index; req->base.c_thread_index = hc->c_thread_index; + req->base.c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; req->stream_id = stream_id; req->stream_state = HTTP2_STREAM_STATE_IDLE; req->sched_list.next = CLIB_LLIST_INVALID_INDEX; From cbc9647feb5366a3af287eff0db7ab87ccd48f59 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 6 Jun 2025 18:09:10 -0400 Subject: [PATCH 037/313] http: http2_conn_cleanup_callback fix Type: fix Change-Id: Id31705fee59d5202ea6924bbd707310d46a0bff8 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index e8d6dff88d..ac0e567bb9 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2019,8 +2019,7 @@ http2_conn_cleanup_callback (http_conn_t *hc) vec_foreach (req_index_p, req_indices) { req = http2_req_get (*req_index_p, hc->c_thread_index); - if (req->stream_state != HTTP2_STREAM_STATE_CLOSED) - session_transport_delete_notify (&req->base.connection); + session_transport_delete_notify (&req->base.connection); http2_conn_free_req (h2c, req, hc->c_thread_index); } From 12771c35b4bea2ab37ac2e20659c07918b7f574f Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 30 Sep 2023 12:56:13 -0700 Subject: [PATCH 038/313] vcl: allow reads after transport cleanup Type: improvement Signed-off-by: Florin Coras Change-Id: Ia464aae418999cc09bc38fe9fadd3b164f4e0067 --- src/vcl/vppcom.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 35af7f7304..0671a37ea2 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -2119,7 +2119,9 @@ vppcom_session_read_internal (uint32_t session_handle, void *buf, int n, VDBG (0, "session %u[0x%llx] is not open! state 0x%x (%s)", s->session_index, s->vpp_handle, s->session_state, vcl_session_state_str (s->session_state)); - return vcl_session_closed_error (s); + rx_fifo = vcl_session_is_ct (s) ? s->ct_rx_fifo : s->rx_fifo; + if (svm_fifo_is_empty_cons (rx_fifo)) + return vcl_session_closed_error (s); } if (PREDICT_FALSE (s->flags & VCL_SESSION_F_RD_SHUTDOWN)) From a1e0651bef1e77771d146b98e400dfd92b4fbcad Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Sun, 8 Jun 2025 10:37:01 -0400 Subject: [PATCH 039/313] http: h2 fix msg.data.len passed to server app Type: fix Change-Id: Iac4e5edf42280a3d6679e12e0dd338adfe16aaf3 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index ac0e567bb9..36a27f40de 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -888,7 +888,7 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, msg.type = HTTP_MSG_REQUEST; msg.method_type = control_data.method; msg.data.type = HTTP_MSG_DATA_INLINE; - msg.data.len = req->base.connection_header_index; + msg.data.len = req->base.control_data_len; msg.data.scheme = control_data.scheme; msg.data.target_authority_offset = control_data.authority - wrk->header_list; msg.data.target_authority_len = control_data.authority_len; From 409b66bd16218f118d02bfdaaf77ae11b2c43a95 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 2 Jun 2025 00:12:36 -0400 Subject: [PATCH 040/313] vcl: inject epoll functions into vcl from ldp Instead of trying to detect in ldp if vcl is asking for real epoll, have vcl use libc epoll directly. This is obtained either at startup or by means of ldp/vls. Type: improvement Change-Id: Ia77d49aef33be7618aeeb1a1d6a618d7ef4bcc6c Signed-off-by: Florin Coras --- src/vcl/ldp.c | 30 ++++-------------------------- src/vcl/vcl_locked.c | 17 ++++++++--------- src/vcl/vcl_locked.h | 9 ++++++++- src/vcl/vcl_private.c | 29 +++++++++++++++++++---------- src/vcl/vcl_private.h | 13 +++++++++++-- src/vcl/vcl_sapi.c | 4 ---- src/vcl/vppcom.c | 9 +++++---- 7 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/vcl/ldp.c b/src/vcl/ldp.c index d9f45b2bce..b4978bf1a8 100644 --- a/src/vcl/ldp.c +++ b/src/vcl/ldp.c @@ -122,9 +122,6 @@ typedef struct u32 vlsh_bit_mask; u32 debug; - /** vcl needs next epoll_create to go to libc_epoll */ - u8 vcl_needs_real_epoll; - /** * crypto state used only for testing */ @@ -288,11 +285,11 @@ ldp_init (void) ldp_init_cfg (); ldp->init = 1; - ldp->vcl_needs_real_epoll = 1; + vls_set_epoll_fns ( + (vls_epoll_fns_t){ libc_epoll_create1, libc_epoll_ctl, libc_epoll_wait }); rv = vls_app_create (ldp_get_app_name ()); if (rv != VPPCOM_OK) { - ldp->vcl_needs_real_epoll = 0; if (rv == VPPCOM_EEXIST) return 0; LDBG (2, @@ -302,7 +299,6 @@ ldp_init (void) ldp->init = 0; return rv; } - ldp->vcl_needs_real_epoll = 0; LDBG (0, "LDP initialization: done!"); @@ -2385,22 +2381,11 @@ shutdown (int fd, int how) int epoll_create1 (int flags) { - ldp_worker_ctx_t *ldpw = ldp_worker_get_current (); vls_handle_t vlsh; int rv; ldp_init_check (); - if (ldp->vcl_needs_real_epoll || vls_use_real_epoll ()) - { - rv = libc_epoll_create1 (flags); - ldp->vcl_needs_real_epoll = 0; - /* Assume this is a request to create the mq epfd */ - ldpw->vcl_mq_epfd = rv; - LDBG (0, "created vcl epfd %u", rv); - return rv; - } - vlsh = vls_epoll_create (); if (PREDICT_FALSE (vlsh == VLS_INVALID_HANDLE)) { @@ -2527,10 +2512,6 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents, if (PREDICT_FALSE (vppcom_worker_index () == ~0)) vls_register_vcl_worker (); - ldpw = ldp_worker_get_current (); - if (epfd == ldpw->vcl_mq_epfd) - return libc_epoll_pwait (epfd, events, maxevents, timeout, sigmask); - ep_vlsh = ldp_fd_to_vlsh (epfd); if (PREDICT_FALSE (ep_vlsh == VLS_INVALID_HANDLE)) { @@ -2539,6 +2520,7 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents, return -1; } + ldpw = ldp_worker_get_current (); if (PREDICT_FALSE (ldpw->clib_time.init_cpu_time == 0)) clib_time_init (&ldpw->clib_time); time_to_wait = ((timeout >= 0) ? (double) timeout / 1000 : 0); @@ -2611,9 +2593,6 @@ ldp_epoll_pwait_eventfd (int epfd, struct epoll_event *events, vls_register_vcl_worker (); ldpw = ldp_worker_get_current (); - if (epfd == ldpw->vcl_mq_epfd) - return libc_epoll_pwait (epfd, events, maxevents, timeout, sigmask); - ep_vlsh = ldp_fd_to_vlsh (epfd); if (PREDICT_FALSE (ep_vlsh == VLS_INVALID_HANDLE)) { @@ -2655,8 +2634,7 @@ ldp_epoll_pwait_eventfd (int epfd, struct epoll_event *events, ldpw->vcl_mq_epfd = vppcom_mq_epoll_fd (); e.events = EPOLLIN; e.data.fd = ldpw->vcl_mq_epfd; - if (libc_epoll_ctl (libc_epfd, EPOLL_CTL_ADD, ldpw->vcl_mq_epfd, &e) < - 0) + if (libc_epoll_ctl (libc_epfd, EPOLL_CTL_ADD, ldpw->vcl_mq_epfd, &e) < 0) { LDBG (0, "epfd %d, add libc mq epoll fd %d to libc epoll fd %d", epfd, ldpw->vcl_mq_epfd, libc_epfd); diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index aaf47c8f9e..15b53a3aba 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -2216,15 +2216,6 @@ vls_mt_wrk_supported (void) return vcm->cfg.mt_wrk_supported; } -int -vls_use_real_epoll (void) -{ - if (vcl_get_worker_index () == ~0) - return 0; - - return vcl_worker_get_current ()->vcl_needs_real_epoll; -} - int vls_set_libc_epfd (vls_handle_t ep_vlsh, int libc_epfd) { @@ -2271,6 +2262,14 @@ vls_get_libc_epfd (vls_handle_t ep_vlsh) return rv; } +void +vls_set_epoll_fns (vls_epoll_fns_t ep_fns) +{ + vcm->vcl_epoll_create1 = ep_fns.epoll_create1_fn; + vcm->vcl_epoll_ctl = ep_fns.epoll_ctl_fn; + vcm->vcl_epoll_wait = ep_fns.epoll_wait_fn; +} + void vls_register_vcl_worker (void) { diff --git a/src/vcl/vcl_locked.h b/src/vcl/vcl_locked.h index 98a1c542e4..a55d182a23 100644 --- a/src/vcl/vcl_locked.h +++ b/src/vcl/vcl_locked.h @@ -24,6 +24,13 @@ #define VLS_WORKER_RPC_TIMEOUT 3 /* timeout to wait rpc response. */ typedef int vls_handle_t; +typedef struct vls_epoll_fns_ +{ + int (*epoll_create1_fn) (int flags); + int (*epoll_ctl_fn) (int epfd, int op, int fd, struct epoll_event *event); + int (*epoll_wait_fn) (int epfd, struct epoll_event *events, int maxevents, + int timeout); +} vls_epoll_fns_t; vls_handle_t vls_create (uint8_t proto, uint8_t is_nonblocking); int vls_shutdown (vls_handle_t vlsh, int how); @@ -57,9 +64,9 @@ vls_handle_t vls_session_index_to_vlsh (uint32_t session_index); int vls_app_create (char *app_name); unsigned char vls_use_eventfd (void); unsigned char vls_mt_wrk_supported (void); -int vls_use_real_epoll (void); int vls_set_libc_epfd (vls_handle_t ep_vlsh, int libc_epfd); int vls_get_libc_epfd (vls_handle_t ep_vlsh); +void vls_set_epoll_fns (vls_epoll_fns_t ep_fns); void vls_register_vcl_worker (void); #endif /* SRC_VCL_VCL_LOCKED_H_ */ diff --git a/src/vcl/vcl_private.c b/src/vcl/vcl_private.c index ea82c26803..dcd827f2a6 100644 --- a/src/vcl/vcl_private.c +++ b/src/vcl/vcl_private.c @@ -48,7 +48,7 @@ vcl_mq_epoll_add_api_sock (vcl_worker_t *wrk) int rv; e.data.u32 = VCL_EP_SAPIFD_EVT; - rv = epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_ADD, cs->fd, &e); + rv = vcm->vcl_epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_ADD, cs->fd, &e); if (rv != EEXIST && rv < 0) return -1; @@ -77,7 +77,7 @@ vcl_mq_epoll_add_evfd (vcl_worker_t * wrk, svm_msg_q_t * mq) e.events = EPOLLIN; e.data.u32 = mqc_index; - if (epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_ADD, mq_fd, &e) < 0) + if (vcm->vcl_epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_ADD, mq_fd, &e) < 0) { VDBG (0, "failed to add mq eventfd to mq epoll fd"); return -1; @@ -101,7 +101,7 @@ vcl_mq_epoll_del_evfd (vcl_worker_t * wrk, u32 mqc_index) return -1; mqc = vcl_mq_evt_conn_get (wrk, mqc_index); - if (epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_DEL, mqc->mq_fd, 0) < 0) + if (vcm->vcl_epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_DEL, mqc->mq_fd, 0) < 0) { VDBG (0, "failed to del mq eventfd to mq epoll fd"); return -1; @@ -187,8 +187,8 @@ vcl_worker_detached_start_signal_mq (vcl_worker_t *wrk) struct epoll_event evt = {}; evt.events = EPOLLIN; evt.data.u32 = VCL_EP_PIPEFD_EVT; - if (epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_ADD, wrk->detached_pipefds[0], - &evt) < 0) + if (vcm->vcl_epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_ADD, + wrk->detached_pipefds[0], &evt) < 0) { VDBG (0, "failed to add mq eventfd to mq epoll fd"); exit (1); @@ -211,8 +211,8 @@ vcl_worker_detached_signal_mq (vcl_worker_t *wrk) void vcl_worker_detached_stop_signal_mq (vcl_worker_t *wrk) { - if (epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_DEL, wrk->detached_pipefds[0], 0) < - 0) + if (vcm->vcl_epoll_ctl (wrk->mqs_epfd, EPOLL_CTL_DEL, + wrk->detached_pipefds[0], 0) < 0) { VDBG (0, "failed to del mq eventfd to mq epoll fd"); exit (1); @@ -324,9 +324,7 @@ vcl_worker_alloc_and_init () wrk->mqs_epfd = -1; if (vcm->cfg.use_mq_eventfd) { - wrk->vcl_needs_real_epoll = 1; - wrk->mqs_epfd = epoll_create (1); - wrk->vcl_needs_real_epoll = 0; + wrk->mqs_epfd = vcm->vcl_epoll_create1 (0); if (wrk->mqs_epfd < 0) { clib_unix_warning ("epoll_create() returned"); @@ -374,6 +372,17 @@ vcl_worker_ctrl_mq (vcl_worker_t * wrk) return wrk->ctrl_mq; } +void +vcl_init_epoll_fns () +{ + if (!vcm->vcl_epoll_create1) + vcm->vcl_epoll_create1 = epoll_create1; + if (!vcm->vcl_epoll_ctl) + vcm->vcl_epoll_ctl = epoll_ctl; + if (!vcm->vcl_epoll_wait) + vcm->vcl_epoll_wait = epoll_wait; +} + int vcl_session_read_ready (vcl_session_t * s) { diff --git a/src/vcl/vcl_private.h b/src/vcl/vcl_private.h index 609653f20a..a66dbf33e8 100644 --- a/src/vcl/vcl_private.h +++ b/src/vcl/vcl_private.h @@ -320,8 +320,6 @@ typedef struct vcl_worker_ int session_attr_op_rv; transport_endpt_attr_t session_attr_rv; - /** vcl needs next epoll_create to go to libc_epoll */ - u8 vcl_needs_real_epoll; volatile int rpc_done; /* functions to be called pre/post wait if vcl managed by vls */ @@ -381,6 +379,14 @@ typedef struct vppcom_main_t_ vcl_rpc_fn_t *wrk_rpc_fn; + /* + * Pointers to libc epoll fns to avoid loops when ldp is on + */ + int (*vcl_epoll_create1) (int flags); + int (*vcl_epoll_ctl) (int epfd, int op, int fd, struct epoll_event *event); + int (*vcl_epoll_wait) (int epfd, struct epoll_event *events, int maxevents, + int timeout); + /* * Binary api context */ @@ -807,6 +813,9 @@ void vcl_worker_set_wait_mq_fns (vcl_worker_wait_mq_fn pre_wait, void vcl_worker_detached_start_signal_mq (vcl_worker_t *wrk); void vcl_worker_detached_signal_mq (vcl_worker_t *wrk); void vcl_worker_detached_stop_signal_mq (vcl_worker_t *wrk); + +void vcl_init_epoll_fns (void); + /* * VCL Binary API */ diff --git a/src/vcl/vcl_sapi.c b/src/vcl/vcl_sapi.c index e3e2b6ac37..f23273aa8f 100644 --- a/src/vcl/vcl_sapi.c +++ b/src/vcl/vcl_sapi.c @@ -26,8 +26,6 @@ vcl_api_connect_app_socket (vcl_worker_t * wrk) cs->flags = CLIB_SOCKET_F_IS_CLIENT | CLIB_SOCKET_F_SEQPACKET | CLIB_SOCKET_F_BLOCKING; - wrk->vcl_needs_real_epoll = 1; - if ((err = clib_socket_init (cs))) { /* don't report the error to avoid flood of error messages during @@ -39,8 +37,6 @@ vcl_api_connect_app_socket (vcl_worker_t * wrk) done: - wrk->vcl_needs_real_epoll = 0; - return rv; } diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 0671a37ea2..62d2addd7b 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -1482,6 +1482,7 @@ vppcom_app_create (const char *app_name) vcm->main_cpu = pthread_self (); vcm->main_pid = getpid (); vcm->app_name = format (0, "%s", app_name); + vcl_init_epoll_fns (); fifo_segment_main_init (&vcm->segment_main, (uword) ~0, 20 /* timeout in secs */); pool_alloc (vcm->workers, vcl_cfg->max_workers); @@ -2799,8 +2800,8 @@ vppcom_select_eventfd (vcl_worker_t * wrk, int n_bits, do { - n_mq_evts = epoll_wait (wrk->mqs_epfd, wrk->mq_events, - vec_len (wrk->mq_events), time_to_wait); + n_mq_evts = vcm->vcl_epoll_wait (wrk->mqs_epfd, wrk->mq_events, + vec_len (wrk->mq_events), time_to_wait); if (n_mq_evts < 0) { if (errno == EINTR) @@ -3599,8 +3600,8 @@ vppcom_epoll_wait_eventfd (vcl_worker_t *wrk, struct epoll_event *events, do { - n_mq_evts = epoll_wait (wrk->mqs_epfd, wrk->mq_events, - vec_len (wrk->mq_events), timeout_ms); + n_mq_evts = vcm->vcl_epoll_wait (wrk->mqs_epfd, wrk->mq_events, + vec_len (wrk->mq_events), timeout_ms); if (n_mq_evts < 0) { if (errno == EINTR) From 56c1824202e3c919674574b1601e5306c747468d Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 6 Jun 2025 13:18:09 -0700 Subject: [PATCH 041/313] vcl: add flag to track need for locks in vls Once an mt app with a single vcl worker adds more than one pthread, set flag that locks are needed. Avoids issues if in a 2 thread app, a pthread disappears while the other holds vls locks. Type: improvement Change-Id: I1203a9060ea88c577f82226a7efd96d0557c7497 Signed-off-by: Florin Coras --- src/vcl/vcl_locked.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index 15b53a3aba..79c4e25015 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -136,6 +136,7 @@ typedef struct vls_local_ { int vls_wrk_index; /**< vls wrk index, 1 per process */ volatile int vls_mt_n_threads; /**< number of threads detected */ + int vls_mt_needs_locks; /**< mt single vcl wrk needs locks */ clib_rwlock_t vls_pool_lock; /**< per process/wrk vls pool locks */ pthread_mutex_t vls_mt_mq_mlock; /**< vcl mq lock */ pthread_mutex_t vls_mt_spool_mlock; /**< vcl select or pool lock */ @@ -267,28 +268,28 @@ vls_shared_data_pool_runlock (void) static inline void vls_mt_pool_rlock (void) { - if (vlsl->vls_mt_n_threads > 1) + if (vlsl->vls_mt_needs_locks) clib_rwlock_reader_lock (&vlsl->vls_pool_lock); } static inline void vls_mt_pool_runlock (void) { - if (vlsl->vls_mt_n_threads > 1) + if (vlsl->vls_mt_needs_locks) clib_rwlock_reader_unlock (&vlsl->vls_pool_lock); } static inline void vls_mt_pool_wlock (void) { - if (vlsl->vls_mt_n_threads > 1) + if (vlsl->vls_mt_needs_locks) clib_rwlock_writer_lock (&vlsl->vls_pool_lock); } static inline void vls_mt_pool_wunlock (void) { - if (vlsl->vls_mt_n_threads > 1) + if (vlsl->vls_mt_needs_locks) clib_rwlock_writer_unlock (&vlsl->vls_pool_lock); } @@ -310,6 +311,7 @@ static void vls_mt_add (void) { vlsl->vls_mt_n_threads += 1; + vlsl->vls_mt_needs_locks = 1; /* If multi-thread workers are supported, for each new thread register a new * vcl worker with vpp. Otherwise, all threads use the same vcl worker, so @@ -404,14 +406,14 @@ vls_is_shared (vcl_locked_session_t * vls) static inline void vls_lock (vcl_locked_session_t * vls) { - if ((vlsl->vls_mt_n_threads > 1) || vls_is_shared (vls)) + if (vlsl->vls_mt_needs_locks || vls_is_shared (vls)) clib_spinlock_lock (&vls->lock); } static inline int vls_trylock (vcl_locked_session_t *vls) { - if ((vlsl->vls_mt_n_threads > 1) || vls_is_shared (vls)) + if (vlsl->vls_mt_needs_locks || vls_is_shared (vls)) return !clib_spinlock_trylock (&vls->lock); return 0; } @@ -419,7 +421,7 @@ vls_trylock (vcl_locked_session_t *vls) static inline void vls_unlock (vcl_locked_session_t * vls) { - if ((vlsl->vls_mt_n_threads > 1) || vls_is_shared (vls)) + if (vlsl->vls_mt_needs_locks || vls_is_shared (vls)) clib_spinlock_unlock (&vls->lock); } @@ -1245,7 +1247,7 @@ vls_mt_detect (void) } \ else \ { \ - if (PREDICT_FALSE (vlsl->vls_mt_n_threads > 1)) \ + if (PREDICT_FALSE (vlsl->vls_mt_needs_locks)) \ vls_mt_acq_locks (_vls, _op, &_locks_acq); \ } From b3d246b54b1b55a3bba4b2915e9891ec75671161 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Sun, 8 Jun 2025 13:38:13 -0400 Subject: [PATCH 042/313] http: http2_handle_continuation_frame coverity fix Type: fix Change-Id: I05b4308dd689d1977b59c6ef5791561f7e45693c Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 36a27f40de..f9f281f8b9 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1198,6 +1198,8 @@ http2_handle_continuation_frame (http_conn_t *hc, http2_frame_header_t *fh) if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS) { req = http2_conn_get_req (hc, fh->stream_id); + if (!req) + return HTTP2_ERROR_PROTOCOL_ERROR; h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION; req->payload = h2c->unparsed_headers; req->payload_len = vec_len (h2c->unparsed_headers); From e4f009f5297766be9fe2a294ee76ba92d2e90435 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Wed, 4 Jun 2025 11:08:47 +0000 Subject: [PATCH 043/313] virtio: conditionally set checksum offload based on TCP/UDP offload flags Type: fix Previously, the Virtio device node unconditionally set the checksum offload flag and the checksum start offset, regardless of whether TCP/UDP checksum offload flags were set. This patch updates the logic to apply these settings only when the TCP/UDP offload flags are explicitly set. Signed-off-by: Mohsin Kazmi Change-Id: I9af37d03b388ff7d1e22ec125fae97fad50e64d2 --- src/vnet/devices/virtio/device.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/vnet/devices/virtio/device.c b/src/vnet/devices/virtio/device.c index 112f77e706..0dd93976c2 100644 --- a/src/vnet/devices/virtio/device.c +++ b/src/vnet/devices/virtio/device.c @@ -313,9 +313,6 @@ set_checksum_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, if (b->flags & VNET_BUFFER_F_IS_IP4) { ip4_header_t *ip4; - hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - hdr->csum_start = l4_hdr_offset; // 0x22; - /* * virtio devices do not support IP4 checksum offload. So driver takes * care of it while doing tx. @@ -333,6 +330,9 @@ set_checksum_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, tcp_header_t *tcp = (tcp_header_t *) (b->data + vnet_buffer (b)->l4_hdr_offset); tcp->checksum = ip4_pseudo_header_cksum (ip4); + hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; + hdr->hdr_len = l4_hdr_offset + tcp_header_bytes (tcp); + hdr->csum_start = l4_hdr_offset; hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); } else if (oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM) @@ -340,14 +340,15 @@ set_checksum_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, udp_header_t *udp = (udp_header_t *) (b->data + vnet_buffer (b)->l4_hdr_offset); udp->checksum = ip4_pseudo_header_cksum (ip4); + hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; + hdr->hdr_len = l4_hdr_offset + sizeof (udp_header_t); + hdr->csum_start = l4_hdr_offset; hdr->csum_offset = STRUCT_OFFSET_OF (udp_header_t, checksum); } } else if (b->flags & VNET_BUFFER_F_IS_IP6) { ip6_header_t *ip6; - hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - hdr->csum_start = l4_hdr_offset; // 0x36; ip6 = (ip6_header_t *) (b->data + vnet_buffer (b)->l3_hdr_offset); /* @@ -359,6 +360,9 @@ set_checksum_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, tcp_header_t *tcp = (tcp_header_t *) (b->data + vnet_buffer (b)->l4_hdr_offset); tcp->checksum = ip6_pseudo_header_cksum (ip6); + hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; + hdr->hdr_len = l4_hdr_offset + tcp_header_bytes (tcp); + hdr->csum_start = l4_hdr_offset; hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); } else if (oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM) @@ -366,6 +370,9 @@ set_checksum_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, udp_header_t *udp = (udp_header_t *) (b->data + vnet_buffer (b)->l4_hdr_offset); udp->checksum = ip6_pseudo_header_cksum (ip6); + hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; + hdr->hdr_len = l4_hdr_offset + sizeof (udp_header_t); + hdr->csum_start = l4_hdr_offset; hdr->csum_offset = STRUCT_OFFSET_OF (udp_header_t, checksum); } } @@ -381,11 +388,11 @@ set_gso_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, if (b->flags & VNET_BUFFER_F_IS_IP4) { ip4_header_t *ip4; + hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV4; - hdr->gso_size = vnet_buffer2 (b)->gso_size; hdr->hdr_len = l4_hdr_offset + vnet_buffer2 (b)->gso_l4_hdr_sz; - hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - hdr->csum_start = l4_hdr_offset; // 0x22; + hdr->gso_size = vnet_buffer2 (b)->gso_size; + hdr->csum_start = l4_hdr_offset; hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); ip4 = (ip4_header_t *) (b->data + vnet_buffer (b)->l3_hdr_offset); /* @@ -397,11 +404,11 @@ set_gso_offsets (vlib_buffer_t *b, vnet_virtio_net_hdr_v1_t *hdr, } else if (b->flags & VNET_BUFFER_F_IS_IP6) { + hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; hdr->gso_type = VIRTIO_NET_HDR_GSO_TCPV6; - hdr->gso_size = vnet_buffer2 (b)->gso_size; hdr->hdr_len = l4_hdr_offset + vnet_buffer2 (b)->gso_l4_hdr_sz; - hdr->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; - hdr->csum_start = l4_hdr_offset; // 0x36; + hdr->gso_size = vnet_buffer2 (b)->gso_size; + hdr->csum_start = l4_hdr_offset; hdr->csum_offset = STRUCT_OFFSET_OF (tcp_header_t, checksum); } } From 2220078939568a52c09668a5fa37f5a4fe0b5b6d Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sun, 8 Jun 2025 15:15:37 -0700 Subject: [PATCH 044/313] vcl: fix fifo private vpp sh on migration Type: fix Change-Id: I97a44a8b5619c30ab0e96785e31dd0e98448e8ed Signed-off-by: Florin Coras --- src/vcl/vppcom.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 62d2addd7b..0e52a26dc9 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -649,6 +649,8 @@ vcl_session_migrated_handler (vcl_worker_t * wrk, void *data) } s->vpp_handle = mp->new_handle; + s->rx_fifo->vpp_sh = mp->new_handle; + s->tx_fifo->vpp_sh = mp->new_handle; vcl_segment_attach_mq (vcl_vpp_worker_segment_handle (0), mp->vpp_evt_q, mp->vpp_thread_index, &s->vpp_evt_q); From 26cac364ffbc76c2122bf4541b49aa9b81309c65 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 10 Jun 2025 04:48:06 -0400 Subject: [PATCH 045/313] vcl: fix read on closed sessions with mt apps Apps like iperf can have a thread close a session that another thread selects/epolls. Make sure to return error when that happens. Type: fix Change-Id: I586f8077431a28e7d8d3e4f21e45efcc035698ba Signed-off-by: Florin Coras --- src/vcl/ldp.c | 4 +++- src/vcl/vppcom.c | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vcl/ldp.c b/src/vcl/ldp.c index b4978bf1a8..7e4cddbb46 100644 --- a/src/vcl/ldp.c +++ b/src/vcl/ldp.c @@ -661,7 +661,9 @@ ldp_select_init_maps (fd_set * __restrict original, { vlsh_to_session_and_worker_index (vlsh, &session_index, &wrk_index); if (wrk_index != vppcom_worker_index ()) - clib_warning ("migration currently not supported"); + clib_warning ( + "migration for %d vlsh %d from %d to %d not supported", fd, vlsh, + wrk_index, vppcom_worker_index ()); else *vclb = clib_bitmap_set (*vclb, session_index, 1); } diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 0e52a26dc9..9aff1e6ccd 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -617,6 +617,7 @@ vcl_session_unlisten_reply_handler (vcl_worker_t * wrk, void *data) if (mp->context != wrk->wrk_index) VDBG (0, "wrong context"); + VDBG (0, "unlisten reply freeing %d[0x%llx]", s->session_index, mp->handle); vcl_session_table_del_vpp_handle (wrk, mp->handle); vcl_session_free (wrk, s); } @@ -2123,7 +2124,9 @@ vppcom_session_read_internal (uint32_t session_handle, void *buf, int n, s->session_index, s->vpp_handle, s->session_state, vcl_session_state_str (s->session_state)); rx_fifo = vcl_session_is_ct (s) ? s->ct_rx_fifo : s->rx_fifo; - if (svm_fifo_is_empty_cons (rx_fifo)) + /* If application closed, e.g., mt app, or no data return error */ + if (s->session_state == VCL_STATE_CLOSED || + svm_fifo_is_empty_cons (rx_fifo)) return vcl_session_closed_error (s); } From 86f3d64b81ffba79805844378667b172f56b6e6c Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Tue, 10 Jun 2025 19:19:13 -0400 Subject: [PATCH 046/313] tests: update govpp version to 0.12.0 in hs-test Type: test Change-Id: Ibf9cd66a277f801451dc2132fd3a39c396f12b86 Signed-off-by: Dave Wallace --- extras/hs-test/go.mod | 37 ++++++++++---------- extras/hs-test/go.sum | 79 +++++++++++++++++++++---------------------- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/extras/hs-test/go.mod b/extras/hs-test/go.mod index 0f11b1431b..dfeb8f874e 100644 --- a/extras/hs-test/go.mod +++ b/extras/hs-test/go.mod @@ -1,19 +1,21 @@ module fd.io/hs-test -go 1.22.5 +go 1.23.8 + +toolchain go1.23.10 require ( github.com/cilium/cilium v1.15.7 github.com/docker/docker v27.1.1+incompatible github.com/docker/go-units v0.5.0 github.com/edwarnicke/exechelper v1.0.3 - github.com/onsi/ginkgo/v2 v2.17.2 - github.com/onsi/gomega v1.33.1 + github.com/onsi/ginkgo/v2 v2.23.3 + github.com/onsi/gomega v1.37.0 github.com/quic-go/quic-go v0.48.2 github.com/sirupsen/logrus v1.9.3 github.com/summerwind/h2spec v2.2.1+incompatible - go.fd.io/govpp v0.10.0 - golang.org/x/net v0.28.0 + go.fd.io/govpp v0.12.0 + golang.org/x/net v0.37.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.30.2 k8s.io/apimachinery v0.30.2 @@ -29,7 +31,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.2 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/errors v0.22.0 // indirect @@ -42,9 +44,9 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect @@ -68,8 +70,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.19.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect @@ -79,18 +80,18 @@ require ( go.opentelemetry.io/otel/sdk v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/mod v0.18.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.30.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools/v3 v3.5.1 // indirect diff --git a/extras/hs-test/go.sum b/extras/hs-test/go.sum index 56cfeea3db..9d4087a1f7 100644 --- a/extras/hs-test/go.sum +++ b/extras/hs-test/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -16,7 +16,6 @@ github.com/cilium/cilium v1.15.7 h1:7LwGfAW/fR/VFcm6zlESjE2Ut5vJWe+kdWq3RNJrNRc= github.com/cilium/cilium v1.15.7/go.mod h1:6Ml8eeyWjMJKDeadutWhn5NibMps0H+yLOgfKBoHTUs= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -37,8 +36,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -76,13 +75,13 @@ github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvR github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -130,8 +129,8 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -145,10 +144,10 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= +github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= @@ -170,7 +169,6 @@ github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KW github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -187,11 +185,10 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA= -github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -218,8 +215,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.fd.io/govpp v0.10.0 h1:lL93SbqOILjON2pMvazrlHRekGYTRy0Qmj57RuAkxR0= -go.fd.io/govpp v0.10.0/go.mod h1:5m3bZM9ck+2EGC2O3ASmSSJAaoouyOlVWtiwj5BdCv0= +go.fd.io/govpp v0.12.0 h1:5HnMzsKHSFdxglsFyEhR0g+CzncWiLYXG2NDYgNUrnE= +go.fd.io/govpp v0.12.0/go.mod h1:6qp4J/+jumgXXoowrtVAk13PSXS6+ghPrDG8CyuU/Is= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= @@ -248,31 +245,31 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -284,18 +281,18 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -303,8 +300,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -320,8 +317,8 @@ google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 4a806462f166e7d4062bf9e213d8dc78ee96c41a Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 10 Jun 2025 17:53:47 -0400 Subject: [PATCH 047/313] udp: regrab connected session after transport clone Type: fix Change-Id: Id8a23a14f9086a68bb235fec4e190a19447c109e Signed-off-by: Florin Coras Signed-off-by: Matus Fabian --- src/vnet/session/session.c | 8 ++++---- src/vnet/session/session.h | 6 +++--- src/vnet/udp/udp_input.c | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vnet/session/session.c b/src/vnet/session/session.c index 27a4177b4c..043ad43f5d 100644 --- a/src/vnet/session/session.c +++ b/src/vnet/session/session.c @@ -852,8 +852,8 @@ session_switch_pool (void *cb_args) * Move dgram session to the right thread */ int -session_dgram_connect_notify (transport_connection_t * tc, - u32 old_thread_index, session_t ** new_session) +session_dgram_connect_notify (transport_connection_t *tc, + session_handle_tu_t osh, session_t **new_session) { session_t *new_s; session_switch_pool_args_t *rpc_args; @@ -863,7 +863,7 @@ session_dgram_connect_notify (transport_connection_t * tc, /* * Clone half-open session to the right thread. */ - new_s = session_clone_safe (tc->s_index, old_thread_index); + new_s = session_clone_safe (tc->s_index, osh.thread_index); new_s->connection_index = tc->c_index; session_set_state (new_s, SESSION_STATE_READY); new_s->flags |= SESSION_F_IS_MIGRATING; @@ -888,7 +888,7 @@ session_dgram_connect_notify (transport_connection_t * tc, rpc_args->new_session_index = new_s->session_index; rpc_args->new_thread_index = new_s->thread_index; rpc_args->session_index = tc->s_index; - rpc_args->thread_index = old_thread_index; + rpc_args->thread_index = osh.thread_index; session_send_rpc_evt_to_thread (rpc_args->thread_index, session_switch_pool, rpc_args); diff --git a/src/vnet/session/session.h b/src/vnet/session/session.h index 744955deec..5977154893 100644 --- a/src/vnet/session/session.h +++ b/src/vnet/session/session.h @@ -591,9 +591,9 @@ transport_cleanup_cb (void *cb_fn, transport_connection_t *tc) int session_stream_connect_notify (transport_connection_t * tc, session_error_t err); -int session_dgram_connect_notify (transport_connection_t * tc, - u32 old_thread_index, - session_t ** new_session); +int session_dgram_connect_notify (transport_connection_t *tc, + session_handle_tu_t osh, + session_t **new_session); int session_stream_accept_notify (transport_connection_t * tc); void session_transport_closing_notify (transport_connection_t * tc); void session_transport_delete_notify (transport_connection_t * tc); diff --git a/src/vnet/udp/udp_input.c b/src/vnet/udp/udp_input.c index ea53db5baf..e982895d2d 100644 --- a/src/vnet/udp/udp_input.c +++ b/src/vnet/udp/udp_input.c @@ -294,19 +294,19 @@ udp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, error0 = UDP_ERROR_CONNECTED; if (s0->thread_index != thread_index) { + /* uc0 clone may allow owner of s0 to grow its pool */ + session_handle_t osh = session_handle (s0); /* * Clone the transport. It will be cleaned up with the * session once we notify the session layer. */ uc0 = udp_connection_clone_safe (s0->connection_index, s0->thread_index); - ASSERT (s0->session_index == uc0->c_s_index); - + ASSERT (session_index_from_handle (osh) == uc0->c_s_index); /* * Ask session layer for a new session. */ - session_dgram_connect_notify (&uc0->connection, - s0->thread_index, &s0); + session_dgram_connect_notify (&uc0->connection, osh, &s0); queue_event = 0; } else From a72e5cd23b28513e449c4312e7d512e97b5e6233 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 11 Jun 2025 06:37:18 -0400 Subject: [PATCH 048/313] http: fix Host header usage in http1_target_fixup Type: fix Change-Id: I034d40872515dcb192ea5cdd244737fafdb12217 Signed-off-by: Matus Fabian --- src/plugins/http/http1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 5ecc1f5230..f7d79b8955 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -768,12 +768,12 @@ http1_target_fixup (http_conn_t *hc, http_req_t *req) HTTP_URL_SCHEME_HTTP; if (req->target_form == HTTP_TARGET_AUTHORITY_FORM || - req->connection_header_index == ~0) + req->host_header_index == ~0) return; /* authority fixup */ - host = vec_elt_at_index (req->headers, req->connection_header_index); - req->target_authority_offset = host->value_offset; + host = vec_elt_at_index (req->headers, req->host_header_index); + req->target_authority_offset = req->headers_offset + host->value_offset; req->target_authority_len = host->value_len; } From 6b2c1293a67aef19d029db9b2816544da6193522 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 11 Jun 2025 07:27:54 -0400 Subject: [PATCH 049/313] http_static: use authority in redirects Use authority in redirects instead of IP address of the listener. Type: improvement Change-Id: Id6dedcb59f4d3e9775ce74be483395ee782ac5d5 Signed-off-by: Matus Fabian --- extras/hs-test/http_test.go | 97 +++++++++++++++---------- extras/hs-test/infra/hst_suite.go | 2 + src/plugins/http_static/http_static.h | 22 +++++- src/plugins/http_static/static_server.c | 55 +++++++------- 4 files changed, 108 insertions(+), 68 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 19f54d1700..12811cbd38 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -37,7 +37,8 @@ func init() { HttpClientGetTlsNoRespBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, - HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest) + HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest, + HttpStaticRedirectTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest, HttpClientGetRepeatMTTest, HttpClientPtrGetRepeatMTTest) @@ -1111,7 +1112,7 @@ func HttpStaticPathSanitizationTest(s *NoTopoSuite) { defer resp.Body.Close() s.Log(DumpHttpResp(resp, true)) s.AssertHttpStatus(resp, 301) - s.AssertHttpHeaderWithValue(resp, "Location", "http://"+s.VppAddr()+"/index.html") + s.AssertHttpHeaderWithValue(resp, "Location", "http://"+serverAddress+"/index.html") } func HttpStaticMovedTest(s *NoTopoSuite) { @@ -1119,11 +1120,11 @@ func HttpStaticMovedTest(s *NoTopoSuite) { vpp.Container.Exec(false, "mkdir -p "+wwwRootPath+"/tmp.aaa") err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "

Hello

") s.AssertNil(err, fmt.Sprint(err)) - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/" + s.Ports.Http + " debug")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + " debug")) client := NewHttpClient(defaultHttpTimeout, false) - req, err := http.NewRequest("GET", "http://"+serverAddress+":"+s.Ports.Http+"/tmp.aaa", nil) + req, err := http.NewRequest("GET", "http://"+serverAddress+"/tmp.aaa", nil) s.AssertNil(err, fmt.Sprint(err)) resp, err := client.Do(req) s.AssertNil(err, fmt.Sprint(err)) @@ -1136,6 +1137,32 @@ func HttpStaticMovedTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } +func HttpStaticRedirectTest(s *NoTopoSuite) { + vpp := s.Containers.Vpp.VppInstance + vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) + err := vpp.Container.CreateFile(wwwRootPath+"/index.html", "

Hello

") + s.AssertNil(err, fmt.Sprint(err)) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + " debug")) + + req := "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\n" + + conn, err := net.DialTimeout("tcp", serverAddress, time.Second*30) + s.AssertNil(err, fmt.Sprint(err)) + defer conn.Close() + err = conn.SetDeadline(time.Now().Add(time.Second * 5)) + s.AssertNil(err, fmt.Sprint(err)) + n, err := conn.Write([]byte(req)) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertEqual(n, len([]rune(req))) + reply := make([]byte, 1024) + _, err = conn.Read(reply) + s.AssertNil(err, fmt.Sprint(err)) + s.Log(string(reply)) + expectedLocation := fmt.Sprintf("Location: http://example.com/index.html") + s.AssertContains(string(reply), expectedLocation) +} + func HttpStaticNotFoundTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) @@ -1432,57 +1459,57 @@ func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) - resp, err := TcpSendReceive(serverAddress, "GET /interface|stats.json HTTP/1.1\r\n\r\n") + resp, err := TcpSendReceive(serverAddress, "GET /interface|stats.json HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'|' not allowed in target path") - resp, err = TcpSendReceive(serverAddress, "GET /interface#stats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface#stats.json HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'#' not allowed in target path") - resp, err = TcpSendReceive(serverAddress, "GET /interface%stats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%stats.json HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress, "GET /interface%1stats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%1stats.json HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress, "GET /interface%Bstats.json HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%Bstats.json HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress, "GET /interface%stats.json%B HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /interface%stats.json%B HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose>true HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose>true HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'>' not allowed in target query") - resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose%true HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose%true HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") - resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose=%1 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose=%1 HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target query") - resp, err = TcpSendReceive(serverAddress, "GET * HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET * HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "asterisk-form is only used for a server-wide OPTIONS request") - resp, err = TcpSendReceive(serverAddress, "GET www.example.com:80 HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET www.example.com:80 HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "authority-form is only used for CONNECT requests") - resp, err = TcpSendReceive(serverAddress, "CONNECT https://www.example.com/tunnel HTTP/1.1\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "CONNECT https://www.example.com/tunnel HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "CONNECT requests must use authority-form only") } @@ -1508,22 +1535,22 @@ func HttpInvalidContentLengthTest(s *NoTopoSuite) { func HttpContentLengthTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance - serverAddress := s.VppAddr() - s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + ":80 url-handlers debug max-body-size 12")) + serverAddress := s.VppAddr() + ":" + s.Ports.Http + s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug max-body-size 12")) ifName := s.VppIfName() - resp, err := TcpSendReceive(serverAddress+":80", - "POST /interface_stats.json HTTP/1.1\r\nContent-Length:4\r\n\r\n"+ifName) + resp, err := TcpSendReceive(serverAddress, + "POST /interface_stats.json HTTP/1.1\r\nHost: example.com\r\nContent-Length:4\r\n\r\n"+ifName) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, resp) - resp, err = TcpSendReceive(serverAddress+":80", - "POST /interface_stats.json HTTP/1.1\r\nContent-Length: 4 \r\n\r\n"+ifName) + resp, err = TcpSendReceive(serverAddress, + "POST /interface_stats.json HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4 \r\n\r\n"+ifName) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, resp) - resp, err = TcpSendReceive(serverAddress+":80", - "POST /interface_stats.json HTTP/1.1\r\nContent-Length:\t\t4\r\n\r\n"+ifName) + resp, err = TcpSendReceive(serverAddress, + "POST /interface_stats.json HTTP/1.1\r\nHost: example.com\r\nContent-Length:\t\t4\r\n\r\n"+ifName) s.AssertNil(err, fmt.Sprint(err)) validatePostInterfaceStats(s, resp) } @@ -1532,7 +1559,7 @@ func HttpHeaderErrorConnectionDropTest(s *NoTopoSuite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug max-body-size 12")) - request := "POST /interface_stats.json HTTP/1.1\r\nContent-Length: 18234234\r\n\r\n" + s.VppIfName() + request := "POST /interface_stats.json HTTP/1.1\r\nHost: example.com\r\nContent-Length: 18234234\r\n\r\n" + s.VppIfName() conn, err := net.DialTimeout("tcp", serverAddress, time.Second*30) s.AssertNil(err, fmt.Sprint(err)) err = conn.SetDeadline(time.Now().Add(time.Second * 10)) @@ -1602,10 +1629,6 @@ func HttpAbsoluteFormUriTest(s *NoTopoSuite) { resp, err := TcpSendReceive(serverAddress, "GET http://"+serverAddress+"/show/version HTTP/1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 200 OK") - - resp, err = TcpSendReceive(serverAddress, "GET http://"+serverAddress+":80/show/version HTTP/1.1\r\n\r\n") - s.AssertNil(err, fmt.Sprint(err)) - s.AssertContains(resp, "HTTP/1.1 200 OK") } func HttpInvalidAuthorityFormUriTest(s *NoTopoSuite) { @@ -1708,31 +1731,31 @@ func HttpInvalidHeadersTest(s *NoTopoSuite) { s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Header section must end with CRLF CRLF") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser@Agent:test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\r\nUser@Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'@' not allowed in field name") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\r\nUser-Agent\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "incomplete field line not allowed") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\n: test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\r\n: test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field name not allowed") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\rUser-Agent:test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\rUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\nUser-Agent:test\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\nUser-Agent:test\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "invalid field line end not allowed") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\r\nUser-Agent:\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") - resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent: \r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+"\r\nUser-Agent: \r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") } diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index e9d0dacce0..bfc6b7a842 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -70,6 +70,8 @@ var reservedPorts = []string{ "53", "67", "68", + "80", + "443", "500", "2152", "3784", diff --git a/src/plugins/http_static/http_static.h b/src/plugins/http_static/http_static.h index 2c65df0c9d..46c9e75884 100644 --- a/src/plugins/http_static/http_static.h +++ b/src/plugins/http_static/http_static.h @@ -47,6 +47,7 @@ typedef struct hss_session_ u32 listener_index; u8 *target_path; u8 *target_query; + u8 *authority; http_req_method_t rt; /** Fully-resolved file path */ u8 *path; @@ -128,6 +129,24 @@ typedef hss_url_handler_rc_t (*hss_url_handler_fn) (hss_url_handler_args_t *); typedef void (*hss_register_url_fn) (hss_url_handler_fn, char *, int); typedef void (*hss_session_send_fn) (hss_url_handler_args_t *args); +#define foreach_hss_listener_flags \ + _ (HTTP1_ONLY) \ + _ (NEED_CRYPTO) + +typedef enum hss_listener_flags_bit_ +{ +#define _(sym) HSS_LISTENER_F_BIT_##sym, + foreach_hss_listener_flags +#undef _ +} hss_listener_flags_bit_t; + +typedef enum hss_listener_flags_ +{ +#define _(sym) HSS_LISTENER_F_##sym = 1 << HSS_LISTENER_F_BIT_##sym, + foreach_hss_listener_flags +#undef _ +} __clib_packed hss_listener_flags_t; + typedef struct hss_listener_ { /** Path to file hash table */ @@ -156,8 +175,7 @@ typedef struct hss_listener_ u32 l_index; /** Listener session handle */ session_handle_t session_handle; - /** Enable only HTTP/1.1 in TLS ALPN list */ - u8 http1_only; + u8 flags; } hss_listener_t; /** \brief Main data structure diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index f1cfbeec1d..4015916bcf 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -435,12 +435,7 @@ static u32 try_index_file (hss_listener_t *l, hss_session_t *hs, u8 *path) { hss_main_t *hsm = &hss_main; - u8 *port_str = 0, *redirect; - transport_endpoint_t endpt; - transport_proto_t proto; - int print_port = 0; - u16 local_port; - session_t *ts; + u8 *redirect; u32 plen; /* Remove the trailing space */ @@ -464,29 +459,13 @@ try_index_file (hss_listener_t *l, hss_session_t *hs, u8 *path) */ vec_delete (path, vec_len (l->www_root) - 1, 0); - ts = session_get (hs->vpp_session_index, hs->thread_index); - session_get_endpoint (ts, &endpt, 1 /* is_local */); - - local_port = clib_net_to_host_u16 (endpt.port); - proto = session_type_transport_proto (ts->session_type); - - if ((proto == TRANSPORT_PROTO_TCP && local_port != 80) || - (proto == TRANSPORT_PROTO_TLS && local_port != 443)) - { - print_port = 1; - port_str = format (0, ":%u", (u32) local_port); - } - - redirect = - format (0, "http%s://%U%s%s", proto == TRANSPORT_PROTO_TLS ? "s" : "", - format_ip46_address, &endpt.ip, endpt.is_ip4, - print_port ? port_str : (u8 *) "", path); + redirect = format (0, "http%s://%s%s", + l->flags & HSS_LISTENER_F_NEED_CRYPTO ? "s" : "", + hs->authority, path); if (hsm->debug_level > 0) clib_warning ("redirect: %s", redirect); - vec_free (port_str); - if (hss_add_header (hs, HTTP_HEADER_LOCATION, (const char *) redirect, vec_len (redirect))) return HTTP_STATUS_INTERNAL_ERROR; @@ -691,6 +670,7 @@ hss_ts_rx_callback (session_t *ts) hs->data_len = 0; vec_free (hs->target_path); vec_free (hs->target_query); + vec_free (hs->authority); http_init_headers_ctx (&hs->resp_headers, hs->headers_buf, vec_len (hs->headers_buf)); @@ -710,6 +690,21 @@ hss_ts_rx_callback (session_t *ts) hs->rt = msg.method_type; + /* Read authority */ + if (msg.data.target_authority_len) + { + vec_validate (hs->authority, msg.data.target_authority_len - 1); + rv = svm_fifo_peek (ts->rx_fifo, msg.data.target_authority_offset, + msg.data.target_authority_len, hs->authority); + ASSERT (rv == msg.data.target_authority_len); + } + else + { + /* Mandatory Host header was missing in HTTP/1.1 request */ + start_send_data (hs, HTTP_STATUS_BAD_REQUEST); + vec_add1 (hs->authority, 0); + goto err_done; + } /* Read target path */ if (msg.data.target_path_len) { @@ -891,6 +886,7 @@ hss_ts_cleanup (session_t *s, session_cleanup_ntf_t ntf) hs->free_data = 0; vec_free (hs->headers_buf); vec_free (hs->path); + vec_free (hs->authority); vec_free (hs->target_path); vec_free (hs->target_query); @@ -988,11 +984,12 @@ hss_listen (hss_listener_t *l, session_handle_t *lh) if (need_crypto) { + l->flags |= HSS_LISTENER_F_NEED_CRYPTO; ext_cfg = session_endpoint_add_ext_cfg ( &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, sizeof (transport_endpt_crypto_cfg_t)); ext_cfg->crypto.ckpair_index = hsm->ckpair_index; - if (l->http1_only) + if (l->flags & HSS_LISTENER_F_HTTP1_ONLY) ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; } @@ -1135,7 +1132,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE; l->rx_buff_thresh = HSS_DEFAULT_RX_BUFFER_THRESH; l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT; - l->http1_only = 0; + l->flags = 0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) @@ -1182,7 +1179,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, &l->use_ptr_thresh)) ; else if (unformat (line_input, "http1-only")) - l->http1_only = 1; + l->flags |= HSS_LISTENER_F_HTTP1_ONLY; else { error = clib_error_return (0, "unknown input `%U'", @@ -1278,7 +1275,7 @@ hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input, l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE; l->rx_buff_thresh = HSS_DEFAULT_RX_BUFFER_THRESH; l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT; - l->http1_only = 0; + l->flags = 0; while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { From 4d9e8e6b3b48b8ca986d641a9f96f887b2b6a237 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 30 May 2025 10:51:59 -0400 Subject: [PATCH 050/313] http: http/2 connect method Type: feature Change-Id: I7dc27a93388a6d680f2a87ccbd2704bb76a91357 Signed-off-by: Matus Fabian --- extras/hs-test/h2spec_extras/h2spec_extras.go | 228 ++++++++++ extras/hs-test/infra/suite_vpp_proxy.go | 123 ++++- extras/hs-test/proxy_test.go | 63 +-- src/plugins/hs_apps/proxy.c | 2 - src/plugins/http/http2/http2.c | 422 ++++++++++++++---- 5 files changed, 705 insertions(+), 133 deletions(-) diff --git a/extras/hs-test/h2spec_extras/h2spec_extras.go b/extras/hs-test/h2spec_extras/h2spec_extras.go index 3c2b9dd76a..6957557a01 100644 --- a/extras/hs-test/h2spec_extras/h2spec_extras.go +++ b/extras/hs-test/h2spec_extras/h2spec_extras.go @@ -2,10 +2,12 @@ package h2spec_extras import ( "fmt" + "slices" "github.com/summerwind/h2spec/config" "github.com/summerwind/h2spec/spec" "golang.org/x/net/http2" + "golang.org/x/net/http2/hpack" ) var key = "extras" @@ -25,6 +27,7 @@ func Spec() *spec.TestGroup { } tg.AddTestGroup(FlowControl()) + tg.AddTestGroup(ConnectMethod()) return tg } @@ -53,6 +56,32 @@ func VerifyWindowUpdate(conn *spec.Conn, streamID, expectedIncrement uint32) err return nil } +func VerifyTunnelClosed(conn *spec.Conn) error { + var streamClosed = false + var lastEvent spec.Event + for !conn.Closed { + ev := conn.WaitEvent() + lastEvent = ev + switch event := ev.(type) { + case spec.DataFrameEvent: + if event.StreamEnded() { + streamClosed = true + goto done + } + case spec.TimeoutEvent: + goto done + } + } +done: + if !streamClosed { + return &spec.TestError{ + Expected: []string{spec.ExpectedStreamClosed}, + Actual: lastEvent.String(), + } + } + return nil +} + func FlowControl() *spec.TestGroup { tg := NewTestGroup("1", "Flow control") tg.AddTestCase(&spec.TestCase{ @@ -168,3 +197,202 @@ func FlowControl() *spec.TestGroup { }) return tg } + +func ConnectHeaders(c *config.Config) []hpack.HeaderField { + + return []hpack.HeaderField{ + spec.HeaderField(":method", "CONNECT"), + spec.HeaderField(":authority", c.Path), + } +} + +func ConnectMethod() *spec.TestGroup { + tg := NewTestGroup("2", "CONNECT method") + + tg.AddTestCase(&spec.TestCase{ + Desc: "Tunnel closed by target", + Requirement: "A proxy that receives a TCP segment with the FIN bit set sends a DATA frame with the END_STREAM flag set.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + // send http/1.0 so target will close connection when send response + conn.WriteData(streamID, false, []byte("GET /index.html HTTP/1.0\r\n\r\n")) + + // wait for DATA frame with END_STREAM flag set + err = VerifyTunnelClosed(conn) + if err != nil { + return err + } + + // client is expected to send DATA frame with the END_STREAM flag set + conn.WriteData(streamID, true, []byte("")) + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "Tunnel closed by client (with attached data)", + Requirement: "A proxy that receives a DATA frame with the END_STREAM flag set sends the attached data with the FIN bit set on the last TCP segment.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + // close tunnel + conn.WriteData(streamID, true, []byte("HEAD /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n")) + + // wait for DATA frame with END_STREAM flag set + err = VerifyTunnelClosed(conn) + if err != nil { + return err + } + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "Tunnel closed by client (empty DATA frame)", + Requirement: "The final DATA frame could be empty.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + conn.WriteData(streamID, false, []byte("HEAD /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n")) + + // verify reception of response DATA frame + err = spec.VerifyEventType(conn, spec.EventDataFrame) + if err != nil { + return err + } + + // close tunnel + conn.WriteData(streamID, true, []byte("")) + + // wait for DATA frame with END_STREAM flag set + err = VerifyTunnelClosed(conn) + if err != nil { + return err + } + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "Multiple tunnels", + Requirement: "In HTTP/2, the CONNECT method establishes a tunnel over a single HTTP/2 stream to a remote host, rather than converting the entire connection to a tunnel.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + maxStreams, ok := conn.Settings[http2.SettingMaxConcurrentStreams] + if !ok { + return spec.ErrSkipped + } + + for i := 0; i < int(maxStreams); i++ { + headers := ConnectHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + streamID += 2 + } + + streamID = 1 + for i := 0; i < int(maxStreams); i++ { + conn.WriteData(streamID, false, []byte("HEAD /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n")) + streamID += 2 + } + + var receivedResp []uint32 + for i := 0; i < int(maxStreams); i++ { + actual, passed := conn.WaitEventByType(spec.EventDataFrame) + switch event := actual.(type) { + case spec.DataFrameEvent: + passed = !slices.Contains(receivedResp, event.StreamID) + default: + passed = false + } + if !passed { + expected := []string{ + "Receive one response per stream (tunnel)", + } + + return &spec.TestError{ + Expected: expected, + Actual: actual.String(), + } + } + } + + return nil + }, + }) + return tg +} diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index 2226358c77..44cc6bb7cb 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -6,15 +6,21 @@ package hst import ( + "bytes" "fmt" + "io" "net" + "os" "reflect" "runtime" "strconv" "strings" + "time" + "fd.io/hs-test/h2spec_extras" . "fd.io/hs-test/infra/common" . "github.com/onsi/ginkgo/v2" + "github.com/summerwind/h2spec/config" ) const ( @@ -124,6 +130,20 @@ func (s *VppProxySuite) SetupNginxServer() { s.AssertNil(s.Containers.NginxServerTransient.Start()) } +func (s *VppProxySuite) ConfigureVppProxy(proto string, proxyPort uint16) { + vppProxy := s.Containers.VppProxy.VppInstance + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri %s://%s:%d", proto, s.VppProxyAddr(), proxyPort) + if proto != "http" && proto != "https" && proto != "udp" { + proto = "tcp" + } + if proto != "http" && proto != "https" { + cmd += fmt.Sprintf(" client-uri %s://%s:%d", proto, s.ServerAddr(), s.Ports.Server) + } + + output := vppProxy.Vppctl(cmd) + s.Log("proxy configured: " + output) +} + func (s *VppProxySuite) ServerAddr() string { return s.Interfaces.Server.Ip4AddressString() } @@ -163,23 +183,45 @@ func (s *VppProxySuite) CurlUploadResource(uri, file string) { s.AssertNotContains(log, "Operation timed out") } -func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri string) { - args := fmt.Sprintf("-w @/tmp/write_out_download_connect --max-time %d --insecure --proxy-insecure -p -x %s --remote-name --output-dir /tmp %s", s.maxTimeout, proxyUri, uri) +func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri string, extraArgs ...string) (string, string) { + extras := "" + if len(extraArgs) > 0 { + extras = strings.Join(extraArgs, " ") + extras += " " + } + args := fmt.Sprintf("%s-w @/tmp/write_out_download_connect --max-time %d --insecure --proxy-insecure -p -x %s --remote-name --output-dir /tmp %s", extras, s.maxTimeout, proxyUri, uri) writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) - s.AssertContains(writeOut, "CONNECT response code: 200") + if strings.Contains(extras, "proxy-http2") { + // in case of h2 connect response code is 000 in write out + s.AssertContains(log, "CONNECT tunnel established, response 200") + } else { + s.AssertContains(writeOut, "CONNECT response code: 200") + } s.AssertContains(writeOut, "GET response code: 200") s.AssertNotContains(log, "bytes remaining to read") s.AssertNotContains(log, "Operation timed out") s.AssertNotContains(log, "Upgrade:") + return writeOut, log } -func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string) { - args := fmt.Sprintf("-w @/tmp/write_out_upload_connect --max-time %d --insecure --proxy-insecure -p -x %s -T %s %s", s.maxTimeout, proxyUri, file, uri) +func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string, extraArgs ...string) (string, string) { + extras := "" + if len(extraArgs) > 0 { + extras = strings.Join(extraArgs, " ") + extras += " " + } + args := fmt.Sprintf("%s-w @/tmp/write_out_upload_connect --max-time %d --insecure --proxy-insecure -p -x %s -T %s %s", extras, s.maxTimeout, proxyUri, file, uri) writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) - s.AssertContains(writeOut, "CONNECT response code: 200") + if strings.Contains(extras, "proxy-http2") { + // in case of h2 connect response code is 000 in write out + s.AssertContains(log, "CONNECT tunnel established, response 200") + } else { + s.AssertContains(writeOut, "CONNECT response code: 200") + } s.AssertContains(writeOut, "PUT response code: 201") s.AssertNotContains(log, "Operation timed out") s.AssertNotContains(log, "Upgrade:") + return writeOut, log } func handleConn(conn net.Conn) { @@ -270,3 +312,72 @@ var _ = Describe("VppProxySuiteSolo", Ordered, ContinueOnFailure, Serial, func() } } }) + +var _ = Describe("H2SpecProxySuite", Ordered, ContinueOnFailure, func() { + var s VppProxySuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + testCases := []struct { + desc string + }{ + {desc: "extras/2/1"}, + {desc: "extras/2/2"}, + {desc: "extras/2/3"}, + {desc: "extras/2/4"}, + } + + for _, test := range testCases { + test := test + testName := "proxy_test.go/h2spec_" + strings.ReplaceAll(test.desc, "/", "_") + It(testName, func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + s.SetupNginxServer() + s.ConfigureVppProxy("https", s.Ports.Proxy) + path := fmt.Sprintf("%s:%d", s.ServerAddr(), s.Ports.Server) + conf := &config.Config{ + Host: s.VppProxyAddr(), + Port: int(s.Ports.Proxy), + Path: path, + Timeout: time.Second * time.Duration(s.maxTimeout), + MaxHeaderLen: 4096, + TLS: true, + Insecure: true, + Sections: []string{test.desc}, + Verbose: true, + } + // capture h2spec output so it will be in log + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + tg := h2spec_extras.Spec() + tg.Test(conf) + + oChan := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + oChan <- buf.String() + }() + + // restore to normal state + w.Close() + os.Stdout = oldStdout + o := <-oChan + s.Log(o) + s.AssertEqual(0, tg.FailedCount) + }, SpecTimeout(TestTimeout)) + } + +}) diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index 875d79f757..ef7748b331 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -22,7 +22,8 @@ import ( func init() { RegisterVppProxyTests(VppProxyHttpGetTcpTest, VppProxyHttpGetTlsTest, VppProxyHttpPutTcpTest, VppProxyHttpPutTlsTest, - VppConnectProxyGetTest, VppConnectProxyPutTest, VppHttpsConnectProxyGetTest) + VppConnectProxyGetTest, VppConnectProxyPutTest, VppHttpsConnectProxyGetTest, VppH2ConnectProxyGetTest, + VppH2ConnectProxyPutTest) RegisterVppProxySoloTests(VppProxyHttpGetTcpMTTest, VppProxyHttpPutTcpMTTest, VppProxyTcpIperfMTTest, VppProxyUdpIperfMTTest, VppConnectProxyStressTest, VppConnectProxyStressMTTest, VppConnectProxyConnectionFailedMTTest) RegisterVppUdpProxyTests(VppProxyUdpTest, VppConnectUdpProxyTest, VppConnectUdpInvalidCapsuleTest, @@ -32,20 +33,6 @@ func init() { RegisterNginxProxySoloTests(NginxMirroringTest, MirrorMultiThreadTest) } -func configureVppProxy(s *VppProxySuite, proto string, proxyPort uint16) { - vppProxy := s.Containers.VppProxy.VppInstance - cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri %s://%s:%d", proto, s.VppProxyAddr(), proxyPort) - if proto != "http" && proto != "https" && proto != "udp" { - proto = "tcp" - } - if proto != "http" && proto != "https" { - cmd += fmt.Sprintf(" client-uri %s://%s:%d", proto, s.ServerAddr(), s.Ports.Server) - } - - output := vppProxy.Vppctl(cmd) - s.Log("proxy configured: " + output) -} - func VppProxyHttpGetTcpMTTest(s *VppProxySuite) { VppProxyHttpGetTcpTest(s) } @@ -71,9 +58,9 @@ func vppProxyIperfMTTest(s *VppProxySuite, proto string) { s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Client)) s.AssertNil(vppProxy.CreateTap(s.Interfaces.Client, false, 2, uint32(s.Interfaces.Client.Peer.Index), Consistent_qp)) - configureVppProxy(s, "tcp", s.Ports.Proxy) + s.ConfigureVppProxy("tcp", s.Ports.Proxy) if proto == "udp" { - configureVppProxy(s, "udp", s.Ports.Proxy) + s.ConfigureVppProxy("udp", s.Ports.Proxy) proto = "-u" } else { proto = "" @@ -111,14 +98,14 @@ func vppProxyIperfMTTest(s *VppProxySuite, proto string) { func VppProxyHttpGetTcpTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "tcp", s.Ports.Proxy) + s.ConfigureVppProxy("tcp", s.Ports.Proxy) uri := fmt.Sprintf("http://%s:%d/httpTestFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } func VppProxyHttpGetTlsTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "tls", s.Ports.Proxy) + s.ConfigureVppProxy("tls", s.Ports.Proxy) uri := fmt.Sprintf("https://%s:%d/httpTestFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlDownloadResource(uri) } @@ -129,14 +116,14 @@ func VppProxyHttpPutTcpMTTest(s *VppProxySuite) { func VppProxyHttpPutTcpTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "tcp", s.Ports.Proxy) + s.ConfigureVppProxy("tcp", s.Ports.Proxy) uri := fmt.Sprintf("http://%s:%d/upload/testFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlUploadResource(uri, CurlContainerTestFile) } func VppProxyHttpPutTlsTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "tls", s.Ports.Proxy) + s.ConfigureVppProxy("tls", s.Ports.Proxy) uri := fmt.Sprintf("https://%s:%d/upload/testFile", s.VppProxyAddr(), s.Ports.Proxy) s.CurlUploadResource(uri, CurlContainerTestFile) } @@ -173,7 +160,7 @@ func nginxMirroring(s *NginxProxySuite, multiThreadWorkers bool) { func VppConnectProxyGetTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "http", s.Ports.Proxy) + s.ConfigureVppProxy("http", s.Ports.Proxy) targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server) proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) @@ -182,16 +169,27 @@ func VppConnectProxyGetTest(s *VppProxySuite) { func VppHttpsConnectProxyGetTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "https", s.Ports.Proxy) + s.ConfigureVppProxy("https", s.Ports.Proxy) targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server) proxyUri := fmt.Sprintf("https://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) s.CurlDownloadResourceViaTunnel(targetUri, proxyUri) } +func VppH2ConnectProxyGetTest(s *VppProxySuite) { + s.SetupNginxServer() + s.ConfigureVppProxy("https", s.Ports.Proxy) + + targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server) + proxyUri := fmt.Sprintf("https://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + _, log := s.CurlDownloadResourceViaTunnel(targetUri, proxyUri, "--proxy-http2") + // ALPN result check + s.AssertContains(log, "CONNECT tunnel: HTTP/2 negotiated") +} + func VppConnectProxyConnectionFailedMTTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "http", s.Ports.Proxy) + s.ConfigureVppProxy("http", s.Ports.Proxy) targetUri := fmt.Sprintf("http://%s:%d/httpTestFile", s.ServerAddr(), s.Ports.Server+1) proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) @@ -201,13 +199,24 @@ func VppConnectProxyConnectionFailedMTTest(s *VppProxySuite) { func VppConnectProxyPutTest(s *VppProxySuite) { s.SetupNginxServer() - configureVppProxy(s, "http", s.Ports.Proxy) + s.ConfigureVppProxy("http", s.Ports.Proxy) proxyUri := fmt.Sprintf("http://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) targetUri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ServerAddr(), s.Ports.Server) s.CurlUploadResourceViaTunnel(targetUri, proxyUri, CurlContainerTestFile) } +func VppH2ConnectProxyPutTest(s *VppProxySuite) { + s.SetupNginxServer() + s.ConfigureVppProxy("https", s.Ports.Proxy) + + proxyUri := fmt.Sprintf("https://%s:%d", s.VppProxyAddr(), s.Ports.Proxy) + targetUri := fmt.Sprintf("http://%s:%d/upload/testFile", s.ServerAddr(), s.Ports.Server) + _, log := s.CurlUploadResourceViaTunnel(targetUri, proxyUri, CurlContainerTestFile, "--proxy-http2") + // ALPN result check + s.AssertContains(log, "CONNECT tunnel: HTTP/2 negotiated") +} + func vppConnectProxyStressLoad(s *VppProxySuite, proxyPort string) { var ( connectError, timeout, readError, writeError, invalidData, total atomic.Uint32 @@ -320,7 +329,7 @@ func VppConnectProxyStressTest(s *VppProxySuite) { remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() - configureVppProxy(s, "http", s.Ports.Proxy) + s.ConfigureVppProxy("http", s.Ports.Proxy) // no goVPP less noise s.Containers.VppProxy.VppInstance.Disconnect() @@ -340,7 +349,7 @@ func VppConnectProxyStressMTTest(s *VppProxySuite) { s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Client)) s.AssertNil(vppProxy.CreateTap(s.Interfaces.Client, false, 2, uint32(s.Interfaces.Client.Peer.Index), Consistent_qp)) - configureVppProxy(s, "http", s.Ports.Proxy) + s.ConfigureVppProxy("http", s.Ports.Proxy) // no goVPP less noise vppProxy.Disconnect() diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index c8bdc73a41..445235fec8 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -1282,8 +1282,6 @@ proxy_server_listen () &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, sizeof (transport_endpt_crypto_cfg_t)); ext_cfg->crypto.ckpair_index = pm->ckpair_index; - /* TODO: remove when http/2 connect done */ - ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; } } else diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index f9f281f8b9..9cf81816ae 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -7,6 +7,7 @@ #include #include #include +#include #define HTTP2_WIN_SIZE_MAX 0x7FFFFFFF #define HTTP2_INITIAL_WIN_SIZE 65535 @@ -58,6 +59,8 @@ typedef struct http2_req_ clib_llist_anchor_t sched_list; void (*dispatch_headers_cb) (struct http2_req_ *req, http_conn_t *hc, u8 *n_emissions, clib_llist_index_t *next_ri); + void (*dispatch_data_cb) (struct http2_req_ *req, http_conn_t *hc, + u8 *n_emissions); } http2_req_t; #define foreach_http2_conn_flags \ @@ -381,6 +384,18 @@ http2_send_stream_error (http_conn_t *hc, u32 stream_id, http2_error_t error, http_io_ts_after_write (hc, 1); } +always_inline void +http2_tunnel_send_close (http_conn_t *hc, http2_req_t *req) +{ + u8 *response; + + response = http_get_tx_buf (hc); + http2_frame_write_data_header (0, req->stream_id, + HTTP2_FRAME_FLAG_END_STREAM, response); + http_io_ts_write (hc, response, HTTP2_FRAME_HEADER_SIZE, 0); + http_io_ts_after_write (hc, 1); +} + /* send RST_STREAM frame and notify app */ always_inline void http2_stream_error (http_conn_t *hc, http2_req_t *req, http2_error_t error, @@ -458,6 +473,150 @@ http2_send_server_preface (http_conn_t *hc) /* stream TX scheduler */ /***********************/ +static void +http2_sched_dispatch_data (http2_req_t *req, http_conn_t *hc, u8 *n_emissions) +{ + u32 max_write, max_read, n_segs, n_read, n_written = 0; + svm_fifo_seg_t *app_segs, *segs = 0; + http_buffer_t *hb = &req->base.tx_buf; + u8 fh[HTTP2_FRAME_HEADER_SIZE]; + u8 finished = 0, flags = 0; + http2_conn_ctx_t *h2c; + + ASSERT (http_buffer_bytes_left (hb) > 0); + + *n_emissions += hb->type == HTTP_BUFFER_PTR ? HTTP2_SCHED_WEIGHT_DATA_PTR : + HTTP2_SCHED_WEIGHT_DATA_INLINE; + + h2c = http2_conn_ctx_get_w_thread (hc); + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, (u32) req->peer_window); + max_write = clib_min (max_write, h2c->peer_window); + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); + + max_read = http_buffer_bytes_left (hb); + + n_read = http_buffer_get_segs (hb, max_write, &app_segs, &n_segs); + if (n_read == 0) + { + HTTP_DBG (1, "no data to deq"); + transport_connection_reschedule (&req->base.connection); + return; + } + + finished = (max_read - n_read) == 0; + flags = finished ? HTTP2_FRAME_FLAG_END_STREAM : 0; + http2_frame_write_data_header (n_read, req->stream_id, flags, fh); + vec_validate (segs, 0); + segs[0].len = HTTP2_FRAME_HEADER_SIZE; + segs[0].data = fh; + vec_append (segs, app_segs); + + n_written = http_io_ts_write_segs (hc, segs, n_segs + 1, 0); + n_written -= HTTP2_FRAME_HEADER_SIZE; + vec_free (segs); + http_buffer_drain (hb, n_written); + req->peer_window -= n_written; + h2c->peer_window -= n_written; + + if (finished) + { + /* all done, close stream */ + http_buffer_free (hb); + if (hc->flags & HTTP_CONN_F_IS_SERVER) + http2_stream_close (req, hc); + else + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + } + else + { + if (req->peer_window == 0) + { + /* mark that we need window update on stream */ + HTTP_DBG (1, "stream window is full"); + req->flags |= HTTP2_REQ_F_NEED_WINDOW_UPDATE; + } + else + { + /* schedule for next round */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + http_io_as_dequeue_notify (&req->base, n_written); + } + } + + http_io_ts_after_write (hc, finished); +} + +static void +http2_sched_dispatch_tunnel (http2_req_t *req, http_conn_t *hc, + u8 *n_emissions) +{ + u32 max_write, max_read, n_segs = 2, n_read, n_written = 0; + svm_fifo_seg_t segs[n_segs + 1]; + u8 fh[HTTP2_FRAME_HEADER_SIZE]; + u8 flags = 0; + http2_conn_ctx_t *h2c; + + *n_emissions += HTTP2_SCHED_WEIGHT_DATA_INLINE; + + h2c = http2_conn_ctx_get_w_thread (hc); + + max_read = http_io_as_max_read (&req->base); + if (max_read == 0) + { + HTTP_DBG (2, "max_read == 0"); + transport_connection_reschedule (&req->base.connection); + return; + } + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, (u32) req->peer_window); + max_write = clib_min (max_write, h2c->peer_window); + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); + n_read = clib_min (max_write, max_read); + + if (req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED && + max_write >= max_read) + { + HTTP_DBG (1, "closing tunnel"); + session_transport_closed_notify (&req->base.connection); + flags = HTTP2_FRAME_FLAG_END_STREAM; + } + http2_frame_write_data_header (n_read, req->stream_id, flags, fh); + segs[0].len = HTTP2_FRAME_HEADER_SIZE; + segs[0].data = fh; + + http_io_as_read_segs (&req->base, segs + 1, &n_segs, n_read); + + n_written = http_io_ts_write_segs (hc, segs, n_segs + 1, 0); + n_written -= HTTP2_FRAME_HEADER_SIZE; + http_io_as_drain (&req->base, n_written); + req->peer_window -= n_written; + h2c->peer_window -= n_written; + + if (req->peer_window == 0) + { + /* mark that we need window update on stream */ + HTTP_DBG (1, "stream window is full"); + req->flags |= HTTP2_REQ_F_NEED_WINDOW_UPDATE; + } + else if (max_read - n_written) + { + /* schedule for next round if we have more data */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + transport_connection_reschedule (&req->base.connection); + + http_io_ts_after_write (hc, 0); +} + static void http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, u8 *n_emissions, @@ -502,6 +661,7 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, if (http_buffer_bytes_left (&req->base.tx_buf)) { /* start sending the actual data */ + req->dispatch_data_cb = http2_sched_dispatch_data; HTTP_DBG (1, "adding to data queue req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); http2_req_schedule_data_tx (hc, req); @@ -567,14 +727,19 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, stream_id = req->stream_id; + /* tunnel established if 2xx (Successful) response to CONNECT */ + req->base.is_tunnel = + (req->base.is_tunnel && http_status_code_str[msg.code][0] == '2'); + /* END_STREAM flag need to be set in HEADERS frame */ if (msg.data.body_len) { + ASSERT (req->base.is_tunnel == 0); http_req_tx_buffer_init (&req->base, &msg); http_io_as_dequeue_notify (&req->base, n_deq); } else - flags |= HTTP2_FRAME_FLAG_END_STREAM; + flags |= req->base.is_tunnel ? 0 : HTTP2_FRAME_FLAG_END_STREAM; if (headers_len <= max_write) { @@ -584,12 +749,23 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, if (msg.data.body_len) { /* start sending the actual data */ + req->dispatch_data_cb = http2_sched_dispatch_data; HTTP_DBG (1, "adding to data queue req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); http2_req_schedule_data_tx (hc, req); } + else if (req->base.is_tunnel) + { + req->dispatch_data_cb = http2_sched_dispatch_tunnel; + transport_connection_reschedule (&req->base.connection); + /* cleanup some stuff we don't need anymore in tunnel mode */ + vec_free (req->base.headers); + } else - http2_stream_close (req, hc); + { + /* otherwise we are done */ + http2_stream_close (req, hc); + } } else { @@ -616,84 +792,6 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, http_io_ts_after_write (hc, 0); } -static void -http2_sched_dispatch_data (http2_req_t *req, http_conn_t *hc, u8 *n_emissions) -{ - u32 max_write, max_read, n_segs, n_read, n_written = 0; - svm_fifo_seg_t *app_segs, *segs = 0; - http_buffer_t *hb = &req->base.tx_buf; - u8 fh[HTTP2_FRAME_HEADER_SIZE]; - u8 finished = 0, flags = 0; - http2_conn_ctx_t *h2c; - - ASSERT (http_buffer_bytes_left (hb) > 0); - - *n_emissions += hb->type == HTTP_BUFFER_PTR ? HTTP2_SCHED_WEIGHT_DATA_PTR : - HTTP2_SCHED_WEIGHT_DATA_INLINE; - - h2c = http2_conn_ctx_get_w_thread (hc); - - max_write = http_io_ts_max_write (hc, 0); - max_write -= HTTP2_FRAME_HEADER_SIZE; - max_write = clib_min (max_write, (u32) req->peer_window); - max_write = clib_min (max_write, h2c->peer_window); - max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); - - max_read = http_buffer_bytes_left (hb); - - n_read = http_buffer_get_segs (hb, max_write, &app_segs, &n_segs); - if (n_read == 0) - { - HTTP_DBG (1, "no data to deq"); - transport_connection_reschedule (&req->base.connection); - return; - } - - finished = (max_read - n_read) == 0; - flags = finished ? HTTP2_FRAME_FLAG_END_STREAM : 0; - http2_frame_write_data_header (n_read, req->stream_id, flags, fh); - vec_validate (segs, 0); - segs[0].len = HTTP2_FRAME_HEADER_SIZE; - segs[0].data = fh; - vec_append (segs, app_segs); - - n_written = http_io_ts_write_segs (hc, segs, n_segs + 1, 0); - ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + n_read)); - vec_free (segs); - http_buffer_drain (hb, n_read); - req->peer_window -= n_read; - h2c->peer_window -= n_read; - - if (finished) - { - /* all done, close stream */ - http_buffer_free (hb); - if (hc->flags & HTTP_CONN_F_IS_SERVER) - http2_stream_close (req, hc); - else - req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; - } - else - { - if (req->peer_window == 0) - { - /* mark that we need window update on stream */ - HTTP_DBG (1, "stream window is full"); - req->flags |= HTTP2_REQ_F_NEED_WINDOW_UPDATE; - } - else - { - /* schedule for next round */ - HTTP_DBG (1, "adding to data queue req_index %x", - ((http_req_handle_t) req->base.hr_req_handle).req_index); - http2_req_schedule_data_tx (hc, req); - http_io_as_dequeue_notify (&req->base, n_read); - } - } - - http_io_ts_after_write (hc, finished); -} - static void http2_update_time_callback (f64 now, u8 thread_index) { @@ -755,7 +853,7 @@ http2_update_time_callback (f64 now, u8 thread_index) 1, "sending data req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); clib_llist_remove (wrk->req_pool, sched_list, req); - http2_sched_dispatch_data (req, hc, &n_emissions); + req->dispatch_data_cb (req, hc, &n_emissions); if (ri == old_ti) break; @@ -791,6 +889,7 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, http2_conn_ctx_t *h2c; hpack_request_control_data_t control_data; http_msg_t msg; + u8 *p; int rv; http_req_state_t new_state = HTTP_REQ_STATE_WAIT_APP_REPLY; http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); @@ -816,8 +915,7 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); return HTTP_SM_STOP; } - if (control_data.method == HTTP_REQ_UNKNOWN || - control_data.method == HTTP_REQ_CONNECT) + if (control_data.method == HTTP_REQ_UNKNOWN) { HTTP_DBG (1, "unsupported method"); http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); @@ -843,13 +941,46 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); return HTTP_SM_STOP; } - if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_AUTHORITY_PARSED) && - control_data.method != HTTP_REQ_CONNECT) + if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_AUTHORITY_PARSED)) { - HTTP_DBG (1, ":path pseudo-header missing in request"); + HTTP_DBG (1, ":authority pseudo-header missing in request"); http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); return HTTP_SM_STOP; } + if (control_data.method == HTTP_REQ_CONNECT) + { + if (control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_SCHEME_PARSED || + control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) + { + HTTP_DBG (1, ":scheme and :path pseudo-header must be omitted for " + "CONNECT method"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + /* quick check if port is present */ + p = control_data.authority + control_data.authority_len; + p--; + if (!isdigit (*p)) + { + HTTP_DBG (1, "port not present in authority"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + p--; + for (; p > control_data.authority; p--) + { + if (!isdigit (*p)) + break; + } + if (*p != ':') + { + HTTP_DBG (1, "port not present in authority"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + req->base.is_tunnel = 1; + http_io_as_add_want_read_ntf (&req->base); + } req->base.control_data_len = control_data.control_data_len; req->base.headers_offset = control_data.headers - wrk->header_list; @@ -868,7 +999,9 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, http_io_as_add_want_read_ntf (&req->base); } /* TODO: message framing without content length using END_STREAM flag */ - if (req->base.body_len == 0 && req->stream_state == HTTP2_STREAM_STATE_OPEN) + if (req->base.body_len == 0 && + req->stream_state == HTTP2_STREAM_STATE_OPEN && + control_data.method != HTTP_REQ_CONNECT) { HTTP_DBG (1, "no content-length and DATA frame expected"); *error = HTTP2_ERROR_INTERNAL_ERROR; @@ -876,14 +1009,22 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, } req->base.to_recv = req->base.body_len; - req->base.target_path_len = control_data.path_len; - req->base.target_path_offset = control_data.path - wrk->header_list; - /* drop leading slash */ - req->base.target_path_offset++; - req->base.target_path_len--; req->base.target_query_offset = 0; req->base.target_query_len = 0; - http_identify_optional_query (&req->base, wrk->header_list); + if (control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) + { + req->base.target_path_len = control_data.path_len; + req->base.target_path_offset = control_data.path - wrk->header_list; + /* drop leading slash */ + req->base.target_path_offset++; + req->base.target_path_len--; + http_identify_optional_query (&req->base, wrk->header_list); + } + else + { + req->base.target_path_len = 0; + req->base.target_path_offset = 0; + } msg.type = HTTP_MSG_REQUEST; msg.method_type = control_data.method; @@ -954,6 +1095,27 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } +static http_sm_result_t +http2_req_state_tunnel_rx (http_conn_t *hc, http2_req_t *req, + transport_send_params_t *sp, http2_error_t *error) +{ + u32 max_enq; + + HTTP_DBG (1, "tunnel received data from client"); + + max_enq = http_io_as_max_write (&req->base); + if (max_enq < req->payload_len) + { + clib_warning ("app's rx fifo full"); + http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, sp); + return HTTP_SM_STOP; + } + http_io_as_write (&req->base, req->payload, req->payload_len); + http_app_worker_rx_notify (&req->base); + + return HTTP_SM_STOP; +} + /*************************************/ /* request state machine handlers TX */ /*************************************/ @@ -977,7 +1139,9 @@ http2_req_state_wait_app_reply (http_conn_t *hc, http2_req_t *req, clib_llist_add_tail (wrk->req_pool, sched_list, req, he); http2_conn_schedule (h2c, hc->c_thread_index); - http_req_state_change (&req->base, HTTP_REQ_STATE_APP_IO_MORE_DATA); + http_req_state_change (&req->base, req->base.is_tunnel ? + HTTP_REQ_STATE_TUNNEL : + HTTP_REQ_STATE_APP_IO_MORE_DATA); http_req_deschedule (&req->base, sp); return HTTP_SM_STOP; @@ -1005,6 +1169,29 @@ http2_req_state_app_io_more_data (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } +static http_sm_result_t +http2_req_state_tunnel_tx (http_conn_t *hc, http2_req_t *req, + transport_send_params_t *sp, http2_error_t *error) +{ + http2_conn_ctx_t *h2c; + + ASSERT (!clib_llist_elt_is_linked (req, sched_list)); + + HTTP_DBG (1, "tunnel received data from target"); + + /* add data back to stream scheduler */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + h2c = http2_conn_ctx_get_w_thread (hc); + if (h2c->peer_window > 0) + http2_conn_schedule (h2c, hc->c_thread_index); + + http_req_deschedule (&req->base, sp); + + return HTTP_SM_STOP; +} + /*************************/ /* request state machine */ /*************************/ @@ -1022,7 +1209,7 @@ static http2_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { 0, /* wait transport method */ http2_req_state_wait_app_reply, http2_req_state_app_io_more_data, - 0, /* tunnel */ + http2_req_state_tunnel_tx, 0, /* udp tunnel */ }; @@ -1034,7 +1221,7 @@ static http2_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { http2_req_state_wait_transport_method, 0, /* wait app reply */ 0, /* app io more data */ - 0, /* tunnel */ + http2_req_state_tunnel_rx, 0, /* udp tunnel */ }; @@ -1268,7 +1455,17 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) } if (fh->flags & HTTP2_FRAME_FLAG_END_STREAM) - req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + { + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + if (req->base.is_tunnel) + { + session_transport_closing_notify (&req->base.connection); + HTTP_DBG (1, "client closed tunnel"); + /* final DATA frame could be empty */ + if (fh->length == 0) + return HTTP2_ERROR_NO_ERROR; + } + } rx_buf = http_get_rx_buf (hc); vec_validate (rx_buf, fh->length - 1); @@ -1687,7 +1884,6 @@ static void http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, clib_thread_index_t thread_index) { - /* TODO: continue tunnel RX */ http2_req_t *req; u8 *response; u32 increment; @@ -1707,6 +1903,8 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, response = http_get_tx_buf (hc); increment = http_io_as_max_write (&req->base) - req->our_window; HTTP_DBG (1, "stream window increment %u", increment); + if (increment == 0) + return; req->our_window += increment; http2_frame_write_window_update (increment, req->stream_id, &response); http_io_ts_write (hc, response, vec_len (response), 0); @@ -1735,6 +1933,34 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, HTTP_DBG (1, "nothing more to send, confirm close"); session_transport_closed_notify (&req->base.connection); } + else if (req->base.is_tunnel) + { + switch (req->stream_state) + { + case HTTP2_STREAM_STATE_OPEN: + HTTP_DBG (1, "proxy closing connection"); + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + if (http_io_as_max_read (&req->base)) + { + HTTP_DBG (1, "wait for all data to be written to ts"); + req->flags |= HTTP2_REQ_F_APP_CLOSED; + } + else + { + HTTP_DBG (1, "nothing more to send, closing tunnel"); + http2_tunnel_send_close (hc, req); + } + break; + case HTTP2_STREAM_STATE_HALF_CLOSED: + HTTP_DBG (1, "proxy confirmed close"); + http2_tunnel_send_close (hc, req); + session_transport_closed_notify (&req->base.connection); + break; + default: + session_transport_closed_notify (&req->base.connection); + break; + } + } else { HTTP_DBG (1, "wait for all data to be written to ts"); From 16189588ddc93a4c021fcd3082aaa86195e5339b Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 10 Jun 2025 20:02:20 -0700 Subject: [PATCH 051/313] http: remove spurious h2 init message Type: improvement Change-Id: I25b0911163a6b6bf8a4f013acc047e5ad3458dc6 Signed-off-by: Florin Coras --- src/plugins/http/http2/http2.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 9cf81816ae..d1b6915ac1 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2359,7 +2359,6 @@ http2_init (vlib_main_t *vm) { http2_main_t *h2m = &http2_main; - clib_warning ("http/2 enabled"); h2m->settings = http2_default_conn_settings; h2m->settings.max_concurrent_streams = 100; /* by default unlimited */ h2m->settings.max_header_list_size = 1 << 14; /* by default unlimited */ From 18bc76d6d5d4b6d947a0ac5851e009ba63ce50fd Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 10 Jun 2025 18:35:09 -0400 Subject: [PATCH 052/313] hs-test: ConnectUdpClient fix deadline in Dial Type: test Change-Id: If2c3061e2c7349c4fd785d6cf417e9b2f0b387a0 Signed-off-by: Matus Fabian --- extras/hs-test/infra/connect_udp_client.go | 2 +- extras/hs-test/proxy_test.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/extras/hs-test/infra/connect_udp_client.go b/extras/hs-test/infra/connect_udp_client.go index a30ad3a769..237b758cdc 100644 --- a/extras/hs-test/infra/connect_udp_client.go +++ b/extras/hs-test/infra/connect_udp_client.go @@ -61,7 +61,7 @@ func (c *ConnectUdpClient) Dial(proxyAddress, targetUri string) error { c.suite.Log("* Connected to proxy") } - conn.SetDeadline(time.Now().Add(time.Second * c.timeout)) + conn.SetDeadline(time.Now().Add(c.timeout)) _, err = conn.Write(req) if err != nil { return err diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index ef7748b331..0eed1d8c92 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -650,7 +650,6 @@ func VppConnectUdpStressMTTest(s *VppUdpProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - vppProxy.Disconnect() cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri http://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) From 64762303a121992840be66c73a18818e13aa637c Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 12 Jun 2025 03:51:46 -0400 Subject: [PATCH 053/313] af_packet: include if name in fanout id Useful when running multiple vpps with veth interfaces with same device ids and multiple rx/tx queues configured Type: improvement Change-Id: I9fd23ad941883850f694c973db8cb8345dd901c2 Signed-off-by: Florin Coras --- src/plugins/af_packet/af_packet.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/plugins/af_packet/af_packet.c b/src/plugins/af_packet/af_packet.c index 56d90c0c98..86c2685870 100644 --- a/src/plugins/af_packet/af_packet.c +++ b/src/plugins/af_packet/af_packet.c @@ -434,6 +434,14 @@ create_packet_sock (int host_if_index, tpacket_req_u_t *rx_req, return ret; } +static u32 +af_packet_make_fanout_id (af_packet_if_t *apif) +{ + u16 if_hash = + hash_memory (apif->host_if_name, strlen ((char *) apif->host_if_name), 0); + return (apif->dev_instance & 0xffff) ^ (if_hash & 0xff00); +} + int af_packet_queue_init (vlib_main_t *vm, af_packet_if_t *apif, af_packet_create_if_arg_t *arg, @@ -506,9 +514,9 @@ af_packet_queue_init (vlib_main_t *vm, af_packet_if_t *apif, if (rx_queue || tx_queue) { - ret = - create_packet_sock (apif->host_if_index, rx_req, tx_req, &fd, &ring, - apif->dev_instance, &arg->flags, apif->version); + ret = create_packet_sock (apif->host_if_index, rx_req, tx_req, &fd, + &ring, af_packet_make_fanout_id (apif), + &arg->flags, apif->version); if (ret != 0) goto error; From 3c562cd1b8b43c8ba01b125146e26f7faf4e1afe Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 10 Jun 2025 01:14:30 -0400 Subject: [PATCH 054/313] hs-test: support for multiple rx/tx af_packet queues Type: test Change-Id: I13d12ea2558fa6ef3849a69a85d970256af52031 Signed-off-by: Florin Coras --- extras/hs-test/infra/netconfig.go | 26 ++++++++++++++++++++++++++ extras/hs-test/infra/suite_ldp.go | 8 ++++++-- extras/hs-test/infra/vppinstance.go | 20 +++++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/extras/hs-test/infra/netconfig.go b/extras/hs-test/infra/netconfig.go index d263b4b43e..c8c2719076 100644 --- a/extras/hs-test/infra/netconfig.go +++ b/extras/hs-test/infra/netconfig.go @@ -177,6 +177,18 @@ func (n *NetInterface) configureUpState() error { return nil } +func (n *NetInterface) configureMultiQueue() error { + // TODO multiqueue for tap + if n.Type() != Veth { + return nil + } + err := linkSetMultiQueue(n.Name()) + if err != nil { + return fmt.Errorf("set multiqueue failed: %v", err) + } + return nil +} + func (n *NetInterface) configureNetworkNamespace() error { if n.NetworkNamespace != "" { err := linkSetNetns(n.name, n.NetworkNamespace) @@ -212,6 +224,10 @@ func (n *NetInterface) configure() error { return err } + if err := n.configureMultiQueue(); err != nil { + return err + } + if err := n.configureNetworkNamespace(); err != nil { return err } @@ -392,6 +408,16 @@ func linkSetNetns(ifName, ns string) error { return nil } +func linkSetMultiQueue(ifName string) error { + cmd := exec.Command("ethtool", "-L", ifName, "rx", "4", "tx", "4") + fmt.Println("configuring multiqueue for interface:", cmd.String()) + err := cmd.Run() + if err != nil { + return fmt.Errorf("error configuring multiqueue '%s: %v", ifName, err) + } + return nil +} + func newCommand(s []string, ns string) *exec.Cmd { return appendNetns(s, ns) } diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index 78c9e14892..76f4289dbe 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -136,7 +136,9 @@ func (s *LdpSuite) SetupServerVpp(serverContainer *Container) { serverVpp := serverContainer.VppInstance s.AssertNil(serverVpp.Start()) - idx, err := serverVpp.createAfPacket(s.Interfaces.Server, false) + numCpus := uint16(len(serverContainer.AllocatedCpus)) + numWorkers := uint16(max(numCpus-1, 1)) + idx, err := serverVpp.createAfPacket(s.Interfaces.Server, false, WithNumRxQueues(numWorkers), WithNumTxQueues(numCpus)) s.AssertNil(err, fmt.Sprint(err)) s.AssertNotEqual(0, idx) } @@ -145,7 +147,9 @@ func (s *LdpSuite) setupClientVpp(clientContainer *Container) { clientVpp := clientContainer.VppInstance s.AssertNil(clientVpp.Start()) - idx, err := clientVpp.createAfPacket(s.Interfaces.Client, false) + numCpus := uint16(len(clientContainer.AllocatedCpus)) + numWorkers := uint16(max(numCpus-1, 1)) + idx, err := clientVpp.createAfPacket(s.Interfaces.Client, false, WithNumRxQueues(numWorkers), WithNumTxQueues(numCpus)) s.AssertNil(err, fmt.Sprint(err)) s.AssertNotEqual(0, idx) } diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go index ec32f85bc4..7acf8aeca9 100644 --- a/extras/hs-test/infra/vppinstance.go +++ b/extras/hs-test/infra/vppinstance.go @@ -321,7 +321,21 @@ func (vpp *VppInstance) WaitForApp(appName string, timeout int) { vpp.getSuite().AssertNil(1, "Timeout while waiting for app '%s'", appName) } -func (vpp *VppInstance) createAfPacket(veth *NetInterface, IPv6 bool) (interface_types.InterfaceIndex, error) { +type AfPacketOption func(*af_packet.AfPacketCreateV3) + +func WithNumRxQueues(numRxQueues uint16) AfPacketOption { + return func(cfg *af_packet.AfPacketCreateV3) { + cfg.NumRxQueues = numRxQueues + } +} + +func WithNumTxQueues(numTxQueues uint16) AfPacketOption { + return func(cfg *af_packet.AfPacketCreateV3) { + cfg.NumTxQueues = numTxQueues + } +} + +func (vpp *VppInstance) createAfPacket(veth *NetInterface, IPv6 bool, opts ...AfPacketOption) (interface_types.InterfaceIndex, error) { var ipAddress string var err error @@ -362,6 +376,10 @@ func (vpp *VppInstance) createAfPacket(veth *NetInterface, IPv6 bool) (interface createReq.HwAddr = veth.HwAddress } + // Apply all optional configs + for _, opt := range opts { + opt(createReq) + } vpp.getSuite().Log("create af-packet interface " + veth.Name()) if err := vpp.ApiStream.SendMsg(createReq); err != nil { vpp.getSuite().HstFail() From e21142ad92e49720a55fd3f466de2eaf8fcb1790 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Tue, 10 Jun 2025 15:33:35 +0200 Subject: [PATCH 055/313] hs-test: support for multiple multi-core containers - framework will now allocate multiple cores for every vpp container when running an MTTest - added missing test timeout in H2SpecSuite Type: improvement Change-Id: I31317560b54b494ab14c8b5f4d7caed9fd3315b0 Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 20 +++-- extras/hs-test/README.rst | 6 +- extras/hs-test/hs_test.sh | 3 + extras/hs-test/http2_test.go | 6 +- extras/hs-test/http_test.go | 11 ++- extras/hs-test/infra/cpu.go | 54 +++++++----- extras/hs-test/infra/hst_suite.go | 94 ++++++++------------- extras/hs-test/infra/suite_cpu_pinning.go | 12 +-- extras/hs-test/infra/suite_h2.go | 35 +++++++- extras/hs-test/infra/suite_ldp.go | 34 ++++++++ extras/hs-test/infra/suite_no_topo.go | 33 ++++++++ extras/hs-test/infra/suite_vpp_proxy.go | 35 +++++++- extras/hs-test/infra/suite_vpp_udp_proxy.go | 33 ++++++++ extras/hs-test/ldp_test.go | 3 +- extras/hs-test/proxy_test.go | 44 +++++++--- 15 files changed, 303 insertions(+), 120 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 24026fc247..b02c499194 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -33,6 +33,10 @@ ifeq ($(CPUS),) CPUS=1 endif +ifeq ($(VPP_CPUS),) +VPP_CPUS=1 +endif + ifeq ($(PARALLEL),) PARALLEL=1 endif @@ -96,9 +100,10 @@ help: @echo " DEBUG=[true|false] - attach VPP to GDB" @echo " TEST=[name1,name2...] - specific test(s) to run" @echo " SKIP=[name1,name2...] - specific test(s) to skip" - @echo " CPUS=[n-cpus] - number of cpus to allocate to VPP and containers" + @echo " CPUS=[n] - number of cpus to allocate to each non-VPP container (default = 1)" + @echo " VPP_CPUS=[n] - number of cpus to allocate to each VPP container (default = 1)" @echo " VPPSRC=[path-to-vpp-src] - path to vpp source files (for gdb)" - @echo " PARALLEL=[n-cpus] - number of test processes to spawn to run in parallel" + @echo " PARALLEL=[n] - number of test processes to spawn to run in parallel" @echo " REPEAT=[n] - repeat tests up to N times or until a failure occurs" @echo " CPU0=[true|false] - use cpu0" @echo " DRYRUN=[true|false] - set up containers but don't run tests" @@ -137,7 +142,8 @@ test: .deps.ok .build.ok @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --cpu0=$(CPU0) \ - --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT); \ + --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT) \ + --vpp_cpus=$(VPP_CPUS); \ ./script/compress.sh $$? .PHONY: test-debug @@ -146,7 +152,8 @@ test-debug: .deps.ok .build_debug.ok @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true \ - --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT); \ + --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT) \ + --vpp_cpus=$(VPP_CPUS); \ ./script/compress.sh $$? .PHONY: wipe-lcov @@ -159,13 +166,14 @@ test-cov: .deps.ok .build.cov.ok wipe-lcov -@bash ./hs_test.sh --coverage=true --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST-HS) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) \ - --timeout=$(TIMEOUT); \ + --timeout=$(TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ ./script/compress.sh $$? $(MAKE) -C ../.. test-cov-post-standalone HS_TEST=1 .PHONY: test-leak test-leak: .deps.ok .build_debug.ok - @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) + @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) \ + --vpp_cpus=$(VPP_CPUS); .PHONY: test-perf test-perf: FORCE_BUILD=false diff --git a/extras/hs-test/README.rst b/extras/hs-test/README.rst index ca9f1f9e55..815f8aa8e6 100644 --- a/extras/hs-test/README.rst +++ b/extras/hs-test/README.rst @@ -67,9 +67,8 @@ For adding a new suite, please see `Modifying the framework`_ below. Assumed are two docker containers, each with its own VPP instance running. One VPP then pings the other. This can be put in file ``extras/hs-test/my_test.go`` and run with command ``make test TEST=MyTest``. -To add a multi-worker test, name it ``[name]MTTest``. Doing this, the framework will allocate 3 CPUs to a VPP container, no matter what ``CPUS`` is set to. -Only a single multi-worker VPP container is supported for now. Please register multi-worker tests as Solo tests to avoid reusing the same cores -when running in parallel. +To add a multi-worker test, register it to a multi-worker suite. The suite *cannot* have ``s.SetupTest()`` in the ``BeforeEach`` block. +Set your desired core counts using ``s.CpusPerContainer`` and/or ``s.CpusPerVppContainer`` and run ``s.SetupTest()`` at the beginning of your test. :: @@ -81,7 +80,6 @@ when running in parallel. func init(){ RegisterMySuiteTests(MyTest) - RegisterSoloMySuiteTests(MyMTTest) } func MyMTTest(s *MySuite){ diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index 8be73a25c4..eb3607ca89 100644 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -64,6 +64,9 @@ case "${i}" in --cpus=*) args="$args -cpus ${i#*=}" ;; + --vpp_cpus=*) + args="$args -vpp_cpus ${i#*=}" + ;; --vppsrc=*) args="$args -vppsrc ${i#*=}" ;; diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 0d170eb121..a813f414b5 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -11,7 +11,7 @@ import ( func init() { RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest) - RegisterH2SoloTests(Http2MultiplexingMTTest) + RegisterH2MWTests(Http2MultiplexingMWTest) } func Http2TcpGetTest(s *H2Suite) { @@ -79,7 +79,9 @@ func Http2MultiplexingTest(s *H2Suite) { s.AssertContains(o, " 0 timeout") } -func Http2MultiplexingMTTest(s *H2Suite) { +func Http2MultiplexingMWTest(s *H2Suite) { + s.CpusPerVppContainer = 3 + s.SetupTest() vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 12811cbd38..8b69072a60 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -41,7 +41,8 @@ func init() { HttpStaticRedirectTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, - PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest, HttpClientGetRepeatMTTest, HttpClientPtrGetRepeatMTTest) + PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest) + RegisterNoTopoMWTests(HttpClientGetRepeatMWTest, HttpClientPtrGetRepeatMWTest) RegisterNoTopo6SoloTests(HttpClientGetResponseBody6Test, HttpClientGetTlsResponseBody6Test) } @@ -591,11 +592,15 @@ func httpClientGet6(s *NoTopo6Suite, response string, size int, proto string) { s.AssertContains(file_contents, response) } -func HttpClientGetRepeatMTTest(s *NoTopoSuite) { +func HttpClientGetRepeatMWTest(s *NoTopoSuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() httpClientRepeat(s, "", "sessions 2") } -func HttpClientPtrGetRepeatMTTest(s *NoTopoSuite) { +func HttpClientPtrGetRepeatMWTest(s *NoTopoSuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() httpClientRepeat(s, "", "use-ptr sessions 2") } diff --git a/extras/hs-test/infra/cpu.go b/extras/hs-test/infra/cpu.go index d6ae14e7f4..589c51d96c 100644 --- a/extras/hs-test/infra/cpu.go +++ b/extras/hs-test/infra/cpu.go @@ -9,7 +9,6 @@ import ( "strings" . "fd.io/hs-test/infra/common" - . "github.com/onsi/ginkgo/v2" ) var CgroupPath = "/sys/fs/cgroup/" @@ -20,8 +19,11 @@ type CpuContext struct { } type CpuAllocatorT struct { - cpus []int - maxContainerCount int + cpus []int + numa0 []int + numa1 []int + lastCpu int + suite *HstSuite } func iterateAndAppend(start int, end int, slice []int) []int { @@ -33,27 +35,40 @@ func iterateAndAppend(start int, end int, slice []int) []int { var cpuAllocator *CpuAllocatorT = nil -func (c *CpuAllocatorT) Allocate(containerCount int, nCpus int, offset int) (*CpuContext, error) { +func (c *CpuAllocatorT) Allocate(nCpus int, offset int) (*CpuContext, error) { var cpuCtx CpuContext // indexes, not actual cores var minCpu, maxCpu int - minCpu = ((GinkgoParallelProcess() - 1) * c.maxContainerCount * nCpus) + offset - maxCpu = (GinkgoParallelProcess() * c.maxContainerCount * nCpus) - 1 + offset + minCpu = offset + maxCpu = nCpus - 1 + offset if len(c.cpus)-1 < maxCpu { - err := fmt.Errorf("could not allocate %d CPUs; available count: %d; attempted to allocate cores with index %d-%d; max index: %d;\n"+ - "available cores: %v", nCpus*containerCount, len(c.cpus), minCpu, maxCpu, len(c.cpus)-1, c.cpus) + msg := fmt.Sprintf("could not allocate %d CPUs; available count: %d; attempted to allocate cores with index %d-%d; max index: %d;\n"+ + "available cores: %v", nCpus, len(c.cpus), minCpu, maxCpu, len(c.cpus)-1, c.cpus) + if c.suite.SkipIfNotEnoguhCpus { + c.suite.Skip("skipping: " + msg) + } + err := fmt.Errorf(msg) return nil, err } - if containerCount == 1 { - cpuCtx.cpus = c.cpus[minCpu : minCpu+nCpus] - } else if containerCount > 1 && containerCount <= c.maxContainerCount { - cpuCtx.cpus = c.cpus[minCpu+(nCpus*(containerCount-1)) : minCpu+(nCpus*containerCount)] + if NumaAwareCpuAlloc { + if len(c.numa0) > maxCpu { + c.suite.Log("Allocating CPUs from numa #0") + cpuCtx.cpus = c.numa0[minCpu : minCpu+nCpus] + } else if len(c.numa1) > maxCpu { + c.suite.Log("Allocating CPUs from numa #1") + cpuCtx.cpus = c.numa1[minCpu : minCpu+nCpus] + } else { + err := fmt.Errorf("could not allocate %d CPUs; not enough CPUs in either numa node", nCpus) + return nil, err + } } else { - return nil, fmt.Errorf("too many containers; CPU allocation for >%d containers is not implemented", c.maxContainerCount) + cpuCtx.cpus = c.cpus[minCpu : minCpu+nCpus] } + + c.lastCpu = minCpu + nCpus cpuCtx.cpuAllocator = c return &cpuCtx, nil } @@ -113,14 +128,12 @@ func (c *CpuAllocatorT) readCpus() error { tmpCpus = tmpCpus[1:] } - // make c.cpus divisible by maxContainerCount * nCpus, so we don't have to check which numa will be used - // and we can use offsets - countToRemove := len(tmpCpus) % (c.maxContainerCount * *NConfiguredCpus) - if countToRemove >= len(tmpCpus) { - return fmt.Errorf("requested too many CPUs per container (%d), should be no more "+ - "than %d", *NConfiguredCpus, len(tmpCpus)/c.maxContainerCount) + c.cpus = append(c.cpus, tmpCpus...) + if i == 0 { + c.numa0 = append(c.numa0, tmpCpus...) + } else { + c.numa1 = append(c.numa1, tmpCpus...) } - c.cpus = append(c.cpus, tmpCpus[:len(tmpCpus)-countToRemove]...) tmpCpus = tmpCpus[:0] } } else { @@ -169,7 +182,6 @@ func CpuAllocator() (*CpuAllocatorT, error) { if cpuAllocator == nil { var err error cpuAllocator = new(CpuAllocatorT) - cpuAllocator.maxContainerCount = 4 err = cpuAllocator.readCpus() if err != nil { return nil, err diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index bfc6b7a842..039d0047f9 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -29,7 +29,8 @@ const ( ) var IsUnconfiguring = flag.Bool("unconfigure", false, "remove topology") -var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp") +var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to non-vpp containers") +var NConfiguredVppCpus = flag.Int("vpp_cpus", 1, "number of CPUs assigned to vpp containers") var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory") var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build") var UseCpu0 = flag.Bool("cpu0", false, "use cpu0") @@ -37,19 +38,21 @@ var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests") type HstSuite struct { HstCommon - AllContainers map[string]*Container - StartedContainers []*Container - NetConfigs []NetConfig - NetInterfaces map[string]*NetInterface - Ip4AddrAllocator *Ip4AddressAllocator - Ip6AddrAllocator *Ip6AddressAllocator - TestIds map[string]string - CpuAllocator *CpuAllocatorT - CpuContexts []*CpuContext - CpuCount int - Docker *client.Client - CoverageRun bool - numOfNewPorts int + AllContainers map[string]*Container + StartedContainers []*Container + NetConfigs []NetConfig + NetInterfaces map[string]*NetInterface + Ip4AddrAllocator *Ip4AddressAllocator + Ip6AddrAllocator *Ip6AddressAllocator + TestIds map[string]string + CpuAllocator *CpuAllocatorT + CpuContexts []*CpuContext + CpusPerContainer int + CpusPerVppContainer int + Docker *client.Client + CoverageRun bool + numOfNewPorts int + SkipIfNotEnoguhCpus bool } type colors struct { @@ -136,42 +139,29 @@ func (s *HstSuite) SetupSuite() { var err error s.CpuAllocator, err = CpuAllocator() + s.CpuAllocator.suite = s if err != nil { Fail("failed to init cpu allocator: " + fmt.Sprint(err)) } - s.CpuCount = *NConfiguredCpus + s.CpusPerContainer = *NConfiguredCpus + s.CpusPerVppContainer = *NConfiguredVppCpus s.CoverageRun = *IsCoverage } func (s *HstSuite) AllocateCpus(containerName string) []int { var cpuCtx *CpuContext var err error - currentTestName := CurrentSpecReport().LeafNodeText - - if strings.Contains(currentTestName, "MTTest") { - prevContainerCount := s.CpuAllocator.maxContainerCount - if strings.Contains(containerName, "vpp") { - // CPU range is assigned based on the Ginkgo process index (or build number if - // running in the CI), *NConfiguredCpus and a maxContainerCount. - // maxContainerCount is set to 4 when CpuAllocator is initialized. - // 4 is not a random number - all of our suites use a maximum of 4 containers simultaneously, - // and it's also the maximum number of containers we can run with *NConfiguredCpus=2 (with CPU0=true) - // on processors with 8 threads. Currently, the CpuAllocator puts all cores into a slice, - // makes the length of the slice divisible by 4x*NConfiguredCpus, and then the minCpu and - // maxCpu (range) for each container is calculated. Then we just offset based on minCpu, - // the number of started containers and *NConfiguredCpus. This way, every container - // uses the correct CPUs, even if multiple NUMA nodes are available. - // However, because of this, if we want to assign different number of cores to different containers, - // we have to change maxContainerCount to manipulate the CPU range. Hopefully a temporary workaround. - s.CpuAllocator.maxContainerCount = 1 - cpuCtx, err = s.CpuAllocator.Allocate(1, 3, 0) - } else { - s.CpuAllocator.maxContainerCount = 3 - cpuCtx, err = s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount, 2) - } - s.CpuAllocator.maxContainerCount = prevContainerCount + + if strings.Contains(containerName, "vpp") { + // CPUs are allocated based on s.CpusPerVppContainer/s.CpusPerContainer (defaults can be overridden globally + // or per test) and 'lastCpu' which serves as an offset. 'lastCpu' is incremented by 4 for each + // GinkgoParallelProcess() in SetupTest() in hst_suite, because all suites use 4 containers + // at most with 1 CPU each. GinkgoParallelProcess() offset doesn't impact MW or solo tests. + // Numa aware cpu allocation will use the second numa + // node if a container doesn't "fit" into the first node. + cpuCtx, err = s.CpuAllocator.Allocate(s.CpusPerVppContainer, s.CpuAllocator.lastCpu) } else { - cpuCtx, err = s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount, 0) + cpuCtx, err = s.CpuAllocator.Allocate(s.CpusPerContainer, s.CpuAllocator.lastCpu) } s.AssertNil(err) @@ -201,6 +191,10 @@ func (s *HstSuite) TeardownSuite() { func (s *HstSuite) TeardownTest() { s.HstCommon.TeardownTest() + s.SkipIfNotEnoguhCpus = false + // reset to defaults + s.CpusPerContainer = *NConfiguredCpus + s.CpusPerVppContainer = *NConfiguredVppCpus coreDump := s.WaitForCoreDump() s.ResetContainers() @@ -223,6 +217,8 @@ func (s *HstSuite) SkipIfNotCoverage() { func (s *HstSuite) SetupTest() { s.HstCommon.SetupTest() + // doesn't impact MW/solo tests + s.CpuAllocator.lastCpu = (GinkgoParallelProcess() - 1) * 4 s.StartedContainers = s.StartedContainers[:0] s.SkipIfUnconfiguring() s.SetupContainers() @@ -291,23 +287,6 @@ func (s *HstSuite) SkipIfMultiWorker(args ...any) { } } -func (s *HstSuite) SkipIfNotEnoughAvailableCpus() { - var maxRequestedCpu int - availableCpus := len(s.CpuAllocator.cpus) - 1 - - if *UseCpu0 { - availableCpus++ - } - - maxRequestedCpu = (GinkgoParallelProcess() * s.CpuAllocator.maxContainerCount * s.CpuCount) - - if availableCpus < maxRequestedCpu { - s.Skip(fmt.Sprintf("Test case cannot allocate requested cpus "+ - "(%d containers * %d cpus, %d available). Try using 'CPU0=true'", - s.CpuAllocator.maxContainerCount, s.CpuCount, availableCpus)) - } -} - func (s *HstSuite) SkipUnlessLeakCheck() { if !*IsLeakCheck { s.Skip("leak-check tests excluded") @@ -384,6 +363,7 @@ func (s *HstSuite) WaitForCoreDump() bool { } func (s *HstSuite) ResetContainers() { + s.CpuAllocator.lastCpu = 0 for _, container := range s.StartedContainers { container.stop() s.Log("Removing container " + container.Name) diff --git a/extras/hs-test/infra/suite_cpu_pinning.go b/extras/hs-test/infra/suite_cpu_pinning.go index e80682dcb2..a9f3d02083 100644 --- a/extras/hs-test/infra/suite_cpu_pinning.go +++ b/extras/hs-test/infra/suite_cpu_pinning.go @@ -15,8 +15,7 @@ var cpuPinningSoloTests = map[string][]func(s *CpuPinningSuite){} type CpuPinningSuite struct { HstSuite - previousMaxContainerCount int - Interfaces struct { + Interfaces struct { Tap *NetInterface } Containers struct { @@ -42,10 +41,8 @@ func (s *CpuPinningSuite) SetupSuite() { func (s *CpuPinningSuite) SetupTest() { // Skip if we cannot allocate 3 CPUs for test container - s.previousMaxContainerCount = s.CpuAllocator.maxContainerCount - s.CpuCount = 3 - s.CpuAllocator.maxContainerCount = 1 - s.SkipIfNotEnoughAvailableCpus() + s.CpusPerVppContainer = 3 + s.SkipIfNotEnoguhCpus = true s.HstSuite.SetupTest() vpp, err := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus) @@ -60,8 +57,7 @@ func (s *CpuPinningSuite) SetupTest() { func (s *CpuPinningSuite) TeardownTest() { defer s.HstSuite.TeardownTest() // reset vars - s.CpuCount = *NConfiguredCpus - s.CpuAllocator.maxContainerCount = s.previousMaxContainerCount + s.CpusPerContainer = *NConfiguredCpus } var _ = Describe("CpuPinningSuite", Ordered, ContinueOnFailure, func() { diff --git a/extras/hs-test/infra/suite_h2.go b/extras/hs-test/infra/suite_h2.go index b00708645d..96395d3c61 100644 --- a/extras/hs-test/infra/suite_h2.go +++ b/extras/hs-test/infra/suite_h2.go @@ -22,6 +22,7 @@ import ( var h2Tests = map[string][]func(s *H2Suite){} var h2SoloTests = map[string][]func(s *H2Suite){} +var h2MWTests = map[string][]func(s *H2Suite){} type H2Suite struct { HstSuite @@ -45,6 +46,9 @@ func RegisterH2Tests(tests ...func(s *H2Suite)) { func RegisterH2SoloTests(tests ...func(s *H2Suite)) { h2SoloTests[GetTestFilename()] = tests } +func RegisterH2MWTests(tests ...func(s *H2Suite)) { + h2MWTests[GetTestFilename()] = tests +} func (s *H2Suite) SetupSuite() { s.HstSuite.SetupSuite() @@ -146,6 +150,35 @@ var _ = Describe("Http2SoloSuite", Ordered, ContinueOnFailure, Serial, func() { } }) +var _ = Describe("Http2MWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s H2Suite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range h2MWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) + type h2specTest struct { desc string } @@ -403,7 +436,7 @@ var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { o := <-oChan s.Log(o) s.AssertEqual(0, tg.FailedCount) - }) + }, SpecTimeout(TestTimeout)) } } }) diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index 76f4289dbe..ed81690f82 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -13,6 +13,7 @@ import ( var ldpTests = map[string][]func(s *LdpSuite){} var ldpSoloTests = map[string][]func(s *LdpSuite){} +var ldpMWTests = map[string][]func(s *LdpSuite){} type LdpSuite struct { HstSuite @@ -37,6 +38,9 @@ func RegisterLdpTests(tests ...func(s *LdpSuite)) { func RegisterSoloLdpTests(tests ...func(s *LdpSuite)) { ldpSoloTests[GetTestFilename()] = tests } +func RegisterLdpMWTests(tests ...func(s *LdpSuite)) { + ldpMWTests[GetTestFilename()] = tests +} func (s *LdpSuite) SetupSuite() { time.Sleep(1 * time.Second) @@ -214,3 +218,33 @@ var _ = Describe("LdpSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { } } }) + +var _ = Describe("LdpMWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s LdpSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + // https://onsi.github.io/ginkgo/#dynamically-generating-specs + for filename, tests := range ldpMWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go index e464ef7f95..f7fc403fe7 100644 --- a/extras/hs-test/infra/suite_no_topo.go +++ b/extras/hs-test/infra/suite_no_topo.go @@ -12,6 +12,7 @@ import ( var noTopoTests = map[string][]func(s *NoTopoSuite){} var noTopoSoloTests = map[string][]func(s *NoTopoSuite){} +var noTopoMWTests = map[string][]func(s *NoTopoSuite){} type NoTopoSuite struct { HstSuite @@ -40,6 +41,9 @@ func RegisterNoTopoTests(tests ...func(s *NoTopoSuite)) { func RegisterNoTopoSoloTests(tests ...func(s *NoTopoSuite)) { noTopoSoloTests[GetTestFilename()] = tests } +func RegisterNoTopoMWTests(tests ...func(s *NoTopoSuite)) { + noTopoMWTests[GetTestFilename()] = tests +} func (s *NoTopoSuite) SetupSuite() { s.HstSuite.SetupSuite() @@ -245,3 +249,32 @@ var _ = Describe("NoTopoSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { } } }) + +var _ = Describe("NoTopoMWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s NoTopoSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range noTopoMWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index 44cc6bb7cb..ae3f203d71 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -49,14 +49,17 @@ type VppProxySuite struct { var vppProxyTests = map[string][]func(s *VppProxySuite){} var vppProxySoloTests = map[string][]func(s *VppProxySuite){} +var vppProxyMWTests = map[string][]func(s *VppProxySuite){} func RegisterVppProxyTests(tests ...func(s *VppProxySuite)) { vppProxyTests[GetTestFilename()] = tests } - func RegisterVppProxySoloTests(tests ...func(s *VppProxySuite)) { vppProxySoloTests[GetTestFilename()] = tests } +func RegisterVppProxyMWTests(tests ...func(s *VppProxySuite)) { + vppProxyMWTests[GetTestFilename()] = tests +} func (s *VppProxySuite) SetupSuite() { s.HstSuite.SetupSuite() @@ -313,6 +316,35 @@ var _ = Describe("VppProxySuiteSolo", Ordered, ContinueOnFailure, Serial, func() } }) +var _ = Describe("VppProxyMWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s VppProxySuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range vppProxyMWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) + var _ = Describe("H2SpecProxySuite", Ordered, ContinueOnFailure, func() { var s VppProxySuite BeforeAll(func() { @@ -379,5 +411,4 @@ var _ = Describe("H2SpecProxySuite", Ordered, ContinueOnFailure, func() { s.AssertEqual(0, tg.FailedCount) }, SpecTimeout(TestTimeout)) } - }) diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index aad821fad1..7043864a23 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -31,6 +31,7 @@ type VppUdpProxySuite struct { var vppUdpProxyTests = map[string][]func(s *VppUdpProxySuite){} var vppUdpProxySoloTests = map[string][]func(s *VppUdpProxySuite){} +var vppUdpProxyMWTests = map[string][]func(s *VppUdpProxySuite){} func RegisterVppUdpProxyTests(tests ...func(s *VppUdpProxySuite)) { vppUdpProxyTests[GetTestFilename()] = tests @@ -39,6 +40,9 @@ func RegisterVppUdpProxyTests(tests ...func(s *VppUdpProxySuite)) { func RegisterVppUdpProxySoloTests(tests ...func(s *VppUdpProxySuite)) { vppUdpProxySoloTests[GetTestFilename()] = tests } +func RegisterVppUdpProxyMWTests(tests ...func(s *VppUdpProxySuite)) { + vppUdpProxyMWTests[GetTestFilename()] = tests +} func (s *VppUdpProxySuite) SetupSuite() { s.HstSuite.SetupSuite() @@ -210,3 +214,32 @@ var _ = Describe("VppUdpProxySuiteSolo", Ordered, ContinueOnFailure, Serial, fun } } }) + +var _ = Describe("VppUdpProxyMWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s VppUdpProxySuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range vppUdpProxyMWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 43b44a73b8..050a1fccd7 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -169,10 +169,9 @@ func RedisBenchmarkTest(s *LdpSuite) { if *NConfiguredCpus == 1 { cmd = "redis-benchmark -q --threads 1 -h " + serverVethAddress } else { - cmd = "redis-benchmark -q --threads " + fmt.Sprint(*NConfiguredCpus) + "-h " + serverVethAddress + cmd = "redis-benchmark -q --threads " + fmt.Sprint(s.CpusPerContainer) + "-h " + serverVethAddress } s.StartClientApp(s.Containers.ClientApp, cmd, clnCh, clnRes) - }() // 4.5 minutes diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index 0eed1d8c92..e239ccd7b9 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -24,28 +24,34 @@ func init() { RegisterVppProxyTests(VppProxyHttpGetTcpTest, VppProxyHttpGetTlsTest, VppProxyHttpPutTcpTest, VppProxyHttpPutTlsTest, VppConnectProxyGetTest, VppConnectProxyPutTest, VppHttpsConnectProxyGetTest, VppH2ConnectProxyGetTest, VppH2ConnectProxyPutTest) - RegisterVppProxySoloTests(VppProxyHttpGetTcpMTTest, VppProxyHttpPutTcpMTTest, VppProxyTcpIperfMTTest, - VppProxyUdpIperfMTTest, VppConnectProxyStressTest, VppConnectProxyStressMTTest, VppConnectProxyConnectionFailedMTTest) + RegisterVppProxyMWTests(VppProxyHttpGetTcpMWTest, VppProxyHttpPutTcpMWTest, VppProxyTcpIperfMWTest, + VppProxyUdpIperfMWTest, VppConnectProxyStressMWTest, VppConnectProxyConnectionFailedMWTest) + RegisterVppProxySoloTests(VppConnectProxyStressTest) RegisterVppUdpProxyTests(VppProxyUdpTest, VppConnectUdpProxyTest, VppConnectUdpInvalidCapsuleTest, VppConnectUdpUnknownCapsuleTest, VppConnectUdpClientCloseTest, VppConnectUdpInvalidTargetTest) - RegisterVppUdpProxySoloTests(VppProxyUdpMigrationMTTest, VppConnectUdpStressMTTest, VppConnectUdpStressTest) + RegisterVppUdpProxySoloTests(VppConnectUdpStressTest) + RegisterVppUdpProxyMWTests(VppProxyUdpMigrationMWTest, VppConnectUdpStressMWTest) RegisterEnvoyProxyTests(EnvoyHttpGetTcpTest, EnvoyHttpPutTcpTest) RegisterNginxProxySoloTests(NginxMirroringTest, MirrorMultiThreadTest) } -func VppProxyHttpGetTcpMTTest(s *VppProxySuite) { +func VppProxyHttpGetTcpMWTest(s *VppProxySuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() VppProxyHttpGetTcpTest(s) } -func VppProxyTcpIperfMTTest(s *VppProxySuite) { - vppProxyIperfMTTest(s, "tcp") +func VppProxyTcpIperfMWTest(s *VppProxySuite) { + vppProxyIperfMWTest(s, "tcp") } -func VppProxyUdpIperfMTTest(s *VppProxySuite) { - vppProxyIperfMTTest(s, "udp") +func VppProxyUdpIperfMWTest(s *VppProxySuite) { + vppProxyIperfMWTest(s, "udp") } -func vppProxyIperfMTTest(s *VppProxySuite, proto string) { +func vppProxyIperfMWTest(s *VppProxySuite, proto string) { + s.CpusPerVppContainer = 3 + s.SetupTest() s.Containers.IperfC.Run() s.Containers.IperfS.Run() vppProxy := s.Containers.VppProxy.VppInstance @@ -110,7 +116,9 @@ func VppProxyHttpGetTlsTest(s *VppProxySuite) { s.CurlDownloadResource(uri) } -func VppProxyHttpPutTcpMTTest(s *VppProxySuite) { +func VppProxyHttpPutTcpMWTest(s *VppProxySuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() VppProxyHttpPutTcpTest(s) } @@ -187,7 +195,9 @@ func VppH2ConnectProxyGetTest(s *VppProxySuite) { s.AssertContains(log, "CONNECT tunnel: HTTP/2 negotiated") } -func VppConnectProxyConnectionFailedMTTest(s *VppProxySuite) { +func VppConnectProxyConnectionFailedMWTest(s *VppProxySuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() s.SetupNginxServer() s.ConfigureVppProxy("http", s.Ports.Proxy) @@ -337,7 +347,9 @@ func VppConnectProxyStressTest(s *VppProxySuite) { vppConnectProxyStressLoad(s, strconv.Itoa(int(s.Ports.Proxy))) } -func VppConnectProxyStressMTTest(s *VppProxySuite) { +func VppConnectProxyStressMWTest(s *VppProxySuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() @@ -372,7 +384,9 @@ func VppProxyUdpTest(s *VppUdpProxySuite) { s.AssertEqual([]byte("hello"), b[:n]) } -func VppProxyUdpMigrationMTTest(s *VppUdpProxySuite) { +func VppProxyUdpMigrationMWTest(s *VppUdpProxySuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() @@ -645,7 +659,9 @@ func VppConnectUdpStressTest(s *VppUdpProxySuite) { vppConnectUdpStressLoad(s) } -func VppConnectUdpStressMTTest(s *VppUdpProxySuite) { +func VppConnectUdpStressMWTest(s *VppUdpProxySuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() remoteServerConn := s.StartEchoServer() defer remoteServerConn.Close() From 1cda910074e3b0e496603d4d42d64c9f2b6ffe02 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 12 Jun 2025 00:45:12 -0400 Subject: [PATCH 056/313] vcl: avoid reading fifo if vcl detached from vpp Type: fix Change-Id: Ie357602140993244e81edb97fac48339a88ecbe8 Signed-off-by: Florin Coras --- src/vcl/vppcom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 9aff1e6ccd..ee249b36da 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -2126,6 +2126,7 @@ vppcom_session_read_internal (uint32_t session_handle, void *buf, int n, rx_fifo = vcl_session_is_ct (s) ? s->ct_rx_fifo : s->rx_fifo; /* If application closed, e.g., mt app, or no data return error */ if (s->session_state == VCL_STATE_CLOSED || + (s->flags & VCL_SESSION_F_APP_CLOSING) || svm_fifo_is_empty_cons (rx_fifo)) return vcl_session_closed_error (s); } From 770e0ef65cfaac54eae42935284ea219a77edb61 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Thu, 12 Jun 2025 14:35:49 +0200 Subject: [PATCH 057/313] hs-test: configurable Ginkgo timeout - global Ginkgo timeout can now be set using GINKGO_TIMEOUT - fixes coverage job timing out because of Ginkgo's default 1h timeout Type: fix Change-Id: Ie1386e3eab31ff843a94b991e43b5270772b7732 Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 16 +++++++++++----- extras/hs-test/hs_test.sh | 7 ++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index b02c499194..c2885b299e 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -69,6 +69,10 @@ ifeq ($(TIMEOUT),) TIMEOUT=5 endif +ifeq ($(GINKGO_TIMEOUT),) +GINKGO_TIMEOUT=3h +endif + FORCE_BUILD?=true .PHONY: help @@ -109,6 +113,7 @@ help: @echo " DRYRUN=[true|false] - set up containers but don't run tests" @echo " NO_COLOR=[true|false] - disables colorful Docker and Ginkgo output" @echo " TIMEOUT=[minutes] - test timeout override (5 minutes by default)" + @echo " GINKGO_TIMEOUT=[Ns/m/h] - Ginkgo timeout override (3h by default)" .PHONY: list-tests list-tests: @@ -143,7 +148,7 @@ test: .deps.ok .build.ok --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --cpu0=$(CPU0) \ --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT) \ - --vpp_cpus=$(VPP_CPUS); \ + --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ ./script/compress.sh $$? .PHONY: test-debug @@ -153,7 +158,7 @@ test-debug: .deps.ok .build_debug.ok --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true \ --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT) \ - --vpp_cpus=$(VPP_CPUS); \ + --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ ./script/compress.sh $$? .PHONY: wipe-lcov @@ -166,21 +171,22 @@ test-cov: .deps.ok .build.cov.ok wipe-lcov -@bash ./hs_test.sh --coverage=true --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST-HS) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) \ - --timeout=$(TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ + --timeout=$(TIMEOUT) --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ ./script/compress.sh $$? $(MAKE) -C ../.. test-cov-post-standalone HS_TEST=1 .PHONY: test-leak test-leak: .deps.ok .build_debug.ok @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) \ - --vpp_cpus=$(VPP_CPUS); + --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); .PHONY: test-perf test-perf: FORCE_BUILD=false test-perf: .deps.ok .build.ok @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --test=$(TEST) --vppsrc=$(VPPSRC) --repeat=$(REPEAT) \ - --skip=$(SKIP) --no_color=$(NO_COLOR) --perf=true --timeout=$(TIMEOUT); \ + --skip=$(SKIP) --no_color=$(NO_COLOR) --perf=true --timeout=$(TIMEOUT) \ + --ginkgo_timeout=$(GINKGO_TIMEOUT); \ ./script/compress.sh $$? .PHONY: setup-cluster diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index eb3607ca89..0254d0b5a9 100644 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -86,6 +86,9 @@ case "${i}" in --parallel=*) ginkgo_args="$ginkgo_args -procs=${i#*=}" ;; + --ginkgo_timeout=*) + ginkgo_args="$ginkgo_args --timeout=${i#*=}" + ;; --repeat=*) ginkgo_args="$ginkgo_args --repeat=${i#*=}" ;; @@ -183,7 +186,9 @@ fi mkdir -p summary # shellcheck disable=SC2086 -sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --json-report=summary/report.json $ginkgo_args -- $args +CMD="sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --json-report=summary/report.json $ginkgo_args -- $args" +echo "$CMD" +$CMD exit_status=$? if [ -e "summary/failed-summary.log" ]; then From aed4f8ead57da9d2ed01551821375e1ef2098e21 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Tue, 3 Jun 2025 15:07:28 +0000 Subject: [PATCH 058/313] hsa: in http client print the response body only for text/* Type: improvement Change-Id: I9a2e6b9344b64018b0c6872dbd051d7c39c7a0c3 Signed-off-by: Semir Sionek --- extras/hs-test/http_test.go | 26 +++++++++- src/plugins/hs_apps/http_client.c | 81 ++++++++++++++++++++++++++----- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 8b69072a60..88cb8863f1 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -38,7 +38,7 @@ func init() { HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest, - HttpStaticRedirectTest) + HttpStaticRedirectTest, HttpClientNoPrintTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest) @@ -459,6 +459,30 @@ func HttpClientPostFormTest(s *NoTopoSuite) { s.AssertContains(o, "200 OK") } +func HttpClientNoPrintTest(s *NoTopoSuite) { + serverAddress := s.HostAddr() + ":" + s.Ports.Http + server := ghttp.NewUnstartedServer() + l, err := net.Listen("tcp", serverAddress) + s.AssertNil(err, fmt.Sprint(err)) + server.HTTPTestServer.Listener = l + server.AppendHandlers( + ghttp.CombineHandlers( + s.LogHttpReq(true), + ghttp.VerifyRequest("GET", "/"), + // Bogus header just for testing + ghttp.RespondWith(http.StatusOK, "

Hello

", http.Header{"Content-Type": {"image/jpeg"}}), + )) + server.Start() + defer server.Close() + uri := "http://" + serverAddress + vpp := s.Containers.Vpp.VppInstance + o := vpp.Vppctl("http client verbose uri " + uri) + + s.Log(o) + s.AssertContains(o, "* binary file, not printing!", "no warning message found!") + s.AssertNotContains(o, "", " found in the result!") +} + func HttpClientGetResponseBodyTest(s *NoTopoSuite) { response := "hello world" size := len(response) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 7b86278d44..5787b4566b 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -6,10 +6,23 @@ #include #include #include +#include #include #include #include +#define foreach_hc_s_flag \ + _ (1, IS_CLOSED) \ + _ (2, PRINTABLE_BODY) \ + _ (4, BODY_OVER_LIMIT) + +typedef enum hc_s_flag_ +{ +#define _(n, s) HC_S_FLAG_##s = n, + foreach_hc_s_flag +#undef _ +} hc_s_flags; + typedef struct { u64 req_per_wrk; @@ -24,8 +37,7 @@ typedef struct u32 session_index; clib_thread_index_t thread_index; u64 to_recv; - u8 is_closed; - u8 body_over_limit; + u8 session_flags; hc_stats_t stats; u64 data_offset; u64 body_recv; @@ -98,6 +110,19 @@ typedef enum HC_REPEAT_DONE, } hc_cli_signal_t; +#define mime_printable_max_len 35 +const char mime_printable[][mime_printable_max_len] = { + "text/\0", + "application/json\0", + "application/javascript\0", + "application/x-yaml\0", + "application/x-www-form-urlencoded\0", + "application/xml\0", + "application/x-sh\0", + "application/x-tex\0", + "application/x-javascript\0", + "application/x-powershell\0" +}; static hc_main_t hc_main; static hc_stats_t hc_stats; @@ -350,7 +375,7 @@ hc_session_reset_callback (session_t *s) int rv; hc_session = hc_session_get (s->opaque, s->thread_index); - hc_session->is_closed = 1; + hc_session->session_flags |= HC_S_FLAG_IS_CLOSED; a->handle = session_handle (s); a->app_index = hcm->app_index; @@ -371,7 +396,7 @@ hc_rx_callback (session_t *s) session_error_t session_err = 0; int send_err = 0; - if (hc_session->is_closed) + if (hc_session->session_flags & HC_S_FLAG_IS_CLOSED) { clib_warning ("hc_session_index[%d] is closed", s->opaque); return -1; @@ -401,17 +426,43 @@ hc_rx_callback (session_t *s) hc_session->response_status = format (0, "%U", format_http_status_code, msg.code); + http_header_table_t ht = HTTP_HEADER_TABLE_NULL; + svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset); vec_validate (hc_session->resp_headers, msg.data.headers_len - 1); vec_set_len (hc_session->resp_headers, msg.data.headers_len); rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len, hc_session->resp_headers); + ht.buf = hc_session->resp_headers; ASSERT (rv == msg.data.headers_len); HTTP_DBG (1, (char *) format (0, "%v", hc_session->resp_headers)); msg.data.body_offset -= msg.data.headers_len + msg.data.headers_offset; + + http_build_header_table (&ht, msg); + const http_token_t *content_type = http_get_header ( + &ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); + if (content_type) + { + for (u8 i = 0; i < sizeof (mime_printable) / + (sizeof (char) * mime_printable_max_len); + i++) + { + u8 mime_len = + clib_strnlen (mime_printable[i], mime_printable_max_len); + if (content_type->len >= mime_len && + clib_strncmp (content_type->base, mime_printable[i], + mime_len) == 0) + { + hc_session->session_flags |= HC_S_FLAG_PRINTABLE_BODY; + break; + } + } + } + ht.buf = NULL; + http_free_header_table (&ht); } if (msg.data.body_len == 0) @@ -428,10 +479,11 @@ hc_rx_callback (session_t *s) goto done; } if (msg.data.body_len > hcm->max_body_size) - hc_session->body_over_limit = true; + hc_session->session_flags |= HC_S_FLAG_BODY_OVER_LIMIT; vec_validate (hc_session->http_response, - (hc_session->body_over_limit ? hcm->rx_fifo_size - 1 : - msg.data.body_len - 1)); + (hc_session->session_flags & HC_S_FLAG_BODY_OVER_LIMIT ? + hcm->rx_fifo_size - 1 : + msg.data.body_len - 1)); vec_reset_length (hc_session->http_response); } @@ -454,7 +506,7 @@ hc_rx_callback (session_t *s) } ASSERT (rv == n_deq); - if (!hc_session->body_over_limit) + if (!(hc_session->session_flags & HC_S_FLAG_BODY_OVER_LIMIT)) vec_set_len (hc_session->http_response, curr + n_deq); ASSERT (hc_session->to_recv >= rv); hc_session->to_recv -= rv; @@ -732,14 +784,21 @@ hc_get_event (vlib_main_t *vm) { wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); - vlib_cli_output (vm, "< %v\n< %v\n", hc_session->response_status, + vlib_cli_output (vm, "< %v\n< %v\n%v", hc_session->response_status, hc_session->resp_headers); - if (hc_session->body_over_limit) + if (hc_session->session_flags & HC_S_FLAG_BODY_OVER_LIMIT) vlib_cli_output ( vm, "* message body over limit, read total %llu bytes", hc_session->body_recv); else - vlib_cli_output (vm, "%v", hc_session->http_response); + { + if (hc_session->session_flags & HC_S_FLAG_PRINTABLE_BODY) + vlib_cli_output (vm, "%v", hc_session->http_response); + else + vlib_cli_output (vm, + "* binary file, not printing!\n* consider " + "saving to file with the 'file' option"); + } } break; case HC_REPEAT_DONE: From 3434f438074bfb8d785d279d26a24cabf4b7ed11 Mon Sep 17 00:00:00 2001 From: Neil McKee Date: Sun, 8 Jun 2025 20:55:14 -0700 Subject: [PATCH 059/313] sflow : documentation Update sFlow plugin documentation to reflect the addition of egress-sampling and drop-monitoring. Link the plugin documentation into the table of contents. Type: docs Change-Id: I6bd0e57860ec569fe955fb3f844aa4f24e8d8e25 Signed-off-by: Neil McKee --- docs/developer/plugins/index.rst | 1 + docs/developer/plugins/sflow.rst | 1 + docs/spelling_wordlist.txt | 11 ++++- src/plugins/sflow/sflow.rst | 77 ++++++++++++++++++-------------- 4 files changed, 55 insertions(+), 35 deletions(-) create mode 120000 docs/developer/plugins/sflow.rst diff --git a/docs/developer/plugins/index.rst b/docs/developer/plugins/index.rst index 393eefec53..064ce6afe9 100644 --- a/docs/developer/plugins/index.rst +++ b/docs/developer/plugins/index.rst @@ -30,6 +30,7 @@ For more on plugins please refer to :ref:`add_plugin`. lb lacp flowprobe + sflow map_lw4o6 mdata dhcp6_pd diff --git a/docs/developer/plugins/sflow.rst b/docs/developer/plugins/sflow.rst new file mode 120000 index 0000000000..fec909f509 --- /dev/null +++ b/docs/developer/plugins/sflow.rst @@ -0,0 +1 @@ +../../../src/plugins/sflow/sflow.rst \ No newline at end of file diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index b407b39c8c..51b929e0f6 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -285,6 +285,8 @@ dpdk dpkg dpo dport +dropmon +DROPMON dryrun DS dsa @@ -506,6 +508,7 @@ Init initiatehost inline inlines +insmod instantiation Instantiation Integrations @@ -692,6 +695,7 @@ mldv mmap modelled modernisation +modprobe modularity Monroy mortem @@ -898,6 +902,8 @@ Promisc proto proxying ps +psample +PSAMPLE pseudocode Pseudocode psp @@ -1024,6 +1030,8 @@ setjmp settingupenvironment setUp setUpClass +sflow +sFlow sfr sha Shamir @@ -1239,6 +1247,7 @@ usergroup Usergroup Usermode username +USERSOCK userspace Userspace usr @@ -1384,5 +1393,3 @@ zoomout zx µs oflags -sflow -sFlow diff --git a/src/plugins/sflow/sflow.rst b/src/plugins/sflow/sflow.rst index f9c1848836..6400bd454f 100644 --- a/src/plugins/sflow/sflow.rst +++ b/src/plugins/sflow/sflow.rst @@ -2,60 +2,71 @@ .. toctree:: -SFlow Monitoring Agent +sFlow Monitoring Agent ====================== Overview ________ -This plugin implements the random packet-sampling and interface -telemetry streaming required to support standard sFlow export -on Linux platforms. The overhead incurred by this monitoring is -minimal, so that detailed, real-time traffic analysis can be -achieved even under high load conditions, with visibility into -any fields that appear in the packet headers. If the VPP linux-cp -plugin is running then interfaces will be mapped to their -equivalent Linux tap ports. +This plugin implements the random packet-sampling, interface +telemetry streaming and packet drop monitoring necessary to support sFlow +export on Linux. The overhead is minimal, allowing detailed real-time +traffic analysis even under high load conditions. The samples, counters and +drops are sent to Linux Netlink channels PSAMPLE, USERSOCK and DROPMON where +tools such as host-sflow at https://sflow.net will receive them and export +standard sFlow. If the VPP linux-cp plugin is running then interfaces will +be mapped to their equivalent Linux tap ports. Example Configuration _____________________ :: + sflow sampling-rate 10000 sflow polling-interval 20 sflow header-bytes 128 + sflow direction both + sflow drop-monitoring enable sflow enable GigabitEthernet0/8/0 sflow enable GigabitEthernet0/9/0 sflow enable GigabitEthernet0/a/0 - ... - sflow enable GigabitEthernet0/a/0 disable Detailed notes ______________ -Each VPP worker that has at least one interface, will create a FIFO -and enqueues samples to it from the interfaces it is servicing that -are enabled. There is a process running in the main thread that will -dequeue the FIFOs periodically. If the FIFO is full, the worker will -drop samples, which helps ensure that (a) the main thread is not -overloaded with samples and (b) that individual workers and interfaces, -even when under high load, can't crowd out other interfaces and workers. - -You can change the sampling-rate at runtime, but keep in mind that -it is a global variable that applies to workers, not interfaces. -This means that (1) all workers will sample at the same rate, and (2) -if there are multiple interfaces assigned to a worker, they'll share -the sampling rate which will undershoot, and similarly (3) if there -are multiple RX queues assigned to more than one worker, the effective -sampling rate will overshoot. +Each VPP worker handling packets on an sFlow-enabled interface will enqueue +1:N random-sampled packet headers to a FIFO that is serviced by a process +in the main thread. These FIFOs are of limited depth. If a FIFO overflows the +worker will drop samples efficiently, which limits the overhead on both workers +and main thread even under high load conditions. + +Similarly, all packets traversing the error-drop arc are enqueued on another +limited-depth FIFO that is also serviced in the main thread. + +The main thread writes the sampled packet headers to netlink-PSAMPLE, +and the dropped packet headers to netlink-DROPMON. It also writes interface +status and counters to netlink-USERSOCK according to the configured +polling-interval. If a tool such as the host-sflow daemon at https://sflow.net +is running locally (with its vpp module enabled) then it will receive them and +export standard sFlow. + +If the VPP linux-cp plugin is running, the mapping from vpp interface to +Linux interface is included in the netlink-USERSOCK feed, allowing the +host-sflow daemon to export with either numbering model. + +For efficiency, the workers take advantage of the fact that sampling all +packets at 1:N is the same as sampling 1:N from each interface. The +same principle allows for sampling on "rx", "tx" or "both" directions +without incurring additional overhead. + +If the configured sampling-rate is too aggressive for the current traffic +level the agent will drop samples, but information about this "clipping" is +also communicated to the sFlow collector. External Dependencies _____________________ -This plugin writes packet samples to the standard Linux netlink PSAMPLE -channel, so the kernel psample module must be loaded with modprobe or -insmod. As such, this plugin only works for Linux environments. - -It also shares periodic interface counter samples vi netlink USERSOCK. -The host-sflow daemon, hsflowd, at https://sflow.net is one example of -a tool that will consume this feed and emit standard sFlow v5. +This plugin only works on Linux platforms. +The Linux kernel "psample" module must be loaded with modprobe or insmod. +The open source host-sflow daemon is at https://sflow.net. +The sFlow v5 spec is published at https://sflow.org. From 1d5da94a433e8e42e425bebea7502c8268b81fd6 Mon Sep 17 00:00:00 2001 From: Hadi Rayan Al-Sandid Date: Fri, 6 Jun 2025 16:18:41 +0200 Subject: [PATCH 060/313] docs: minor improvement to core-pinning documentation Type: docs Change-Id: I05691e77855832ebbd9a3f9ee7de8a4edc1d75fe Signed-off-by: Hadi Rayan Al-Sandid --- docs/configuration/reference.rst | 14 +++++++++----- docs/spelling_wordlist.txt | 1 + src/vpp/conf/startup.conf | 4 ++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/configuration/reference.rst b/docs/configuration/reference.rst index 9378ce5b58..e2016b1d0e 100644 --- a/docs/configuration/reference.rst +++ b/docs/configuration/reference.rst @@ -469,12 +469,16 @@ CPU cores while skipping "skip-cores" CPU core(s) and main thread's CPU core relative ^^^^^^^^^ -Apply thread pinning configuration with respect to the available logical cores -in the current control group CPU set. +Apply thread pinning configuration with respect to the logical cores available +to the VPP process, rather than all logical cores present on the host machine. + By default, VPP applies the thread pinning configuration with respect to the -available logical cores on host (e.g. '/sys/devices/system/cpu/online'). With -the 'relative' keyword, the thread pinning configuration is applied with respect -to the available logical cores (obtained with sched_getaffinity). +available logical cores on host (e.g. '/sys/devices/system/cpu/online'), but with +the 'relative' keyword, we apply the thread pinning configuration with respect +to logical cores available to the VPP process (obtained with sched_getaffinity). + +This parameter can be useful when running VPP in an environment with restricted access +to host CPU resources (e.g. running in a container, or using taskset). .. code-block:: console diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 51b929e0f6..cd9d75089c 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1135,6 +1135,7 @@ Tahr tapcli tcp tcpcontrolbits +taskset te teardown tearDown diff --git a/src/vpp/conf/startup.conf b/src/vpp/conf/startup.conf index 8e7aebd827..938869d61a 100644 --- a/src/vpp/conf/startup.conf +++ b/src/vpp/conf/startup.conf @@ -81,6 +81,10 @@ cpu { ## and main thread's CPU core # workers 2 + ## Apply thread pinning configuration with respect to the logical cores available + ## to VPP at launch, rather than all logical cores present on the host machine + # relative + ## Set scheduling policy and priority of main and worker threads ## Scheduling policy options are: other (SCHED_OTHER), batch (SCHED_BATCH) From ac145350f7b1e283fa43f8bb4cabf45a08b7da76 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 12 Jun 2025 02:26:00 -0400 Subject: [PATCH 061/313] af_packet: fix rx/tx queue cli config Type: fix Change-Id: I45da2ee461c9b0a76eaee341eacd1449e88b87c6 Signed-off-by: Florin Coras --- src/plugins/af_packet/cli.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugins/af_packet/cli.c b/src/plugins/af_packet/cli.c index 2af3fb17ee..0f09badce7 100644 --- a/src/plugins/af_packet/cli.c +++ b/src/plugins/af_packet/cli.c @@ -47,6 +47,7 @@ af_packet_create_command_fn (vlib_main_t * vm, unformat_input_t * input, af_packet_create_if_arg_t _arg, *arg = &_arg; clib_error_t *error = NULL; u8 hwaddr[6]; + u32 nqs; int r; clib_memset (arg, 0, sizeof (*arg)); @@ -79,10 +80,10 @@ af_packet_create_command_fn (vlib_main_t * vm, unformat_input_t * input, else if (unformat (line_input, "tx-per-block %u", &arg->tx_frames_per_block)) ; - else if (unformat (line_input, "num-rx-queues %u", &arg->num_rxqs)) - ; - else if (unformat (line_input, "num-tx-queues %u", &arg->num_txqs)) - ; + else if (unformat (line_input, "num-rx-queues %u", &nqs)) + arg->num_rxqs = nqs; + else if (unformat (line_input, "num-tx-queues %u", &nqs)) + arg->num_txqs = nqs; else if (unformat (line_input, "qdisc-bypass-disable")) arg->flags &= ~AF_PACKET_IF_FLAGS_QDISC_BYPASS; else if (unformat (line_input, "cksum-gso-disable")) From 3fe8ddaf328a769b149eb29cde94f68202798307 Mon Sep 17 00:00:00 2001 From: Sivaprasad Tummala Date: Wed, 11 Jun 2025 10:21:43 +0000 Subject: [PATCH 062/313] vlib: increase CPU base frequency precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the 'show cpu' CLI command to print the base frequency with 4 decimal places instead of 2 (e.g., 2.69 GHz → 2.6945 GHz) for improved accuracy when inspecting CPU timing characteristics Type: improvement Change-Id: I8f3384204ddcb8033aae9a1e1f6d06bed46478ab Signed-off-by: Sivaprasad Tummala --- src/vlib/cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vlib/cli.c b/src/vlib/cli.c index 38a8c2aa19..f66d57ae49 100644 --- a/src/vlib/cli.c +++ b/src/vlib/cli.c @@ -980,8 +980,8 @@ show_cpu (vlib_main_t * vm, unformat_input_t * input, _("Model name", "%U", format_cpu_model_name); _("Microarch model (family)", "%U", format_cpu_uarch); _("Flags", "%U", format_cpu_flags); - _("Base frequency", "%.2f GHz", - ((f64) vm->clib_time.clocks_per_second) * 1e-9); + _ ("Base frequency", "%.4f GHz", + ((f64) vm->clib_time.clocks_per_second) * 1e-9); #undef _ return 0; } From 407c3e95b40cc4b01f67202f71bc0e8f77b90a28 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Fri, 13 Jun 2025 00:09:20 -0400 Subject: [PATCH 063/313] tests: run docker login script hs-test jobs - login to docker hub in the CI to avoid hitting the docker hub rate-limiter Type: test Change-Id: I4f6a854136b9bae9da1493a2aaa88576a64b314d Signed-off-by: Dave Wallace --- extras/hs-test/docker/setup-local-registry.sh | 5 +++++ extras/hs-test/script/build-images.sh | 5 +++++ extras/hs-test/script/build_hst.sh | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/extras/hs-test/docker/setup-local-registry.sh b/extras/hs-test/docker/setup-local-registry.sh index 684f858584..638a2f898d 100755 --- a/extras/hs-test/docker/setup-local-registry.sh +++ b/extras/hs-test/docker/setup-local-registry.sh @@ -3,6 +3,11 @@ set -e +DOCKER_LOGIN_SCRIPT="/scratch/nomad/.docker-ro/dlogin.sh" +if [ -x "$DOCKER_LOGIN_SCRIPT" ] ; then + $DOCKER_LOGIN_SCRIPT +fi + # Check if Docker is running if ! docker info &>/dev/null; then echo "Error: Docker is not running. Please start Docker and try again." diff --git a/extras/hs-test/script/build-images.sh b/extras/hs-test/script/build-images.sh index 86398f881b..74bb2eb06c 100755 --- a/extras/hs-test/script/build-images.sh +++ b/extras/hs-test/script/build-images.sh @@ -10,10 +10,15 @@ ARCH=${OS_ARCH:-$(dpkg --print-architecture)} DOCKER_BUILD_DIR="/scratch/docker-build" DOCKER_CACHE_DIR="${DOCKER_BUILD_DIR}/docker_cache" DOCKER_HST_BUILDER="hst_builder" +DOCKER_LOGIN_SCRIPT="/scratch/nomad/.docker-ro/dlogin.sh" if [ -d "${DOCKER_BUILD_DIR}" ] ; then mkdir -p "${DOCKER_CACHE_DIR}" + if [ -x "$DOCKER_LOGIN_SCRIPT" ] ; then + $DOCKER_LOGIN_SCRIPT + fi + # Create buildx builder if it doesn't exist if ! docker buildx ls --format "{{.Name}}" | grep -q "${DOCKER_HST_BUILDER}"; then docker buildx create --use \ diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh index 0e7656354f..a36037c0b6 100755 --- a/extras/hs-test/script/build_hst.sh +++ b/extras/hs-test/script/build_hst.sh @@ -38,6 +38,10 @@ fi OS_ARCH="$(uname -m)" DOCKER_BUILD_DIR="/scratch/docker-build" DOCKER_CACHE_DIR="${DOCKER_BUILD_DIR}/docker_cache" +DOCKER_LOGIN_SCRIPT="/scratch/nomad/.docker-ro/dlogin.sh" +if [ -x "$DOCKER_LOGIN_SCRIPT" ] ; then + $DOCKER_LOGIN_SCRIPT +fi # Set up the local registry before creating containers echo "=== Setting up local registry ===" From 23b51bd52a0641f33f79d20fe4566a9ad3b4675b Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Fri, 13 Jun 2025 13:16:47 +0000 Subject: [PATCH 064/313] hsa: improve handling of SESSION_E_REFUSED in http client In that case (e.g trying to connect on a closed port), a connection is refused and no session is allocated. As such, we cannot use the struct to obtain the worker or vlib_main_t. Type: fix Change-Id: I9b44ccb42ba56a75b8ecc39501a7386ba2e84981 Signed-off-by: Semir Sionek --- src/plugins/hs_apps/http_client.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 5787b4566b..a284da8beb 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -225,17 +225,17 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, hc_session_t *hc_session; hc_http_header_t *header; - wrk = hc_worker_get (s->thread_index); - if (err) { clib_warning ("hc_session_index[%d] connected error: %U", hc_session_index, format_session_error, err); - vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, + vlib_process_signal_event_mt (vlib_get_main (), hcm->cli_node_index, HC_CONNECT_FAILED, 0); return -1; } + wrk = hc_worker_get (s->thread_index); + hc_session = hc_session_alloc (wrk); clib_spinlock_lock_if_init (&hcm->lock); hcm->connected_counter++; From bd70ed1543cee3a753904887f0e61f0fefe2645f Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Fri, 13 Jun 2025 11:07:47 -0700 Subject: [PATCH 065/313] vcl: make vcl_evt code compile To include vcl_evt code in the build, make build VPP_EXTRA_CMAKE_ARGS=-DVPP_VCL_ELOG=ON Type: fix Change-Id: I45bd093001de6e3dd4d6894726a470cf1ded952b Signed-off-by: Steven Luong --- src/vcl/vcl_bapi.c | 3 +- src/vcl/vcl_debug.h | 312 +++++++++++++++++++-------------------- src/vcl/vcl_private.h | 4 +- src/vcl/vppcom.c | 10 +- src/vpp/vnet/config.h.in | 1 + 5 files changed, 165 insertions(+), 165 deletions(-) diff --git a/src/vcl/vcl_bapi.c b/src/vcl/vcl_bapi.c index 42704f42c5..edb7e6f7b5 100644 --- a/src/vcl/vcl_bapi.c +++ b/src/vcl/vcl_bapi.c @@ -553,7 +553,6 @@ vcl_bapi_connect_to_vpp (void) wrk->api_client_handle = (u32) am->my_client_index; VDBG (0, "app (%s) is connected to VPP!", wrk_name); - vcl_evt (VCL_EVT_INIT, vcm); error: vec_free (wrk_name); @@ -618,7 +617,7 @@ vcl_bapi_wait_for_wrk_state_change (vcl_bapi_app_state_t app_state) } VDBG (0, "timeout waiting for state %s, current state %d", vcl_bapi_app_state_str (app_state), wrk->bapi_app_state); - vcl_evt (VCL_EVT_SESSION_TIMEOUT, vcm, bapi_app_state); + // vcl_evt (VCL_EVT_SESSION_TIMEOUT, vcm, bapi_app_state); return VPPCOM_ETIMEDOUT; } diff --git a/src/vcl/vcl_debug.h b/src/vcl/vcl_debug.h index 8ee716a3c8..1238e0d7cc 100644 --- a/src/vcl/vcl_debug.h +++ b/src/vcl/vcl_debug.h @@ -18,7 +18,12 @@ #include -#define VCL_ELOG 0 +#ifdef VPP_VCL_ELOG +#define VCL_ELOG 1 +#else +#define VCL_ELOG 0 +#endif + #define VCL_DBG_ON 1 #define VDBG(_lvl, _fmt, _args...) \ @@ -59,117 +64,113 @@ typedef enum vcl_dbg_evt_ #undef _ } vcl_dbg_evt_e; -#if VCL_ELOG +#if (VCL_ELOG > 0) -#define VCL_DECLARE_ETD(_s, _e, _size) \ - struct { u32 data[_size]; } * ed; \ - ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track) \ +#define VCL_DECLARE_ETD(_s, _e, _size) \ + struct \ + { \ + u32 data[_size]; \ + } *ed; \ + ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track) -#define VCL_EVT_INIT_HANDLER(_vcm, ...) \ -{ \ - _vcm->elog_track.name = (char *) format (0, "P:%d:C:%d%c", getpid (), \ - _vcm->my_client_index, 0); \ - elog_track_register (&_vcm->elog_main, &_vcm->elog_track); \ - ELOG_TYPE_DECLARE (e) = \ - { \ - .format = "connect_vpp:rv:%d", \ - .format_args = "i4", \ - }; \ - struct { u32 data; } *ed; \ - ed = ELOG_TRACK_DATA (&_vcm->elog_main, e, _vcm->elog_track); \ - ed->data = (u32) rv; \ -} +#define VCL_EVT_INIT_HANDLER(_vcm, ...) \ + { \ + _vcm->elog_track.name = \ + (char *) format (0, "P:%d:C:%d%c", getpid (), _vcm->app_index, 0); \ + elog_track_register (&_vcm->elog_main, &_vcm->elog_track); \ + } -#define VCL_EVT_SESSION_INIT_HANDLER(_s, _s_index, ...) \ -{ \ - _s->elog_track.name = (char *) format (0, "CI:%d:S:%d%c", \ - vcm->my_client_index, \ - _s_index, 0); \ - elog_track_register (&vcm->elog_main, &_s->elog_track); \ -} +#define VCL_EVT_SESSION_INIT_HANDLER(_s, _s_index, ...) \ + { \ + _s->elog_track.name = \ + (char *) format (0, "CI:%d:S:%d%c", vcm->app_index, _s->_s_index, 0); \ + elog_track_register (&vcm->elog_main, &_s->elog_track); \ + } -#define VCL_EVT_BIND_HANDLER(_s, ...) \ -{ \ - if (_s->lcl_addr.is_ip4) \ - { \ - ELOG_TYPE_DECLARE (_e) = \ +#define VCL_EVT_BIND_HANDLER(_s, ...) \ + { \ + if (_s->transport.is_ip4) \ + { \ + ELOG_TYPE_DECLARE (_e) = \ { \ .format = "bind local:%s:%d.%d.%d.%d:%d ", \ .format_args = "t1i1i1i1i1i2", \ .n_enum_strings = 2, \ .enum_strings = {"TCP", "UDP",}, \ - }; \ - CLIB_PACKED (struct { \ - u8 proto; \ - u8 addr[4]; \ - u16 port; \ - }) *ed; \ - ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track); \ - ed->proto = _s->proto; \ - ed->addr[0] = _s->lcl_addr.ip46.ip4.as_u8[0]; \ - ed->addr[1] = _s->lcl_addr.ip46.ip4.as_u8[1]; \ - ed->addr[2] = _s->lcl_addr.ip46.ip4.as_u8[2]; \ - ed->addr[3] = _s->lcl_addr.ip46.ip4.as_u8[3]; \ - ed->port = clib_net_to_host_u16 (_s->lcl_port); \ - } \ - else \ - { \ - /* TBD */ \ - } \ -} + }; \ + CLIB_PACKED (struct { \ + u8 proto; \ + u8 addr[4]; \ + u16 port; \ + }) * \ + ed; \ + ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track); \ + ed->proto = _s->session_type; \ + ed->addr[0] = _s->transport.lcl_ip.ip4.as_u8[0]; \ + ed->addr[1] = _s->transport.lcl_ip.ip4.as_u8[1]; \ + ed->addr[2] = _s->transport.lcl_ip.ip4.as_u8[2]; \ + ed->addr[3] = _s->transport.lcl_ip.ip4.as_u8[3]; \ + ed->port = clib_net_to_host_u16 (_s->transport.lcl_port); \ + } \ + else \ + { \ + /* TBD */ \ + } \ + } -#define VCL_EVT_ACCEPT_HANDLER(_s, _ls, _s_idx, ...) \ -{ \ - VCL_EVT_SESSION_INIT_HANDLER (_s, _s_idx); \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "accept: listen_handle:%x from_handle:%x", \ - .format_args = "i8i8", \ - }; \ - struct { u64 handle[2]; } *ed; \ - ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track); \ - ed->handle[0] = _ls->vpp_handle; \ - ed->handle[1] = _s->vpp_handle; \ - if (_s->peer_addr.is_ip4) \ - { \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "accept:S:%x addr:%d.%d.%d.%d:%d", \ - .format_args = "i8i1i1i1i1i2", \ - }; \ - CLIB_PACKED (struct { \ - u32 s_idx; \ - u8 addr[4]; \ - u16 port; \ - }) * ed; \ - ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track); \ - ed->s_idx = _s_idx; \ - ed->addr[0] = _s->peer_addr.ip46.ip4.as_u8[0]; \ - ed->addr[1] = _s->peer_addr.ip46.ip4.as_u8[1]; \ - ed->addr[2] = _s->peer_addr.ip46.ip4.as_u8[2]; \ - ed->addr[3] = _s->peer_addr.ip46.ip4.as_u8[3]; \ - ed->port = clib_net_to_host_u16 (_s->peer_port); \ - } \ - else \ - { \ - /* TBD */ \ - } \ -} +#define VCL_EVT_ACCEPT_HANDLER(_s, _ls, _s_idx, ...) \ + { \ + VCL_EVT_SESSION_INIT_HANDLER (_s, _s_idx); \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "accept: listen_handle:%x from_handle:%x", \ + .format_args = "i8i8", \ + }; \ + struct \ + { \ + u64 handle[2]; \ + } *ed; \ + ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track); \ + ed->handle[0] = _ls->vpp_handle; \ + ed->handle[1] = _s->vpp_handle; \ + if (_s->transport.is_ip4) \ + { \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "accept:S:%x addr:%d.%d.%d.%d:%d", \ + .format_args = "i8i1i1i1i1i2", \ + }; \ + CLIB_PACKED (struct { \ + u32 s_idx; \ + u8 addr[4]; \ + u16 port; \ + }) * \ + ed; \ + ed = ELOG_TRACK_DATA (&vcm->elog_main, _e, _s->elog_track); \ + ed->s_idx = _s->_s_idx; \ + ed->addr[0] = _s->transport.rmt_ip.ip4.as_u8[0]; \ + ed->addr[1] = _s->transport.rmt_ip.ip4.as_u8[1]; \ + ed->addr[2] = _s->transport.rmt_ip.ip4.as_u8[2]; \ + ed->addr[3] = _s->transport.rmt_ip.ip4.as_u8[3]; \ + ed->port = clib_net_to_host_u16 (_s->transport.rmt_port); \ + } \ + else \ + { \ + /* TBD */ \ + } \ + } -#define VCL_EVT_CREATE_HANDLER(_s, _proto, _state, _is_nb, _s_idx, ...) \ -{ \ - VCL_EVT_SESSION_INIT_HANDLER (_s, _s_idx); \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "create:proto:%d state:%d is_nonblk:%d idx: %d", \ - .format_args = "i4i4i4i4", \ - }; \ - VCL_DECLARE_ETD (_s, _e, 4); \ - ed->data[0] = _proto; \ - ed->data[1] = _state; \ - ed->data[2] = _is_nb; \ - ed->data[3] = _s_idx; \ -} +#define VCL_EVT_CREATE_HANDLER(_s, _proto, _state, _is_nb, _s_idx, ...) \ + { \ + VCL_EVT_SESSION_INIT_HANDLER (_s, _s_idx); \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "create:proto:%d state:%d is_nonblk:%d idx: %d", \ + .format_args = "i4i4i4i4", \ + }; \ + VCL_DECLARE_ETD (_s, _e, 4); \ + ed->data[0] = _s->_proto; \ + ed->data[1] = _s->_state; \ + ed->data[2] = _is_nb; \ + ed->data[3] = _s->_s_idx; \ + } #define VCL_EVT_CLOSE_HANDLER(_s, _rv, ...) \ { \ @@ -182,16 +183,15 @@ typedef enum vcl_dbg_evt_ ed->data[0] = _rv; \ } -#define VCL_EVT_SESSION_TIMEOUT_HANDLER(_s, _state, ...) \ -{ \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "ERR: timeout state:%d", \ - .format_args = "i4", \ - }; \ - VCL_DECLARE_ETD (_s, _e, 1); \ - ed->data[0] = _state; \ -} +#define VCL_EVT_SESSION_TIMEOUT_HANDLER(_s, _state, ...) \ + { \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "ERR: timeout state:%d", \ + .format_args = "i4", \ + }; \ + VCL_DECLARE_ETD (_s, _e, 1); \ + ed->data[0] = _s->_state; \ + } #define VCL_EVT_TIMEOUT_HANDLER(_vcm, _state, ...) \ { \ @@ -205,17 +205,19 @@ typedef enum vcl_dbg_evt_ ed->data[0] = _state; \ } -#define VCL_EVT_DETACH_HANDLER(_vcm, ...) \ -{ \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "app_detach:C:%d", \ - .format_args = "i4", \ - }; \ - struct { u32 data; } * ed; \ - ed = ELOG_TRACK_DATA (&_vcm->elog_main, _e, _vcm->elog_track); \ - ed->data = _vcm->my_client_index; \ -} +#define VCL_EVT_DETACH_HANDLER(_vcm, ...) \ + { \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "app_detach:C:%d", \ + .format_args = "i4", \ + }; \ + struct \ + { \ + u32 data; \ + } *ed; \ + ed = ELOG_TRACK_DATA (&_vcm->elog_main, _e, _vcm->elog_track); \ + ed->data = _vcm->app_index; \ + } #define VCL_EVT_UNBIND_HANDLER(_s, ...) \ { \ @@ -229,17 +231,16 @@ typedef enum vcl_dbg_evt_ ed->data = _s->vpp_handle; \ } -#define VCL_EVT_EPOLL_CREATE_HANDLER(_s, _s_idx, ...) \ -{ \ - VCL_EVT_SESSION_INIT_HANDLER (_s, _s_idx); \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "create epoll vep_idx: %d", \ - .format_args = "i4", \ - }; \ - VCL_DECLARE_ETD (_s, _e, 1); \ - ed->data[0] = _s_idx; \ -} +#define VCL_EVT_EPOLL_CREATE_HANDLER(_s, _s_idx, ...) \ + { \ + VCL_EVT_SESSION_INIT_HANDLER (_s, _s_idx); \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "create epoll vep_idx: %d", \ + .format_args = "i4", \ + }; \ + VCL_DECLARE_ETD (_s, _e, 1); \ + ed->data[0] = _s->_s_idx; \ + } #define VCL_EVT_EPOLL_CTLADD_HANDLER(_s, _evts, _evt_data, ...) \ { \ @@ -268,30 +269,29 @@ typedef enum vcl_dbg_evt_ ed->data[0] = _vep_idx; \ } -#define vcl_elog_init(_vcm) \ -{ \ - _vcm->elog_main.lock = clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES, \ - CLIB_CACHE_LINE_BYTES);\ - _vcm->elog_main.lock[0] = 0; \ - _vcm->elog_main.event_ring_size = _vcm->cfg.event_ring_size; \ - elog_init (&_vcm->elog_main, _vcm->elog_main.event_ring_size); \ - elog_enable_disable (&_vcm->elog_main, 1); \ -} +#define vcl_elog_init(_vcm) \ + { \ + _vcm->elog_main.lock = \ + clib_mem_alloc_aligned (CLIB_CACHE_LINE_BYTES, CLIB_CACHE_LINE_BYTES); \ + _vcm->elog_main.lock[0] = 0; \ + _vcm->elog_main.event_ring_size = (128 << 10); \ + elog_init (&_vcm->elog_main, _vcm->elog_main.event_ring_size); \ + elog_enable_disable (&_vcm->elog_main, 1); \ + } -#define vcl_elog_stop(_vcm) \ -{ \ - clib_error_t *error = 0; \ - char *chroot_file = (char *) format (0, "%s/%d-%d-vcl-elog%c", \ - _vcm->cfg.event_log_path, \ - _vcm->my_client_index, \ - getpid (), 0); \ - error = elog_write_file (&_vcm->elog_main, chroot_file, \ - 1 /* flush ring */ ); \ - if (error) \ - clib_error_report (error); \ - clib_warning ("[%d] Event Log:'%s' ", getpid (), chroot_file); \ - vec_free (chroot_file); \ -} +#define vcl_elog_stop(_vcm) \ + { \ + clib_error_t *error = 0; \ + char *chroot_file = \ + (char *) format (0, "%s/%d-%d-vcl-elog%c", _vcm->cfg.event_log_path, \ + _vcm->app_index, getpid (), 0); \ + error = \ + elog_write_file (&_vcm->elog_main, chroot_file, 1 /* flush ring */); \ + if (error) \ + clib_error_report (error); \ + clib_warning ("[%d] Event Log:'%s' ", getpid (), chroot_file); \ + vec_free (chroot_file); \ + } #define CONCAT_HELPER(_a, _b) _a##_b #define CC(_a, _b) CONCAT_HELPER(_a, _b) diff --git a/src/vcl/vcl_private.h b/src/vcl/vcl_private.h index a66dbf33e8..61894d7fe2 100644 --- a/src/vcl/vcl_private.h +++ b/src/vcl/vcl_private.h @@ -186,7 +186,7 @@ typedef struct vcl_session_ i32 vpp_error; -#if VCL_ELOG +#if (VCL_ELOG > 0) elog_track_t elog_track; #endif @@ -394,7 +394,7 @@ typedef struct vppcom_main_t_ /* VNET_API_ERROR_FOO -> "Foo" hash table */ uword *error_string_by_error_number; -#ifdef VCL_ELOG +#if (VCL_ELOG > 0) /* VPP Event-logger */ elog_main_t elog_main; elog_track_t elog_track; diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index ee249b36da..57b7214fdd 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -1493,6 +1492,7 @@ vppcom_app_create (const char *app_name) clib_rwlock_init (&vcm->segment_table_lock); atexit (vppcom_app_exit); vcl_elog_init (vcm); + vcl_evt (VCL_EVT_INIT, vcm); /* Allocate default worker */ vcl_worker_alloc_and_init (); @@ -1560,7 +1560,7 @@ vppcom_session_create (u8 proto, u8 is_nonblocking) if (is_nonblocking) vcl_session_set_attr (session, VCL_SESS_ATTR_NONBLOCK); - vcl_evt (VCL_EVT_CREATE, session, session_type, session->session_state, + vcl_evt (VCL_EVT_CREATE, session, session_type, session_state, is_nonblocking, session_index); VDBG (0, "created session %u", session->session_index); @@ -1941,7 +1941,7 @@ vppcom_session_accept (uint32_t ls_handle, vppcom_endpt_t *ep, uint32_t flags) vcl_format_ip46_address, &client_session->transport.lcl_ip, client_session->transport.is_ip4 ? IP46_TYPE_IP4 : IP46_TYPE_IP6, clib_net_to_host_u16 (client_session->transport.lcl_port)); - vcl_evt (VCL_EVT_ACCEPT, client_session, ls, client_session_index); + vcl_evt (VCL_EVT_ACCEPT, client_session, ls, session_index); /* * Session might have been closed already @@ -3029,7 +3029,7 @@ vppcom_epoll_create (void) vep_session->vep.prev_sh = ~0; vep_session->vpp_handle = SESSION_INVALID_HANDLE; - vcl_evt (VCL_EVT_EPOLL_CREATE, vep_session, vep_session->session_index); + vcl_evt (VCL_EVT_EPOLL_CREATE, vep_session, session_index); VDBG (0, "Created vep_idx %u", vep_session->session_index); return vcl_session_handle (vep_session); @@ -3287,7 +3287,7 @@ vppcom_epoll_ctl (uint32_t vep_handle, int op, uint32_t session_handle, VDBG (1, "EPOLL_CTL_DEL: vep_idx %u, sh %u!", vep_handle, session_handle); - vcl_evt (VCL_EVT_EPOLL_CTLDEL, s, vep_sh); + vcl_evt (VCL_EVT_EPOLL_CTLDEL, s, vep_handle); break; default: diff --git a/src/vpp/vnet/config.h.in b/src/vpp/vnet/config.h.in index e8548c9b49..d3012b3338 100644 --- a/src/vpp/vnet/config.h.in +++ b/src/vpp/vnet/config.h.in @@ -20,5 +20,6 @@ #cmakedefine VPP_IP_FIB_MTRIE_16 #cmakedefine VPP_TCP_DEBUG_ALWAYS #cmakedefine VPP_SESSION_DEBUG +#cmakedefine VPP_VCL_ELOG #endif From 40e1ff601501649d64789d80b2a1c7652b325249 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 13 Jun 2025 17:26:54 -0400 Subject: [PATCH 066/313] hsa: mt support for cl udp test Also add tests for - multi worker cl connects/binds - 2 multi worker servers binding the same port Type: improvement Change-Id: I222756b7664ffdba83cb69bb0c730526dad3065c Signed-off-by: Florin Coras --- src/plugins/hs_apps/vcl/vcl_test_cl_udp.c | 279 +++++++++++++++++++--- test/asf/test_vcl.py | 96 ++++++++ 2 files changed, 336 insertions(+), 39 deletions(-) diff --git a/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c b/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c index 066635e3d9..a0df4c228c 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c +++ b/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c @@ -2,9 +2,28 @@ * Copyright(c) 2025 Cisco Systems, Inc. */ +/* + * VCL CL UDP Test Client/Server with Multi-threading Support + * + * Usage: + * Server: vcl_test_cl_udp -s [-w ] + * Client: vcl_test_cl_udp -c [-w ] + * + * Options: + * -s Start as server bound to specified IP address + * -c Start as client connecting to specified IP address + * -w Number of worker threads (default: 1) + */ + #include #include #include +#include +#include +#include +#include +#include +#include #include #include @@ -25,10 +44,20 @@ typedef struct vtclu_main_ struct sockaddr_storage clnt_addr; }; uint16_t port; + int num_workers; + pthread_t *worker_threads; + int thread_id_counter; + volatile int msgs_received; } vt_clu_main_t; static vt_clu_main_t vt_clu_main; +typedef struct vtclu_worker_args_ +{ + vt_clu_main_t *vclum; + int worker_id; +} vtclu_worker_args_t; + static void vt_clu_parse_args (vt_clu_main_t *vclum, int argc, char **argv) { @@ -36,9 +65,10 @@ vt_clu_parse_args (vt_clu_main_t *vclum, int argc, char **argv) memset (vclum, 0, sizeof (*vclum)); vclum->port = VCL_TEST_SERVER_PORT; + vclum->num_workers = 1; opterr = 0; - while ((c = getopt (argc, argv, "s:c:")) != -1) + while ((c = getopt (argc, argv, "s:c:w:")) != -1) switch (c) { case 's': @@ -53,7 +83,16 @@ vt_clu_parse_args (vt_clu_main_t *vclum, int argc, char **argv) if (inet_pton ( AF_INET, optarg, &((struct sockaddr_in *) &vclum->clnt_addr)->sin_addr) != 1) - break; + vtwrn ("couldn't parse ipv4 addr %s", optarg); + break; + case 'w': + vclum->num_workers = atoi (optarg); + if (vclum->num_workers <= 0) + { + vtwrn ("invalid number of workers %s", optarg); + vclum->num_workers = 1; + } + break; } if (vclum->app_type == VT_CLU_TYPE_NONE) @@ -65,81 +104,163 @@ vt_clu_parse_args (vt_clu_main_t *vclum, int argc, char **argv) vclum->endpt.is_ip4 = 1; vclum->endpt.ip = (uint8_t *) &((struct sockaddr_in *) &vclum->srvr_addr)->sin_addr; - vclum->endpt.port = htons (vclum->endpt.port); + vclum->endpt.port = htons (vclum->port); } -int -main (int argc, char **argv) +static int +vt_clu_test_done (vt_clu_main_t *vclum) { - vt_clu_main_t *vclum = &vt_clu_main; + return vclum->msgs_received >= vclum->num_workers; +} + +__thread char ep_ip_str[INET_ADDRSTRLEN + 16]; + +static char * +vt_clu_ep_to_str (vppcom_endpt_t *ep) +{ + inet_ntop (AF_INET, ep->ip, ep_ip_str, INET_ADDRSTRLEN); + snprintf (ep_ip_str + strlen (ep_ip_str), + INET_ADDRSTRLEN - strlen (ep_ip_str), ":%d", ntohs (ep->port)); + return ep_ip_str; +} + +__thread jmp_buf sig_jmp_buf; + +static void +vt_clu_sig_handler (int sig) +{ + longjmp (sig_jmp_buf, 1); +} + +void +vt_clu_catch_sig (void (*handler) (int)) +{ + signal (SIGUSR1, handler); +} + +void +vt_clu_handle_sig (vt_clu_main_t *vclum, int worker_id) +{ + vtinf ("Worker %d interrupted", worker_id); + vclum->msgs_received = vclum->num_workers; +} + +static void * +vt_clu_server_worker (void *arg) +{ + vtclu_worker_args_t *args = (vtclu_worker_args_t *) arg; + vt_clu_main_t *vclum = args->vclum; + int worker_id = args->worker_id; int rv, vcl_sh; const int buflen = 64; char buf[buflen]; - struct sockaddr_in _addr; vppcom_endpt_t rmt_ep = { .ip = (void *) &_addr }; - vt_clu_parse_args (vclum, argc, argv); + if (worker_id) + vppcom_worker_register (); - rv = vppcom_app_create ("vcl_test_cl_udp"); - if (rv) - vtfail ("vppcom_app_create()", rv); + vtinf ("Server worker %d starting", worker_id); vcl_sh = vppcom_session_create (VPPCOM_PROTO_UDP, 0 /* is_nonblocking */); if (vcl_sh < 0) { vterr ("vppcom_session_create()", vcl_sh); - return vcl_sh; + return NULL; } - if (vclum->app_type == VT_CLU_TYPE_SERVER) + /* Bind to the same endpoint as main thread */ + rv = vppcom_session_bind (vcl_sh, &vclum->endpt); + if (rv < 0) { - /* Listen is implicit */ - rv = vppcom_session_bind (vcl_sh, &vclum->endpt); - if (rv < 0) - { - vterr ("vppcom_session_bind()", rv); - return rv; - } + vterr ("vppcom_session_bind()", rv); + return NULL; + } + + vt_clu_catch_sig (vt_clu_sig_handler); + if (setjmp (sig_jmp_buf)) + vt_clu_handle_sig (vclum, worker_id); + /* Server worker loop */ + while (!vt_clu_test_done (vclum)) + { rv = vppcom_session_recvfrom (vcl_sh, buf, buflen, 0, &rmt_ep); if (rv < 0) { - vterr ("vppcom_session_recvfrom()", rv); - return rv; + vtwrn ("worker %d: recvfrom returned %d", worker_id, rv); + break; } buf[rv] = 0; - vtinf ("Received message from client: %s", buf); - char *msg = "hello cl udp client"; - int msg_len = strnlen (msg, buflen); - memcpy (buf, msg, msg_len); + vtinf ("Worker %d received message from client %s: %s", worker_id, + vt_clu_ep_to_str (&rmt_ep), buf); + vt_atomic_add (&vclum->msgs_received, 1); + + char response[buflen]; + int msg_len = + snprintf (response, buflen, "hello from worker %d", worker_id); + /* send 2 times to be sure */ for (int i = 0; i < 2; i++) { - rv = vppcom_session_sendto (vcl_sh, buf, msg_len, 0, &rmt_ep); + rv = vppcom_session_sendto (vcl_sh, response, msg_len, 0, &rmt_ep); if (rv < 0) { - vterr ("vppcom_session_sendto()", rv); - return rv; + vtwrn ("worker %d: sendto returned %d", worker_id, rv); + break; } usleep (500); } } - else if (vclum->app_type == VT_CLU_TYPE_CLIENT) + + vppcom_session_close (vcl_sh); + vtinf ("Server worker %d exiting", worker_id); + return NULL; +} + +static void * +vt_clu_client_worker (void *arg) +{ + vtclu_worker_args_t *args = (vtclu_worker_args_t *) arg; + vt_clu_main_t *vclum = args->vclum; + int worker_id = args->worker_id; + int rv, vcl_sh; + const int buflen = 64; + char buf[buflen]; + struct sockaddr_in _addr; + vppcom_endpt_t rmt_ep = { .ip = (void *) &_addr }; + + if (worker_id) + vppcom_worker_register (); + + vtinf ("Client worker %d starting", worker_id); + + vcl_sh = vppcom_session_create (VPPCOM_PROTO_UDP, 0 /* is_nonblocking */); + if (vcl_sh < 0) { - char *msg = "hello cl udp server"; - int msg_len = strnlen (msg, buflen); - memcpy (buf, msg, msg_len); + vterr ("vppcom_session_create()", vcl_sh); + return NULL; + } + + char message[buflen]; + int msg_len = + snprintf (message, buflen, "hello from client worker %d", worker_id); + + vt_clu_catch_sig (vt_clu_sig_handler); + if (setjmp (sig_jmp_buf)) + vt_clu_handle_sig (vclum, worker_id); + while (!vt_clu_test_done (vclum)) + { /* send 3 times to be sure */ for (int i = 0; i < 3; i++) { - rv = vppcom_session_sendto (vcl_sh, buf, msg_len, 0, &vclum->endpt); + rv = + vppcom_session_sendto (vcl_sh, message, msg_len, 0, &vclum->endpt); if (rv < 0) { - vterr ("vppcom_session_sendto()", rv); - return rv; + vtwrn ("worker %d: sendto returned %d", worker_id, rv); + goto cleanup; } usleep (500); } @@ -147,10 +268,90 @@ main (int argc, char **argv) rv = vppcom_session_recvfrom (vcl_sh, buf, buflen, 0, &rmt_ep); if (rv < 0) { - vterr ("vppcom_session_recvfrom()", rv); - return rv; + vtwrn ("worker %d: recvfrom returned %d", worker_id, rv); + goto cleanup; } buf[rv] = 0; - vtinf ("Received message from server: %s", buf); + + vtinf ("Worker %d received message from server %s: %s", worker_id, + vt_clu_ep_to_str (&rmt_ep), buf); + + vt_atomic_add (&vclum->msgs_received, 1); + } + +cleanup: + vppcom_session_close (vcl_sh); + vtinf ("Client worker %d exiting", worker_id); + return NULL; +} + +int +main (int argc, char **argv) +{ + vt_clu_main_t *vclum = &vt_clu_main; + int rv; + + vt_clu_parse_args (vclum, argc, argv); + + rv = vppcom_app_create ("vcl_test_cl_udp"); + if (rv) + vtfail ("vppcom_app_create()", rv); + + vtinf ("Starting %s with %d worker(s)", + vclum->app_type == VT_CLU_TYPE_SERVER ? "server" : "client", + vclum->num_workers); + + vclum->worker_threads = calloc (vclum->num_workers, sizeof (pthread_t)); + vtclu_worker_args_t *worker_args = + calloc (vclum->num_workers, sizeof (vtclu_worker_args_t)); + + if (!vclum->worker_threads || !worker_args) + { + vterr ("Failed to allocate memory for worker threads", -1); + return -1; } + + void *(*worker_func) (void *) = (vclum->app_type == VT_CLU_TYPE_SERVER) ? + vt_clu_server_worker : + vt_clu_client_worker; + + /* Create worker threads */ + for (int i = 1; i < vclum->num_workers; i++) + { + worker_args[i].vclum = vclum; + worker_args[i].worker_id = i; + + rv = pthread_create (&vclum->worker_threads[i], NULL, worker_func, + &worker_args[i]); + if (rv != 0) + { + vterr ("Failed to create worker thread", rv); + /* Clean up any threads that were created */ + for (int j = 0; j < i; j++) + pthread_cancel (vclum->worker_threads[j]); + return rv; + } + } + + /* First worker */ + worker_args[0].vclum = vclum; + worker_args[0].worker_id = 0; + worker_func (worker_args); + + /* Wait for all worker threads to complete */ + while (!vt_clu_test_done (vclum)) + ; + + for (int i = 1; i < vclum->num_workers; i++) + { + pthread_kill (vclum->worker_threads[i], SIGUSR1); + pthread_join (vclum->worker_threads[i], NULL); + } + + free (vclum->worker_threads); + free (worker_args); + vtinf ("All worker threads completed"); + + vppcom_app_destroy (); + return 0; } \ No newline at end of file diff --git a/test/asf/test_vcl.py b/test/asf/test_vcl.py index 6044f76f83..76a368f28d 100644 --- a/test/asf/test_vcl.py +++ b/test/asf/test_vcl.py @@ -569,6 +569,7 @@ class VCLThruHostStackCLUDPEcho(VCLTestCase): @classmethod def setUpClass(cls): + cls.session_startup = ["poll-main", "use-app-socket-api"] super(VCLThruHostStackCLUDPEcho, cls).setUpClass() @classmethod @@ -578,6 +579,8 @@ def tearDownClass(cls): def setUp(self): super(VCLThruHostStackCLUDPEcho, self).setUp() + self.sapi_server_sock = "1" + self.sapi_client_sock = "2" self.thru_host_stack_setup() self.pre_test_sleep = 2 self.timeout = 5 @@ -597,6 +600,99 @@ def test_vcl_thru_host_stack_cl_udp_echo(self): client_args, ) + def test_vcl_thru_host_stack_cl_udp_mt_echo(self): + """run VCL IPv4 thru host stack CL UDP MT echo test""" + server_args = ["-s", self.loop0.local_ip4, "-w", "2"] + client_args = ["-c", self.loop0.local_ip4, "-w", "2"] + self.thru_host_stack_test( + "vcl_test_cl_udp", + server_args, + "vcl_test_cl_udp", + client_args, + ) + + def show_commands_at_teardown(self): + self.logger.debug(self.vapi.cli("show app server")) + self.logger.debug(self.vapi.cli("show session verbose")) + self.logger.debug(self.vapi.cli("show app mq")) + + +@unittest.skipIf( + "hs_apps" in config.excluded_plugins, "Exclude tests requiring hs_apps plugin" +) +class VCLThruHostStackCLUDPBinds(VCLTestCase): + """VCL Thru Host Stack CL UDP Binds""" + + @classmethod + def setUpClass(cls): + cls.session_startup = ["poll-main", "use-app-socket-api"] + super(VCLThruHostStackCLUDPBinds, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(VCLThruHostStackCLUDPBinds, cls).tearDownClass() + + def setUp(self): + super(VCLThruHostStackCLUDPBinds, self).setUp() + + self.sapi_server_sock = "default" + self.timeout = 5 + + self.vapi.session_enable_disable(is_enable=1) + self.create_loopback_interfaces(2) + for i in self.lo_interfaces: + i.admin_up() + i.config_ip4() + + def tearDown(self): + for i in self.lo_interfaces: + i.unconfig_ip4() + i.admin_down() + i.remove_vpp_config() + super(VCLThruHostStackCLUDPBinds, self).tearDown() + + def test_vcl_thru_host_stack_cl_udp_multiple_binds(self): + """run VCL IPv4 thru host stack CL UDP multiple binds test""" + + # 2 CL UDP servers bound to the same port but different IPs + server1_args = ["-s", self.loop0.local_ip4, "-w", "2"] + server2_args = ["-s", self.loop1.local_ip4, "-w", "2"] + + sapi_sock = "%s/app_ns_sockets/%s" % (self.tempdir, self.sapi_server_sock) + self.vcl_app_env = { + "VCL_APP_SCOPE_GLOBAL": "true", + "VCL_VPP_SAPI_SOCKET": sapi_sock, + } + + worker_server1 = VCLAppWorker( + "vcl_test_cl_udp", server1_args, self.logger, self.vcl_app_env, "server1" + ) + worker_server1.start() + self.sleep(0.5) + + worker_server2 = VCLAppWorker( + "vcl_test_cl_udp", server2_args, self.logger, self.vcl_app_env, "server2" + ) + worker_server2.start() + self.sleep(0.5) + + session_output = self.vapi.cli("show session verbose") + self.logger.debug(session_output) + self.assertIn(self.loop0.local_ip4, session_output) + self.assertIn(self.loop1.local_ip4, session_output) + self.assertIn("[U]", session_output) + self.assertIn("LISTEN", session_output) + + try: + worker_server1.process.send_signal(signal.SIGUSR1) + worker_server2.process.send_signal(signal.SIGUSR1) + except (AttributeError, OSError) as e: + self.logger.warning(f"Failed to send SIGUSR1: {e}") + + self.sleep(0.5) + + worker_server2.join(self.timeout) + def show_commands_at_teardown(self): self.logger.debug(self.vapi.cli("show app server")) self.logger.debug(self.vapi.cli("show session verbose")) From 0d075407f335d48f790d6e2d70819047c1c920ca Mon Sep 17 00:00:00 2001 From: Anna Neiman Date: Tue, 27 May 2025 15:28:53 +0300 Subject: [PATCH 067/313] arp: checks for null in add neighbor Type: fix Details: I have the situation that ip_neighbor_learn is called for an already deleted interface. The reproduction sequence is following 1. arp_input -> arp_reply on some worker 2. call ip_neighbor_learn_dp , so request to perform ip_neighbor_learn on the vpp_main thread 3. the vpp_main thread is very busy - at the same moment we remove most of l2 interfaces and vrfs under barrier sync, including the TX interface of arp_reply 4. call ip_neighbor_learn in the main thread , when the appropriate interface is already deleted Change-Id: I69b167ba919d57f19d6b941260243bca889c31c1 Signed-off-by: Anna Neiman --- src/vnet/ip-neighbor/ip_neighbor.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vnet/ip-neighbor/ip_neighbor.c b/src/vnet/ip-neighbor/ip_neighbor.c index 73fa0b3031..ab3bd7eac4 100644 --- a/src/vnet/ip-neighbor/ip_neighbor.c +++ b/src/vnet/ip-neighbor/ip_neighbor.c @@ -335,6 +335,8 @@ ip_neighbor_adj_fib_add (ip_neighbor_t * ipn, u32 fib_index) } else { + if (INDEX_INVALID == fib_index) + return; fib_protocol_t fproto; fproto = ip_address_family_to_fib_proto (af); @@ -552,6 +554,9 @@ ip_neighbor_add (const ip_address_t * ip, /* main thread only */ ASSERT (0 == vlib_get_thread_index ()); + if (!vnet_sw_interface_is_valid (vnet_get_main (), sw_if_index)) + return 0; + fproto = ip_address_family_to_fib_proto (ip_addr_version (ip)); const ip_neighbor_key_t key = { From 6a6ffc52fc3d6d9cc0309d4e90af8a3487acfa4b Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Mon, 17 Mar 2025 13:37:18 +0000 Subject: [PATCH 068/313] interface: add support for proper checksum handling Type: improvement Signed-off-by: Mohsin Kazmi Change-Id: I035acc97abb1ce63ce09019b790ee81c803d5d90 --- src/vnet/interface_output.c | 64 ++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/vnet/interface_output.c b/src/vnet/interface_output.c index 8f023a84bf..7991f6c90d 100644 --- a/src/vnet/interface_output.c +++ b/src/vnet/interface_output.c @@ -167,10 +167,32 @@ vnet_interface_output_trace (vlib_main_t * vm, } static_always_inline void -vnet_interface_output_handle_offload (vlib_main_t *vm, vlib_buffer_t *b) +vnet_interface_output_handle_offload (vlib_main_t *vm, vlib_buffer_t *b, + vnet_hw_if_caps_t caps) { + u8 oflags; if (b->flags & VNET_BUFFER_F_GSO) return; + oflags = vnet_buffer (b)->oflags; + + if ((caps & VNET_HW_IF_CAP_TX_CKSUM) == VNET_HW_IF_CAP_TX_CKSUM) + { + if (((oflags & VNET_BUFFER_OFFLOAD_F_TNL_IPIP) != + VNET_BUFFER_OFFLOAD_F_TNL_IPIP) && + ((oflags & VNET_BUFFER_OFFLOAD_F_TNL_VXLAN) != + VNET_BUFFER_OFFLOAD_F_TNL_VXLAN)) + return; + + if (((oflags & VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM) != + VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM) && + ((oflags & VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM) != + VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM)) + { + if ((caps & VNET_HW_IF_CAP_TX_FIXED_OFFSET) != + VNET_HW_IF_CAP_TX_FIXED_OFFSET) + return; + } + } vnet_calc_checksums_inline (vm, b, b->flags & VNET_BUFFER_F_IS_IP4, b->flags & VNET_BUFFER_F_IS_IP6); vnet_calc_outer_checksums_inline (vm, b); @@ -180,8 +202,8 @@ static_always_inline uword vnet_interface_output_node_inline (vlib_main_t *vm, u32 sw_if_index, vlib_combined_counter_main_t *ccm, vlib_buffer_t **b, void **p, - u32 config_index, u8 arc, u32 n_left, - int processing_level) + vnet_hw_if_caps_t caps, u32 config_index, + u8 arc, u32 n_left, int processing_level) { u32 n_bytes = 0; u32 n_bytes0, n_bytes1, n_bytes2, n_bytes3; @@ -257,10 +279,10 @@ vnet_interface_output_node_inline (vlib_main_t *vm, u32 sw_if_index, if (processing_level >= 1 && (or_flags & VNET_BUFFER_F_OFFLOAD)) { - vnet_interface_output_handle_offload (vm, b[0]); - vnet_interface_output_handle_offload (vm, b[1]); - vnet_interface_output_handle_offload (vm, b[2]); - vnet_interface_output_handle_offload (vm, b[3]); + vnet_interface_output_handle_offload (vm, b[0], caps); + vnet_interface_output_handle_offload (vm, b[1], caps); + vnet_interface_output_handle_offload (vm, b[2], caps); + vnet_interface_output_handle_offload (vm, b[3], caps); } n_left -= 4; @@ -295,8 +317,8 @@ vnet_interface_output_node_inline (vlib_main_t *vm, u32 sw_if_index, vlib_increment_combined_counter (ccm, ti, tx_swif0, 1, n_bytes0); } - if (processing_level >= 1) - vnet_interface_output_handle_offload (vm, b[0]); + if ((processing_level >= 1) && (b[0]->flags & VNET_BUFFER_F_OFFLOAD)) + vnet_interface_output_handle_offload (vm, b[0], caps); n_left -= 1; b += 1; @@ -639,27 +661,31 @@ VLIB_NODE_FN (vnet_interface_output_node) ccm = im->combined_sw_if_counters + VNET_INTERFACE_COUNTER_TX; - /* if not all three flags IP4_,TCP_,UDP_CKSUM set, do compute them - * here before sending to the interface */ - if ((hi->caps & VNET_HW_IF_CAP_TX_CKSUM) != VNET_HW_IF_CAP_TX_CKSUM) + /* if not all three flags IP4_,TCP_,UDP_, IP4_OUTER_, UDP_OUTER_CKSUM set, do + * compute them here before sending to the interface */ + if ((hi->caps & VNET_HW_IF_CAP_TX_CKSUM_MASK) != + VNET_HW_IF_CAP_TX_CKSUM_MASK) do_tx_offloads = 1; // basic processing if (do_tx_offloads == 0 && arc_or_subif == 0 && is_parr == 0) n_bytes = vnet_interface_output_node_inline ( - vm, sw_if_index, ccm, bufs, NULL, config_index, arc, n_buffers, 0); + vm, sw_if_index, ccm, bufs, NULL, 0, config_index, arc, n_buffers, 0); // basic processing + tx offloads else if (do_tx_offloads == 1 && arc_or_subif == 0 && is_parr == 0) - n_bytes = vnet_interface_output_node_inline ( - vm, sw_if_index, ccm, bufs, NULL, config_index, arc, n_buffers, 1); + n_bytes = vnet_interface_output_node_inline (vm, sw_if_index, ccm, bufs, + NULL, hi->caps, config_index, + arc, n_buffers, 1); // basic processing + tx offloads + vlans + arcs else if (do_tx_offloads == 1 && arc_or_subif == 1 && is_parr == 0) - n_bytes = vnet_interface_output_node_inline ( - vm, sw_if_index, ccm, bufs, NULL, config_index, arc, n_buffers, 2); + n_bytes = vnet_interface_output_node_inline (vm, sw_if_index, ccm, bufs, + NULL, hi->caps, config_index, + arc, n_buffers, 2); // basic processing + tx offloads + vlans + arcs + multi-txqs else - n_bytes = vnet_interface_output_node_inline ( - vm, sw_if_index, ccm, bufs, p, config_index, arc, n_buffers, 3); + n_bytes = vnet_interface_output_node_inline (vm, sw_if_index, ccm, bufs, p, + hi->caps, config_index, arc, + n_buffers, 3); from = vlib_frame_vector_args (frame); if (PREDICT_TRUE (next_index == VNET_INTERFACE_OUTPUT_NEXT_TX)) From 6628ef6647a7a985e14cbdcb10f2ad12a6cd5795 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 16 Jun 2025 08:17:02 -0400 Subject: [PATCH 069/313] hs-test: WaitForCoreDump improvement - multiple core files support - choose correct app binary in gdb bt (before vpp only) - paltform independent solib-search-path (before x86_64 only) Type: test Change-Id: I70f48defcdfc6821e321b4b15ba95ad245407db0 Signed-off-by: Matus Fabian --- extras/hs-test/hs_test.sh | 5 ++- extras/hs-test/infra/hst_suite.go | 55 ++++++++++++++++++++++++------- extras/hs-test/infra/utils.go | 19 +++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index 0254d0b5a9..96d287a658 100644 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -204,9 +204,8 @@ Suite: Message:\n" + ( if .ReportEntries? then - (.ReportEntries[] | select(.Name == "VPP Backtrace") | - "\tVPP crashed -Full Back Trace: + (.ReportEntries[] | select(.Name | contains("Backtrace")) | + "\tFull Back Trace: \(.Value.Representation | ltrimstr("{{red}}") | rtrimstr("{{/}}"))" ) // "\(.Failure.Message)" else diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index 039d0047f9..dc06934e4c 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -299,8 +299,13 @@ func (s *HstSuite) SkipIfArm() { } } +type coreInfo struct { + file string + binPath string +} + func (s *HstSuite) WaitForCoreDump() bool { - var filename string + var coreFiles []coreInfo dir, err := os.Open(s.getLogDirPath()) if err != nil { s.Log(err) @@ -314,15 +319,21 @@ func (s *HstSuite) WaitForCoreDump() bool { return false } for _, file := range files { - if strings.Contains(file, "core") { - filename = file + coreBin, isCore := s.GetCoreProcessName(s.getLogDirPath() + file) + if isCore { + coreFiles = append(coreFiles, coreInfo{file, coreBin}) } } timeout := 60 waitTime := 5 - if filename != "" { - corePath := s.getLogDirPath() + filename + if len(coreFiles) == 0 { + return false + } + arch, _ := exechelper.Output("uname -m") + archStr := strings.TrimSpace(string(arch)) + for _, core := range coreFiles { + corePath := s.getLogDirPath() + core.file s.Log(fmt.Sprintf("WAITING FOR CORE DUMP (%s)", corePath)) for i := waitTime; i <= timeout; i += waitTime { fileInfo, err := os.Stat(corePath) @@ -340,13 +351,33 @@ func (s *HstSuite) WaitForCoreDump() bool { if *IsDebugBuild { debug = "_debug" } - vppBinPath := fmt.Sprintf("../../build-root/build-vpp%s-native/vpp/bin/vpp", debug) - pluginsLibPath := fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/x86_64-linux-gnu/vpp_plugins", debug) - cmd := fmt.Sprintf("sudo gdb %s -c %s -ex 'set solib-search-path %s/%s' -ex 'bt full' -batch", vppBinPath, corePath, *VppSourceFileDir, pluginsLibPath) + var binPath, libPath string + if strings.Contains(core.binPath, "vpp") { + binPath = fmt.Sprintf("../../build-root/build-vpp%s-native/vpp/bin/vpp", debug) + libPath = fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/%s-linux-gnu/vpp_plugins", debug, archStr) + + } else { + binPath = core.binPath + // this was most likely LDP and we want symbol table + libPath = fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/%s-linux-gnu", debug, archStr) + } + cmd := fmt.Sprintf("sudo gdb %s -c %s -ex 'set solib-search-path %s/%s' -ex 'bt full' -batch", binPath, corePath, *VppSourceFileDir, libPath) s.Log(cmd) output, _ := exechelper.Output(cmd) - AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)}) - os.WriteFile(s.getLogDirPath()+"backtrace.log", output, os.FileMode(0644)) + if strings.Contains(core.binPath, "vpp") { + AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)}) + } else { + AddReportEntry("APP Backtrace", StringerStruct{Label: string(output)}) + } + f, err := os.OpenFile(s.getLogDirPath()+"backtrace.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.FileMode(0644)) + if err != nil { + s.Log("Error opening backtrace.log: " + fmt.Sprint(err)) + } else { + if _, err := f.Write(output); err != nil { + s.Log("Error writing backtrace.log: " + fmt.Sprint(err)) + } + f.Close() + } if RunningInCi { err = os.Remove(corePath) if err == nil { @@ -355,11 +386,11 @@ func (s *HstSuite) WaitForCoreDump() bool { s.Log(err) } } - return true + break } } } - return false + return true } func (s *HstSuite) ResetContainers() { diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index 8be7fb510c..e320345d5a 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -12,6 +12,8 @@ import ( "os/exec" "strings" "time" + + "github.com/edwarnicke/exechelper" ) const networkTopologyDir string = "topo-network/" @@ -308,3 +310,20 @@ func (s *HstSuite) StartClientApp(c *Container, cmd string, clnRes <- o } } + +func (s *HstSuite) GetCoreProcessName(file string) (string, bool) { + cmd := fmt.Sprintf("sudo file -b %s", file) + output, _ := exechelper.Output(cmd) + outputStr := string(output) + // ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'vpp -c /tmp/server/etc/vpp/startup.conf', real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, execfn: '/usr/bin/vpp', platform: 'x86_64' + if !strings.Contains(outputStr, "core file") { + return "", false + } + soutputSplit := strings.Split(outputStr, ",") + for _, tmp := range soutputSplit { + if strings.Contains(tmp, "execfn:") { + return strings.Trim(strings.Split(tmp, ": ")[1], "'"), true + } + } + return "", false +} From 380bdcfd6733cfdef7fe65cb8720bccc48aac41f Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 14 Jun 2025 18:19:14 -0400 Subject: [PATCH 070/313] vcl: validate ldp select bitmap only if bits set Type: fix Change-Id: Ic873cb9511cf70619722e7b0f58211ad2a2a6772 Signed-off-by: Florin Coras --- src/vcl/ldp.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/vcl/ldp.c b/src/vcl/ldp.c index 7e4cddbb46..ffd7b045de 100644 --- a/src/vcl/ldp.c +++ b/src/vcl/ldp.c @@ -651,14 +651,15 @@ ldp_select_init_maps (fd_set * __restrict original, clib_memcpy_fast (*resultb, original, n_bytes); memset (original, 0, n_bytes); - clib_bitmap_foreach (fd, *resultb) { - if (fd > nfds) - break; - vlsh = ldp_fd_to_vlsh (fd); - if (vlsh == VLS_INVALID_HANDLE) - clib_bitmap_set_no_check (*libcb, fd, 1); - else - { + clib_bitmap_foreach (fd, *resultb) + { + if (fd > nfds) + break; + vlsh = ldp_fd_to_vlsh (fd); + if (vlsh == VLS_INVALID_HANDLE) + clib_bitmap_set_no_check (*libcb, fd, 1); + else + { vlsh_to_session_and_worker_index (vlsh, &session_index, &wrk_index); if (wrk_index != vppcom_worker_index ()) clib_warning ( @@ -666,12 +667,13 @@ ldp_select_init_maps (fd_set * __restrict original, wrk_index, vppcom_worker_index ()); else *vclb = clib_bitmap_set (*vclb, session_index, 1); - } - } + } + } si_bits_set = clib_bitmap_last_set (*vclb) + 1; *si_bits = (si_bits_set > *si_bits) ? si_bits_set : *si_bits; - clib_bitmap_validate (*resultb, *si_bits); + if (*si_bits) + clib_bitmap_validate (*resultb, *si_bits); libc_bits_set = clib_bitmap_last_set (*libcb) + 1; *libc_bits = (libc_bits_set > *libc_bits) ? libc_bits_set : *libc_bits; From d2bf69ddeb80dc62ad97596aa2e79ddac533fb2a Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sun, 15 Jun 2025 20:50:26 -0700 Subject: [PATCH 071/313] udp: add basic unit test For now test if binds work as expected. Type: improvement Signed-off-by: Florin Coras Change-Id: I3227c5b298763dd8d48ef1bf4858cb66df9aeafd --- extras/hs-test/unittests_test.go | 5 + src/plugins/unittest/CMakeLists.txt | 1 + src/plugins/unittest/udp_test.c | 314 ++++++++++++++++++++++++++++ 3 files changed, 320 insertions(+) create mode 100644 src/plugins/unittest/udp_test.c diff --git a/extras/hs-test/unittests_test.go b/extras/hs-test/unittests_test.go index 26faca572b..1ac358c8a4 100644 --- a/extras/hs-test/unittests_test.go +++ b/extras/hs-test/unittests_test.go @@ -24,6 +24,11 @@ func TcpUnitTest(s *NoTopoSuite) { runUnitTest(s, "test tcp all") } +func UdpUnitTest(s *NoTopoSuite) { + s.SkipIfNotCoverage() + runUnitTest(s, "test udp all") +} + func SvmUnitTest(s *NoTopoSuite) { s.SkipIfNotCoverage() runUnitTest(s, "test svm fifo all") diff --git a/src/plugins/unittest/CMakeLists.txt b/src/plugins/unittest/CMakeLists.txt index 0382841379..ab9168fd45 100644 --- a/src/plugins/unittest/CMakeLists.txt +++ b/src/plugins/unittest/CMakeLists.txt @@ -56,6 +56,7 @@ add_vpp_plugin(unittest tcp_test.c test_buffer.c unittest.c + udp_test.c util_test.c vlib_test.c counter_test.c diff --git a/src/plugins/unittest/udp_test.c b/src/plugins/unittest/udp_test.c new file mode 100644 index 0000000000..e15aa96b77 --- /dev/null +++ b/src/plugins/unittest/udp_test.c @@ -0,0 +1,314 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#include +#include +#include +#include + +#define UDP_TEST_I(_cond, _comment, _args...) \ + ({ \ + int _evald = (_cond); \ + if (!(_evald)) \ + { \ + fformat (stderr, "FAIL:%d: " _comment "\n", __LINE__, ##_args); \ + } \ + else \ + { \ + fformat (stderr, "PASS:%d: " _comment "\n", __LINE__, ##_args); \ + } \ + _evald; \ + }) + +#define UDP_TEST(_cond, _comment, _args...) \ + { \ + if (!UDP_TEST_I (_cond, _comment, ##_args)) \ + { \ + return 1; \ + } \ + } + +int +udp_test_connected_callback (u32 app_index, u32 api_context, session_t *s, + session_error_t err) +{ + return 0; +} + +int +udp_test_add_segment_callback (u32 client_index, u64 segment_handle) +{ + return 0; +} + +int +udp_test_del_segment_callback (u32 client_index, u64 segment_handle) +{ + return 0; +} + +int +udp_test_accept_callback (session_t *s) +{ + clib_warning ("called..."); + return 0; +} + +int +udp_test_server_rx_callback (session_t *s) +{ + clib_warning ("called..."); + return -1; +} + +void +udp_test_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf) +{ + clib_warning ("called..."); +} + +void +udp_test_disconnect_callback (session_t *s) +{ + vnet_disconnect_args_t da = { + .handle = session_handle (s), + .app_index = app_worker_get (s->app_wrk_index)->app_index + }; + vnet_disconnect_session (&da); +} + +void +udp_test_reset_callback (session_t *s) +{ + clib_warning ("called..."); +} + +static session_cb_vft_t udp_test_session_cbs = { + .session_connected_callback = udp_test_connected_callback, + .session_accept_callback = udp_test_accept_callback, + .session_disconnect_callback = udp_test_disconnect_callback, + .session_reset_callback = udp_test_reset_callback, + .session_cleanup_callback = udp_test_cleanup_callback, + .builtin_app_rx_callback = udp_test_server_rx_callback, + .add_segment_callback = udp_test_add_segment_callback, + .del_segment_callback = udp_test_del_segment_callback, +}; + +static int +udp_create_lookpback (u32 table_id, u32 *sw_if_index, ip4_address_t *intf_addr) +{ + u8 intf_mac[6]; + + clib_memset (intf_mac, 0, sizeof (intf_mac)); + + if (vnet_create_loopback_interface (sw_if_index, intf_mac, 0, 0)) + { + clib_warning ("couldn't create loopback. stopping the test!"); + return -1; + } + + if (table_id != 0) + { + ip_table_create (FIB_PROTOCOL_IP4, table_id, 0 /* is_api */, + 1 /* create_mfib */, 0); + ip_table_bind (FIB_PROTOCOL_IP4, *sw_if_index, table_id); + } + + vnet_sw_interface_set_flags (vnet_get_main (), *sw_if_index, + VNET_SW_INTERFACE_FLAG_ADMIN_UP); + + if (ip4_add_del_interface_address (vlib_get_main (), *sw_if_index, intf_addr, + 24, 0)) + { + clib_warning ("couldn't assign loopback ip %U", format_ip4_address, + intf_addr); + return -1; + } + + return 0; +} + +static void +udp_delete_loopback (u32 sw_if_index) +{ + /* loopback interface deletion fails */ + /* vnet_delete_loopback_interface (sw_if_index); */ + + vnet_sw_interface_set_flags (vnet_get_main (), sw_if_index, 0); +} + +#define UDP_TEST_NO_RESULT 1e6 +static volatile int udp_test_connect_rpc_result = UDP_TEST_NO_RESULT; + +static void +udp_test_connect_rpc (void *args) +{ + vnet_connect_args_t *a = (vnet_connect_args_t *) args; + udp_test_connect_rpc_result = vnet_connect (a); + clib_mem_free (a); +} + +static session_error_t +udp_test_do_connect (vlib_main_t *vm, vnet_connect_args_t *args) +{ + if (vlib_num_workers ()) + { + vnet_connect_args_t *rpc_args = clib_mem_alloc (sizeof (*args)); + clib_memcpy_fast (rpc_args, args, sizeof (*args)); + session_send_rpc_evt_to_thread (transport_cl_thread (), + udp_test_connect_rpc, rpc_args); + + while (udp_test_connect_rpc_result == UDP_TEST_NO_RESULT) + { + vlib_worker_thread_barrier_release (vm); + vlib_process_suspend (vm, 100e-3); + vlib_worker_thread_barrier_sync (vm); + } + return udp_test_connect_rpc_result; + } + else + { + return vnet_connect (args); + } +} + +static int +udp_test_binds (vlib_main_t *vm, unformat_input_t *input) +{ + session_endpoint_cfg_t server_sep1 = SESSION_ENDPOINT_CFG_NULL; + session_endpoint_cfg_t server_sep2 = SESSION_ENDPOINT_CFG_NULL; + u64 options[APP_OPTIONS_N_OPTIONS]; + session_error_t error; + u32 server_index; + u16 port = 1234; + int rv = 0; + + clib_memset (options, 0, sizeof (options)); + options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; + options[APP_OPTIONS_ADD_SEGMENT_SIZE] = 16 << 20; + options[APP_OPTIONS_FLAGS] |= APP_OPTIONS_FLAGS_USE_GLOBAL_SCOPE; + options[APP_OPTIONS_FLAGS] |= APP_OPTIONS_FLAGS_USE_LOCAL_SCOPE; + vnet_app_attach_args_t attach_args = { + .api_client_index = ~0, + .options = options, + .namespace_id = 0, + .session_cb_vft = &udp_test_session_cbs, + .name = format (0, "udp_test"), + }; + + error = vnet_application_attach (&attach_args); + UDP_TEST ((error == 0), "app attached"); + server_index = attach_args.app_index; + vec_free (attach_args.name); + + /* Set up first IP address (20.1.1.1) */ + server_sep1.is_ip4 = 1; + server_sep1.ip.ip4.as_u32 = clib_host_to_net_u32 (0x14010101); + udp_create_lookpback (0, &server_sep1.sw_if_index, + (ip4_address_t *) &server_sep1.ip.ip4); + server_sep1.port = clib_host_to_net_u16 (port); + server_sep1.is_ip4 = 1; + server_sep1.transport_proto = TRANSPORT_PROTO_UDP; + + vnet_listen_args_t bind_args1 = { + .sep_ext = server_sep1, + .app_index = server_index, + .wrk_map_index = 0, + }; + + /* Set up second IP address (21.1.1.1) */ + server_sep2.ip.ip4.as_u32 = clib_host_to_net_u32 (0x15020202); + udp_create_lookpback (0, &server_sep2.sw_if_index, + (ip4_address_t *) &server_sep2.ip.ip4); + server_sep2.port = clib_host_to_net_u16 (port); + server_sep2.is_ip4 = 1; + server_sep2.transport_proto = TRANSPORT_PROTO_UDP; + + vnet_listen_args_t bind_args2 = { + .sep_ext = server_sep2, + .app_index = server_index, + .wrk_map_index = 0, + }; + + /* Test server1 ip:port bind */ + error = vnet_listen (&bind_args1); + UDP_TEST ((error == 0), "server1 bind should work: %U", format_session_error, + error); + + /* Subsequent bind to same ip1:port pair should fail */ + error = vnet_listen (&bind_args1); + UDP_TEST ((error == SESSION_E_ALREADY_LISTENING), + "second server1 bind should fail: %U", format_session_error, + error); + + /* Try connecting using server1 as lcl ip1:port */ + vnet_connect_args_t connect_args = { + .sep_ext = server_sep2, + .app_index = server_index, + }; + connect_args.sep_ext.peer.ip = server_sep1.ip; + connect_args.sep_ext.peer.port = server_sep1.port; + connect_args.sep_ext.peer.is_ip4 = 1; + error = udp_test_do_connect (vm, &connect_args); + UDP_TEST ((error == 0), "connect using lcl ip1:port should work: %U", + format_session_error, error); + + /* Test server2 bind ip2:port */ + error = vnet_listen (&bind_args2); + UDP_TEST ((error == 0), "server2 bind should work: %U", format_session_error, + error); + + /* Start cleanup by detaching */ + vnet_app_detach_args_t detach_args = { + .app_index = server_index, + .api_client_index = ~0, + }; + vnet_application_detach (&detach_args); + + /* Cleanup loopbacks */ + udp_delete_loopback (server_sep1.sw_if_index); + udp_delete_loopback (server_sep2.sw_if_index); + + return rv; +} + +static clib_error_t * +udp_test (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd_arg) +{ + int res = 0; + session_enable_disable_args_t args = { .is_en = 1, + .rt_engine_type = + RT_BACKEND_ENGINE_RULE_TABLE }; + + vnet_session_enable_disable (vm, &args); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "binds")) + { + res = udp_test_binds (vm, input); + } + else if (unformat (input, "all")) + { + if ((res = udp_test_binds (vm, input))) + goto done; + } + else + break; + } + +done: + if (res) + return clib_error_return (0, "UDP unit test failed"); + + vlib_cli_output (vm, "SUCCESS"); + return 0; +} + +VLIB_CLI_COMMAND (udp_test_command, static) = { + .path = "test udp", + .short_help = "internal udp unit tests", + .function = udp_test, +}; \ No newline at end of file From 897615764bac73b9eed97b03d48bb8f92dd4eb10 Mon Sep 17 00:00:00 2001 From: Andrew Yourtchenko Date: Fri, 13 Jun 2025 16:13:27 +0200 Subject: [PATCH 072/313] http: implement HTTP PUT method This implements the HTTP PUT request with the ability to stream the data in chunks, rather than sending the entire request body at once. Type: feature Change-Id: Ib04103a4bacf76a3c0bf9483a63a2edb693276c6 Signed-off-by: Andrew Yourtchenko --- src/plugins/http/http.c | 1 + src/plugins/http/http.h | 6 +- src/plugins/http/http1.c | 141 +++++++++++++++++++++++++++++++- src/plugins/http/http_buffer.c | 100 +++++++++++++++++++++- src/plugins/http/http_buffer.h | 4 + src/plugins/http/http_private.h | 3 +- 6 files changed, 250 insertions(+), 5 deletions(-) diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 951bf3ad96..5a61b0d717 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -28,6 +28,7 @@ static http_engine_vft_t *http_vfts; const http_buffer_type_t msg_to_buf_type[] = { [HTTP_MSG_DATA_INLINE] = HTTP_BUFFER_FIFO, [HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR, + [HTTP_MSG_DATA_STREAMING] = HTTP_BUFFER_STREAMING, }; void diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 434ff965b6..bd9e40e23a 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -55,6 +55,7 @@ typedef enum http_req_method_ { HTTP_REQ_GET = 0, HTTP_REQ_POST, + HTTP_REQ_PUT, HTTP_REQ_CONNECT, HTTP_REQ_UNKNOWN, /* for internal use */ } http_req_method_t; @@ -335,7 +336,10 @@ typedef enum http_upgrade_proto_ typedef enum http_msg_data_type_ { HTTP_MSG_DATA_INLINE, - HTTP_MSG_DATA_PTR + HTTP_MSG_DATA_PTR, + HTTP_MSG_DATA_STREAMING, + /* The value below is used for boundary checks of http_msg_data_type_t */ + HTTP_MSG_DATA_N_TYPES, } http_msg_data_type_t; typedef struct http_field_line_ diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index f7d79b8955..a0aaf067da 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -55,6 +55,17 @@ static const char *post_request_template = "POST %s HTTP/1.1\r\n" "User-Agent: %v\r\n" "Content-Length: %llu\r\n"; +static const char *put_request_template = "PUT %s HTTP/1.1\r\n" + "Host: %v\r\n" + "User-Agent: %v\r\n" + "Content-Length: %llu\r\n"; + +static const char *put_chunked_request_template = + "PUT %s HTTP/1.1\r\n" + "Host: %v\r\n" + "User-Agent: %v\r\n" + "Transfer-Encoding: chunked\r\n"; + always_inline http_req_t * http1_conn_alloc_req (http_conn_t *hc) { @@ -304,6 +315,12 @@ http1_parse_request_line (http_req_t *req, u8 *rx_buf, http_status_code_t *ec) req->method = HTTP_REQ_POST; req->target_path_offset = method_offset + 5; } + else if (!memcmp (rx_buf + method_offset, "PUT ", 4)) + { + HTTP_DBG (0, "PUT method"); + req->method = HTTP_REQ_PUT; + req->target_path_offset = method_offset + 4; + } else if (!memcmp (rx_buf + method_offset, "CONNECT ", 8)) { HTTP_DBG (0, "CONNECT method"); @@ -1249,7 +1266,7 @@ http1_req_state_wait_app_reply (http_conn_t *hc, http_req_t *req, http_get_app_msg (req, &msg); - if (msg.data.type > HTTP_MSG_DATA_PTR) + if (msg.data.type >= HTTP_MSG_DATA_N_TYPES) { clib_warning ("no data"); sc = HTTP_STATUS_INTERNAL_ERROR; @@ -1363,7 +1380,7 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, http_get_app_msg (req, &msg); - if (msg.data.type > HTTP_MSG_DATA_PTR) + if (msg.data.type >= HTTP_MSG_DATA_N_TYPES) { clib_warning ("no data"); goto error; @@ -1431,6 +1448,55 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; sm_result = HTTP_SM_CONTINUE; } + else if (msg.method_type == HTTP_REQ_PUT) + { + /* Check if this is a streaming PUT */ + if (msg.data.type == HTTP_MSG_DATA_STREAMING) + { + /* + * Streaming PUT with chunked transfer encoding + */ + request = format (request, put_chunked_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name); + + http_req_tx_buffer_init (req, &msg); + + /* For streaming, we need a different state */ + next_state = HTTP_REQ_STATE_APP_IO_MORE_STREAMING_DATA; + sm_result = HTTP_SM_CONTINUE; + } + else + { + if (!msg.data.body_len) + { + clib_warning ("PUT request should include data"); + goto error; + } + /* + * Regular PUT with Content-Length + */ + request = format (request, put_request_template, + /* target */ + target, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name, + /* Content-Length */ + msg.data.body_len); + + http_req_tx_buffer_init (req, &msg); + + next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; + sm_result = HTTP_SM_CONTINUE; + } + } + else { clib_warning ("unsupported method %d", msg.method_type); @@ -1517,6 +1583,75 @@ http1_req_state_app_io_more_data (http_conn_t *hc, http_req_t *req, return HTTP_SM_STOP; } +static http_sm_result_t +http1_req_state_app_io_more_streaming_data (http_conn_t *hc, http_req_t *req, + transport_send_params_t *sp) +{ + u32 max_write, chunk_size, n_segs, n_written = 0; + http_buffer_t *hb = &req->tx_buf; + svm_fifo_seg_t *seg; + int finished = 0; + int chunk_sz_value_headroom = 20; + u8 chunk_hdr[32]; + int hdr_len; + + ASSERT (hb->type == HTTP_BUFFER_STREAMING); + + /* For streaming, check if we have data available */ + max_write = http_io_ts_max_write (hc, sp); + /* + * do not drain more than we are going to write at a max - which + * is max_write minus chunk_sz_value_headroom (overhead for the chunk + * size value) bytes to leave the room for chunk headers. + */ + if (max_write < chunk_sz_value_headroom) + { + HTTP_DBG (1, "ts tx fifo full - before write"); + goto check_fifo; + } + chunk_size = http_buffer_get_segs (hb, max_write - chunk_sz_value_headroom, + &seg, &n_segs); + if (chunk_size == 0) + { + /* No data available right now, wait for more */ + HTTP_DBG (1, "streaming: no data available"); + return HTTP_SM_STOP; + } + + /* Write chunk size in hex */ + hdr_len = + snprintf ((char *) chunk_hdr, sizeof (chunk_hdr), "%x\r\n", chunk_size); + http_io_ts_write (hc, chunk_hdr, hdr_len, sp); + + /* Write chunk data */ + n_written = http_io_ts_write_segs (hc, seg, n_segs, sp); + + /* Write chunk trailer */ + http_io_ts_write (hc, (u8 *) "\r\n", 2, sp); + + http_buffer_drain (hb, n_written); + + finished = http_buffer_bytes_left (hb) == 0; + if (finished) + { + /* Send final chunk (0-sized) */ + http_io_ts_write (hc, (u8 *) "0\r\n\r\n", 5, sp); + + /* Finished transaction: + * server back to HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD + * client to HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY */ + http_req_state_change (req, (hc->flags & HTTP_CONN_F_IS_SERVER) ? + HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD : + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + http_buffer_free (hb); + } + http_io_ts_after_write (hc, finished); + +check_fifo: + http1_check_and_deschedule (hc, req, sp); + return HTTP_SM_STOP; +} + static http_sm_result_t http1_req_state_tunnel_tx (http_conn_t *hc, http_req_t *req, transport_send_params_t *sp) @@ -1614,6 +1749,7 @@ static http_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { http1_req_state_app_io_more_data, http1_req_state_tunnel_tx, http1_req_state_udp_tunnel_tx, + http1_req_state_app_io_more_streaming_data, }; static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { @@ -1626,6 +1762,7 @@ static http_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { 0, /* app io more data */ http1_req_state_tunnel_rx, http1_req_state_udp_tunnel_rx, + 0, /* app io more streaming data */ }; static_always_inline int diff --git a/src/plugins/http/http_buffer.c b/src/plugins/http/http_buffer.c index 493abb3b8c..87a1f80844 100644 --- a/src/plugins/http/http_buffer.c +++ b/src/plugins/http/http_buffer.c @@ -16,7 +16,7 @@ #include #include -static http_buffer_vft_t buf_vfts[HTTP_BUFFER_PTR + 1]; +static http_buffer_vft_t buf_vfts[HTTP_BUFFER_N_TYPES]; #define HTTP_BUFFER_REGISTER_VFT(type, vft) \ static void __attribute__ ((constructor)) http_buf_init_##type (void) \ @@ -206,6 +206,104 @@ const static http_buffer_vft_t buf_ptr_vft = { HTTP_BUFFER_REGISTER_VFT (HTTP_BUFFER_PTR, buf_ptr_vft); +typedef struct http_buffer_streaming_ +{ + svm_fifo_t *src; + svm_fifo_seg_t *segs; + u64 total_len; /* total expected length (can be ~0 for unknown) */ + u64 sent; /* bytes sent so far */ +} http_buffer_streaming_t; + +STATIC_ASSERT (sizeof (http_buffer_streaming_t) <= HTTP_BUFFER_DATA_SZ, + "buf data"); + +static void +buf_streaming_init (http_buffer_t *hb, void *data, u64 len) +{ + svm_fifo_t *f = (svm_fifo_t *) data; + http_buffer_streaming_t *bs; + + bs = (http_buffer_streaming_t *) &hb->data; + + bs->total_len = len; + bs->sent = 0; + bs->src = f; + bs->segs = 0; +} + +static void +buf_streaming_free (http_buffer_t *hb) +{ + http_buffer_streaming_t *bs = (http_buffer_streaming_t *) &hb->data; + + bs->src = 0; + vec_free (bs->segs); +} + +static u32 +buf_streaming_get_segs (http_buffer_t *hb, u32 max_len, svm_fifo_seg_t **fs, + u32 *n_segs) +{ + http_buffer_streaming_t *bs = (http_buffer_streaming_t *) &hb->data; + + u32 _n_segs = 5; + int len; + + /* For streaming, we send whatever is available */ + u32 available = svm_fifo_max_dequeue (bs->src); + if (available == 0) + return 0; + + max_len = clib_min (available, max_len); + + vec_validate (bs->segs, _n_segs - 1); + + len = svm_fifo_segments (bs->src, 0, bs->segs, &_n_segs, max_len); + if (len < 0) + return 0; + + *n_segs = _n_segs; + + HTTP_DBG (1, "streaming: available to send %u n_segs %u", len, *n_segs); + + *fs = bs->segs; + return len; +} + +static u32 +buf_streaming_drain (http_buffer_t *hb, u32 len) +{ + http_buffer_streaming_t *bs = (http_buffer_streaming_t *) &hb->data; + + bs->sent += len; + svm_fifo_dequeue_drop (bs->src, len); + HTTP_DBG (1, "streaming: drained %u total sent %lu", len, bs->sent); + + return len; +} + +static u64 +buf_streaming_bytes_left (http_buffer_t *hb) +{ + http_buffer_streaming_t *bs = (http_buffer_streaming_t *) &hb->data; + if (bs->total_len == ~0) + { + return ~0; + } + + return (bs->total_len > bs->sent ? (bs->total_len - bs->sent) : 0); +} + +const static http_buffer_vft_t buf_streaming_vft = { + .init = buf_streaming_init, + .free = buf_streaming_free, + .get_segs = buf_streaming_get_segs, + .drain = buf_streaming_drain, + .bytes_left = buf_streaming_bytes_left, +}; + +HTTP_BUFFER_REGISTER_VFT (HTTP_BUFFER_STREAMING, buf_streaming_vft); + void http_buffer_init (http_buffer_t *hb, http_buffer_type_t type, svm_fifo_t *f, u64 data_len) diff --git a/src/plugins/http/http_buffer.h b/src/plugins/http/http_buffer.h index 6176f106a9..d487c56ff6 100644 --- a/src/plugins/http/http_buffer.h +++ b/src/plugins/http/http_buffer.h @@ -24,6 +24,10 @@ typedef enum http_buffer_type_ { HTTP_BUFFER_FIFO, HTTP_BUFFER_PTR, + HTTP_BUFFER_STREAMING, + /* the value below is used to size the structures indexed by + http_buffer_type_t */ + HTTP_BUFFER_N_TYPES, } http_buffer_type_t; typedef struct http_buffer_vft_ http_buffer_vft_t; diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index a7898cfa1d..5ad940d5c4 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -65,7 +65,8 @@ typedef enum http_conn_state_ _ (5, WAIT_APP_REPLY, "wait app reply") \ _ (6, APP_IO_MORE_DATA, "app io more data") \ _ (7, TUNNEL, "tunnel") \ - _ (8, UDP_TUNNEL, "udp tunnel") + _ (8, UDP_TUNNEL, "udp tunnel") \ + _ (9, APP_IO_MORE_STREAMING_DATA, "app io more streaming data") typedef enum http_req_state_ { From 2eb0e479bf76b5202d24d4088ea97d86ee5aa0ee Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 11 Jun 2025 11:02:25 -0400 Subject: [PATCH 073/313] http: http/2 extended connect method Type: feature Change-Id: I42e94b6282fa693d3c69f938ec9d3a290b71b9fa Signed-off-by: Matus Fabian --- extras/hs-test/h2spec_extras/h2spec_extras.go | 86 +++++++++++++++++ extras/hs-test/infra/suite_vpp_proxy.go | 1 + extras/hs-test/infra/suite_vpp_udp_proxy.go | 76 +++++++++++++++ src/plugins/http/http2/hpack.c | 14 ++- src/plugins/http/http2/hpack.h | 2 + src/plugins/http/http2/http2.c | 95 +++++++++++++------ src/plugins/http/http2/http2.h | 7 +- 7 files changed, 251 insertions(+), 30 deletions(-) diff --git a/extras/hs-test/h2spec_extras/h2spec_extras.go b/extras/hs-test/h2spec_extras/h2spec_extras.go index 6957557a01..b1d86a0b3a 100644 --- a/extras/hs-test/h2spec_extras/h2spec_extras.go +++ b/extras/hs-test/h2spec_extras/h2spec_extras.go @@ -3,6 +3,7 @@ package h2spec_extras import ( "fmt" "slices" + "strconv" "github.com/summerwind/h2spec/config" "github.com/summerwind/h2spec/spec" @@ -28,6 +29,7 @@ func Spec() *spec.TestGroup { tg.AddTestGroup(FlowControl()) tg.AddTestGroup(ConnectMethod()) + tg.AddTestGroup(ExtendedConnectMethod()) return tg } @@ -394,5 +396,89 @@ func ConnectMethod() *spec.TestGroup { return nil }, }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "The \":scheme\" and \":path\" pseudo-header fields MUST be omitted.", + Requirement: "A CONNECT request that does not conform to these restrictions is malformed.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectHeaders(c) + headers = append(headers, spec.HeaderField(":scheme", "https")) + headers = append(headers, spec.HeaderField(":path", "/")) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + + return spec.VerifyStreamError(conn, http2.ErrCodeProtocol) + }, + }) + return tg +} + +func ExtendedConnectMethod() *spec.TestGroup { + tg := NewTestGroup("3", "Extended CONNECT method") + + tg.AddTestCase(&spec.TestCase{ + Desc: "SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with value 1 received.", + Requirement: "Using a SETTINGS parameter to opt into an otherwise incompatible protocol change is a use of \"Extending HTTP/2\" defined by Section 5.5 of RFC9113.", + Run: func(c *config.Config, conn *spec.Conn) error { + + err := conn.Handshake() + if err != nil { + return err + } + + enabled, ok := conn.Settings[http2.SettingEnableConnectProtocol] + if !ok { + return &spec.TestError{ + Expected: []string{"SETTINGS_ENABLE_CONNECT_PROTOCOL received"}, + Actual: "SETTINGS_ENABLE_CONNECT_PROTOCOL not received", + } + } + if enabled != uint32(1) { + return &spec.TestError{ + Expected: []string{"SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with value 1 received"}, + Actual: "SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with value " + strconv.Itoa(int(enabled)) + " received", + } + } + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "The \":scheme\" and \":path\" pseudo-header fields MUST be included.", + Requirement: "A CONNECT request bearing the \":protocol\" pseudo-header that does not conform is malformed.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectHeaders(c) + headers = append(headers, spec.HeaderField(":protocol", "connect-udp")) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + + return spec.VerifyStreamError(conn, http2.ErrCodeProtocol) + }, + }) return tg } diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index ae3f203d71..db6b6bedf7 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -367,6 +367,7 @@ var _ = Describe("H2SpecProxySuite", Ordered, ContinueOnFailure, func() { {desc: "extras/2/2"}, {desc: "extras/2/3"}, {desc: "extras/2/4"}, + {desc: "extras/2/5"}, } for _, test := range testCases { diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index 7043864a23..29ee5b7e6d 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -1,16 +1,22 @@ package hst import ( + "bytes" "fmt" + "io" "net" + "os" "reflect" "runtime" "strconv" "strings" "time" + "fd.io/hs-test/h2spec_extras" + . "fd.io/hs-test/infra/common" . "github.com/onsi/ginkgo/v2" + "github.com/summerwind/h2spec/config" ) type VppUdpProxySuite struct { @@ -243,3 +249,73 @@ var _ = Describe("VppUdpProxyMWSuite", Ordered, ContinueOnFailure, Serial, func( } } }) + +var _ = Describe("H2SpecUdpProxySuite", Ordered, ContinueOnFailure, func() { + var s VppUdpProxySuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + testCases := []struct { + desc string + }{ + {desc: "extras/3/1"}, + {desc: "extras/3/2"}, + } + + for _, test := range testCases { + test := test + testName := "proxy_test.go/h2spec_" + strings.ReplaceAll(test.desc, "/", "_") + It(testName, func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + vppProxy := s.Containers.VppProxy.VppInstance + remoteServerConn := s.StartEchoServer() + defer remoteServerConn.Close() + cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri https://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) + s.Log(vppProxy.Vppctl(cmd)) + path := fmt.Sprintf("/.well-known/masque/udp/%s/%d/", s.ServerAddr(), s.Ports.Server) + conf := &config.Config{ + Host: s.VppProxyAddr(), + Port: s.Ports.Proxy, + Path: path, + Timeout: time.Second * s.MaxTimeout, + MaxHeaderLen: 4096, + TLS: true, + Insecure: true, + Sections: []string{test.desc}, + Verbose: true, + } + // capture h2spec output so it will be in log + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + tg := h2spec_extras.Spec() + tg.Test(conf) + + oChan := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + oChan <- buf.String() + }() + + // restore to normal state + w.Close() + os.Stdout = oldStdout + o := <-oChan + s.Log(o) + s.AssertEqual(0, tg.FailedCount) + }, SpecTimeout(TestTimeout)) + } + +}) diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 76021ae14a..324602ce34 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -813,6 +813,18 @@ hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, return HTTP2_ERROR_PROTOCOL_ERROR; } break; + case 9: + if (!memcmp (name + 1, "protocol", 8)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_PROTOCOL_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_PROTOCOL_PARSED; + control_data->protocol = value; + control_data->protocol_len = value_len; + break; + } + break; case 10: if (!memcmp (name + 1, "authority", 9)) { @@ -874,7 +886,7 @@ hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, } break; case 14: - if (!memcmp (name, "content-length", 7) && + if (!memcmp (name, "content-length", 14) && control_data->content_len_header_index == ~0) control_data->content_len_header_index = index; break; diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index 69144de133..fef2a96c2d 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -54,6 +54,8 @@ typedef struct u8 *path; u32 path_len; u8 *headers; + u8 *protocol; + u32 protocol_len; uword content_len_header_index; u32 headers_len; u32 control_data_len; diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index d1b6915ac1..19a43314f3 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -909,6 +909,10 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, HTTP_DBG (1, "decompressed headers size %u", control_data.headers_len); HTTP_DBG (1, "dynamic table size %u", h2c->decoder_dynamic_table.used); + req->base.control_data_len = control_data.control_data_len; + req->base.headers_offset = control_data.headers - wrk->header_list; + req->base.headers_len = control_data.headers_len; + if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_METHOD_PARSED)) { HTTP_DBG (1, ":method pseudo-header missing in request"); @@ -949,42 +953,77 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, } if (control_data.method == HTTP_REQ_CONNECT) { - if (control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_SCHEME_PARSED || - control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) - { - HTTP_DBG (1, ":scheme and :path pseudo-header must be omitted for " - "CONNECT method"); - http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); - return HTTP_SM_STOP; - } - /* quick check if port is present */ - p = control_data.authority + control_data.authority_len; - p--; - if (!isdigit (*p)) + if (control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PROTOCOL_PARSED) { - HTTP_DBG (1, "port not present in authority"); - http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); - return HTTP_SM_STOP; + /* extended CONNECT (RFC8441) */ + if (!(control_data.parsed_bitmap & + HPACK_PSEUDO_HEADER_SCHEME_PARSED) || + !(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED)) + { + HTTP_DBG (1, + ":scheme and :path pseudo-header must be present for " + "extended CONNECT method"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + /* parse protocol header value */ + if (0) + ; +#define _(sym, str) \ + else if (http_token_is_case ((const char *) control_data.protocol, \ + control_data.protocol_len, \ + http_token_lit (str))) \ + req->base.upgrade_proto = HTTP_UPGRADE_PROTO_##sym; + foreach_http_upgrade_proto +#undef _ + else + { + HTTP_DBG (1, "unsupported extended connect protocol %U", + format_http_bytes, control_data.protocol, + control_data.protocol_len); + http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, sp); + return HTTP_SM_STOP; + } } - p--; - for (; p > control_data.authority; p--) + else { + if (control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_SCHEME_PARSED || + control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) + { + HTTP_DBG (1, + ":scheme and :path pseudo-header must be omitted for " + "CONNECT method"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + /* quick check if port is present */ + p = control_data.authority + control_data.authority_len; + p--; if (!isdigit (*p)) - break; - } - if (*p != ':') - { - HTTP_DBG (1, "port not present in authority"); - http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); - return HTTP_SM_STOP; + { + HTTP_DBG (1, "port not present in authority"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + p--; + for (; p > control_data.authority; p--) + { + if (!isdigit (*p)) + break; + } + if (*p != ':') + { + HTTP_DBG (1, "port not present in authority"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + req->base.upgrade_proto = HTTP_UPGRADE_PROTO_NA; } + req->base.is_tunnel = 1; http_io_as_add_want_read_ntf (&req->base); } - req->base.control_data_len = control_data.control_data_len; - req->base.headers_offset = control_data.headers - wrk->header_list; - req->base.headers_len = control_data.headers_len; if (control_data.content_len_header_index != ~0) { req->base.content_len_header_index = @@ -1043,6 +1082,7 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, msg.data.upgrade_proto = HTTP_UPGRADE_PROTO_NA; msg.data.body_offset = req->base.control_data_len; msg.data.body_len = req->base.body_len; + msg.data.upgrade_proto = req->base.upgrade_proto; svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, { wrk->header_list, @@ -2362,6 +2402,7 @@ http2_init (vlib_main_t *vm) h2m->settings = http2_default_conn_settings; h2m->settings.max_concurrent_streams = 100; /* by default unlimited */ h2m->settings.max_header_list_size = 1 << 14; /* by default unlimited */ + h2m->settings.enable_connect_protocol = 1; /* enable extended connect */ http_register_engine (&http2_engine, HTTP_VERSION_2); return 0; diff --git a/src/plugins/http/http2/http2.h b/src/plugins/http/http2/http2.h index 9fc9534477..b48c2a2715 100644 --- a/src/plugins/http/http2/http2.h +++ b/src/plugins/http/http2/http2.h @@ -56,7 +56,8 @@ format_http2_error (u8 *s, va_list *va) _ (1, SCHEME, "scheme") \ _ (2, AUTHORITY, "authority") \ _ (3, PATH, "path") \ - _ (4, STATUS, "status") + _ (4, STATUS, "status") \ + _ (5, PROTOCOL, "protocol") /* value, label, member, min, max, default_value, err_code */ #define foreach_http2_settings \ @@ -70,7 +71,9 @@ format_http2_error (u8 *s, va_list *va) _ (5, MAX_FRAME_SIZE, max_frame_size, 16384, 16777215, 16384, \ HTTP2_ERROR_PROTOCOL_ERROR) \ _ (6, MAX_HEADER_LIST_SIZE, max_header_list_size, 0, CLIB_U32_MAX, \ - CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR) + CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR) \ + _ (8, ENABLE_CONNECT_PROTOCOL, enable_connect_protocol, 0, 1, 0, \ + HTTP2_ERROR_NO_ERROR) typedef enum { From f5116517e8e9dd59fe35614dc62035a6c94ebe96 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Thu, 12 Jun 2025 11:21:01 +0000 Subject: [PATCH 074/313] hsa: make http client do chunked reads/writes to file When using the save-to arg, the http client saves the body of the response to file. With the current mechanism it would allocate a buffer as big as the body (up to the limit), iteratively fill it and dump everything (along with the headers) into the file. This would limit how big of a response can be saved due to memory constraints and settings, as well as not reproduce it accurately (e.g the file would need to be trimmed from the saved headers. With the new approach, if the response is too big for the max-body-size settings, we reduce the buffer size to the fifo size, fill it up and write it to file. We keep the file pointer and write to it, until we have the response fully saved. The headers are now being displayed through the cli, similarly to the verbose mode. Type: improvement Change-Id: I6a72749bc9c1175aba7769d83984d1d4a40ee9f0 Signed-off-by: Semir Sionek --- extras/hs-test/http_test.go | 31 ++++++++++++-- src/plugins/hs_apps/http_client.c | 68 ++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 88cb8863f1..6047270980 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -38,7 +38,7 @@ func init() { HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest, - HttpStaticRedirectTest, HttpClientNoPrintTest) + HttpStaticRedirectTest, HttpClientNoPrintTest, HttpClientChunkedDownloadTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest) @@ -339,6 +339,31 @@ func HttpClientTest(s *NoTopoSuite) { s.AssertContains(o, "", " not found in the result!") } +func HttpClientChunkedDownloadTest(s *NoTopoSuite) { + serverAddress := s.HostAddr() + ":" + s.Ports.Http + server := ghttp.NewUnstartedServer() + l, err := net.Listen("tcp", serverAddress) + s.AssertNil(err, fmt.Sprint(err)) + server.HTTPTestServer.Listener = l + response := strings.Repeat("a", 128*1024) + server.AppendHandlers( + ghttp.CombineHandlers( + s.LogHttpReq(true), + ghttp.VerifyRequest("GET", "/"), + ghttp.RespondWith(http.StatusOK, response, http.Header{"Content-Length": {strconv.Itoa(len(response))}}), + )) + server.Start() + defer server.Close() + uri := "http://" + serverAddress + vpp := s.Containers.Vpp.VppInstance + o := vpp.Vppctl("http client save-to response.txt fifo-size 64k max-body-size 64k uri " + uri) + + s.Log(o) + file_contents, err := vpp.Container.Exec(false, "cat /tmp/response.txt") + s.AssertNil(err) + s.AssertContains(file_contents, response) +} + func HttpClientBodySizeTest(s *NoTopoSuite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() @@ -358,7 +383,7 @@ func HttpClientBodySizeTest(s *NoTopoSuite) { o := vpp.Vppctl("http client max-body-size 5 verbose uri " + uri) s.Log(o) - s.AssertContains(o, "* message body over limit", "message body size info not found in result!") + s.AssertContains(o, "* response body over limit", "response body size info not found in result!") s.AssertContains(o, ", read total 38 bytes", "client retrieved invalid amount of bytes!") } @@ -544,7 +569,6 @@ func httpClientGet(s *NoTopoSuite, response string, size int, proto string) { s.Log(o) } s.AssertContains(o, "200 OK") - s.AssertContains(o, response) s.AssertContains(o, "Content-Length: "+strconv.Itoa(size)) file_contents, err := vpp.Container.Exec(false, "cat /tmp/response.txt") @@ -608,7 +632,6 @@ func httpClientGet6(s *NoTopo6Suite, response string, size int, proto string) { o := vpp.Vppctl(cmd) s.Log(o) s.AssertContains(o, "200 OK") - s.AssertContains(o, response) s.AssertContains(o, "Content-Length: "+strconv.Itoa(size)) file_contents, err := vpp.Container.Exec(false, "cat /tmp/response.txt") diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index a284da8beb..6e52100952 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -14,7 +14,7 @@ #define foreach_hc_s_flag \ _ (1, IS_CLOSED) \ _ (2, PRINTABLE_BODY) \ - _ (4, BODY_OVER_LIMIT) + _ (4, CHUNKED_BODY) typedef enum hc_s_flag_ { @@ -44,6 +44,7 @@ typedef struct u8 *resp_headers; u8 *http_response; u8 *response_status; + FILE *file_ptr; } hc_session_t; typedef struct @@ -107,6 +108,7 @@ typedef enum HC_TRANSPORT_CLOSED, HC_REPLY_RECEIVED, HC_GENERIC_ERR, + HC_FOPEN_FAILED, HC_REPEAT_DONE, } hc_cli_signal_t; @@ -263,6 +265,17 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, hc_session->stats.req_per_wrk = hcm->repeat_count; hcm->worker_index = s->thread_index; } + if (hcm->filename) + { + hc_session->file_ptr = + fopen ((char *) format (0, "/tmp/%v", hcm->filename), "w"); + if (hc_session->file_ptr == NULL) + { + vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, + HC_FOPEN_FAILED, 0); + return -1; + } + } if (!wrk->has_common_headers) { @@ -478,10 +491,11 @@ hc_rx_callback (session_t *s) { goto done; } - if (msg.data.body_len > hcm->max_body_size) - hc_session->session_flags |= HC_S_FLAG_BODY_OVER_LIMIT; + + if (msg.data.body_len > hcm->max_body_size || hcm->filename) + hc_session->session_flags |= HC_S_FLAG_CHUNKED_BODY; vec_validate (hc_session->http_response, - (hc_session->session_flags & HC_S_FLAG_BODY_OVER_LIMIT ? + (hc_session->session_flags & HC_S_FLAG_CHUNKED_BODY ? hcm->rx_fifo_size - 1 : msg.data.body_len - 1)); vec_reset_length (hc_session->http_response); @@ -506,11 +520,22 @@ hc_rx_callback (session_t *s) } ASSERT (rv == n_deq); - if (!(hc_session->session_flags & HC_S_FLAG_BODY_OVER_LIMIT)) + if (!(hc_session->session_flags & HC_S_FLAG_CHUNKED_BODY)) vec_set_len (hc_session->http_response, curr + n_deq); ASSERT (hc_session->to_recv >= rv); hc_session->to_recv -= rv; hc_session->body_recv += rv; + if (hcm->filename) + { + if (hc_session->file_ptr == NULL) + { + vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, + HC_FOPEN_FAILED, 0); + goto done; + } + fwrite (hc_session->http_response, sizeof (u8), rv, + hc_session->file_ptr); + } done: if (hc_session->to_recv == 0) @@ -734,7 +759,6 @@ hc_get_event (vlib_main_t *vm) hc_main_t *hcm = &hc_main; uword event_type, *event_data = 0; clib_error_t *err = NULL; - FILE *file_ptr; u64 event_timeout; hc_worker_t *wrk; hc_session_t *hc_session; @@ -760,35 +784,31 @@ hc_get_event (vlib_main_t *vm) case HC_GENERIC_ERR: err = clib_error_return (0, "error: unknown"); break; + case HC_FOPEN_FAILED: + err = clib_error_return (0, "* couldn't open file %v", hcm->filename); + break; case HC_REPLY_RECEIVED: if (hcm->filename) { wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); - file_ptr = - fopen ((char *) format (0, "/tmp/%v", hcm->filename), "a"); - if (file_ptr == NULL) - { - vlib_cli_output (vm, "* couldn't open file %v", hcm->filename); - } - else - { - fprintf (file_ptr, "< %s\n< %s\n< %s", - hc_session->response_status, hc_session->resp_headers, - hc_session->http_response); - fclose (file_ptr); - vlib_cli_output (vm, "* file saved (/tmp/%v)", hcm->filename); - } + vlib_cli_output ( + vm, "< %s\n< %s\n* %u bytes saved to file (/tmp/%s)", + hc_session->response_status, hc_session->resp_headers, + hc_session->body_recv, hcm->filename); + fclose (hc_session->file_ptr); } - if (hcm->verbose) + else if (hcm->verbose) { wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); vlib_cli_output (vm, "< %v\n< %v\n%v", hc_session->response_status, hc_session->resp_headers); - if (hc_session->session_flags & HC_S_FLAG_BODY_OVER_LIMIT) + /* if the body was read in chunks and not saved to file - that + means we've hit the response body size limit */ + if (hc_session->session_flags & HC_S_FLAG_CHUNKED_BODY) vlib_cli_output ( - vm, "* message body over limit, read total %llu bytes", + vm, "* response body over limit, read total %llu bytes", hc_session->body_recv); else { @@ -931,10 +951,12 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hcm->private_segment_size = 0; hcm->fifo_size = 0; hcm->was_transport_closed = false; + hcm->verbose = false; /* default max - 64MB */ hcm->max_body_size = 64 << 20; hc_stats.request_count = 0; hc_stats.elapsed_time = 0; + vec_free (hcm->filename); if (hcm->attached) return clib_error_return (0, "failed: already running!"); From 68a023d9d9ef87bf98a3ddb8069359af1dd10ff0 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 16 Jun 2025 12:47:49 -0400 Subject: [PATCH 075/313] vcl: improve vls pthread cleanup handling Try to drop locks if interrupted with locks grabbed. Type: fix Change-Id: I8d4996b6f35a8a2610327fb11e80e9951808b535 Signed-off-by: Florin Coras --- src/vcl/vcl_locked.c | 162 +++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 67 deletions(-) diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index 79c4e25015..4f2d89dade 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -136,7 +136,7 @@ typedef struct vls_local_ { int vls_wrk_index; /**< vls wrk index, 1 per process */ volatile int vls_mt_n_threads; /**< number of threads detected */ - int vls_mt_needs_locks; /**< mt single vcl wrk needs locks */ + volatile int vls_mt_needs_locks; /**< mt single vcl wrk needs locks */ clib_rwlock_t vls_pool_lock; /**< per process/wrk vls pool locks */ pthread_mutex_t vls_mt_mq_mlock; /**< vcl mq lock */ pthread_mutex_t vls_mt_spool_mlock; /**< vcl select or pool lock */ @@ -159,6 +159,22 @@ vls_main_t *vlsm; static pthread_key_t vls_mt_pthread_stop_key; +typedef enum +{ + VLS_MT_LOCK_MQ = 1 << 0, + VLS_MT_LOCK_SPOOL = 1 << 1, + VLS_MT_RLOCK_POOL = 1 << 2, + VLS_MT_WLOCK_POOL = 1 << 3 +} vls_mt_lock_type_t; + +typedef struct vls_mt_pthread_local_ +{ + vls_mt_lock_type_t locks_acq; +} vls_mt_pthread_local_t; + +static __thread vls_mt_pthread_local_t vls_mt_pthread_local; +#define vlspt (&vls_mt_pthread_local) + typedef enum { VLS_RPC_STATE_INIT, @@ -269,28 +285,40 @@ static inline void vls_mt_pool_rlock (void) { if (vlsl->vls_mt_needs_locks) - clib_rwlock_reader_lock (&vlsl->vls_pool_lock); + { + clib_rwlock_reader_lock (&vlsl->vls_pool_lock); + vlspt->locks_acq |= VLS_MT_RLOCK_POOL; + } } static inline void vls_mt_pool_runlock (void) { - if (vlsl->vls_mt_needs_locks) - clib_rwlock_reader_unlock (&vlsl->vls_pool_lock); + if (vlspt->locks_acq & VLS_MT_RLOCK_POOL) + { + clib_rwlock_reader_unlock (&vlsl->vls_pool_lock); + vlspt->locks_acq &= ~VLS_MT_RLOCK_POOL; + } } static inline void vls_mt_pool_wlock (void) { if (vlsl->vls_mt_needs_locks) - clib_rwlock_writer_lock (&vlsl->vls_pool_lock); + { + clib_rwlock_writer_lock (&vlsl->vls_pool_lock); + vlspt->locks_acq |= VLS_MT_WLOCK_POOL; + } } static inline void vls_mt_pool_wunlock (void) { - if (vlsl->vls_mt_needs_locks) - clib_rwlock_writer_unlock (&vlsl->vls_pool_lock); + if (vlspt->locks_acq & VLS_MT_WLOCK_POOL) + { + clib_rwlock_writer_unlock (&vlsl->vls_pool_lock); + vlspt->locks_acq &= ~VLS_MT_WLOCK_POOL; + } } typedef enum @@ -301,17 +329,12 @@ typedef enum VLS_MT_OP_XPOLL, } vls_mt_ops_t; -typedef enum -{ - VLS_MT_LOCK_MQ = 1 << 0, - VLS_MT_LOCK_SPOOL = 1 << 1 -} vls_mt_lock_type_t; - static void vls_mt_add (void) { - vlsl->vls_mt_n_threads += 1; vlsl->vls_mt_needs_locks = 1; + vlsl->vls_mt_n_threads += 1; + vlspt->locks_acq = 0; /* If multi-thread workers are supported, for each new thread register a new * vcl worker with vpp. Otherwise, all threads use the same vcl worker, so @@ -332,62 +355,49 @@ vls_mt_add (void) VDBG (0, "failed to setup key value"); } -static void -vls_mt_del (void *arg) -{ - vcl_worker_t *wrk = (vcl_worker_t *) arg; - - VDBG (0, "vls worker %u vcl worker %u nthreads %u cleaning up pthread", - vlsl->vls_wrk_index, vcl_get_worker_index (), vlsl->vls_mt_n_threads); - - if (wrk != vcl_worker_get_current ()) - { - VDBG (0, "vls_mt_del called with wrong worker %u != %u", wrk->wrk_index, - vcl_get_worker_index ()); - return; - } - - vlsl->vls_mt_n_threads -= 1; - - if (vls_mt_wrk_supported ()) - { - vppcom_worker_unregister (); - } - else - { - if (!vlsl->vls_mt_n_threads) - vppcom_worker_unregister (); - } -} - static inline void vls_mt_mq_lock (void) { pthread_mutex_lock (&vlsl->vls_mt_mq_mlock); + vlspt->locks_acq |= VLS_MT_LOCK_MQ; } static inline int vls_mt_mq_trylock (void) { - return pthread_mutex_trylock (&vlsl->vls_mt_mq_mlock); + int rv = pthread_mutex_trylock (&vlsl->vls_mt_mq_mlock); + vlspt->locks_acq |= rv ? 0 : VLS_MT_LOCK_MQ; + return rv; } static inline void vls_mt_mq_unlock (void) { pthread_mutex_unlock (&vlsl->vls_mt_mq_mlock); + vlspt->locks_acq &= ~VLS_MT_LOCK_MQ; } static inline void vls_mt_spool_lock (void) { pthread_mutex_lock (&vlsl->vls_mt_spool_mlock); + vlspt->locks_acq |= VLS_MT_LOCK_SPOOL; } static inline void -vls_mt_create_unlock (void) +vls_mt_spool_unlock (void) { pthread_mutex_unlock (&vlsl->vls_mt_spool_mlock); + vlspt->locks_acq &= ~VLS_MT_LOCK_SPOOL; +} + +static void +vls_mt_rel_locks () +{ + if (vlspt->locks_acq & VLS_MT_LOCK_MQ) + vls_mt_mq_unlock (); + if (vlspt->locks_acq & VLS_MT_LOCK_SPOOL) + vls_mt_spool_unlock (); } static void @@ -397,6 +407,39 @@ vls_mt_locks_init (void) pthread_mutex_init (&vlsl->vls_mt_spool_mlock, NULL); } +static void +vls_mt_del (void *arg) +{ + vcl_worker_t *wrk = (vcl_worker_t *) arg; + + VDBG (0, "vls worker %u vcl worker %u nthreads %u cleaning up pthread", + vlsl->vls_wrk_index, vcl_get_worker_index (), vlsl->vls_mt_n_threads); + + if (wrk != vcl_worker_get_current ()) + { + VDBG (0, "vls_mt_del called with wrong worker %u != %u", wrk->wrk_index, + vcl_get_worker_index ()); + return; + } + + vlsl->vls_mt_n_threads -= 1; + + /* drop locks if any held */ + vls_mt_rel_locks (); + vls_mt_pool_runlock (); + vls_mt_pool_wunlock (); + + if (vls_mt_wrk_supported ()) + { + vppcom_worker_unregister (); + } + else + { + if (!vlsl->vls_mt_n_threads) + vppcom_worker_unregister (); + } +} + u8 vls_is_shared (vcl_locked_session_t * vls) { @@ -1060,7 +1103,7 @@ vcl_session_is_write_nonblk (vcl_session_t *s) } static void -vls_mt_acq_locks (vcl_locked_session_t * vls, vls_mt_ops_t op, int *locks_acq) +vls_mt_acq_locks (vcl_locked_session_t *vls, vls_mt_ops_t op) { vcl_worker_t *wrk = vcl_worker_get_current (); vcl_session_t *s = 0; @@ -1083,8 +1126,6 @@ vls_mt_acq_locks (vcl_locked_session_t * vls, vls_mt_ops_t op, int *locks_acq) /* might get data while waiting for lock */ is_nonblk = vcl_session_read_ready (s) != 0; } - if (!is_nonblk) - *locks_acq |= VLS_MT_LOCK_MQ; break; case VLS_MT_OP_WRITE: ASSERT (s); @@ -1094,31 +1135,18 @@ vls_mt_acq_locks (vcl_locked_session_t * vls, vls_mt_ops_t op, int *locks_acq) /* might get space while waiting for lock */ is_nonblk = vcl_session_is_write_nonblk (s); } - if (!is_nonblk) - *locks_acq |= VLS_MT_LOCK_MQ; break; case VLS_MT_OP_XPOLL: vls_mt_mq_lock (); - *locks_acq |= VLS_MT_LOCK_MQ; break; case VLS_MT_OP_SPOOL: vls_mt_spool_lock (); - *locks_acq |= VLS_MT_LOCK_SPOOL; break; default: break; } } -static void -vls_mt_rel_locks (int locks_acq) -{ - if (locks_acq & VLS_MT_LOCK_MQ) - vls_mt_mq_unlock (); - if (locks_acq & VLS_MT_LOCK_SPOOL) - vls_mt_create_unlock (); -} - static inline u8 vls_mt_session_should_migrate (vcl_locked_session_t * vls) { @@ -1233,7 +1261,6 @@ vls_mt_detect (void) } #define vls_mt_guard(_vls, _op) \ - int _locks_acq = 0; \ if (vls_mt_wrk_supported ()) \ { \ if (PREDICT_FALSE (_vls && \ @@ -1248,12 +1275,12 @@ vls_mt_detect (void) else \ { \ if (PREDICT_FALSE (vlsl->vls_mt_needs_locks)) \ - vls_mt_acq_locks (_vls, _op, &_locks_acq); \ + vls_mt_acq_locks (_vls, _op); \ } -#define vls_mt_unguard() \ - if (PREDICT_FALSE (_locks_acq)) \ - vls_mt_rel_locks (_locks_acq) +#define vls_mt_unguard() \ + if (PREDICT_FALSE (vlspt->locks_acq)) \ + vls_mt_rel_locks () int vls_write (vls_handle_t vlsh, void *buf, size_t nbytes) @@ -2131,7 +2158,7 @@ vls_mt_mq_wait_lock (vcl_session_handle_t vcl_sh) uword *vlshp; /* If mt wrk supported or single threaded just return */ - if (vls_mt_wrk_supported () || (vlsl->vls_mt_n_threads <= 1)) + if (vls_mt_wrk_supported () || !vlsl->vls_mt_needs_locks) return; wrk = vls_worker_get_current (); @@ -2159,7 +2186,7 @@ vls_mt_mq_wait_lock (vcl_session_handle_t vcl_sh) static inline void vls_mt_mq_wait_unlock (vcl_session_handle_t vcl_sh) { - if (vls_mt_wrk_supported () || (vlsl->vls_mt_n_threads <= 1)) + if (vls_mt_wrk_supported () || !vlsl->vls_mt_needs_locks) return; vls_mt_mq_lock (); @@ -2194,6 +2221,7 @@ vls_app_create (char *app_name) vls_worker_alloc (); vlsl->vls_wrk_index = vcl_get_worker_index (); vlsl->vls_mt_n_threads = 1; + vlspt->locks_acq = 0; if (pthread_setspecific (vls_mt_pthread_stop_key, vcl_worker_get_current ())) VDBG (0, "failed to setup key value"); clib_rwlock_init (&vlsl->vls_pool_lock); From f588c2123323425d159690e498aab4463d596512 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 18 Jun 2025 14:20:35 +0200 Subject: [PATCH 076/313] build: add knob to disable vcl VCL doesn't work with musl. Type: improvement Change-Id: I5cb69da680dc98d14d88e340b4db6b5a8584ff23 Signed-off-by: Damjan Marion --- src/plugins/hs_apps/CMakeLists.txt | 4 ++++ src/vcl/CMakeLists.txt | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/plugins/hs_apps/CMakeLists.txt b/src/plugins/hs_apps/CMakeLists.txt index 1d83175f64..a30105397a 100644 --- a/src/plugins/hs_apps/CMakeLists.txt +++ b/src/plugins/hs_apps/CMakeLists.txt @@ -11,6 +11,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +if(NOT VPP_BUILD_VCL) + return() +endif() + ############################################################################## # vpp builtin hs apps ############################################################################## diff --git a/src/vcl/CMakeLists.txt b/src/vcl/CMakeLists.txt index c8835e771c..2787ce6780 100644 --- a/src/vcl/CMakeLists.txt +++ b/src/vcl/CMakeLists.txt @@ -14,6 +14,12 @@ ############################################################################## # vppcom shared library ############################################################################## + +option(VPP_BUILD_VCL "Build VCL" ON) +if(NOT VPP_BUILD_VCL) + return() +endif(NOT VPP_BUILD_VCL) + add_vpp_library(vppcom SOURCES vppcom.c From e81b121877f6f0d6caa0e05b50412cfa36c76e2f Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 18 Jun 2025 15:34:01 -0400 Subject: [PATCH 077/313] hsa: fix http client path formating for fopen format full path into variable, null terminate and free it after use in hc_session_connected_callback Type: fix Change-Id: I3ed64dd247bf5ac9af8fa65517b6308a98205fd4 Signed-off-by: Matus Fabian --- src/plugins/hs_apps/http_client.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 6e52100952..fbb2b1c6dd 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -226,6 +226,7 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, hc_worker_t *wrk; hc_session_t *hc_session; hc_http_header_t *header; + u8 *f = 0; if (err) { @@ -267,8 +268,9 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, } if (hcm->filename) { - hc_session->file_ptr = - fopen ((char *) format (0, "/tmp/%v", hcm->filename), "w"); + f = format (0, "/tmp/%s%c", hcm->filename, 0); + hc_session->file_ptr = fopen ((char *) f, "w"); + vec_free (f); if (hc_session->file_ptr == NULL) { vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, From 9adcb25964129521ef1af216e35055b183bda2db Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 18 Jun 2025 15:18:08 -0400 Subject: [PATCH 078/313] hsa: fix http client logging with file Print response status and headers as vectors. Type: fix Change-Id: I7321776e4914c139d85cd3f45ee67afd0850caee Signed-off-by: Florin Coras --- src/plugins/hs_apps/http_client.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index fbb2b1c6dd..d49c6a4429 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -714,7 +714,7 @@ hc_connect () } static void -hc_get_repeat_stats (vlib_main_t *vm) +hc_get_req_stats (vlib_main_t *vm) { hc_main_t *hcm = &hc_main; @@ -770,7 +770,7 @@ hc_get_event (vlib_main_t *vm) event_timeout += 5; vlib_process_wait_for_event_or_clock (vm, event_timeout); event_type = vlib_process_get_events (vm, &event_data); - hc_get_repeat_stats (vm); + hc_get_req_stats (vm); switch (event_type) { @@ -795,7 +795,7 @@ hc_get_event (vlib_main_t *vm) wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); vlib_cli_output ( - vm, "< %s\n< %s\n* %u bytes saved to file (/tmp/%s)", + vm, "< %v\n< %v\n* %u bytes saved to file (/tmp/%s)", hc_session->response_status, hc_session->resp_headers, hc_session->body_recv, hcm->filename); fclose (hc_session->file_ptr); From 1fffe64d49b82b2c4b7425eb956f75c59732e900 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 18 Jun 2025 17:49:55 +0200 Subject: [PATCH 079/313] hs-test: change local registry port - changed to 5001, CalicoVPP uses port 5000 Type: test Change-Id: Ic45c613d684685f21e49612c4e6454c302bbabb6 Signed-off-by: Adrian Villin --- extras/hs-test/docker/Dockerfile.ab | 2 +- extras/hs-test/docker/Dockerfile.curl | 2 +- extras/hs-test/docker/Dockerfile.h2load | 2 +- extras/hs-test/docker/Dockerfile.nginx | 2 +- extras/hs-test/docker/Dockerfile.nginx-http3 | 2 +- extras/hs-test/docker/Dockerfile.nginx-server | 2 +- extras/hs-test/docker/Dockerfile.vpp | 2 +- extras/hs-test/docker/Dockerfile.wrk | 2 +- extras/hs-test/docker/setup-local-registry.sh | 14 +++++++------- extras/hs-test/infra/hst_suite.go | 1 + extras/hs-test/script/build-images.sh | 2 +- extras/hs-test/script/build_hst.sh | 7 ++++--- 12 files changed, 21 insertions(+), 19 deletions(-) diff --git a/extras/hs-test/docker/Dockerfile.ab b/extras/hs-test/docker/Dockerfile.ab index 5e975af3b9..d61e125503 100644 --- a/extras/hs-test/docker/Dockerfile.ab +++ b/extras/hs-test/docker/Dockerfile.ab @@ -1,6 +1,6 @@ # Apache Bench container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # apache2-utils is now installed in the base image diff --git a/extras/hs-test/docker/Dockerfile.curl b/extras/hs-test/docker/Dockerfile.curl index b76a9d8da6..e2fea20a45 100644 --- a/extras/hs-test/docker/Dockerfile.curl +++ b/extras/hs-test/docker/Dockerfile.curl @@ -1,6 +1,6 @@ # curl container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # Note: wget and xz-utils are already in the base image diff --git a/extras/hs-test/docker/Dockerfile.h2load b/extras/hs-test/docker/Dockerfile.h2load index 40bfc72aae..276e026823 100644 --- a/extras/hs-test/docker/Dockerfile.h2load +++ b/extras/hs-test/docker/Dockerfile.h2load @@ -1,6 +1,6 @@ # h2load container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # nghttp2 is now installed in the base image diff --git a/extras/hs-test/docker/Dockerfile.nginx b/extras/hs-test/docker/Dockerfile.nginx index bc392d5f0c..5a3e7e2a84 100644 --- a/extras/hs-test/docker/Dockerfile.nginx +++ b/extras/hs-test/docker/Dockerfile.nginx @@ -1,6 +1,6 @@ # nginx container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # nginx is now installed in the base image diff --git a/extras/hs-test/docker/Dockerfile.nginx-http3 b/extras/hs-test/docker/Dockerfile.nginx-http3 index 568bd9baab..701c66b0b6 100644 --- a/extras/hs-test/docker/Dockerfile.nginx-http3 +++ b/extras/hs-test/docker/Dockerfile.nginx-http3 @@ -1,6 +1,6 @@ ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest COPY resources/nginx/vcl.conf /vcl.conf COPY resources/nginx/nginx_http3.conf /nginx.conf diff --git a/extras/hs-test/docker/Dockerfile.nginx-server b/extras/hs-test/docker/Dockerfile.nginx-server index 33c004a48b..5284ea7763 100644 --- a/extras/hs-test/docker/Dockerfile.nginx-server +++ b/extras/hs-test/docker/Dockerfile.nginx-server @@ -1,6 +1,6 @@ # nginx server container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # nginx is now installed in the base image diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp index 69414c80d1..1e69e2cf2f 100644 --- a/extras/hs-test/docker/Dockerfile.vpp +++ b/extras/hs-test/docker/Dockerfile.vpp @@ -1,6 +1,6 @@ # VPP container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # We don't need to install these packages as they're in the base image # Just install anything specific to VPP that isn't in the base diff --git a/extras/hs-test/docker/Dockerfile.wrk b/extras/hs-test/docker/Dockerfile.wrk index ae376c5af8..8517a05896 100644 --- a/extras/hs-test/docker/Dockerfile.wrk +++ b/extras/hs-test/docker/Dockerfile.wrk @@ -1,6 +1,6 @@ # wrk HTTP benchmarking container that uses the base image ARG UBUNTU_VERSION=22.04 -FROM localhost:5000/vpp-test-base:latest +FROM localhost:5001/vpp-test-base:latest # wrk is installed in the base image diff --git a/extras/hs-test/docker/setup-local-registry.sh b/extras/hs-test/docker/setup-local-registry.sh index 638a2f898d..ea8cb1c24c 100755 --- a/extras/hs-test/docker/setup-local-registry.sh +++ b/extras/hs-test/docker/setup-local-registry.sh @@ -16,7 +16,7 @@ fi # Registry container name REGISTRY_NAME="local-registry" -REGISTRY_PORT=5000 +REGISTRY_PORT=${1:-5001} # Check if registry container is already running if docker container inspect "$REGISTRY_NAME" &>/dev/null; then @@ -48,17 +48,17 @@ else echo "Adding 'localhost:$REGISTRY_PORT' to insecure-registries in /etc/docker/daemon.json" echo "You may need to restart Docker for changes to take effect" echo "Please add the following to /etc/docker/daemon.json:" - echo '{ - "insecure-registries": ["localhost:5000"] -}' + echo "{ + \"insecure-registries\": [\"localhost:$REGISTRY_PORT\"] +}" fi else echo "Creating /etc/docker/daemon.json with insecure-registries configuration" echo "You may need to restart Docker for changes to take effect" echo "Please create /etc/docker/daemon.json with the following content:" - echo '{ - "insecure-registries": ["localhost:5000"] -}' + echo "{ + \"insecure-registries\": [\"localhost:$REGISTRY_PORT\"] +}" fi fi diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index dc06934e4c..b610b66b61 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -89,6 +89,7 @@ var reservedPorts = []string{ "48879", "4790", "5000", + "5001", "6633", "6081", "53053", diff --git a/extras/hs-test/script/build-images.sh b/extras/hs-test/script/build-images.sh index 74bb2eb06c..2ec7de3746 100755 --- a/extras/hs-test/script/build-images.sh +++ b/extras/hs-test/script/build-images.sh @@ -34,7 +34,7 @@ if [ -d "${DOCKER_BUILD_DIR}" ] ; then fi # Set the tag for the base image -BASE_TAG=${BASE_TAG:-"localhost:5000/vpp-test-base:latest"} +BASE_TAG=${BASE_TAG:-"localhost:5001/vpp-test-base:latest"} echo "=== Building base image ===" # shellcheck disable=2086 diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh index a36037c0b6..b527466062 100755 --- a/extras/hs-test/script/build_hst.sh +++ b/extras/hs-test/script/build_hst.sh @@ -39,6 +39,7 @@ OS_ARCH="$(uname -m)" DOCKER_BUILD_DIR="/scratch/docker-build" DOCKER_CACHE_DIR="${DOCKER_BUILD_DIR}/docker_cache" DOCKER_LOGIN_SCRIPT="/scratch/nomad/.docker-ro/dlogin.sh" +REGISTRY_PORT=5001 if [ -x "$DOCKER_LOGIN_SCRIPT" ] ; then $DOCKER_LOGIN_SCRIPT fi @@ -46,12 +47,12 @@ fi # Set up the local registry before creating containers echo "=== Setting up local registry ===" if [ -x "$(dirname "$0")/../docker/setup-local-registry.sh" ]; then - "$(dirname "$0")/../docker/setup-local-registry.sh" + "$(dirname "$0")/../docker/setup-local-registry.sh" "$REGISTRY_PORT" else echo "Warning: setup-local-registry.sh not found or not executable" echo "Attempting to create and use local registry at localhost:5000" if ! docker ps | grep -q "local-registry"; then - docker run -d --restart=always -p 5000:5000 --name local-registry registry:2 + docker run -d --restart=always -p $REGISTRY_PORT:5000 --name local-registry registry:2 fi fi @@ -83,7 +84,7 @@ fi echo "=== Building all containers using build-images.sh ===" ( # Export necessary environment variables for build-images.sh - export BASE_TAG="localhost:5000/vpp-test-base:latest" + export BASE_TAG="localhost:$REGISTRY_PORT/vpp-test-base:latest" export OS_ARCH export UBUNTU_VERSION export HTTP_PROXY From 8558a290a67e1cec915a9c3bf905d264b7d93dc4 Mon Sep 17 00:00:00 2001 From: Mohammed Hawari Date: Tue, 3 Jun 2025 17:05:46 +0200 Subject: [PATCH 080/313] vppinfra: install ring.h Change-Id: I920bef41426c10a4560cc3e923ca747054a5aeec Type: improvement Signed-off-by: Mohammed Hawari --- src/vppinfra/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vppinfra/CMakeLists.txt b/src/vppinfra/CMakeLists.txt index 08a5fa213a..d52ecb096d 100644 --- a/src/vppinfra/CMakeLists.txt +++ b/src/vppinfra/CMakeLists.txt @@ -193,6 +193,7 @@ set(VPPINFRA_HEADERS random.h random_isaac.h rbtree.h + ring.h serialize.h socket.h sparse_vec.h From a5acfe0751f713384f5d9f8923bb9cee7c6d8613 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 18 Jun 2025 14:26:47 +0200 Subject: [PATCH 081/313] vppinfra: don't use internal __CPU_* macros Breaks non-glibc builds... Type: improvement Change-Id: If48a444ff358ef85973504795c06287269ed5c55 Signed-off-by: Damjan Marion --- src/vppinfra/unix-misc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vppinfra/unix-misc.c b/src/vppinfra/unix-misc.c index 2255cc7cc3..ef649fd913 100644 --- a/src/vppinfra/unix-misc.c +++ b/src/vppinfra/unix-misc.c @@ -291,12 +291,12 @@ os_get_cpu_affinity_bitmap () int cpu; uword *affinity_cpus; - clib_bitmap_alloc (affinity_cpus, __CPU_SETSIZE); + clib_bitmap_alloc (affinity_cpus, CPU_SETSIZE); clib_bitmap_zero (affinity_cpus); /* set__os_affinity_cpu_set once on first call to * os_get_cpu_affinity_bitmap() */ - if (__CPU_COUNT_S (sizeof (cpu_set_t), &__os_affinity_cpu_set) == 0) + if (CPU_COUNT_S (sizeof (cpu_set_t), &__os_affinity_cpu_set) == 0) { int ret; ret = sched_getaffinity (0, sizeof (cpu_set_t), &__os_affinity_cpu_set); @@ -307,8 +307,8 @@ os_get_cpu_affinity_bitmap () } } - for (cpu = 0; cpu < __CPU_SETSIZE; cpu++) - if (__CPU_ISSET_S (cpu, sizeof (cpu_set_t), &__os_affinity_cpu_set)) + for (cpu = 0; cpu < CPU_SETSIZE; cpu++) + if (CPU_ISSET_S (cpu, sizeof (cpu_set_t), &__os_affinity_cpu_set)) clib_bitmap_set (affinity_cpus, cpu, 1); return affinity_cpus; #elif defined(__FreeBSD__) @@ -409,7 +409,7 @@ os_translate_cpu_bmp_to_affinity_bitmap (clib_bitmap_t *cpu_bmp) return NULL; uword *translated_cpulist; - clib_bitmap_alloc (translated_cpulist, __CPU_SETSIZE); + clib_bitmap_alloc (translated_cpulist, CPU_SETSIZE); clib_bitmap_zero (translated_cpulist); uword cpu_it; From 20f342ce6e425072cb38eaff92f59a342feab834 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 19 Jun 2025 13:48:04 +0000 Subject: [PATCH 082/313] http: client can receive response while sending Client can receive response (error) from server while still sending body bytes, handle this as exception in state machine instead of error. Type: improvement Change-Id: I6aa3f7f5aaa299ac781109dd75295a7eb3a42cf9 Signed-off-by: Matus Fabian --- extras/hs-test/http_test.go | 32 ++++++++++++++++++++++++++++++- src/plugins/hs_apps/http_client.c | 2 ++ src/plugins/http/http1.c | 27 ++++++++++++++++++++------ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 6047270980..8670b1deac 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -38,7 +38,7 @@ func init() { HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest, - HttpStaticRedirectTest, HttpClientNoPrintTest, HttpClientChunkedDownloadTest) + HttpStaticRedirectTest, HttpClientNoPrintTest, HttpClientChunkedDownloadTest, HttpClientPostRejectedTest) RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest) @@ -793,6 +793,36 @@ func HttpClientPostFilePtrTest(s *NoTopoSuite) { httpClientPostFile(s, true, 131072) } +func HttpClientPostRejectedTest(s *NoTopoSuite) { + serverAddress := s.HostAddr() + ":" + s.Ports.Http + vpp := s.Containers.Vpp.VppInstance + fileName := "/tmp/test_file.txt" + // send something big so we are sure that server respond when we are still sending body + s.Log(vpp.Container.Exec(false, "fallocate -l "+strconv.Itoa(10<<20)+" "+fileName)) + s.Log(vpp.Container.Exec(false, "ls -la "+fileName)) + + server := ghttp.NewUnstartedServer() + l, err := net.Listen("tcp", serverAddress) + s.AssertNil(err, fmt.Sprint(err)) + server.HTTPTestServer.Listener = l + server.AppendHandlers( + ghttp.CombineHandlers( + s.LogHttpReq(false), + ghttp.VerifyRequest("POST", "/test"), + ghttp.RespondWith(http.StatusForbidden, nil), + )) + server.Start() + defer server.Close() + + uri := "http://" + serverAddress + "/test" + cmd := "http client post verbose uri " + uri + " file " + fileName + o := vpp.Vppctl(cmd) + + s.Log(o) + s.AssertContains(o, "403") + s.Log(vpp.Vppctl("show session verbose 2")) +} + func HttpStaticPromTest(s *NoTopoSuite) { query := "stats.prom" vpp := s.Containers.Vpp.VppInstance diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index d49c6a4429..937d98edae 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -483,6 +483,8 @@ hc_rx_callback (session_t *s) if (msg.data.body_len == 0) { svm_fifo_dequeue_drop_all (s->rx_fifo); + /* we don't need to print warning about binary content */ + hc_session->session_flags |= HC_S_FLAG_PRINTABLE_BODY; goto done; } diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index a0aaf067da..106a1fdc52 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -1975,15 +1975,30 @@ http1_transport_rx_callback (http_conn_t *hc) if (!http1_req_state_is_rx_valid (req)) { if (http_io_ts_max_read (hc)) - clib_warning ("hc [%u]%x invalid rx state: http req state " - "'%U', session state '%U'", - hc->c_thread_index, hc->hc_hc_index, - format_http_req_state, req->state, - format_http_conn_state, hc); - http_io_ts_drain_all (hc); + { + if (req->state == HTTP_REQ_STATE_APP_IO_MORE_DATA && + !(hc->flags & HTTP_CONN_F_IS_SERVER)) + { + /* client can receive error response from server when still + * sending content */ + /* TODO: 100 continue support */ + HTTP_DBG (1, "server send response while client sending data"); + http_io_as_drain_all (req); + hc->state = HTTP_CONN_STATE_CLOSED; + http_req_state_change (req, HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + goto run_sm; + } + clib_warning ("hc [%u]%x invalid rx state: http req state " + "'%U', session state '%U'", + hc->c_thread_index, hc->hc_hc_index, + format_http_req_state, req->state, + format_http_conn_state, hc); + http_io_ts_drain_all (hc); + } return; } +run_sm: HTTP_DBG (1, "run state machine"); http1_req_run_state_machine (hc, req, 0, 0); } From b246b8cfaa197f624d9e08dbefba7218e57e56b8 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 17 Jun 2025 21:20:23 -0400 Subject: [PATCH 083/313] vcl: improve vls locking for mt apps For mt single vcl worker apps, only guard vcl session pool, as opposed to vls pool, to allow workers to perform updates. Also convert spool mutex into rwlock and make sure all operations that interact with vcl session pools grab at least the reader lock. Type: improvement Change-Id: Ief41912bc84881772d2279cd84dabb983a91b4cb Signed-off-by: Florin Coras --- src/vcl/vcl_locked.c | 99 +++++++++++++++++++++++++++++++++++--------- src/vcl/vppcom.c | 18 +++++--- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index 4f2d89dade..576bbdb007 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -139,7 +139,7 @@ typedef struct vls_local_ volatile int vls_mt_needs_locks; /**< mt single vcl wrk needs locks */ clib_rwlock_t vls_pool_lock; /**< per process/wrk vls pool locks */ pthread_mutex_t vls_mt_mq_mlock; /**< vcl mq lock */ - pthread_mutex_t vls_mt_spool_mlock; /**< vcl select or pool lock */ + pthread_rwlock_t vls_mt_spool_rwlock; /**< vcl select or pool rwlock */ volatile u8 select_mp_check; /**< flag set if select checks done */ struct sigaction old_sa; /**< old sigaction to restore */ } vls_process_local_t; @@ -159,12 +159,25 @@ vls_main_t *vlsm; static pthread_key_t vls_mt_pthread_stop_key; -typedef enum +#define foreach_mt_lock_type \ + _ (LOCK_MQ, "mq") \ + _ (RLOCK_SPOOL, "rlock_spool") \ + _ (WLOCK_SPOOL, "wlock_spool") \ + _ (RLOCK_POOL, "rlock_pool") \ + _ (WLOCK_POOL, "wlock_pool") + +enum vls_mt_lock_type_bit_ +{ +#define _(sym, str) VLS_MT_BIT_##sym, + foreach_mt_lock_type +#undef _ +}; + +typedef enum vls_mt_lock_type_ { - VLS_MT_LOCK_MQ = 1 << 0, - VLS_MT_LOCK_SPOOL = 1 << 1, - VLS_MT_RLOCK_POOL = 1 << 2, - VLS_MT_WLOCK_POOL = 1 << 3 +#define _(sym, str) VLS_MT_##sym = 1 << VLS_MT_BIT_##sym, + foreach_mt_lock_type +#undef _ } vls_mt_lock_type_t; typedef struct vls_mt_pthread_local_ @@ -321,6 +334,21 @@ vls_mt_pool_wunlock (void) } } +static inline void +vls_mt_pool_unlock (void) +{ + if (vlspt->locks_acq & VLS_MT_RLOCK_POOL) + { + clib_rwlock_reader_unlock (&vlsl->vls_pool_lock); + vlspt->locks_acq &= ~VLS_MT_RLOCK_POOL; + } + else if (vlspt->locks_acq & VLS_MT_WLOCK_POOL) + { + clib_rwlock_writer_unlock (&vlsl->vls_pool_lock); + vlspt->locks_acq &= ~VLS_MT_WLOCK_POOL; + } +} + typedef enum { VLS_MT_OP_READ, @@ -378,17 +406,40 @@ vls_mt_mq_unlock (void) } static inline void -vls_mt_spool_lock (void) +vls_mt_spool_rlock (void) { - pthread_mutex_lock (&vlsl->vls_mt_spool_mlock); - vlspt->locks_acq |= VLS_MT_LOCK_SPOOL; + pthread_rwlock_rdlock (&vlsl->vls_mt_spool_rwlock); + vlspt->locks_acq |= VLS_MT_RLOCK_SPOOL; +} + +static inline void +vls_mt_spool_wlock (void) +{ + pthread_rwlock_wrlock (&vlsl->vls_mt_spool_rwlock); + vlspt->locks_acq |= VLS_MT_WLOCK_SPOOL; +} + +static inline void +vls_mt_spool_runlock (void) +{ + pthread_rwlock_unlock (&vlsl->vls_mt_spool_rwlock); + vlspt->locks_acq &= ~VLS_MT_RLOCK_SPOOL; +} + +static inline void +vls_mt_spool_wunlock (void) +{ + pthread_rwlock_unlock (&vlsl->vls_mt_spool_rwlock); + vlspt->locks_acq &= ~VLS_MT_WLOCK_SPOOL; } static inline void vls_mt_spool_unlock (void) { - pthread_mutex_unlock (&vlsl->vls_mt_spool_mlock); - vlspt->locks_acq &= ~VLS_MT_LOCK_SPOOL; + if (vlspt->locks_acq & VLS_MT_RLOCK_SPOOL) + vls_mt_spool_runlock (); + else if (vlspt->locks_acq & VLS_MT_WLOCK_SPOOL) + vls_mt_spool_wunlock (); } static void @@ -396,7 +447,7 @@ vls_mt_rel_locks () { if (vlspt->locks_acq & VLS_MT_LOCK_MQ) vls_mt_mq_unlock (); - if (vlspt->locks_acq & VLS_MT_LOCK_SPOOL) + if (vlspt->locks_acq & (VLS_MT_RLOCK_SPOOL | VLS_MT_WLOCK_SPOOL)) vls_mt_spool_unlock (); } @@ -404,7 +455,7 @@ static void vls_mt_locks_init (void) { pthread_mutex_init (&vlsl->vls_mt_mq_mlock, NULL); - pthread_mutex_init (&vlsl->vls_mt_spool_mlock, NULL); + pthread_rwlock_init (&vlsl->vls_mt_spool_rwlock, NULL); } static void @@ -426,8 +477,7 @@ vls_mt_del (void *arg) /* drop locks if any held */ vls_mt_rel_locks (); - vls_mt_pool_runlock (); - vls_mt_pool_wunlock (); + vls_mt_pool_unlock (); if (vls_mt_wrk_supported ()) { @@ -1126,6 +1176,7 @@ vls_mt_acq_locks (vcl_locked_session_t *vls, vls_mt_ops_t op) /* might get data while waiting for lock */ is_nonblk = vcl_session_read_ready (s) != 0; } + vls_mt_spool_rlock (); break; case VLS_MT_OP_WRITE: ASSERT (s); @@ -1135,12 +1186,14 @@ vls_mt_acq_locks (vcl_locked_session_t *vls, vls_mt_ops_t op) /* might get space while waiting for lock */ is_nonblk = vcl_session_is_write_nonblk (s); } + vls_mt_spool_rlock (); break; case VLS_MT_OP_XPOLL: vls_mt_mq_lock (); + vls_mt_spool_rlock (); break; case VLS_MT_OP_SPOOL: - vls_mt_spool_lock (); + vls_mt_spool_wlock (); break; default: break; @@ -2180,7 +2233,12 @@ vls_mt_mq_wait_lock (vcl_session_handle_t vcl_sh) } } + vls_mt_pool_runlock (); + vls_mt_mq_unlock (); + + /* Only lock taken should be spool reader. Needed because VCL is probably + * touching a session while waiting for events */ } static inline void @@ -2189,10 +2247,13 @@ vls_mt_mq_wait_unlock (vcl_session_handle_t vcl_sh) if (vls_mt_wrk_supported () || !vlsl->vls_mt_needs_locks) return; - vls_mt_mq_lock (); + vls_mt_spool_runlock (); - /* writers can grab lock now */ - vls_mt_pool_runlock (); + /* No locks should be taken by pthread at this point. So writers to spool, + * which were blocked until now, should be able to proceed */ + + vls_mt_mq_lock (); + vls_mt_spool_rlock (); } int diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 57b7214fdd..40209baeab 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -1260,7 +1260,7 @@ vcl_flush_mq_events (void) vcl_worker_flush_mq_events (vcl_worker_get_current ()); } -static inline void +static inline vcl_session_t * vcl_worker_wait_mq (vcl_worker_t *wrk, u32 session_handle, vcl_worker_wait_type_t wait) { @@ -1276,7 +1276,7 @@ vcl_worker_wait_mq (vcl_worker_t *wrk, u32 session_handle, /* Session might've been closed by another thread if multi-threaded * as opposed to multi-worker app */ if (s->flags & VCL_SESSION_F_APP_CLOSING) - return; + goto done; } /* Short sleeps waiting on mq notifications. Note that we drop mq lock for @@ -1303,6 +1303,11 @@ vcl_worker_wait_mq (vcl_worker_t *wrk, u32 session_handle, if (wrk->post_wait_fn) wrk->post_wait_fn (session_handle); + +done: + return session_handle != VCL_INVALID_SESSION_INDEX ? + vcl_session_get_w_handle (wrk, session_handle) : + 0; } static int @@ -2170,7 +2175,7 @@ vppcom_session_read_internal (uint32_t session_handle, void *buf, int n, svm_fifo_unset_event (s->rx_fifo); svm_fifo_unset_event (rx_fifo); - vcl_worker_wait_mq (wrk, session_handle, VCL_WRK_WAIT_IO_RX); + s = vcl_worker_wait_mq (wrk, session_handle, VCL_WRK_WAIT_IO_RX); vcl_worker_flush_mq_events (wrk); } } @@ -2290,7 +2295,7 @@ vppcom_session_read_segments (uint32_t session_handle, svm_fifo_unset_event (s->rx_fifo); svm_fifo_unset_event (rx_fifo); - vcl_worker_wait_mq (wrk, session_handle, VCL_WRK_WAIT_IO_RX); + s = vcl_worker_wait_mq (wrk, session_handle, VCL_WRK_WAIT_IO_RX); vcl_worker_flush_mq_events (wrk); } } @@ -2405,7 +2410,8 @@ vppcom_session_write_inline (vcl_worker_t *wrk, vcl_session_t *s, void *buf, if (s->flags & VCL_SESSION_F_APP_CLOSING) return vcl_session_closed_error (s); - vcl_worker_wait_mq (wrk, vcl_session_handle (s), VCL_WRK_WAIT_IO_TX); + s = vcl_worker_wait_mq (wrk, vcl_session_handle (s), + VCL_WRK_WAIT_IO_TX); vcl_worker_flush_mq_events (wrk); } } @@ -2490,7 +2496,7 @@ vppcom_session_write_segments (uint32_t session_handle, if (s->flags & VCL_SESSION_F_APP_CLOSING) return vcl_session_closed_error (s); - vcl_worker_wait_mq (wrk, session_handle, VCL_WRK_WAIT_IO_TX); + s = vcl_worker_wait_mq (wrk, session_handle, VCL_WRK_WAIT_IO_TX); vcl_worker_flush_mq_events (wrk); } } From 1c1c84e9b31a85d2fefcbaa9dc4db33d1d4fa829 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 17 Jun 2025 23:04:25 -0400 Subject: [PATCH 084/313] hs-test: add udp mw test Type: test Change-Id: I30a2541bda71aae4cbf2be76f428d23309470631 Signed-off-by: Florin Coras --- extras/hs-test/infra/suite_ldp.go | 2 +- extras/hs-test/ldp_test.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/extras/hs-test/infra/suite_ldp.go b/extras/hs-test/infra/suite_ldp.go index ed81690f82..7009631bec 100644 --- a/extras/hs-test/infra/suite_ldp.go +++ b/extras/hs-test/infra/suite_ldp.go @@ -109,7 +109,7 @@ func (s *LdpSuite) TeardownTest() { s.CollectIperfLogs(s.Containers.ServerApp) s.CollectRedisServerLogs(s.Containers.ServerApp) s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("show error")) - s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("show error")) + s.Log(s.Containers.ClientVpp.VppInstance.Vppctl("show error")) } for _, container := range s.StartedContainers { diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 050a1fccd7..73f10ed422 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -14,6 +14,13 @@ func init() { RegisterSoloLdpTests(LdpIperfUdpTest, LdpIperfUdpVppInterruptModeTest, RedisBenchmarkTest, LdpIperfTlsTcpTest, LdpIperfTcpTest, LdpIperfTcpReorderTest, LdpIperfReverseTcpReorderTest, LdpIperfUdpReorderTest, LdpIperfReverseUdpReorderTest) + RegisterLdpMWTests(LdpIperfUdpMWTest) +} + +func LdpIperfUdpMWTest(s *LdpSuite) { + s.CpusPerVppContainer = 3 + s.SetupTest() + s.AssertIperfMinTransfer(ldPreloadIperf(s, "-u -P 5"), 50) } func LdpIperfUdpVppInterruptModeTest(s *LdpSuite) { From 97b1380df3089d107d5524d023a873e67170a57c Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 20 Jun 2025 13:30:28 +0000 Subject: [PATCH 085/313] http: fix path formatting in request templates Client app pass path as data bytes and length, not null terminated string. Fix also msg.data.target_path_len value in http client and http cli client, set it to string length not vec_len. Type: fix Change-Id: Icab6d830812bbfc2e6df82564d6d087476769111 Signed-off-by: Matus Fabian --- src/plugins/hs_apps/http_client.c | 9 +++--- src/plugins/hs_apps/http_client_cli.c | 5 +-- src/plugins/http/http1.c | 46 ++++++++++++++------------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 937d98edae..0137f8895a 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -190,8 +190,9 @@ hc_request (session_t *s, hc_worker_t *wrk, hc_session_t *hc_session, rv = svm_fifo_enqueue (s->tx_fifo, sizeof (wrk->msg), (u8 *) &wrk->msg); ASSERT (rv == sizeof (wrk->msg)); - rv = svm_fifo_enqueue (s->tx_fifo, vec_len (hcm->target), hcm->target); - ASSERT (rv == vec_len (hcm->target)); + rv = + svm_fifo_enqueue (s->tx_fifo, wrk->msg.data.target_path_len, hcm->target); + ASSERT (rv == wrk->msg.data.target_path_len); rv = svm_fifo_enqueue (s->tx_fifo, wrk->req_headers.tail_offset, wrk->headers_buf); @@ -308,8 +309,8 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, wrk->msg.data.body_len = 0; wrk->msg.type = HTTP_MSG_REQUEST; - /* request target */ - wrk->msg.data.target_path_len = vec_len (hcm->target); + /* request target len must be without null termination */ + wrk->msg.data.target_path_len = strlen ((char *) hcm->target); /* custom headers */ wrk->msg.data.headers_len = wrk->req_headers.tail_offset; /* total length */ diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c index b72d4dfae5..f8984be83b 100644 --- a/src/plugins/hs_apps/http_client_cli.c +++ b/src/plugins/hs_apps/http_client_cli.c @@ -167,7 +167,8 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, msg.method_type = HTTP_REQ_GET; /* request target */ msg.data.target_path_offset = 0; - msg.data.target_path_len = vec_len (hcm->http_query); + /* request target len must be without null termination */ + msg.data.target_path_len = strlen ((char *) hcm->http_query); /* custom headers */ msg.data.headers_offset = msg.data.target_path_len; msg.data.headers_len = headers.tail_offset; @@ -179,7 +180,7 @@ hcc_ts_connected_callback (u32 app_index, u32 hc_index, session_t *as, msg.data.target_path_len + msg.data.headers_len + msg.data.body_len; svm_fifo_seg_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, - { hcm->http_query, vec_len (hcm->http_query) }, + { hcm->http_query, msg.data.target_path_len }, { headers_buf, msg.data.headers_len } }; rv = svm_fifo_enqueue_segments (as->tx_fifo, segs, 3, 0 /* allow partial */); diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 106a1fdc52..e7ddaf350b 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -46,22 +46,22 @@ static const char *connection_upgrade_template = "Connection: upgrade\r\n" /** * http request boilerplate */ -static const char *get_request_template = "GET %s HTTP/1.1\r\n" +static const char *get_request_template = "GET %U HTTP/1.1\r\n" "Host: %v\r\n" "User-Agent: %v\r\n"; -static const char *post_request_template = "POST %s HTTP/1.1\r\n" +static const char *post_request_template = "POST %U HTTP/1.1\r\n" "Host: %v\r\n" "User-Agent: %v\r\n" "Content-Length: %llu\r\n"; -static const char *put_request_template = "PUT %s HTTP/1.1\r\n" +static const char *put_request_template = "PUT %U HTTP/1.1\r\n" "Host: %v\r\n" "User-Agent: %v\r\n" "Content-Length: %llu\r\n"; static const char *put_chunked_request_template = - "PUT %s HTTP/1.1\r\n" + "PUT %U HTTP/1.1\r\n" "Host: %v\r\n" "User-Agent: %v\r\n" "Transfer-Encoding: chunked\r\n"; @@ -1411,7 +1411,7 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, */ request = format (request, get_request_template, /* target */ - target, + format_http_bytes, target, msg.data.target_path_len, /* Host */ hc->host, /* User-Agent */ @@ -1435,7 +1435,7 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, */ request = format (request, post_request_template, /* target */ - target, + format_http_bytes, target, msg.data.target_path_len, /* Host */ hc->host, /* User-Agent */ @@ -1456,13 +1456,14 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, /* * Streaming PUT with chunked transfer encoding */ - request = format (request, put_chunked_request_template, - /* target */ - target, - /* Host */ - hc->host, - /* User-Agent */ - hc->app_name); + request = + format (request, put_chunked_request_template, + /* target */ + format_http_bytes, target, msg.data.target_path_len, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name); http_req_tx_buffer_init (req, &msg); @@ -1480,15 +1481,16 @@ http1_req_state_wait_app_method (http_conn_t *hc, http_req_t *req, /* * Regular PUT with Content-Length */ - request = format (request, put_request_template, - /* target */ - target, - /* Host */ - hc->host, - /* User-Agent */ - hc->app_name, - /* Content-Length */ - msg.data.body_len); + request = + format (request, put_request_template, + /* target */ + format_http_bytes, target, msg.data.target_path_len, + /* Host */ + hc->host, + /* User-Agent */ + hc->app_name, + /* Content-Length */ + msg.data.body_len); http_req_tx_buffer_init (req, &msg); From f84494d29b776bf1fa4c4ab435794aa1a705482c Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 20 Jun 2025 19:04:43 -0700 Subject: [PATCH 086/313] http_static: improve cli short help - make sure all params are present in both create and add/del listener - rename max-body-size to max-req-body-size as it should be easier to glean the purpose of the config Type: refactor Change-Id: I0f30eebe0b001e48ff640552396087e5da35334d Signed-off-by: Florin Coras --- src/plugins/http_static/http_static.c | 2 +- src/plugins/http_static/http_static.h | 2 +- src/plugins/http_static/static_server.c | 75 +++++++++++++++++-------- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/plugins/http_static/http_static.c b/src/plugins/http_static/http_static.c index 85b044fb86..91168570c2 100644 --- a/src/plugins/http_static/http_static.c +++ b/src/plugins/http_static/http_static.c @@ -80,7 +80,7 @@ hss_enable_api (u32 fifo_size, u32 cache_limit, u32 prealloc_fifos, hsm->default_listener.www_root = format (0, "%s%c", www_root, 0); hsm->default_listener.cache_size = cache_limit; hsm->default_listener.max_age = max_age; - hsm->default_listener.max_body_size = max_body_size; + hsm->default_listener.max_req_body_size = max_body_size; hsm->default_listener.rx_buff_thresh = rx_buff_thresh; hsm->default_listener.keepalive_timeout = keepalive_timeout; hsm->have_default_listener = 1; diff --git a/src/plugins/http_static/http_static.h b/src/plugins/http_static/http_static.h index 46c9e75884..b5407de641 100644 --- a/src/plugins/http_static/http_static.h +++ b/src/plugins/http_static/http_static.h @@ -160,7 +160,7 @@ typedef struct hss_listener_ /** Max cache size before LRU occurs */ u64 cache_size; /** Maximum size of a request body (in bytes) **/ - u64 max_body_size; + u64 max_req_body_size; /** Maximum size of a large memory allocation */ u32 rx_buff_thresh; /** Timeout during which client connection will stay open */ diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index 4015916bcf..25606ec96a 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -597,7 +597,7 @@ handle_request (hss_session_t *hs) l = hss_listener_get (hs->listener_index); - if (hs->left_recv > l->max_body_size) + if (hs->left_recv > l->max_req_body_size) { start_send_data (hs, HTTP_STATUS_CONTENT_TOO_LARGE); hss_session_disconnect_transport (hs); @@ -1129,7 +1129,7 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, l->cache_size = 10 << 20; l->max_age = HSS_DEFAULT_MAX_AGE; - l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE; + l->max_req_body_size = HSS_DEFAULT_MAX_BODY_SIZE; l->rx_buff_thresh = HSS_DEFAULT_RX_BUFFER_THRESH; l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT; l->flags = 0; @@ -1166,8 +1166,8 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "max-age %d", &l->max_age)) ; - else if (unformat (line_input, "max-body-size %U", unformat_memory_size, - &l->max_body_size)) + else if (unformat (line_input, "max-req-body-size %U", + unformat_memory_size, &l->max_req_body_size)) ; else if (unformat (line_input, "rx-buff-thresh %U", unformat_memory_size, &l->rx_buff_thresh)) @@ -1180,6 +1180,10 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "http1-only")) l->flags |= HSS_LISTENER_F_HTTP1_ONLY; + /* Deprecated */ + else if (unformat (line_input, "max-body-size %U", unformat_memory_size, + &l->max_req_body_size)) + ; else { error = clib_error_return (0, "unknown input `%U'", @@ -1233,23 +1237,24 @@ hss_create_command_fn (vlib_main_t *vm, unformat_input_t *input, * Enable the static http server * * @cliexpar - * This command enables the static http server. Only the www-root - * parameter is required + * This command enables the static http server. Listeners can be added later * @clistart * http static server www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m * @cliend - * @cliexcmd{http static server www-root [url-handlers] - * [private-segment-size ] [fifo-size ] [max-age ] - * [uri ] [ptr-thresh ] [prealloc-fifos ] [debug [nn]] - * [keepalive-timeout ] [max-body-size ] [http1-only]} + * @cliexcmd{http static server [private-segment-size ] + * [fifo-size ] [prealloc-fifos ] [debug ] [uri ] + * [www-root ] [url-handlers] [cache-size ] [max-age ] + * [max-req-body-size ] [rx-buff-thresh ] [keepalive-timeout ] + * [ptr-thresh ] [http1-only]} ?*/ VLIB_CLI_COMMAND (hss_create_command, static) = { .path = "http static server", .short_help = - "http static server [www-root ] [url-handlers]\n" - "[private-segment-size ] [fifo-size ] [max-age ]\n" - "[uri ] [ptr-thresh ] [prealloc-fifos ] [debug [nn]]\n" - "[keepalive-timeout ] [max-body-size ] [http1-only]\n", + "http static server [private-segment-size ] [fifo-size ]\n" + "[prealloc-fifos ] [debug ] [uri ] [www-root ]\n" + "[url-handlers] [cache-size ] [max-age ]\n" + "[max-req-body-size ] [rx-buff-thresh ] [keepalive-timeout ]\n" + "[ptr-thresh ] [http1-only]\n", .function = hss_create_command_fn, }; @@ -1272,7 +1277,7 @@ hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input, l->cache_size = 10 << 20; l->max_age = HSS_DEFAULT_MAX_AGE; - l->max_body_size = HSS_DEFAULT_MAX_BODY_SIZE; + l->max_req_body_size = HSS_DEFAULT_MAX_BODY_SIZE; l->rx_buff_thresh = HSS_DEFAULT_RX_BUFFER_THRESH; l->keepalive_timeout = HSS_DEFAULT_KEEPALIVE_TIMEOUT; l->flags = 0; @@ -1292,19 +1297,25 @@ hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "cache-size %U", unformat_memory_size, &l->cache_size)) ; + else if (unformat (line_input, "max-age %d", &l->max_age)) + ; + else if (unformat (line_input, "max-req-body-size %U", + unformat_memory_size, &l->max_req_body_size)) + ; + else if (unformat (line_input, "rx-buff-thresh %U", unformat_memory_size, + &l->rx_buff_thresh)) + ; else if (unformat (line_input, "keepalive-timeout %d", &l->keepalive_timeout)) ; else if (unformat (line_input, "ptr-thresh %U", unformat_memory_size, &l->use_ptr_thresh)) ; - else if (unformat (line_input, "max-age %d", &l->max_age)) - ; + else if (unformat (line_input, "http1-only")) + l->flags |= HSS_LISTENER_F_HTTP1_ONLY; + /* Deprecated */ else if (unformat (line_input, "max-body-size %U", unformat_memory_size, - &l->max_body_size)) - ; - else if (unformat (line_input, "rx-buff-thresh %U", unformat_memory_size, - &l->rx_buff_thresh)) + &l->max_req_body_size)) ; else { @@ -1357,10 +1368,28 @@ hss_add_del_listener_command_fn (vlib_main_t *vm, unformat_input_t *input, return error; } +/*? + * Add static http server listener + * + * @cliexpar + * Add a static http server listener. The listener can be used to + * serve static files from the www-root directory or to handle + * requests using url handlers. + * @clistart + * http static listener www-root /tmp/www uri tcp://0.0.0.0/80 cache-size 2m + * @cliend + * @cliexcmd{http static listener [uri ] [www-root ] [url-handlers] + * [cache-size ] [max-age ] [max-req-body-size ] + * [rx-buff-thresh ] [keepalive-timeout ] [ptr-thresh ] + * [http1-only]} +?*/ VLIB_CLI_COMMAND (hss_add_del_listener_command, static) = { .path = "http static listener", - .short_help = "http static listener [add|del] uri \n" - "[www-root ] [url-handlers] [http1-only]\n", + .short_help = + "http static listener [add|del] [uri ] [www-root ]\n" + "[url-handlers] [cache-size ] [max-age ]\n" + "[max-req-body-size ] [rx-buff-thresh ] [keepalive-timeout ]\n" + "[ptr-thresh ] [http1-only]\n", .function = hss_add_del_listener_command_fn, }; From 594da0e3b0a1c1faf67a6093668cb141a71eb334 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 21 Jun 2025 15:42:58 -0700 Subject: [PATCH 087/313] vcl: add reattach spinlock Avoid potential deadlock if app is sigtermed and wants workers lock to cleanup worker while reattaching. Type: improvement Change-Id: I97f5935d309de83717e5a0a82055c91e07c4cb17 Signed-off-by: Florin Coras --- src/vcl/vcl_private.h | 9 +++++---- src/vcl/vppcom.c | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vcl/vcl_private.h b/src/vcl/vcl_private.h index 61894d7fe2..cc51946dd2 100644 --- a/src/vcl/vcl_private.h +++ b/src/vcl/vcl_private.h @@ -362,10 +362,6 @@ typedef struct vppcom_main_t_ /** Lock to protect worker registrations */ clib_spinlock_t workers_lock; - /** Counter to determine order of execution of `vcl_api_retry_attach` - * function by multiple workers */ - int reattach_count; - /** Lock to protect segment hash table */ clib_rwlock_t segment_table_lock; @@ -387,6 +383,11 @@ typedef struct vppcom_main_t_ int (*vcl_epoll_wait) (int epfd, struct epoll_event *events, int maxevents, int timeout); + clib_spinlock_t reattach_lock; + /** Counter to determine order of execution of `vcl_api_retry_attach` + * function by multiple workers */ + int reattach_count; + /* * Binary api context */ diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 40209baeab..ffa29bac34 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -1413,23 +1413,23 @@ vcl_api_retry_attach (vcl_worker_t *wrk) vcl_worker_detached_signal_mq (wrk); - clib_spinlock_lock (&vcm->workers_lock); + clib_spinlock_lock (&vcm->reattach_lock); if (vcl_is_first_reattach_to_execute ()) { if (vcl_api_attach ()) { - clib_spinlock_unlock (&vcm->workers_lock); + clib_spinlock_unlock (&vcm->reattach_lock); return; } vcl_worker_detached_stop_signal_mq (wrk); vcl_set_reattach_counter (); - clib_spinlock_unlock (&vcm->workers_lock); + clib_spinlock_unlock (&vcm->reattach_lock); } else { vcl_set_reattach_counter (); vcl_worker_detached_stop_signal_mq (wrk); - clib_spinlock_unlock (&vcm->workers_lock); + clib_spinlock_unlock (&vcm->reattach_lock); vcl_worker_register_with_vpp (); } @@ -1494,6 +1494,7 @@ vppcom_app_create (const char *app_name) 20 /* timeout in secs */); pool_alloc (vcm->workers, vcl_cfg->max_workers); clib_spinlock_init (&vcm->workers_lock); + clib_spinlock_init (&vcm->reattach_lock); clib_rwlock_init (&vcm->segment_table_lock); atexit (vppcom_app_exit); vcl_elog_init (vcm); From 5e9b38df7d124074e3100afca952c8ef0365b940 Mon Sep 17 00:00:00 2001 From: Varun Rapelly Date: Sat, 21 Jun 2025 04:15:11 +0000 Subject: [PATCH 088/313] tls: add async support for SSL client This patch enables async processing support for SSL clients. Type: improvement Change-Id: I8d9462b439ff6e0962ee30cb8b596a2744a1aa33 Signed-off-by: Varun Rapelly --- src/plugins/tlsopenssl/tls_async.c | 46 ++++++++++++++++++++-------- src/plugins/tlsopenssl/tls_openssl.c | 6 +++- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_async.c b/src/plugins/tlsopenssl/tls_async.c index dfdababa5f..1ded98dcd9 100644 --- a/src/plugins/tlsopenssl/tls_async.c +++ b/src/plugins/tlsopenssl/tls_async.c @@ -775,21 +775,43 @@ tls_async_handshake_event_handler (void *async_evt, void *unused) return 0; } - /* client not supported */ - if (!SSL_is_server (oc->ssl)) - return 0; - - /* Need to check transport status */ - if (ctx->flags & TLS_CONN_F_PASSIVE_CLOSE) + if (SSL_is_server (oc->ssl)) { - openssl_handle_handshake_failure (ctx); - return 0; - } + /* Need to check transport status */ + if (ctx->flags & TLS_CONN_F_PASSIVE_CLOSE) + { + openssl_handle_handshake_failure (ctx); + return 0; + } - if (tls_notify_app_accept (ctx)) + if (tls_notify_app_accept (ctx)) + { + ctx->c_s_index = SESSION_INVALID_INDEX; + tls_disconnect_transport (ctx); + } + } + else { - ctx->c_s_index = SESSION_INVALID_INDEX; - tls_disconnect_transport (ctx); + /* Verify server certificate */ + if ((rv = SSL_get_verify_result (oc->ssl)) != X509_V_OK) + { + TLS_DBG (1, " failed verify: %s\n", + X509_verify_cert_error_string (rv)); + /* + * Presence of hostname enforces strict certificate verification + */ + if (ctx->srv_hostname) + { + TLS_DBG (1, "Server host name verification failed"); + openssl_handle_handshake_failure (ctx); + return -1; + } + } + if (tls_notify_app_connected (ctx, SESSION_E_NONE)) + { + tls_disconnect_transport (ctx); + return -1; + } } TLS_DBG (1, diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index bcb3c965fb..a5b6b062c8 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -789,7 +789,11 @@ openssl_ctx_init_client (tls_ctx_t * ctx) SSL_CTX_set_ecdh_auto (oc->client_ssl_ctx, 1); SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); if (om->async) - SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ASYNC); + { + SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ASYNC); + SSL_CTX_set_async_callback (oc->client_ssl_ctx, + tls_async_openssl_callback); + } rv = SSL_CTX_set_cipher_list (oc->client_ssl_ctx, (const char *) om->ciphers); From 1523b51e8a8a7b4f36e4d57ae4eceac81153c630 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 20 Jun 2025 17:03:59 +0000 Subject: [PATCH 089/313] http: h2 encoding request Type: improvement Change-Id: I4609c3a89c4df0883aa25f07623dad68c539d70d Signed-off-by: Matus Fabian --- src/plugins/http/http.h | 15 ++- src/plugins/http/http2/hpack.c | 193 ++++++++++++++++++++++++++++-- src/plugins/http/http2/hpack.h | 15 +++ src/plugins/http/test/http_test.c | 70 ++++++++++- 4 files changed, 275 insertions(+), 18 deletions(-) diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index bd9e40e23a..0e028a7760 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -51,13 +51,18 @@ typedef struct #define http_token_lit(s) (s), sizeof (s) - 1 +#define foreach_http_method \ + _ (GET, "GET") \ + _ (POST, "POST") \ + _ (PUT, "PUT") \ + _ (CONNECT, "CONNECT") \ + _ (UNKNOWN, "UNKNOWN") /* for internal use */ + typedef enum http_req_method_ { - HTTP_REQ_GET = 0, - HTTP_REQ_POST, - HTTP_REQ_PUT, - HTTP_REQ_CONNECT, - HTTP_REQ_UNKNOWN, /* for internal use */ +#define _(s, str) HTTP_REQ_##s, + foreach_http_method +#undef _ } http_req_method_t; typedef enum http_msg_type_ diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 324602ce34..15e92fe683 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -100,6 +100,14 @@ static hpack_token_t hpack_headers[] = { #undef _ }; +static http_token_t http_methods[] = { +#define _(s, str) { http_token_lit (str) }, + foreach_http_method +#undef _ +}; + +#define http_method_token(e) http_methods[e].base, http_methods[e].len + __clib_export uword hpack_decode_int (u8 **src, u8 *end, u8 prefix_len) { @@ -1050,38 +1058,38 @@ hpack_encode_custom_header (u8 *dst, const u8 *name, u32 name_len, return dst; } +#define encode_indexed_static_entry(_index) \ + vec_add2 (dst, a, 1); \ + *a++ = 0x80 | _index; + static inline u8 * hpack_encode_status_code (u8 *dst, http_status_code_t sc) { u32 orig_len, actual_size; u8 *a, *b; -#define encode_common_sc(_index) \ - vec_add2 (dst, a, 1); \ - *a++ = 0x80 | _index; - switch (sc) { case HTTP_STATUS_OK: - encode_common_sc (8); + encode_indexed_static_entry (8); break; case HTTP_STATUS_NO_CONTENT: - encode_common_sc (9); + encode_indexed_static_entry (9); break; case HTTP_STATUS_PARTIAL_CONTENT: - encode_common_sc (10); + encode_indexed_static_entry (10); break; case HTTP_STATUS_NOT_MODIFIED: - encode_common_sc (11); + encode_indexed_static_entry (11); break; case HTTP_STATUS_BAD_REQUEST: - encode_common_sc (12); + encode_indexed_static_entry (12); break; case HTTP_STATUS_NOT_FOUND: - encode_common_sc (13); + encode_indexed_static_entry (13); break; case HTTP_STATUS_INTERNAL_ERROR: - encode_common_sc (14); + encode_indexed_static_entry (14); break; default: orig_len = vec_len (dst); @@ -1097,6 +1105,110 @@ hpack_encode_status_code (u8 *dst, http_status_code_t sc) return dst; } +static inline u8 * +hpack_encode_method (u8 *dst, http_req_method_t method) +{ + u32 orig_len, actual_size; + u8 *a, *b; + + switch (method) + { + case HTTP_REQ_GET: + encode_indexed_static_entry (2); + break; + case HTTP_REQ_POST: + encode_indexed_static_entry (3); + break; + default: + orig_len = vec_len (dst); + vec_add2 (dst, a, 9); + b = a; + /* Literal Header Field without Indexing — Indexed Name */ + *b++ = 2; + b = hpack_encode_string (b, (const u8 *) http_method_token (method)); + actual_size = b - a; + vec_set_len (dst, orig_len + actual_size); + break; + } + return dst; +} + +static inline u8 * +hpack_encode_scheme (u8 *dst, http_url_scheme_t scheme) +{ + u8 *a; + + switch (scheme) + { + case HTTP_URL_SCHEME_HTTP: + encode_indexed_static_entry (6); + break; + case HTTP_URL_SCHEME_HTTPS: + encode_indexed_static_entry (7); + break; + default: + ASSERT (0); + break; + } + return dst; +} + +static inline u8 * +hpack_encode_path (u8 *dst, u8 *path, u32 path_len) +{ + u32 orig_len, actual_size; + u8 *a, *b; + + switch (path_len) + { + case 1: + if (path[0] == '/') + { + encode_indexed_static_entry (4); + return dst; + } + break; + case 11: + if (!memcmp (path, "/index.html", 11)) + { + encode_indexed_static_entry (5); + return dst; + } + break; + default: + break; + } + + orig_len = vec_len (dst); + vec_add2 (dst, a, path_len + 2); + b = a; + /* Literal Header Field without Indexing — Indexed Name */ + *b++ = 4; + b = hpack_encode_string (b, path, path_len); + actual_size = b - a; + vec_set_len (dst, orig_len + actual_size); + + return dst; +} + +static inline u8 * +hpack_encode_authority (u8 *dst, u8 *authority, u32 authority_len) +{ + u32 orig_len, actual_size; + u8 *a, *b; + + orig_len = vec_len (dst); + vec_add2 (dst, a, authority_len + 2); + b = a; + /* Literal Header Field without Indexing — Indexed Name */ + *b++ = 1; + b = hpack_encode_string (b, authority, authority_len); + actual_size = b - a; + vec_set_len (dst, orig_len + actual_size); + + return dst; +} + static inline u8 * hpack_encode_content_len (u8 *dst, u64 content_len) { @@ -1183,3 +1295,62 @@ hpack_serialize_response (u8 *app_headers, u32 app_headers_len, *dst = p; } + +__clib_export void +hpack_serialize_request (u8 *app_headers, u32 app_headers_len, + hpack_request_control_data_t *control_data, u8 **dst) +{ + u8 *p, *end; + + p = *dst; + + /* pseudo-headers must go first */ + p = hpack_encode_method (p, control_data->method); + + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_SCHEME_PARSED) + p = hpack_encode_scheme (p, control_data->scheme); + + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) + p = hpack_encode_path (p, control_data->path, control_data->path_len); + + p = hpack_encode_authority (p, control_data->authority, + control_data->authority_len); + + /* user agent */ + if (control_data->user_agent_len) + p = + hpack_encode_header (p, HTTP_HEADER_USER_AGENT, control_data->user_agent, + control_data->user_agent_len); + + /* content length if any */ + if (control_data->content_len != HPACK_ENCODER_SKIP_CONTENT_LEN) + p = hpack_encode_content_len (p, control_data->content_len); + + end = app_headers + app_headers_len; + while (app_headers < end) + { + /* custom header name? */ + u32 *tmp = (u32 *) app_headers; + if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT)) + { + http_custom_token_t *name, *value; + name = (http_custom_token_t *) app_headers; + u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT; + app_headers += sizeof (http_custom_token_t) + name_len; + value = (http_custom_token_t *) app_headers; + app_headers += sizeof (http_custom_token_t) + value->len; + p = hpack_encode_custom_header (p, name->token, name_len, + value->token, value->len); + } + else + { + http_app_header_t *header; + header = (http_app_header_t *) app_headers; + app_headers += sizeof (http_app_header_t) + header->value.len; + p = hpack_encode_header (p, header->name, header->value.token, + header->value.len); + } + } + + *dst = p; +} diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index fef2a96c2d..a2fd5faed5 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -56,10 +56,13 @@ typedef struct u8 *headers; u8 *protocol; u32 protocol_len; + u8 *user_agent; + u32 user_agent_len; uword content_len_header_index; u32 headers_len; u32 control_data_len; u16 parsed_bitmap; + u64 content_len; } hpack_request_control_data_t; typedef struct @@ -182,4 +185,16 @@ void hpack_serialize_response (u8 *app_headers, u32 app_headers_len, hpack_response_control_data_t *control_data, u8 **dst); +/** + * Serialize request + * + * @param app_headers App header list + * @param app_headers_len App header list length + * @param control_data Header values set by protocol layer + * @param dst Vector where serialized headers will be added + */ +void hpack_serialize_request (u8 *app_headers, u32 app_headers_len, + hpack_request_control_data_t *control_data, + u8 **dst); + #endif /* SRC_PLUGINS_HTTP_HPACK_H_ */ diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index f44d3cbd31..73a397aef2 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -1013,11 +1013,77 @@ http_test_hpack (vlib_main_t *vm) HTTP_TEST ((vec_len (buf) == (sizeof (expected2) - 1) && !memcmp (buf, expected2, sizeof (expected2) - 1)), "response encoded as %U", format_hex_bytes, buf, vec_len (buf)); - vec_free (buf); + vec_reset_length (buf); vec_free (headers_buf); - vec_free (server_name); vec_free (date); + vlib_cli_output (vm, "hpack_serialize_request"); + + hpack_request_control_data_t req_cd; + u8 *authority, *path; + + static void (*_hpack_serialize_request) ( + u8 * app_headers, u32 app_headers_len, + hpack_request_control_data_t * control_data, u8 * *dst); + + _hpack_serialize_request = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_serialize_request"); + + authority = format (0, "www.example.com"); + path = format (0, "/"); + + vec_validate (buf, 127); + vec_reset_length (buf); + + req_cd.method = HTTP_REQ_GET; + req_cd.parsed_bitmap = HPACK_PSEUDO_HEADER_SCHEME_PARSED; + req_cd.scheme = HTTP_URL_SCHEME_HTTP; + req_cd.parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; + req_cd.path = path; + req_cd.path_len = vec_len (path); + req_cd.parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + req_cd.authority = authority; + req_cd.authority_len = vec_len (authority); + req_cd.user_agent_len = 0; + req_cd.content_len = HPACK_ENCODER_SKIP_CONTENT_LEN; + u8 expected3[] = + "\x82\x86\x84\x01\x8C\xF1\xE3\xC2\xE5\xF2\x3A\x6B\xA0\xAB\x90\xF4\xFF"; + _hpack_serialize_request (0, 0, &req_cd, &buf); + HTTP_TEST ((vec_len (buf) == (sizeof (expected3) - 1) && + !memcmp (buf, expected3, sizeof (expected3) - 1)), + "request encoded as %U", format_hex_bytes, buf, vec_len (buf)); + vec_reset_length (buf); + vec_free (authority); + vec_free (path); + memset (&req_cd, 0, sizeof (req_cd)); + + authority = format (0, "example.org:123"); + + req_cd.method = HTTP_REQ_CONNECT; + req_cd.parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + req_cd.authority = authority; + req_cd.authority_len = vec_len (authority); + req_cd.user_agent = server_name; + req_cd.user_agent_len = vec_len (server_name); + req_cd.content_len = HPACK_ENCODER_SKIP_CONTENT_LEN; + + vec_validate (headers_buf, 127); + http_init_headers_ctx (&headers, headers_buf, vec_len (headers_buf)); + http_add_custom_header (&headers, http_token_lit ("sandwich"), + http_token_lit ("spam")); + + u8 expected4[] = + "\x02\x07\x43\x4F\x4E\x4E\x45\x43\x54\x01\x8B\x2F\x91\xD3\x5D\x05\x5C\xF6" + "\x4D\x70\x22\x67\x0F\x2B\x8B\x9D\x29\xAD\x4B\x6A\x32\x54\x49\x50\x94\x7f" + "\x00\x86\x40\xEA\x93\xC1\x89\x3F\x83\x45\x63\xA7"; + _hpack_serialize_request (headers_buf, headers.tail_offset, &req_cd, &buf); + HTTP_TEST ((vec_len (buf) == (sizeof (expected4) - 1) && + !memcmp (buf, expected4, sizeof (expected4) - 1)), + "request encoded as %U", format_hex_bytes, buf, vec_len (buf)); + vec_free (buf); + vec_free (server_name); + vec_free (authority); + return 0; } From 8fc82bbbee0bcd39585aabd88a7fb64a45b838e5 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 23 Jun 2025 13:22:02 -0700 Subject: [PATCH 090/313] hs-test: fix mw tests that rely on tap interfaces Make sure taps have multiple queues and consistent qp mode enabled. Type: test Change-Id: Icf00290fad1934adcbfcfe56530d37f0793b0bca Signed-off-by: Florin Coras Signed-off-by: Matus Fabian --- extras/hs-test/http_test.go | 4 ---- extras/hs-test/infra/suite_envoy_proxy.go | 4 ++-- extras/hs-test/infra/suite_h2.go | 2 +- extras/hs-test/infra/suite_nginx_proxy.go | 4 ++-- extras/hs-test/infra/suite_no_topo.go | 2 +- extras/hs-test/infra/suite_no_topo6.go | 2 +- extras/hs-test/infra/suite_vpp_proxy.go | 4 ++-- extras/hs-test/infra/suite_vpp_udp_proxy.go | 4 ++-- extras/hs-test/infra/vppinstance.go | 20 ++++++++------------ extras/hs-test/proxy_test.go | 15 --------------- src/plugins/hs_apps/proxy.c | 2 +- 11 files changed, 20 insertions(+), 43 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 8670b1deac..1caefe51f7 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -667,10 +667,6 @@ func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { durationInSec := 10 var err error - // recreate interfaces with RX-queues - s.AssertNil(vpp.DeleteTap(s.Interfaces.Tap)) - s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, false, 2, 2)) - s.CreateNginxServer() s.AssertNil(s.Containers.NginxServer.Start()) logPath := s.Containers.NginxServer.GetContainerWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log" diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go index a099d2ca40..42a5b93768 100644 --- a/extras/hs-test/infra/suite_envoy_proxy.go +++ b/extras/hs-test/infra/suite_envoy_proxy.go @@ -129,8 +129,8 @@ func (s *EnvoyProxySuite) SetupTest() { s.AssertNil(vpp.Start()) // wait for VPP to start time.Sleep(time.Second * 1) - s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1, 1)) - s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 1, 2)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 2)) s.Containers.Vpp.Exec(false, "chmod 777 -R %s", s.Containers.Vpp.GetContainerWorkDir()) // Add Ipv4 ARP entry for nginx HTTP server, otherwise first request fail (HTTP error 503) diff --git a/extras/hs-test/infra/suite_h2.go b/extras/hs-test/infra/suite_h2.go index 96395d3c61..ddd128551d 100644 --- a/extras/hs-test/infra/suite_h2.go +++ b/extras/hs-test/infra/suite_h2.go @@ -76,7 +76,7 @@ func (s *H2Suite) SetupTest() { vpp, _ := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus, memoryConfig, sessionConfig) s.AssertNil(vpp.Start()) - s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, false, 1, 1), "failed to create tap interface") + s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, false, 1), "failed to create tap interface") if *DryRun { s.LogStartedContainers() diff --git a/extras/hs-test/infra/suite_nginx_proxy.go b/extras/hs-test/infra/suite_nginx_proxy.go index 05b0960eed..90306bdea3 100644 --- a/extras/hs-test/infra/suite_nginx_proxy.go +++ b/extras/hs-test/infra/suite_nginx_proxy.go @@ -103,8 +103,8 @@ func (s *NginxProxySuite) SetupTest() { ) s.AssertNil(vpp.Start()) - s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1, 1)) - s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 1, 2)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 2)) if *DryRun { s.LogStartedContainers() diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go index f7fc403fe7..1749d13d53 100644 --- a/extras/hs-test/infra/suite_no_topo.go +++ b/extras/hs-test/infra/suite_no_topo.go @@ -82,7 +82,7 @@ func (s *NoTopoSuite) SetupTest() { vpp, _ := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus, sessionConfig) s.AssertNil(vpp.Start()) - s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, false, 1, 1), "failed to create tap interface") + s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, false, 1), "failed to create tap interface") if *DryRun { s.LogStartedContainers() diff --git a/extras/hs-test/infra/suite_no_topo6.go b/extras/hs-test/infra/suite_no_topo6.go index 1b21469d1a..a57cb29721 100644 --- a/extras/hs-test/infra/suite_no_topo6.go +++ b/extras/hs-test/infra/suite_no_topo6.go @@ -76,7 +76,7 @@ func (s *NoTopo6Suite) SetupTest() { vpp, _ := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus, sessionConfig) s.AssertNil(vpp.Start()) - s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, true, 1, 1), "failed to create tap interface") + s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, true, 1), "failed to create tap interface") if *DryRun { s.LogStartedContainers() diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index db6b6bedf7..d1c125e3a5 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -92,8 +92,8 @@ func (s *VppProxySuite) SetupTest() { s.AssertNotNil(vpp, fmt.Sprint(err)) s.AssertNil(vpp.Start()) - s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1, 1)) - s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 1, 2)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 2)) if *DryRun { s.LogStartedContainers() diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index 29ee5b7e6d..c424790db7 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -77,8 +77,8 @@ func (s *VppUdpProxySuite) SetupTest() { s.AssertNotNil(vpp, fmt.Sprint(err)) s.AssertNil(vpp.Start()) - s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1, 1)) - s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 1, 2)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Client, false, 1)) + s.AssertNil(vpp.CreateTap(s.Interfaces.Server, false, 2)) arp := fmt.Sprintf("set ip neighbor %s %s %s", s.Interfaces.Server.Peer.Name(), diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go index 7acf8aeca9..b601e46fef 100644 --- a/extras/hs-test/infra/vppinstance.go +++ b/extras/hs-test/infra/vppinstance.go @@ -102,6 +102,7 @@ type VppCpuConfig struct { PinWorkersCorelist bool RelativeCores bool SkipCores int + NumWorkers int } type VppMemTrace struct { @@ -500,22 +501,15 @@ func (vpp *VppInstance) addAppNamespace( return nil } -func (vpp *VppInstance) CreateTap(tap *NetInterface, IPv6 bool, numRxQueues uint16, tapId uint32, flags ...uint32) error { - var tapFlags uint32 = 0 - - if len(flags) > 0 { - tapFlags = flags[0] - } +func (vpp *VppInstance) CreateTap(tap *NetInterface, IPv6 bool, tapId uint32) error { + numRxQueues := uint16(max(1, vpp.CpuConfig.NumWorkers)) + tapFlags := Consistent_qp if *DryRun { - flagsCli := "" + flagsCli := "consistent-qp" ipAddress := "" ipAddressPeer := "" - if tapFlags == Consistent_qp { - flagsCli = "consistent-qp" - } - if IPv6 { ipAddress = "host-ip6-addr " + tap.Ip6Address ipAddressPeer = tap.Peer.Ip6Address @@ -553,7 +547,7 @@ func (vpp *VppInstance) CreateTap(tap *NetInterface, IPv6 bool, numRxQueues uint TapFlags: tapv2.TapFlags(tapFlags), } - vpp.getSuite().Log("create tap interface " + tap.Name()) + vpp.getSuite().Log("create tap interface " + tap.Name() + " num-rx-queues " + strconv.Itoa(int(numRxQueues))) // Create tap interface if err := vpp.ApiStream.SendMsg(createTapReq); err != nil { return err @@ -689,6 +683,7 @@ func (vpp *VppInstance) setDefaultCpuConfig() { vpp.CpuConfig.PinWorkersCorelist = true vpp.CpuConfig.RelativeCores = false vpp.CpuConfig.SkipCores = 0 + vpp.CpuConfig.NumWorkers = 0 } func (vpp *VppInstance) generateVPPCpuConfig() string { @@ -728,6 +723,7 @@ func (vpp *VppInstance) generateVPPCpuConfig() string { workers := vpp.Cpus[startCpu+1:] workersRelativeCpu := startCpu + 1 + vpp.CpuConfig.NumWorkers = len(workers) if len(workers) > 0 && vpp.CpuConfig.UseWorkers { if vpp.CpuConfig.PinWorkersCorelist { diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index e239ccd7b9..40f3558b4c 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -54,15 +54,6 @@ func vppProxyIperfMWTest(s *VppProxySuite, proto string) { s.SetupTest() s.Containers.IperfC.Run() s.Containers.IperfS.Run() - vppProxy := s.Containers.VppProxy.VppInstance - - // tap interfaces are created on test setup with 1 rx-queue, - // need to recreate them with 2 + consistent-qp - s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Server)) - s.AssertNil(vppProxy.CreateTap(s.Interfaces.Server, false, 2, uint32(s.Interfaces.Server.Peer.Index), Consistent_qp)) - - s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Client)) - s.AssertNil(vppProxy.CreateTap(s.Interfaces.Client, false, 2, uint32(s.Interfaces.Client.Peer.Index), Consistent_qp)) s.ConfigureVppProxy("tcp", s.Ports.Proxy) if proto == "udp" { @@ -354,12 +345,6 @@ func VppConnectProxyStressMWTest(s *VppProxySuite) { defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance - // tap interfaces are created on test setup with 1 rx-queue, - // need to recreate them with 2 + consistent-qp - s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Server)) - s.AssertNil(vppProxy.CreateTap(s.Interfaces.Server, false, 2, uint32(s.Interfaces.Server.Peer.Index), Consistent_qp)) - s.AssertNil(vppProxy.DeleteTap(s.Interfaces.Client)) - s.AssertNil(vppProxy.CreateTap(s.Interfaces.Client, false, 2, uint32(s.Interfaces.Client.Peer.Index), Consistent_qp)) s.ConfigureVppProxy("http", s.Ports.Proxy) diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index 445235fec8..a4b1aeaea4 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -915,7 +915,7 @@ active_open_connected_callback (u32 app_index, u32 opaque, ps->ao_disconnected = 1; if (ps->po.is_http) { - session_send_rpc_evt_to_thread ( + session_send_rpc_evt_to_thread_force ( session_thread_from_handle (ps->po.session_handle), active_open_send_http_resp_rpc, uword_to_pointer (ps->ps_index, void *)); From 4ba427d7eb8108079c09fa548c5202a3af55d3b8 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Tue, 24 Jun 2025 10:23:22 +0200 Subject: [PATCH 091/313] hs-test: support building master CalicoVPP (KinD) - we can now build master CalicoVPP with master VPP using: 'make setup-cluster BASE=master' - TODO: docs, use the same VPP build in CalicoVPP as in HST images (so that VPP isn't built twice) Type: test Change-Id: If5ba154a2f6868db04c17742f4e531269e57a56e Signed-off-by: Adrian Villin --- .gitignore | 1 + extras/hs-test/Makefile | 5 +- extras/hs-test/kubernetes/setupCluster.sh | 77 +++++++++++++++++++---- 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 5b20e10799..17b48e307c 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,7 @@ compile_commands.json /extras/hs-test/.goimports.ok /extras/hs-test/summary/ /extras/hs-test/.last_state_hash +/extras/hs-test/.kind_deps.ok # ./configure /CMakeFiles diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index c2885b299e..82b548d300 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -114,6 +114,9 @@ help: @echo " NO_COLOR=[true|false] - disables colorful Docker and Ginkgo output" @echo " TIMEOUT=[minutes] - test timeout override (5 minutes by default)" @echo " GINKGO_TIMEOUT=[Ns/m/h] - Ginkgo timeout override (3h by default)" + @echo + @echo "'make setup-cluster' arguments:" + @echo " BASE=[master|latest] - build master CalicoVPP with master VPP or use latest release of CalicoVPP (default = latest)" .PHONY: list-tests list-tests: @@ -191,7 +194,7 @@ test-perf: .deps.ok .build.ok .PHONY: setup-cluster setup-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh + @bash ./kubernetes/setupCluster.sh $(BASE) .PHONY: build-go build-go: diff --git a/extras/hs-test/kubernetes/setupCluster.sh b/extras/hs-test/kubernetes/setupCluster.sh index 4cbc9914e4..b972839b3d 100755 --- a/extras/hs-test/kubernetes/setupCluster.sh +++ b/extras/hs-test/kubernetes/setupCluster.sh @@ -1,19 +1,72 @@ #!/usr/bin/env bash set -e -echo "********" -echo "Performance tests only work on Ubuntu 22.04 for now." -echo "********" +MASTER_OR_LATEST=${1-"latest"} +CALICOVPP_DIR="$HOME/vpp-dataplane" -kind create cluster --config kubernetes/kind-config.yaml -kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml +if [ $MASTER_OR_LATEST = "master" ]; then + if [ ! -d "$CALICOVPP_DIR" ]; then + git clone https://github.com/projectcalico/vpp-dataplane.git $CALICOVPP_DIR + fi + cd $CALICOVPP_DIR + git pull -echo "Sleeping for 10s, waiting for tigera operator to start up." -sleep 10 + # ---------------- images ---------------- + export CALICO_AGENT_IMAGE=localhost:5000/calicovpp/agent:latest + export CALICO_VPP_IMAGE=localhost:5000/calicovpp/vpp:latest + export MULTINET_MONITOR_IMAGE=localhost:5000/calicovpp/multinet-monitor:latest + export IMAGE_PULL_POLICY=Always -kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml -kubectl create -f kubernetes/calico-config.yaml + # ---------------- interfaces ---------------- + export CALICOVPP_INTERFACES='{ + "uplinkInterfaces": [ + { + "interfaceName": "eth0", + "vppDriver": "af_packet" + } + ] + }' + export CALICOVPP_DISABLE_HUGEPAGES=true + export CALICOVPP_CONFIG_TEMPLATE=" + unix { + nodaemon + full-coredump + log /var/run/vpp/vpp.log + cli-listen /var/run/vpp/cli.sock + pidfile /run/vpp/vpp.pid + } + buffers { + buffers-per-numa 131072 + } + socksvr { socket-name /var/run/vpp/vpp-api.sock } + plugins { + plugin default { enable } + plugin calico_plugin.so { enable } + plugin dpdk_plugin.so { disable } + }" + export CALICOVPP_ENABLE_VCL=true -echo "Done. Please wait for the cluster to come fully online before running tests." -echo "Use 'watch kubectl get pods -A' to monitor cluster status." -echo "To delete the cluster, use 'kind delete cluster'" \ No newline at end of file + make -C $CALICOVPP_DIR kind-new-cluster N_KIND_WORKERS=2 + kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml + make -C $CALICOVPP_DIR/vpp-manager vpp BASE=origin/master + make -C $CALICOVPP_DIR dev-kind + make -C $CALICOVPP_DIR load-kind + $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up + else + echo "********" + echo "Performance tests only work on Ubuntu 22.04 for now." + echo "********" + + kind create cluster --config kubernetes/kind-config.yaml + kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml + + echo "Sleeping for 10s, waiting for tigera operator to start up." + sleep 10 + + kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml + kubectl create -f kubernetes/calico-config.yaml + + echo "Done. Please wait for the cluster to come fully online before running tests." + echo "Use 'watch kubectl get pods -A' to monitor cluster status." + echo "To delete the cluster, use 'kind delete cluster'" + fi From 6b8975e2a158cc84425f269abb4958d37beebfa5 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 23 Jun 2025 17:15:04 +0000 Subject: [PATCH 092/313] http: h2 decoding response Type: improvement Change-Id: I5e582e6fec972d6d61683a7a76c2a3f222a9030b Signed-off-by: Matus Fabian --- src/plugins/http/http2/hpack.c | 153 ++++++++++++++++++++++++- src/plugins/http/http2/hpack.h | 24 ++++ src/plugins/http/test/http_test.c | 184 ++++++++++++++++++++++++++++++ 3 files changed, 355 insertions(+), 6 deletions(-) diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 15e92fe683..8af061eb75 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -7,6 +7,7 @@ #include #include #include +#include #define HPACK_STATIC_TABLE_SIZE 61 @@ -771,6 +772,30 @@ hpack_parse_scheme (u8 *value, u32 value_len) return HTTP_URL_SCHEME_UNKNOWN; } +static inline int +hpack_parse_status_code (u8 *value, u32 value_len, http_status_code_t *sc) +{ + u16 status_code = 0; + u8 *p; + + if (value_len != 3) + return HTTP2_ERROR_PROTOCOL_ERROR; + + p = value; + parse_int (status_code, 100); + parse_int (status_code, 10); + parse_int (status_code, 1); + if (status_code < 100 || status_code > 599) + { + HTTP_DBG (1, "invalid status code %d", status_code); + return -1; + } + HTTP_DBG (1, "status code: %d", status_code); + *sc = http_sc_by_u16 (status_code); + + return 0; +} + static http2_error_t hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, u32 value_len, @@ -852,6 +877,32 @@ hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, return HTTP2_ERROR_NO_ERROR; } +static http2_error_t +hpack_parse_resp_pseudo_header (u8 *name, u32 name_len, u8 *value, + u32 value_len, + hpack_response_control_data_t *control_data) +{ + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, + value, value_len); + switch (name_len) + { + case 7: + if (!memcmp (name + 1, "status", 6)) + { + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_STATUS_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_STATUS_PARSED; + if (hpack_parse_status_code (value, value_len, &control_data->sc)) + return HTTP2_ERROR_PROTOCOL_ERROR; + } + break; + default: + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + return HTTP2_ERROR_NO_ERROR; +} + /* Special treatment for headers like: * * RFC9113 8.2.2: any message containing connection-specific header @@ -863,8 +914,7 @@ hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, */ always_inline http2_error_t hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, - uword index, - hpack_request_control_data_t *control_data) + uword index, uword *content_len_header_index) { switch (name_len) { @@ -895,8 +945,8 @@ hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, break; case 14: if (!memcmp (name, "content-length", 14) && - control_data->content_len_header_index == ~0) - control_data->content_len_header_index = index; + *content_len_header_index == ~0) + *content_len_header_index = index; break; case 16: if (!memcmp (name, "proxy-connection", 16)) @@ -987,8 +1037,99 @@ hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, control_data->headers_len += value_len; if (regular_header_parsed) { - rv = hpack_preprocess_header (name, name_len, value, value_len, - header - *headers, control_data); + rv = hpack_preprocess_header ( + name, name_len, value, value_len, header - *headers, + &control_data->content_len_header_index); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "connection-specific header present"); + return rv; + } + } + } + control_data->control_data_len = dst_len - b_left; + HTTP_DBG (2, "%U", format_hpack_dynamic_table, dynamic_table); + return HTTP2_ERROR_NO_ERROR; +} + +__clib_export http2_error_t +hpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len, + hpack_response_control_data_t *control_data, + http_field_line_t **headers, + hpack_dynamic_table_t *dynamic_table) +{ + u8 *p, *end, *b, *name, *value; + u8 regular_header_parsed = 0; + u32 name_len, value_len; + uword b_left; + http_field_line_t *header; + http2_error_t rv; + + p = src; + end = src + src_len; + b = dst; + b_left = dst_len; + control_data->parsed_bitmap = 0; + control_data->headers_len = 0; + control_data->content_len_header_index = ~0; + + while (p != end) + { + name = b; + rv = hpack_decode_header (&p, end, &b, &b_left, &name_len, &value_len, + dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_decode_header: %U", format_http2_error, rv); + return rv; + } + value = name + name_len; + + /* pseudo header */ + if (name[0] == ':') + { + /* all pseudo-headers must be before regular headers */ + if (regular_header_parsed) + { + HTTP_DBG (1, "pseudo-headers after regular header"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + rv = hpack_parse_resp_pseudo_header (name, name_len, value, + value_len, control_data); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_parse_resp_pseudo_header: %U", + format_http2_error, rv); + return rv; + } + continue; + } + else + { + if (!hpack_header_name_is_valid (name, name_len)) + return HTTP2_ERROR_PROTOCOL_ERROR; + if (!regular_header_parsed) + { + regular_header_parsed = 1; + control_data->headers = name; + } + } + if (!hpack_header_value_is_valid (value, value_len)) + return HTTP2_ERROR_PROTOCOL_ERROR; + vec_add2 (*headers, header, 1); + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, + format_http_bytes, value, value_len); + header->name_offset = name - control_data->headers; + header->name_len = name_len; + header->value_offset = value - control_data->headers; + header->value_len = value_len; + control_data->headers_len += name_len; + control_data->headers_len += value_len; + if (regular_header_parsed) + { + rv = hpack_preprocess_header ( + name, name_len, value, value_len, header - *headers, + &control_data->content_len_header_index); if (rv != HTTP2_ERROR_NO_ERROR) { HTTP_DBG (1, "connection-specific header present"); diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index a2fd5faed5..72b9f2b4be 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -73,6 +73,11 @@ typedef struct u32 server_name_len; u8 *date; u32 date_len; + u16 parsed_bitmap; + uword content_len_header_index; + u8 *headers; + u32 headers_len; + u32 control_data_len; } hpack_response_control_data_t; /** @@ -173,6 +178,25 @@ http2_error_t hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, http_field_line_t **headers, hpack_dynamic_table_t *dynamic_table); +/** + * Response parser + * + * @param src Header block to parse + * @param src_len Length of header block + * @param dst Buffer where headers will be decoded + * @param dst_len Length of buffer for decoded headers + * @param control_data Preparsed pseudo-headers + * @param headers List of regular headers + * @param dynamic_table Decoder dynamic table + * + * @return @c HTTP2_ERROR_NO_ERROR on success, connection error otherwise + */ +http2_error_t +hpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len, + hpack_response_control_data_t *control_data, + http_field_line_t **headers, + hpack_dynamic_table_t *dynamic_table); + /** * Serialize response * diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index 73a397aef2..fbeee4b6b0 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -635,6 +635,147 @@ http_test_parse_request (const char *first_req, uword first_req_len, return 0; } +static int +http_test_parse_response (const char *first_resp, uword first_resp_len, + const char *second_resp, uword second_resp_len, + const char *third_resp, uword third_resp_len, + hpack_dynamic_table_t *dynamic_table) +{ + http2_error_t rv; + u8 *buf = 0; + hpack_response_control_data_t control_data; + http_field_line_t *headers = 0; + u16 parsed_bitmap; + + static http2_error_t (*_hpack_parse_response) ( + u8 * src, u32 src_len, u8 * dst, u32 dst_len, + hpack_response_control_data_t * control_data, http_field_line_t * *headers, + hpack_dynamic_table_t * dynamic_table); + + _hpack_parse_response = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_parse_response"); + + parsed_bitmap = HPACK_PSEUDO_HEADER_STATUS_PARSED; + + /* first request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_response ((u8 *) first_resp, (u32) first_resp_len, buf, + 254, &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.sc != HTTP_STATUS_FOUND || vec_len (headers) != 3 || + dynamic_table->used != 222) + return 1; + if (headers[0].name_len != 13 || headers[0].value_len != 7) + return 1; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 1; + if (memcmp (control_data.headers + headers[0].value_offset, "private", 7)) + return 1; + if (headers[1].name_len != 4 || headers[1].value_len != 29) + return 1; + if (memcmp (control_data.headers + headers[1].name_offset, "date", 4)) + return 1; + if (memcmp (control_data.headers + headers[1].value_offset, + "Mon, 21 Oct 2013 20:13:21 GMT", 29)) + return 1; + if (headers[2].name_len != 8 || headers[2].value_len != 23) + return 1; + if (memcmp (control_data.headers + headers[2].name_offset, "location", 8)) + return 1; + if (memcmp (control_data.headers + headers[2].value_offset, + "https://www.example.com", 23)) + return 1; + vec_free (headers); + vec_free (buf); + + /* second request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_response ((u8 *) second_resp, (u32) second_resp_len, buf, + 254, &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.sc != HTTP_STATUS_TEMPORARY_REDIRECT || + vec_len (headers) != 3 || dynamic_table->used != 222) + return 2; + if (headers[0].name_len != 13 || headers[0].value_len != 7) + return 1; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 1; + if (memcmp (control_data.headers + headers[0].value_offset, "private", 7)) + return 1; + if (headers[1].name_len != 4 || headers[1].value_len != 29) + return 1; + if (memcmp (control_data.headers + headers[1].name_offset, "date", 4)) + return 1; + if (memcmp (control_data.headers + headers[1].value_offset, + "Mon, 21 Oct 2013 20:13:21 GMT", 29)) + return 1; + if (headers[2].name_len != 8 || headers[2].value_len != 23) + return 1; + if (memcmp (control_data.headers + headers[2].name_offset, "location", 8)) + return 1; + if (memcmp (control_data.headers + headers[2].value_offset, + "https://www.example.com", 23)) + return 1; + vec_free (headers); + vec_free (buf); + + /* third request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_response ((u8 *) third_resp, (u32) third_resp_len, buf, + 254, &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.sc != HTTP_STATUS_OK || vec_len (headers) != 5 || + dynamic_table->used != 215) + return 3; + if (headers[0].name_len != 13 || headers[0].value_len != 7) + return 1; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 1; + if (memcmp (control_data.headers + headers[0].value_offset, "private", 7)) + return 1; + if (headers[1].name_len != 4 || headers[1].value_len != 29) + return 1; + if (memcmp (control_data.headers + headers[1].name_offset, "date", 4)) + return 1; + if (memcmp (control_data.headers + headers[1].value_offset, + "Mon, 21 Oct 2013 20:13:22 GMT", 29)) + return 1; + if (headers[2].name_len != 8 || headers[2].value_len != 23) + return 1; + if (memcmp (control_data.headers + headers[2].name_offset, "location", 8)) + return 1; + if (memcmp (control_data.headers + headers[2].value_offset, + "https://www.example.com", 23)) + return 1; + if (headers[3].name_len != 16 || headers[3].value_len != 4) + return 1; + if (memcmp (control_data.headers + headers[3].name_offset, + "content-encoding", 16)) + return 1; + if (memcmp (control_data.headers + headers[3].value_offset, "gzip", 4)) + return 1; + if (headers[4].name_len != 10 || headers[4].value_len != 56) + return 1; + if (memcmp (control_data.headers + headers[4].name_offset, "set-cookie", 10)) + return 1; + if (memcmp (control_data.headers + headers[4].value_offset, + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 56)) + return 1; + vec_free (headers); + vec_free (buf); + + return 0; +} + static int http_test_hpack (vlib_main_t *vm) { @@ -956,6 +1097,49 @@ http_test_hpack (vlib_main_t *vm) _hpack_dynamic_table_free (&table); HTTP_TEST ((result == 0), "request with Huffman Coding (result=%d)", result); + vlib_cli_output (vm, "hpack_parse_response"); + + /* C.5. Response Examples without Huffman Coding */ + _hpack_dynamic_table_init (&table, 256); + result = http_test_parse_response ( + http_token_lit ( + "\x48\x03\x33\x30\x32\x58\x07\x70\x72\x69\x76\x61\x74\x65" + "\x61\x1D\x4D\x6F\x6E\x2C\x20\x32\x31\x20\x4F\x63\x74\x20\x32\x30\x31" + "\x33\x20\x32\x30\x3A\x31\x33\x3A\x32\x31\x20\x47\x4D\x54\x6E\x17\x68" + "\x74\x74\x70\x73\x3A\x2F\x2F\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C" + "\x65\x2E\x63\x6F\x6D"), + http_token_lit ("\x48\x03\x33\x30\x37\xC1\xC0\xBF"), + http_token_lit ( + "\x88\xC1\x61\x1D\x4D\x6F\x6E\x2C\x20\x32\x31\x20\x4F\x63\x74\x20\x32" + "\x30\x31\x33\x20\x32\x30\x3A\x31\x33\x3A\x32\x32\x20\x47\x4D\x54\xC0" + "\x5A\x04\x67\x7A\x69\x70\x77\x38\x66\x6F\x6F\x3D\x41\x53\x44\x4A\x4B" + "\x48\x51\x4B\x42\x5A\x58\x4F\x51\x57\x45\x4F\x50\x49\x55\x41\x58\x51" + "\x57\x45\x4F\x49\x55\x3B\x20\x6D\x61\x78\x2D\x61\x67\x65\x3D\x33\x36" + "\x30\x30\x3B\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x31"), + &table); + _hpack_dynamic_table_free (&table); + HTTP_TEST ((result == 0), "response without Huffman Coding (result=%d)", + result); + + /* C.6. Response Examples with Huffman Coding */ + _hpack_dynamic_table_init (&table, 256); + result = http_test_parse_response ( + http_token_lit ("\x48\x82\x64\x02\x58\x85\xAE\xC3\x77\x1A\x4B\x61\x96\xD0" + "\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04\x0B\x81" + "\x66\xE0\x82\xA6\x2D\x1B\xFF\x6E\x91\x9D\x29\xAD\x17\x18" + "\x63\xC7\x8F\x0B\x97\xC8\xE9\xAE\x82\xAE\x43\xD3"), + http_token_lit ("\x48\x83\x64\x0E\xFF\xC1\xC0\xBF"), + http_token_lit ( + "\x88\xC1\x61\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04" + "\x0B\x81\x66\xE0\x84\xA6\x2D\x1B\xFF\xC0\x5A\x83\x9B\xD9\xAB\x77\xAD" + "\x94\xE7\x82\x1D\xD7\xF2\xE6\xC7\xB3\x35\xDF\xDF\xCD\x5B\x39\x60\xD5" + "\xAF\x27\x08\x7F\x36\x72\xC1\xAB\x27\x0F\xB5\x29\x1F\x95\x87\x31\x60" + "\x65\xC0\x03\xED\x4E\xE5\xB1\x06\x3D\x50\x07"), + &table); + _hpack_dynamic_table_free (&table); + HTTP_TEST ((result == 0), "response with Huffman Coding (result=%d)", + result); + vlib_cli_output (vm, "hpack_serialize_response"); hpack_response_control_data_t resp_cd; From 74cf96576768f6e9200848c613428990049b3563 Mon Sep 17 00:00:00 2001 From: Masih Nilforoush Date: Thu, 27 Feb 2025 14:44:44 +0100 Subject: [PATCH 093/313] gre: Add support for GRE keys in the GRE plugin The added feature enables the GRE plugin to create tunnels between the same endpoints, distinguishing them by the "key" value. It uses the standard 'key' parameter in the GRE header. Changes have been made to add support for CLI and API to create tunnels with a "key" value. The CLI syntax is as follows: create gre tunnel src src_IP_Address dst dst_IP_Address key key_value All existing GRE functionalities, such as tunnel type and mode, remain unchanged. GRE key support has been implemented for all non-ERSPAN tunnel types, including both IPv4 and IPv6. Additionally, modifications were made to the GRE packet header, data structure, and inbound/outbound packet processing to accommodate key configuration through CLI and API. Type: feature Change-Id: I222d585007fa264e7cc12c79d6ba9c63c044f133 Signed-off-by: Masih Nilforoush --- src/plugins/gre/FEATURE.yaml | 3 +- src/plugins/gre/gre.api | 96 +++++++++++++++++++++++ src/plugins/gre/gre.c | 27 +++++-- src/plugins/gre/gre.h | 103 ++++++++++++++----------- src/plugins/gre/gre_api.c | 115 ++++++++++++++++++++++++++++ src/plugins/gre/interface.c | 35 +++++++-- src/plugins/gre/node.c | 142 +++++++++++++++++++++++------------ src/vnet/gre/packet.h | 90 +++++++++++----------- test/test_gre.py | 122 ++++++++++++++++++++++++++++++ test/vpp_gre_interface.py | 125 +++++++++++++++++++++++------- 10 files changed, 687 insertions(+), 171 deletions(-) diff --git a/src/plugins/gre/FEATURE.yaml b/src/plugins/gre/FEATURE.yaml index 4b35b870dc..029a59a40f 100644 --- a/src/plugins/gre/FEATURE.yaml +++ b/src/plugins/gre/FEATURE.yaml @@ -6,8 +6,7 @@ features: - Encap/Decap flags to control the copying of DSCP, ECN, DF from overlay to underlay and vice-versa. - L2 tunnels -missing: - - GRE keys + - GRE keys for IPv4 and IPv6 description: "An implementation of Generic Routing Encapsulation (GRE)" state: production properties: [API, CLI, MULTITHREAD] diff --git a/src/plugins/gre/gre.api b/src/plugins/gre/gre.api index 9c69ba4007..1f680c8d89 100644 --- a/src/plugins/gre/gre.api +++ b/src/plugins/gre/gre.api @@ -20,6 +20,17 @@ import "vnet/interface_types.api"; import "vnet/tunnel/tunnel_types.api"; import "vnet/ip/ip_types.api"; +/* Enable the in-progress messages */ +option status = "in_progress"; + +/* + * Define the service relationships + */ +service { + rpc gre_tunnel_dump returns gre_tunnel_dump_reply events gre_tunnel_details; + rpc gre_tunnel_dump_v2 returns gre_tunnel_dump_v2_reply events gre_tunnel_details_v2; +}; + /** \brief A GRE tunnel type */ enum gre_tunnel_type : u8 @@ -55,6 +66,32 @@ typedef gre_tunnel vl_api_address_t dst; }; +/** \brief A composite type uniquely defining a GRE tunnel with key support. + @param type - tunnel type (see enum definition), 0: L3, 1: TEB, 2: ERSPAN + @param mode - P2P or P2MP + @param flags - to control encap/decap behaviour + @param session_id - session for ERSPAN tunnel, range 0-1023 + @param instance - optional unique custom device instance, else ~0. + @param outer_table_id - Encap FIB table ID + @param sw_if_index - ignored on create/delete, present in details. + @param src - Source IP address + @param dst - Destination IP address, can be multicast + @param key - GRE key value (RFC 2890), 0 for no key +*/ +typedef gre_tunnel_v2 +{ + vl_api_gre_tunnel_type_t type; + vl_api_tunnel_mode_t mode; + vl_api_tunnel_encap_decap_flags_t flags; + u16 session_id; + u32 instance; + u32 outer_table_id; + vl_api_interface_index_t sw_if_index; + vl_api_address_t src; + vl_api_address_t dst; + u32 key; +}; + /** \brief Add or delete a single GRE tunnel. @param client_index - opaque cookie to identify the sender. @param context - sender context, to match reply w/ request. @@ -81,6 +118,32 @@ define gre_tunnel_add_del_reply vl_api_interface_index_t sw_if_index; }; +/** \brief Add or delete a single GRE tunnel with key support. + @param client_index - opaque cookie to identify the sender. + @param context - sender context, to match reply w/ request. + @param is_add - add if true, delete if false. + @param tunnel - tunnel definition to add or delete (with key field). +*/ +define gre_tunnel_add_del_v2 +{ + u32 client_index; + u32 context; + bool is_add; + vl_api_gre_tunnel_v2_t tunnel; +}; + +/** \brief Add or delete a single GRE tunnel with key support. + @param context - sender context, to match reply w/ request. + @param retval - return code for the request. + @param sw_if_index - the interface corresponding to the affected tunnel. +*/ +define gre_tunnel_add_del_v2_reply +{ + u32 context; + i32 retval; + vl_api_interface_index_t sw_if_index; +}; + /** \brief Dump details of all or just a single GRE tunnel. @param client_index - opaque cookie to identify the sender. @param context - sender context, to match reply w/ request. @@ -93,6 +156,29 @@ define gre_tunnel_dump vl_api_interface_index_t sw_if_index; }; +/** \brief Reply for gre_tunnel_dump - empty since actual data is sent via gre_tunnel_details. + @param context - sender context, to match reply w/ request. + @param retval - return code for the request. +*/ +define gre_tunnel_dump_reply +{ + u32 context; + i32 retval; +}; + +/** \brief Dump details of all or just a single GRE tunnel with key support. + @param client_index - opaque cookie to identify the sender. + @param context - sender context, to match reply w/ request. + @param sw_if_index - filter for tunnel of this interface index, ~0 for all. +*/ +autoreply define gre_tunnel_dump_v2 +{ + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index; +}; + + /** \brief Details response for one of the requested GRE tunnels. @param context - sender context, to match reply w/ request. @param tunnel - definition of the dumped tunnel. @@ -103,6 +189,16 @@ define gre_tunnel_details vl_api_gre_tunnel_t tunnel; }; +/** \brief Details response for one of the requested GRE tunnels with key support. + @param context - sender context, to match reply w/ request. + @param tunnel - definition of the dumped tunnel (with key field). +*/ +define gre_tunnel_details_v2 +{ + u32 context; + vl_api_gre_tunnel_v2_t tunnel; +}; + /* * Local Variables: * eval: (c-set-style "gnu") diff --git a/src/plugins/gre/gre.c b/src/plugins/gre/gre.c index ce11ee9ecb..21a628b449 100644 --- a/src/plugins/gre/gre.c +++ b/src/plugins/gre/gre.c @@ -232,7 +232,11 @@ gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, if (!is_ipv6) { - vec_validate (rewrite, sizeof (*h4) - 1); + /* Allocate space for maximum header size including key */ + if (gre_key_is_valid (t->gre_key)) + vec_validate (rewrite, sizeof (*h4) + sizeof (gre_key_t) - 1); + else + vec_validate (rewrite, sizeof (*h4) - 1); h4 = (ip4_and_gre_header_t *) rewrite; gre = &h4->gre; h4->ip4.ip_version_and_header_length = 0x45; @@ -245,7 +249,11 @@ gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, } else { - vec_validate (rewrite, sizeof (*h6) - 1); + /* Allocate space for maximum header size including key */ + if (gre_key_is_valid (t->gre_key)) + vec_validate (rewrite, sizeof (*h6) + sizeof (gre_key_t) - 1); + else + vec_validate (rewrite, sizeof (*h6) - 1); h6 = (ip6_and_gre_header_t *) rewrite; gre = &h6->gre; h6->ip6.ip_version_traffic_class_and_flow_label = @@ -265,9 +273,18 @@ gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, gre->flags_and_version = clib_host_to_net_u16 (GRE_FLAGS_SEQUENCE); } else - gre->protocol = - clib_host_to_net_u16 (gre_proto_from_vnet_link (link_type)); - + { + gre->protocol = + clib_host_to_net_u16 (gre_proto_from_vnet_link (link_type)); + gre->flags_and_version = 0; // Clear flags first + /* Add key only for non-ERSPAN tunnels */ + if (gre_key_is_valid (t->gre_key)) + { + gre_header_with_key_t *grek = (gre_header_with_key_t *) gre; + grek->flags_and_version = clib_host_to_net_u16 (GRE_FLAGS_KEY); + grek->key = clib_host_to_net_u32 (t->gre_key); + } + } return (rewrite); } diff --git a/src/plugins/gre/gre.h b/src/plugins/gre/gre.h index ce57454f9b..03f0df163d 100644 --- a/src/plugins/gre/gre.h +++ b/src/plugins/gre/gre.h @@ -31,7 +31,7 @@ extern vnet_hw_interface_class_t mgre_hw_interface_class; typedef enum { -#define gre_error(n,s) GRE_ERROR_##n, +#define gre_error(n, s) GRE_ERROR_##n, #include #undef gre_error GRE_N_ERROR, @@ -45,10 +45,10 @@ typedef enum * and output of mirrored packet from a L2 network only. There is * no support for receiving ERSPAN packets from a GRE ERSPAN tunnel */ -#define foreach_gre_tunnel_type \ - _(L3, "L3") \ - _(TEB, "TEB") \ - _(ERSPAN, "ERSPAN") \ +#define foreach_gre_tunnel_type \ + _ (L3, "L3") \ + _ (TEB, "TEB") \ + _ (ERSPAN, "ERSPAN") /** * @brief The GRE tunnel type @@ -60,8 +60,22 @@ typedef enum gre_tunnel_type_t_ #undef _ } __clib_packed gre_tunnel_type_t; -extern u8 *format_gre_tunnel_type (u8 * s, va_list * args); +/** + * @brief GRE key type (RFC 2890) + */ +typedef u32 gre_key_t; + +/** + * @brief Check if a GRE key is valid (non-zero) + */ +#define gre_key_is_valid(_key) ((_key) != 0) +extern u8 *format_gre_tunnel_type (u8 *s, va_list *args); + +/** + * @brief Format a GRE key for display + */ +format_function_t format_gre_key; /** * A GRE payload protocol registration @@ -94,15 +108,16 @@ typedef struct gre_tunnel_key_common_t_ struct { u32 fib_index; + gre_key_t gre_key; u16 session_id; gre_tunnel_type_t type; tunnel_mode_t mode; }; - u64 as_u64; + u64 as_u64[2]; }; -} gre_tunnel_key_common_t; +} __clib_packed gre_tunnel_key_common_t; -STATIC_ASSERT_SIZEOF (gre_tunnel_key_common_t, sizeof (u64)); +STATIC_ASSERT_SIZEOF (gre_tunnel_key_common_t, 2 * sizeof (u64)); /** * @brief Key for a IPv4 GRE Tunnel @@ -126,7 +141,7 @@ typedef struct gre_tunnel_key4_t_ gre_tunnel_key_common_t gtk_common; } __attribute__ ((packed)) gre_tunnel_key4_t; -STATIC_ASSERT_SIZEOF (gre_tunnel_key4_t, 2 * sizeof (u64)); +STATIC_ASSERT_SIZEOF (gre_tunnel_key4_t, 3 * sizeof (u64)); /** * @brief Key for a IPv6 GRE Tunnel @@ -144,7 +159,7 @@ typedef struct gre_tunnel_key6_t_ gre_tunnel_key_common_t gtk_common; } __attribute__ ((packed)) gre_tunnel_key6_t; -STATIC_ASSERT_SIZEOF (gre_tunnel_key6_t, 5 * sizeof (u64)); +STATIC_ASSERT_SIZEOF (gre_tunnel_key6_t, 6 * sizeof (u64)); /** * Union of the two possible key types @@ -205,6 +220,8 @@ typedef struct u32 sw_if_index; gre_tunnel_type_t type; tunnel_mode_t mode; + gre_key_t gre_key; + tunnel_encap_decap_flags_t flags; /** @@ -220,14 +237,14 @@ typedef struct /** * GRE header sequence number (SN) used for ERSPAN type 2 header, must be * bumped automically to be thread safe. As multiple GRE tunnels are created - * for the same fib-idx/DIP/SIP with different ERSPAN session number, they all - * share the same SN which is kept per FIB/DIP/SIP, as specified by RFC2890. + * for the same fib-idx/DIP/SIP with different ERSPAN session number, they + * all share the same SN which is kept per FIB/DIP/SIP, as specified by + * RFC2890. */ gre_sn_t *gre_sn; - - u32 dev_instance; /* Real device instance in tunnel vector */ - u32 user_instance; /* Instance name being shown to user */ + u32 dev_instance; /* Real device instance in tunnel vector */ + u32 user_instance; /* Instance name being shown to user */ } gre_tunnel_t; typedef struct @@ -307,7 +324,7 @@ typedef CLIB_PACKED (struct { }) ip6_and_gre_header_t; always_inline gre_protocol_info_t * -gre_get_protocol_info (gre_main_t * em, gre_protocol_t protocol) +gre_get_protocol_info (gre_main_t *em, gre_protocol_t protocol) { uword *p = hash_get (em->protocol_info_by_protocol, protocol); return p ? vec_elt_at_index (em->protocol_infos, p[0]) : 0; @@ -315,12 +332,11 @@ gre_get_protocol_info (gre_main_t * em, gre_protocol_t protocol) extern gre_main_t gre_main; -extern clib_error_t *gre_interface_admin_up_down (vnet_main_t * vnm, +extern clib_error_t *gre_interface_admin_up_down (vnet_main_t *vnm, u32 hw_if_index, u32 flags); extern void gre_tunnel_stack (adj_index_t ai); -extern void gre_update_adj (vnet_main_t * vnm, - u32 sw_if_index, adj_index_t ai); +extern void gre_update_adj (vnet_main_t *vnm, u32 sw_if_index, adj_index_t ai); typedef struct mgre_walk_ctx_t_ { @@ -350,12 +366,12 @@ unformat_function_t unformat_gre_protocol_net_byte_order; unformat_function_t unformat_gre_header; unformat_function_t unformat_pg_gre_header; -void -gre_register_input_protocol (vlib_main_t * vm, gre_protocol_t protocol, - u32 node_index, gre_tunnel_type_t tunnel_type); +void gre_register_input_protocol (vlib_main_t *vm, gre_protocol_t protocol, + u32 node_index, + gre_tunnel_type_t tunnel_type); /* manually added to the interface output node in gre.c */ -#define GRE_OUTPUT_NEXT_LOOKUP 1 +#define GRE_OUTPUT_NEXT_LOOKUP 1 typedef struct { @@ -367,61 +383,62 @@ typedef struct ip46_address_t src, dst; u32 outer_table_id; u16 session_id; + gre_key_t gre_key; tunnel_encap_decap_flags_t flags; } vnet_gre_tunnel_add_del_args_t; -extern int vnet_gre_tunnel_add_del (vnet_gre_tunnel_add_del_args_t * a, - u32 * sw_if_indexp); +extern int vnet_gre_tunnel_add_del (vnet_gre_tunnel_add_del_args_t *a, + u32 *sw_if_indexp); static inline void -gre_mk_key4 (ip4_address_t src, - ip4_address_t dst, - u32 fib_index, - gre_tunnel_type_t ttype, - tunnel_mode_t tmode, u16 session_id, gre_tunnel_key4_t * key) +gre_mk_key4 (ip4_address_t src, ip4_address_t dst, u32 fib_index, + gre_tunnel_type_t ttype, tunnel_mode_t tmode, u16 session_id, + gre_key_t gre_key, gre_tunnel_key4_t *key) { + clib_memset (key, 0, sizeof (*key)); // Zero entire structure first key->gtk_src = src; key->gtk_dst = dst; key->gtk_common.type = ttype; key->gtk_common.mode = tmode; key->gtk_common.fib_index = fib_index; key->gtk_common.session_id = session_id; + key->gtk_common.gre_key = gre_key; } static inline int -gre_match_key4 (const gre_tunnel_key4_t * key1, - const gre_tunnel_key4_t * key2) +gre_match_key4 (const gre_tunnel_key4_t *key1, const gre_tunnel_key4_t *key2) { return ((key1->gtk_as_u64 == key2->gtk_as_u64) && - (key1->gtk_common.as_u64 == key2->gtk_common.as_u64)); + (key1->gtk_common.as_u64[0] == key2->gtk_common.as_u64[0]) && + (key1->gtk_common.as_u64[1] == key2->gtk_common.as_u64[1])); } static inline void -gre_mk_key6 (const ip6_address_t * src, - const ip6_address_t * dst, - u32 fib_index, - gre_tunnel_type_t ttype, - tunnel_mode_t tmode, u16 session_id, gre_tunnel_key6_t * key) +gre_mk_key6 (const ip6_address_t *src, const ip6_address_t *dst, u32 fib_index, + gre_tunnel_type_t ttype, tunnel_mode_t tmode, u16 session_id, + gre_key_t gre_key, gre_tunnel_key6_t *key) { + clib_memset (key, 0, sizeof (*key)); // Zero entire structure first key->gtk_src = *src; key->gtk_dst = *dst; key->gtk_common.type = ttype; key->gtk_common.mode = tmode; key->gtk_common.fib_index = fib_index; key->gtk_common.session_id = session_id; + key->gtk_common.gre_key = gre_key; } static inline int -gre_match_key6 (const gre_tunnel_key6_t * key1, - const gre_tunnel_key6_t * key2) +gre_match_key6 (const gre_tunnel_key6_t *key1, const gre_tunnel_key6_t *key2) { return (ip6_address_is_equal (&key1->gtk_src, &key2->gtk_src) && ip6_address_is_equal (&key1->gtk_dst, &key2->gtk_dst) && - (key1->gtk_common.as_u64 == key2->gtk_common.as_u64)); + (key1->gtk_common.as_u64[0] == key2->gtk_common.as_u64[0]) && + (key1->gtk_common.as_u64[1] == key2->gtk_common.as_u64[1])); } static inline void -gre_mk_sn_key (const gre_tunnel_t * gt, gre_sn_key_t * key) +gre_mk_sn_key (const gre_tunnel_t *gt, gre_sn_key_t *key) { key->src = gt->tunnel_src; key->dst = gt->tunnel_dst.fp_addr; diff --git a/src/plugins/gre/gre_api.c b/src/plugins/gre/gre_api.c index 5149f92fb8..168f2e153f 100644 --- a/src/plugins/gre/gre_api.c +++ b/src/plugins/gre/gre_api.c @@ -114,6 +114,7 @@ vl_api_gre_tunnel_add_del_t_handler (vl_api_gre_tunnel_add_del_t *mp) a->session_id = ntohs (mp->tunnel.session_id); a->outer_table_id = ntohl (mp->tunnel.outer_table_id); a->flags = flags; + a->gre_key = 0; // No key in the v1 API rv = vnet_gre_tunnel_add_del (a, &sw_if_index); @@ -122,6 +123,61 @@ vl_api_gre_tunnel_add_del_t_handler (vl_api_gre_tunnel_add_del_t *mp) ({ rmp->sw_if_index = ntohl (sw_if_index); })); } +static void +vl_api_gre_tunnel_add_del_v2_t_handler (vl_api_gre_tunnel_add_del_v2_t *mp) +{ + vnet_gre_tunnel_add_del_args_t _a = {}, *a = &_a; + vl_api_gre_tunnel_add_del_v2_reply_t *rmp; + tunnel_encap_decap_flags_t flags; + u32 sw_if_index = ~0; + ip46_type_t itype[2]; + int rv = 0; + + itype[0] = ip_address_decode (&mp->tunnel.src, &a->src); + itype[1] = ip_address_decode (&mp->tunnel.dst, &a->dst); + + if (itype[0] != itype[1]) + { + rv = VNET_API_ERROR_INVALID_PROTOCOL; + goto out; + } + + if (ip46_address_is_equal (&a->src, &a->dst)) + { + rv = VNET_API_ERROR_SAME_SRC_DST; + goto out; + } + + rv = gre_tunnel_type_decode (mp->tunnel.type, &a->type); + + if (rv) + goto out; + + rv = tunnel_mode_decode (mp->tunnel.mode, &a->mode); + + if (rv) + goto out; + + rv = tunnel_encap_decap_flags_decode (mp->tunnel.flags, &flags); + + if (rv) + goto out; + + a->is_add = mp->is_add; + a->is_ipv6 = (itype[0] == IP46_TYPE_IP6); + a->instance = ntohl (mp->tunnel.instance); + a->session_id = ntohs (mp->tunnel.session_id); + a->outer_table_id = ntohl (mp->tunnel.outer_table_id); + a->flags = flags; + a->gre_key = ntohl (mp->tunnel.key); // Key field present in v2 API + + rv = vnet_gre_tunnel_add_del (a, &sw_if_index); + +out: + REPLY_MACRO2 (VL_API_GRE_TUNNEL_ADD_DEL_V2_REPLY, + ({ rmp->sw_if_index = ntohl (sw_if_index); })); +} + static void send_gre_tunnel_details (gre_tunnel_t *t, vl_api_gre_tunnel_dump_t *mp) { @@ -145,6 +201,31 @@ send_gre_tunnel_details (gre_tunnel_t *t, vl_api_gre_tunnel_dump_t *mp) })); } +static void +send_gre_tunnel_details_v2 (gre_tunnel_t *t, vl_api_gre_tunnel_dump_v2_t *mp) +{ + vl_api_gre_tunnel_details_v2_t *rmp; + + REPLY_MACRO_DETAILS2 ( + VL_API_GRE_TUNNEL_DETAILS_V2, ({ + ip_address_encode (&t->tunnel_src, IP46_TYPE_ANY, &rmp->tunnel.src); + ip_address_encode (&t->tunnel_dst.fp_addr, IP46_TYPE_ANY, + &rmp->tunnel.dst); + + rmp->tunnel.outer_table_id = htonl ( + fib_table_get_table_id (t->outer_fib_index, t->tunnel_dst.fp_proto)); + + rmp->tunnel.type = gre_tunnel_type_encode (t->type); + rmp->tunnel.mode = tunnel_mode_encode (t->mode); + rmp->tunnel.flags = tunnel_encap_decap_flags_encode (t->flags); + rmp->tunnel.instance = htonl (t->user_instance); + rmp->tunnel.sw_if_index = htonl (t->sw_if_index); + rmp->tunnel.session_id = htons (t->session_id); + rmp->tunnel.key = + htonl (t->gre_key); // Include the GRE key in the v2 API + })); +} + static void vl_api_gre_tunnel_dump_t_handler (vl_api_gre_tunnel_dump_t *mp) { @@ -179,6 +260,40 @@ vl_api_gre_tunnel_dump_t_handler (vl_api_gre_tunnel_dump_t *mp) } } +static void +vl_api_gre_tunnel_dump_v2_t_handler (vl_api_gre_tunnel_dump_v2_t *mp) +{ + vl_api_registration_t *reg; + gre_main_t *gm = &gre_main; + gre_tunnel_t *t; + u32 sw_if_index; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + sw_if_index = ntohl (mp->sw_if_index); + + if (~0 == sw_if_index) + { + pool_foreach (t, gm->tunnels) + { + send_gre_tunnel_details_v2 (t, mp); + } + } + + else + { + if ((sw_if_index >= vec_len (gm->tunnel_index_by_sw_if_index)) || + (~0 == gm->tunnel_index_by_sw_if_index[sw_if_index])) + { + return; + } + t = &gm->tunnels[gm->tunnel_index_by_sw_if_index[sw_if_index]]; + send_gre_tunnel_details_v2 (t, mp); + } +} + /* * gre_api_hookup * Add vpe's API message handlers to the table. diff --git a/src/plugins/gre/interface.c b/src/plugins/gre/interface.c index bd9a607850..414a7f8171 100644 --- a/src/plugins/gre/interface.c +++ b/src/plugins/gre/interface.c @@ -43,6 +43,20 @@ format_gre_tunnel_type (u8 *s, va_list *args) return (s); } +/** + * @brief Format a GRE key for display + */ +u8 * +format_gre_key (u8 *s, va_list *args) +{ + gre_key_t key = va_arg (*args, gre_key_t); + + if (!gre_key_is_valid (key)) + return format (s, "INVALID"); + else + return format (s, "%u", key); +} + static u8 * format_gre_tunnel (u8 *s, va_list *args) { @@ -57,6 +71,9 @@ format_gre_tunnel (u8 *s, va_list *args) s = format (s, "payload %U ", format_gre_tunnel_type, t->type); s = format (s, "%U ", format_tunnel_mode, t->mode); + if (gre_key_is_valid (t->gre_key)) + s = format (s, "key %U ", format_gre_key, t->gre_key); + if (t->type == GRE_TUNNEL_TYPE_ERSPAN) s = format (s, "session %d ", t->session_id); @@ -76,13 +93,13 @@ gre_tunnel_db_find (const vnet_gre_tunnel_add_del_args_t *a, if (!a->is_ipv6) { gre_mk_key4 (a->src.ip4, a->dst.ip4, outer_fib_index, a->type, a->mode, - a->session_id, &key->gtk_v4); + a->session_id, a->gre_key, &key->gtk_v4); p = hash_get_mem (gm->tunnel_by_key4, &key->gtk_v4); } else { gre_mk_key6 (&a->src.ip6, &a->dst.ip6, outer_fib_index, a->type, a->mode, - a->session_id, &key->gtk_v6); + a->session_id, a->gre_key, &key->gtk_v6); p = hash_get_mem (gm->tunnel_by_key6, &key->gtk_v6); } @@ -247,11 +264,11 @@ gre_teib_mk_key (const gre_tunnel_t *t, const teib_entry_t *ne, if (FIB_PROTOCOL_IP4 == nh->fp_proto) gre_mk_key4 (t->tunnel_src.ip4, nh->fp_addr.ip4, teib_entry_get_fib_index (ne), t->type, TUNNEL_MODE_P2P, 0, - &key->gtk_v4); + t->gre_key, &key->gtk_v4); else gre_mk_key6 (&t->tunnel_src.ip6, &nh->fp_addr.ip6, teib_entry_get_fib_index (ne), t->type, TUNNEL_MODE_P2P, 0, - &key->gtk_v6); + t->gre_key, &key->gtk_v6); } /** @@ -373,6 +390,8 @@ vnet_gre_tunnel_add (vnet_gre_tunnel_add_del_args_t *a, u32 outer_fib_index, pool_get_aligned (gm->tunnels, t, CLIB_CACHE_LINE_BYTES); clib_memset (t, 0, sizeof (*t)); + // added for GRE Key - only mark as present if key is non-zero + t->gre_key = a->gre_key; /* Reconcile the real dev_instance and a possible requested instance */ u32 t_idx = t - gm->tunnels; /* tunnel index (or instance) */ @@ -646,7 +665,7 @@ create_gre_tunnel_command_fn (vlib_main_t *vm, unformat_input_t *input, u8 is_add = 1; u32 sw_if_index; clib_error_t *error = NULL; - + u32 key = 0; // added GRE key /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) return 0; @@ -672,6 +691,8 @@ create_gre_tunnel_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "flags %U", unformat_tunnel_encap_decap_flags, &flags)) ; + else if (unformat (line_input, "key %u", &key)) + ; else { error = clib_error_return (0, "unknown input `%U'", @@ -713,6 +734,7 @@ create_gre_tunnel_command_fn (vlib_main_t *vm, unformat_input_t *input, a->is_ipv6 = !ip46_address_is_ip4 (&src); a->instance = instance; a->flags = flags; + a->gre_key = key; clib_memcpy (&a->src, &src, sizeof (a->src)); clib_memcpy (&a->dst, &dst, sizeof (a->dst)); @@ -756,7 +778,8 @@ VLIB_CLI_COMMAND (create_gre_tunnel_command, static) = { .path = "create gre tunnel", .short_help = "create gre tunnel src dst [instance ] " "[outer-fib-id ] [teb | erspan ] [del] " - "[multipoint]", + "[multipoint]" + "[key ]", .function = create_gre_tunnel_command_fn, }; diff --git a/src/plugins/gre/node.c b/src/plugins/gre/node.c index 5235888cc6..541cae31a4 100644 --- a/src/plugins/gre/node.c +++ b/src/plugins/gre/node.c @@ -102,7 +102,7 @@ gre_tunnel_get (const gre_main_t *gm, vlib_node_runtime_t *node, { const uword *p; p = is_ipv6 ? hash_get_mem (gm->tunnel_by_key6, &key->gtk_v6) : - hash_get_mem (gm->tunnel_by_key4, &key->gtk_v4); + hash_get_mem (gm->tunnel_by_key4, &key->gtk_v4); if (PREDICT_FALSE (!p)) { *next = GRE_INPUT_NEXT_DROP; @@ -172,8 +172,17 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, ip6[1] = vlib_buffer_get_current (b[1]); gre[0] = (void *) (ip6[0] + 1); gre[1] = (void *) (ip6[1] + 1); - vlib_buffer_advance (b[0], sizeof (*ip6[0]) + sizeof (*gre[0])); - vlib_buffer_advance (b[1], sizeof (*ip6[0]) + sizeof (*gre[0])); + /* Calculate total header size for each packet */ + u16 gre_hdr_size0 = sizeof (*gre[0]); + u16 gre_hdr_size1 = sizeof (*gre[1]); + if (gre[0]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + gre_hdr_size0 += sizeof (gre_key_t); + if (gre[1]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + gre_hdr_size1 += sizeof (gre_key_t); + + /* Single buffer advance for each packet */ + vlib_buffer_advance (b[0], sizeof (*ip6[0]) + gre_hdr_size0); + vlib_buffer_advance (b[1], sizeof (*ip6[1]) + gre_hdr_size1); } else { @@ -182,8 +191,30 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, ip4[1] = vlib_buffer_get_current (b[1]); gre[0] = (void *) (ip4[0] + 1); gre[1] = (void *) (ip4[1] + 1); - vlib_buffer_advance (b[0], sizeof (*ip4[0]) + sizeof (*gre[0])); - vlib_buffer_advance (b[1], sizeof (*ip4[0]) + sizeof (*gre[0])); + /* Calculate total header size for each packet */ + u16 gre_hdr_size0 = sizeof (*gre[0]); + u16 gre_hdr_size1 = sizeof (*gre[1]); + if (gre[0]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + gre_hdr_size0 += sizeof (gre_key_t); + if (gre[1]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + gre_hdr_size1 += sizeof (gre_key_t); + + /* Single buffer advance for each packet */ + vlib_buffer_advance (b[0], sizeof (*ip4[0]) + gre_hdr_size0); + vlib_buffer_advance (b[1], sizeof (*ip4[1]) + gre_hdr_size1); + } + + /* GRE key processing here */ + gre_key_t gre_key[2] = { 0, 0 }; + if (gre[0]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + { + gre_header_with_key_t *grek = (gre_header_with_key_t *) gre[0]; + gre_key[0] = clib_net_to_host_u32 (grek->key); + } + if (gre[1]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + { + gre_header_with_key_t *grek = (gre_header_with_key_t *) gre[1]; + gre_key[1] = clib_net_to_host_u32 (grek->key); } if (PREDICT_TRUE (cached_protocol == gre[0]->protocol)) @@ -215,11 +246,11 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, type[1] = ni[1].tunnel_type; b[0]->error = nidx[0] == SPARSE_VEC_INVALID_INDEX ? - node->errors[GRE_ERROR_UNKNOWN_PROTOCOL] : - node->errors[GRE_ERROR_NONE]; + node->errors[GRE_ERROR_UNKNOWN_PROTOCOL] : + node->errors[GRE_ERROR_NONE]; b[1]->error = nidx[1] == SPARSE_VEC_INVALID_INDEX ? - node->errors[GRE_ERROR_UNKNOWN_PROTOCOL] : - node->errors[GRE_ERROR_NONE]; + node->errors[GRE_ERROR_UNKNOWN_PROTOCOL] : + node->errors[GRE_ERROR_NONE]; version[0] = clib_net_to_host_u16 (gre[0]->flags_and_version); version[1] = clib_net_to_host_u16 (gre[1]->flags_and_version); @@ -241,10 +272,10 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, { gre_mk_key6 (&ip6[0]->dst_address, &ip6[0]->src_address, vnet_buffer (b[0])->ip.fib_index, type[0], - TUNNEL_MODE_P2P, 0, &key[0].gtk_v6); + TUNNEL_MODE_P2P, 0, gre_key[0], &key[0].gtk_v6); gre_mk_key6 (&ip6[1]->dst_address, &ip6[1]->src_address, vnet_buffer (b[1])->ip.fib_index, type[1], - TUNNEL_MODE_P2P, 0, &key[1].gtk_v6); + TUNNEL_MODE_P2P, 0, gre_key[1], &key[1].gtk_v6); matched[0] = gre_match_key6 (&cached_key.gtk_v6, &key[0].gtk_v6); matched[1] = gre_match_key6 (&cached_key.gtk_v6, &key[1].gtk_v6); } @@ -252,10 +283,10 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, { gre_mk_key4 (ip4[0]->dst_address, ip4[0]->src_address, vnet_buffer (b[0])->ip.fib_index, type[0], - TUNNEL_MODE_P2P, 0, &key[0].gtk_v4); + TUNNEL_MODE_P2P, 0, gre_key[0], &key[0].gtk_v4); gre_mk_key4 (ip4[1]->dst_address, ip4[1]->src_address, vnet_buffer (b[1])->ip.fib_index, type[1], - TUNNEL_MODE_P2P, 0, &key[1].gtk_v4); + TUNNEL_MODE_P2P, 0, gre_key[1], &key[1].gtk_v4); matched[0] = gre_match_key4 (&cached_key.gtk_v4, &key[0].gtk_v4); matched[1] = gre_match_key4 (&cached_key.gtk_v4, &key[1].gtk_v4); } @@ -328,14 +359,33 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, /* ip6_local hands us the ip header, not the gre header */ ip6[0] = vlib_buffer_get_current (b[0]); gre[0] = (void *) (ip6[0] + 1); - vlib_buffer_advance (b[0], sizeof (*ip6[0]) + sizeof (*gre[0])); + + /* Calculate total header size */ + u16 gre_hdr_size = sizeof (*gre[0]); + if (gre[0]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + gre_hdr_size += sizeof (gre_key_t); + /* Single buffer advance */ + vlib_buffer_advance (b[0], sizeof (*ip6[0]) + gre_hdr_size); } else { /* ip4_local hands us the ip header, not the gre header */ ip4[0] = vlib_buffer_get_current (b[0]); gre[0] = (void *) (ip4[0] + 1); - vlib_buffer_advance (b[0], sizeof (*ip4[0]) + sizeof (*gre[0])); + /* Calculate total header size */ + u16 gre_hdr_size = sizeof (*gre[0]); + if (gre[0]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + gre_hdr_size += sizeof (gre_key_t); + /* Single buffer advance */ + vlib_buffer_advance (b[0], sizeof (*ip4[0]) + gre_hdr_size); + } + + /* GRE key processing here */ + gre_key_t gre_key = 0; + if (gre[0]->flags_and_version & clib_host_to_net_u16 (GRE_FLAGS_KEY)) + { + gre_header_with_key_t *grek = (gre_header_with_key_t *) gre[0]; + gre_key = clib_net_to_host_u32 (grek->key); } if (PREDICT_TRUE (cached_protocol == gre[0]->protocol)) @@ -354,8 +404,8 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, type[0] = ni[0].tunnel_type; b[0]->error = nidx[0] == SPARSE_VEC_INVALID_INDEX ? - node->errors[GRE_ERROR_UNKNOWN_PROTOCOL] : - node->errors[GRE_ERROR_NONE]; + node->errors[GRE_ERROR_UNKNOWN_PROTOCOL] : + node->errors[GRE_ERROR_NONE]; version[0] = clib_net_to_host_u16 (gre[0]->flags_and_version); version[0] &= GRE_VERSION_MASK; @@ -370,14 +420,14 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, { gre_mk_key6 (&ip6[0]->dst_address, &ip6[0]->src_address, vnet_buffer (b[0])->ip.fib_index, type[0], - TUNNEL_MODE_P2P, 0, &key[0].gtk_v6); + TUNNEL_MODE_P2P, 0, gre_key, &key[0].gtk_v6); matched[0] = gre_match_key6 (&cached_key.gtk_v6, &key[0].gtk_v6); } else { gre_mk_key4 (ip4[0]->dst_address, ip4[0]->src_address, vnet_buffer (b[0])->ip.fib_index, type[0], - TUNNEL_MODE_P2P, 0, &key[0].gtk_v4); + TUNNEL_MODE_P2P, 0, gre_key, &key[0].gtk_v4); matched[0] = gre_match_key4 (&cached_key.gtk_v4, &key[0].gtk_v4); } @@ -435,46 +485,46 @@ static char *gre_error_strings[] = { }; VLIB_REGISTER_NODE (gre4_input_node) = { - .name = "gre4-input", - /* Takes a vector of packets. */ - .vector_size = sizeof (u32), + .name = "gre4-input", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), - .n_errors = GRE_N_ERROR, - .error_strings = gre_error_strings, + .n_errors = GRE_N_ERROR, + .error_strings = gre_error_strings, - .n_next_nodes = GRE_INPUT_N_NEXT, - .next_nodes = { + .n_next_nodes = GRE_INPUT_N_NEXT, + .next_nodes = { #define _(s, n) [GRE_INPUT_NEXT_##s] = n, - foreach_gre_input_next + foreach_gre_input_next #undef _ - }, + }, - .format_buffer = format_gre_header_with_length, - .format_trace = format_gre_rx_trace, - .unformat_buffer = unformat_gre_header, -}; + .format_buffer = format_gre_header_with_length, + .format_trace = format_gre_rx_trace, + .unformat_buffer = unformat_gre_header, + }; VLIB_REGISTER_NODE (gre6_input_node) = { - .name = "gre6-input", - /* Takes a vector of packets. */ - .vector_size = sizeof (u32), + .name = "gre6-input", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), - .runtime_data_bytes = sizeof (gre_input_runtime_t), + .runtime_data_bytes = sizeof (gre_input_runtime_t), - .n_errors = GRE_N_ERROR, - .error_strings = gre_error_strings, + .n_errors = GRE_N_ERROR, + .error_strings = gre_error_strings, - .n_next_nodes = GRE_INPUT_N_NEXT, - .next_nodes = { + .n_next_nodes = GRE_INPUT_N_NEXT, + .next_nodes = { #define _(s, n) [GRE_INPUT_NEXT_##s] = n, - foreach_gre_input_next + foreach_gre_input_next #undef _ - }, + }, - .format_buffer = format_gre_header_with_length, - .format_trace = format_gre_rx_trace, - .unformat_buffer = unformat_gre_header, -}; + .format_buffer = format_gre_header_with_length, + .format_trace = format_gre_rx_trace, + .unformat_buffer = unformat_gre_header, + }; #ifndef CLIB_MARCH_VARIANT void diff --git a/src/vnet/gre/packet.h b/src/vnet/gre/packet.h index bbda2df3f6..e80a36a8db 100644 --- a/src/vnet/gre/packet.h +++ b/src/vnet/gre/packet.h @@ -18,18 +18,18 @@ * limitations under the License. */ -#define foreach_gre_protocol \ -_ (0x0800, ip4) \ -_ (0x86DD, ip6) \ -_ (0x6558, teb) \ -_ (0x0806, arp) \ -_ (0x8847, mpls_unicast) \ -_ (0x88BE, erspan) \ -_ (0x894F, nsh) +#define foreach_gre_protocol \ + _ (0x0800, ip4) \ + _ (0x86DD, ip6) \ + _ (0x6558, teb) \ + _ (0x0806, arp) \ + _ (0x8847, mpls_unicast) \ + _ (0x88BE, erspan) \ + _ (0x894F, nsh) typedef enum { -#define _(n,f) GRE_PROTOCOL_##f = n, +#define _(n, f) GRE_PROTOCOL_##f = n, foreach_gre_protocol #undef _ } gre_protocol_t; @@ -42,26 +42,33 @@ typedef struct #define GRE_FLAGS_CHECKSUM (1 << 15) /* deprecated, according to rfc2784 */ -#define GRE_FLAGS_ROUTING (1 << 14) -#define GRE_FLAGS_KEY (1 << 13) -#define GRE_FLAGS_SEQUENCE (1 << 12) +#define GRE_FLAGS_ROUTING (1 << 14) +#define GRE_FLAGS_KEY (1 << 13) +#define GRE_FLAGS_SEQUENCE (1 << 12) #define GRE_FLAGS_STRICT_SOURCE_ROUTE (1 << 11) /* version 1 is PPTP which we don't support */ #define GRE_SUPPORTED_VERSION 0 -#define GRE_VERSION_MASK 0x7 +#define GRE_VERSION_MASK 0x7 /* 0x800 for ip4, etc. */ u16 protocol; } gre_header_t; +typedef struct +{ + u16 flags_and_version; + u16 protocol; + u32 key; +} gre_header_with_key_t; + /* From draft-foschiano-erspan-03.txt Different frame variants known as "ERSPAN Types" can be distinguished based on the GRE "Protocol Type" field value: Type I and II's value is 0x88BE while Type III's is 0x22EB [ETYPES]. - GRE header for ERSPAN Type II encapsulation (8 octets [34:41]) + GRE header for ERSPAN Type II encapsulation (8 octets [34:41]) 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -80,7 +87,7 @@ typedef struct The ERSPAN Type II feature header is described below: - ERSPAN Type II header (8 octets [42:49]) + ERSPAN Type II header (8 octets [42:49]) 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -92,50 +99,50 @@ typedef struct The various fields of the above header are described in this table: Field Position Length Definition - [octet:bit] (bits) + [octet:bit] (bits) Ver [42:0] 4 ERSPAN Encapsulation version. - This indicates the version of - the ERSPAN encapsulation - specification. Set to 0x1 for - Type II. + This indicates the version of + the ERSPAN encapsulation + specification. Set to 0x1 for + Type II. VLAN [42:4] 12 Original VLAN of the frame, - mirrored from the source. - If the En field is set to 11, - the value of VLAN is undefined. + mirrored from the source. + If the En field is set to 11, + the value of VLAN is undefined. COS [44:0] 3 Original class of service of the - frame, mirrored from the source. + frame, mirrored from the source. En [44:3] 2 The trunk encapsulation type - associated with the ERSPAN source - port for ingress ERSPAN traffic. + associated with the ERSPAN source + port for ingress ERSPAN traffic. - The possible values are: - 00-originally without VLAN tag - 01-originally ISL encapsulated - 10-originally 802.1Q encapsulated - 11-VLAN tag preserved in frame. + The possible values are: + 00-originally without VLAN tag + 01-originally ISL encapsulated + 10-originally 802.1Q encapsulated + 11-VLAN tag preserved in frame. T [44:5] 1 This bit indicates that the frame - copy encapsulated in the ERSPAN - packet has been truncated. This - occurs if the ERSPAN encapsulated - frame exceeds the configured MTU. + copy encapsulated in the ERSPAN + packet has been truncated. This + occurs if the ERSPAN encapsulated + frame exceeds the configured MTU. Session ID [44:6] 10 Identification associated with (ERSPAN ID) each ERSPAN session. Must be - unique between the source and the - receiver(s). (See section below.) + unique between the source and the + receiver(s). (See section below.) Reserved [46:0] 12 All bits are set to zero Index [47:4] 20 A 20 bit index/port number - associated with the ERSPAN - traffic's port and - direction (ingress/egress). N.B.: - This field is platform dependent. + associated with the ERSPAN + traffic's port and + direction (ingress/egress). N.B.: + This field is platform dependent. */ typedef CLIB_PACKED (struct { @@ -157,7 +164,6 @@ typedef CLIB_PACKED (struct { erspan_t2_t erspan; }) erspan_t2_header_t; - /* u64 template for ERSPAN type 2 header with both EN bits set */ #define ERSPAN_HDR2 0x1000180000000000ul diff --git a/test/test_gre.py b/test/test_gre.py index 8b2851baea..51a15b02fd 100644 --- a/test/test_gre.py +++ b/test/test_gre.py @@ -168,6 +168,25 @@ def create_tunnel_stream_4o4(self, src_if, tunnel_src, tunnel_dst, src_ip, dst_i pkts.append(p) return pkts + def create_tunnel_stream_4o4_with_key( + self, src_if, tunnel_src, tunnel_dst, src_ip, dst_ip, key + ): + pkts = [] + for i in range(0, 257): + info = self.create_packet_info(src_if, src_if) + payload = self.info_to_payload(info) + p = ( + Ether(dst=src_if.local_mac, src=src_if.remote_mac) + / IP(src=tunnel_src, dst=tunnel_dst) + / GRE(key=key) + / IP(src=src_ip, dst=dst_ip) + / UDP(sport=1234, dport=1234) + / Raw(payload) + ) + info.data = p.copy() + pkts.append(p) + return pkts + def create_tunnel_stream_6o4(self, src_if, tunnel_src, tunnel_dst, src_ip, dst_ip): pkts = [] for i in range(0, 257): @@ -271,6 +290,60 @@ def verify_tunneled_4o4( self.logger.error(ppp("Tx:", tx)) raise + def verify_tunneled_4o4_with_key( + self, src_if, capture, sent, tunnel_src, tunnel_dst, key, dscp=0, ecn=0 + ): + """ + Verify GRE encapsulation with key field: + - Basic GRE tunnel checks (source, destination, etc.) + - Verify GRE flags indicate key field is present (0x2000) + - Verify the GRE key value matches what we configured + """ + self.assertEqual(len(capture), len(sent)) + tos = (dscp << 2) | ecn + + for tx, rx in zip(sent, capture): + try: + + tx_ip = tx[IP] + rx_ip = rx[IP] + + # Verify tunnel outer header + self.assertEqual(rx_ip.src, tunnel_src) + self.assertEqual(rx_ip.dst, tunnel_dst) + self.assertEqual(rx_ip.tos, tos) + self.assertEqual(rx_ip.len, len(rx_ip)) + + # Verify GRE header + rx_gre = rx[GRE] + + # Check if GRE flags has the KEY bit set (0x2000) + # In Scapy GRE, this should be reflected in the 'flags' field or through presence of 'key' field + self.assertTrue( + hasattr(rx_gre, "key"), "GRE key field not present in packet" + ) + + # Verify the key value is correct + self.assertEqual( + rx_gre.key, + key, + f"GRE key value mismatch: expected {key}, got {rx_gre.key}", + ) + self.logger.info(f"Verified GRE key: {rx_gre.key}") + + # Verify inner packet data + rx_inner_ip = rx_gre[IP] + self.assertEqual(rx_inner_ip.src, tx_ip.src) + self.assertEqual(rx_inner_ip.dst, tx_ip.dst) + + # IP processing post pop has decremented the TTL + self.assertEqual(rx_inner_ip.ttl + 1, tx_ip.ttl) + + except: + self.logger.error(ppp("Rx:", rx)) + self.logger.error(ppp("Tx:", tx)) + raise + def verify_tunneled_6o6( self, src_if, capture, sent, tunnel_src, tunnel_dst, dscp=0, ecn=0 ): @@ -718,6 +791,55 @@ def test_gre(self): self.pg0.unconfig_ip6() + def test_gre_with_key(self): + """GRE IPv4 tunnel with Key Tests""" + + # Use a key value for the GRE tunnel + key_value = 123 + + # Create a GRE interface with key support + gre_if = VppGreInterface(self, self.pg0.local_ip4, "1.1.1.2", gre_key=key_value) + gre_if.add_vpp_config() + + # Configure the interface using VPP methods + gre_if.admin_up() + gre_if.config_ip4() + + # Create route via tunnel + route_via_tun = VppIpRoute( + self, "4.4.4.4", 32, [VppRoutePath("0.0.0.0", gre_if.sw_if_index)] + ) + route_via_tun.add_vpp_config() + + # Add a route that resolves the tunnel's destination + route_tun_dst = VppIpRoute( + self, + "1.1.1.2", + 32, + [VppRoutePath(self.pg0.remote_ip4, self.pg0.sw_if_index)], + ) + route_tun_dst.add_vpp_config() + + # Send packets that should be routed via the tunnel + tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4") + rx = self.send_and_expect(self.pg0, tx, self.pg0) + + # Verify GRE encapsulation with key using the verification function + self.logger.info(f"Verifying GRE encapsulation with key {key_value}") + self.verify_tunneled_4o4_with_key( + self.pg0, # source interface + rx, # received packets + tx, # sent packets + self.pg0.local_ip4, # tunnel source + "1.1.1.2", # tunnel destination + key_value, # GRE key value + ) + + # Cleanup + route_tun_dst.remove_vpp_config() + route_via_tun.remove_vpp_config() + gre_if.remove_vpp_config() + def test_gre6(self): """GRE IPv6 tunnel Tests""" diff --git a/test/vpp_gre_interface.py b/test/vpp_gre_interface.py index a40e8531a6..0249ae46a1 100644 --- a/test/vpp_gre_interface.py +++ b/test/vpp_gre_interface.py @@ -17,6 +17,7 @@ def __init__( mode=None, flags=0, session=0, + gre_key=0, ): """Create VPP GRE interface""" super(VppGreInterface, self).__init__(test) @@ -24,6 +25,7 @@ def __init__( self.t_dst = dst_ip self.t_outer_table = outer_table_id self.t_session = session + self.t_gre_key = gre_key # Added GRE key field self.t_flags = flags self.t_type = type if not self.t_type: @@ -33,19 +35,38 @@ def __init__( self.t_mode = VppEnum.vl_api_tunnel_mode_t.TUNNEL_API_MODE_P2P def add_vpp_config(self): - r = self.test.vapi.gre_tunnel_add_del( - is_add=1, - tunnel={ - "src": self.t_src, - "dst": self.t_dst, - "outer_table_id": self.t_outer_table, - "instance": 0xFFFFFFFF, - "type": self.t_type, - "mode": self.t_mode, - "flags": self.t_flags, - "session_id": self.t_session, - }, - ) + # If we need a key, use the v2 API + if self.t_gre_key != 0: + r = self.test.vapi.gre_tunnel_add_del_v2( + is_add=1, + tunnel={ + "src": self.t_src, + "dst": self.t_dst, + "outer_table_id": self.t_outer_table, + "instance": 0xFFFFFFFF, + "type": self.t_type, + "mode": self.t_mode, + "flags": self.t_flags, + "session_id": self.t_session, + "key": self.t_gre_key, + }, + ) + else: + # Use regular v1 API for tunnels without key + r = self.test.vapi.gre_tunnel_add_del( + is_add=1, + tunnel={ + "src": self.t_src, + "dst": self.t_dst, + "outer_table_id": self.t_outer_table, + "instance": 0xFFFFFFFF, + "type": self.t_type, + "mode": self.t_mode, + "flags": self.t_flags, + "session_id": self.t_session, + }, + ) + self.set_sw_if_index(r.sw_if_index) self.generate_remote_hosts() self.test.registry.register(self, self.test.logger) @@ -53,25 +74,75 @@ def add_vpp_config(self): def remove_vpp_config(self): self.unconfig() - self.test.vapi.gre_tunnel_add_del( - is_add=0, - tunnel={ - "src": self.t_src, - "dst": self.t_dst, - "outer_table_id": self.t_outer_table, - "instance": 0xFFFFFFFF, - "type": self.t_type, - "mode": self.t_mode, - "flags": self.t_flags, - "session_id": self.t_session, - }, - ) + + # Use appropriate API based on whether tunnel has a key + if self.t_gre_key != 0: + # Use v2 API for tunnels with keys + self.test.vapi.gre_tunnel_add_del_v2( + is_add=0, + tunnel={ + "src": self.t_src, + "dst": self.t_dst, + "outer_table_id": self.t_outer_table, + "instance": 0xFFFFFFFF, + "type": self.t_type, + "mode": self.t_mode, + "flags": self.t_flags, + "session_id": self.t_session, + "key": self.t_gre_key, + }, + ) + else: + # Use v1 API for tunnels without keys + self.test.vapi.gre_tunnel_add_del( + is_add=0, + tunnel={ + "src": self.t_src, + "dst": self.t_dst, + "outer_table_id": self.t_outer_table, + "instance": 0xFFFFFFFF, + "type": self.t_type, + "mode": self.t_mode, + "flags": self.t_flags, + "session_id": self.t_session, + }, + ) def object_id(self): return "gre-%d" % self.sw_if_index def query_vpp_config(self): - return self.test.vapi.gre_tunnel_dump(sw_if_index=self._sw_if_index) + try: + # Use appropriate API based on whether tunnel has a key + if hasattr(self, "t_gre_key") and self.t_gre_key != 0: + dump = self.test.vapi.gre_tunnel_dump_v2(sw_if_index=self.sw_if_index) + else: + dump = self.test.vapi.gre_tunnel_dump(sw_if_index=self.sw_if_index) + + # Validate dump data matches this tunnel's configuration + for entry in dump: + # Skip non-tunnel entries (like int values) + if not hasattr(entry, "sw_if_index"): + continue + + # Compare tunnel parameters + key_match = True + if hasattr(self, "t_gre_key") and self.t_gre_key != 0: + # For tunnels with keys, also validate the key value + key_match = hasattr(entry, "key") and entry.key == self.t_gre_key + + if ( + entry.sw_if_index == self.sw_if_index + and str(entry.src) == str(self.t_src) + and str(entry.dst) == str(self.t_dst) + and entry.type == self.t_type + and key_match + ): + return True + + return False + except Exception: + return False @property def remote_ip(self): From a834089f57b0df2804b7aa9e06b8cfb0e800412c Mon Sep 17 00:00:00 2001 From: Brian Morris Date: Tue, 24 Jun 2025 20:55:29 +0000 Subject: [PATCH 094/313] tls: check error when SSL_shutdown fails this pulls the error from the per-thread error queue, which if not empty could cause the wrong error to be returned elsewhere Type: fix Change-Id: Ie8741f32de61ef1f469e694ac27ee937a45f5b01 Signed-off-by: Brian Morris --- src/plugins/tlsopenssl/tls_openssl.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index a5b6b062c8..651053b33c 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -74,7 +74,11 @@ openssl_ctx_free (tls_ctx_t * ctx) { if (SSL_is_init_finished (oc->ssl) && !(ctx->flags & TLS_CONN_F_PASSIVE_CLOSE)) - SSL_shutdown (oc->ssl); + { + int rv = SSL_shutdown (oc->ssl); + if (rv < 0) + SSL_get_error (oc->ssl, rv); + } if (openssl_main.async) tls_async_evts_free_list (ctx); @@ -187,6 +191,8 @@ openssl_read_from_ssl_into_fifo (svm_fifo_t *f, tls_ctx_t *ctx, u32 max_len) read = SSL_read (ssl, fs[0].data, fs[0].len); if (read <= 0) { + ossl_check_err_is_fatal (ssl, read); + if (openssl_main.async && SSL_want_async (oc->ssl)) { session_t *tls_session = @@ -195,7 +201,6 @@ openssl_read_from_ssl_into_fifo (svm_fifo_t *f, tls_ctx_t *ctx, u32 max_len) tls_session, SSL_ASYNC_EVT_RD, NULL, 0); return 0; } - ossl_check_err_is_fatal (ssl, read); return 0; } @@ -421,7 +426,9 @@ void openssl_confirm_app_close (tls_ctx_t *ctx) { openssl_ctx_t *oc = (openssl_ctx_t *) ctx; - SSL_shutdown (oc->ssl); + int rv = SSL_shutdown (oc->ssl); + if (rv < 0) + SSL_get_error (oc->ssl, rv); if (ctx->flags & TLS_CONN_F_SHUTDOWN_TRANSPORT) tls_shutdown_transport (ctx); else From 2f8bc0f258f66b4cb60ba2af2f045febacaf496e Mon Sep 17 00:00:00 2001 From: Mohammed Hawari Date: Tue, 24 Jun 2025 18:20:25 +0200 Subject: [PATCH 095/313] vppinfra: introduce bihash_56_8 Change-Id: I4d3cb6194c02ae53ead8f5ede35c3204d336a25f Type: improvement Signed-off-by: Mohammed Hawari --- src/vppinfra/CMakeLists.txt | 1 + src/vppinfra/bihash_56_8.h | 117 ++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 src/vppinfra/bihash_56_8.h diff --git a/src/vppinfra/CMakeLists.txt b/src/vppinfra/CMakeLists.txt index d52ecb096d..131f5a5b53 100644 --- a/src/vppinfra/CMakeLists.txt +++ b/src/vppinfra/CMakeLists.txt @@ -128,6 +128,7 @@ set(VPPINFRA_HEADERS bihash_32_8.h bihash_40_8.h bihash_48_8.h + bihash_56_8.h bihash_8_8.h bihash_8_16.h bihash_24_16.h diff --git a/src/vppinfra/bihash_56_8.h b/src/vppinfra/bihash_56_8.h new file mode 100644 index 0000000000..3943a5aa19 --- /dev/null +++ b/src/vppinfra/bihash_56_8.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed 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. + */ + +#undef BIHASH_TYPE +#undef BIHASH_KVP_PER_PAGE +#undef BIHASH_32_64_SVM +#undef BIHASH_ENABLE_STATS +#undef BIHASH_KVP_AT_BUCKET_LEVEL +#undef BIHASH_LAZY_INSTANTIATE +#undef BIHASH_BUCKET_PREFETCH_CACHE_LINES + +#define BIHASH_TYPE _56_8 +#define BIHASH_KVP_PER_PAGE 4 +#define BIHASH_KVP_AT_BUCKET_LEVEL 0 +#define BIHASH_LAZY_INSTANTIATE 1 +#define BIHASH_BUCKET_PREFETCH_CACHE_LINES 1 + +#ifndef __included_bihash_56_8_h__ +#define __included_bihash_56_8_h__ + +#include +#include +#include +#include +#include + +typedef struct +{ + u64 key[7]; + u64 value; +} clib_bihash_kv_56_8_t; + +static inline void +clib_bihash_mark_free_56_8 (clib_bihash_kv_56_8_t *v) +{ + v->value = 0xFEEDFACE8BADF00DULL; +} + +static inline int +clib_bihash_is_free_56_8 (const clib_bihash_kv_56_8_t *v) +{ + if (v->value == 0xFEEDFACE8BADF00DULL) + return 1; + return 0; +} + +static inline u64 +clib_bihash_hash_56_8 (const clib_bihash_kv_56_8_t *v) +{ +#ifdef clib_crc32c_uses_intrinsics + return clib_crc32c ((u8 *) v->key, 56); +#else + u64 tmp = v->key[0] ^ v->key[1] ^ v->key[2] ^ v->key[3] ^ v->key[4] ^ + v->key[5] ^ v->key[6]; + return clib_xxhash (tmp); +#endif +} + +static inline u8 * +format_bihash_kvp_56_8 (u8 *s, va_list *args) +{ + clib_bihash_kv_56_8_t *v = va_arg (*args, clib_bihash_kv_56_8_t *); + + s = format (s, "key %llu %llu %llu %llu %llu %llu %llu value %llu", + v->key[0], v->key[1], v->key[2], v->key[3], v->key[4], v->key[5], + v->key[6], v->value); + return s; +} + +static inline int +clib_bihash_key_compare_56_8 (u64 *a, u64 *b) +{ +#if defined(CLIB_HAVE_VEC512) + return u64x8_is_equal (u64x8_mask_load_zero (a, 0x7f), + u64x8_mask_load_zero (b, 0x7f)); +#elif defined(CLIB_HAVE_VEC256) && defined(CLIB_HAVE_VEC256_MASK_LOAD_STORE) + u64x4 v = { 0 }; + v = u64x4_mask_load_zero (a + 4, 0x7) ^ u64x4_mask_load_zero (b + 4, 0x7); + v |= u64x4_load_unaligned (a) ^ u64x4_load_unaligned (b); + return u64x4_is_all_zero (v); +#elif defined(CLIB_HAVE_VEC128) && \ + defined(CLIB_HAVE_VEC128_UNALIGNED_LOAD_STORE) + u64x2 v = { 0, a[6] ^ b[6] }; + v |= u64x2_load_unaligned (a) ^ u64x2_load_unaligned (b); + v |= u64x2_load_unaligned (a + 2) ^ u64x2_load_unaligned (b + 2); + v |= u64x2_load_unaligned (a + 4) ^ u64x2_load_unaligned (b + 4); + return u64x2_is_all_zero (v); +#else + return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2]) | (a[3] ^ b[3]) | + (a[4] ^ b[4]) | (a[5] ^ b[5]) | (a[6] ^ b[6])) == 0; +#endif +} + +#undef __included_bihash_template_h__ +#include + +#endif /* __included_bihash_56_8_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ From 5a5b908f7b026252cdb317ee2867681b2c18e2ac Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 25 Jun 2025 17:38:45 +0000 Subject: [PATCH 096/313] http: test code coverage improvement Type: test Change-Id: Iae1487d74db77c438b2fd531f4a675a5994f91af Signed-off-by: Matus Fabian --- extras/hs-test/http_test.go | 18 +++++++- src/plugins/http/test/http_test.c | 70 +++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go index 1caefe51f7..0ed8b298de 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http_test.go @@ -1499,9 +1499,17 @@ func HttpInvalidRequestLineTest(s *NoTopoSuite) { s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP/x' invalid http version not allowed") + resp, err = TcpSendReceive(serverAddress, "get / HTTP/1.1\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "method must be uppercase") + resp, err = TcpSendReceive(serverAddress, "GET / HTTP1.1\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'HTTP1.1' invalid http version not allowed") + + resp, err = TcpSendReceive(serverAddress, "/\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") } func HttpTimerSessionDisable(s *NoTopoSuite) { @@ -1565,7 +1573,7 @@ func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "after '%' there must be two hex-digit characters in target path") - resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose>true HTTP/1.1\r\nHost: example.com\r\n\r\n") + resp, err = TcpSendReceive(serverAddress, "GET /version.json?verbose?>true HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "'>' not allowed in target query") @@ -1590,6 +1598,10 @@ func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { resp, err = TcpSendReceive(serverAddress, "CONNECT https://www.example.com/tunnel HTTP/1.1\r\nHost: example.com\r\n\r\n") s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "CONNECT requests must use authority-form only") + + resp, err = TcpSendReceive(serverAddress, "GET index HTTP/1.1\r\nHost: example.com\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request") } func HttpInvalidContentLengthTest(s *NoTopoSuite) { @@ -1609,6 +1621,10 @@ func HttpInvalidContentLengthTest(s *NoTopoSuite) { s.AssertNil(err, fmt.Sprint(err)) s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value other than digit not allowed") + + resp, err = TcpSendReceive(serverAddress, "GET /show/version HTTP/1.1\r\nContent-Length: 111111111111111111111111111111111111111111111111\r\n\r\n") + s.AssertNil(err, fmt.Sprint(err)) + s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value exceeded U64_MAX") } func HttpContentLengthTest(s *NoTopoSuite) { diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index fbeee4b6b0..0d2e3a7b49 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -81,6 +81,17 @@ http_test_parse_authority (vlib_main_t *vm) vec_free (authority); vec_free (formated); + authority = format (0, "[DEAD:BEEF::1234]:443"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_IP6), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_IP6); + HTTP_TEST ((parsed.ip.as_u8[0] == 0xDE && parsed.ip.as_u8[1] == 0xAD && + parsed.ip.as_u8[2] == 0xBE && parsed.ip.as_u8[3] == 0xEF), + "not parsed correctly"); + vec_free (authority); + /* registered name */ authority = format (0, "example.com:80"); rv = http_parse_authority (authority, vec_len (authority), &parsed); @@ -152,55 +163,84 @@ http_test_parse_authority (vlib_main_t *vm) vec_free (authority); vec_free (formated); + authority = format (0, "1e.com"); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == 0), "'%v' should be valid", authority); + HTTP_TEST ((parsed.host_type == HTTP_URI_HOST_TYPE_REG_NAME), + "host_type=%d should be %d", parsed.host_type, + HTTP_URI_HOST_TYPE_REG_NAME); + HTTP_TEST ((parsed.port == 0), "port=%u should be 0", parsed.port); + formated = http_serialize_authority (&parsed); + rv = vec_cmp (authority, formated); + HTTP_TEST ((rv == 0), "'%v' should match '%v'", authority, formated); + vec_free (authority); + vec_free (formated); + /* invalid port */ authority = format (0, "example.com:80000000"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* no port after colon */ authority = format (0, "example.com:"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid character in registered name */ authority = format (0, "bad#example.com"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid IPv6 address not terminated with ']' */ authority = format (0, "[dead:beef::1234"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* empty IPv6 address */ authority = format (0, "[]"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid IPv6 address too few hex quads */ authority = format (0, "[dead:beef]:80"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid IPv6 address more than one :: */ authority = format (0, "[dead::beef::1]:80"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid IPv6 address too much hex quads */ authority = format (0, "[d:e:a:d:b:e:e:f:1:2]:80"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid character in IPv6 address */ authority = format (0, "[xyz0::1234]:443"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); /* invalid IPv6 address */ authority = format (0, "[deadbeef::1234"); rv = http_parse_authority (authority, vec_len (authority), &parsed); HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); + + /* registered name too long */ + vec_validate_init_empty (authority, 257, 'a'); + rv = http_parse_authority (authority, vec_len (authority), &parsed); + HTTP_TEST ((rv == -1), "'%v' should be invalid", authority); + vec_free (authority); return 0; } @@ -336,6 +376,17 @@ http_test_udp_payload_datagram (vlib_main_t *vm) HTTP_TEST ((payload_len == 39), "payload_len=%llu should be 39", payload_len); + /* Type = 0x00, Len = 37, Context ID = 0x01 */ + u8 nonzero_context_id[] = { 0x00, 0x25, 0x01, 0x12, 0x34, 0x56, 0x78 }; + rv = http_decap_udp_payload_datagram (nonzero_context_id, + sizeof (nonzero_context_id), + &payload_offset, &payload_len); + HTTP_TEST ((rv == 1), "'%U' should be skipped (context id is not zero)", + format_hex_bytes, nonzero_context_id, + sizeof (nonzero_context_id)); + HTTP_TEST ((payload_len == 39), "payload_len=%llu should be 39", + payload_len); + u8 *buffer = 0, *ret; vec_validate (buffer, HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD + 2); ret = http_encap_udp_payload_datagram (buffer, 15292); @@ -489,8 +540,12 @@ http_test_http_header_table (vlib_main_t *vm) HTTP_TEST ((value != 0), "'%s' is in headers", http_header_name_str (HTTP_HEADER_CONTENT_ENCODING)); rv = http_token_is (value->base, value->len, http_token_lit ("GZIP")); - HTTP_TEST ((rv = 1), "header value '%U' should be 'GZIP'", format_http_bytes, - value->base, value->len); + HTTP_TEST ((rv == 1), "header value '%U' should be 'GZIP'", + format_http_bytes, value->base, value->len); + rv = + http_token_contains (value->base, value->len, http_token_lit ("deflate")); + HTTP_TEST ((rv == 0), "header value '%U' doesn't contain 'deflate'", + format_http_bytes, value->base, value->len); value = http_get_header (&ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); @@ -515,8 +570,17 @@ http_test_http_header_table (vlib_main_t *vm) /* repeated header */ value = http_get_header (&ht, http_token_lit ("sandwich")); HTTP_TEST ((value != 0), "'sandwich' is in headers"); + rv = http_token_is (value->base, value->len, http_token_lit ("Spam")); + HTTP_TEST ((rv == 0), "header value '%U' should be 'Eggs, Spam'", + format_http_bytes, value->base, value->len); rv = http_token_is (value->base, value->len, http_token_lit ("Eggs, Spam")); - HTTP_TEST ((rv = 1), "header value '%U' should be 'Eggs, Spam'", + HTTP_TEST ((rv == 1), "header value '%U' should be 'Eggs, Spam'", + format_http_bytes, value->base, value->len); + rv = http_token_contains (value->base, value->len, http_token_lit ("Spam")); + HTTP_TEST ((rv == 1), "header value '%U' contains 'Spam'", format_http_bytes, + value->base, value->len); + rv = http_token_contains (value->base, value->len, http_token_lit ("spam")); + HTTP_TEST ((rv == 0), "header value '%U' doesn't contain 'spam'", format_http_bytes, value->base, value->len); value = http_get_header (&ht, http_token_lit ("Jade")); From f3f9100fe4184bb37d44eb82ec6f027f3482f95f Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 25 Jun 2025 09:53:23 +0000 Subject: [PATCH 097/313] hsa: http client formatting of headers improvement Do not print response headers as raw bytes recieved from http transport but use format_http_header_table which will work nice with http/2 too. Type: improvement Change-Id: I48b75c024e361879b611588fc3345ef343cb4a1b Signed-off-by: Matus Fabian --- src/plugins/hs_apps/http_client.c | 38 +++++++++++++++---------------- src/plugins/http/http.h | 21 +++++++++++++++++ 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 0137f8895a..ddb92b3a3a 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -41,7 +41,7 @@ typedef struct hc_stats_t stats; u64 data_offset; u64 body_recv; - u8 *resp_headers; + http_header_table_t resp_headers; u8 *http_response; u8 *response_status; FILE *file_ptr; @@ -424,6 +424,7 @@ hc_rx_callback (session_t *s) if (hc_session->to_recv == 0) { + http_reset_header_table (&hc_session->resp_headers); rv = svm_fifo_dequeue (s->rx_fifo, sizeof (msg), (u8 *) &msg); ASSERT (rv == sizeof (msg)); @@ -437,29 +438,27 @@ hc_rx_callback (session_t *s) if (msg.data.headers_len) { + http_init_header_table_buf (&hc_session->resp_headers, msg); if (!hcm->repeat) hc_session->response_status = format (0, "%U", format_http_status_code, msg.code); - http_header_table_t ht = HTTP_HEADER_TABLE_NULL; - svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset); - vec_validate (hc_session->resp_headers, msg.data.headers_len - 1); - vec_set_len (hc_session->resp_headers, msg.data.headers_len); rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len, - hc_session->resp_headers); - ht.buf = hc_session->resp_headers; - + hc_session->resp_headers.buf); ASSERT (rv == msg.data.headers_len); - HTTP_DBG (1, (char *) format (0, "%v", hc_session->resp_headers)); + HTTP_DBG (1, + (char *) format (0, "%U", format_hash, + hc_session->resp_headers.value_by_name)); msg.data.body_offset -= msg.data.headers_len + msg.data.headers_offset; - http_build_header_table (&ht, msg); + http_build_header_table (&hc_session->resp_headers, msg); const http_token_t *content_type = http_get_header ( - &ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); + &hc_session->resp_headers, + http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); if (content_type) { for (u8 i = 0; i < sizeof (mime_printable) / @@ -477,8 +476,6 @@ hc_rx_callback (session_t *s) } } } - ht.buf = NULL; - http_free_header_table (&ht); } if (msg.data.body_len == 0) @@ -797,18 +794,19 @@ hc_get_event (vlib_main_t *vm) { wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); - vlib_cli_output ( - vm, "< %v\n< %v\n* %u bytes saved to file (/tmp/%s)", - hc_session->response_status, hc_session->resp_headers, - hc_session->body_recv, hcm->filename); + vlib_cli_output (vm, "< %v\n%U\n* %u bytes saved to file (/tmp/%s)", + hc_session->response_status, + format_http_header_table, &hc_session->resp_headers, + "< ", hc_session->body_recv, hcm->filename); fclose (hc_session->file_ptr); } else if (hcm->verbose) { wrk = hc_worker_get (hcm->worker_index); hc_session = hc_session_get (wrk->session_index, wrk->thread_index); - vlib_cli_output (vm, "< %v\n< %v\n%v", hc_session->response_status, - hc_session->resp_headers); + vlib_cli_output (vm, "< %v\n%U\n", hc_session->response_status, + format_http_header_table, &hc_session->resp_headers, + "< "); /* if the body was read in chunks and not saved to file - that means we've hit the response body size limit */ if (hc_session->session_flags & HC_S_FLAG_CHUNKED_BODY) @@ -897,7 +895,7 @@ hc_worker_cleanup (hc_worker_t *wrk) vec_free (wrk->headers_buf); vec_foreach (hc_session, wrk->sessions) { - vec_free (hc_session->resp_headers); + http_free_header_table (&hc_session->resp_headers); vec_free (hc_session->http_response); vec_free (hc_session->response_status); } diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 0e028a7760..95b2faf062 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -899,6 +899,27 @@ http_get_header (http_header_table_t *header_table, const char *name, return 0; } +/** + * Print all headers in http header table. + * + * @note arg0 - http_header_table_t *ht, arg1 - char *line_prefix + */ +always_inline u8 * +format_http_header_table (u8 *s, va_list *va) +{ + http_header_table_t *ht = va_arg (*va, http_header_table_t *); + char *prefix = va_arg (*va, char *); + void *v = (void *) ht->value_by_name; + hash_t *h = hash_header (v); + hash_pair_t *p; + + hash_foreach_pair (p, v, { + s = format (s, "%s%U\n", prefix, h->format_pair, h->format_pair_arg, v, p); + }); + + return s; +} + typedef struct { u32 len; /**< length of the header data buffer */ From d2a2473a6accc7c30eb9fb2f9ae5b0979d638d5b Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Tue, 24 Jun 2025 12:19:04 +0000 Subject: [PATCH 098/313] hsa: fix echo-bytes echo client runs over UDP Previously counting received bytes didn't take into account the datagram header size, leading to a bigger than expected size and an overflow of es->bytes_to_receive. Type: fix Change-Id: If31d4ed5f989645388dbf9c1b9829c6f2e4e8c62 Signed-off-by: Semir Sionek --- extras/hs-test/echo_test.go | 16 +++++++++++++++- src/plugins/hs_apps/echo_client.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index b753121f40..53f83542bc 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -8,7 +8,7 @@ import ( ) func init() { - RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest) + RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest) RegisterSoloVethTests(TcpWithLossTest) RegisterVeth6Tests(TcpWithLoss6Test) } @@ -57,6 +57,20 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { } } +func EchoBuiltinEchobytesTest(s *VethsSuite) { + serverVpp := s.Containers.ServerVpp.VppInstance + + serverVpp.Vppctl("test echo server " + + " uri udp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + + clientVpp := s.Containers.ClientVpp.VppInstance + + o := clientVpp.Vppctl("test echo client echo-bytes verbose uri" + + " udp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + s.Log(o) + s.AssertNotContains(o, "test echo clients: failed: timeout with 1 sessions") +} + func TcpWithLossTest(s *VethsSuite) { serverVpp := s.Containers.ServerVpp.VppInstance diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 54e806a9ba..843ce40a4d 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -190,6 +190,7 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) { ec_main_t *ecm = &ec_main; svm_fifo_t *rx_fifo = es->rx_fifo; + session_dgram_pre_hdr_t ph; int n_read, i; if (ecm->cfg.test_bytes) @@ -199,8 +200,23 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) } else { - n_read = svm_fifo_max_dequeue_cons (rx_fifo); - svm_fifo_dequeue_drop (rx_fifo, n_read); + if (!es->is_dgram) + { + n_read = svm_fifo_max_dequeue_cons (rx_fifo); + svm_fifo_dequeue_drop (rx_fifo, n_read); + } + else + { + n_read = svm_fifo_max_dequeue_cons (rx_fifo); + if (n_read <= sizeof (session_dgram_hdr_t)) + return; + svm_fifo_peek (rx_fifo, 0, sizeof (ph), (u8 *) &ph); + if (n_read < (ph.data_length + SESSION_CONN_HDR_LEN)) + return; + svm_fifo_dequeue_drop (rx_fifo, + ph.data_length + SESSION_CONN_HDR_LEN); + n_read = ph.data_length; + } } if (n_read > 0) @@ -233,7 +249,14 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) } } } - ASSERT (n_read <= es->bytes_to_receive); + if (n_read > es->bytes_to_receive) + { + ec_err ("expected %llu, received %llu bytes!", + es->bytes_received + es->bytes_to_receive, + es->bytes_received + n_read); + ecm->test_failed = 1; + es->bytes_to_receive = n_read; + } es->bytes_to_receive -= n_read; es->bytes_received += n_read; } From 77e80a460460fa9fec332d99a99b6c18ffb57ff0 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 25 Jun 2025 11:03:43 -0700 Subject: [PATCH 099/313] http: fix h2 settings leak Type: fix Change-Id: I680eb6bafb4b6218e60523e2abc2962f2f5cbefc Signed-off-by: Florin Coras --- src/plugins/http/http2/http2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 19a43314f3..5888733490 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -467,6 +467,7 @@ http2_send_server_preface (http_conn_t *hc) &response); http_io_ts_write (hc, response, vec_len (response), 0); http_io_ts_after_write (hc, 1); + vec_free (settings_list); } /***********************/ From 784410190eb96fa8dea9fa67a086aa0044fe9025 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 25 Jun 2025 09:23:13 -0700 Subject: [PATCH 100/313] hsa: fix echo client vm use for time Type: fix Change-Id: Iecabf8a8c6b0bd262be32ef144281a5f0c052381 Signed-off-by: Florin Coras --- src/plugins/hs_apps/echo_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 843ce40a4d..5211f00c99 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -317,7 +317,7 @@ ec_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) ecm->repeats = 0; } - time_now = vlib_time_now (ecm->vlib_main); + time_now = vlib_time_now (vm); /* * Handle connections in this batch */ From 87557a38778f4d87c44b476efb896337dd8241e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Ganne?= Date: Wed, 25 Jun 2025 16:39:54 +0200 Subject: [PATCH 101/313] dns: fix recursive locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All callers to delete_random_entry() are already holding the lock to the DNS cache. Thanks to Rain for the report. Type: fix Change-Id: Id051427016f9b7444b812019ccc0f5085fd3e469 Signed-off-by: Benoît Ganne --- src/plugins/dns/dns.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/dns/dns.c b/src/plugins/dns/dns.c index de01474703..b73d18a741 100644 --- a/src/plugins/dns/dns.c +++ b/src/plugins/dns/dns.c @@ -654,7 +654,8 @@ delete_random_entry (dns_main_t * dm) return VNET_API_ERROR_UNSPECIFIED; #endif - dns_cache_lock (dm, 3); + CLIB_SPINLOCK_ASSERT_LOCKED (&dm->cache_lock); + limit = pool_elts (dm->entries); start_index = random_u32 (&dm->random_seed) % limit; @@ -670,12 +671,10 @@ delete_random_entry (dns_main_t * dm) && ((ep->flags & DNS_CACHE_ENTRY_FLAG_STATIC) == 0)) { rv = vnet_dns_delete_entry_by_index_nolock (dm, victim_index); - dns_cache_unlock (dm); return rv; } } } - dns_cache_unlock (dm); clib_warning ("Couldn't find an entry to delete?"); return VNET_API_ERROR_UNSPECIFIED; From f3d247a4a48c348c964430be2ac45a4db634e58c Mon Sep 17 00:00:00 2001 From: Andrew Yourtchenko Date: Wed, 11 Jun 2025 18:07:51 +0200 Subject: [PATCH 102/313] misc: VPP 25.06 Release Notes Type: docs Change-Id: Id32389963ab02af9ee70dcc040505b12bbc0e9db Signed-off-by: Andrew Yourtchenko (cherry picked from commit 1573e751c5478d3914d26cdde153390967932d6b) --- docs/aboutvpp/releasenotes/index.rst | 1 + docs/aboutvpp/releasenotes/v25.06.rst | 540 ++++++++++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 docs/aboutvpp/releasenotes/v25.06.rst diff --git a/docs/aboutvpp/releasenotes/index.rst b/docs/aboutvpp/releasenotes/index.rst index 1c3097288b..2f26cc8dff 100644 --- a/docs/aboutvpp/releasenotes/index.rst +++ b/docs/aboutvpp/releasenotes/index.rst @@ -6,6 +6,7 @@ Release notes .. toctree:: :maxdepth: 2 + v25.06 v25.02 v24.10 v24.06 diff --git a/docs/aboutvpp/releasenotes/v25.06.rst b/docs/aboutvpp/releasenotes/v25.06.rst new file mode 100644 index 0000000000..23a7b7d91c --- /dev/null +++ b/docs/aboutvpp/releasenotes/v25.06.rst @@ -0,0 +1,540 @@ +Release notes for VPP 25.06 +=========================== + +More than 260 commits since the previous release, including 92 fixes. + +Features +-------- + +- Build System + + - Update VPP-opt-deps to openssl 3.5.0 (`8a5a8de71 `_) + +- Host stack test framework + + - Added basic performance testing infra (`7cd37a9d8 `_) + +- Plugins + + - HTTP + + - Hpack primitive types (`5985e8683 `_) + - Hpack headers decoding (`58b6c4e6b `_) + - Hpack headers encoding (`a013224b9 `_) + - Http2 frames (`86abfc3e0 `_) + - Http/2 core skeleton (`492d2c15d `_) + - Http/2 stream state machine (`24668f2a3 `_) + - Http/2 starting tcp connection (`b43c7090f `_) + - Http/2 flow control (`ad5159837 `_) + - Http/2 multiplexing (`2d6b545f2 `_) + + - Host Stack Applications + + - Http client parallel sessions (`c8174f366 `_) + + - Marvell Octeon device driver + + - Set cpt descriptor count to 16k (`803eac3ef `_) + - Flush CQ buffers on stop (`c8d431ea2 `_) + - Update roc version (`2eb3240d1 `_) + - Configure max npa pools using driver arg (`06df0ac7a `_) + + - QUIC protocol + + - Quic engine api (`a94fab2fb `_) + + - Http\_static + + - Support multiple listeners (`0a897eb8d `_) + +- VNET + + - Crypto Infra + + - Make configurable crypto engines (`f479eeb76 `_) + - Add support for aes-cbc with hmac (`fba37eea8 `_) + - Add new handlers for cbc/ctr+hmac (`659b78d46 `_) + + - IPSec + + - Add support for bypass and discard policies for ipv6 (`9ab79f54d `_) + - Enable support for ipv6 udp ipsec encapsulation in policy mode (`0b04d71ce `_) + + - New Device Drivers Infra + + - Add support to configure driver arguments (`0e811a0d4 `_) + + - Session Layer + + - Add session eventing infra for apps (`9ed4013fd `_) + + - TLS and TLS engine plugins + + - Add ALPN support (`0b039ae97 `_) + +- Vector Library + + - Add new node type - SCHED nodes (`8a5add5c0 `_) + - Add 'relative' keyword for cpu configuration (`9b2015150 `_) + + +Known issues +------------ + +For the full list of issues please refer to fd.io `JIRA `_. + +Fixed issues +------------ + +For the full list of fixed issues please refer to: +- fd.io `JIRA `_ +- git `commit log `_ + + +API changes +----------- + +Description of results: + +- *Definition changed*: indicates that the API file was modified between releases. +- *Only in image*: indicates the API is new for this release. +- *Only in file*: indicates the API has been removed in this release. + +============================================================= ================== +Message Name Result +============================================================= ================== +feature_is_enabled only in image +feature_is_enabled_reply only in image +http_static_enable_v2 only in file +http_static_enable_v2_reply only in file +http_static_enable_v3 only in file +http_static_enable_v3_reply only in file +http_static_enable_v4 only in image +http_static_enable_v4_reply only in image +http_static_enable_v5 only in image +http_static_enable_v5_reply only in image +ip_session_redirect_details only in image +ip_session_redirect_dump only in image +pnat_flow_lookup only in image +pnat_flow_lookup_reply only in image +============================================================= ================== + +Found 14 api message signature differences + + +Newly deprecated API messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These messages are still there in the API, but can and probably +will disappear in the next release. + +- http_static_enable_v4 +- http_static_enable_v4_reply + +In-progress API messages +~~~~~~~~~~~~~~~~~~~~~~~~ + +These messages are provided for testing and experimentation only. +They are *not* subject to any compatibility process, +and therefore can arbitrarily change or disappear at *any* moment. +Also they may have less than satisfactory testing, making +them unsuitable for other use than the technology preview. +If you are intending to use these messages in production projects, +please collaborate with the feature maintainer on their productization. + +- abf_itf_attach_add_del +- abf_itf_attach_add_del_reply +- abf_itf_attach_details +- abf_itf_attach_dump +- abf_plugin_get_version +- abf_plugin_get_version_reply +- abf_policy_add_del +- abf_policy_add_del_reply +- abf_policy_details +- abf_policy_dump +- acl_plugin_use_hash_lookup_get +- acl_plugin_use_hash_lookup_get_reply +- acl_plugin_use_hash_lookup_set +- acl_plugin_use_hash_lookup_set_reply +- bpf_trace_filter_set +- bpf_trace_filter_set_reply +- bpf_trace_filter_set_v2 +- bpf_trace_filter_set_v2_reply +- cnat_get_snat_addresses +- cnat_get_snat_addresses_reply +- cnat_session_details +- cnat_session_dump +- cnat_session_purge +- cnat_session_purge_reply +- cnat_set_snat_addresses +- cnat_set_snat_addresses_reply +- cnat_set_snat_policy +- cnat_set_snat_policy_reply +- cnat_snat_policy_add_del_exclude_pfx +- cnat_snat_policy_add_del_exclude_pfx_reply +- cnat_snat_policy_add_del_if +- cnat_snat_policy_add_del_if_reply +- cnat_translation_del +- cnat_translation_del_reply +- cnat_translation_details +- cnat_translation_dump +- cnat_translation_update +- cnat_translation_update_reply +- det44_get_timeouts_reply +- det44_set_timeouts +- det44_set_timeouts_reply +- dev_attach +- dev_attach_reply +- dev_create_port_if +- dev_create_port_if_reply +- dev_detach +- dev_detach_reply +- dev_remove_port_if +- dev_remove_port_if_reply +- flowprobe_get_params +- flowprobe_get_params_reply +- flowprobe_interface_add_del +- flowprobe_interface_add_del_reply +- flowprobe_interface_details +- flowprobe_interface_dump +- flowprobe_set_params +- flowprobe_set_params_reply +- gbp_bridge_domain_add +- gbp_bridge_domain_add_reply +- gbp_bridge_domain_del +- gbp_bridge_domain_del_reply +- gbp_bridge_domain_details +- gbp_bridge_domain_dump +- gbp_bridge_domain_dump_reply +- gbp_contract_add_del +- gbp_contract_add_del_reply +- gbp_contract_details +- gbp_contract_dump +- gbp_endpoint_add +- gbp_endpoint_add_reply +- gbp_endpoint_del +- gbp_endpoint_del_reply +- gbp_endpoint_details +- gbp_endpoint_dump +- gbp_endpoint_group_add +- gbp_endpoint_group_add_reply +- gbp_endpoint_group_del +- gbp_endpoint_group_del_reply +- gbp_endpoint_group_details +- gbp_endpoint_group_dump +- gbp_ext_itf_add_del +- gbp_ext_itf_add_del_reply +- gbp_ext_itf_details +- gbp_ext_itf_dump +- gbp_recirc_add_del +- gbp_recirc_add_del_reply +- gbp_recirc_details +- gbp_recirc_dump +- gbp_route_domain_add +- gbp_route_domain_add_reply +- gbp_route_domain_del +- gbp_route_domain_del_reply +- gbp_route_domain_details +- gbp_route_domain_dump +- gbp_route_domain_dump_reply +- gbp_subnet_add_del +- gbp_subnet_add_del_reply +- gbp_subnet_details +- gbp_subnet_dump +- gbp_vxlan_tunnel_add +- gbp_vxlan_tunnel_add_reply +- gbp_vxlan_tunnel_del +- gbp_vxlan_tunnel_del_reply +- gbp_vxlan_tunnel_details +- gbp_vxlan_tunnel_dump +- gtpu_add_del_forward +- gtpu_add_del_forward_reply +- gtpu_add_del_tunnel_v2 +- gtpu_add_del_tunnel_v2_reply +- gtpu_get_transfer_counts +- gtpu_get_transfer_counts_reply +- gtpu_tunnel_v2_details +- gtpu_tunnel_v2_dump +- ikev2_child_sa_v2_details +- ikev2_child_sa_v2_dump +- ikev2_initiate_del_child_sa +- ikev2_initiate_del_child_sa_reply +- ikev2_initiate_del_ike_sa +- ikev2_initiate_del_ike_sa_reply +- ikev2_initiate_rekey_child_sa +- ikev2_initiate_rekey_child_sa_reply +- ikev2_initiate_sa_init +- ikev2_initiate_sa_init_reply +- ikev2_nonce_get +- ikev2_nonce_get_reply +- ikev2_profile_add_del +- ikev2_profile_add_del_reply +- ikev2_profile_details +- ikev2_profile_disable_natt +- ikev2_profile_disable_natt_reply +- ikev2_profile_dump +- ikev2_profile_set_auth +- ikev2_profile_set_auth_reply +- ikev2_profile_set_id +- ikev2_profile_set_id_reply +- ikev2_profile_set_ipsec_udp_port +- ikev2_profile_set_ipsec_udp_port_reply +- ikev2_profile_set_liveness +- ikev2_profile_set_liveness_reply +- ikev2_profile_set_ts +- ikev2_profile_set_ts_reply +- ikev2_profile_set_udp_encap +- ikev2_profile_set_udp_encap_reply +- ikev2_sa_v3_details +- ikev2_sa_v3_dump +- ikev2_set_esp_transforms +- ikev2_set_esp_transforms_reply +- ikev2_set_ike_transforms +- ikev2_set_ike_transforms_reply +- ikev2_set_local_key +- ikev2_set_local_key_reply +- ikev2_set_responder +- ikev2_set_responder_hostname +- ikev2_set_responder_hostname_reply +- ikev2_set_responder_reply +- ikev2_set_sa_lifetime +- ikev2_set_sa_lifetime_reply +- ikev2_set_tunnel_interface +- ikev2_set_tunnel_interface_reply +- ikev2_traffic_selector_details +- ikev2_traffic_selector_dump +- ip_neighbor_config_get +- ip_neighbor_config_get_reply +- ip_route_add_del_v2 +- ip_route_add_del_v2_reply +- ip_route_lookup_v2 +- ip_route_lookup_v2_reply +- ip_route_v2_details +- ip_route_v2_dump +- ip_session_redirect_add +- ip_session_redirect_add_reply +- ip_session_redirect_add_v2 +- ip_session_redirect_add_v2_reply +- ip_session_redirect_del +- ip_session_redirect_del_reply +- ip_session_redirect_details +- ip_session_redirect_dump +- l2_emulation +- l2_emulation_reply +- lcp_default_ns_get_reply +- lcp_default_ns_set +- lcp_default_ns_set_reply +- lcp_itf_pair_add_del_v2 +- lcp_itf_pair_add_del_v2_reply +- lcp_itf_pair_add_del_v3 +- lcp_itf_pair_add_del_v3_reply +- lcp_itf_pair_details +- lldp_details +- mdata_enable_disable +- mdata_enable_disable_reply +- nat44_ed_vrf_tables_v2_details +- nat44_ed_vrf_tables_v2_dump +- nat44_ei_add_del_address_range +- nat44_ei_add_del_address_range_reply +- nat44_ei_add_del_static_mapping +- nat44_ei_add_del_static_mapping_reply +- nat44_ei_address_details +- nat44_ei_address_dump +- nat44_ei_del_session +- nat44_ei_del_session_reply +- nat44_ei_del_user +- nat44_ei_del_user_reply +- nat44_ei_forwarding_enable_disable +- nat44_ei_forwarding_enable_disable_reply +- nat44_ei_ha_flush +- nat44_ei_ha_flush_reply +- nat44_ei_ha_resync +- nat44_ei_ha_resync_completed_event +- nat44_ei_ha_resync_reply +- nat44_ei_ha_set_failover +- nat44_ei_ha_set_failover_reply +- nat44_ei_ha_set_listener +- nat44_ei_ha_set_listener_reply +- nat44_ei_interface_add_del_feature +- nat44_ei_interface_add_del_feature_reply +- nat44_ei_interface_details +- nat44_ei_interface_dump +- nat44_ei_ipfix_enable_disable +- nat44_ei_ipfix_enable_disable_reply +- nat44_ei_plugin_enable_disable +- nat44_ei_plugin_enable_disable_reply +- nat44_ei_set_addr_and_port_alloc_alg +- nat44_ei_set_addr_and_port_alloc_alg_reply +- nat44_ei_set_fq_options +- nat44_ei_set_fq_options_reply +- nat44_ei_set_mss_clamping +- nat44_ei_set_mss_clamping_reply +- nat44_ei_set_timeouts +- nat44_ei_set_timeouts_reply +- nat44_ei_set_workers +- nat44_ei_set_workers_reply +- nat44_ei_show_fq_options +- nat44_ei_show_fq_options_reply +- nat44_ei_show_running_config +- nat44_ei_show_running_config_reply +- nat44_ei_static_mapping_details +- nat44_ei_static_mapping_dump +- nat44_ei_user_details +- nat44_ei_user_dump +- nat44_ei_user_session_details +- nat44_ei_user_session_dump +- nat44_ei_user_session_v2_details +- nat44_ei_user_session_v2_dump +- nat44_ei_worker_details +- nat44_ei_worker_dump +- nat64_plugin_enable_disable +- nat64_plugin_enable_disable_reply +- npt66_binding_add_del +- npt66_binding_add_del_reply +- oddbuf_enable_disable +- oddbuf_enable_disable_reply +- pg_interface_enable_disable_coalesce +- pg_interface_enable_disable_coalesce_reply +- ping_finished_event +- pnat_binding_add +- pnat_binding_add_reply +- pnat_binding_add_v2 +- pnat_binding_add_v2_reply +- pnat_binding_attach +- pnat_binding_attach_reply +- pnat_binding_del +- pnat_binding_del_reply +- pnat_binding_detach +- pnat_binding_detach_reply +- pnat_bindings_details +- pnat_bindings_get +- pnat_bindings_get_reply +- pnat_flow_lookup +- pnat_flow_lookup_reply +- pnat_interfaces_details +- pnat_interfaces_get +- pnat_interfaces_get_reply +- pvti_interface_create +- pvti_interface_create_reply +- pvti_interface_delete +- pvti_interface_delete_reply +- pvti_interface_details +- pvti_interface_dump +- sample_macswap_enable_disable +- sample_macswap_enable_disable_reply +- set_ip_flow_hash_v3 +- set_ip_flow_hash_v3_reply +- sflow_enable_disable +- sflow_enable_disable_reply +- sflow_header_bytes_get +- sflow_header_bytes_get_reply +- sflow_header_bytes_set +- sflow_header_bytes_set_reply +- sflow_interface_details +- sflow_interface_dump +- sflow_polling_interval_get +- sflow_polling_interval_get_reply +- sflow_polling_interval_set +- sflow_polling_interval_set_reply +- sflow_sampling_rate_get +- sflow_sampling_rate_get_reply +- sflow_sampling_rate_set +- sflow_sampling_rate_set_reply +- sr_localsids_with_packet_stats_details +- sr_localsids_with_packet_stats_dump +- sr_mobile_localsid_add_del +- sr_mobile_localsid_add_del_reply +- sr_mobile_policy_add +- sr_mobile_policy_add_reply +- sr_policies_with_sl_index_details +- sr_policies_with_sl_index_dump +- sr_policy_add_v2 +- sr_policy_add_v2_reply +- sr_policy_mod_v2 +- sr_policy_mod_v2_reply +- sw_interface_ip6nd_ra_details +- sw_interface_ip6nd_ra_dump +- sw_interface_set_vxlan_gbp_bypass +- sw_interface_set_vxlan_gbp_bypass_reply +- test_addresses +- test_addresses2 +- test_addresses2_reply +- test_addresses3 +- test_addresses3_reply +- test_addresses_reply +- test_empty +- test_empty_reply +- test_enum +- test_enum_reply +- test_interface +- test_interface_reply +- test_prefix +- test_prefix_reply +- test_string +- test_string2 +- test_string2_reply +- test_string_reply +- test_vla +- test_vla2 +- test_vla2_reply +- test_vla3 +- test_vla3_reply +- test_vla4 +- test_vla4_reply +- test_vla5 +- test_vla5_reply +- test_vla_reply +- trace_capture_packets +- trace_capture_packets_reply +- trace_clear_cache +- trace_clear_cache_reply +- trace_clear_capture +- trace_clear_capture_reply +- trace_details +- trace_dump +- trace_dump_reply +- trace_filter_function_details +- trace_filter_function_dump +- trace_set_filter_function +- trace_set_filter_function_reply +- trace_set_filters +- trace_set_filters_reply +- trace_v2_details +- trace_v2_dump +- tracenode_enable_disable +- tracenode_enable_disable_reply +- vxlan_gbp_tunnel_add_del +- vxlan_gbp_tunnel_add_del_reply +- vxlan_gbp_tunnel_details +- vxlan_gbp_tunnel_dump +- want_ping_finished_events +- want_ping_finished_events_reply + +Patches that changed API definitions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +``src/vnet/feature/feature.api`` + +* `6022e9566 `_ vnet: add vapi interface feature enablement check function + +``src/plugins/linux-cp/lcp.api`` + +* `9543e2970 `_ linux-cp: Add support for LACP packets + +``src/plugins/nat/pnat/pnat.api`` + +* `406232920 `_ pnat: expose binding_index over API + +``src/plugins/vxlan-gpe/vxlan_gpe.api`` + +* `bb4858cdf `_ vxlan: move vxlan-gpe to a plugin + +``src/plugins/http_static/http_static.api`` + +* `c3bbeb93b `_ http_static: url handler buffer large POST body +* `5e94895df `_ http_static: introduce max-body-size parameter + +``src/plugins/ip_session_redirect/ip_session_redirect.api`` + +* `eca860c85 `_ ip_session_redirect: add dump api for session redirects From 75fd8adb48d7ca47dac9b2b7b227fd07b434fcf0 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Wed, 18 Jun 2025 12:25:02 +0000 Subject: [PATCH 103/313] hsa: introduce max-tx-chunk parameter to built-in echo client ...which enables the user to control the size of the data chunk being written, unless the size needs to be reduced due to other reasons. Type: improvement Change-Id: I660e3482047ae2a13b337df1abb405ecef3cf0d0 Signed-off-by: Semir Sionek --- src/plugins/hs_apps/echo_client.c | 15 +++++++++++---- src/plugins/hs_apps/echo_client.h | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 5211f00c99..2d70120b8d 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -79,7 +79,6 @@ ec_session_get (ec_worker_t *wrk, u32 ec_index) static void send_data_chunk (ec_main_t *ecm, ec_session_t *es) { - const u64 max_burst = 128000; u8 *test_data = ecm->connect_test_data; int test_buf_len, test_buf_offset, rv; u64 bytes_to_send; @@ -89,9 +88,10 @@ send_data_chunk (ec_main_t *ecm, ec_session_t *es) test_buf_len = vec_len (test_data); ASSERT (test_buf_len > 0); if (ecm->run_time) - bytes_to_send = clib_min (svm_fifo_max_enqueue_prod (f), max_burst); + bytes_to_send = + clib_min (svm_fifo_max_enqueue_prod (f), ecm->max_chunk_bytes); else - bytes_to_send = clib_min (es->bytes_to_send, max_burst); + bytes_to_send = clib_min (es->bytes_to_send, ecm->max_chunk_bytes); if (ecm->throughput) bytes_to_send = clib_min (es->bytes_paced_current, bytes_to_send); test_buf_offset = es->bytes_sent % test_buf_len; @@ -419,6 +419,7 @@ ec_reset_runtime_config (ec_main_t *ecm) ecm->run_time = 0; ecm->throughput = 0; ecm->pacing_window_len = 1; + ecm->max_chunk_bytes = 128 << 10; vec_free (ecm->connect_uri); } @@ -1174,7 +1175,7 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, ec_main_t *ecm = &ec_main; uword *event_data = 0, event_type; clib_error_t *error = 0; - int rv, timed_run_conflict = 0, had_config = 1; + int rv, timed_run_conflict = 0, tput_conflict = 0, had_config = 1; u64 total_bytes; f64 delta; @@ -1224,6 +1225,9 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "throughput %U", unformat_memory_size, &ecm->throughput)) ; + else if (unformat (line_input, "max-tx-chunk %U", unformat_memory_size, + &ecm->max_chunk_bytes)) + tput_conflict = 1; else if (unformat (line_input, "preallocate-fifos")) ecm->prealloc_fifos = 1; else if (unformat (line_input, "preallocate-sessions")) @@ -1258,6 +1262,9 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, if (timed_run_conflict && ecm->run_time) return clib_error_return (0, "failed: invalid arguments for a timed run!"); + if (ecm->throughput && tput_conflict) + return clib_error_return ( + 0, "failed: can't set fixed tx chunk for a throughput run!"); parse_config: diff --git a/src/plugins/hs_apps/echo_client.h b/src/plugins/hs_apps/echo_client.h index d928a4e936..d0eed6bd73 100644 --- a/src/plugins/hs_apps/echo_client.h +++ b/src/plugins/hs_apps/echo_client.h @@ -105,6 +105,7 @@ typedef struct f64 syn_timeout; /**< Test syn timeout (s) */ f64 test_timeout; /**< Test timeout (s) */ f64 run_time; /**< Length of a test (s) */ + u64 max_chunk_bytes; /* * Flags From 834bb74b1567e26ce2bbaa1a9626b3bd73fae0dd Mon Sep 17 00:00:00 2001 From: Arthur de Kerhor Date: Tue, 17 Jun 2025 16:30:51 +0200 Subject: [PATCH 104/313] policer: don't compute CPU clock frequency for each API call In some virtualized environments, where we can't get TSC from clib_get_cpuid, os_cpu_clock_frequency can take more than 1ms to return. As the TSC value is invariant over time, it can be cached in policer_init instead of being recalculated everytime a policer is created/updated. Type: improvement Change-Id: Icfd00feac76f35d562546a17e4830efb91dba219 Signed-off-by: Arthur de Kerhor --- src/vnet/policer/policer.c | 14 ++++++++++++++ src/vnet/policer/policer.h | 3 +++ src/vnet/policer/xlate.c | 36 +++++++++--------------------------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/vnet/policer/policer.c b/src/vnet/policer/policer.c index 00180d9c99..a24edb3c12 100644 --- a/src/vnet/policer/policer.c +++ b/src/vnet/policer/policer.c @@ -1037,6 +1037,19 @@ VLIB_CLI_COMMAND (show_policer_pools_command, static) = { .function = show_policer_pools_command_fn, }; +/* + * Return the number of hardware TSC timer ticks per second for the dataplane. + * This is approximately, but not exactly, the clock speed. + */ +static u64 +get_tsc_hz (void) +{ + f64 cpu_freq; + + cpu_freq = os_cpu_clock_frequency (); + return (u64) cpu_freq; +} + clib_error_t * policer_init (vlib_main_t * vm) { @@ -1052,6 +1065,7 @@ policer_init (vlib_main_t * vm) pm->policer_config_by_name = hash_create_string (0, sizeof (uword)); pm->policer_index_by_name = hash_create_string (0, sizeof (uword)); + pm->tsc_hz = get_tsc_hz (); vnet_classify_register_unformat_policer_next_index_fn (unformat_policer_classify_next_index); diff --git a/src/vnet/policer/policer.h b/src/vnet/policer/policer.h index 7ce7fc79d4..9eef408fd8 100644 --- a/src/vnet/policer/policer.h +++ b/src/vnet/policer/policer.h @@ -50,6 +50,9 @@ typedef struct /* frame queue for thread handoff */ u32 fq_index[VLIB_N_RX_TX]; + /* cached TSC value. No need to re-compute for each new policer */ + u64 tsc_hz; + u16 msg_id_base; } vnet_policer_main_t; diff --git a/src/vnet/policer/xlate.c b/src/vnet/policer/xlate.c index bffd208716..26b0f593b1 100644 --- a/src/vnet/policer/xlate.c +++ b/src/vnet/policer/xlate.c @@ -843,19 +843,6 @@ pol_compute_hw_params (qos_pol_cfg_params_st *cfg, qos_pol_hw_params_st *hw) return 0; } -/* - * Return the number of hardware TSC timer ticks per second for the dataplane. - * This is approximately, but not exactly, the clock speed. - */ -static u64 -get_tsc_hz (void) -{ - f64 cpu_freq; - - cpu_freq = os_cpu_clock_frequency (); - return (u64) cpu_freq; -} - /* * Convert rates into bytes_per_period and scale. * Return 0 if ok or 1 if error. @@ -948,8 +935,8 @@ compute_policer_params (u64 hz, /* CPU speed in clocks per second */ int x86_pol_compute_hw_params (qos_pol_cfg_params_st *cfg, policer_t *hw) { + vnet_policer_main_t *pm = &vnet_policer_main; const int BYTES_PER_KBIT = (1000 / 8); - u64 hz; u32 cap; if (!cfg || !hw) @@ -958,7 +945,6 @@ x86_pol_compute_hw_params (qos_pol_cfg_params_st *cfg, policer_t *hw) return (-1); } - hz = get_tsc_hz (); hw->last_update_time = 0; /* @@ -1001,10 +987,9 @@ x86_pol_compute_hw_params (qos_pol_cfg_params_st *cfg, policer_t *hw) return (-1); } - if (compute_policer_params (hz, - (u64) cfg->rb.kbps.cir_kbps * - BYTES_PER_KBIT, 0, &hw->current_limit, - &hw->extended_limit, + if (compute_policer_params (pm->tsc_hz, + (u64) cfg->rb.kbps.cir_kbps * BYTES_PER_KBIT, + 0, &hw->current_limit, &hw->extended_limit, &hw->cir_tokens_per_period, &hw->pir_tokens_per_period, &hw->scale)) { @@ -1025,14 +1010,11 @@ x86_pol_compute_hw_params (qos_pol_cfg_params_st *cfg, policer_t *hw) return (-1); } - if (compute_policer_params (hz, - (u64) cfg->rb.kbps.cir_kbps * - BYTES_PER_KBIT, - (u64) cfg->rb.kbps.eir_kbps * - BYTES_PER_KBIT, &hw->current_limit, - &hw->extended_limit, - &hw->cir_tokens_per_period, - &hw->pir_tokens_per_period, &hw->scale)) + if (compute_policer_params ( + pm->tsc_hz, (u64) cfg->rb.kbps.cir_kbps * BYTES_PER_KBIT, + (u64) cfg->rb.kbps.eir_kbps * BYTES_PER_KBIT, &hw->current_limit, + &hw->extended_limit, &hw->cir_tokens_per_period, + &hw->pir_tokens_per_period, &hw->scale)) { QOS_DEBUG_ERROR ("Policer parameter computation failed."); return (-1); From 241e116e65e76f4f7b4b983a1d6d07b3e0c5f37a Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 25 Jun 2025 14:20:57 +0200 Subject: [PATCH 105/313] svm: don't use __WORDSIZE It is glibc specific, breaks MUSL Type: fix Change-Id: I4f898ec14a9776e298bdf529545adef70f15ddf5 Signed-off-by: Damjan Marion --- src/svm/svm_common.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/svm/svm_common.h b/src/svm/svm_common.h index 0e19ffd3f7..890bc584d2 100644 --- a/src/svm/svm_common.h +++ b/src/svm/svm_common.h @@ -87,16 +87,14 @@ typedef struct svm_map_region_args_ /* * Memory mapped to high addresses for session/vppcom/vcl/etc... */ -#if __WORDSIZE == 64 +#if uword_bits == 64 #ifdef CLIB_SANITIZE_ADDR #define HIGH_SEGMENT_BASEVA 0x300000000000 /* DO NOT CHANGE THIS: YOU'LL BREAK ASAN */ #else /* CLIB_SANITIZE_ADDR */ #define HIGH_SEGMENT_BASEVA (128ULL << 30) /* 128GB */ -#endif /* CLIB_SANITIZE_ADDR */ -#elif __WORDSIZE == 32 -#define HIGH_SEGMENT_BASEVA (3584UL << 20) /* 3.5GB */ +#endif /* CLIB_SANITIZE_ADDR */ #else -#error "unknown __WORDSIZE" +#define HIGH_SEGMENT_BASEVA (3584UL << 20) /* 3.5GB */ #endif /* From de042161625b0cadf5ae26b9007443406b34aefd Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 25 Jun 2025 15:09:31 +0200 Subject: [PATCH 106/313] ena: use snprintf instead of strncpy .. and silence gcc-11 Type: fix Change-Id: Iad2671baf7d29aa861e74aafe952591e7205732d Signed-off-by: Damjan Marion --- src/plugins/dev_ena/ena.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/dev_ena/ena.c b/src/plugins/dev_ena/ena.c index a81a33d5f2..6e7afaa772 100644 --- a/src/plugins/dev_ena/ena.c +++ b/src/plugins/dev_ena/ena.c @@ -170,8 +170,9 @@ ena_init (vlib_main_t *vm, vnet_dev_t *dev) *ed->host_info = host_info; ed->host_info->num_cpus = vlib_get_n_threads (); - strncpy ((char *) ed->host_info->kernel_ver_str, VPP_BUILD_VER, - sizeof (ed->host_info->kernel_ver_str) - 1); + snprintf ((char *) ed->host_info->kernel_ver_str, + sizeof (ed->host_info->kernel_ver_str), "%.*s", + (int) (sizeof (ed->host_info->kernel_ver_str) - 1), VPP_BUILD_VER); ena_set_mem_addr (vm, dev, &host_attr.os_info_ba, ed->host_info); if ((rv = ena_aq_set_feature (vm, dev, ENA_ADMIN_FEAT_ID_HOST_ATTR_CONFIG, From c869293bfcbf4b4df06fa834dd861f20539e8e5c Mon Sep 17 00:00:00 2001 From: Varun Rapelly Date: Mon, 30 Jun 2025 15:35:59 +0000 Subject: [PATCH 107/313] tls: fix compilation issue with openssl-1.1 This patch fixes compilation issue observed with openssl-1.1 with 'SSL_CTX_set_async_callback'. Type: fix Change-Id: I3d65f69056034b4afcebd7813c34f9ecebd299e0 Signed-off-by: Varun Rapelly --- src/plugins/tlsopenssl/tls_openssl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 651053b33c..8d3f5f381c 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -795,12 +795,14 @@ openssl_ctx_init_client (tls_ctx_t * ctx) SSL_CTX_set_ecdh_auto (oc->client_ssl_ctx, 1); SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); +#ifdef HAVE_OPENSSL_ASYNC if (om->async) { SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ASYNC); SSL_CTX_set_async_callback (oc->client_ssl_ctx, tls_async_openssl_callback); } +#endif rv = SSL_CTX_set_cipher_list (oc->client_ssl_ctx, (const char *) om->ciphers); From 230195db6a86b927f213fb42acc0d0baadec5de7 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Fri, 20 Jun 2025 17:06:22 +0000 Subject: [PATCH 108/313] ioam: remove use of local_next_by_ip_protocol struct Type: fix ioam plugin is using data structure which is specific to ip4 and ip6 lookup nodes. This patch also use the IP_PROTOCOL defines instead of custom define in srv6 code. Signed-off-by: Mohsin Kazmi Change-Id: Iafd2a8416dd1f44ef7afd07c92915cd7dd6400a6 --- src/plugins/ioam/ip6/ioam_cache_node.c | 2 +- src/plugins/ioam/udp-ping/udp_ping_node.c | 60 ++++++++++++++--------- src/plugins/srv6-mobile/gtp6_d_di.c | 2 +- src/plugins/srv6-mobile/node.c | 8 +-- src/vnet/srv6/sr_packet.h | 4 +- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/plugins/ioam/ip6/ioam_cache_node.c b/src/plugins/ioam/ip6/ioam_cache_node.c index 9859ee6fbf..cef54637e8 100644 --- a/src/plugins/ioam/ip6/ioam_cache_node.c +++ b/src/plugins/ioam/ip6/ioam_cache_node.c @@ -354,7 +354,7 @@ VLIB_NODE_FN (ip6_add_from_cache_hbh_node) (vlib_main_t * vm, /* Patch the protocol chain, insert the h-b-h (type 0) header */ srh0->protocol = ip0->protocol; - hbh0->protocol = IPPROTO_IPV6_ROUTE; + hbh0->protocol = IP_PROTOCOL_IPV6_ROUTE; ip0->protocol = 0; new_l0 = clib_net_to_host_u16 (ip0->payload_length) + rewrite_len + diff --git a/src/plugins/ioam/udp-ping/udp_ping_node.c b/src/plugins/ioam/udp-ping/udp_ping_node.c index fbc3b13971..f7302e99f3 100644 --- a/src/plugins/ioam/udp-ping/udp_ping_node.c +++ b/src/plugins/ioam/udp-ping/udp_ping_node.c @@ -34,6 +34,7 @@ typedef enum UDP_PING_NEXT_UDP_LOOKUP, UDP_PING_NEXT_ICMP, UDP_PING_NEXT_IP6_LOOKUP, + UDP_PING_NEXT_SRV6_LOCAL, UDP_PING_NEXT_IP6_DROP, UDP_PING_N_NEXT, } udp_ping_next_t; @@ -517,9 +518,6 @@ udp_ping_local_analyse (vlib_node_runtime_t * node, vlib_buffer_t * b0, ip6_header_t * ip0, ip6_hop_by_hop_header_t * hbh0, u16 * next0) { - ip6_main_t *im = &ip6_main; - ip_lookup_main_t *lm = &im->lookup_main; - *next0 = UDP_PING_NEXT_IP6_DROP; /* @@ -578,32 +576,47 @@ udp_ping_local_analyse (vlib_node_runtime_t * node, vlib_buffer_t * b0, * remote address. So pass it to SR processing as it may be local packet * afterall */ - if (PREDICT_FALSE (hbh0->protocol == IPPROTO_IPV6_ROUTE)) - goto end; - - /* Other case remove hbh-ioam headers */ - u64 *copy_dst0, *copy_src0; - u16 new_l0; + if (PREDICT_FALSE (hbh0->protocol == IP_PROTOCOL_IPV6_ROUTE)) + *next0 = UDP_PING_NEXT_SRV6_LOCAL; + else + { + /* Other case remove hbh-ioam headers */ + u64 *copy_dst0, *copy_src0; + u16 new_l0; - vlib_buffer_advance (b0, (hbh0->length + 1) << 3); + vlib_buffer_advance (b0, (hbh0->length + 1) << 3); - new_l0 = clib_net_to_host_u16 (ip0->payload_length) - - ((hbh0->length + 1) << 3); + new_l0 = + clib_net_to_host_u16 (ip0->payload_length) - ((hbh0->length + 1) << 3); - ip0->payload_length = clib_host_to_net_u16 (new_l0); + ip0->payload_length = clib_host_to_net_u16 (new_l0); - ip0->protocol = hbh0->protocol; + ip0->protocol = hbh0->protocol; - copy_src0 = (u64 *) ip0; - copy_dst0 = copy_src0 + (hbh0->length + 1); - copy_dst0[4] = copy_src0[4]; - copy_dst0[3] = copy_src0[3]; - copy_dst0[2] = copy_src0[2]; - copy_dst0[1] = copy_src0[1]; - copy_dst0[0] = copy_src0[0]; + copy_src0 = (u64 *) ip0; + copy_dst0 = copy_src0 + (hbh0->length + 1); + copy_dst0[4] = copy_src0[4]; + copy_dst0[3] = copy_src0[3]; + copy_dst0[2] = copy_src0[2]; + copy_dst0[1] = copy_src0[1]; + copy_dst0[0] = copy_src0[0]; -end: - *next0 = lm->local_next_by_ip_protocol[hbh0->protocol]; + if (PREDICT_FALSE (hbh0->protocol == IP_PROTOCOL_ICMP6)) + { + /* If next header is ICMP, then pass it to ICMP processing */ + *next0 = UDP_PING_NEXT_ICMP; + } + else if (PREDICT_FALSE (hbh0->protocol == IP_PROTOCOL_UDP)) + { + /* If next header is UDP, then pass it to UDP processing */ + *next0 = UDP_PING_NEXT_UDP_LOOKUP; + } + else + { + /* If next header is not UDP or ICMP, then punt it */ + *next0 = UDP_PING_NEXT_PUNT; + } + } return; } @@ -804,6 +817,7 @@ VLIB_REGISTER_NODE (udp_ping_local, static) = [UDP_PING_NEXT_UDP_LOOKUP] = "ip6-udp-lookup", [UDP_PING_NEXT_ICMP] = "ip6-icmp-input", [UDP_PING_NEXT_IP6_LOOKUP] = "ip6-lookup", + [UDP_PING_NEXT_SRV6_LOCAL] = "sr-localsid-d", [UDP_PING_NEXT_IP6_DROP] = "ip6-drop", }, }; diff --git a/src/plugins/srv6-mobile/gtp6_d_di.c b/src/plugins/srv6-mobile/gtp6_d_di.c index 94bc684161..2c78038d79 100644 --- a/src/plugins/srv6-mobile/gtp6_d_di.c +++ b/src/plugins/srv6-mobile/gtp6_d_di.c @@ -187,7 +187,7 @@ srv6_end_m_gtp6_d_di_init (vlib_main_t * vm) // IPv6 header (default) ip6->ip.ip_version_traffic_class_and_flow_label = 0x60; ip6->ip.hop_limit = 64; - ip6->ip.protocol = IPPROTO_IPV6_ROUTE; + ip6->ip.protocol = IP_PROTOCOL_IPV6_ROUTE; // SR header (default) ip6->sr.type = 4; diff --git a/src/plugins/srv6-mobile/node.c b/src/plugins/srv6-mobile/node.c index c8f619cf04..f47fd9dbb6 100644 --- a/src/plugins/srv6-mobile/node.c +++ b/src/plugins/srv6-mobile/node.c @@ -374,7 +374,7 @@ VLIB_NODE_FN (srv6_end_m_gtp4_e) len0 = vlib_buffer_length_in_chain (vm, b0); - if ((ip6srv0->ip.protocol == IPPROTO_IPV6_ROUTE && + if ((ip6srv0->ip.protocol == IP_PROTOCOL_IPV6_ROUTE && len0 < sizeof (ip6srv_combo_header_t) + ip6srv0->sr.length * 8) || (len0 < sizeof (ip6_header_t))) @@ -402,7 +402,7 @@ VLIB_NODE_FN (srv6_end_m_gtp4_e) void *p; uword plen; - if (ip6srv0->ip.protocol == IPPROTO_IPV6_ROUTE) + if (ip6srv0->ip.protocol == IP_PROTOCOL_IPV6_ROUTE) { tag = ip6srv0->sr.tag; } @@ -513,7 +513,7 @@ VLIB_NODE_FN (srv6_end_m_gtp4_e) } } - if (ip6srv0->ip.protocol == IPPROTO_IPV6_ROUTE) + if (ip6srv0->ip.protocol == IP_PROTOCOL_IPV6_ROUTE) { vlib_buffer_advance (b0, (word) sizeof (ip6srv_combo_header_t) + @@ -1384,7 +1384,7 @@ VLIB_NODE_FN (srv6_end_m_gtp6_e) len0 = vlib_buffer_length_in_chain (vm, b0); - if ((ip6srv0->ip.protocol != IPPROTO_IPV6_ROUTE) || + if ((ip6srv0->ip.protocol != IP_PROTOCOL_IPV6_ROUTE) || (len0 < sizeof (ip6srv_combo_header_t) + 8 * ip6srv0->sr.length)) { next0 = SRV6_END_M_GTP6_E_NEXT_DROP; diff --git a/src/vnet/srv6/sr_packet.h b/src/vnet/srv6/sr_packet.h index cf9fcb70bc..3320be78ca 100644 --- a/src/vnet/srv6/sr_packet.h +++ b/src/vnet/srv6/sr_packet.h @@ -110,8 +110,8 @@ * */ -#ifndef IPPROTO_IPV6_ROUTE -#define IPPROTO_IPV6_ROUTE 43 +#ifndef IP_PROTOCOL_IPV6_ROUTE +#define IP_PROTOCOL_IPV6_ROUTE 43 #endif #define ROUTING_HEADER_TYPE_SR 4 From 94adfee2124ff61b3d35e2dd5988f87498a69ef4 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Thu, 26 Jun 2025 10:14:18 +0000 Subject: [PATCH 109/313] gso: fix ip fragment support for gso packet Type: fix GSO packets must be fragmented if the egress interface does not support GSO offload and its MTU is smaller than the combined size of the GSO chunk and protocol headers. Signed-off-by: Mohsin Kazmi Change-Id: I94dff39c475a6ba31b1d8f4517e84be3ab506607 --- src/vnet/buffer.h | 13 +- src/vnet/gso/node.c | 6 +- src/vnet/ip/ip_frag.c | 6 +- test/test_gso.py | 363 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 327 insertions(+), 61 deletions(-) diff --git a/src/vnet/buffer.h b/src/vnet/buffer.h index ae89915863..c9005b70da 100644 --- a/src/vnet/buffer.h +++ b/src/vnet/buffer.h @@ -530,10 +530,15 @@ STATIC_ASSERT (sizeof (vnet_buffer_opaque2_t) == STRUCT_SIZE_OF (vlib_buffer_t, opaque2), "VNET buffer opaque2 meta-data too large for vlib_buffer"); -#define gso_mtu_sz(b) (vnet_buffer2(b)->gso_size + \ - vnet_buffer2(b)->gso_l4_hdr_sz + \ - vnet_buffer(b)->l4_hdr_offset - \ - vnet_buffer (b)->l3_hdr_offset) +#define gso_mtu_tunnel_size(b) \ + ((vnet_buffer (b)->oflags & VNET_BUFFER_OFFLOAD_F_TNL_MASK) ? \ + vnet_buffer (b)->l3_hdr_offset - vnet_buffer2 (b)->outer_l3_hdr_offset : \ + 0) + +#define gso_mtu_sz(b) \ + (vnet_buffer2 (b)->gso_size + vnet_buffer2 (b)->gso_l4_hdr_sz + \ + vnet_buffer (b)->l4_hdr_offset - vnet_buffer (b)->l3_hdr_offset + \ + gso_mtu_tunnel_size (b)) format_function_t format_vnet_buffer_no_chain; format_function_t format_vnet_buffer; diff --git a/src/vnet/gso/node.c b/src/vnet/gso/node.c index c4f4b74cd9..1fabe44d2a 100644 --- a/src/vnet/gso/node.c +++ b/src/vnet/gso/node.c @@ -135,10 +135,10 @@ tso_segment_vxlan_tunnel_headers_fixup (vlib_main_t *vm, vlib_buffer_t *b) ip4->length = clib_host_to_net_u16 ( b->current_length - (outer_l3_hdr_offset - b->current_data)); ip4->checksum = ip4_header_checksum (ip4); + udp->length = clib_host_to_net_u16 ( + b->current_length - (outer_l4_hdr_offset - b->current_data)); if (vnet_buffer (b)->oflags & VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM) { - udp->length = clib_host_to_net_u16 ( - b->current_length - (outer_l4_hdr_offset - b->current_data)); // udp checksum is 0, in udp tunnel udp->checksum = 0; } @@ -151,11 +151,11 @@ tso_segment_vxlan_tunnel_headers_fixup (vlib_main_t *vm, vlib_buffer_t *b) { ip6->payload_length = clib_host_to_net_u16 ( b->current_length - (outer_l4_hdr_offset - b->current_data)); + udp->length = ip6->payload_length; if (vnet_buffer (b)->oflags & VNET_BUFFER_OFFLOAD_F_OUTER_UDP_CKSUM) { int bogus; - udp->length = ip6->payload_length; // udp checksum is 0, in udp tunnel udp->checksum = 0; udp->checksum = diff --git a/src/vnet/ip/ip_frag.c b/src/vnet/ip/ip_frag.c index 729aa677d4..aa7a031c90 100644 --- a/src/vnet/ip/ip_frag.c +++ b/src/vnet/ip/ip_frag.c @@ -103,9 +103,10 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, if (from_b->flags & VNET_BUFFER_F_OFFLOAD) { - ASSERT ((from_b->flags & VNET_BUFFER_F_GSO) == 0); vnet_calc_checksums_inline (vm, from_b, 1 /* is_v4 */, 0 /* is_v6 */); vnet_calc_outer_checksums_inline (vm, from_b); + /* Packet is going to be fragmented, so remove GSO flag */ + from_b->flags &= ~VNET_BUFFER_F_GSO; } org_from_packet = vlib_buffer_get_current (from_b); @@ -385,9 +386,10 @@ ip6_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u16 mtu, if (from_b->flags & VNET_BUFFER_F_OFFLOAD) { - ASSERT ((from_b->flags & VNET_BUFFER_F_GSO) == 0); vnet_calc_checksums_inline (vm, from_b, 0 /* is_v4 */, 1 /* is_v6 */); vnet_calc_outer_checksums_inline (vm, from_b); + /* Packet is going to be fragmented, so remove GSO flag */ + from_b->flags &= ~VNET_BUFFER_F_GSO; } org_from_packet = vlib_buffer_get_current (from_b); diff --git a/test/test_gso.py b/test/test_gso.py index c3822b01fa..63b42c9530 100644 --- a/test/test_gso.py +++ b/test/test_gso.py @@ -14,7 +14,7 @@ from scapy.layers.l2 import GRE from scapy.layers.inet6 import IPv6, Ether, IP, ICMPv6PacketTooBig from scapy.layers.inet6 import ipv6nh, IPerror6 -from scapy.layers.inet import TCP, ICMP +from scapy.layers.inet import TCP, ICMP, UDP, defragment from scapy.layers.vxlan import VXLAN from scapy.layers.ipsec import ESP @@ -49,12 +49,16 @@ def __init__(self, *args): def setUpClass(self): super(TestGSO, self).setUpClass() res = self.create_pg_interfaces(range(2)) - res_gso = self.create_pg_interfaces(range(2, 4), 1, 1460) - self.create_pg_interfaces(range(4, 5), 1, 8940) + res_gso1 = self.create_pg_interfaces(range(2, 3), 1, 1460) + res_gso2 = self.create_pg_interfaces(range(3, 4), 1, 1440) + self.pg_interfaces = self.create_pg_interfaces(range(4, 5), 1, 8940) self.pg_interfaces.append(res[0]) self.pg_interfaces.append(res[1]) - self.pg_interfaces.append(res_gso[0]) - self.pg_interfaces.append(res_gso[1]) + self.pg_interfaces.append(res_gso1[0]) + self.pg_interfaces.append(res_gso2[0]) + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1500, 0, 0, 0]) + self.vapi.sw_interface_set_mtu(self.pg2.sw_if_index, [1500, 0, 0, 0]) + self.vapi.sw_interface_set_mtu(self.pg3.sw_if_index, [1500, 0, 0, 0]) @classmethod def tearDownClass(self): @@ -85,13 +89,34 @@ def setUp(self): vni=self.single_tunnel_bd, ) - self.ipip4 = VppIpIpTunInterface( + self.single_tunnel_bd2 = 20 + self.vxlan3 = VppVxlanTunnel( + self, + src=self.pg1.local_ip4, + dst=self.pg1.remote_ip4, + vni=self.single_tunnel_bd2, + ) + self.vxlan4 = VppVxlanTunnel( + self, + src=self.pg1.local_ip6, + dst=self.pg1.remote_ip6, + vni=self.single_tunnel_bd2, + ) + + self.ipip4_0 = VppIpIpTunInterface( self, self.pg0, self.pg0.local_ip4, self.pg0.remote_ip4 ) - self.ipip6 = VppIpIpTunInterface( + self.ipip6_0 = VppIpIpTunInterface( self, self.pg0, self.pg0.local_ip6, self.pg0.remote_ip6 ) + self.ipip4_1 = VppIpIpTunInterface( + self, self.pg1, self.pg1.local_ip4, self.pg1.remote_ip4 + ) + self.ipip6_1 = VppIpIpTunInterface( + self, self.pg1, self.pg1.local_ip6, self.pg1.remote_ip6 + ) + self.gre4 = VppGreInterface(self, self.pg0.local_ip4, self.pg0.remote_ip4) self.gre6 = VppGreInterface(self, self.pg0.local_ip6, self.pg0.remote_ip6) @@ -103,6 +128,13 @@ def tearDown(self): i.unconfig_ip6() i.admin_down() + def get_mtu(self, sw_if_index): + rv = self.vapi.sw_interface_dump(sw_if_index=sw_if_index) + for i in rv: + if i.sw_if_index == sw_if_index: + return i.mtu[0] + return 0 + def test_gso(self): """GSO test""" # @@ -194,19 +226,19 @@ def test_gso(self): # ipv6 # p61 = ( - Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac) - / IPv6(src=self.pg2.remote_ip6, dst=self.pg3.remote_ip6) + Ether(src=self.pg3.remote_mac, dst=self.pg3.local_mac) + / IPv6(src=self.pg3.remote_ip6, dst=self.pg2.remote_ip6) / TCP(sport=1234, dport=1234) / Raw(b"\xa5" * 65200) ) - rxs = self.send_and_expect(self.pg2, 100 * [p61], self.pg3, 100) + rxs = self.send_and_expect(self.pg3, 100 * [p61], self.pg2, 100) for rx in rxs: - self.assertEqual(rx[Ether].src, self.pg3.local_mac) - self.assertEqual(rx[Ether].dst, self.pg3.remote_mac) - self.assertEqual(rx[IPv6].src, self.pg2.remote_ip6) - self.assertEqual(rx[IPv6].dst, self.pg3.remote_ip6) + self.assertEqual(rx[Ether].src, self.pg2.local_mac) + self.assertEqual(rx[Ether].dst, self.pg2.remote_mac) + self.assertEqual(rx[IPv6].src, self.pg3.remote_ip6) + self.assertEqual(rx[IPv6].dst, self.pg2.remote_ip6) self.assertEqual(rx[IPv6].plen, 65220) # 65200 + 20 (TCP) self.assertEqual(rx[TCP].sport, 1234) self.assertEqual(rx[TCP].dport, 1234) @@ -290,6 +322,7 @@ def test_gso(self): self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) self.assertEqual(rx[IP].src, self.pg2.remote_ip4) self.assertEqual(rx[IP].dst, self.pg1.remote_ip4) + self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0)) self.assert_ip_checksum_valid(rx) size += rx[IP].len - 20 size -= 20 * 5 # TCP header @@ -322,12 +355,11 @@ def test_gso(self): self.assertEqual(rx[IPerror6].plen - 20, 65200) # - # Send jumbo frame with gso enabled only on input interface with 9K MTU - # and DF bit is unset. GSO packet will be fragmented. MSS is 8960. GSO + # Send jumbo frame with gso enabled only on input interface with 9K MTU. + # GSO packet will be chunked. MSS is 8960. GSO # size will be min(MSS, 2048 - 14 - 20) vlib_buffer_t size # self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [9000, 0, 0, 0]) - self.vapi.sw_interface_set_mtu(self.pg4.sw_if_index, [9000, 0, 0, 0]) p44 = ( Ether(src=self.pg4.remote_mac, dst=self.pg4.local_mac) / IP(src=self.pg4.remote_ip4, dst=self.pg1.remote_ip4) @@ -372,6 +404,39 @@ def test_gso(self): size += payload_len self.assertEqual(size, 65200 * 5) + # + # Send jumbo frame with gso enabled only on input interface with 9K MTU. + # DF bit is unset. GSO packet will be fragmented. + # + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [8000, 0, 0, 0]) + + rxs = self.send_and_expect(self.pg4, 5 * [p44], self.pg1, 165) + size = 0 + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) + self.assertEqual(rx[IP].src, self.pg4.remote_ip4) + self.assertEqual(rx[IP].dst, self.pg1.remote_ip4) + self.assert_ip_checksum_valid(rx) + self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0)) + size += rx[IP].len - 20 # len - 20 (IP4) + size -= 20 * 5 # TCP header + self.assertEqual(size, 65200 * 5) + + rxs = self.send_and_expect_some(self.pg4, 5 * [p64], self.pg4, 5) + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg4.local_mac) + self.assertEqual(rx[Ether].dst, self.pg4.remote_mac) + self.assertEqual(rx[IPv6].src, self.pg4.local_ip6) + self.assertEqual(rx[IPv6].dst, self.pg4.remote_ip6) + self.assertEqual(rx[IPv6].plen, 1240) # MTU - IPv6 header + self.assertEqual(ipv6nh[rx[IPv6].nh], "ICMPv6") + self.assertEqual(rx[ICMPv6PacketTooBig].mtu, 8000) + self.assertEqual(rx[IPerror6].src, self.pg4.remote_ip6) + self.assertEqual(rx[IPerror6].dst, self.pg1.remote_ip6) + self.assertEqual(rx[IPerror6].plen - 20, 65200) + + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, [1500, 0, 0, 0]) self.vapi.feature_gso_enable_disable( sw_if_index=self.pg0.sw_if_index, enable_disable=0 ) @@ -384,7 +449,6 @@ def test_gso(self): ) def test_gso_vxlan(self): """GSO VXLAN test""" - self.logger.info(self.vapi.cli("sh int addr")) # # Send jumbo frame with gso enabled only on input interface and # create VXLAN VTEP on VPP pg0, and put vxlan_tunnel0 and pg2 @@ -555,9 +619,81 @@ def test_gso_vxlan(self): sw_if_index=self.pg0.sw_if_index, enable_disable=0 ) + # + # IPv4/IPv4 - VXLAN - Fragmented test + # Send jumbo frame with gso enabled only on input interface and + # create VXLAN VTEP on VPP pg1, and put vxlan_tunnel and pg2 + # into BD. + # Packets will be fragmented as + # gso_size (1460) + headers size (50 vxlan encap + 20 inner IPv4 + 20 TCP) + # is larger than MTU of the output interface (1500). + # + + self.vxlan3.add_vpp_config() + self.vapi.sw_interface_set_l2_bridge( + rx_sw_if_index=self.vxlan3.sw_if_index, bd_id=self.single_tunnel_bd2 + ) + self.vapi.sw_interface_set_l2_bridge( + rx_sw_if_index=self.pg2.sw_if_index, bd_id=self.single_tunnel_bd2 + ) + self.vapi.feature_gso_enable_disable( + sw_if_index=self.pg1.sw_if_index, enable_disable=1 + ) + + p67 = ( + Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac) + / IP(src=self.pg2.remote_ip4, dst="172.16.3.3") + / TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 65200) + ) + + rxs = self.send_and_expect(self.pg2, 5 * [p67], self.pg1, 225) + size = 0 + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) + self.assertEqual(rx[IP].src, self.pg1.local_ip4) + self.assertEqual(rx[IP].dst, self.pg1.remote_ip4) + self.assert_ip_checksum_valid(rx) + self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0)) + size += rx[IP].len - 20 # outer IP len + size -= ( + 8 + 8 + 14 + 20 + 20 + ) * 5 # UDP header + VXLAN header + Ethernet header + inner IP header + TCP header + self.assertEqual(size, 65200 * 5) + + assembled_pkt = defragment(rxs) + for rx in assembled_pkt: + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) + self.assertEqual(rx[IP].src, self.pg1.local_ip4) + self.assertEqual(rx[IP].dst, self.pg1.remote_ip4) + self.assertEqual( + rx[IP].len, 65290 + ) # 65200 + 50 (VXLAN encap) + 20 (IP) + 20 (TCP) + self.assertEqual( + rx[UDP].len, 65270 + ) # 65200 + 50 (VXLAN encap) - 20 (outer IP) + 20 (IP) + 20 (TCP) + self.assert_ip_checksum_valid(rx) + self.assert_udp_checksum_valid(rx, ignore_zero_checksum=True) + self.assertEqual(rx[VXLAN].vni, 20) + inner = rx[VXLAN].payload + self.assertEqual(inner[Ether].src, self.pg2.remote_mac) + self.assertEqual(inner[Ether].dst, self.pg2.local_mac) + self.assertEqual(inner[IP].src, self.pg2.remote_ip4) + self.assertEqual(inner[IP].dst, "172.16.3.3") + self.assert_ip_checksum_valid(inner) + self.assert_tcp_checksum_valid(inner) + self.assertEqual(inner[IP].len - 20 - 20, 65200) + + self.vxlan3.remove_vpp_config() + + self.vapi.feature_gso_enable_disable( + sw_if_index=self.pg1.sw_if_index, enable_disable=0 + ) + def test_gso_ipip(self): """GSO IPIP test""" - self.logger.info(self.vapi.cli("sh int addr")) # # Send jumbo frame with gso enabled only on input interface and # create IPIP tunnel on VPP pg0. @@ -569,11 +705,11 @@ def test_gso_ipip(self): # # enable ipip4 # - self.ipip4.add_vpp_config() + self.ipip4_0.add_vpp_config() # Set interface up and enable IP on it - self.ipip4.admin_up() - self.ipip4.set_unnumbered(self.pg0.sw_if_index) + self.ipip4_0.admin_up() + self.ipip4_0.set_unnumbered(self.pg0.sw_if_index) # Add IPv4 routes via tunnel interface self.ip4_via_ip4_tunnel = VppIpRoute( @@ -583,7 +719,7 @@ def test_gso_ipip(self): [ VppRoutePath( "0.0.0.0", - self.ipip4.sw_if_index, + self.ipip4_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP4, ) ], @@ -627,7 +763,7 @@ def test_gso_ipip(self): [ VppRoutePath( "::", - self.ipip4.sw_if_index, + self.ipip4_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) ], @@ -671,7 +807,7 @@ def test_gso_ipip(self): sw_if_index=self.pg0.sw_if_index, enable_disable=0 ) self.vapi.feature_gso_enable_disable( - sw_if_index=self.ipip4.sw_if_index, enable_disable=1 + sw_if_index=self.ipip4_0.sw_if_index, enable_disable=1 ) rxs = self.send_and_expect(self.pg2, 5 * [p47], self.pg0, 225) @@ -698,11 +834,11 @@ def test_gso_ipip(self): # disable ipip4 # self.vapi.feature_gso_enable_disable( - sw_if_index=self.ipip4.sw_if_index, enable_disable=0 + sw_if_index=self.ipip4_0.sw_if_index, enable_disable=0 ) self.ip4_via_ip4_tunnel.remove_vpp_config() self.ip6_via_ip4_tunnel.remove_vpp_config() - self.ipip4.remove_vpp_config() + self.ipip4_0.remove_vpp_config() # # enable ipip6 @@ -710,11 +846,11 @@ def test_gso_ipip(self): self.vapi.feature_gso_enable_disable( sw_if_index=self.pg0.sw_if_index, enable_disable=1 ) - self.ipip6.add_vpp_config() + self.ipip6_0.add_vpp_config() # Set interface up and enable IP on it - self.ipip6.admin_up() - self.ipip6.set_unnumbered(self.pg0.sw_if_index) + self.ipip6_0.admin_up() + self.ipip6_0.set_unnumbered(self.pg0.sw_if_index) # Add IPv4 routes via tunnel interface self.ip4_via_ip6_tunnel = VppIpRoute( @@ -724,7 +860,7 @@ def test_gso_ipip(self): [ VppRoutePath( "0.0.0.0", - self.ipip6.sw_if_index, + self.ipip6_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP4, ) ], @@ -767,7 +903,7 @@ def test_gso_ipip(self): [ VppRoutePath( "::", - self.ipip6.sw_if_index, + self.ipip6_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) ], @@ -807,12 +943,135 @@ def test_gso_ipip(self): # self.ip4_via_ip6_tunnel.remove_vpp_config() self.ip6_via_ip6_tunnel.remove_vpp_config() - self.ipip6.remove_vpp_config() + self.ipip6_0.remove_vpp_config() self.vapi.feature_gso_enable_disable( sw_if_index=self.pg0.sw_if_index, enable_disable=0 ) + # + # IPIP - Fragmented test + # enable ipip4 + # + self.vapi.feature_gso_enable_disable( + sw_if_index=self.pg1.sw_if_index, enable_disable=1 + ) + self.ipip4_1.add_vpp_config() + + # Set interface up and enable IP on it + self.ipip4_1.admin_up() + self.ipip4_1.set_unnumbered(self.pg1.sw_if_index) + + # Add IPv4 routes via tunnel interface + self.ip4_via_ip4_tunnel_1 = VppIpRoute( + self, + "172.16.10.0", + 24, + [ + VppRoutePath( + "0.0.0.0", + self.ipip4_1.sw_if_index, + proto=FibPathProto.FIB_PATH_NH_PROTO_IP4, + ) + ], + ) + self.ip4_via_ip4_tunnel_1.add_vpp_config() + + p47_1 = ( + Ether(src=self.pg2.remote_mac, dst=self.pg2.local_mac) + / IP(src=self.pg2.remote_ip4, dst="172.16.10.3") + / TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 65200) + ) + + rxs = self.send_and_expect(self.pg2, 5 * [p47_1], self.pg1, 225) + size = 0 + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) + self.assertEqual(rx[IP].src, self.pg1.local_ip4) + self.assertEqual(rx[IP].dst, self.pg1.remote_ip4) + self.assert_ip_checksum_valid(rx) + self.assertTrue((rx[IP].flags == "MF") or (rx[IP].frag != 0)) + size += rx[IP].len - 20 # outer IP len + size -= (20 + 20) * 5 # inner IP header + TCP header + self.assertEqual(size, 65200 * 5) + + assembled_pkt = defragment(rxs) + for rx in assembled_pkt: + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) + self.assertEqual(rx[IP].src, self.pg1.local_ip4) + self.assertEqual(rx[IP].dst, self.pg1.remote_ip4) + self.assertEqual( + rx[IP].len, 65260 + ) # 65200 + 20 (outer IP) + 20 (inner IP) + 20 (TCP) + self.assert_ip_checksum_valid(rx) + inner = rx[IP].payload + self.assertEqual(inner[IP].src, self.pg2.remote_ip4) + self.assertEqual(inner[IP].dst, "172.16.10.3") + self.assert_ip_checksum_valid(inner) + self.assert_tcp_checksum_valid(inner) + self.assertEqual(inner[IP].len - 20 - 20, 65200) + + self.ip4_via_ip4_tunnel_1.remove_vpp_config() + self.ipip4_1.remove_vpp_config() + + self.vapi.feature_gso_enable_disable( + sw_if_index=self.pg1.sw_if_index, enable_disable=0 + ) + + # + # enable ipip6 + # + self.vapi.feature_gso_enable_disable( + sw_if_index=self.pg1.sw_if_index, enable_disable=1 + ) + self.ipip6_1.add_vpp_config() + # Set interface up and enable IP on it + self.ipip6_1.admin_up() + self.ipip6_1.set_unnumbered(self.pg1.sw_if_index) + # Add IPv6 routes via tunnel interface + self.ip6_via_ip6_tunnel_1 = VppIpRoute( + self, + "fd01:10::", + 64, + [ + VppRoutePath( + "::", + self.ipip6_1.sw_if_index, + proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, + ) + ], + ) + self.ip6_via_ip6_tunnel_1.add_vpp_config() + + p68_1 = ( + Ether(src=self.pg3.remote_mac, dst=self.pg3.local_mac) + / IPv6(src=self.pg3.remote_ip6, dst="fd01:10::3") + / TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 65200) + ) + + rxs = self.send_and_expect(self.pg3, 5 * [p68_1], self.pg1, 230) + size = 0 + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg1.local_mac) + self.assertEqual(rx[Ether].dst, self.pg1.remote_mac) + self.assertEqual(rx[IPv6].src, self.pg1.local_ip6) + self.assertEqual(rx[IPv6].dst, self.pg1.remote_ip6) + self.assertEqual(ipv6nh[rx[IPv6].nh], "Fragment Header") + size += rx[IPv6].plen - 8 # remove fragment header size + size -= (40 + 20) * 5 # inner IPv6 header + TCP header + self.assertEqual(size, 65200 * 5) + + self.ip6_via_ip6_tunnel_1.remove_vpp_config() + self.ipip6_1.remove_vpp_config() + + self.vapi.feature_gso_enable_disable( + sw_if_index=self.pg1.sw_if_index, enable_disable=0 + ) + def test_gso_gre(self): """GSO GRE test""" # @@ -1019,9 +1278,9 @@ def test_gso_ipsec(self): # # enable ipip4 # - self.ipip4.add_vpp_config() + self.ipip4_0.add_vpp_config() self.vapi.feature_gso_enable_disable( - sw_if_index=self.ipip4.sw_if_index, enable_disable=1 + sw_if_index=self.ipip4_0.sw_if_index, enable_disable=1 ) # Add IPv4 routes via tunnel interface @@ -1032,7 +1291,7 @@ def test_gso_ipsec(self): [ VppRoutePath( "0.0.0.0", - self.ipip4.sw_if_index, + self.ipip4_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP4, ) ], @@ -1042,7 +1301,7 @@ def test_gso_ipsec(self): # IPSec config self.ipv4_params = IPsecIPv4Params() self.encryption_type = ESP - config_tun_params(self.ipv4_params, self.encryption_type, self.ipip4) + config_tun_params(self.ipv4_params, self.encryption_type, self.ipip4_0) self.tun_sa_in_v4 = VppIpsecSA( self, @@ -1069,14 +1328,14 @@ def test_gso_ipsec(self): self.tun_sa_out_v4.add_vpp_config() self.tun_protect_v4 = VppIpsecTunProtect( - self, self.ipip4, self.tun_sa_out_v4, [self.tun_sa_in_v4] + self, self.ipip4_0, self.tun_sa_out_v4, [self.tun_sa_in_v4] ) self.tun_protect_v4.add_vpp_config() # Set interface up and enable IP on it - self.ipip4.admin_up() - self.ipip4.set_unnumbered(self.pg0.sw_if_index) + self.ipip4_0.admin_up() + self.ipip4_0.set_unnumbered(self.pg0.sw_if_index) # # IPv4/IPv4 - IPSEC @@ -1110,7 +1369,7 @@ def test_gso_ipsec(self): [ VppRoutePath( "::", - self.ipip4.sw_if_index, + self.ipip4_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) ], @@ -1149,20 +1408,20 @@ def test_gso_ipsec(self): # # disable ipip4 # - self.vapi.feature_gso_enable_disable(self.ipip4.sw_if_index, enable_disable=0) + self.vapi.feature_gso_enable_disable(self.ipip4_0.sw_if_index, enable_disable=0) self.ip4_via_ip4_tunnel.remove_vpp_config() self.ip6_via_ip4_tunnel.remove_vpp_config() - self.ipip4.remove_vpp_config() + self.ipip4_0.remove_vpp_config() # # enable ipip6 # - self.ipip6.add_vpp_config() - self.vapi.feature_gso_enable_disable(self.ipip6.sw_if_index, enable_disable=1) + self.ipip6_0.add_vpp_config() + self.vapi.feature_gso_enable_disable(self.ipip6_0.sw_if_index, enable_disable=1) # Set interface up and enable IP on it - self.ipip6.admin_up() - self.ipip6.set_unnumbered(self.pg0.sw_if_index) + self.ipip6_0.admin_up() + self.ipip6_0.set_unnumbered(self.pg0.sw_if_index) # Add IPv4 routes via tunnel interface self.ip4_via_ip6_tunnel = VppIpRoute( @@ -1172,7 +1431,7 @@ def test_gso_ipsec(self): [ VppRoutePath( "0.0.0.0", - self.ipip6.sw_if_index, + self.ipip6_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP4, ) ], @@ -1182,7 +1441,7 @@ def test_gso_ipsec(self): # IPSec config self.ipv6_params = IPsecIPv6Params() self.encryption_type = ESP - config_tun_params(self.ipv6_params, self.encryption_type, self.ipip6) + config_tun_params(self.ipv6_params, self.encryption_type, self.ipip6_0) self.tun_sa_in_v6 = VppIpsecSA( self, self.ipv6_params.scapy_tun_sa_id, @@ -1208,7 +1467,7 @@ def test_gso_ipsec(self): self.tun_sa_out_v6.add_vpp_config() self.tun_protect_v6 = VppIpsecTunProtect( - self, self.ipip6, self.tun_sa_out_v6, [self.tun_sa_in_v6] + self, self.ipip6_0, self.tun_sa_out_v6, [self.tun_sa_in_v6] ) self.tun_protect_v6.add_vpp_config() @@ -1245,7 +1504,7 @@ def test_gso_ipsec(self): [ VppRoutePath( "::", - self.ipip6.sw_if_index, + self.ipip6_0.sw_if_index, proto=FibPathProto.FIB_PATH_NH_PROTO_IP6, ) ], @@ -1287,7 +1546,7 @@ def test_gso_ipsec(self): # self.ip4_via_ip6_tunnel.remove_vpp_config() self.ip6_via_ip6_tunnel.remove_vpp_config() - self.ipip6.remove_vpp_config() + self.ipip6_0.remove_vpp_config() self.vapi.feature_gso_enable_disable(self.pg0.sw_if_index, enable_disable=0) From 7b830f7d72ecdfb166e8d5a8c690686488971420 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Fri, 27 Jun 2025 14:18:43 +0200 Subject: [PATCH 110/313] hs-test: individual http1 suite - Http1Suite is the same as NoTopoSuite with some things removed - renamed http related files Type: test Change-Id: Ifb0ff0ba9a1151ff025faa6edd208f183830d2f5 Signed-off-by: Adrian Villin --- .../hs-test/{http_test.go => http1_test.go} | 158 +++++++------- extras/hs-test/http2_test.go | 12 +- extras/hs-test/infra/suite_http1.go | 204 ++++++++++++++++++ .../infra/{suite_h2.go => suite_http2.go} | 30 +-- 4 files changed, 304 insertions(+), 100 deletions(-) rename extras/hs-test/{http_test.go => http1_test.go} (94%) create mode 100644 extras/hs-test/infra/suite_http1.go rename extras/hs-test/infra/{suite_h2.go => suite_http2.go} (94%) diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http1_test.go similarity index 94% rename from extras/hs-test/http_test.go rename to extras/hs-test/http1_test.go index 0ed8b298de..e3a7002337 100644 --- a/extras/hs-test/http_test.go +++ b/extras/hs-test/http1_test.go @@ -25,7 +25,7 @@ import ( func init() { RegisterVethTests(HttpCliTest, HttpCliConnectErrorTest, HttpCliTlsTest) RegisterSoloVethTests(HttpClientGetMemLeakTest) - RegisterNoTopoTests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest, + RegisterHttp1Tests(HeaderServerTest, HttpPersistentConnectionTest, HttpPipeliningTest, HttpStaticMovedTest, HttpStaticNotFoundTest, HttpCliMethodNotAllowedTest, HttpAbsoluteFormUriTest, HttpCliBadRequestTest, HttpStaticBuildInUrlGetIfStatsTest, HttpStaticBuildInUrlPostIfStatsTest, HttpInvalidRequestLineTest, HttpMethodNotImplementedTest, HttpInvalidHeadersTest, HttpStaticPostTest, @@ -34,15 +34,15 @@ func init() { HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathSanitizationTest, HttpUriDecodeTest, HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest, - HttpClientGetTlsNoRespBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, + HttpClientGetTlsNoRespBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest, HttpClientGetRepeatTest, HttpClientPostRepeatTest, HttpIgnoreH2UpgradeTest, HttpInvalidAuthorityFormUriTest, HttpHeaderErrorConnectionDropTest, HttpClientInvalidHeaderNameTest, HttpStaticHttp1OnlyTest, HttpTimerSessionDisable, HttpClientBodySizeTest, HttpStaticRedirectTest, HttpClientNoPrintTest, HttpClientChunkedDownloadTest, HttpClientPostRejectedTest) - RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, + RegisterHttp1SoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest, PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest, PromConsecutiveConnectionsTest, HttpGetTpsTlsTest, HttpPostTpsTlsTest) - RegisterNoTopoMWTests(HttpClientGetRepeatMWTest, HttpClientPtrGetRepeatMWTest) + RegisterHttp1MWTests(HttpClientGetRepeatMWTest, HttpClientPtrGetRepeatMWTest) RegisterNoTopo6SoloTests(HttpClientGetResponseBody6Test, HttpClientGetTlsResponseBody6Test) } @@ -66,11 +66,11 @@ func httpDownloadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data in experiment.RecordValue("Download Speed", (float64(resp.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2)) } -func HttpGetTpsInterruptModeTest(s *NoTopoSuite) { +func HttpGetTpsInterruptModeTest(s *Http1Suite) { HttpGetTpsTest(s) } -func HttpGetTpsTest(s *NoTopoSuite) { +func HttpGetTpsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http url := "http://" + serverAddress + "/test_file_10M" @@ -80,7 +80,7 @@ func HttpGetTpsTest(s *NoTopoSuite) { s.RunBenchmark("HTTP tps download 10M", 10, 0, httpDownloadBenchmark, url) } -func HttpGetTpsTlsTest(s *NoTopoSuite) { +func HttpGetTpsTlsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http url := "https://" + serverAddress + "/test_file_10M" @@ -109,11 +109,11 @@ func httpUploadBenchmark(s *HstSuite, experiment *gmeasure.Experiment, data inte experiment.RecordValue("Upload Speed", (float64(req.ContentLength)/1024/1024)/duration.Seconds(), gmeasure.Units("MB/s"), gmeasure.Precision(2)) } -func HttpPostTpsInterruptModeTest(s *NoTopoSuite) { +func HttpPostTpsInterruptModeTest(s *Http1Suite) { HttpPostTpsTest(s) } -func HttpPostTpsTest(s *NoTopoSuite) { +func HttpPostTpsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http url := "http://" + serverAddress + "/test_file_10M" @@ -123,7 +123,7 @@ func HttpPostTpsTest(s *NoTopoSuite) { s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } -func HttpPostTpsTlsTest(s *NoTopoSuite) { +func HttpPostTpsTlsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http url := "https://" + serverAddress + "/test_file_10M" @@ -133,7 +133,7 @@ func HttpPostTpsTlsTest(s *NoTopoSuite) { s.RunBenchmark("HTTP tps upload 10M", 10, 0, httpUploadBenchmark, url) } -func HttpPersistentConnectionTest(s *NoTopoSuite) { +func HttpPersistentConnectionTest(s *Http1Suite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.Containers.Vpp.VppInstance @@ -198,7 +198,7 @@ func HttpPersistentConnectionTest(s *NoTopoSuite) { s.AssertEqual(o1, o2) } -func HttpPipeliningTest(s *NoTopoSuite) { +func HttpPipeliningTest(s *Http1Suite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.Containers.Vpp.VppInstance @@ -233,7 +233,7 @@ func HttpPipeliningTest(s *NoTopoSuite) { s.AssertMatchError(err, os.ErrDeadlineExceeded, "second request response received") } -func HttpStaticPostTest(s *NoTopoSuite) { +func HttpStaticPostTest(s *Http1Suite) { // testing url handler app do not support multi-thread s.SkipIfMultiWorker() vpp := s.Containers.Vpp.VppInstance @@ -314,7 +314,7 @@ func HttpCliConnectErrorTest(s *VethsSuite) { s.AssertContains(o, "failed to connect") } -func HttpClientTest(s *NoTopoSuite) { +func HttpClientTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress) @@ -339,7 +339,7 @@ func HttpClientTest(s *NoTopoSuite) { s.AssertContains(o, "", " not found in the result!") } -func HttpClientChunkedDownloadTest(s *NoTopoSuite) { +func HttpClientChunkedDownloadTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress) @@ -364,7 +364,7 @@ func HttpClientChunkedDownloadTest(s *NoTopoSuite) { s.AssertContains(file_contents, response) } -func HttpClientBodySizeTest(s *NoTopoSuite) { +func HttpClientBodySizeTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress) @@ -387,7 +387,7 @@ func HttpClientBodySizeTest(s *NoTopoSuite) { s.AssertContains(o, ", read total 38 bytes", "client retrieved invalid amount of bytes!") } -func HttpClientInvalidHeaderNameTest(s *NoTopoSuite) { +func HttpClientInvalidHeaderNameTest(s *Http1Suite) { serverAddress := s.HostAddr() l, err := net.Listen("tcp", serverAddress+":80") s.AssertNil(err, fmt.Sprint(err)) @@ -434,7 +434,7 @@ func HttpClientInvalidHeaderNameTest(s *NoTopoSuite) { s.AssertEqual(true, httpCleanupDone, "HTTP not cleanup") } -func HttpClientErrRespTest(s *NoTopoSuite) { +func HttpClientErrRespTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress) @@ -456,7 +456,7 @@ func HttpClientErrRespTest(s *NoTopoSuite) { s.AssertContains(o, "404: Not Found", "error not found in the result!") } -func HttpClientPostFormTest(s *NoTopoSuite) { +func HttpClientPostFormTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http body := "field1=value1&field2=value2" @@ -484,7 +484,7 @@ func HttpClientPostFormTest(s *NoTopoSuite) { s.AssertContains(o, "200 OK") } -func HttpClientNoPrintTest(s *NoTopoSuite) { +func HttpClientNoPrintTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http server := ghttp.NewUnstartedServer() l, err := net.Listen("tcp", serverAddress) @@ -508,24 +508,24 @@ func HttpClientNoPrintTest(s *NoTopoSuite) { s.AssertNotContains(o, "", " found in the result!") } -func HttpClientGetResponseBodyTest(s *NoTopoSuite) { +func HttpClientGetResponseBodyTest(s *Http1Suite) { response := "hello world" size := len(response) httpClientGet(s, response, size, "http") } -func HttpClientGet128kbResponseTest(s *NoTopoSuite) { +func HttpClientGet128kbResponseTest(s *Http1Suite) { response := strings.Repeat("a", 128*1024) size := len(response) httpClientGet(s, response, size, "http") } -func HttpClientGetTlsNoRespBodyTest(s *NoTopoSuite) { +func HttpClientGetTlsNoRespBodyTest(s *Http1Suite) { response := "" httpClientGet(s, response, 0, "https") } -func httpClientGet(s *NoTopoSuite, response string, size int, proto string) { +func httpClientGet(s *Http1Suite, response string, size int, proto string) { var l net.Listener var err error vpp := s.Containers.Vpp.VppInstance @@ -639,27 +639,27 @@ func httpClientGet6(s *NoTopo6Suite, response string, size int, proto string) { s.AssertContains(file_contents, response) } -func HttpClientGetRepeatMWTest(s *NoTopoSuite) { +func HttpClientGetRepeatMWTest(s *Http1Suite) { s.CpusPerVppContainer = 3 s.SetupTest() httpClientRepeat(s, "", "sessions 2") } -func HttpClientPtrGetRepeatMWTest(s *NoTopoSuite) { +func HttpClientPtrGetRepeatMWTest(s *Http1Suite) { s.CpusPerVppContainer = 3 s.SetupTest() httpClientRepeat(s, "", "use-ptr sessions 2") } -func HttpClientGetRepeatTest(s *NoTopoSuite) { +func HttpClientGetRepeatTest(s *Http1Suite) { httpClientRepeat(s, "", "") } -func HttpClientPostRepeatTest(s *NoTopoSuite) { +func HttpClientPostRepeatTest(s *Http1Suite) { httpClientRepeat(s, "post", "") } -func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { +func httpClientRepeat(s *Http1Suite, requestMethod string, clientArgs string) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.Interfaces.Tap.Ip4AddressString() + ":" + s.Ports.NginxServer replyCountInt := 0 @@ -721,7 +721,7 @@ func httpClientRepeat(s *NoTopoSuite, requestMethod string, clientArgs string) { s.AssertEqual(repeatAmount, replyCountInt) } -func HttpClientGetTimeout(s *NoTopoSuite) { +func HttpClientGetTimeout(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http vpp := s.Containers.Vpp.VppInstance @@ -748,7 +748,7 @@ func HttpClientGetTimeout(s *NoTopoSuite) { s.AssertContains(o, "error: timeout") } -func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) { +func httpClientPostFile(s *Http1Suite, usePtr bool, fileSize int) { serverAddress := s.HostAddr() + ":" + s.Ports.Http vpp := s.Containers.Vpp.VppInstance fileName := "/tmp/test_file.txt" @@ -781,15 +781,15 @@ func httpClientPostFile(s *NoTopoSuite, usePtr bool, fileSize int) { s.AssertContains(o, "200 OK") } -func HttpClientPostFileTest(s *NoTopoSuite) { +func HttpClientPostFileTest(s *Http1Suite) { httpClientPostFile(s, false, 32768) } -func HttpClientPostFilePtrTest(s *NoTopoSuite) { +func HttpClientPostFilePtrTest(s *Http1Suite) { httpClientPostFile(s, true, 131072) } -func HttpClientPostRejectedTest(s *NoTopoSuite) { +func HttpClientPostRejectedTest(s *Http1Suite) { serverAddress := s.HostAddr() + ":" + s.Ports.Http vpp := s.Containers.Vpp.VppInstance fileName := "/tmp/test_file.txt" @@ -819,7 +819,7 @@ func HttpClientPostRejectedTest(s *NoTopoSuite) { s.Log(vpp.Vppctl("show session verbose 2")) } -func HttpStaticPromTest(s *NoTopoSuite) { +func HttpStaticPromTest(s *Http1Suite) { query := "stats.prom" vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http @@ -840,7 +840,7 @@ func HttpStaticPromTest(s *NoTopoSuite) { s.AssertNil(err, fmt.Sprint(err)) } -func promReq(s *NoTopoSuite, url string, timeout time.Duration) { +func promReq(s *Http1Suite, url string, timeout time.Duration) { client := NewHttpClient(timeout, false) req, err := http.NewRequest("GET", url, nil) s.AssertNil(err, fmt.Sprint(err)) @@ -852,13 +852,13 @@ func promReq(s *NoTopoSuite, url string, timeout time.Duration) { s.AssertNil(err, fmt.Sprint(err)) } -func promReqWg(s *NoTopoSuite, url string, wg *sync.WaitGroup) { +func promReqWg(s *Http1Suite, url string, wg *sync.WaitGroup) { defer GinkgoRecover() defer wg.Done() promReq(s, url, defaultHttpTimeout) } -func PromConcurrentConnectionsTest(s *NoTopoSuite) { +func PromConcurrentConnectionsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http url := "http://" + serverAddress + "/stats.prom" @@ -876,7 +876,7 @@ func PromConcurrentConnectionsTest(s *NoTopoSuite) { s.Log(vpp.Vppctl("show session verbose proto http")) } -func PromConsecutiveConnectionsTest(s *NoTopoSuite) { +func PromConsecutiveConnectionsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http url := "http://" + serverAddress + "/stats.prom" @@ -890,7 +890,7 @@ func PromConsecutiveConnectionsTest(s *NoTopoSuite) { } } -func PromMemLeakTest(s *NoTopoSuite) { +func PromMemLeakTest(s *Http1Suite) { s.SkipUnlessLeakCheck() vpp := s.Containers.Vpp.VppInstance @@ -958,7 +958,7 @@ func HttpClientGetMemLeakTest(s *VethsSuite) { clientVpp.MemLeakCheck(traces1, traces2) } -func HttpClientPostMemLeakTest(s *NoTopoSuite) { +func HttpClientPostMemLeakTest(s *Http1Suite) { s.SkipUnlessLeakCheck() serverAddress := s.HostAddr() + ":" + s.Ports.Http @@ -1007,7 +1007,7 @@ func HttpClientPostMemLeakTest(s *NoTopoSuite) { vpp.MemLeakCheck(traces1, traces2) } -func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { +func HttpInvalidClientRequestMemLeakTest(s *Http1Suite) { s.SkipUnlessLeakCheck() vpp := s.Containers.Vpp.VppInstance @@ -1041,7 +1041,7 @@ func HttpInvalidClientRequestMemLeakTest(s *NoTopoSuite) { } -func runWrkPerf(s *NoTopoSuite) { +func runWrkPerf(s *Http1Suite) { nConnections := 1000 serverAddress := s.VppAddr() + ":" + s.Ports.Http @@ -1054,7 +1054,7 @@ func runWrkPerf(s *NoTopoSuite) { s.AssertEmpty(err, "err: '%s'", err) } -func HttpStaticFileHandlerWrkTest(s *NoTopoSuite) { +func HttpStaticFileHandlerWrkTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) @@ -1065,7 +1065,7 @@ func HttpStaticFileHandlerWrkTest(s *NoTopoSuite) { runWrkPerf(s) } -func HttpStaticUrlHandlerWrkTest(s *NoTopoSuite) { +func HttpStaticUrlHandlerWrkTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers private-segment-size 256m")) @@ -1073,15 +1073,15 @@ func HttpStaticUrlHandlerWrkTest(s *NoTopoSuite) { runWrkPerf(s) } -func HttpStaticFileHandlerDefaultMaxAgeTest(s *NoTopoSuite) { +func HttpStaticFileHandlerDefaultMaxAgeTest(s *Http1Suite) { HttpStaticFileHandlerTestFunction(s, "default") } -func HttpStaticFileHandlerTest(s *NoTopoSuite) { +func HttpStaticFileHandlerTest(s *Http1Suite) { HttpStaticFileHandlerTestFunction(s, "123") } -func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) { +func HttpStaticFileHandlerTestFunction(s *Http1Suite, max_age string) { var maxAgeFormatted string if max_age == "default" { maxAgeFormatted = "" @@ -1148,7 +1148,7 @@ func HttpStaticFileHandlerTestFunction(s *NoTopoSuite, max_age string) { s.AssertContains(o, "page.html") } -func HttpStaticPathSanitizationTest(s *NoTopoSuite) { +func HttpStaticPathSanitizationTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) vpp.Container.Exec(false, "mkdir -p "+"/tmp/secret_folder") @@ -1193,7 +1193,7 @@ func HttpStaticPathSanitizationTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Location", "http://"+serverAddress+"/index.html") } -func HttpStaticMovedTest(s *NoTopoSuite) { +func HttpStaticMovedTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath+"/tmp.aaa") err := vpp.Container.CreateFile(wwwRootPath+"/tmp.aaa/index.html", "

Hello

") @@ -1215,7 +1215,7 @@ func HttpStaticMovedTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } -func HttpStaticRedirectTest(s *NoTopoSuite) { +func HttpStaticRedirectTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) err := vpp.Container.CreateFile(wwwRootPath+"/index.html", "

Hello

") @@ -1241,7 +1241,7 @@ func HttpStaticRedirectTest(s *NoTopoSuite) { s.AssertContains(string(reply), expectedLocation) } -func HttpStaticNotFoundTest(s *NoTopoSuite) { +func HttpStaticNotFoundTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance vpp.Container.Exec(false, "mkdir -p "+wwwRootPath) serverAddress := s.VppAddr() + ":" + s.Ports.Http @@ -1260,7 +1260,7 @@ func HttpStaticNotFoundTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } -func HttpCliMethodNotAllowedTest(s *NoTopoSuite) { +func HttpCliMethodNotAllowedTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1278,7 +1278,7 @@ func HttpCliMethodNotAllowedTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } -func HttpCliBadRequestTest(s *NoTopoSuite) { +func HttpCliBadRequestTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1295,7 +1295,7 @@ func HttpCliBadRequestTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } -func HttpStaticHttp1OnlyTest(s *NoTopoSuite) { +func HttpStaticHttp1OnlyTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers http1-only debug")) @@ -1314,7 +1314,7 @@ func HttpStaticHttp1OnlyTest(s *NoTopoSuite) { s.AssertContains(string(data), "version") } -func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) { +func HttpStaticBuildInUrlGetVersionTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers debug")) @@ -1339,7 +1339,7 @@ func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } -func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) { +func HttpStaticBuildInUrlGetVersionVerboseTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) @@ -1363,7 +1363,7 @@ func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } -func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) { +func HttpStaticBuildInUrlGetIfListTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) @@ -1383,7 +1383,7 @@ func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } -func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) { +func HttpStaticBuildInUrlGetIfStatsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) @@ -1404,14 +1404,14 @@ func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } -func validatePostInterfaceStats(s *NoTopoSuite, data string) { +func validatePostInterfaceStats(s *Http1Suite, data string) { s.AssertContains(data, "interface_stats") s.AssertContains(data, s.VppIfName()) s.AssertNotContains(data, "error") s.AssertNotContains(data, "local0") } -func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) { +func HttpStaticBuildInUrlPostIfStatsTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) @@ -1432,7 +1432,7 @@ func HttpStaticBuildInUrlPostIfStatsTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "application/json") } -func HttpStaticMacTimeTest(s *NoTopoSuite) { +func HttpStaticMacTimeTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) @@ -1458,7 +1458,7 @@ func HttpStaticMacTimeTest(s *NoTopoSuite) { s.AssertEqual(len(resp.Header.Get("Date")), 29) } -func HttpInvalidRequestLineTest(s *NoTopoSuite) { +func HttpInvalidRequestLineTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1512,7 +1512,7 @@ func HttpInvalidRequestLineTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request") } -func HttpTimerSessionDisable(s *NoTopoSuite) { +func HttpTimerSessionDisable(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress)) @@ -1529,7 +1529,7 @@ func HttpTimerSessionDisable(s *NoTopoSuite) { s.AssertContains(resp, "node http-timer-process, type process, state \"any wait\"") } -func HttpRequestLineTest(s *NoTopoSuite) { +func HttpRequestLineTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1540,7 +1540,7 @@ func HttpRequestLineTest(s *NoTopoSuite) { s.AssertContains(resp, "", "html content not found") } -func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { +func HttpInvalidTargetSyntaxTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug")) @@ -1604,7 +1604,7 @@ func HttpInvalidTargetSyntaxTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request") } -func HttpInvalidContentLengthTest(s *NoTopoSuite) { +func HttpInvalidContentLengthTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1627,7 +1627,7 @@ func HttpInvalidContentLengthTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "Content-Length value exceeded U64_MAX") } -func HttpContentLengthTest(s *NoTopoSuite) { +func HttpContentLengthTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug max-body-size 12")) @@ -1649,7 +1649,7 @@ func HttpContentLengthTest(s *NoTopoSuite) { validatePostInterfaceStats(s, resp) } -func HttpHeaderErrorConnectionDropTest(s *NoTopoSuite) { +func HttpHeaderErrorConnectionDropTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug max-body-size 12")) @@ -1668,7 +1668,7 @@ func HttpHeaderErrorConnectionDropTest(s *NoTopoSuite) { _, err = conn.Read(check) s.AssertEqual(err, io.EOF) } -func HttpMethodNotImplementedTest(s *NoTopoSuite) { +func HttpMethodNotImplementedTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1685,7 +1685,7 @@ func HttpMethodNotImplementedTest(s *NoTopoSuite) { s.AssertHttpContentLength(resp, int64(0)) } -func HttpVersionNotSupportedTest(s *NoTopoSuite) { +func HttpVersionNotSupportedTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1695,7 +1695,7 @@ func HttpVersionNotSupportedTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 505 HTTP Version Not Supported") } -func HttpUriDecodeTest(s *NoTopoSuite) { +func HttpUriDecodeTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1715,7 +1715,7 @@ func HttpUriDecodeTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") } -func HttpAbsoluteFormUriTest(s *NoTopoSuite) { +func HttpAbsoluteFormUriTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1725,7 +1725,7 @@ func HttpAbsoluteFormUriTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 200 OK") } -func HttpInvalidAuthorityFormUriTest(s *NoTopoSuite) { +func HttpInvalidAuthorityFormUriTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("test proxy server fifo-size 512k server-uri http://%s", serverAddress) @@ -1763,7 +1763,7 @@ func HttpInvalidAuthorityFormUriTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "name resolution not supported") } -func HttpHeadersTest(s *NoTopoSuite) { +func HttpHeadersTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1816,7 +1816,7 @@ func HttpHeadersTest(s *NoTopoSuite) { } } -func HttpInvalidHeadersTest(s *NoTopoSuite) { +func HttpInvalidHeadersTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1854,7 +1854,7 @@ func HttpInvalidHeadersTest(s *NoTopoSuite) { s.AssertContains(resp, "HTTP/1.1 400 Bad Request", "empty field value not allowed") } -func HeaderServerTest(s *NoTopoSuite) { +func HeaderServerTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http vpp.Vppctl("http cli server uri http://" + serverAddress) @@ -1871,7 +1871,7 @@ func HeaderServerTest(s *NoTopoSuite) { s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/html") } -func HttpConnTimeoutTest(s *NoTopoSuite) { +func HttpConnTimeoutTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers debug keepalive-timeout 2")) @@ -1901,7 +1901,7 @@ func HttpConnTimeoutTest(s *NoTopoSuite) { s.AssertMatchError(err, io.EOF, "connection not closed by server") } -func HttpIgnoreH2UpgradeTest(s *NoTopoSuite) { +func HttpIgnoreH2UpgradeTest(s *Http1Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Http s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers")) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index a813f414b5..b1d7c6fcb4 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -14,7 +14,7 @@ func init() { RegisterH2MWTests(Http2MultiplexingMWTest) } -func Http2TcpGetTest(s *H2Suite) { +func Http2TcpGetTest(s *Http2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 vpp.Vppctl("http cli server listener add uri tcp://" + serverAddress) @@ -51,7 +51,7 @@ func Http2TcpGetTest(s *H2Suite) { s.AssertNotContains(o, "LISTEN") } -func Http2TcpPostTest(s *H2Suite) { +func Http2TcpPostTest(s *Http2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + " url-handlers max-body-size 20m rx-buff-thresh 20m fifo-size 65k debug 2")) @@ -61,7 +61,7 @@ func Http2TcpPostTest(s *H2Suite) { s.AssertContains(log, "HTTP/2 200") } -func Http2MultiplexingTest(s *H2Suite) { +func Http2MultiplexingTest(s *Http2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") @@ -79,7 +79,7 @@ func Http2MultiplexingTest(s *H2Suite) { s.AssertContains(o, " 0 timeout") } -func Http2MultiplexingMWTest(s *H2Suite) { +func Http2MultiplexingMWTest(s *Http2Suite) { s.CpusPerVppContainer = 3 s.SetupTest() vpp := s.Containers.Vpp.VppInstance @@ -97,7 +97,7 @@ func Http2MultiplexingMWTest(s *H2Suite) { s.AssertContains(o, " 0 timeout") } -func Http2TlsTest(s *H2Suite) { +func Http2TlsTest(s *Http2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 s.Log(vpp.Vppctl("http static server uri tls://" + serverAddress + " url-handlers debug")) @@ -110,7 +110,7 @@ func Http2TlsTest(s *H2Suite) { s.AssertContains(writeOut, "version") } -func Http2ContinuationTxTest(s *H2Suite) { +func Http2ContinuationTxTest(s *Http2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 vpp.Vppctl("http tps uri tcp://" + serverAddress + " no-zc") diff --git a/extras/hs-test/infra/suite_http1.go b/extras/hs-test/infra/suite_http1.go new file mode 100644 index 0000000000..ff2a65f90f --- /dev/null +++ b/extras/hs-test/infra/suite_http1.go @@ -0,0 +1,204 @@ +package hst + +import ( + "reflect" + "runtime" + "strings" + + . "fd.io/hs-test/infra/common" + . "github.com/onsi/ginkgo/v2" +) + +var http1Tests = map[string][]func(s *Http1Suite){} +var http1SoloTests = map[string][]func(s *Http1Suite){} +var http1MWTests = map[string][]func(s *Http1Suite){} + +type Http1Suite struct { + HstSuite + Interfaces struct { + Tap *NetInterface + } + Containers struct { + Vpp *Container + NginxServer *Container + Wrk *Container + } + Ports struct { + NginxServer string + Http string + } +} + +func RegisterHttp1Tests(tests ...func(s *Http1Suite)) { + http1Tests[GetTestFilename()] = tests +} +func RegisterHttp1SoloTests(tests ...func(s *Http1Suite)) { + http1SoloTests[GetTestFilename()] = tests +} +func RegisterHttp1MWTests(tests ...func(s *Http1Suite)) { + http1MWTests[GetTestFilename()] = tests +} + +func (s *Http1Suite) SetupSuite() { + s.HstSuite.SetupSuite() + s.LoadNetworkTopology("tap") + s.LoadContainerTopology("single") + s.Interfaces.Tap = s.GetInterfaceByName("htaphost") + s.Containers.Vpp = s.GetContainerByName("vpp") + s.Containers.NginxServer = s.GetTransientContainerByName("nginx-server") + s.Containers.Wrk = s.GetContainerByName("wrk") + s.Ports.Http = s.GeneratePort() + s.Ports.NginxServer = s.GeneratePort() +} + +func (s *Http1Suite) SetupTest() { + s.HstSuite.SetupTest() + + // Setup test conditions + var sessionConfig Stanza + sessionConfig. + NewStanza("session"). + Append("enable"). + Append("use-app-socket-api") + + if strings.Contains(CurrentSpecReport().LeafNodeText, "InterruptMode") { + sessionConfig.Append("use-private-rx-mqs").Close() + s.Log("**********************INTERRUPT MODE**********************") + } else { + sessionConfig.Close() + } + + vpp, _ := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus, sessionConfig) + + s.AssertNil(vpp.Start()) + s.AssertNil(vpp.CreateTap(s.Interfaces.Tap, false, 1), "failed to create tap interface") + + if *DryRun { + s.LogStartedContainers() + s.Skip("Dry run mode = true") + } +} + +func (s *Http1Suite) TeardownTest() { + defer s.HstSuite.TeardownTest() +} + +// Creates container and config. +func (s *Http1Suite) CreateNginxServer() { + s.AssertNil(s.Containers.NginxServer.Create()) + nginxSettings := struct { + LogPrefix string + Address string + Port string + Timeout int + }{ + LogPrefix: s.Containers.NginxServer.Name, + Address: s.Interfaces.Tap.Ip4AddressString(), + Port: s.Ports.NginxServer, + Timeout: 600, + } + s.Containers.NginxServer.CreateConfigFromTemplate( + "/nginx.conf", + "./resources/nginx/nginx_server.conf", + nginxSettings, + ) +} + +func (s *Http1Suite) VppAddr() string { + return s.Interfaces.Tap.Peer.Ip4AddressString() +} + +func (s *Http1Suite) VppIfName() string { + return s.Interfaces.Tap.Peer.Name() +} + +func (s *Http1Suite) HostAddr() string { + return s.Interfaces.Tap.Ip4AddressString() +} + +var _ = Describe("Http1Suite", Ordered, ContinueOnFailure, func() { + var s Http1Suite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range http1Tests { + 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)) + } + } +}) + +var _ = Describe("Http1SuiteSolo", Ordered, ContinueOnFailure, Serial, func() { + var s Http1Suite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range http1SoloTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) + +var _ = Describe("Http1MWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s Http1Suite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range http1MWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) diff --git a/extras/hs-test/infra/suite_h2.go b/extras/hs-test/infra/suite_http2.go similarity index 94% rename from extras/hs-test/infra/suite_h2.go rename to extras/hs-test/infra/suite_http2.go index ddd128551d..a0507a38d4 100644 --- a/extras/hs-test/infra/suite_h2.go +++ b/extras/hs-test/infra/suite_http2.go @@ -20,11 +20,11 @@ import ( "github.com/summerwind/h2spec/spec" ) -var h2Tests = map[string][]func(s *H2Suite){} -var h2SoloTests = map[string][]func(s *H2Suite){} -var h2MWTests = map[string][]func(s *H2Suite){} +var h2Tests = map[string][]func(s *Http2Suite){} +var h2SoloTests = map[string][]func(s *Http2Suite){} +var h2MWTests = map[string][]func(s *Http2Suite){} -type H2Suite struct { +type Http2Suite struct { HstSuite Interfaces struct { Tap *NetInterface @@ -40,17 +40,17 @@ type H2Suite struct { } } -func RegisterH2Tests(tests ...func(s *H2Suite)) { +func RegisterH2Tests(tests ...func(s *Http2Suite)) { h2Tests[GetTestFilename()] = tests } -func RegisterH2SoloTests(tests ...func(s *H2Suite)) { +func RegisterH2SoloTests(tests ...func(s *Http2Suite)) { h2SoloTests[GetTestFilename()] = tests } -func RegisterH2MWTests(tests ...func(s *H2Suite)) { +func RegisterH2MWTests(tests ...func(s *Http2Suite)) { h2MWTests[GetTestFilename()] = tests } -func (s *H2Suite) SetupSuite() { +func (s *Http2Suite) SetupSuite() { s.HstSuite.SetupSuite() s.LoadNetworkTopology("tap") s.LoadContainerTopology("single") @@ -64,7 +64,7 @@ func (s *H2Suite) SetupSuite() { s.AssertNil(err) } -func (s *H2Suite) SetupTest() { +func (s *Http2Suite) SetupTest() { s.HstSuite.SetupTest() // Setup test conditions @@ -84,16 +84,16 @@ func (s *H2Suite) SetupTest() { } } -func (s *H2Suite) TeardownTest() { +func (s *Http2Suite) TeardownTest() { s.HstSuite.TeardownTest() } -func (s *H2Suite) VppAddr() string { +func (s *Http2Suite) VppAddr() string { return s.Interfaces.Tap.Peer.Ip4AddressString() } var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { - var s H2Suite + var s Http2Suite BeforeAll(func() { s.SetupSuite() }) @@ -122,7 +122,7 @@ var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { }) var _ = Describe("Http2SoloSuite", Ordered, ContinueOnFailure, Serial, func() { - var s H2Suite + var s Http2Suite BeforeAll(func() { s.SetupSuite() }) @@ -151,7 +151,7 @@ var _ = Describe("Http2SoloSuite", Ordered, ContinueOnFailure, Serial, func() { }) var _ = Describe("Http2MWSuite", Ordered, ContinueOnFailure, Serial, func() { - var s H2Suite + var s Http2Suite BeforeAll(func() { s.SetupSuite() }) @@ -366,7 +366,7 @@ var specs = []struct { // Marked as pending since http plugin is not build with http/2 enabled by default var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { - var s H2Suite + var s Http2Suite BeforeAll(func() { s.SetupSuite() }) From 9d0d575a38528ead0d6893f1c83ce22e10580e57 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 25 Jun 2025 20:16:36 -0700 Subject: [PATCH 111/313] tls: fix coverity warning Type: fix Change-Id: I2505613734667ae59fc51bd42f8dcd15206cded1 Signed-off-by: Florin Coras --- src/plugins/tlsopenssl/tls_openssl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 8d3f5f381c..65fe9a3f58 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -77,7 +77,7 @@ openssl_ctx_free (tls_ctx_t * ctx) { int rv = SSL_shutdown (oc->ssl); if (rv < 0) - SSL_get_error (oc->ssl, rv); + (void) SSL_get_error (oc->ssl, rv); } if (openssl_main.async) @@ -428,7 +428,7 @@ openssl_confirm_app_close (tls_ctx_t *ctx) openssl_ctx_t *oc = (openssl_ctx_t *) ctx; int rv = SSL_shutdown (oc->ssl); if (rv < 0) - SSL_get_error (oc->ssl, rv); + (void) SSL_get_error (oc->ssl, rv); if (ctx->flags & TLS_CONN_F_SHUTDOWN_TRANSPORT) tls_shutdown_transport (ctx); else From eb25e6c556ad4290a3416a6461ea0a7c6977d4ac Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Mon, 30 Jun 2025 15:53:21 +0200 Subject: [PATCH 112/313] vppinfra: avoid string truncation errors by some gcc versions Type: fix Change-Id: If33251e1eac7e437ae803363d960896da02ae567 Signed-off-by: Damjan Marion --- src/vppinfra/devicetree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vppinfra/devicetree.c b/src/vppinfra/devicetree.c index 309308c926..5d14231429 100644 --- a/src/vppinfra/devicetree.c +++ b/src/vppinfra/devicetree.c @@ -118,7 +118,9 @@ clib_dt_read_from_sysfs (clib_dt_main_t *dm) if (read (fd, p->data, st.st_size) == st.st_size) { - strncpy (p->name, e->d_name, sizeof (p->name)); + snprintf (p->name, sizeof (p->name), "%.*s", + (int) sizeof (p->name) - 1, e->d_name); + p->size = st.st_size; vec_add1 (n->properties, p); if (strncmp ("name", p->name, 5) == 0) From cc45a24b18d12b2d574c5c6573440cad65bfff1e Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Tue, 1 Jul 2025 17:28:31 +0200 Subject: [PATCH 113/313] octeon: use plain strcmp Some gcc versions have issues with clib_strcmp... Type: fix Change-Id: Idfdcb7d13f14eadb7a7f210743a783172e2d5774 Signed-off-by: Damjan Marion --- src/plugins/dev_octeon/roc_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/dev_octeon/roc_helper.c b/src/plugins/dev_octeon/roc_helper.c index 0f872047fb..ccc381d2eb 100644 --- a/src/plugins/dev_octeon/roc_helper.c +++ b/src/plugins/dev_octeon/roc_helper.c @@ -134,7 +134,7 @@ oct_plt_memzone_lookup (const char *name) pool_foreach (mem_pool, memzone_list.mem_pool) { - if (!clib_strcmp (mem_pool->name, name)) + if (!strcmp (mem_pool->name, name)) return mem_pool; } From a11222d91e39ce483879a49046a253b23f3b2b56 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 27 Jun 2025 13:40:51 +0200 Subject: [PATCH 114/313] http: fix http header table hash keys leak Type: fix Change-Id: I60ebf767a1f96a1290b7738d6859241739ce452c Signed-off-by: Matus Fabian --- src/plugins/http/http.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 95b2faf062..5777bd520a 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -723,11 +723,16 @@ always_inline void http_reset_header_table (http_header_table_t *ht) { int i; + hash_pair_t *p; for (i = 0; i < vec_len (ht->concatenated_values); i++) vec_free (ht->concatenated_values[i]); vec_reset_length (ht->concatenated_values); vec_reset_length (ht->values); vec_reset_length (ht->buf); + hash_foreach_pair (p, ht->value_by_name, ({ + void *k = uword_to_pointer (p->key, void *); + clib_mem_free (k); + })); hash_free (ht->value_by_name); } @@ -751,11 +756,16 @@ always_inline void http_free_header_table (http_header_table_t *ht) { int i; + hash_pair_t *p; for (i = 0; i < vec_len (ht->concatenated_values); i++) vec_free (ht->concatenated_values[i]); vec_free (ht->concatenated_values); vec_free (ht->values); vec_free (ht->buf); + hash_foreach_pair (p, ht->value_by_name, ({ + void *k = uword_to_pointer (p->key, void *); + clib_mem_free (k); + })); hash_free (ht->value_by_name); } From f6c13424fa9a22b5cddbe2b59fc33c74b7160aee Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 27 Jun 2025 13:43:09 +0200 Subject: [PATCH 115/313] hs-test: h2 memory leak test Type: test Change-Id: Ib68aabecafc4e17acae9942b6791d4aeb4833f96 Signed-off-by: Matus Fabian --- extras/hs-test/http2_test.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index b1d7c6fcb4..a46da7a542 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -10,7 +10,7 @@ import ( ) func init() { - RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest) + RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest) RegisterH2MWTests(Http2MultiplexingMWTest) } @@ -122,3 +122,36 @@ func Http2ContinuationTxTest(s *Http2Suite) { s.AssertNil(err, fmt.Sprint(err)) s.AssertGreaterThan(sizeHeader, 32768) } + +func Http2ServerMemLeakTest(s *H2Suite) { + s.SkipUnlessLeakCheck() + + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.VppAddr() + ":" + s.Ports.Port1 + vpp.Vppctl("http cli server uri http://" + serverAddress) + target := fmt.Sprintf("http://%s/show/version", serverAddress) + + /* no goVPP less noise */ + vpp.Disconnect() + + /* warmup request (FIB) */ + args := fmt.Sprintf("--max-time 10 --noproxy '*' --http2-prior-knowledge -z %s %s %s %s", target, target, target, target) + _, log := s.RunCurlContainer(s.Containers.Curl, args) + s.AssertContains(log, "HTTP/2 200") + + vpp.EnableMemoryTrace() + traces1, err := vpp.GetMemoryTrace() + s.AssertNil(err, fmt.Sprint(err)) + + for i := 0; i < 10; i++ { + time.Sleep(time.Second * 1) + s.AssertNil(s.Containers.Curl.Start()) + } + + /* let's give it some time to clean up sessions */ + time.Sleep(time.Second * 15) + + traces2, err := vpp.GetMemoryTrace() + s.AssertNil(err, fmt.Sprint(err)) + vpp.MemLeakCheck(traces1, traces2) +} From 9ed96671b8bfa82cbc0f41945d3bcfe42fa003e0 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 2 Jul 2025 16:42:10 +0200 Subject: [PATCH 116/313] hs-test: fix Http2ServerMemLeakTest Type: test Change-Id: Id8745fd8c67924cb5067c54695d3594220c590ed Signed-off-by: Adrian Villin --- extras/hs-test/http2_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index a46da7a542..1b75c52c02 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -123,7 +123,7 @@ func Http2ContinuationTxTest(s *Http2Suite) { s.AssertGreaterThan(sizeHeader, 32768) } -func Http2ServerMemLeakTest(s *H2Suite) { +func Http2ServerMemLeakTest(s *Http2Suite) { s.SkipUnlessLeakCheck() vpp := s.Containers.Vpp.VppInstance From 5e35e9341ea6c04cc9622ad5bcbc6894ecd92609 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 20 Jun 2025 22:12:48 -0700 Subject: [PATCH 117/313] session: cli to add/del collector to app Type: improvement Change-Id: Ie1ec27eab7d297f20ec7cad46d2e90b9965f896a Signed-off-by: Florin Coras --- src/vnet/session/application.c | 2 +- src/vnet/session/application_eventing.c | 22 +++++++++++++++++++++- src/vnet/session/application_interface.h | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index 1bfce79e4b..a550aec2b5 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -856,7 +856,7 @@ application_alloc_and_init (app_init_args_t *a) props->pct_first_alloc = opts[APP_OPTIONS_PCT_FIRST_ALLOC]; props->segment_type = seg_type; - if (opts[APP_OPTIONS_FLAGS] & APP_OPTIONS_FLAGS_LOG_COLLECTOR) + if (opts[APP_OPTIONS_FLAGS] & APP_OPTIONS_FLAGS_EVT_COLLECTOR) app->cb_fns.app_evt_callback = app_evt_collector_get_cb_fn (); /* Add app to lookup by api_client_index table */ diff --git a/src/vnet/session/application_eventing.c b/src/vnet/session/application_eventing.c index e698d1ce91..9ce8f4d059 100644 --- a/src/vnet/session/application_eventing.c +++ b/src/vnet/session/application_eventing.c @@ -518,6 +518,7 @@ app_evt_collector_enable_command_fn (vlib_main_t *vm, unformat_input_t *input, u8 *collector_uri = 0, is_enable = 0, is_add = 1; app_evt_main_t *alm = &app_evt_main; clib_error_t *error = 0; + u32 app_index = ~0; u64 tmp64 = 0; if (!unformat_user (input, unformat_line_input, line_input)) @@ -542,6 +543,8 @@ app_evt_collector_enable_command_fn (vlib_main_t *vm, unformat_input_t *input, alm->segment_size = tmp64; else if (unformat (line_input, "uri %s", &collector_uri)) vec_add1 (collector_uri, 0); + else if (unformat (line_input, "app %d", &app_index)) + ; else if (unformat (line_input, "add")) ; else if (unformat (line_input, "del")) @@ -592,6 +595,23 @@ app_evt_collector_enable_command_fn (vlib_main_t *vm, unformat_input_t *input, } } + if (app_index != ~0) + { + application_t *app = application_get (app_index); + if (!app) + { + error = clib_error_return (0, "Invalid app index %u", app_index); + goto done; + } + if (!is_add) + { + app->evt_collector_index = APP_INVALID_INDEX; + app->cb_fns.app_evt_callback = 0; + goto done; + } + app->cb_fns.app_evt_callback = app_evt_collector_get_cb_fn (); + } + done: unformat_free (line_input); vec_free (collector_uri); @@ -601,7 +621,7 @@ app_evt_collector_enable_command_fn (vlib_main_t *vm, unformat_input_t *input, VLIB_CLI_COMMAND (app_evt_collector_command, static) = { .path = "app evt-collector", .short_help = "app evt-collector [enable] [segment-size [k|m]] " - "[fifo-size [k|m]] [add|del] uri ", + "[fifo-size [k|m]] [add|del] [uri ] [app ] ", .function = app_evt_collector_enable_command_fn, }; diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index a8ca422afa..4d53eec108 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -246,7 +246,7 @@ typedef enum _ (MEMFD_FOR_BUILTIN, "Use memfd for builtin app segs") \ _ (USE_HUGE_PAGE, "Use huge page for FIFO") \ _ (GET_ORIGINAL_DST, "Get original dst enabled") \ - _ (LOG_COLLECTOR, "App requests log collector") + _ (EVT_COLLECTOR, "App requests event collector") typedef enum _app_options { From b24b1c61370dc4cfc4f7b419ff7e2ffd88b32d34 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 27 May 2025 15:30:33 -0400 Subject: [PATCH 118/313] session: move app crypto to separate files Type: refactor Change-Id: Iac10665d3060e4c585e3ee1f94743809ab09d9db Signed-off-by: Florin Coras --- src/vnet/CMakeLists.txt | 2 + src/vnet/session/application.c | 174 +------------------- src/vnet/session/application.h | 20 +-- src/vnet/session/application_crypto.c | 192 +++++++++++++++++++++++ src/vnet/session/application_crypto.h | 68 ++++++++ src/vnet/session/application_interface.h | 38 +---- 6 files changed, 268 insertions(+), 226 deletions(-) create mode 100644 src/vnet/session/application_crypto.c create mode 100644 src/vnet/session/application_crypto.h diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt index 65d612c0f3..e3abe93da2 100644 --- a/src/vnet/CMakeLists.txt +++ b/src/vnet/CMakeLists.txt @@ -978,6 +978,7 @@ list(APPEND VNET_SOURCES session/application_interface.c session/application_local.c session/application_namespace.c + session/application_crypto.c session/segment_manager.c session/session_api.c session/session_sdl.c @@ -996,6 +997,7 @@ list(APPEND VNET_HEADERS session/application_eventing.h session/application_local.h session/application_namespace.h + session/application_crypto.h session/session_debug.h session/segment_manager.h session/mma_template.h diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index a550aec2b5..1ba4604a97 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -1781,67 +1782,13 @@ application_format_connects (application_t * app, int verbose) } } -u8 * -format_cert_key_pair (u8 * s, va_list * args) -{ - app_cert_key_pair_t *ckpair = va_arg (*args, app_cert_key_pair_t *); - int key_len = 0, cert_len = 0; - cert_len = vec_len (ckpair->cert); - key_len = vec_len (ckpair->key); - if (ckpair->cert_key_index == 0) - s = format (s, "DEFAULT (cert:%d, key:%d)", cert_len, key_len); - else - s = format (s, "%d (cert:%d, key:%d)", ckpair->cert_key_index, - cert_len, key_len); - return s; -} - -u8 * -format_crypto_engine (u8 * s, va_list * args) -{ - u32 engine = va_arg (*args, u32); - switch (engine) - { - case CRYPTO_ENGINE_NONE: - return format (s, "none"); - case CRYPTO_ENGINE_MBEDTLS: - return format (s, "mbedtls"); - case CRYPTO_ENGINE_OPENSSL: - return format (s, "openssl"); - case CRYPTO_ENGINE_PICOTLS: - return format (s, "picotls"); - case CRYPTO_ENGINE_VPP: - return format (s, "vpp"); - default: - return format (s, "unknown engine"); - } - return s; -} - -uword -unformat_crypto_engine (unformat_input_t * input, va_list * args) -{ - u8 *a = va_arg (*args, u8 *); - if (unformat (input, "mbedtls")) - *a = CRYPTO_ENGINE_MBEDTLS; - else if (unformat (input, "openssl")) - *a = CRYPTO_ENGINE_OPENSSL; - else if (unformat (input, "picotls")) - *a = CRYPTO_ENGINE_PICOTLS; - else if (unformat (input, "vpp")) - *a = CRYPTO_ENGINE_VPP; - else - return 0; - return 1; -} - u8 * format_crypto_context (u8 * s, va_list * args) { crypto_context_t *crctx = va_arg (*args, crypto_context_t *); s = format (s, "[0x%x][sub%d,ckpair%x]", crctx->ctx_index, crctx->n_subscribers, crctx->ckpair_index); - s = format (s, "[%U]", format_crypto_engine, crctx->crypto_engine); + s = format (s, "[engine:%U]", format_crypto_engine, crctx->crypto_engine); return s; } @@ -1937,19 +1884,6 @@ application_format_all_clients (vlib_main_t * vm, int verbose) } } -static clib_error_t * -show_certificate_command_fn (vlib_main_t * vm, unformat_input_t * input, - vlib_cli_command_t * cmd) -{ - app_cert_key_pair_t *ckpair; - session_cli_return_if_not_enabled (); - - pool_foreach (ckpair, app_main.cert_key_pair_store) { - vlib_cli_output (vm, "%U", format_cert_key_pair, ckpair); - } - return 0; -} - static inline void appliction_format_app_mq (vlib_main_t * vm, application_t * app) { @@ -2072,85 +2006,6 @@ show_app_command_fn (vlib_main_t * vm, unformat_input_t * input, return 0; } -/* Certificate store */ - -static app_cert_key_pair_t * -app_cert_key_pair_alloc () -{ - app_cert_key_pair_t *ckpair; - pool_get (app_main.cert_key_pair_store, ckpair); - clib_memset (ckpair, 0, sizeof (*ckpair)); - ckpair->cert_key_index = ckpair - app_main.cert_key_pair_store; - return ckpair; -} - -app_cert_key_pair_t * -app_cert_key_pair_get_if_valid (u32 index) -{ - if (pool_is_free_index (app_main.cert_key_pair_store, index)) - return 0; - return app_cert_key_pair_get (index); -} - -app_cert_key_pair_t * -app_cert_key_pair_get (u32 index) -{ - return pool_elt_at_index (app_main.cert_key_pair_store, index); -} - -app_cert_key_pair_t * -app_cert_key_pair_get_default () -{ - /* To maintain legacy bapi */ - return app_cert_key_pair_get (0); -} - -int -vnet_app_add_cert_key_pair (vnet_app_add_cert_key_pair_args_t * a) -{ - app_cert_key_pair_t *ckpair = app_cert_key_pair_alloc (); - vec_validate (ckpair->cert, a->cert_len - 1); - clib_memcpy_fast (ckpair->cert, a->cert, a->cert_len); - vec_validate (ckpair->key, a->key_len - 1); - clib_memcpy_fast (ckpair->key, a->key, a->key_len); - a->index = ckpair->cert_key_index; - return 0; -} - -int -vnet_app_add_cert_key_interest (u32 index, u32 app_index) -{ - app_cert_key_pair_t *ckpair; - if (!(ckpair = app_cert_key_pair_get_if_valid (index))) - return -1; - if (vec_search (ckpair->app_interests, app_index) != ~0) - vec_add1 (ckpair->app_interests, app_index); - return 0; -} - -int -vnet_app_del_cert_key_pair (u32 index) -{ - app_cert_key_pair_t *ckpair; - application_t *app; - u32 *app_index; - - if (!(ckpair = app_cert_key_pair_get_if_valid (index))) - return SESSION_E_INVALID; - - vec_foreach (app_index, ckpair->app_interests) - { - if ((app = application_get_if_valid (*app_index)) - && app->cb_fns.app_cert_key_pair_delete_callback) - app->cb_fns.app_cert_key_pair_delete_callback (ckpair); - } - - vec_free (ckpair->cert); - vec_free (ckpair->key); - pool_put (app_main.cert_key_pair_store, ckpair); - return 0; -} - clib_error_t * application_init (vlib_main_t * vm) { @@ -2158,13 +2013,10 @@ application_init (vlib_main_t * vm) u32 n_workers; n_workers = vlib_num_workers (); - - /* Index 0 was originally used by legacy apis, maintain as invalid */ - (void) app_cert_key_pair_alloc (); - am->last_crypto_engine = CRYPTO_ENGINE_LAST; + vec_validate (am->wrk, n_workers); am->app_by_name = hash_create_vec (0, sizeof (u8), sizeof (uword)); - vec_validate (am->wrk, n_workers); + application_crypto_init (); return 0; } @@ -2178,24 +2030,6 @@ VLIB_CLI_COMMAND (show_app_command, static) = { .function = show_app_command_fn, }; -VLIB_CLI_COMMAND (show_certificate_command, static) = { - .path = "show app certificate", - .short_help = "list app certs and keys present in store", - .function = show_certificate_command_fn, -}; - -crypto_engine_type_t -app_crypto_engine_type_add (void) -{ - return (++app_main.last_crypto_engine); -} - -u8 -app_crypto_engine_n_types (void) -{ - return (app_main.last_crypto_engine + 1); -} - /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/session/application.h b/src/vnet/session/application.h index 2d605c3af8..92c182f63b 100644 --- a/src/vnet/session/application.h +++ b/src/vnet/session/application.h @@ -213,16 +213,6 @@ typedef struct app_main_ */ uword *app_by_name; - /** - * Pool from which we allocate certificates (key, cert) - */ - app_cert_key_pair_t *cert_key_pair_store; - - /* - * Last registered crypto engine type - */ - crypto_engine_type_t last_crypto_engine; - /** * App sublayer per-worker state */ @@ -401,23 +391,15 @@ session_t *app_worker_proxy_listener (app_worker_t * app, u8 fib_proto, u8 transport_proto); void app_worker_del_detached_sm (app_worker_t * app_wrk, u32 sm_index); u8 *format_app_worker (u8 * s, va_list * args); -u8 *format_app_worker_listener (u8 * s, va_list * args); -u8 *format_crypto_engine (u8 * s, va_list * args); +u8 *format_app_worker_listener (u8 *s, va_list *args); u8 *format_crypto_context (u8 * s, va_list * args); void app_worker_format_connects (app_worker_t * app_wrk, int verbose); session_error_t vnet_app_worker_add_del (vnet_app_worker_add_del_args_t *a); uword unformat_application_proto (unformat_input_t * input, va_list * args); -app_cert_key_pair_t *app_cert_key_pair_get (u32 index); -app_cert_key_pair_t *app_cert_key_pair_get_if_valid (u32 index); -app_cert_key_pair_t *app_cert_key_pair_get_default (); - void sapi_socket_close_w_handle (u32 api_handle); -crypto_engine_type_t app_crypto_engine_type_add (void); -u8 app_crypto_engine_n_types (void); - static inline u8 app_worker_application_is_builtin (app_worker_t *app_wrk) { diff --git a/src/vnet/session/application_crypto.c b/src/vnet/session/application_crypto.c new file mode 100644 index 0000000000..a48ee07b4c --- /dev/null +++ b/src/vnet/session/application_crypto.c @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2025 Cisco Systems, Inc. + */ + +#include +#include +#include + +typedef struct app_crypto_main_ +{ + crypto_engine_type_t last_crypto_engine; /* Last crypto engine type used */ + app_cert_key_pair_t *cert_key_pair_store; /* Pool of cert/key pairs */ +} app_crypto_main_t; + +static app_crypto_main_t app_crypto_main; + +static app_cert_key_pair_t * +app_cert_key_pair_alloc () +{ + app_cert_key_pair_t *ckpair; + pool_get (app_crypto_main.cert_key_pair_store, ckpair); + clib_memset (ckpair, 0, sizeof (*ckpair)); + ckpair->cert_key_index = ckpair - app_crypto_main.cert_key_pair_store; + return ckpair; +} + +app_cert_key_pair_t * +app_cert_key_pair_get (u32 index) +{ + return pool_elt_at_index (app_crypto_main.cert_key_pair_store, index); +} + +app_cert_key_pair_t * +app_cert_key_pair_get_if_valid (u32 index) +{ + if (pool_is_free_index (app_crypto_main.cert_key_pair_store, index)) + return 0; + return app_cert_key_pair_get (index); +} + +app_cert_key_pair_t * +app_cert_key_pair_get_default () +{ + /* To maintain legacy bapi */ + return app_cert_key_pair_get (0); +} + +int +vnet_app_add_cert_key_pair (vnet_app_add_cert_key_pair_args_t *a) +{ + app_cert_key_pair_t *ckpair = app_cert_key_pair_alloc (); + vec_validate (ckpair->cert, a->cert_len - 1); + clib_memcpy_fast (ckpair->cert, a->cert, a->cert_len); + vec_validate (ckpair->key, a->key_len - 1); + clib_memcpy_fast (ckpair->key, a->key, a->key_len); + a->index = ckpair->cert_key_index; + return 0; +} + +int +vnet_app_add_cert_key_interest (u32 index, u32 app_index) +{ + app_cert_key_pair_t *ckpair; + if (!(ckpair = app_cert_key_pair_get_if_valid (index))) + return -1; + if (vec_search (ckpair->app_interests, app_index) != ~0) + vec_add1 (ckpair->app_interests, app_index); + return 0; +} + +int +vnet_app_del_cert_key_pair (u32 index) +{ + app_cert_key_pair_t *ckpair; + application_t *app; + u32 *app_index; + + if (!(ckpair = app_cert_key_pair_get_if_valid (index))) + return SESSION_E_INVALID; + + vec_foreach (app_index, ckpair->app_interests) + { + if ((app = application_get_if_valid (*app_index)) && + app->cb_fns.app_cert_key_pair_delete_callback) + app->cb_fns.app_cert_key_pair_delete_callback (ckpair); + } + + vec_free (ckpair->cert); + vec_free (ckpair->key); + pool_put (app_crypto_main.cert_key_pair_store, ckpair); + return 0; +} + +u8 * +format_cert_key_pair (u8 *s, va_list *args) +{ + app_cert_key_pair_t *ckpair = va_arg (*args, app_cert_key_pair_t *); + int key_len = 0, cert_len = 0; + cert_len = vec_len (ckpair->cert); + key_len = vec_len (ckpair->key); + if (ckpair->cert_key_index == 0) + s = format (s, "DEFAULT (cert:%d, key:%d)", cert_len, key_len); + else + s = format (s, "%d (cert:%d, key:%d)", ckpair->cert_key_index, cert_len, + key_len); + return s; +} + +static clib_error_t * +show_certificate_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + app_cert_key_pair_t *ckpair; + session_cli_return_if_not_enabled (); + + pool_foreach (ckpair, app_crypto_main.cert_key_pair_store) + { + vlib_cli_output (vm, "%U", format_cert_key_pair, ckpair); + } + return 0; +} + +VLIB_CLI_COMMAND (show_certificate_command, static) = { + .path = "show app certificate", + .short_help = "list app certs and keys present in store", + .function = show_certificate_command_fn, +}; + +crypto_engine_type_t +app_crypto_engine_type_add (void) +{ + return (++app_crypto_main.last_crypto_engine); +} + +u8 * +format_crypto_engine (u8 *s, va_list *args) +{ + u32 engine = va_arg (*args, u32); + switch (engine) + { + case CRYPTO_ENGINE_NONE: + return format (s, "none"); + case CRYPTO_ENGINE_MBEDTLS: + return format (s, "mbedtls"); + case CRYPTO_ENGINE_OPENSSL: + return format (s, "openssl"); + case CRYPTO_ENGINE_PICOTLS: + return format (s, "picotls"); + case CRYPTO_ENGINE_VPP: + return format (s, "vpp"); + default: + return format (s, "unknown engine"); + } + return s; +} + +uword +unformat_crypto_engine (unformat_input_t *input, va_list *args) +{ + u8 *a = va_arg (*args, u8 *); + if (unformat (input, "mbedtls")) + *a = CRYPTO_ENGINE_MBEDTLS; + else if (unformat (input, "openssl")) + *a = CRYPTO_ENGINE_OPENSSL; + else if (unformat (input, "picotls")) + *a = CRYPTO_ENGINE_PICOTLS; + else if (unformat (input, "vpp")) + *a = CRYPTO_ENGINE_VPP; + else + return 0; + return 1; +} + +u8 +app_crypto_engine_n_types (void) +{ + return (app_crypto_main.last_crypto_engine + 1); +} + +clib_error_t * +application_crypto_init () +{ + app_crypto_main_t *acm = &app_crypto_main; + + /* Index 0 was originally used by legacy apis, maintain as invalid */ + app_cert_key_pair_alloc (); + + acm->last_crypto_engine = CRYPTO_ENGINE_LAST; + return 0; +} + +VLIB_INIT_FUNCTION (application_crypto_init); \ No newline at end of file diff --git a/src/vnet/session/application_crypto.h b/src/vnet/session/application_crypto.h new file mode 100644 index 0000000000..20e385e4ae --- /dev/null +++ b/src/vnet/session/application_crypto.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2025 Cisco Systems, Inc. + */ + +#ifndef SRC_VNET_SESSION_APPLICATION_CRYPTO_H_ +#define SRC_VNET_SESSION_APPLICATION_CRYPTO_H_ + +#include + +typedef struct certificate_ +{ + u32 *app_interests; /* vec of application index asking for deletion cb */ + u32 cert_key_index; /* index in cert & key pool */ + u8 *key; + u8 *cert; +} app_cert_key_pair_t; + +typedef enum crypto_engine_type_ +{ + CRYPTO_ENGINE_NONE, + CRYPTO_ENGINE_OPENSSL, + CRYPTO_ENGINE_MBEDTLS, + CRYPTO_ENGINE_VPP, + CRYPTO_ENGINE_PICOTLS, + CRYPTO_ENGINE_LAST = CRYPTO_ENGINE_PICOTLS, +} crypto_engine_type_t; + +typedef struct _vnet_app_add_cert_key_pair_args_ +{ + u8 *cert; + u8 *key; + u32 cert_len; + u32 key_len; + u32 index; +} vnet_app_add_cert_key_pair_args_t; + +typedef struct crypto_ctx_ +{ + u32 ctx_index; /**< index in crypto context pool */ + u32 n_subscribers; /**< refcount of sessions using said context */ + u32 ckpair_index; /**< certificate & key */ + u8 crypto_engine; + void *data; /**< protocol specific data */ +} crypto_context_t; + +/* + * Certificate key-pair management + */ + +app_cert_key_pair_t *app_cert_key_pair_get (u32 index); +app_cert_key_pair_t *app_cert_key_pair_get_if_valid (u32 index); +app_cert_key_pair_t *app_cert_key_pair_get_default (); + +int vnet_app_add_cert_key_pair (vnet_app_add_cert_key_pair_args_t *a); +int vnet_app_add_cert_key_interest (u32 index, u32 app_index); +int vnet_app_del_cert_key_pair (u32 index); + +/* + * Crypto engine management + */ +crypto_engine_type_t app_crypto_engine_type_add (void); +u8 app_crypto_engine_n_types (void); +u8 *format_crypto_engine (u8 *s, va_list *args); +uword unformat_crypto_engine (unformat_input_t *input, va_list *args); + +clib_error_t *application_crypto_init (); + +#endif /* SRC_VNET_SESSION_APPLICATION_CRYPTO_H_ */ diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index 4d53eec108..e9284b9806 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -17,18 +17,10 @@ #include #include +#include #include -#include #include -typedef struct certificate_ -{ - u32 *app_interests; /* vec of application index asking for deletion cb */ - u32 cert_key_index; /* index in cert & key pool */ - u8 *key; - u8 *cert; -} app_cert_key_pair_t; - typedef struct session_cb_vft_ { /** Notify server of new segment */ @@ -182,34 +174,6 @@ typedef struct _vnet_application_add_tls_key_args_t u8 *key; } vnet_app_add_tls_key_args_t; -typedef enum crypto_engine_type_ -{ - CRYPTO_ENGINE_NONE, - CRYPTO_ENGINE_OPENSSL, - CRYPTO_ENGINE_MBEDTLS, - CRYPTO_ENGINE_VPP, - CRYPTO_ENGINE_PICOTLS, - CRYPTO_ENGINE_LAST = CRYPTO_ENGINE_PICOTLS, -} crypto_engine_type_t; - -typedef struct _vnet_app_add_cert_key_pair_args_ -{ - u8 *cert; - u8 *key; - u32 cert_len; - u32 key_len; - u32 index; -} vnet_app_add_cert_key_pair_args_t; - -typedef struct crypto_ctx_ -{ - u32 ctx_index; /**< index in crypto context pool */ - u32 n_subscribers; /**< refcount of sessions using said context */ - u32 ckpair_index; /**< certificate & key */ - u8 crypto_engine; - void *data; /**< protocol specific data */ -} crypto_context_t; - /* Application attach options */ typedef enum { From 49d93a7d65eb800642387e8b6a37cd2e76350e36 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 3 Jul 2025 18:00:39 -0400 Subject: [PATCH 119/313] vlib: handle worker sync in wait one loop Type: improvement Change-Id: I8dcd9b7d305878eb385eb27481be6a241b4a024c Signed-off-by: Florin Coras --- src/vlib/threads.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vlib/threads.c b/src/vlib/threads.c index a1839e787c..8c4a6fe3ab 100644 --- a/src/vlib/threads.c +++ b/src/vlib/threads.c @@ -1616,7 +1616,13 @@ vlib_worker_wait_one_loop (void) for (ii = 1; ii < vec_len (counts); ii++) { while (counts[ii] == vgm->vlib_mains[ii]->main_loop_count) - CLIB_PAUSE (); + { + /* worker sync requested, vlib_worker_sync_rpc probably pending + * so at least one worker cannot make any progress */ + if (vlib_worker_threads->wait_before_barrier) + break; + CLIB_PAUSE (); + } } vec_free (counts); From e99ef3a280e4bf36925d1bbb90b4b97f1fd79cfa Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Tue, 1 Jul 2025 12:42:07 +0200 Subject: [PATCH 120/313] hs-test: use current VPP dir for CalicoVPP build - make setup-cluster will use current VPP dir instead of CalicoVPP's dir - VPP will be built only once - changed build targets to build VPP as a non-root user (to avoid permission issues when building CalicoVPP) Type: improvement Change-Id: Iba90219660c1259da6ad81fe002f99b6d43ea248 Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 26 +++++++++++++++-------- extras/hs-test/kubernetes/setupCluster.sh | 15 +++++++++++-- extras/hs-test/script/build_hst.sh | 6 ++++-- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 82b548d300..2bc3db1186 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -74,6 +74,7 @@ GINKGO_TIMEOUT=3h endif FORCE_BUILD?=true +BUILD_AS:=$(strip $(shell echo $${SUDO_USER:-$${USER:-root}})) .PHONY: help help: @@ -123,17 +124,24 @@ list-tests: @go run github.com/onsi/ginkgo/v2/ginkgo --dry-run -v --no-color --seed=2 | head -n -1 | grep 'test.go' | \ sed 's/^/* /; s/\(Suite\) /\1\//g' +.PHONY: build-msg +build-msg: + @echo "Building VPP as '$(BUILD_AS)'" + @echo "****************************************************************" + @echo "If you wish to build VPP as root, use 'SUDO_USER=root make ...'" + @echo "****************************************************************" + .PHONY: build-vpp-release -build-vpp-release: - @$(MAKE) -C ../.. build-release +build-vpp-release: build-msg + @sudo -u $(BUILD_AS) $(MAKE) -C ../.. build-release .PHONY: build-vpp-debug -build-vpp-debug: - @$(MAKE) -C ../.. build +build-vpp-debug: build-msg + @sudo -u $(BUILD_AS) $(MAKE) -C ../.. build .PHONY: build-vpp-gcov -build-vpp-gcov: - @$(MAKE) -C ../.. build-gcov +build-vpp-gcov: build-msg + @sudo -u $(BUILD_AS) $(MAKE) -C ../.. build-gcov .build.ok: build @touch .build.ok @@ -203,19 +211,19 @@ build-go: .PHONY: build build: .deps.ok build-vpp-release build-go @rm -f .build.ok - bash ./script/build_hst.sh release $(FORCE_BUILD) + bash ./script/build_hst.sh release $(FORCE_BUILD) $(BUILD_AS) @touch .build.ok .PHONY: build-cov build-cov: .deps.ok build-vpp-gcov build-go @rm -f .build.cov.ok - bash ./script/build_hst.sh gcov $(FORCE_BUILD) + bash ./script/build_hst.sh gcov $(FORCE_BUILD) $(BUILD_AS) @touch .build.cov.ok .PHONY: build-debug build-debug: .deps.ok build-vpp-debug build-go @rm -f .build.ok - bash ./script/build_hst.sh debug $(FORCE_BUILD) + bash ./script/build_hst.sh debug $(FORCE_BUILD) $(BUILD_AS) @touch .build.ok .deps.ok: diff --git a/extras/hs-test/kubernetes/setupCluster.sh b/extras/hs-test/kubernetes/setupCluster.sh index b972839b3d..131248ca27 100755 --- a/extras/hs-test/kubernetes/setupCluster.sh +++ b/extras/hs-test/kubernetes/setupCluster.sh @@ -3,6 +3,9 @@ set -e MASTER_OR_LATEST=${1-"latest"} CALICOVPP_DIR="$HOME/vpp-dataplane" +VPP_DIR=$(pwd) +VPP_DIR=${VPP_DIR%extras*} +STASH_SAVED=0 if [ $MASTER_OR_LATEST = "master" ]; then if [ ! -d "$CALICOVPP_DIR" ]; then @@ -10,6 +13,7 @@ if [ $MASTER_OR_LATEST = "master" ]; then fi cd $CALICOVPP_DIR git pull + cd $VPP_DIR # ---------------- images ---------------- export CALICO_AGENT_IMAGE=localhost:5000/calicovpp/agent:latest @@ -48,10 +52,17 @@ if [ $MASTER_OR_LATEST = "master" ]; then make -C $CALICOVPP_DIR kind-new-cluster N_KIND_WORKERS=2 kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml - make -C $CALICOVPP_DIR/vpp-manager vpp BASE=origin/master + make -C $CALICOVPP_DIR cherry-vpp FORCE=y BASE=origin/master VPP_DIR=$VPP_DIR + make build make -C $CALICOVPP_DIR dev-kind make -C $CALICOVPP_DIR load-kind $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up + if ! git diff-index --quiet HEAD --; then + echo "Saving stash" + git stash save "HST: temp stash" + git reset --hard origin/master + git stash pop + fi else echo "********" echo "Performance tests only work on Ubuntu 22.04 for now." @@ -63,7 +74,7 @@ if [ $MASTER_OR_LATEST = "master" ]; then echo "Sleeping for 10s, waiting for tigera operator to start up." sleep 10 - kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml + kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml kubectl create -f kubernetes/calico-config.yaml echo "Done. Please wait for the cluster to come fully online before running tests." diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh index b527466062..77d9dc7fef 100755 --- a/extras/hs-test/script/build_hst.sh +++ b/extras/hs-test/script/build_hst.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +BUILD_AS=$3 + if [ "$(lsb_release -is)" != Ubuntu ]; then echo "Host stack test framework is supported only on Ubuntu" exit 1 @@ -71,9 +73,9 @@ rm -rf vpp-data/bin/* || true rm -rf vpp-data/lib/* || true declare -i res=0 -cp ${VPP_BUILD_ROOT}/bin/* ${bin} +sudo -u $BUILD_AS cp ${VPP_BUILD_ROOT}/bin/* ${bin} res+=$? -cp -r ${VPP_BUILD_ROOT}/lib/"${OS_ARCH}"-linux-gnu/* ${lib} +sudo -u $BUILD_AS cp -r ${VPP_BUILD_ROOT}/lib/"${OS_ARCH}"-linux-gnu/* ${lib} res+=$? if [ "$res" -ne 0 ]; then echo "Failed to copy VPP files. Is VPP built? Try running 'make build' in VPP directory." From d6c8d33fb2253370b11c28a9f4dd16363888e85d Mon Sep 17 00:00:00 2001 From: Vladimir Zhigulin Date: Fri, 4 Jul 2025 10:47:14 +0200 Subject: [PATCH 121/313] fib: take barrier on LB pool put Fixes ASAN crash after fib update with multiple workers Type: fix Change-Id: I3d0112b608ffa5b5559311c6d494d27d6c1db511 Signed-off-by: Vladimir Zhigulin --- src/vnet/dpo/load_balance.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vnet/dpo/load_balance.c b/src/vnet/dpo/load_balance.c index f6f9392a42..c0f04f0ab9 100644 --- a/src/vnet/dpo/load_balance.c +++ b/src/vnet/dpo/load_balance.c @@ -894,6 +894,7 @@ load_balance_destroy (load_balance_t *lb) { dpo_id_t *buckets; int i; + u8 need_barrier_sync; buckets = load_balance_get_buckets(lb); @@ -911,7 +912,14 @@ load_balance_destroy (load_balance_t *lb) fib_urpf_list_unlock(lb->lb_urpf); load_balance_map_unlock(lb->lb_map); + need_barrier_sync = pool_put_will_expand (load_balance_pool, lb); + if (PREDICT_FALSE (need_barrier_sync)) + vlib_worker_thread_barrier_sync (vlib_get_main()); + pool_put(load_balance_pool, lb); + + if (PREDICT_FALSE (need_barrier_sync)) + vlib_worker_thread_barrier_release (vlib_get_main()); } static void From 4e58900cf8d1e34d023b36e1518c4cdfa04d5da6 Mon Sep 17 00:00:00 2001 From: Stanislav Zaikin Date: Mon, 26 May 2025 09:52:57 +0200 Subject: [PATCH 122/313] linux-cp: do not lock table when it's not needed Do not lock table when lcp is not installing routes (e.g. zero nexthops or ipv6 multicast routes), otherwise it will stay locked forever. Type: fix Signed-off-by: Stanislav Zaikin Change-Id: I4070ce0e0823b5afbd56c5cd79ae39a02c7d4300 --- src/plugins/linux-cp/lcp_router.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/plugins/linux-cp/lcp_router.c b/src/plugins/linux-cp/lcp_router.c index 27f53357a4..4c13c360e0 100644 --- a/src/plugins/linux-cp/lcp_router.c +++ b/src/plugins/linux-cp/lcp_router.c @@ -1332,7 +1332,6 @@ lcp_router_route_add (struct rtnl_route *rr, int is_replace) lcp_router_route_mk_prefix (rr, &pfx); entry_flags = lcp_router_route_mk_entry_flags (rtype, table_id, rproto); - nlt = lcp_router_table_add_or_lock (table_id, pfx.fp_proto); /* Skip any kernel routes and IPv6 LL or multicast routes */ if (rproto == RTPROT_KERNEL || (FIB_PROTOCOL_IP6 == pfx.fp_proto && @@ -1360,6 +1359,8 @@ lcp_router_route_add (struct rtnl_route *rr, int is_replace) if (0 != vec_len (np.paths)) { + nlt = lcp_router_table_add_or_lock (table_id, pfx.fp_proto); + if (rtype == RTN_MULTICAST) { /* it's not clear to me how linux expresses the RPF paramters @@ -1423,6 +1424,16 @@ lcp_router_route_add (struct rtnl_route *rr, int is_replace) LCP_ROUTER_DBG ("no paths for route: %d:%U %U", rtnl_route_get_table (rr), format_fib_prefix, &pfx, format_fib_entry_flags, entry_flags); + + nlt = + lcp_router_table_find (lcp_router_table_k2f (table_id), pfx.fp_proto); + + if (is_replace && nlt) + { + fib_source_t fib_src; + fib_src = lcp_router_proto_fib_source (rproto); + fib_table_entry_delete (nlt->nlt_fib_index, &pfx, fib_src); + } } vec_free (np.paths); } From 87d44f65916517725110f3b732537fc2011f63d2 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Tue, 8 Jul 2025 10:45:55 +0200 Subject: [PATCH 123/313] hs-test: fix install-kind-deps and kubeconfig path Type: test Change-Id: Ic9f883e510e275e862c700e4aae4630ad3dcfee8 Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 14 +++++++++----- extras/hs-test/hs_test.sh | 2 +- extras/hs-test/infra/kind/deployment.go | 2 +- extras/hs-test/infra/kind/suite_kind.go | 2 ++ extras/hs-test/kind_test.go | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 2bc3db1186..11b5235419 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -234,20 +234,24 @@ build-debug: .deps.ok build-vpp-debug build-go .PHONY: install-kind-deps install-kind-deps: .deps.ok - @go install sigs.k8s.io/kind@v0.29.0 - -@if which kind > /dev/null 2>&1; then \ - sudo ln -s $$(which kind) /usr/bin/kind; \ + -@if ! command -v kind >/dev/null 2>&1; then \ + echo "Installing KinD"; \ + go install sigs.k8s.io/kind@v0.29.0; \ + echo "Creating symlink from '$(HOME)/go/bin/kind' to '/usr/bin/kind'"; \ + sudo ln -s $(HOME)/go/bin/kind /usr/bin/kind; \ + else \ + echo "KinD is already installed. Skipping."; \ fi @if ! command -v kubectl >/dev/null 2>&1; then \ echo "kubectl not found. Installing kubectl..."; \ sudo -E apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl gnupg; \ - curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg; \ + curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg; \ sudo -E chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg; \ echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list; \ sudo -E chmod 644 /etc/apt/sources.list.d/kubernetes.list; \ sudo apt-get update && sudo apt-get install -y kubectl; \ else \ - echo "kubectl is already installed. Skipping installation."; \ + echo "kubectl is already installed. Skipping."; \ fi @touch .kind_deps.ok diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index 96d287a658..0337489b95 100644 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -167,7 +167,7 @@ if [ $focused_test -eq 0 ] && [ $debug_set -eq 1 ]; then exit 2 fi -sudo_user="${SUDO_USER:-root}" +sudo_user="${SUDO_USER:-${USER:-root}}" args="$args -sudo_user $sudo_user" if [ $leak_check_set -eq 1 ]; then diff --git a/extras/hs-test/infra/kind/deployment.go b/extras/hs-test/infra/kind/deployment.go index d28ffc8059..6404056500 100644 --- a/extras/hs-test/infra/kind/deployment.go +++ b/extras/hs-test/infra/kind/deployment.go @@ -20,7 +20,7 @@ func (s *KindSuite) loadDockerImages() { var err error for _, image := range s.images { s.Log("loading docker image %s...", image) - cmd = exec.Command("kind", "load", "docker-image", image) + cmd = exec.Command("go", "run", "sigs.k8s.io/kind@v0.29.0", "load", "docker-image", image) out, err = cmd.CombinedOutput() s.Log(string(out)) s.AssertNil(err, string(out)) diff --git a/extras/hs-test/infra/kind/suite_kind.go b/extras/hs-test/infra/kind/suite_kind.go index 2095b7f4e2..45b24fc3cf 100644 --- a/extras/hs-test/infra/kind/suite_kind.go +++ b/extras/hs-test/infra/kind/suite_kind.go @@ -59,6 +59,8 @@ func (s *KindSuite) SetupSuite() { } else { s.KubeconfigPath = "/home/" + *SudoUser + "/.kube/config" } + s.Log("User: '%s'", *SudoUser) + s.Log("Config path: '%s'", s.KubeconfigPath) s.Config, err = clientcmd.BuildConfigFromFlags("", s.KubeconfigPath) s.AssertNil(err) diff --git a/extras/hs-test/kind_test.go b/extras/hs-test/kind_test.go index 05841b6093..0e0431d0c2 100644 --- a/extras/hs-test/kind_test.go +++ b/extras/hs-test/kind_test.go @@ -37,7 +37,7 @@ func KindIperfVclTest(s *KindSuite) { vcl + " " + ldp + " iperf3 -s -D -4"}) s.AssertNil(err, o) output, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", - vcl + " " + ldp + " iperf3 -c " + s.Pods.ServerGeneric.IpAddress}) + vcl + " " + ldp + " iperf3 -l 1460 -b 10g -c " + s.Pods.ServerGeneric.IpAddress}) s.Log(output) s.AssertNil(err) } From 89bbec8df640e2fc787340db550d02ef34b875a0 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Wed, 9 Jul 2025 14:34:11 +0200 Subject: [PATCH 124/313] arp: fix dump API Add missing fib index to table id conversion in dump API. Type: fix Change-Id: Id5f7325ca31875abb39b33156f3938a051888ad3 Signed-off-by: Klement Sekera --- src/vnet/arp/arp_api.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vnet/arp/arp_api.c b/src/vnet/arp/arp_api.c index 170bace2b0..083fa50aa5 100644 --- a/src/vnet/arp/arp_api.c +++ b/src/vnet/arp/arp_api.c @@ -86,7 +86,8 @@ send_proxy_arp_details (const ip4_address_t * lo_addr, clib_memset (mp, 0, sizeof (*mp)); mp->_vl_msg_id = ntohs (VL_API_PROXY_ARP_DETAILS + REPLY_MSG_ID_BASE); mp->context = ctx->context; - mp->proxy.table_id = htonl (fib_index); + mp->proxy.table_id = clib_host_to_net_u32 ( + fib_table_get_table_id (fib_index, FIB_PROTOCOL_IP4)); ip4_address_encode (lo_addr, mp->proxy.low); ip4_address_encode (hi_addr, mp->proxy.hi); From 10df6146f3e5628a8f3458be24c2796cdd736ec1 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 9 Jul 2025 05:46:09 -0400 Subject: [PATCH 125/313] hs-test: fix go version mismatch Type: test Change-Id: Idce98c261c50ef0da2a4b7c4ee9146faa2194c4b Signed-off-by: Matus Fabian --- extras/hs-test/Makefile | 8 +++++--- extras/hs-test/README.rst | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 11b5235419..643cf52221 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -93,6 +93,8 @@ help: @echo " cleanup-hst - removes all docker containers and namespaces from last test run" @echo " cleanup-perf - removes all kubernetes pods and namespaces from last test run" @echo " list-tests - list all tests" + @echo " install-deps - install software dependencies" + @echo " install-kind-deps - install software dependencies for cluster" @echo @echo "'make build' and 'make test' arguments:" @echo " UBUNTU_VERSION - ubuntu version for docker image" @@ -259,11 +261,11 @@ install-kind-deps: .deps.ok install-deps: @rm -f .deps.ok @if [ -d "/usr/local/go" ]; then \ - echo "Go is already installed. You may have to update it manually if version < 1.22"; \ + echo "Go is already installed. You may have to update it manually if version < 1.23.10"; \ go version; \ else \ - echo "Installing Go 1.22"; \ - wget https://go.dev/dl/go1.22.12.linux-amd64.tar.gz -O /tmp/go1.22.12.linux-amd64.tar.gz && sudo tar -C /usr/local -xzf /tmp/go1.22.12.linux-amd64.tar.gz; \ + echo "Installing Go 1.23"; \ + wget https://go.dev/dl/go1.23.10.linux-$(ARCH).tar.gz -O /tmp/go1.23.10.linux-$(ARCH).tar.gz && sudo tar -C /usr/local -xzf /tmp/go1.23.10.linux-$(ARCH).tar.gz; \ sudo ln -s /usr/local/go/bin/go /usr/bin/go ; \ fi @sudo -E apt-get update diff --git a/extras/hs-test/README.rst b/extras/hs-test/README.rst index 815f8aa8e6..39ca850174 100644 --- a/extras/hs-test/README.rst +++ b/extras/hs-test/README.rst @@ -20,7 +20,6 @@ Anatomy of a test case **Prerequisites**: * Install hs-test dependencies with ``make install-deps`` -* `Install Go `_, it has to be in path of both the running user (follow instructions on Go installation page) and root (run ``sudo visudo`` and edit ``secure_path`` line, run ``sudo go version`` to verify) * Root privileges are required to run tests as it uses Linux ``ip`` command for configuring topology * Tests use *hs-test*'s own docker image, they are rebuilt automatically when needed, you can run ``make build[-debug]`` to do so or use ``FORCE_BUILD=true`` make parameter From a67d90d0975d138e03ffc7a442354a63a66ddc3d Mon Sep 17 00:00:00 2001 From: Andrew Yourtchenko Date: Fri, 4 Jul 2025 18:07:27 +0200 Subject: [PATCH 126/313] memif: avoid double-close of the socket on client abort with un-read data In some cases, there are two times the clib_file_del is called: 1) from memif_disconnect -> memif_socket_close call site in memif_master_conn_fd_error 2) the supposedly defensive code at the end of memif_master_conn_fd_error. This was observed upon using of an artisanal memif client, which did not correctly drain the data written by VPP to the notification socket - a simple Ctrl-C in the client was enough to trivially reproduce the issue. In real world scenario using the stock client this is expected to be a fairly narrow race condition. The issue can be demonstrated as follows: diff --git a/src/vlib/file.c b/src/vlib/file.c index 286b0d1f2..29138f485 100644 --- a/src/vlib/file.c +++ b/src/vlib/file.c @@ -200,6 +200,8 @@ epoll: { clib_file_t *f = e->data.ptr; clib_error_t *err; + clib_warning("EPOLL: count %d, fd index: %d, events: %x", n_fds_ready, f->index, e->events); + if (PREDICT_FALSE (!f->active)) { vlib_file_poll:203: EPOLL: count 1, fd index: 6, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 11 memif_plugin [error ]: memif0/0: default_socket_recvmsg: disconnected Failing case, where the client did not drain the data sent to it: DBGvpp# create interface memif master DBGvpp# set int ip address memif0/0 192.0.2.1/24 DBGvpp# set interface state memif0/0 up DBGvpp# vlib_file_poll:203: EPOLL: count 1, fd index: 6, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 1 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 4 vlib_file_poll:203: EPOLL: count 1, fd index: 7, events: 19 memif_plugin [warn ]: Error on unknown file descriptor 21 vlib/file [error ]: vlib_file_update: epoll_ctl() failed, errno 9 /home/ayourtch/vpp/src/vppinfra/pool.h:291 (_pool_put_index) assertion `!pool_is_free_index (p, index)' fails received signal SIGABRT, PC 0xffffa7f10a50 Type: fix Change-Id: I13247c431605470c6a59d7d4630cefa999733107 Signed-off-by: Andrew Yourtchenko --- src/plugins/memif/socket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/memif/socket.c b/src/plugins/memif/socket.c index c2b11fc2ec..5890e2d91f 100644 --- a/src/plugins/memif/socket.c +++ b/src/plugins/memif/socket.c @@ -629,6 +629,7 @@ memif_master_conn_fd_error (clib_file_t * uf) err = clib_error_return (0, "connection fd error"); memif_disconnect (mif, err); clib_error_free (err); + return 0; } else { From f72488b3229b4a7a7fdfac0be596d87090adc229 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 13 Jun 2025 11:39:23 -0400 Subject: [PATCH 127/313] http: Tunneling UDP over HTTP/2 Type: feature Change-Id: I94f6af893872ae28669b7b9c30d61e58c0b65422 Signed-off-by: Matus Fabian --- extras/hs-test/h2spec_extras/h2spec_extras.go | 454 ++++++++++++++++++ extras/hs-test/infra/suite_vpp_proxy.go | 17 - extras/hs-test/infra/suite_vpp_udp_proxy.go | 34 +- extras/hs-test/infra/utils.go | 36 ++ extras/hs-test/proxy_test.go | 20 +- src/plugins/http/http2/http2.c | 241 +++++++++- 6 files changed, 736 insertions(+), 66 deletions(-) diff --git a/extras/hs-test/h2spec_extras/h2spec_extras.go b/extras/hs-test/h2spec_extras/h2spec_extras.go index b1d86a0b3a..a48cdac137 100644 --- a/extras/hs-test/h2spec_extras/h2spec_extras.go +++ b/extras/hs-test/h2spec_extras/h2spec_extras.go @@ -1,10 +1,15 @@ package h2spec_extras import ( + "bytes" + "errors" "fmt" "slices" "strconv" + "strings" + "github.com/quic-go/quic-go/http3" + "github.com/quic-go/quic-go/quicvarint" "github.com/summerwind/h2spec/config" "github.com/summerwind/h2spec/spec" "golang.org/x/net/http2" @@ -208,6 +213,24 @@ func ConnectHeaders(c *config.Config) []hpack.HeaderField { } } +func readTcpTunnel(conn *spec.Conn, streamID uint32) ([]byte, error) { + actual, passed := conn.WaitEventByType(spec.EventDataFrame) + switch event := actual.(type) { + case spec.DataFrameEvent: + passed = event.Header().StreamID == streamID + default: + passed = false + } + if !passed { + return nil, &spec.TestError{ + Expected: []string{spec.EventDataFrame.String()}, + Actual: actual.String(), + } + } + df, _ := actual.(spec.DataFrameEvent) + return df.Data(), nil +} + func ConnectMethod() *spec.TestGroup { tg := NewTestGroup("2", "CONNECT method") @@ -480,5 +503,436 @@ func ExtendedConnectMethod() *spec.TestGroup { return spec.VerifyStreamError(conn, http2.ErrCodeProtocol) }, }) + + tg.AddTestGroup(ConnectUdp()) + + return tg +} + +func ConnectUdpHeaders(c *config.Config) []hpack.HeaderField { + + headers := spec.CommonHeaders(c) + headers[0].Value = "CONNECT" + headers = append(headers, spec.HeaderField(":protocol", "connect-udp")) + headers = append(headers, spec.HeaderField("capsule-protocol", "?1")) + return headers +} + +func writeCapsule(conn *spec.Conn, streamID uint32, endStream bool, payload []byte) error { + b := make([]byte, 0) + b = quicvarint.Append(b, 0) + b = append(b, payload...) + var capsule bytes.Buffer + err := http3.WriteCapsule(&capsule, 0, b) + if err != nil { + return err + } + + return conn.WriteData(streamID, endStream, capsule.Bytes()) +} + +func readCapsule(conn *spec.Conn, streamID uint32) ([]byte, error) { + actual, passed := conn.WaitEventByType(spec.EventDataFrame) + switch event := actual.(type) { + case spec.DataFrameEvent: + passed = event.Header().StreamID == streamID + default: + passed = false + } + if !passed { + return nil, &spec.TestError{ + Expected: []string{spec.EventDataFrame.String()}, + Actual: actual.String(), + } + } + df, _ := actual.(spec.DataFrameEvent) + r := bytes.NewReader(df.Data()) + capsuleType, payloadReader, err := http3.ParseCapsule(r) + if err != nil { + return nil, err + } + if capsuleType != 0 { + return nil, errors.New("capsule type should be 0") + } + b := make([]byte, 1024) + n, err := payloadReader.Read(b) + if err != nil { + return nil, err + } + if n < 3 { + return nil, errors.New("response payload too short") + } + if b[0] != 0 { + return nil, errors.New("context id should be 0") + } + return b[1:n], nil +} + +func ConnectUdp() *spec.TestGroup { + tg := NewTestGroup("3.1", "Proxying UDP in HTTP") + + tg.AddTestCase(&spec.TestCase{ + Desc: "Tunneling UDP over HTTP/2", + Requirement: "To initiate a UDP tunnel associated with a single HTTP stream, a client issues a request containing the \"connect-udp\" upgrade token. The target of the tunnel is indicated by the client to the UDP proxy via the \"target_host\" and \"target_port\" variables of the URI Template", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectUdpHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + // verify response headers + actual, passed := conn.WaitEventByType(spec.EventHeadersFrame) + switch event := actual.(type) { + case spec.HeadersFrameEvent: + passed = event.Header().StreamID == streamID + default: + passed = false + } + if !passed { + expected := []string{ + fmt.Sprintf("DATA Frame (length:1, flags:0x00, stream_id:%d)", streamID), + } + + return &spec.TestError{ + Expected: expected, + Actual: actual.String(), + } + } + hf, _ := actual.(spec.HeadersFrameEvent) + respHeaders := make([]hpack.HeaderField, 0, 256) + decoder := hpack.NewDecoder(4096, func(f hpack.HeaderField) { respHeaders = append(respHeaders, f) }) + _, err = decoder.Write(hf.HeaderBlockFragment()) + if err != nil { + return err + } + if !slices.Contains(respHeaders, spec.HeaderField("capsule-protocol", "?1")) { + hs := "" + for _, h := range respHeaders { + hs += h.String() + "\n" + } + return &spec.TestError{ + Expected: []string{"\"capsule-protocol: ?1\" header received"}, + Actual: hs, + } + } + if !slices.Contains(respHeaders, spec.HeaderField(":status", "200")) { + hs := "" + for _, h := range respHeaders { + hs += h.String() + "\n" + } + return &spec.TestError{ + Expected: []string{"\":status: 200\" header received"}, + Actual: hs, + } + } + for _, h := range respHeaders { + if h.Name == "content-length" { + return &spec.TestError{ + Expected: []string{"\"content-length\" header must not be used"}, + Actual: h.String(), + } + } + } + + // send and receive data over tunnel + data := []byte("hello") + err = writeCapsule(conn, streamID, false, data) + if err != nil { + return err + } + resp, err := readCapsule(conn, streamID) + if err != nil { + return err + } + if !bytes.Equal(data, resp) { + return &spec.TestError{ + Expected: []string{"capsule payload: " + string(data)}, + Actual: "capsule payload:" + string(resp), + } + } + // try again + err = writeCapsule(conn, streamID, false, data) + if err != nil { + return err + } + resp, err = readCapsule(conn, streamID) + if err != nil { + return err + } + if !bytes.Equal(data, resp) { + return &spec.TestError{ + Expected: []string{"capsule payload: " + string(data)}, + Actual: "capsule payload:" + string(resp), + } + } + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "Multiple tunnels", + Requirement: "In HTTP/2, the data stream of a given HTTP request consists of all bytes sent in DATA frames with the corresponding stream ID.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + maxStreams, ok := conn.Settings[http2.SettingMaxConcurrentStreams] + if !ok { + return spec.ErrSkipped + } + + for i := 0; i < int(maxStreams); i++ { + headers := ConnectUdpHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + streamID += 2 + } + + streamID = 1 + data := []byte("hello") + for i := 0; i < int(maxStreams); i++ { + err = writeCapsule(conn, streamID, false, data) + if err != nil { + return err + } + } + + for i := 0; i < int(maxStreams); i++ { + resp, err := readCapsule(conn, streamID) + if err != nil { + return err + } + if !bytes.Equal(data, resp) { + return &spec.TestError{ + Expected: []string{"capsule payload: " + string(data)}, + Actual: "capsule payload:" + string(resp), + } + } + } + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "Tunnel closed by client (with attached data)", + Requirement: "A proxy that receives a DATA frame with the END_STREAM flag set sends the attached data and close UDP connection.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectUdpHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + // close tunnel + data := []byte("hello") + err = writeCapsule(conn, streamID, true, data) + if err != nil { + return err + } + + // wait for DATA frame with END_STREAM flag set + err = VerifyTunnelClosed(conn) + if err != nil { + return err + } + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "Tunnel closed by client (empty DATA frame)", + Requirement: "The final DATA frame could be empty.", + Run: func(c *config.Config, conn *spec.Conn) error { + var streamID uint32 = 1 + + err := conn.Handshake() + if err != nil { + return err + } + + headers := ConnectUdpHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: streamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, streamID) + if err != nil { + return err + } + + // send and receive data over tunnel + data := []byte("hello") + err = writeCapsule(conn, streamID, false, data) + if err != nil { + return err + } + resp, err := readCapsule(conn, streamID) + if err != nil { + return err + } + if !bytes.Equal(data, resp) { + return &spec.TestError{ + Expected: []string{"capsule payload: " + string(data)}, + Actual: "capsule payload:" + string(resp), + } + } + + // close tunnel + conn.WriteData(streamID, true, []byte("")) + + // wait for DATA frame with END_STREAM flag set + err = VerifyTunnelClosed(conn) + if err != nil { + return err + } + + return nil + }, + }) + + tg.AddTestCase(&spec.TestCase{ + Desc: "CONNECT and CONNECT-UDP on single connection", + Requirement: "One stream establish TCP tunnel and second UDP tunnel.", + Run: func(c *config.Config, conn *spec.Conn) error { + err := conn.Handshake() + if err != nil { + return err + } + + var udpTunnelStreamID uint32 = 1 + var tcpTunnelStreamID uint32 = 3 + + headers := ConnectUdpHeaders(c) + hp := http2.HeadersFrameParam{ + StreamID: udpTunnelStreamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, udpTunnelStreamID) + if err != nil { + return err + } + + pathSplit := strings.Split(c.Path, "/") + path := fmt.Sprintf("%s:%s", pathSplit[4], pathSplit[5]) + headers = []hpack.HeaderField{ + spec.HeaderField(":method", "CONNECT"), + spec.HeaderField(":authority", path), + } + hp = http2.HeadersFrameParam{ + StreamID: tcpTunnelStreamID, + EndStream: false, + EndHeaders: true, + BlockFragment: conn.EncodeHeaders(headers), + } + conn.WriteHeaders(hp) + err = spec.VerifyHeadersFrame(conn, tcpTunnelStreamID) + if err != nil { + return err + } + + // send and receive data over UDP tunnel + udpData := []byte("hello UDP") + err = writeCapsule(conn, udpTunnelStreamID, false, udpData) + if err != nil { + return err + } + udpResp, err := readCapsule(conn, udpTunnelStreamID) + if err != nil { + return err + } + if !bytes.Equal(udpData, udpResp) { + return &spec.TestError{ + Expected: []string{"capsule payload: " + string(udpData)}, + Actual: "capsule payload:" + string(udpResp), + } + } + + // send and receive data over TCP tunnel + tcpData := []byte("hello TCP") + conn.WriteData(tcpTunnelStreamID, false, tcpData) + tcpResp, err := readTcpTunnel(conn, tcpTunnelStreamID) + if !bytes.Equal(tcpData, tcpResp) { + return &spec.TestError{ + Expected: []string{"payload: " + string(tcpData)}, + Actual: "payload:" + string(tcpResp), + } + } + + // send and receive data over TCP tunnel + conn.WriteData(tcpTunnelStreamID, false, tcpData) + tcpResp, err = readTcpTunnel(conn, tcpTunnelStreamID) + if !bytes.Equal(tcpData, tcpResp) { + return &spec.TestError{ + Expected: []string{"payload: " + string(tcpData)}, + Actual: "payload:" + string(tcpResp), + } + } + + // send and receive data over UDP tunnel + err = writeCapsule(conn, udpTunnelStreamID, false, udpData) + if err != nil { + return err + } + udpResp, err = readCapsule(conn, udpTunnelStreamID) + if err != nil { + return err + } + if !bytes.Equal(udpData, udpResp) { + return &spec.TestError{ + Expected: []string{"capsule payload: " + string(udpData)}, + Actual: "capsule payload:" + string(udpResp), + } + } + + return nil + }, + }) + return tg } diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index d1c125e3a5..d43a588fe4 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -13,7 +13,6 @@ import ( "os" "reflect" "runtime" - "strconv" "strings" "time" @@ -242,22 +241,6 @@ func handleConn(conn net.Conn) { } } -func (s *VppProxySuite) StartEchoServer() *net.TCPListener { - listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP(s.ServerAddr()), Port: int(s.Ports.Server)}) - s.AssertNil(err, fmt.Sprint(err)) - go func() { - for { - conn, err := listener.Accept() - if err != nil { - continue - } - go handleConn(conn) - } - }() - s.Log("* started tcp echo server " + s.ServerAddr() + ":" + strconv.Itoa(int(s.Ports.Server))) - return listener -} - var _ = Describe("VppProxySuite", Ordered, ContinueOnFailure, func() { var s VppProxySuite BeforeAll(func() { diff --git a/extras/hs-test/infra/suite_vpp_udp_proxy.go b/extras/hs-test/infra/suite_vpp_udp_proxy.go index c424790db7..912ab64eaa 100644 --- a/extras/hs-test/infra/suite_vpp_udp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_udp_proxy.go @@ -8,7 +8,6 @@ import ( "os" "reflect" "runtime" - "strconv" "strings" "time" @@ -118,25 +117,6 @@ func (s *VppUdpProxySuite) ClientAddr() string { return s.Interfaces.Client.Ip4AddressString() } -func (s *VppUdpProxySuite) StartEchoServer() *net.UDPConn { - conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(s.ServerAddr()), Port: s.Ports.Server}) - s.AssertNil(err, fmt.Sprint(err)) - go func() { - for { - b := make([]byte, 1500) - n, addr, err := conn.ReadFrom(b) - if err != nil { - return - } - if _, err := conn.WriteTo(b[:n], addr); err != nil { - return - } - } - }() - s.Log("* started udp echo server " + s.ServerAddr() + ":" + strconv.Itoa(s.Ports.Server)) - return conn -} - func (s *VppUdpProxySuite) ClientSendReceive(toSend []byte, rcvBuffer []byte) (int, error) { proxiedConn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(s.ClientAddr()), Port: 0}, @@ -270,6 +250,11 @@ var _ = Describe("H2SpecUdpProxySuite", Ordered, ContinueOnFailure, func() { }{ {desc: "extras/3/1"}, {desc: "extras/3/2"}, + {desc: "extras/3.1/1"}, + {desc: "extras/3.1/2"}, + {desc: "extras/3.1/3"}, + {desc: "extras/3.1/4"}, + {desc: "extras/3.1/5"}, } for _, test := range testCases { @@ -278,8 +263,13 @@ var _ = Describe("H2SpecUdpProxySuite", Ordered, ContinueOnFailure, func() { It(testName, func(ctx SpecContext) { s.Log(testName + ": BEGIN") vppProxy := s.Containers.VppProxy.VppInstance - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() + // this one will open TCP tunnel too + if strings.Contains(test.desc, "extras/3.1/5") { + remoteTcpServerConn := s.StartTcpEchoServer(s.ServerAddr(), s.Ports.Server) + defer remoteTcpServerConn.Close() + } cmd := fmt.Sprintf("test proxy server fifo-size 512k server-uri https://%s/%d", s.VppProxyAddr(), s.Ports.Proxy) s.Log(vppProxy.Vppctl(cmd)) path := fmt.Sprintf("/.well-known/masque/udp/%s/%d/", s.ServerAddr(), s.Ports.Server) @@ -287,7 +277,7 @@ var _ = Describe("H2SpecUdpProxySuite", Ordered, ContinueOnFailure, func() { Host: s.VppProxyAddr(), Port: s.Ports.Proxy, Path: path, - Timeout: time.Second * s.MaxTimeout, + Timeout: s.MaxTimeout, MaxHeaderLen: 4096, TLS: true, Insecure: true, diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index e320345d5a..14d084014b 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -10,6 +10,7 @@ import ( "net/http/httputil" "os" "os/exec" + "strconv" "strings" "time" @@ -327,3 +328,38 @@ func (s *HstSuite) GetCoreProcessName(file string) (string, bool) { } return "", false } + +func (s *HstSuite) StartTcpEchoServer(addr string, port int) *net.TCPListener { + listener, err := net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP(addr), Port: port}) + s.AssertNil(err, fmt.Sprint(err)) + go func() { + for { + conn, err := listener.Accept() + if err != nil { + continue + } + go handleConn(conn) + } + }() + s.Log("* started tcp echo server " + addr + ":" + strconv.Itoa(port)) + return listener +} + +func (s *HstSuite) StartUdpEchoServer(addr string, port int) *net.UDPConn { + conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(addr), Port: port}) + s.AssertNil(err, fmt.Sprint(err)) + go func() { + for { + b := make([]byte, 1500) + n, addr, err := conn.ReadFrom(b) + if err != nil { + return + } + if _, err := conn.WriteTo(b[:n], addr); err != nil { + return + } + } + }() + s.Log("* started udp echo server " + addr + ":" + strconv.Itoa(port)) + return conn +} diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index 40f3558b4c..183cca7252 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -327,7 +327,7 @@ func vppConnectProxyStressLoad(s *VppProxySuite, proxyPort string) { } func VppConnectProxyStressTest(s *VppProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartTcpEchoServer(s.ServerAddr(), int(s.Ports.Server)) defer remoteServerConn.Close() s.ConfigureVppProxy("http", s.Ports.Proxy) @@ -341,7 +341,7 @@ func VppConnectProxyStressTest(s *VppProxySuite) { func VppConnectProxyStressMWTest(s *VppProxySuite) { s.CpusPerVppContainer = 3 s.SetupTest() - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartTcpEchoServer(s.ServerAddr(), int(s.Ports.Server)) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -355,7 +355,7 @@ func VppConnectProxyStressMWTest(s *VppProxySuite) { } func VppProxyUdpTest(s *VppUdpProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -372,7 +372,7 @@ func VppProxyUdpTest(s *VppUdpProxySuite) { func VppProxyUdpMigrationMWTest(s *VppUdpProxySuite) { s.CpusPerVppContainer = 3 s.SetupTest() - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -394,7 +394,7 @@ func VppProxyUdpMigrationMWTest(s *VppUdpProxySuite) { } func VppConnectUdpProxyTest(s *VppUdpProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -441,7 +441,7 @@ func VppConnectUdpInvalidTargetTest(s *VppUdpProxySuite) { } func VppConnectUdpInvalidCapsuleTest(s *VppUdpProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -471,7 +471,7 @@ func VppConnectUdpInvalidCapsuleTest(s *VppUdpProxySuite) { } func VppConnectUdpUnknownCapsuleTest(s *VppUdpProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -500,7 +500,7 @@ func VppConnectUdpUnknownCapsuleTest(s *VppUdpProxySuite) { } func VppConnectUdpClientCloseTest(s *VppUdpProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -631,7 +631,7 @@ func vppConnectUdpStressLoad(s *VppUdpProxySuite) { } func VppConnectUdpStressTest(s *VppUdpProxySuite) { - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance @@ -647,7 +647,7 @@ func VppConnectUdpStressTest(s *VppUdpProxySuite) { func VppConnectUdpStressMWTest(s *VppUdpProxySuite) { s.CpusPerVppContainer = 3 s.SetupTest() - remoteServerConn := s.StartEchoServer() + remoteServerConn := s.StartUdpEchoServer(s.ServerAddr(), s.Ports.Server) defer remoteServerConn.Close() vppProxy := s.Containers.VppProxy.VppInstance diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 5888733490..8e9e6d923a 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -57,6 +57,7 @@ typedef struct http2_req_ u8 *payload; u32 payload_len; clib_llist_anchor_t sched_list; + http_req_state_t app_reply_next_state; void (*dispatch_headers_cb) (struct http2_req_ *req, http_conn_t *hc, u8 *n_emissions, clib_llist_index_t *next_ri); void (*dispatch_data_cb) (struct http2_req_ *req, http_conn_t *hc, @@ -618,6 +619,95 @@ http2_sched_dispatch_tunnel (http2_req_t *req, http_conn_t *hc, http_io_ts_after_write (hc, 0); } +static void +http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, + u8 *n_emissions) +{ + http2_conn_ctx_t *h2c; + u32 max_write, max_read, dgram_size, capsule_size, n_written; + session_dgram_hdr_t hdr; + u8 fh[HTTP2_FRAME_HEADER_SIZE]; + u8 *buf, *payload; + + *n_emissions += HTTP2_SCHED_WEIGHT_DATA_INLINE; + + max_read = http_io_as_max_read (&req->base); + if (max_read == 0) + { + HTTP_DBG (2, "max_read == 0"); + transport_connection_reschedule (&req->base.connection); + return; + } + /* read datagram header */ + http_io_as_read (&req->base, (u8 *) &hdr, sizeof (hdr), 1); + ASSERT (hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN); + dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN; + ASSERT (max_read >= dgram_size); + + h2c = http2_conn_ctx_get_w_thread (hc); + + if (PREDICT_FALSE ( + (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD) > + h2c->peer_settings.max_frame_size)) + { + /* drop datagram if not fit into frame */ + HTTP_DBG (1, "datagram too large, dropped"); + http_io_as_drain (&req->base, dgram_size); + return; + } + + if (req->peer_window < + (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD)) + { + /* mark that we need window update on stream */ + HTTP_DBG (1, "not enough space in stream window for capsule"); + req->flags |= HTTP2_REQ_F_NEED_WINDOW_UPDATE; + } + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write -= HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD; + max_write = clib_min (max_write, h2c->peer_window); + if (PREDICT_FALSE (max_write < hdr.data_length)) + { + /* we should have at least 16kB free space in underlying transport, + * maybe peer is doing small connection window updates */ + HTTP_DBG (1, "datagram dropped"); + http_io_as_drain (&req->base, dgram_size); + return; + } + + buf = http_get_tx_buf (hc); + /* create capsule header */ + payload = http_encap_udp_payload_datagram (buf, hdr.data_length); + capsule_size = (payload - buf) + hdr.data_length; + /* read payload */ + http_io_as_read (&req->base, payload, hdr.data_length, 1); + http_io_as_drain (&req->base, dgram_size); + + req->peer_window -= capsule_size; + h2c->peer_window -= capsule_size; + + http2_frame_write_data_header (capsule_size, req->stream_id, 0, fh); + + svm_fifo_seg_t segs[2] = { { fh, HTTP2_FRAME_HEADER_SIZE }, + { buf, capsule_size } }; + n_written = http_io_ts_write_segs (hc, segs, 2, 0); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + capsule_size)); + + if (max_read - dgram_size) + { + /* schedule for next round if we have more data */ + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + transport_connection_reschedule (&req->base.connection); + + http_io_ts_after_write (hc, 0); +} + static void http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, u8 *n_emissions, @@ -701,13 +791,33 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, response = http_get_tx_buf (hc); date = format (0, "%U", format_http_time_now, hc); - control_data.sc = msg.code; control_data.content_len = msg.data.body_len; control_data.server_name = hc->app_name; control_data.server_name_len = vec_len (hc->app_name); control_data.date = date; control_data.date_len = vec_len (date); + if (req->base.is_tunnel) + { + switch (msg.code) + { + case HTTP_STATUS_SWITCHING_PROTOCOLS: + /* remap status code for extended connect response */ + msg.code = HTTP_STATUS_OK; + case HTTP_STATUS_OK: + case HTTP_STATUS_CREATED: + case HTTP_STATUS_ACCEPTED: + /* tunnel established if 2xx (Successful) response to CONNECT */ + control_data.content_len = HPACK_ENCODER_SKIP_CONTENT_LEN; + break; + default: + /* tunnel not established */ + req->base.is_tunnel = 0; + break; + } + } + control_data.sc = msg.code; + if (msg.data.headers_len) { n_deq += msg.data.type == HTTP_MSG_DATA_PTR ? sizeof (uword) : @@ -728,10 +838,6 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, stream_id = req->stream_id; - /* tunnel established if 2xx (Successful) response to CONNECT */ - req->base.is_tunnel = - (req->base.is_tunnel && http_status_code_str[msg.code][0] == '2'); - /* END_STREAM flag need to be set in HEADERS frame */ if (msg.data.body_len) { @@ -757,7 +863,11 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, } else if (req->base.is_tunnel) { - req->dispatch_data_cb = http2_sched_dispatch_tunnel; + if (req->base.upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP && + hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + req->dispatch_data_cb = http2_sched_dispatch_udp_tunnel; + else + req->dispatch_data_cb = http2_sched_dispatch_tunnel; transport_connection_reschedule (&req->base.connection); /* cleanup some stuff we don't need anymore in tunnel mode */ vec_free (req->base.headers); @@ -914,6 +1024,8 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, req->base.headers_offset = control_data.headers - wrk->header_list; req->base.headers_len = control_data.headers_len; + req->app_reply_next_state = HTTP_REQ_STATE_APP_IO_MORE_DATA; + if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_METHOD_PARSED)) { HTTP_DBG (1, ":method pseudo-header missing in request"); @@ -954,6 +1066,7 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, } if (control_data.method == HTTP_REQ_CONNECT) { + req->app_reply_next_state = HTTP_REQ_STATE_TUNNEL; if (control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_PROTOCOL_PARSED) { /* extended CONNECT (RFC8441) */ @@ -985,6 +1098,9 @@ http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, sp); return HTTP_SM_STOP; } + if (req->base.upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP && + hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + req->app_reply_next_state = HTTP_REQ_STATE_UDP_TUNNEL; } else { @@ -1154,9 +1270,84 @@ http2_req_state_tunnel_rx (http_conn_t *hc, http2_req_t *req, http_io_as_write (&req->base, req->payload, req->payload_len); http_app_worker_rx_notify (&req->base); + switch (req->stream_state) + { + case HTTP2_STREAM_STATE_HALF_CLOSED: + HTTP_DBG (1, "client want to close tunnel"); + session_transport_closing_notify (&req->base.connection); + break; + case HTTP2_STREAM_STATE_CLOSED: + HTTP_DBG (1, "client closed tunnel"); + http2_stream_close (req, hc); + break; + default: + break; + } + return HTTP_SM_STOP; } +static http_sm_result_t +http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, + transport_send_params_t *sp, + http2_error_t *error) +{ + int rv; + u8 payload_offset; + u64 payload_len; + session_dgram_hdr_t hdr; + + HTTP_DBG (1, "udp tunnel received data from client"); + + rv = http_decap_udp_payload_datagram (req->payload, req->payload_len, + &payload_offset, &payload_len); + HTTP_DBG (1, "rv=%d, payload_offset=%u, payload_len=%llu", rv, + payload_offset, payload_len); + if (PREDICT_FALSE (rv != 0)) + { + if (rv < 0) + { + /* capsule datagram is invalid (stream need to be aborted) */ + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + else + { + /* unknown capsule should be skipped */ + return HTTP_SM_STOP; + } + } + /* check if we have the full capsule */ + if (PREDICT_FALSE (req->payload_len != (payload_offset + payload_len))) + { + HTTP_DBG (1, "capsule not complete"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + if (http_io_as_max_write (&req->base) < (sizeof (hdr) + payload_len)) + { + clib_warning ("app's rx fifo full"); + http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, sp); + return HTTP_SM_STOP; + } + + hdr.data_length = payload_len; + hdr.data_offset = 0; + + /* send datagram header and payload */ + svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, + { req->payload + payload_offset, payload_len } }; + http_io_as_write_segs (&req->base, segs, 2); + http_app_worker_rx_notify (&req->base); + + if (req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED) + { + HTTP_DBG (1, "client want to close tunnel"); + session_transport_closing_notify (&req->base.connection); + } + + return HTTP_SM_STOP; +} /*************************************/ /* request state machine handlers TX */ /*************************************/ @@ -1180,9 +1371,7 @@ http2_req_state_wait_app_reply (http_conn_t *hc, http2_req_t *req, clib_llist_add_tail (wrk->req_pool, sched_list, req, he); http2_conn_schedule (h2c, hc->c_thread_index); - http_req_state_change (&req->base, req->base.is_tunnel ? - HTTP_REQ_STATE_TUNNEL : - HTTP_REQ_STATE_APP_IO_MORE_DATA); + http_req_state_change (&req->base, req->app_reply_next_state); http_req_deschedule (&req->base, sp); return HTTP_SM_STOP; @@ -1250,8 +1439,9 @@ static http2_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { 0, /* wait transport method */ http2_req_state_wait_app_reply, http2_req_state_app_io_more_data, + /* both can be same, we use different scheduler data dispatch cb */ + http2_req_state_tunnel_tx, http2_req_state_tunnel_tx, - 0, /* udp tunnel */ }; static http2_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { @@ -1263,7 +1453,7 @@ static http2_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { 0, /* wait app reply */ 0, /* app io more data */ http2_req_state_tunnel_rx, - 0, /* udp tunnel */ + http2_req_state_udp_tunnel_rx, }; static_always_inline int @@ -1476,7 +1666,7 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) /* bogus state */ if (hc->flags & HTTP_CONN_F_IS_SERVER && - req->stream_state != HTTP2_STREAM_STATE_OPEN) + req->stream_state != HTTP2_STREAM_STATE_OPEN && !req->base.is_tunnel) { HTTP_DBG (1, "error: stream already half-closed"); http2_stream_error (hc, req, HTTP2_ERROR_STREAM_CLOSED, 0); @@ -1497,15 +1687,32 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) if (fh->flags & HTTP2_FRAME_FLAG_END_STREAM) { - req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + HTTP_DBG (1, "END_STREAM flag set"); if (req->base.is_tunnel) { - session_transport_closing_notify (&req->base.connection); - HTTP_DBG (1, "client closed tunnel"); + /* client can initiate or confirm tunnel close */ + req->stream_state = + req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED ? + HTTP2_STREAM_STATE_CLOSED : + HTTP2_STREAM_STATE_HALF_CLOSED; /* final DATA frame could be empty */ if (fh->length == 0) - return HTTP2_ERROR_NO_ERROR; + { + if (req->stream_state == HTTP2_STREAM_STATE_CLOSED) + { + HTTP_DBG (1, "client closed tunnel"); + http2_stream_close (req, hc); + } + else + { + HTTP_DBG (1, "client want to close tunnel"); + session_transport_closing_notify (&req->base.connection); + } + return HTTP2_ERROR_NO_ERROR; + } } + else + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; } rx_buf = http_get_rx_buf (hc); @@ -1995,7 +2202,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, case HTTP2_STREAM_STATE_HALF_CLOSED: HTTP_DBG (1, "proxy confirmed close"); http2_tunnel_send_close (hc, req); - session_transport_closed_notify (&req->base.connection); + http2_stream_close (req, hc); break; default: session_transport_closed_notify (&req->base.connection); From e2ba9ec4e01a82c9b4e67305b11e3466831578f7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 10 Jul 2025 07:44:07 -0400 Subject: [PATCH 128/313] hsa: http cli server add http1-only option Type: improvement Change-Id: I2d3656ee7e154a44efab7501399de4b1fc696dc7 Signed-off-by: Matus Fabian --- extras/hs-test/http1_test.go | 2 +- src/plugins/hs_apps/http_cli.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/extras/hs-test/http1_test.go b/extras/hs-test/http1_test.go index e3a7002337..6bc5c98749 100644 --- a/extras/hs-test/http1_test.go +++ b/extras/hs-test/http1_test.go @@ -288,7 +288,7 @@ func HttpCliTest(s *VethsSuite) { func HttpCliTlsTest(s *VethsSuite) { uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1 - s.Containers.ServerVpp.VppInstance.Vppctl("http cli server uri " + uri) + s.Containers.ServerVpp.VppInstance.Vppctl("http cli server http1-only uri " + uri) o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + " uri " + uri + " query /show/version") diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index 40acf6a163..6fddd5e11c 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,22 @@ typedef struct u8 *resp_headers_buf; } hcs_session_t; +#define foreach_hcs_listener_flags _ (HTTP1_ONLY) + +typedef enum hcs_listener_flags_bit_ +{ +#define _(sym) HCS_LISTENER_F_BIT_##sym, + foreach_hcs_listener_flags +#undef _ +} hcs_listener_flags_bit_t; + +typedef enum hcs_listener_flags_ +{ +#define _(sym) HCS_LISTENER_F_##sym = 1 << HCS_LISTENER_F_BIT_##sym, + foreach_hcs_listener_flags +#undef _ +} __clib_packed hcs_listener_flags_t; + typedef struct { hcs_session_t **sessions; @@ -70,6 +87,7 @@ typedef struct u32 fifo_size; u8 *uri; vlib_main_t *vlib_main; + u8 flags; /* hash table to store uri -> uri map pool index */ uword *index_by_uri; @@ -649,6 +667,8 @@ hcs_listen () &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, sizeof (transport_endpt_crypto_cfg_t)); ext_cfg->crypto.ckpair_index = hcm->ckpair_index; + if (hcm->flags & HCS_LISTENER_F_HTTP1_ONLY) + ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; } rv = vnet_listen (a); @@ -774,6 +794,8 @@ hcs_create_command_fn (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "secret %lu", &hcm->appns_secret)) ; + else if (unformat (line_input, "http1-only")) + hcm->flags |= HCS_LISTENER_F_HTTP1_ONLY; else if (unformat (line_input, "listener")) { if (unformat (line_input, "add")) From 29d66fb249659615da8e41437f34297e2814ebcc Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Thu, 10 Jul 2025 09:28:04 -0400 Subject: [PATCH 129/313] hs-test: memleak tests fixes & json raport generation Type: improvement Change-Id: Ie2c18224d7073dd4821cb7716eb124f26dbe44ec Signed-off-by: Semir Sionek --- extras/hs-test/Makefile | 1 + extras/hs-test/hs_test.sh | 10 ++++++---- extras/hs-test/http1_test.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 643cf52221..39c5f08e50 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -189,6 +189,7 @@ test-cov: .deps.ok .build.cov.ok wipe-lcov $(MAKE) -C ../.. test-cov-post-standalone HS_TEST=1 .PHONY: test-leak +test-leak: FORCE_BUILD=false test-leak: .deps.ok .build_debug.ok @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) \ --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index 0337489b95..a6c70a2bd3 100644 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -172,12 +172,14 @@ args="$args -sudo_user $sudo_user" if [ $leak_check_set -eq 1 ]; then if [ $focused_test -eq 0 ]; then - echo -e "\e[1;31ma single test has to be specified when leak_check is set\e[1;0m" + echo -e "\e[1;31ma single test has to be specified via TEST var when leak_check is set\e[1;0m" exit 2 + else + if [[ $tc_list != *"MemLeak"* ]]; then + echo -e "\e[1;31ma none of the selected tests are memleak tests\e[1;0m" + exit 2 + fi fi - ginkgo_args="--focus ${tc_names[0]}" - sudo -E go run github.com/onsi/ginkgo/v2/ginkgo $ginkgo_args -- $args - exit 0 fi if [ -n "${BUILD_NUMBER}" ]; then diff --git a/extras/hs-test/http1_test.go b/extras/hs-test/http1_test.go index 6bc5c98749..b806826e19 100644 --- a/extras/hs-test/http1_test.go +++ b/extras/hs-test/http1_test.go @@ -1016,7 +1016,7 @@ func HttpInvalidClientRequestMemLeakTest(s *Http1Suite) { /* no goVPP less noise */ vpp.Disconnect() - vpp.Vppctl("http cli server") + vpp.Vppctl("http cli server uri http://" + serverAddress) /* warmup request (FIB) */ _, err := TcpSendReceive(serverAddress, "GET / HTTP/1.1\r\n") From 19e0d0ef8a6537c17d80c98365728b9e52e305fc Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Wed, 18 Jun 2025 16:20:51 -0400 Subject: [PATCH 130/313] quic: don't crash when no quic engine is available - When there is no quic engine available, vpp crashes with an assert on startup which is not production friendly. Instead, emit warning message to stderr. - Fix crash in session_transport_closing_notify if a remote closed notification happens on an invalid session in the quicly engine. - Fix compilation errors when QUIC_DEBUG is set to 2 - Fix crash in 'show quic crypto context' command when quic engine is not present. - Add quic engine type to 'show quic' output. Type: fix Change-Id: I83b9e69873fa3cd61d1d852b78ed1ccd2112825c Signed-off-by: Dave Wallace --- src/plugins/quic/quic.c | 141 ++++++++++++------- src/plugins/quic_quicly/quic_quicly.c | 6 +- src/plugins/quic_quicly/quic_quicly_crypto.c | 22 ++- src/vnet/session/transport.c | 3 +- 4 files changed, 110 insertions(+), 62 deletions(-) diff --git a/src/plugins/quic/quic.c b/src/plugins/quic/quic.c index 1daf78b58f..3a6ae06baf 100644 --- a/src/plugins/quic/quic.c +++ b/src/plugins/quic/quic.c @@ -74,6 +74,21 @@ quic_list_crypto_context_command_fn (vlib_main_t *vm, unformat_input_t *input, crypto_context_t *crctx; vlib_thread_main_t *vtm = vlib_get_thread_main (); int i, num_threads = 1 /* main thread */ + vtm->n_threads; + quic_main_t *qm = &quic_main; + + session_cli_return_if_not_enabled (); + if (qm->engine_type == QUIC_ENGINE_NONE) + { + vlib_cli_output (vm, "No QUIC engine plugin enabled"); + return 0; + } + if (qm->engine_is_initialized[qm->engine_type] == 0) + { + vlib_cli_output (vm, "quic engine %s not initialized", + quic_engine_type_str (qm->engine_type)); + return 0; + } + for (i = 0; i < num_threads; i++) { pool_foreach (crctx, quic_wrk_ctx_get (&quic_main, i)->crypto_ctx_pool) @@ -805,7 +820,10 @@ static session_cb_vft_t quic_app_cb_vft = { .app_cert_key_pair_delete_callback = quic_app_cert_key_pair_delete_callback, }; -static const transport_proto_vft_t quic_proto = { +static clib_error_t *quic_enable (vlib_main_t *vm, u8 is_en); + +static transport_proto_vft_t quic_proto = { + .enable = quic_enable, .connect = quic_connect, .close = quic_proto_on_close, .start_listen = quic_start_listen, @@ -828,6 +846,58 @@ static const transport_proto_vft_t quic_proto = { }, }; +static clib_error_t * +quic_enable (vlib_main_t *vm, u8 is_en) +{ + quic_main_t *qm = &quic_main; + quic_worker_ctx_t *wrk_ctx; + quic_ctx_t *ctx; + crypto_context_t *crctx; + vlib_thread_main_t *vtm = vlib_get_thread_main (); + int i; + + qm->engine_type = + quic_get_engine_type (QUIC_ENGINE_QUICLY, QUIC_ENGINE_OPENSSL); + if (qm->engine_type == QUIC_ENGINE_NONE) + { + /* Prevent crash in transport layer callbacks with no quic engine */ + quic_proto.connect = 0; + quic_proto.start_listen = 0; + clib_warning ( + "ERROR: NO QUIC ENGINE PLUGIN ENABLED!" + "\nEnable a quic engine plugin in the startup configuration."); + return clib_error_return (0, "No QUIC engine plugin enabled"); + } + + QUIC_DBG (1, "QUIC engine %s init", quic_engine_type_str (qm->engine_type)); + if (!is_en || qm->engine_is_initialized[qm->engine_type]) + return 0; + + qm->quic_input_node = &quic_input_node; + qm->num_threads = 1 /* main thread */ + vtm->n_threads; + vec_validate (quic_main.wrk_ctx, qm->num_threads - 1); + + for (i = 0; i < qm->num_threads; i++) + { + wrk_ctx = quic_wrk_ctx_get (qm, i); + pool_get_aligned_safe (wrk_ctx->crypto_ctx_pool, crctx, + CLIB_CACHE_LINE_BYTES); + pool_program_safe_realloc ((void **) &wrk_ctx->crypto_ctx_pool, + QUIC_CRYPTO_CTX_POOL_PER_THREAD_SIZE, + CLIB_CACHE_LINE_BYTES); + pool_get_aligned_safe (wrk_ctx->ctx_pool, ctx, CLIB_CACHE_LINE_BYTES); + pool_program_safe_realloc ((void **) &wrk_ctx->ctx_pool, + QUIC_CTX_POOL_PER_THREAD_SIZE, + CLIB_CACHE_LINE_BYTES); + } + + QUIC_DBG (1, "Initializing quic engine to %s", + quic_engine_type_str (qm->engine_type)); + quic_eng_engine_init (qm); + qm->engine_is_initialized[qm->engine_type] = 1; + return 0; +} + static void quic_update_fifo_size () { @@ -849,22 +919,19 @@ quic_update_fifo_size () static clib_error_t * quic_init (vlib_main_t * vm) { - u32 segment_size = 256 << 20; - vlib_thread_main_t *vtm = vlib_get_thread_main (); + quic_main_t *qm = &quic_main; vnet_app_attach_args_t _a, *a = &_a; u64 options[APP_OPTIONS_N_OPTIONS]; - quic_main_t *qm = &quic_main; + // TODO: Don't use hard-coded values for segment_size and seed[] + u32 segment_size = 256 << 20; u8 seed[32]; QUIC_DBG (1, "QUIC plugin init"); - qm->quic_input_node = &quic_input_node; if (syscall (SYS_getrandom, &seed, sizeof (seed), 0) != sizeof (seed)) return clib_error_return_unix (0, "getrandom() failed"); RAND_seed (seed, sizeof (seed)); - qm->num_threads = 1 /* main thread */ + vtm->n_threads; - clib_memset (a, 0, sizeof (*a)); clib_memset (options, 0, sizeof (options)); @@ -886,7 +953,6 @@ quic_init (vlib_main_t * vm) clib_warning ("failed to attach quic app"); return clib_error_return (0, "failed to attach quic app"); } - qm->app_index = a->app_index; transport_register_protocol (TRANSPORT_PROTO_QUIC, &quic_proto, @@ -898,53 +964,7 @@ quic_init (vlib_main_t * vm) return 0; } -static void -quic_engine_init () -{ - quic_main_t *qm = &quic_main; - quic_worker_ctx_t *wrk_ctx; - quic_ctx_t *ctx; - crypto_context_t *crctx; - int i; - - vec_validate (quic_main.wrk_ctx, qm->num_threads - 1); - - QUIC_DBG (1, "Initializing quic engine to %s", - quic_engine_type_str (qm->engine_type)); - - for (i = 0; i < qm->num_threads; i++) - { - wrk_ctx = quic_wrk_ctx_get (qm, i); - pool_get_aligned_safe (wrk_ctx->crypto_ctx_pool, crctx, - CLIB_CACHE_LINE_BYTES); - pool_program_safe_realloc ((void **) &wrk_ctx->crypto_ctx_pool, - QUIC_CRYPTO_CTX_POOL_PER_THREAD_SIZE, - CLIB_CACHE_LINE_BYTES); - pool_get_aligned_safe (wrk_ctx->ctx_pool, ctx, CLIB_CACHE_LINE_BYTES); - pool_program_safe_realloc ((void **) &wrk_ctx->ctx_pool, - QUIC_CTX_POOL_PER_THREAD_SIZE, - CLIB_CACHE_LINE_BYTES); - } - - quic_eng_engine_init (qm); - qm->engine_is_initialized[qm->engine_type] = 1; -} - -static clib_error_t * -quic_main_loop_init (vlib_main_t *vm) -{ - quic_main_t *qm = &quic_main; - - qm->engine_type = - quic_get_engine_type (QUIC_ENGINE_QUICLY, QUIC_ENGINE_OPENSSL); - QUIC_ASSERT (qm->engine_type != QUIC_ENGINE_NONE); - - quic_engine_init (); - return 0; -} - VLIB_INIT_FUNCTION (quic_init); -VLIB_MAIN_LOOP_ENTER_FUNCTION (quic_main_loop_init); static clib_error_t * quic_plugin_crypto_command_fn (vlib_main_t *vm, unformat_input_t *input, @@ -1159,9 +1179,22 @@ quic_show_connections_command_fn (vlib_main_t *vm, unformat_input_t *input, u32 num_workers = vlib_num_workers (); clib_error_t *error = 0; quic_ctx_t *ctx = NULL; + quic_main_t *qm = &quic_main; session_cli_return_if_not_enabled (); + if (qm->engine_type == QUIC_ENGINE_NONE) + { + vlib_cli_output (vm, "No QUIC engine plugin enabled"); + return 0; + } + if (qm->engine_is_initialized[qm->engine_type] == 0) + { + vlib_cli_output (vm, "quic engine %s not initialized", + quic_engine_type_str (qm->engine_type)); + return 0; + } + vlib_cli_output (vm, "engine: %s", quic_engine_type_str (qm->engine_type)); if (!unformat_user (input, unformat_line_input, line_input)) { quic_show_aggregated_stats (vm); diff --git a/src/plugins/quic_quicly/quic_quicly.c b/src/plugins/quic_quicly/quic_quicly.c index e621b0445e..3c076ae2e7 100644 --- a/src/plugins/quic_quicly/quic_quicly.c +++ b/src/plugins/quic_quicly/quic_quicly.c @@ -327,8 +327,9 @@ static void quic_quicly_expired_timers_dispatch (u32 *expired_timers) { int i; -#if QUIC_DEBUG >= 4 - int64_t time_now = quic_wrk_ctx_get(quic_quicly_main.qm, vlib_get_thread_index ())->time_now); +#if QUIC_DEBUG >= 1 + int64_t time_now = + quic_wrk_ctx_get (quic_quicly_main.qm, vlib_get_thread_index ())->time_now; #endif for (i = 0; i < vec_len (expired_timers); i++) { @@ -1213,6 +1214,7 @@ quic_quicly_engine_init (quic_main_t *qm) tw_timer_wheel_1t_3w_1024sl_ov_t *tw; u32 i; + QUIC_DBG (2, "quic_quicly_engine_init -- start"); qm->default_crypto_engine = CRYPTO_ENGINE_PICOTLS; qm->default_quic_cc = QUIC_CC_RENO; qm->max_packets_per_key = DEFAULT_MAX_PACKETS_PER_KEY; diff --git a/src/plugins/quic_quicly/quic_quicly_crypto.c b/src/plugins/quic_quicly/quic_quicly_crypto.c index 13c7e6d208..cc2780fc29 100644 --- a/src/plugins/quic_quicly/quic_quicly_crypto.c +++ b/src/plugins/quic_quicly/quic_quicly_crypto.c @@ -203,13 +203,25 @@ quic_quicly_on_closed_by_remote (quicly_closed_by_remote_t *self, { quic_ctx_t *ctx = quic_quicly_get_conn_ctx (conn); #if QUIC_DEBUG >= 2 - session_t *quic_session = session_get (ctx->c_s_index, ctx->c_thread_index); - clib_warning ("Session 0x%lx closed by peer (%U) %.*s ", - session_handle (quic_session), quic_quicly_format_err, code, - reason_len, reason); + if (ctx->c_s_index == QUIC_SESSION_INVALID) + { + clib_warning ("Unopened Session closed by peer (%U) %.*S ", + quic_quicly_format_err, code, reason_len, reason); + } + else + { + session_t *quic_session = + session_get (ctx->c_s_index, ctx->c_thread_index); + clib_warning ("Session 0x%lx closed by peer (%U) %.*s ", + session_handle (quic_session), quic_quicly_format_err, + code, reason_len, reason); + } #endif ctx->conn_state = QUIC_CONN_STATE_PASSIVE_CLOSING; - session_transport_closing_notify (&ctx->connection); + if (ctx->c_s_index != QUIC_SESSION_INVALID) + { + session_transport_closing_notify (&ctx->connection); + } } static int64_t diff --git a/src/vnet/session/transport.c b/src/vnet/session/transport.c index edec182541..fa3106a2c5 100644 --- a/src/vnet/session/transport.c +++ b/src/vnet/session/transport.c @@ -1015,7 +1015,8 @@ transport_enable_disable (vlib_main_t * vm, u8 is_en) vec_foreach (vft, tp_vfts) { if (vft->enable) - (vft->enable) (vm, is_en); + if ((vft->enable) (vm, is_en) != 0) + continue; if (vft->update_time) session_register_update_time_fn (vft->update_time, is_en); From f248a16c57933e8c7296576884f54bdd7288bb64 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 11 Jul 2025 09:13:34 -0400 Subject: [PATCH 131/313] hsa: http cli client improvement notify http transport when body data is read and transport requested notification (e.g. used by h2 flow control) switch to new format of host and target path parsing Type: improvement Change-Id: Ie209f1e6fa244f44baa51b4e9a7c66b51b7e5a50 Signed-off-by: Matus Fabian --- extras/hs-test/http1_test.go | 22 +++++++++++----------- extras/hs-test/http2_test.go | 4 ++-- src/plugins/hs_apps/http_cli.c | 16 +++------------- src/plugins/hs_apps/http_client_cli.c | 25 +++++++++++++++++++------ 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/extras/hs-test/http1_test.go b/extras/hs-test/http1_test.go index b806826e19..045109c895 100644 --- a/extras/hs-test/http1_test.go +++ b/extras/hs-test/http1_test.go @@ -261,7 +261,7 @@ func HttpCliTest(s *VethsSuite) { s.Containers.ServerVpp.VppInstance.Vppctl(cliServerCmd) o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + - " uri http://" + serverAddress + " query /show/vlib/graph") + " uri http://" + serverAddress + "/show/vlib/graph") s.Log(o) s.AssertContains(o, "", " not found in the result!") @@ -286,19 +286,19 @@ func HttpCliTest(s *VethsSuite) { } func HttpCliTlsTest(s *VethsSuite) { - uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1 + uri := "https://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 s.Containers.ServerVpp.VppInstance.Vppctl("http cli server http1-only uri " + uri) o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + - " uri " + uri + " query /show/version") + " uri " + uri + "/show/version") s.Log(o) s.AssertContains(o, "", " not found in the result!") s.AssertContains(o, "", " not found in the result!") /* second request to test postponed ho-cleanup */ o = s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + - " uri " + uri + " query /show/version") + " uri " + uri + "/show/version") s.Log(o) s.AssertContains(o, "", " not found in the result!") s.AssertContains(o, "", " not found in the result!") @@ -308,7 +308,7 @@ func HttpCliConnectErrorTest(s *VethsSuite) { uri := "http://" + s.Interfaces.Server.Ip4AddressString() + "/80" o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + - " uri " + uri + " query /show/vlib/graph") + " uri " + uri + "/show/vlib/graph") s.Log(o) s.AssertContains(o, "failed to connect") @@ -332,7 +332,7 @@ func HttpClientTest(s *Http1Suite) { defer server.Close() uri := "http://" + serverAddress vpp := s.Containers.Vpp.VppInstance - o := vpp.Vppctl("http cli client uri " + uri + " query /test") + o := vpp.Vppctl("http cli client uri " + uri + "/test") s.Log(o) s.AssertContains(o, "", " not found in the result!") @@ -450,7 +450,7 @@ func HttpClientErrRespTest(s *Http1Suite) { defer server.Close() uri := "http://" + serverAddress vpp := s.Containers.Vpp.VppInstance - o := vpp.Vppctl("http cli client uri " + uri + " query /test") + o := vpp.Vppctl("http cli client uri " + uri + "/test") s.Log(o) s.AssertContains(o, "404: Not Found", "error not found in the result!") @@ -934,12 +934,12 @@ func HttpClientGetMemLeakTest(s *VethsSuite) { /* no goVPP less noise */ clientVpp.Disconnect() - serverVpp.Vppctl("http cli server uri " + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + uri := "http://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 - uri := "http://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1 + serverVpp.Vppctl("http cli server uri " + uri) /* warmup request (FIB) */ - clientVpp.Vppctl("http cli client uri " + uri + " query /show/version") + clientVpp.Vppctl("http cli client uri " + uri + "/show/version") /* let's give it some time to clean up sessions, so local port can be reused and we have less noise */ time.Sleep(time.Second * 12) @@ -948,7 +948,7 @@ func HttpClientGetMemLeakTest(s *VethsSuite) { traces1, err := clientVpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) - clientVpp.Vppctl("http cli client uri " + uri + " query /show/vlib/graph") + clientVpp.Vppctl("http cli client uri " + uri + "/show/vlib/graph") /* let's give it some time to clean up sessions */ time.Sleep(time.Second * 12) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 1b75c52c02..011e4b907a 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -17,7 +17,7 @@ func init() { func Http2TcpGetTest(s *Http2Suite) { vpp := s.Containers.Vpp.VppInstance serverAddress := s.VppAddr() + ":" + s.Ports.Port1 - vpp.Vppctl("http cli server listener add uri tcp://" + serverAddress) + vpp.Vppctl("http cli server listener add uri http://" + serverAddress) s.Log(vpp.Vppctl("show session verbose 2")) args := fmt.Sprintf("--max-time 10 --noproxy '*' --http2-prior-knowledge http://%s/show/version", serverAddress) writeOut, log := s.RunCurlContainer(s.Containers.Curl, args) @@ -46,7 +46,7 @@ func Http2TcpGetTest(s *Http2Suite) { s.AssertEqual(true, httpStreamCleanupDone, "HTTP/2 stream not cleaned up") /* test server app stop listen */ - vpp.Vppctl("http cli server listener del uri tcp://" + serverAddress) + vpp.Vppctl("http cli server listener del uri http://" + serverAddress) o := vpp.Vppctl("show session verbose proto http") s.AssertNotContains(o, "LISTEN") } diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index 6fddd5e11c..fdc549f21e 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -630,20 +630,12 @@ hcs_attach () return 0; } -static int -hcs_transport_needs_crypto (transport_proto_t proto) -{ - return proto == TRANSPORT_PROTO_TLS || proto == TRANSPORT_PROTO_DTLS || - proto == TRANSPORT_PROTO_QUIC; -} - static int hcs_listen () { session_endpoint_cfg_t sep = SESSION_ENDPOINT_CFG_NULL; hcs_main_t *hcm = &hcs_main; vnet_listen_args_t _a, *a = &_a; - u8 need_crypto; int rv; char *uri; @@ -656,12 +648,10 @@ hcs_listen () if (parse_uri (uri, &sep)) return -1; - need_crypto = hcs_transport_needs_crypto (sep.transport_proto); - sep.transport_proto = TRANSPORT_PROTO_HTTP; clib_memcpy (&a->sep_ext, &sep, sizeof (sep)); - if (need_crypto) + if (sep.flags & SESSION_ENDPT_CFG_F_SECURE) { transport_endpt_ext_cfg_t *ext_cfg = session_endpoint_add_ext_cfg ( &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, @@ -681,7 +671,7 @@ hcs_listen () hash_set_mem (hcm->index_by_uri, map->uri, map - hcm->uri_map_pool); } - if (need_crypto) + if (sep.flags & SESSION_ENDPT_CFG_F_SECURE) session_endpoint_free_ext_cfgs (&a->sep_ext); return rv; @@ -824,7 +814,7 @@ hcs_create_command_fn (vlib_main_t *vm, unformat_input_t *input, start_server: if (hcm->uri == 0) - hcm->uri = format (0, "tcp://0.0.0.0/80"); + hcm->uri = format (0, "http://0.0.0.0"); if (hcm->app_index != (u32) ~0) { diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c index f8984be83b..f6247ea7c5 100644 --- a/src/plugins/hs_apps/http_client_cli.c +++ b/src/plugins/hs_apps/http_client_cli.c @@ -270,6 +270,8 @@ hcc_ts_rx_callback (session_t *ts) } u32 max_deq = svm_fifo_max_dequeue (ts->rx_fifo); + if (!max_deq) + goto done; u32 n_deq = clib_min (hs->to_recv, max_deq); u64 curr = vec_len (hcm->http_response); @@ -283,6 +285,12 @@ hcc_ts_rx_callback (session_t *ts) if (rv != n_deq) return -1; + if (svm_fifo_needs_deq_ntf (ts->rx_fifo, n_deq)) + { + svm_fifo_clear_deq_ntf (ts->rx_fifo); + session_program_transport_io_evt (ts->handle, SESSION_IO_EVT_RX); + } + vec_set_len (hcm->http_response, curr + n_deq); ASSERT (hs->to_recv >= rv); hs->to_recv -= rv; @@ -579,8 +587,6 @@ hcc_command_fn (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "secret %lu", &hcm->appns_secret)) ; - else if (unformat (line_input, "query %s", &hcm->http_query)) - ; else { err = clib_error_return (0, "unknown input `%U'", @@ -598,13 +604,19 @@ hcc_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } + if ((rv = parse_target ((char **) &hcm->uri, (char **) &hcm->http_query))) + { + err = clib_error_return (0, "target parse error: %U", + format_session_error, rv); + goto done; + } + if ((rv = parse_uri ((char *) hcm->uri, &hcm->connect_sep))) { err = clib_error_return (0, "Uri parse error: %d", rv); goto done; } - hcm->need_crypto = hcm->connect_sep.transport_proto == TRANSPORT_PROTO_TLS; - hcm->connect_sep.transport_proto = TRANSPORT_PROTO_HTTP; + hcm->need_crypto = hcm->connect_sep.flags & SESSION_ENDPT_CFG_F_SECURE; session_enable_disable_args_t args = { .is_en = 1, .rt_engine_type = @@ -631,8 +643,9 @@ hcc_command_fn (vlib_main_t *vm, unformat_input_t *input, VLIB_CLI_COMMAND (hcc_command, static) = { .path = "http cli client", - .short_help = "[appns secret ] uri http:// " - "query [no-output]", + .short_help = + "[appns secret ] uri http[s]:/// " + "[no-output]", .function = hcc_command_fn, .is_mp_safe = 1, }; From a7da4d1184c3266062ec1daf910b03eaa9e39267 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 10 Jul 2025 20:27:08 -0700 Subject: [PATCH 132/313] tls: fix cert and pkey leak Free cert and pkey once assigned to make sure they're freed one the ssl structs are freed. Type: fix Change-Id: I41546c8ae7bad169a1462b3b9a3807e4644a1c2c Signed-off-by: Florin Coras --- src/plugins/tlsopenssl/tls_openssl.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 65fe9a3f58..020971d0b6 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -721,6 +721,7 @@ openssl_set_ckpair (SSL *ssl, u32 ckpair_index) } SSL_use_certificate (ssl, srvcert); BIO_free (cert_bio); + X509_free (srvcert); cert_bio = BIO_new (BIO_s_mem ()); BIO_write (cert_bio, ckpair->key, vec_len (ckpair->key)); @@ -732,6 +733,7 @@ openssl_set_ckpair (SSL *ssl, u32 ckpair_index) } SSL_use_PrivateKey (ssl, pkey); BIO_free (cert_bio); + EVP_PKEY_free (pkey); TLS_DBG (1, "TLS client using ckpair index: %d", ckpair_index); return 0; } @@ -1033,6 +1035,7 @@ openssl_start_listen (tls_ctx_t * lctx) } BIO_free (cert_bio); + X509_free (srvcert); cert_bio = BIO_new (BIO_s_mem ()); if (!cert_bio) @@ -1055,6 +1058,7 @@ openssl_start_listen (tls_ctx_t * lctx) } BIO_free (cert_bio); + EVP_PKEY_free (pkey); if (lctx->alpn_list) SSL_CTX_set_alpn_select_cb (ssl_ctx, openssl_alpn_select_cb, @@ -1086,9 +1090,6 @@ openssl_stop_listen (tls_ctx_t * lctx) olc_index = lctx->tls_ssl_ctx; olc = openssl_lctx_get (olc_index); - X509_free (olc->srvcert); - EVP_PKEY_free (olc->pkey); - SSL_CTX_free (olc->ssl_ctx); openssl_listen_ctx_free (olc); From 13b49bb3be30d95b39e04c080b51fc5bc5004b7c Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Mon, 14 Jul 2025 12:49:59 +0200 Subject: [PATCH 133/313] hs-test: KinD cluster build script improvements - new makefile targets, cleanup - cluster can be rebuilt without shutting down Type: improvement Change-Id: I020e396a6410786ae29b9146a358d60503ac5662 Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 18 ++- extras/hs-test/kubernetes/setupCluster.sh | 172 +++++++++++++--------- 2 files changed, 118 insertions(+), 72 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 39c5f08e50..6b94fe682c 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -87,7 +87,9 @@ help: @echo " build-cov - coverage build of VPP and Docker images" @echo " build-debug - build test infra (vpp debug image)" @echo " build-go - just build golang files" - @echo " setup-cluster - setup KinD cluster for performance testing" + @echo " master-cluster - setup KinD cluster for performance testing (master CalicoVPP + VPP)" + @echo " rebuild-master-cluster - rebuild VPP and update related pods without shutting down the cluster" + @echo " release-cluster - setup KinD cluster for performance testing (latest CalicoVPP release)" @echo " checkstyle-go - check style of .go source files" @echo " fixstyle-go - format .go source files" @echo " cleanup-hst - removes all docker containers and namespaces from last test run" @@ -203,9 +205,17 @@ test-perf: .deps.ok .build.ok --ginkgo_timeout=$(GINKGO_TIMEOUT); \ ./script/compress.sh $$? -.PHONY: setup-cluster -setup-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh $(BASE) +.PHONY: release-cluster +release-cluster: .kind_deps.ok + @bash ./kubernetes/setupCluster.sh setup_release + +.PHONY: master-cluster +master-cluster: .kind_deps.ok + @bash ./kubernetes/setupCluster.sh setup_master + +.PHONY: rebuild-master-cluster +rebuild-cluster: .kind_deps.ok + @bash ./kubernetes/setupCluster.sh rebuild_master .PHONY: build-go build-go: diff --git a/extras/hs-test/kubernetes/setupCluster.sh b/extras/hs-test/kubernetes/setupCluster.sh index 131248ca27..3c176f6e98 100755 --- a/extras/hs-test/kubernetes/setupCluster.sh +++ b/extras/hs-test/kubernetes/setupCluster.sh @@ -1,83 +1,119 @@ #!/usr/bin/env bash set -e -MASTER_OR_LATEST=${1-"latest"} +COMMAND=$1 CALICOVPP_DIR="$HOME/vpp-dataplane" VPP_DIR=$(pwd) VPP_DIR=${VPP_DIR%extras*} STASH_SAVED=0 -if [ $MASTER_OR_LATEST = "master" ]; then - if [ ! -d "$CALICOVPP_DIR" ]; then - git clone https://github.com/projectcalico/vpp-dataplane.git $CALICOVPP_DIR - fi - cd $CALICOVPP_DIR - git pull - cd $VPP_DIR +# ---------------- images ---------------- +export CALICO_AGENT_IMAGE=localhost:5000/calicovpp/agent:latest +export CALICO_VPP_IMAGE=localhost:5000/calicovpp/vpp:latest +export MULTINET_MONITOR_IMAGE=localhost:5000/calicovpp/multinet-monitor:latest +export IMAGE_PULL_POLICY=Always - # ---------------- images ---------------- - export CALICO_AGENT_IMAGE=localhost:5000/calicovpp/agent:latest - export CALICO_VPP_IMAGE=localhost:5000/calicovpp/vpp:latest - export MULTINET_MONITOR_IMAGE=localhost:5000/calicovpp/multinet-monitor:latest - export IMAGE_PULL_POLICY=Always +# ---------------- interfaces ---------------- +export CALICOVPP_INTERFACES='{ + "uplinkInterfaces": [ + { + "interfaceName": "eth0", + "vppDriver": "af_packet" + } + ] + }' +export CALICOVPP_DISABLE_HUGEPAGES=true +export CALICOVPP_CONFIG_TEMPLATE=" + unix { + nodaemon + full-coredump + log /var/run/vpp/vpp.log + cli-listen /var/run/vpp/cli.sock + pidfile /run/vpp/vpp.pid + } + buffers { + buffers-per-numa 131072 + } + socksvr { socket-name /var/run/vpp/vpp-api.sock } + plugins { + plugin default { enable } + plugin calico_plugin.so { enable } + plugin dpdk_plugin.so { disable } + }" +export CALICOVPP_ENABLE_VCL=true - # ---------------- interfaces ---------------- - export CALICOVPP_INTERFACES='{ - "uplinkInterfaces": [ - { - "interfaceName": "eth0", - "vppDriver": "af_packet" - } - ] - }' - export CALICOVPP_DISABLE_HUGEPAGES=true - export CALICOVPP_CONFIG_TEMPLATE=" - unix { - nodaemon - full-coredump - log /var/run/vpp/vpp.log - cli-listen /var/run/vpp/cli.sock - pidfile /run/vpp/vpp.pid - } - buffers { - buffers-per-numa 131072 - } - socksvr { socket-name /var/run/vpp/vpp-api.sock } - plugins { - plugin default { enable } - plugin calico_plugin.so { enable } - plugin dpdk_plugin.so { disable } - }" - export CALICOVPP_ENABLE_VCL=true +help() { + echo "Usage:" + echo " make master-cluster | rebuild-master-cluster | release-cluster" + echo "or" + echo " ./kubernetes/setupCluster.sh [master-cluster | rebuild-master-cluster | release-cluster]" + echo "" + echo "'master-cluster' pulls CalicoVPP and builds VPP from this directory, then brings up a KinD cluster." + echo "'rebuild-master-cluster' stops CalicoVPP pods, rebuilds VPP and restarts CalicoVPP pods. Cluster keeps running." + echo "'release-cluster' starts up a KinD cluster and uses latest CalicoVPP release (e.g. v3.29)" + echo "" + echo "To shut down the cluster, use 'kind delete cluster'" +} - make -C $CALICOVPP_DIR kind-new-cluster N_KIND_WORKERS=2 - kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml - make -C $CALICOVPP_DIR cherry-vpp FORCE=y BASE=origin/master VPP_DIR=$VPP_DIR - make build - make -C $CALICOVPP_DIR dev-kind - make -C $CALICOVPP_DIR load-kind - $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up - if ! git diff-index --quiet HEAD --; then - echo "Saving stash" - git stash save "HST: temp stash" - git reset --hard origin/master - git stash pop - fi - else - echo "********" - echo "Performance tests only work on Ubuntu 22.04 for now." - echo "********" +setup_master() { + if [ ! -d "$CALICOVPP_DIR" ]; then + git clone https://github.com/projectcalico/vpp-dataplane.git $CALICOVPP_DIR + else + cd $CALICOVPP_DIR + git pull + cd $VPP_DIR + fi - kind create cluster --config kubernetes/kind-config.yaml - kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml + make -C $CALICOVPP_DIR kind-new-cluster N_KIND_WORKERS=2 + kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml + make -C $CALICOVPP_DIR cherry-vpp FORCE=y BASE=origin/master VPP_DIR=$VPP_DIR + make build-vpp-release + make -C $CALICOVPP_DIR dev-kind + make -C $CALICOVPP_DIR load-kind + $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up + if ! git diff-index --quiet HEAD --; then + echo "Saving stash" + git stash save "HST: temp stash" + git reset --hard origin/master + git stash pop + fi +} - echo "Sleeping for 10s, waiting for tigera operator to start up." - sleep 10 +rebuild_master() { + echo "Shutting down pods may take some time, timeout is set to 1m." + timeout 1m $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh dn || true + make build-vpp-release + make -C $CALICOVPP_DIR dev-kind + make -C $CALICOVPP_DIR load-kind + $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up +} - kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml - kubectl create -f kubernetes/calico-config.yaml +setup_release() { + kind create cluster --config kubernetes/kind-config.yaml + kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml - echo "Done. Please wait for the cluster to come fully online before running tests." - echo "Use 'watch kubectl get pods -A' to monitor cluster status." - echo "To delete the cluster, use 'kind delete cluster'" - fi + echo "Sleeping for 10s, waiting for tigera operator to start up." + sleep 10 + + kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml + kubectl create -f kubernetes/calico-config.yaml + + echo "Done. Please wait for the cluster to come fully online before running tests." + echo "Use 'watch kubectl get pods -A' to monitor cluster status." + echo "To delete the cluster, use 'kind delete cluster'" +} + +case "$COMMAND" in + master-cluster) + setup_master + ;; + rebuild-master-cluster) + rebuild_master + ;; + release-cluster) + setup_release + ;; +*) + help + ;; +esac From f2223b6fcc3d5da375f506a0ebfd7da2c03d2b9a Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Thu, 10 Jul 2025 11:47:05 +0200 Subject: [PATCH 134/313] tap: add option to specify interface name Type: feature Change-Id: If3f8b082a2750f2786724f0e55f501df3c7d4991 Signed-off-by: Damjan Marion --- src/vnet/devices/tap/cli.c | 5 ++++- src/vnet/devices/tap/tap.c | 4 ++++ src/vnet/devices/tap/tap.h | 2 ++ src/vnet/devices/virtio/format.c | 3 +++ src/vnet/devices/virtio/virtio.h | 3 ++- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/vnet/devices/tap/cli.c b/src/vnet/devices/tap/cli.c index 5738ef237b..ff6af2cca9 100644 --- a/src/vnet/devices/tap/cli.c +++ b/src/vnet/devices/tap/cli.c @@ -50,6 +50,8 @@ tap_create_command_fn (vlib_main_t * vm, unformat_input_t * input, { if (unformat (line_input, "id %u", &args.id)) ; + else if (unformat (line_input, "if-name %s", &args.if_name)) + ; else if (unformat (line_input, "host-if-name %s", &args.host_if_name)) ; @@ -130,6 +132,7 @@ tap_create_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name, vnet_get_main (), args.sw_if_index); + vec_free (args.if_name); vec_free (args.host_if_name); vec_free (args.host_namespace); vec_free (args.host_bridge); @@ -141,7 +144,7 @@ tap_create_command_fn (vlib_main_t * vm, unformat_input_t * input, VLIB_CLI_COMMAND (tap_create_command, static) = { .path = "create tap", .short_help = - "create tap {id } [hw-addr ] " + "create tap {id } [hw-addr ] [if-name ] " "[num-rx-queues ] [num-tx-queues ] [rx-ring-size ] " "[tx-ring-size ] [host-ns ] [host-bridge ] " "[host-ip4-addr ] [host-ip6-addr ] " diff --git a/src/vnet/devices/tap/tap.c b/src/vnet/devices/tap/tap.c index 8f39204a1a..38ece0a0a8 100644 --- a/src/vnet/devices/tap/tap.c +++ b/src/vnet/devices/tap/tap.c @@ -117,6 +117,7 @@ tap_free (vlib_main_t * vm, virtio_if_t * vif) vec_free (vif->rxq_vrings); vec_free (vif->txq_vrings); vec_free (vif->host_if_name); + vec_free (vif->initial_if_name); vec_free (vif->net_ns); vec_free (vif->host_bridge); clib_error_free (vif->error); @@ -202,6 +203,9 @@ tap_create_if (vlib_main_t * vm, tap_create_if_args_t * args) vif->num_txqs = clib_max (args->num_tx_queues, thm->n_vlib_mains); vif->num_rxqs = clib_max (args->num_rx_queues, 1); + if (args->if_name) + CLIB_SWAP (args->if_name, vif->initial_if_name); + if (args->tap_flags & TAP_FLAG_ATTACH) { if (args->host_if_name == NULL) diff --git a/src/vnet/devices/tap/tap.h b/src/vnet/devices/tap/tap.h index 66f5576c5b..7b928188d5 100644 --- a/src/vnet/devices/tap/tap.h +++ b/src/vnet/devices/tap/tap.h @@ -52,6 +52,7 @@ typedef struct u16 tx_ring_sz; u32 tap_flags; u8 *host_namespace; + u8 *if_name; u8 *host_if_name; mac_address_t host_mac_addr; u8 *host_bridge; @@ -65,6 +66,7 @@ typedef struct u8 host_ip6_gw_set; u8 host_mtu_set; u32 host_mtu_size; + /* return */ u32 sw_if_index; int rv; diff --git a/src/vnet/devices/virtio/format.c b/src/vnet/devices/virtio/format.c index fe46d92cba..d463a6f7ff 100644 --- a/src/vnet/devices/virtio/format.c +++ b/src/vnet/devices/virtio/format.c @@ -30,6 +30,9 @@ format_virtio_device_name (u8 * s, va_list * args) virtio_main_t *mm = &virtio_main; virtio_if_t *vif = pool_elt_at_index (mm->interfaces, dev_instance); + if (vif->initial_if_name) + return format (s, "%s", vif->initial_if_name); + if (vif->type == VIRTIO_IF_TYPE_TAP) s = format (s, "tap%u", vif->id); else if (vif->type == VIRTIO_IF_TYPE_PCI) diff --git a/src/vnet/devices/virtio/virtio.h b/src/vnet/devices/virtio/virtio.h index fb72051ce4..9e75b695e9 100644 --- a/src/vnet/devices/virtio/virtio.h +++ b/src/vnet/devices/virtio/virtio.h @@ -155,8 +155,9 @@ typedef struct u32 hw_if_index; u32 sw_if_index; + u8 *initial_if_name; - CLIB_CACHE_LINE_ALIGN_MARK (cacheline1); + CLIB_CACHE_LINE_ALIGN_MARK (cacheline1); int packet_coalesce; int packet_buffering; u32 dev_instance; From 84f09f471519dc6fa943532ccba7fe3a79d758a7 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Thu, 10 Jul 2025 13:39:27 +0200 Subject: [PATCH 135/313] virtio: add option to set interface name Type: feature Change-Id: I99421fc1f946e65a8e23a74c04593fe1ca4f4857 Signed-off-by: Damjan Marion --- src/vnet/devices/virtio/cli.c | 13 ++++++------- src/vnet/devices/virtio/pci.c | 7 +++++-- src/vnet/devices/virtio/pci.h | 1 + 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vnet/devices/virtio/cli.c b/src/vnet/devices/virtio/cli.c index 34c74ac91a..d363882738 100644 --- a/src/vnet/devices/virtio/cli.c +++ b/src/vnet/devices/virtio/cli.c @@ -28,20 +28,17 @@ virtio_pci_create_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { unformat_input_t _line_input, *line_input = &_line_input; - virtio_pci_create_if_args_t args; + virtio_pci_create_if_args_t args = {}; u64 feature_mask = (u64) ~ (0ULL); u32 buffering_size = 0; u32 txq_size = 0; - /* Get a line of input. */ - if (!unformat_user (input, unformat_line_input, line_input)) - return 0; - - memset (&args, 0, sizeof (args)); while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "%U", unformat_vlib_pci_addr, &args.addr)) ; + else if (unformat (line_input, "if-name %s", &args.if_name)) + ; else if (unformat (line_input, "feature-mask 0x%llx", &feature_mask)) args.features = feature_mask; else if (unformat (line_input, "tx-queue-size %u", &txq_size)) @@ -74,12 +71,14 @@ virtio_pci_create_command_fn (vlib_main_t * vm, unformat_input_t * input, virtio_pci_create_if (vm, &args); + vec_free (args.if_name); + return args.error; } VLIB_CLI_COMMAND (virtio_pci_create_command, static) = { .path = "create interface virtio", - .short_help = "create interface virtio " + .short_help = "create interface virtio [if-name ] " "[feature-mask ] [tx-queue-size ] " "[gso-enabled] [csum-enabled] [rss-enabled] " "[buffering [size ]] [packed] [bind [force]]", diff --git a/src/vnet/devices/virtio/pci.c b/src/vnet/devices/virtio/pci.c index c85e316b1f..f9aceb1dc8 100644 --- a/src/vnet/devices/virtio/pci.c +++ b/src/vnet/devices/virtio/pci.c @@ -1413,7 +1413,7 @@ virtio_pci_create_if (vlib_main_t * vm, virtio_pci_create_if_args_t * args) } } - pool_get (vim->interfaces, vif); + pool_get_zero (vim->interfaces, vif); vif->dev_instance = vif - vim->interfaces; vif->per_interface_next_index = ~0; vif->pci_addr.as_u32 = args->addr; @@ -1441,6 +1441,9 @@ virtio_pci_create_if (vlib_main_t * vm, virtio_pci_create_if_args_t * args) vif->numa_node = vlib_pci_get_numa_node (vm, h); vif->type = VIRTIO_IF_TYPE_PCI; + if (args->if_name) + CLIB_SWAP (args->if_name, vif->initial_if_name); + if ((error = vlib_pci_bus_master_enable (vm, h))) { virtio_log_error (vif, "error encountered on pci bus master enable"); @@ -1681,7 +1684,7 @@ virtio_pci_delete_if (vlib_main_t * vm, virtio_if_t * vif) vec_free (vif->cxq_vring); clib_error_free (vif->error); - memset (vif, 0, sizeof (*vif)); + vec_free (vif->initial_if_name); pool_put (vim->interfaces, vif); return 0; diff --git a/src/vnet/devices/virtio/pci.h b/src/vnet/devices/virtio/pci.h index 5977853331..16562dee56 100644 --- a/src/vnet/devices/virtio/pci.h +++ b/src/vnet/devices/virtio/pci.h @@ -317,6 +317,7 @@ typedef struct u32 buffering_size; u32 virtio_flags; clib_error_t *error; + u8 *if_name; } virtio_pci_create_if_args_t; extern const virtio_pci_func_t virtio_pci_legacy_func; From b6cc662e49cadcd47ba4b4d1f7ed25c172a56fc7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 10 Jul 2025 12:39:37 -0400 Subject: [PATCH 136/313] http: http2 client side - only with TLS - request are serialized within one app session (no multiplexing) - http version can be specified in http client Type: feature Change-Id: I2fe11bd3252985d1bd1732616837f7a91f37f6a3 Signed-off-by: Matus Fabian --- extras/hs-test/docker/Dockerfile.nginx-server | 1 + extras/hs-test/http2_test.go | 91 ++- extras/hs-test/infra/suite_envoy_proxy.go | 6 + extras/hs-test/infra/suite_http1.go | 10 +- extras/hs-test/infra/suite_http2.go | 38 +- extras/hs-test/infra/suite_no_topo.go | 12 +- extras/hs-test/infra/suite_no_topo6.go | 10 +- extras/hs-test/infra/suite_veth.go | 5 +- extras/hs-test/infra/suite_vpp_proxy.go | 10 +- .../hs-test/resources/nginx/nginx_server.conf | 14 + .../hs-test/topo-containers/envoyProxy.yaml | 2 + extras/hs-test/topo-containers/single.yaml | 2 + extras/hs-test/topo-containers/vppProxy.yaml | 2 + src/plugins/hs_apps/http_client.c | 44 +- src/plugins/http/http.c | 12 +- src/plugins/http/http.h | 69 +++ src/plugins/http/http2/frame.c | 3 +- src/plugins/http/http2/http2.c | 558 ++++++++++++++++-- src/plugins/http/http2/http2.h | 26 +- src/plugins/http/http_private.h | 20 - 20 files changed, 818 insertions(+), 117 deletions(-) diff --git a/extras/hs-test/docker/Dockerfile.nginx-server b/extras/hs-test/docker/Dockerfile.nginx-server index 5284ea7763..30f32a5a26 100644 --- a/extras/hs-test/docker/Dockerfile.nginx-server +++ b/extras/hs-test/docker/Dockerfile.nginx-server @@ -9,6 +9,7 @@ COPY script/nginx_server_entrypoint.sh /usr/bin/nginx_server_entrypoint.sh COPY resources/nginx/html/index.html /usr/share/nginx/index.html RUN fallocate -l 10MB /usr/share/nginx/httpTestFile +RUN touch /usr/share/nginx/test_upload RUN mkdir /usr/share/nginx/upload && chmod 777 /usr/share/nginx/upload ENTRYPOINT ["nginx_server_entrypoint.sh"] diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 011e4b907a..238d335be8 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -10,8 +10,10 @@ import ( ) func init() { - RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest) + RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest, + Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest) RegisterH2MWTests(Http2MultiplexingMWTest) + RegisterVethTests(Http2CliTlsTest, Http2ClientContinuationTest) } func Http2TcpGetTest(s *Http2Suite) { @@ -155,3 +157,90 @@ func Http2ServerMemLeakTest(s *Http2Suite) { s.AssertNil(err, fmt.Sprint(err)) vpp.MemLeakCheck(traces1, traces2) } + +func Http2CliTlsTest(s *VethsSuite) { + uri := "https://" + s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + + s.Containers.ServerVpp.VppInstance.Vppctl("http cli server uri " + uri) + + o := s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + + " uri " + uri + "/show/version") + s.Log(o) + s.AssertContains(o, "", " not found in the result!") + s.AssertContains(o, "", " not found in the result!") + + /* second request to test postponed ho-cleanup */ + o = s.Containers.ClientVpp.VppInstance.Vppctl("http cli client" + + " uri " + uri + "/show/vlib/graph") + s.Log(o) + s.AssertContains(o, "", " not found in the result!") + s.AssertContains(o, "", " not found in the result!") +} + +func Http2ClientGetTest(s *Http2Suite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.HostAddr() + ":" + s.Ports.Port2 + + s.CreateNginxServer() + s.AssertNil(s.Containers.NginxServer.Start()) + + uri := "https://" + serverAddress + "/httpTestFile" + o := vpp.Vppctl("http client 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") +} + +func http2ClientPostFile(s *Http2Suite, usePtr bool, fileSize int) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.HostAddr() + ":" + s.Ports.Port2 + + fileName := "/tmp/test_file.txt" + s.Log(vpp.Container.Exec(false, "fallocate -l "+strconv.Itoa(fileSize)+" "+fileName)) + s.Log(vpp.Container.Exec(false, "ls -la "+fileName)) + + s.CreateNginxServer() + s.AssertNil(s.Containers.NginxServer.Start()) + + uri := "https://" + serverAddress + "/test_upload" + cmd := "http client post verbose uri " + uri + " file " + fileName + if usePtr { + cmd += " use-ptr" + } + o := vpp.Vppctl(cmd) + s.Log(o) + s.AssertContains(o, "HTTP/2 200 OK") +} + +func Http2ClientPostTest(s *Http2Suite) { + http2ClientPostFile(s, false, 131072) +} + +func Http2ClientPostPtrTest(s *Http2Suite) { + http2ClientPostFile(s, true, 131072) +} + +func Http2ClientGetRepeatTest(s *Http2Suite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.HostAddr() + ":" + s.Ports.Port2 + + s.CreateNginxServer() + s.AssertNil(s.Containers.NginxServer.Start()) + + uri := "https://" + serverAddress + "/64B" + cmd := fmt.Sprintf("http client http2 repeat %d uri %s", 10, uri) + o := vpp.Vppctl(cmd) + s.Log(o) +} + +func Http2ClientContinuationTest(s *VethsSuite) { + serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 + + s.Containers.ServerVpp.VppInstance.Vppctl("http tps uri tls://" + serverAddress + " no-zc") + + uri := fmt.Sprintf("https://%s/test_file_64?test_header=32k", serverAddress) + o := s.Containers.ClientVpp.VppInstance.Vppctl("http client fifo-size 64k verbose save-to response.txt uri " + uri) + s.Log(o) + s.AssertContains(o, "HTTP/2 200 OK") + s.AssertGreaterThan(strings.Count(o, "x"), 32768) +} diff --git a/extras/hs-test/infra/suite_envoy_proxy.go b/extras/hs-test/infra/suite_envoy_proxy.go index 42a5b93768..1932f18116 100644 --- a/extras/hs-test/infra/suite_envoy_proxy.go +++ b/extras/hs-test/infra/suite_envoy_proxy.go @@ -31,6 +31,7 @@ type EnvoyProxySuite struct { } Ports struct { Nginx uint16 + NginxSsl uint16 Proxy uint16 EnvoyAdmin uint16 } @@ -64,6 +65,7 @@ func (s *EnvoyProxySuite) SetupSuite() { s.Containers.EnvoyProxy = s.GetContainerByName("envoy-vcl") s.Containers.Curl = s.GetContainerByName("curl") s.Ports.Nginx = s.GeneratePortAsInt() + s.Ports.NginxSsl = s.GeneratePortAsInt() s.Ports.Proxy = s.GeneratePortAsInt() s.Ports.EnvoyAdmin = s.GeneratePortAsInt() } @@ -89,11 +91,15 @@ func (s *EnvoyProxySuite) SetupTest() { LogPrefix string Address string Port uint16 + PortSsl uint16 + Http2 string Timeout int }{ LogPrefix: s.Containers.NginxServerTransient.Name, Address: s.Interfaces.Server.Ip4AddressString(), Port: s.Ports.Nginx, + PortSsl: s.Ports.NginxSsl, + Http2: "off", Timeout: s.maxTimeout, } s.Containers.NginxServerTransient.CreateConfigFromTemplate( diff --git a/extras/hs-test/infra/suite_http1.go b/extras/hs-test/infra/suite_http1.go index ff2a65f90f..248a413bce 100644 --- a/extras/hs-test/infra/suite_http1.go +++ b/extras/hs-test/infra/suite_http1.go @@ -24,8 +24,9 @@ type Http1Suite struct { Wrk *Container } Ports struct { - NginxServer string - Http string + NginxServer string + NginxServerSsl string + Http string } } @@ -49,6 +50,7 @@ func (s *Http1Suite) SetupSuite() { s.Containers.Wrk = s.GetContainerByName("wrk") s.Ports.Http = s.GeneratePort() s.Ports.NginxServer = s.GeneratePort() + s.Ports.NginxServerSsl = s.GeneratePort() } func (s *Http1Suite) SetupTest() { @@ -90,11 +92,15 @@ func (s *Http1Suite) CreateNginxServer() { LogPrefix string Address string Port string + PortSsl string + Http2 string Timeout int }{ LogPrefix: s.Containers.NginxServer.Name, Address: s.Interfaces.Tap.Ip4AddressString(), Port: s.Ports.NginxServer, + PortSsl: s.Ports.NginxServerSsl, + Http2: "off", Timeout: 600, } s.Containers.NginxServer.CreateConfigFromTemplate( diff --git a/extras/hs-test/infra/suite_http2.go b/extras/hs-test/infra/suite_http2.go index a0507a38d4..d67399b577 100644 --- a/extras/hs-test/infra/suite_http2.go +++ b/extras/hs-test/infra/suite_http2.go @@ -30,13 +30,15 @@ type Http2Suite struct { Tap *NetInterface } Containers struct { - Vpp *Container - Curl *Container - H2load *Container + Vpp *Container + Curl *Container + H2load *Container + NginxServer *Container } Ports struct { Port1 string Port1AsInt int + Port2 string } } @@ -58,7 +60,9 @@ func (s *Http2Suite) SetupSuite() { s.Containers.Vpp = s.GetContainerByName("vpp") s.Containers.Curl = s.GetContainerByName("curl") s.Containers.H2load = s.GetContainerByName("h2load") + s.Containers.NginxServer = s.GetTransientContainerByName("nginx-server") s.Ports.Port1 = s.GeneratePort() + s.Ports.Port2 = s.GeneratePort() var err error s.Ports.Port1AsInt, err = strconv.Atoi(s.Ports.Port1) s.AssertNil(err) @@ -92,6 +96,34 @@ func (s *Http2Suite) VppAddr() string { return s.Interfaces.Tap.Peer.Ip4AddressString() } +func (s *Http2Suite) HostAddr() string { + return s.Interfaces.Tap.Ip4AddressString() +} + +func (s *Http2Suite) CreateNginxServer() { + s.AssertNil(s.Containers.NginxServer.Create()) + nginxSettings := struct { + LogPrefix string + Address string + Port string + PortSsl string + Http2 string + Timeout int + }{ + LogPrefix: s.Containers.NginxServer.Name, + Address: s.Interfaces.Tap.Ip4AddressString(), + Port: s.Ports.Port1, + PortSsl: s.Ports.Port2, + Http2: "on", + Timeout: 600, + } + s.Containers.NginxServer.CreateConfigFromTemplate( + "/nginx.conf", + "./resources/nginx/nginx_server.conf", + nginxSettings, + ) +} + var _ = Describe("Http2Suite", Ordered, ContinueOnFailure, func() { var s Http2Suite BeforeAll(func() { diff --git a/extras/hs-test/infra/suite_no_topo.go b/extras/hs-test/infra/suite_no_topo.go index 1749d13d53..5a411adc29 100644 --- a/extras/hs-test/infra/suite_no_topo.go +++ b/extras/hs-test/infra/suite_no_topo.go @@ -29,9 +29,10 @@ type NoTopoSuite struct { Ab *Container } Ports struct { - NginxServer string - NginxHttp3 string - Http string + NginxServer string + NginxServerSsl string + NginxHttp3 string + Http string } } @@ -59,6 +60,7 @@ func (s *NoTopoSuite) SetupSuite() { s.Containers.Ab = s.GetContainerByName("ab") s.Ports.Http = s.GeneratePort() s.Ports.NginxServer = s.GeneratePort() + s.Ports.NginxServerSsl = s.GeneratePort() s.Ports.NginxHttp3 = s.GeneratePort() } @@ -125,11 +127,15 @@ func (s *NoTopoSuite) CreateNginxServer() { LogPrefix string Address string Port string + PortSsl string + Http2 string Timeout int }{ LogPrefix: s.Containers.NginxServer.Name, Address: s.Interfaces.Tap.Ip4AddressString(), Port: s.Ports.NginxServer, + PortSsl: s.Ports.NginxServerSsl, + Http2: "off", Timeout: 600, } s.Containers.NginxServer.CreateConfigFromTemplate( diff --git a/extras/hs-test/infra/suite_no_topo6.go b/extras/hs-test/infra/suite_no_topo6.go index a57cb29721..0997e8a172 100644 --- a/extras/hs-test/infra/suite_no_topo6.go +++ b/extras/hs-test/infra/suite_no_topo6.go @@ -28,8 +28,9 @@ type NoTopo6Suite struct { Ab *Container } Ports struct { - NginxServer string - Http string + NginxServer string + NginxServerSsl string + Http string } } @@ -54,6 +55,7 @@ func (s *NoTopo6Suite) SetupSuite() { s.Containers.Ab = s.GetContainerByName("ab") s.Ports.Http = s.GeneratePort() s.Ports.NginxServer = s.GeneratePort() + s.Ports.NginxServerSsl = s.GeneratePort() } func (s *NoTopo6Suite) SetupTest() { @@ -119,11 +121,15 @@ func (s *NoTopo6Suite) CreateNginxServer() { LogPrefix string Address string Port string + PortSsl string + Http2 string Timeout int }{ LogPrefix: s.Containers.NginxServer.Name, Address: "[" + s.Interfaces.Tap.Ip6AddressString() + "]", Port: s.Ports.NginxServer, + PortSsl: s.Ports.NginxServerSsl, + Http2: "off", Timeout: 600, } s.Containers.NginxServer.CreateConfigFromTemplate( diff --git a/extras/hs-test/infra/suite_veth.go b/extras/hs-test/infra/suite_veth.go index 2b7832e288..65506c448b 100644 --- a/extras/hs-test/infra/suite_veth.go +++ b/extras/hs-test/infra/suite_veth.go @@ -70,13 +70,16 @@ func (s *VethsSuite) SetupTest() { } else { sessionConfig.Close() } + // For http/2 continuation frame test between http tps and http client + var httpConfig Stanza + httpConfig.NewStanza("http").NewStanza("http2").Append("max-header-list-size 65536") // ... For server serverVpp, err := s.Containers.ServerVpp.newVppInstance(s.Containers.ServerVpp.AllocatedCpus, sessionConfig) s.AssertNotNil(serverVpp, fmt.Sprint(err)) // ... For client - clientVpp, err := s.Containers.ClientVpp.newVppInstance(s.Containers.ClientVpp.AllocatedCpus, sessionConfig) + clientVpp, err := s.Containers.ClientVpp.newVppInstance(s.Containers.ClientVpp.AllocatedCpus, sessionConfig, httpConfig) s.AssertNotNil(clientVpp, fmt.Sprint(err)) s.SetupServerVpp() diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go index d43a588fe4..7b108b966f 100644 --- a/extras/hs-test/infra/suite_vpp_proxy.go +++ b/extras/hs-test/infra/suite_vpp_proxy.go @@ -41,8 +41,9 @@ type VppProxySuite struct { IperfC *Container } Ports struct { - Server uint16 - Proxy uint16 + Server uint16 + ServerSsl uint16 + Proxy uint16 } } @@ -65,6 +66,7 @@ func (s *VppProxySuite) SetupSuite() { s.LoadNetworkTopology("2taps") s.LoadContainerTopology("vppProxy") s.Ports.Server = s.GeneratePortAsInt() + s.Ports.ServerSsl = s.GeneratePortAsInt() s.Ports.Proxy = s.GeneratePortAsInt() if *IsVppDebug { @@ -117,11 +119,15 @@ func (s *VppProxySuite) SetupNginxServer() { LogPrefix string Address string Port uint16 + PortSsl uint16 + Http2 string Timeout int }{ LogPrefix: s.Containers.NginxServerTransient.Name, Address: s.Interfaces.Server.Ip4AddressString(), Port: s.Ports.Server, + PortSsl: s.Ports.ServerSsl, + Http2: "off", Timeout: s.maxTimeout, } s.Containers.NginxServerTransient.CreateConfigFromTemplate( diff --git a/extras/hs-test/resources/nginx/nginx_server.conf b/extras/hs-test/resources/nginx/nginx_server.conf index 26d5834803..a40ed7c309 100644 --- a/extras/hs-test/resources/nginx/nginx_server.conf +++ b/extras/hs-test/resources/nginx/nginx_server.conf @@ -22,9 +22,15 @@ http { server { access_log /tmp/nginx/{{.LogPrefix}}-access.log; listen {{.Port}}; + listen {{.PortSsl}} ssl; server_name {{.Address}}; root /usr/share/nginx; + ssl_certificate /etc/nginx/ssl/localhost.crt; + ssl_certificate_key /etc/nginx/ssl/localhost.key; + http2 {{.Http2}}; index index.html index.htm; + # to allow POST on static pages + error_page 405 =200 $uri; location ~ "/upload/([0-9a-zA-Z-.]*)$" { alias /usr/share/nginx/upload/$1; client_body_temp_path /tmp; @@ -39,5 +45,13 @@ http { location / { sendfile on; } + # HTTP2 will not wait for the post body and return 200 + location = /test_upload { + proxy_pass http://127.0.0.1:{{.Port}}/dev-null; + } + location = /dev-null { + return 200; + } + # HTTP2 upload fix end } } diff --git a/extras/hs-test/topo-containers/envoyProxy.yaml b/extras/hs-test/topo-containers/envoyProxy.yaml index cb2d673b78..3189080cb9 100644 --- a/extras/hs-test/topo-containers/envoyProxy.yaml +++ b/extras/hs-test/topo-containers/envoyProxy.yaml @@ -28,6 +28,8 @@ containers: - <<: *shared-vol container-dir: "/tmp/nginx" is-default-work-dir: true + - host-dir: $HST_DIR/resources/cert + container-dir: "/etc/nginx/ssl" image: "hs-test/nginx-server" is-optional: true - name: "curl" diff --git a/extras/hs-test/topo-containers/single.yaml b/extras/hs-test/topo-containers/single.yaml index d1f43ad07b..c862a7991b 100644 --- a/extras/hs-test/topo-containers/single.yaml +++ b/extras/hs-test/topo-containers/single.yaml @@ -33,6 +33,8 @@ containers: - <<: *shared-vol container-dir: "/tmp/nginx" is-default-work-dir: true + - host-dir: $HST_DIR/resources/cert + container-dir: "/etc/nginx/ssl" image: "hs-test/nginx-server" is-optional: true diff --git a/extras/hs-test/topo-containers/vppProxy.yaml b/extras/hs-test/topo-containers/vppProxy.yaml index 5f0248027a..54aac03f03 100644 --- a/extras/hs-test/topo-containers/vppProxy.yaml +++ b/extras/hs-test/topo-containers/vppProxy.yaml @@ -26,6 +26,8 @@ containers: - <<: *shared-vol container-dir: "/tmp/nginx" is-default-work-dir: true + - host-dir: $HST_DIR/resources/cert + container-dir: "/etc/nginx/ssl" image: "hs-test/nginx-server" is-optional: true - name: "curl" diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index ddb92b3a3a..6ddf0b52fe 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -10,6 +10,7 @@ #include #include #include +#include #define foreach_hc_s_flag \ _ (1, IS_CLOSED) \ @@ -100,6 +101,7 @@ typedef struct bool was_transport_closed; u32 ckpair_index; u64 max_body_size; + http_version_t http_version; } hc_main_t; typedef enum @@ -411,6 +413,7 @@ hc_rx_callback (session_t *s) u32 max_deq; session_error_t session_err = 0; int send_err = 0; + http_version_t http_version; if (hc_session->session_flags & HC_S_FLAG_IS_CLOSED) { @@ -441,21 +444,24 @@ hc_rx_callback (session_t *s) http_init_header_table_buf (&hc_session->resp_headers, msg); if (!hcm->repeat) - hc_session->response_status = - format (0, "%U", format_http_status_code, msg.code); + { + http_version = http_session_get_version (s); + hc_session->response_status = + format (0, "%U %U", format_http_version, http_version, + format_http_status_code, msg.code); + } svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset); rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len, hc_session->resp_headers.buf); ASSERT (rv == msg.data.headers_len); - HTTP_DBG (1, - (char *) format (0, "%U", format_hash, - hc_session->resp_headers.value_by_name)); msg.data.body_offset -= msg.data.headers_len + msg.data.headers_offset; http_build_header_table (&hc_session->resp_headers, msg); + HTTP_DBG (2, "%U", format_hash, + hc_session->resp_headers.value_by_name); const http_token_t *content_type = http_get_header ( &hc_session->resp_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); @@ -522,6 +528,11 @@ hc_rx_callback (session_t *s) } ASSERT (rv == n_deq); + if (svm_fifo_needs_deq_ntf (s->rx_fifo, n_deq)) + { + svm_fifo_clear_deq_ntf (s->rx_fifo); + session_program_transport_io_evt (s->handle, SESSION_IO_EVT_RX); + } if (!(hc_session->session_flags & HC_S_FLAG_CHUNKED_BODY)) vec_set_len (hc_session->http_response, curr + n_deq); ASSERT (hc_session->to_recv >= rv); @@ -552,10 +563,12 @@ hc_rx_callback (session_t *s) if (hc_session->stats.elapsed_time >= hcm->duration && hc_session->stats.request_count >= hc_session->stats.req_per_wrk) { + HTTP_DBG (1, "repeat done"); hc_session_disconnect_callback (s); } else { + HTTP_DBG (1, "doing another repeat"); send_err = hc_request (s, wrk, hc_session, session_err); if (send_err) clib_warning ("failed to send request, error %d", send_err); @@ -633,7 +646,7 @@ hc_attach () a->options[APP_OPTIONS_SEGMENT_SIZE] = segment_size; a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = segment_size; a->options[APP_OPTIONS_RX_FIFO_SIZE] = - hcm->fifo_size ? hcm->fifo_size : 8 << 10; + hcm->fifo_size ? hcm->fifo_size : 32 << 10; a->options[APP_OPTIONS_TX_FIFO_SIZE] = hcm->fifo_size ? hcm->fifo_size : 32 << 10; a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; @@ -707,6 +720,17 @@ hc_connect () &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, sizeof (transport_endpt_crypto_cfg_t)); ext_cfg->crypto.ckpair_index = hcm->ckpair_index; + switch (hcm->http_version) + { + case HTTP_VERSION_1: + ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; + break; + case HTTP_VERSION_2: + ext_cfg->crypto.alpn_protos[1] = TLS_ALPN_PROTO_HTTP_2; + break; + default: + break; + } } session_send_rpc_evt_to_thread_force (transport_cl_thread (), hc_connect_rpc, @@ -955,6 +979,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hcm->fifo_size = 0; hcm->was_transport_closed = false; hcm->verbose = false; + hcm->http_version = HTTP_VERSION_NA; /* default max - 64MB */ hcm->max_body_size = 64 << 20; hc_stats.request_count = 0; @@ -1035,7 +1060,10 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "secret %lu", &hcm->appns_secret)) ; - + else if (unformat (line_input, "http1")) + hcm->http_version = HTTP_VERSION_1; + else if (unformat (line_input, "http2")) + hcm->http_version = HTTP_VERSION_2; else { err = clib_error_return (0, "unknown input `%U'", @@ -1140,7 +1168,7 @@ VLIB_CLI_COMMAND (hc_command, static) = { "[timeout (default = 10)] [repeat | duration ] " "[sessions <# of sessions>] [appns secret ] " "[fifo-size ] [private-segment-size ] [prealloc-fifos ]" - "[max-body-size ]", + "[max-body-size ] [http1|http2]", .function = hc_command_fn, .is_mp_safe = 1, }; diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 5a61b0d717..ccf987a6ad 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -869,6 +869,7 @@ http_transport_connect (transport_endpoint_cfg_t *tep) u32 hc_index; session_t *ho; transport_endpt_ext_cfg_t *ext_cfg; + segment_manager_props_t *props; app_worker_t *app_wrk = app_worker_get (sep->app_wrk_index); clib_memset (cargs, 0, sizeof (*cargs)); @@ -883,7 +884,6 @@ http_transport_connect (transport_endpoint_cfg_t *tep) hc->hc_pa_wrk_index = sep->app_wrk_index; hc->hc_pa_app_api_ctx = sep->opaque; hc->state = HTTP_CONN_STATE_CONNECTING; - /* TODO: set to HTTP_VERSION_NA in case of TLS */ hc->version = HTTP_VERSION_1; cargs->api_context = hc_index; @@ -900,7 +900,15 @@ http_transport_connect (transport_endpoint_cfg_t *tep) if (ext_cfg) { HTTP_DBG (1, "app set tls"); + hc->version = HTTP_VERSION_NA; cargs->sep.transport_proto = TRANSPORT_PROTO_TLS; + if (ext_cfg->crypto.alpn_protos[0] == TLS_ALPN_PROTO_NONE) + { + HTTP_DBG (1, + "app do not set alpn list, using default (h2,http/1.1)"); + ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_2; + ext_cfg->crypto.alpn_protos[1] = TLS_ALPN_PROTO_HTTP_1_1; + } } if (vec_len (app->name)) @@ -928,6 +936,8 @@ http_transport_connect (transport_endpoint_cfg_t *tep) session_type_from_proto_and_ip (TRANSPORT_PROTO_HTTP, sep->is_ip4); hc->hc_tc_session_handle = cargs->sh; hc->c_s_index = ho->session_index; + props = application_segment_manager_properties (app); + hc->app_rx_fifo_size = props->rx_fifo_size; return 0; } diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 5777bd520a..61c387bf78 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -20,6 +20,7 @@ #include #include #include +#include #define HTTP_DEBUG 0 @@ -31,6 +32,14 @@ #define HTTP_DBG(_lvl, _fmt, _args...) #endif +typedef enum http_version_ +{ + HTTP_VERSION_1, + HTTP_VERSION_2, + HTTP_VERSION_3, + HTTP_VERSION_NA = 7, +} http_version_t; + typedef enum http_udp_tunnel_mode_ { HTTP_UDP_TUNNEL_CAPSULE, /**< app receive raw capsule */ @@ -393,6 +402,66 @@ typedef struct http_msg_ http_msg_data_t data; } http_msg_t; +typedef union +{ + struct + { + u32 version : 3; + u32 req_index : 29; + }; + u32 as_u32; +} http_req_handle_t; + +STATIC_ASSERT (sizeof (http_req_handle_t) == sizeof (u32), "must fit in u32"); + +always_inline http_version_t +http_session_get_version (session_t *s) +{ + http_req_handle_t h; + h.as_u32 = s->connection_index; + return (http_version_t) h.version; +} + +always_inline u8 * +format_http_version (u8 *s, va_list *va) +{ + http_version_t v = va_arg (*va, http_version_t); + switch (v) + { + case HTTP_VERSION_1: + s = format (s, "HTTP/1.1"); + break; + case HTTP_VERSION_2: + s = format (s, "HTTP/2"); + break; + case HTTP_VERSION_3: + s = format (s, "HTTP/3"); + break; + default: + s = format (s, "unknown"); + break; + } + return s; +} + +always_inline u8 * +format_http_method (u8 *s, va_list *va) +{ + http_req_method_t method = va_arg (*va, http_req_method_t); + u8 *t = 0; + + switch (method) + { +#define _(s, str) \ + case HTTP_REQ_##s: \ + t = (u8 *) str; \ + break; + foreach_http_method +#undef _ + default : return format (s, "unknown"); + } + return format (s, "%s", t); +} always_inline u8 * format_http_bytes (u8 *s, va_list *va) { diff --git a/src/plugins/http/http2/frame.c b/src/plugins/http/http2/frame.c index 07821de3be..fe593a0015 100644 --- a/src/plugins/http/http2/frame.c +++ b/src/plugins/http/http2/frame.c @@ -73,7 +73,8 @@ http2_frame_read_settings (http2_conn_settings_t *settings, u8 *payload, entry = (http2_settings_entry_t *) payload; switch (clib_net_to_host_u16 (entry->identifier)) { -#define _(v, label, member, min, max, default_value, err_code) \ +#define _(v, label, member, min, max, default_value, err_code, server, \ + client) \ case HTTP2_SETTINGS_##label: \ value = clib_net_to_host_u32 (entry->value); \ if (!(value >= min && value <= max)) \ diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 8e9e6d923a..9060a73bb1 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -30,7 +30,8 @@ typedef enum http2_stream_state_ #define foreach_http2_req_flags \ _ (APP_CLOSED, "app-closed") \ - _ (NEED_WINDOW_UPDATE, "need-window-update") + _ (NEED_WINDOW_UPDATE, "need-window-update") \ + _ (IS_PARENT, "is-parent") typedef enum http2_req_flags_bit_ { @@ -67,6 +68,7 @@ typedef struct http2_req_ #define foreach_http2_conn_flags \ _ (EXPECT_PREFACE, "expect-preface") \ _ (EXPECT_CONTINUATION, "expect-continuation") \ + _ (EXPECT_SERVER_SETTINGS, "expect-server-settings") \ _ (PREFACE_VERIFIED, "preface-verified") \ _ (TS_DESCHED, "ts-descheduled") @@ -206,7 +208,7 @@ http2_conn_ctx_free (http_conn_t *hc) } static inline http2_req_t * -http2_conn_alloc_req (http_conn_t *hc, u32 stream_id) +http2_conn_alloc_req (http_conn_t *hc) { http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); http2_conn_ctx_t *h2c; @@ -224,19 +226,29 @@ http2_conn_alloc_req (http_conn_t *hc, u32 stream_id) req->base.hr_hc_index = hc->hc_hc_index; req->base.c_thread_index = hc->c_thread_index; req->base.c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; - req->stream_id = stream_id; req->stream_state = HTTP2_STREAM_STATE_IDLE; req->sched_list.next = CLIB_LLIST_INVALID_INDEX; req->sched_list.prev = CLIB_LLIST_INVALID_INDEX; h2c = http2_conn_ctx_get_w_thread (hc); - HTTP_DBG (1, "h2c [%u]%x req_index %x stream_id %u", hc->c_thread_index, - h2c - wrk->conn_pool, req_index, stream_id); + HTTP_DBG (1, "h2c [%u]%x req_index %x", hc->c_thread_index, + h2c - wrk->conn_pool, req_index); req->peer_window = h2c->peer_settings.initial_window_size; req->our_window = h2c->settings.initial_window_size; - hash_set (h2c->req_by_stream_id, stream_id, req_index); return req; } +static_always_inline void +http2_req_set_stream_id (http2_req_t *req, http2_conn_ctx_t *h2c, + u32 stream_id) +{ + HTTP_DBG (1, "req_index [%u]%x stream_id %u", req->base.c_thread_index, + ((http_req_handle_t) req->base.hr_req_handle).req_index, + stream_id); + req->stream_id = stream_id; + hash_set (h2c->req_by_stream_id, stream_id, + ((http_req_handle_t) req->base.hr_req_handle).req_index); +} + static inline void http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req, clib_thread_index_t thread_index) @@ -252,12 +264,30 @@ http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req, vec_free (req->base.headers); vec_free (req->base.target); http_buffer_free (&req->base.tx_buf); - hash_unset (h2c->req_by_stream_id, req->stream_id); + if (req->stream_id) + hash_unset (h2c->req_by_stream_id, req->stream_id); if (CLIB_DEBUG) memset (req, 0xba, sizeof (*req)); pool_put (wrk->req_pool, req); } +static inline void +http2_conn_reset_req (http2_conn_ctx_t *h2c, http2_req_t *req, + clib_thread_index_t thread_index) +{ + http2_worker_ctx_t *wrk = http2_get_worker (thread_index); + + if (clib_llist_elt_is_linked (req, sched_list)) + clib_llist_remove (wrk->req_pool, sched_list, req); + http_buffer_free (&req->base.tx_buf); + if (req->stream_id) + hash_unset (h2c->req_by_stream_id, req->stream_id); + req->flags = 0; + req->stream_state = HTTP2_STREAM_STATE_IDLE; + req->peer_window = h2c->peer_settings.initial_window_size; + req->our_window = h2c->settings.initial_window_size; +} + http2_req_t * http2_conn_get_req (http_conn_t *hc, u32 stream_id) { @@ -287,6 +317,16 @@ http2_req_get (u32 req_index, clib_thread_index_t thread_index) return pool_elt_at_index (wrk->req_pool, req_index); } +always_inline u32 +http2_conn_get_next_stream_id (http2_conn_ctx_t *h2c) +{ + if (h2c->last_opened_stream_id) + h2c->last_opened_stream_id += 2; + else + h2c->last_opened_stream_id = 1; + return h2c->last_opened_stream_id; +} + always_inline void http2_conn_schedule (http2_conn_ctx_t *h2c, clib_thread_index_t thread_index) { @@ -451,8 +491,9 @@ http2_send_server_preface (http_conn_t *hc) http2_settings_entry_t *setting, *settings_list = 0; http2_conn_ctx_t *h2c = http2_conn_ctx_get_w_thread (hc); -#define _(v, label, member, min, max, default_value, err_code) \ - if (h2c->settings.member != default_value) \ +#define _(v, label, member, min, max, default_value, err_code, server, \ + client) \ + if (h2c->settings.member != default_value && server) \ { \ vec_add2 (settings_list, setting, 1); \ setting->identifier = HTTP2_SETTINGS_##label; \ @@ -471,6 +512,37 @@ http2_send_server_preface (http_conn_t *hc) vec_free (settings_list); } +always_inline void +http2_send_client_preface (http_conn_t *hc) +{ + u8 *response, *p; + http2_settings_entry_t *setting, *settings_list = 0; + http2_conn_ctx_t *h2c = http2_conn_ctx_get_w_thread (hc); + + response = http_get_tx_buf (hc); + vec_add2 (response, p, http2_conn_preface.len); + clib_memcpy_fast (p, http2_conn_preface.base, http2_conn_preface.len); + +#define _(v, label, member, min, max, default_value, err_code, server, \ + client) \ + if (h2c->settings.member != default_value && client) \ + { \ + vec_add2 (settings_list, setting, 1); \ + setting->identifier = HTTP2_SETTINGS_##label; \ + setting->value = h2c->settings.member; \ + } + foreach_http2_settings +#undef _ + + http2_frame_write_settings (settings_list, &response); + /* send also connection window update */ + http2_frame_write_window_update (h2c->our_window - HTTP2_INITIAL_WIN_SIZE, 0, + &response); + http_io_ts_write (hc, response, vec_len (response), 0); + http_io_ts_after_write (hc, 1); + vec_free (settings_list); +} + /***********************/ /* stream TX scheduler */ /***********************/ @@ -530,10 +602,15 @@ http2_sched_dispatch_data (http2_req_t *req, http_conn_t *hc, u8 *n_emissions) if (hc->flags & HTTP_CONN_F_IS_SERVER) http2_stream_close (req, hc); else - req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + { + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + http_req_state_change (&req->base, + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + } } else { + http_io_as_dequeue_notify (&req->base, n_written); if (req->peer_window == 0) { /* mark that we need window update on stream */ @@ -546,7 +623,6 @@ http2_sched_dispatch_data (http2_req_t *req, http_conn_t *hc, u8 *n_emissions) HTTP_DBG (1, "adding to data queue req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); http2_req_schedule_data_tx (hc, req); - http_io_as_dequeue_notify (&req->base, n_written); } } @@ -748,7 +824,6 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, vec_free (h2c->unsent_headers); *next_ri = clib_llist_next_index (req, sched_list); clib_llist_remove (wrk->req_pool, sched_list, req); - flags |= HTTP2_FRAME_FLAG_END_HEADERS; if (http_buffer_bytes_left (&req->base.tx_buf)) { /* start sending the actual data */ @@ -758,7 +833,16 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, http2_req_schedule_data_tx (hc, req); } else - http2_stream_close (req, hc); + { + if (hc->flags & HTTP_CONN_F_IS_SERVER) + http2_stream_close (req, hc); + else + { + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + http_req_state_change (&req->base, + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + } + } } else { @@ -769,8 +853,9 @@ http2_sched_dispatch_continuation (http2_req_t *req, http_conn_t *hc, } static void -http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, - u8 *n_emissions, clib_llist_index_t *next_ri) +http2_sched_dispatch_resp_headers (http2_req_t *req, http_conn_t *hc, + u8 *n_emissions, + clib_llist_index_t *next_ri) { http_msg_t msg; u8 *response, *date, *app_headers = 0; @@ -903,6 +988,122 @@ http2_sched_dispatch_headers (http2_req_t *req, http_conn_t *hc, http_io_ts_after_write (hc, 0); } +static void +http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, + u8 *n_emissions, clib_llist_index_t *next_ri) +{ + http_msg_t msg; + u8 *request, *app_headers = 0; + u8 fh[HTTP2_FRAME_HEADER_SIZE]; + hpack_request_control_data_t control_data; + u8 flags = 0; + u32 n_written, stream_id, n_deq, max_write, headers_len, headers_left; + http2_conn_ctx_t *h2c; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); + + req->stream_state = HTTP2_STREAM_STATE_OPEN; + + http_get_app_msg (&req->base, &msg); + ASSERT (msg.type == HTTP_MSG_REQUEST); + n_deq = sizeof (msg); + *n_emissions += msg.data.type == HTTP_MSG_DATA_PTR ? + HTTP2_SCHED_WEIGHT_HEADERS_PTR : + HTTP2_SCHED_WEIGHT_HEADERS_INLINE; + + request = http_get_tx_buf (hc); + + control_data.method = msg.method_type; + control_data.parsed_bitmap = HPACK_PSEUDO_HEADER_SCHEME_PARSED; + control_data.scheme = HTTP_URL_SCHEME_HTTPS; + control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; + control_data.path = http_get_app_target (&req->base, &msg); + control_data.path_len = msg.data.target_path_len; + control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + control_data.authority = hc->host; + control_data.authority_len = vec_len (hc->host); + control_data.user_agent = hc->app_name; + control_data.user_agent_len = vec_len (hc->app_name); + + HTTP_DBG (1, "%U %U", format_http_method, control_data.method, + format_http_bytes, control_data.path, control_data.path_len); + if (msg.data.body_len) + { + control_data.content_len = msg.data.body_len; + http_req_tx_buffer_init (&req->base, &msg); + } + else + { + control_data.content_len = HPACK_ENCODER_SKIP_CONTENT_LEN; + flags |= HTTP2_FRAME_FLAG_END_STREAM; + } + + if (msg.data.headers_len) + { + n_deq += msg.data.type == HTTP_MSG_DATA_PTR ? sizeof (uword) : + msg.data.headers_len; + app_headers = http_get_app_header_list (&req->base, &msg); + } + + hpack_serialize_request (app_headers, msg.data.headers_len, &control_data, + &request); + headers_len = vec_len (request); + + h2c = http2_conn_ctx_get_w_thread (hc); + + max_write = http_io_ts_max_write (hc, 0); + max_write -= HTTP2_FRAME_HEADER_SIZE; + max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); + + stream_id = http2_conn_get_next_stream_id (h2c); + http2_req_set_stream_id (req, h2c, stream_id); + + http_io_as_dequeue_notify (&req->base, n_deq); + + if (headers_len <= max_write) + { + *next_ri = clib_llist_next_index (req, sched_list); + clib_llist_remove (wrk->req_pool, sched_list, req); + flags |= HTTP2_FRAME_FLAG_END_HEADERS; + if (msg.data.body_len) + { + /* start sending the actual data */ + req->dispatch_data_cb = http2_sched_dispatch_data; + HTTP_DBG (1, "adding to data queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + http2_req_schedule_data_tx (hc, req); + } + else + { + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + http_req_state_change (&req->base, + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + } + } + else + { + /* we need to send CONTINUATION frame as next */ + HTTP_DBG (1, "response headers need to be fragmented"); + *next_ri = clib_llist_entry_index (wrk->req_pool, req); + headers_len = max_write; + headers_left = vec_len (request) - headers_len; + req->dispatch_headers_cb = http2_sched_dispatch_continuation; + /* move unsend portion of headers to connection ctx */ + ASSERT (h2c->unsent_headers == 0); + vec_validate (h2c->unsent_headers, headers_left - 1); + clib_memcpy_fast (h2c->unsent_headers, request + headers_len, + headers_left); + h2c->unsent_headers_offset = 0; + *n_emissions += HTTP2_SCHED_WEIGHT_HEADERS_CONTINUATION; + } + + http2_frame_write_headers_header (headers_len, stream_id, flags, fh); + svm_fifo_seg_t segs[2] = { { fh, HTTP2_FRAME_HEADER_SIZE }, + { request, headers_len } }; + n_written = http_io_ts_write_segs (hc, segs, 2, 0); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + headers_len)); + http_io_ts_after_write (hc, 0); +} + static void http2_update_time_callback (f64 now, u8 thread_index) { @@ -992,6 +1193,101 @@ http2_update_time_callback (f64 now, u8 thread_index) /* request state machine handlers RX */ /*************************************/ +static http_sm_result_t +http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, + transport_send_params_t *sp, + http2_error_t *error) +{ + http2_conn_ctx_t *h2c; + hpack_response_control_data_t control_data; + http_msg_t msg; + int rv; + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); + + h2c = http2_conn_ctx_get_w_thread (hc); + + vec_reset_length (req->base.headers); + *error = + hpack_parse_response (req->payload, req->payload_len, wrk->header_list, + vec_len (wrk->header_list), &control_data, + &req->base.headers, &h2c->decoder_dynamic_table); + if (*error != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_parse_response failed"); + return HTTP_SM_ERROR; + } + + HTTP_DBG (1, "decompressed headers size %u", control_data.headers_len); + HTTP_DBG (1, "dynamic table size %u", h2c->decoder_dynamic_table.used); + + req->base.control_data_len = control_data.control_data_len; + req->base.headers_offset = control_data.headers - wrk->header_list; + req->base.headers_len = control_data.headers_len; + req->base.status_code = control_data.sc; + + if (!(control_data.parsed_bitmap & HPACK_PSEUDO_HEADER_STATUS_PARSED)) + { + HTTP_DBG (1, ":status pseudo-header missing in request"); + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + + if (control_data.content_len_header_index != ~0) + { + req->base.content_len_header_index = + control_data.content_len_header_index; + rv = http_parse_content_length (&req->base, wrk->header_list); + if (rv) + { + http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); + return HTTP_SM_STOP; + } + } + /* TODO: message framing without content length using END_STREAM flag */ + if (req->base.body_len == 0 && + req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED) + { + HTTP_DBG (1, "no content-length and DATA frame expected"); + *error = HTTP2_ERROR_INTERNAL_ERROR; + return HTTP_SM_ERROR; + } + req->base.to_recv = req->base.body_len; + + msg.type = HTTP_MSG_REPLY; + msg.code = req->base.status_code; + msg.data.headers_offset = req->base.headers_offset; + msg.data.headers_len = req->base.headers_len; + msg.data.headers_ctx = pointer_to_uword (req->base.headers); + msg.data.body_offset = req->base.control_data_len; + msg.data.body_len = req->base.body_len; + msg.data.type = HTTP_MSG_DATA_INLINE; + + svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) }, + { wrk->header_list, + req->base.control_data_len } }; + HTTP_DBG (3, "%U", format_http_bytes, wrk->header_list, + req->base.control_data_len); + http_io_as_write_segs (&req->base, segs, 2); + + if (req->base.body_len) + { + http_req_state_change (&req->base, + HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); + http_io_as_add_want_read_ntf (&req->base); + } + else + { + /* we are done wait for the next app request */ + http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); + transport_connection_reschedule (&req->base.connection); + http2_conn_reset_req (h2c, req, hc->c_thread_index); + } + + http_app_worker_rx_notify (&req->base); + + return HTTP_SM_STOP; +} + static http_sm_result_t http2_req_state_wait_transport_method (http_conn_t *hc, http2_req_t *req, transport_send_params_t *sp, @@ -1222,6 +1518,8 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req, http2_error_t *error) { u32 max_enq; + http2_stream_state_t expected_state; + http2_conn_ctx_t *h2c; if (req->payload_len > req->base.to_recv) { @@ -1230,8 +1528,10 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } req->base.to_recv -= req->payload_len; - if (req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED && - req->base.to_recv != 0) + expected_state = hc->flags & HTTP_CONN_F_IS_SERVER ? + HTTP2_STREAM_STATE_HALF_CLOSED : + HTTP2_STREAM_STATE_CLOSED; + if (req->stream_state == expected_state && req->base.to_recv != 0) { HTTP_DBG (1, "peer closed stream but don't send all data"); http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); @@ -1245,7 +1545,23 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } if (req->base.to_recv == 0) - http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_REPLY); + { + if (hc->flags & HTTP_CONN_F_IS_SERVER) + { + http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_REPLY); + } + else + { + /* we are done wait for the next app request */ + http_req_state_change (&req->base, + hc->flags & HTTP_CONN_F_IS_SERVER ? + HTTP_REQ_STATE_WAIT_APP_REPLY : + HTTP_REQ_STATE_WAIT_APP_METHOD); + transport_connection_reschedule (&req->base.connection); + h2c = http2_conn_ctx_get_w_thread (hc); + http2_conn_reset_req (h2c, req, hc->c_thread_index); + } + } http_io_as_write (&req->base, req->payload, req->payload_len); http_app_worker_rx_notify (&req->base); @@ -1348,6 +1664,7 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } + /*************************************/ /* request state machine handlers TX */ /*************************************/ @@ -1399,6 +1716,40 @@ http2_req_state_app_io_more_data (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } +static http_sm_result_t +http2_req_state_wait_app_method (http_conn_t *hc, http2_req_t *req, + transport_send_params_t *sp, + http2_error_t *error) +{ + http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); + http2_req_t *he; + http2_conn_ctx_t *h2c; + + ASSERT (!clib_llist_elt_is_linked (req, sched_list)); + + h2c = http2_conn_ctx_get_w_thread (hc); + + if (!(hc->flags & HTTP_CONN_F_HAS_REQUEST)) + { + hc->flags |= HTTP_CONN_F_HAS_REQUEST; + hpack_dynamic_table_init (&h2c->decoder_dynamic_table, + http2_default_conn_settings.header_table_size); + } + + /* add response to stream scheduler */ + HTTP_DBG (1, "adding to headers queue req_index %x", + ((http_req_handle_t) req->base.hr_req_handle).req_index); + he = clib_llist_elt (wrk->req_pool, h2c->new_tx_streams); + clib_llist_add_tail (wrk->req_pool, sched_list, req, he); + http2_conn_schedule (h2c, hc->c_thread_index); + + req->dispatch_headers_cb = http2_sched_dispatch_req_headers; + http_req_state_change (&req->base, HTTP_REQ_STATE_APP_IO_MORE_DATA); + http_req_deschedule (&req->base, sp); + + return HTTP_SM_STOP; +} + static http_sm_result_t http2_req_state_tunnel_tx (http_conn_t *hc, http2_req_t *req, transport_send_params_t *sp, http2_error_t *error) @@ -1433,7 +1784,7 @@ typedef http_sm_result_t (*http2_sm_handler) (http_conn_t *hc, static http2_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { 0, /* idle */ - 0, /* wait app method */ + http2_req_state_wait_app_method, 0, /* wait transport reply */ 0, /* transport io more data */ 0, /* wait transport method */ @@ -1447,7 +1798,7 @@ static http2_sm_handler tx_state_funcs[HTTP_REQ_N_STATES] = { static http2_sm_handler rx_state_funcs[HTTP_REQ_N_STATES] = { 0, /* idle */ 0, /* wait app method */ - 0, /* wait transport reply */ + http2_req_state_wait_transport_reply, http2_req_state_transport_io_more_data, http2_req_state_wait_transport_method, 0, /* wait app reply */ @@ -1462,6 +1813,12 @@ http2_req_state_is_tx_valid (http2_req_t *req) return tx_state_funcs[req->base.state] ? 1 : 0; } +static_always_inline int +http2_req_state_is_rx_valid (http2_req_t *req) +{ + return rx_state_funcs[req->base.state] ? 1 : 0; +} + static_always_inline http2_error_t http2_req_run_state_machine (http_conn_t *hc, http2_req_t *req, transport_send_params_t *sp, u8 is_tx) @@ -1501,9 +1858,10 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) http2_error_t rv; http2_conn_ctx_t *h2c; + h2c = http2_conn_ctx_get_w_thread (hc); + if (hc->flags & HTTP_CONN_F_IS_SERVER) { - h2c = http2_conn_ctx_get_w_thread (hc); /* streams initiated by client must use odd-numbered stream id */ if ((fh->stream_id & 1) == 0) { @@ -1526,8 +1884,9 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) HTTP2_ERROR_REFUSED_STREAM, 0); return HTTP2_ERROR_NO_ERROR; } - req = http2_conn_alloc_req (hc, fh->stream_id); - req->dispatch_headers_cb = http2_sched_dispatch_headers; + req = http2_conn_alloc_req (hc); + http2_req_set_stream_id (req, h2c, fh->stream_id); + req->dispatch_headers_cb = http2_sched_dispatch_resp_headers; http_conn_accept_request (hc, &req->base); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); req->stream_state = HTTP2_STREAM_STATE_OPEN; @@ -1569,8 +1928,54 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) } else { - /* TODO: client */ - return HTTP2_ERROR_INTERNAL_ERROR; + req = http2_conn_get_req (hc, fh->stream_id); + if (!req) + return HTTP2_ERROR_PROTOCOL_ERROR; + + if (!http2_req_state_is_rx_valid (req)) + { + if (req->base.state == HTTP_REQ_STATE_APP_IO_MORE_DATA) + { + /* client can receive error response from server when still + * sending content */ + /* TODO: 100 continue support */ + HTTP_DBG (1, "server send response while client sending data"); + http_io_as_drain_all (&req->base); + hc->state = HTTP_CONN_STATE_CLOSED; + http_req_state_change (&req->base, + HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); + } + else + return HTTP2_ERROR_INTERNAL_ERROR; + } + + if (fh->flags & HTTP2_FRAME_FLAG_END_STREAM) + req->stream_state = HTTP2_STREAM_STATE_CLOSED; + + if (!(fh->flags & HTTP2_FRAME_FLAG_END_HEADERS)) + { + HTTP_DBG (1, "fragmented headers stream id %u", fh->stream_id); + h2c->flags |= HTTP2_CONN_F_EXPECT_CONTINUATION; + vec_validate (h2c->unparsed_headers, fh->length - 1); + http_io_ts_read (hc, h2c->unparsed_headers, fh->length, 0); + rv = http2_frame_read_headers (&headers_start, &headers_len, + h2c->unparsed_headers, fh->length, + fh->flags); + if (rv != HTTP2_ERROR_NO_ERROR) + return rv; + + /* in case frame has padding */ + if (PREDICT_FALSE (headers_start != h2c->unparsed_headers)) + { + n_dec = fh->length - headers_len; + n_del = headers_start - h2c->unparsed_headers; + n_dec -= n_del; + vec_delete (h2c->unparsed_headers, n_del, 0); + vec_dec_len (h2c->unparsed_headers, n_dec); + } + + return HTTP2_ERROR_NO_ERROR; + } } rx_buf = http_get_rx_buf (hc); @@ -1594,42 +1999,34 @@ http2_handle_continuation_frame (http_conn_t *hc, http2_frame_header_t *fh) u8 *p; http2_error_t rv = HTTP2_ERROR_NO_ERROR; - if (hc->flags & HTTP_CONN_F_IS_SERVER) - { - h2c = http2_conn_ctx_get_w_thread (hc); + h2c = http2_conn_ctx_get_w_thread (hc); - if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION)) - { - HTTP_DBG (1, "unexpected CONTINUATION frame"); - return HTTP2_ERROR_PROTOCOL_ERROR; - } + if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION)) + { + HTTP_DBG (1, "unexpected CONTINUATION frame"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } - if (fh->stream_id != h2c->last_opened_stream_id) - { - HTTP_DBG (1, "invalid stream id %u", fh->stream_id); - return HTTP2_ERROR_PROTOCOL_ERROR; - } + if (fh->stream_id != h2c->last_opened_stream_id) + { + HTTP_DBG (1, "invalid stream id %u", fh->stream_id); + return HTTP2_ERROR_PROTOCOL_ERROR; + } - vec_add2 (h2c->unparsed_headers, p, fh->length); - http_io_ts_read (hc, p, fh->length, 0); + vec_add2 (h2c->unparsed_headers, p, fh->length); + http_io_ts_read (hc, p, fh->length, 0); - if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS) - { - req = http2_conn_get_req (hc, fh->stream_id); - if (!req) - return HTTP2_ERROR_PROTOCOL_ERROR; - h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION; - req->payload = h2c->unparsed_headers; - req->payload_len = vec_len (h2c->unparsed_headers); - HTTP_DBG (1, "run state machine"); - rv = http2_req_run_state_machine (hc, req, 0, 0); - vec_free (h2c->unparsed_headers); - } - } - else + if (fh->flags & HTTP2_FRAME_FLAG_END_HEADERS) { - /* TODO: client */ - return HTTP2_ERROR_INTERNAL_ERROR; + req = http2_conn_get_req (hc, fh->stream_id); + if (!req) + return HTTP2_ERROR_PROTOCOL_ERROR; + h2c->flags &= ~HTTP2_CONN_F_EXPECT_CONTINUATION; + req->payload = h2c->unparsed_headers; + req->payload_len = vec_len (h2c->unparsed_headers); + HTTP_DBG (1, "run state machine"); + rv = http2_req_run_state_machine (hc, req, 0, 0); + vec_free (h2c->unparsed_headers); } return rv; @@ -1712,7 +2109,9 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) } } else - req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + req->stream_state = hc->flags & HTTP_CONN_F_IS_SERVER ? + HTTP2_STREAM_STATE_HALF_CLOSED : + HTTP2_STREAM_STATE_CLOSED; } rx_buf = http_get_rx_buf (hc); @@ -1820,8 +2219,12 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) if (fh->stream_id != 0) return HTTP2_ERROR_PROTOCOL_ERROR; + h2c = http2_conn_ctx_get_w_thread (hc); + if (fh->flags == HTTP2_FRAME_FLAG_ACK) { + if (h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS) + return HTTP2_ERROR_PROTOCOL_ERROR; if (fh->length != 0) return HTTP2_ERROR_FRAME_SIZE_ERROR; /* TODO: we can start using non-default settings */ @@ -1835,12 +2238,21 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) vec_validate (rx_buf, fh->length - 1); http_io_ts_read (hc, rx_buf, fh->length, 0); - h2c = http2_conn_ctx_get_w_thread (hc); new_settings = h2c->peer_settings; rv = http2_frame_read_settings (&new_settings, rx_buf, fh->length); if (rv != HTTP2_ERROR_NO_ERROR) return rv; + if (h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS) + { + h2c->flags &= ~HTTP2_CONN_F_EXPECT_SERVER_SETTINGS; + /* client connection is now established */ + req = http2_conn_alloc_req (hc); + http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); + if (http_conn_established (hc, &req->base)) + return HTTP2_ERROR_INTERNAL_ERROR; + } + /* ACK peer settings */ http2_frame_write_settings_ack (&resp); http_io_ts_write (hc, resp, vec_len (resp), 0); @@ -2135,6 +2547,7 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, http2_req_t *req; u8 *response; u32 increment; + http2_stream_state_t expected_state; req = http2_req_get (req_index, thread_index); if (!req) @@ -2145,7 +2558,10 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, HTTP_DBG (1, "received app read notification stream id %u", req->stream_id); /* send stream window update if app read data in rx fifo and we expect more * data (stream is still open) */ - if (req->stream_state == HTTP2_STREAM_STATE_OPEN) + expected_state = hc->flags & HTTP_CONN_F_IS_SERVER ? + HTTP2_STREAM_STATE_OPEN : + HTTP2_STREAM_STATE_HALF_CLOSED; + if (req->stream_state == expected_state) { http_io_as_reset_has_read_ntf (&req->base); response = http_get_tx_buf (hc); @@ -2176,6 +2592,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, } if (req->stream_state == HTTP2_STREAM_STATE_CLOSED || + req->stream_state == HTTP2_STREAM_STATE_IDLE || hc->state == HTTP_CONN_STATE_CLOSED) { HTTP_DBG (1, "nothing more to send, confirm close"); @@ -2232,8 +2649,15 @@ http2_app_reset_callback (http_conn_t *hc, u32 req_index, static int http2_transport_connected_callback (http_conn_t *hc) { - /* TODO */ - return -1; + 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_SERVER_SETTINGS; + + http2_send_client_preface (hc); + + return 0; } static void @@ -2324,6 +2748,14 @@ http2_transport_rx_callback (http_conn_t *hc) return; } + if ((h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS) && + fh.type != HTTP2_FRAME_TYPE_SETTINGS) + { + HTTP_DBG (1, "expected SETTINGS frame (server preface)"); + http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); + return; + } + switch (fh.type) { case HTTP2_FRAME_TYPE_HEADERS: @@ -2530,7 +2962,8 @@ http2_update_settings (http_settings_t type, u32 value) switch (type) { -#define _(v, label, member, min, max, default_value, err_code) \ +#define _(v, label, member, min, max, default_value, err_code, server, \ + client) \ case HTTP2_SETTINGS_##label: \ if (!(value >= min && value <= max)) \ return -1; \ @@ -2611,6 +3044,7 @@ http2_init (vlib_main_t *vm) h2m->settings.max_concurrent_streams = 100; /* by default unlimited */ h2m->settings.max_header_list_size = 1 << 14; /* by default unlimited */ h2m->settings.enable_connect_protocol = 1; /* enable extended connect */ + h2m->settings.enable_push = 0; /* by default enabled */ http_register_engine (&http2_engine, HTTP_VERSION_2); return 0; diff --git a/src/plugins/http/http2/http2.h b/src/plugins/http/http2/http2.h index b48c2a2715..67277f1fa7 100644 --- a/src/plugins/http/http2/http2.h +++ b/src/plugins/http/http2/http2.h @@ -59,25 +59,26 @@ format_http2_error (u8 *s, va_list *va) _ (4, STATUS, "status") \ _ (5, PROTOCOL, "protocol") -/* value, label, member, min, max, default_value, err_code */ +/* value, label, member, min, max, default_value, err_code, server, client */ #define foreach_http2_settings \ _ (1, HEADER_TABLE_SIZE, header_table_size, 0, CLIB_U32_MAX, 4096, \ - HTTP2_ERROR_NO_ERROR) \ - _ (2, ENABLE_PUSH, enable_push, 0, 1, 1, HTTP2_ERROR_PROTOCOL_ERROR) \ + HTTP2_ERROR_NO_ERROR, 1, 1) \ + _ (2, ENABLE_PUSH, enable_push, 0, 1, 1, HTTP2_ERROR_PROTOCOL_ERROR, 0, 1) \ _ (3, MAX_CONCURRENT_STREAMS, max_concurrent_streams, 0, CLIB_U32_MAX, \ - CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR) \ + CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR, 1, 1) \ _ (4, INITIAL_WINDOW_SIZE, initial_window_size, 0, 0x7FFFFFFF, 65535, \ - HTTP2_ERROR_FLOW_CONTROL_ERROR) \ + HTTP2_ERROR_FLOW_CONTROL_ERROR, 1, 1) \ _ (5, MAX_FRAME_SIZE, max_frame_size, 16384, 16777215, 16384, \ - HTTP2_ERROR_PROTOCOL_ERROR) \ + HTTP2_ERROR_PROTOCOL_ERROR, 1, 1) \ _ (6, MAX_HEADER_LIST_SIZE, max_header_list_size, 0, CLIB_U32_MAX, \ - CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR) \ + CLIB_U32_MAX, HTTP2_ERROR_NO_ERROR, 1, 1) \ _ (8, ENABLE_CONNECT_PROTOCOL, enable_connect_protocol, 0, 1, 0, \ - HTTP2_ERROR_NO_ERROR) + HTTP2_ERROR_NO_ERROR, 1, 0) typedef enum { -#define _(value, label, member, min, max, default_value, err_code) \ +#define _(value, label, member, min, max, default_value, err_code, server, \ + client) \ HTTP2_SETTINGS_##label = value, foreach_http2_settings #undef _ @@ -85,13 +86,16 @@ typedef enum typedef struct { -#define _(value, label, member, min, max, default_value, err_code) u32 member; +#define _(value, label, member, min, max, default_value, err_code, server, \ + client) \ + u32 member; foreach_http2_settings #undef _ } http2_conn_settings_t; static const http2_conn_settings_t http2_default_conn_settings = { -#define _(value, label, member, min, max, default_value, err_code) \ +#define _(value, label, member, min, max, default_value, err_code, server, \ + client) \ default_value, foreach_http2_settings #undef _ diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index 5ad940d5c4..51fdfd65d3 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -29,18 +29,6 @@ typedef union STATIC_ASSERT (sizeof (http_conn_handle_t) == sizeof (u32), "must fit in u32"); -typedef union -{ - struct - { - u32 version : 3; - u32 req_index : 29; - }; - u32 as_u32; -} http_req_handle_t; - -STATIC_ASSERT (sizeof (http_req_handle_t) == sizeof (u32), "must fit in u32"); - #define foreach_http_conn_state \ _ (LISTEN, "LISTEN") \ _ (CONNECTING, "CONNECTING") \ @@ -84,14 +72,6 @@ typedef enum http_target_form_ HTTP_TARGET_ASTERISK_FORM } http_target_form_t; -typedef enum http_version_ -{ - HTTP_VERSION_1, - HTTP_VERSION_2, - HTTP_VERSION_3, - HTTP_VERSION_NA = 7, -} http_version_t; - typedef struct http_req_id_ { session_handle_t app_session_handle; From 5c7b8cabbf7bdaf43651160c1e064915572415f5 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Wed, 25 Jun 2025 12:16:41 +0000 Subject: [PATCH 137/313] hsa: measure rtt in echo client For TCP, rtt is extracted from transport info. For UDP & echo-bytes option, we're measuring the time between sending & receiving the first packet. When using multiple clients, min/avg/max rtt values are displayed. Type: improvement Change-Id: I617527ca55726571638996dbcff05c2292f0ad18 Signed-off-by: Semir Sionek --- extras/hs-test/echo_test.go | 30 +++++++++++++++- src/plugins/hs_apps/echo_client.c | 60 +++++++++++++++++++++++++++++++ src/plugins/hs_apps/echo_client.h | 18 ++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 53f83542bc..24ebb6d3d8 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -8,7 +8,7 @@ import ( ) func init() { - RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest) + RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest) RegisterSoloVethTests(TcpWithLossTest) RegisterVeth6Tests(TcpWithLoss6Test) } @@ -57,6 +57,34 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { } } +func EchoBuiltinRoundtripTest(s *VethsSuite) { + regex := regexp.MustCompile(`(\.\d+)ms roundtrip`) + serverVpp := s.Containers.ServerVpp.VppInstance + + serverVpp.Vppctl("test echo server " + + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + + clientVpp := s.Containers.ClientVpp.VppInstance + + o := clientVpp.Vppctl("test echo client bytes 8m" + + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + s.Log(o) + s.AssertContains(o, "Test started") + s.AssertContains(o, "Test finished") + if regex.MatchString(o) { + matches := regex.FindStringSubmatch(o) + if len(matches) != 0 { + seconds, _ := strconv.ParseFloat(matches[1], 32) + // Make sure that we are within ms range + s.AssertEqualWithinThreshold(seconds, 0.5, 0.5) + } else { + s.AssertEmpty("invalid echo test client output") + } + } else { + s.AssertEmpty("invalid echo test client output") + } +} + func EchoBuiltinEchobytesTest(s *VethsSuite) { serverVpp := s.Containers.ServerVpp.VppInstance diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 2d70120b8d..4bc464b7b8 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -16,6 +16,7 @@ */ #include +#include static ec_main_t ec_main; @@ -76,6 +77,20 @@ ec_session_get (ec_worker_t *wrk, u32 ec_index) return pool_elt_at_index (wrk->sessions, ec_index); } +static void +update_rtt_stats (f64 session_rtt) +{ + ec_main_t *ecm = &ec_main; + clib_spinlock_lock (&ecm->rtt_stats.w_lock); + ecm->rtt_stats.sum_rtt += session_rtt; + ecm->rtt_stats.n_sum++; + if (session_rtt < ecm->rtt_stats.min_rtt) + ecm->rtt_stats.min_rtt = session_rtt; + if (session_rtt > ecm->rtt_stats.max_rtt) + ecm->rtt_stats.max_rtt = session_rtt; + clib_spinlock_unlock (&ecm->rtt_stats.w_lock); +} + static void send_data_chunk (ec_main_t *ecm, ec_session_t *es) { @@ -221,6 +236,12 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) if (n_read > 0) { + if (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes && + (es->rtt_stat & EC_UDP_RTT_RX_FLAG) == 0) + { + es->rtt_stat |= EC_UDP_RTT_RX_FLAG; + update_rtt_stats (vlib_time_now (vlib_get_main ()) - es->send_rtt); + } if (ecm->cfg.verbose) { ELOG_TYPE_DECLARE (e) = @@ -331,6 +352,12 @@ ec_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) if (es->bytes_to_send > 0) { + if (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes && + (es->rtt_stat & EC_UDP_RTT_TX_FLAG) == 0) + { + es->send_rtt = time_now; + es->rtt_stat |= EC_UDP_RTT_TX_FLAG; + } send_data_chunk (ecm, es); if (ecm->throughput) es->time_to_send += ecm->pacing_window_len; @@ -350,6 +377,19 @@ ec_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) if (s) { + if (ecm->transport_proto == TRANSPORT_PROTO_TCP) + { + transport_connection_t *tc; + tcp_connection_t *tcpc; + tc = transport_get_connection ( + TRANSPORT_PROTO_TCP, s->connection_index, s->thread_index); + if (PREDICT_TRUE (tc != NULL)) + { + tcpc = tcp_get_connection_from_transport (tc); + update_rtt_stats (tcpc->srtt * TCP_TICK); + } + } + vnet_disconnect_args_t _a, *a = &_a; a->handle = session_handle (s); a->app_index = ecm->app_index; @@ -420,6 +460,10 @@ ec_reset_runtime_config (ec_main_t *ecm) ecm->throughput = 0; ecm->pacing_window_len = 1; ecm->max_chunk_bytes = 128 << 10; + clib_memset (&ecm->rtt_stats, 0, sizeof (ec_rttstat_t)); + ecm->rtt_stats.min_rtt = CLIB_F64_MAX; + if (ecm->rtt_stats.w_lock == NULL) + clib_spinlock_init (&ecm->rtt_stats.w_lock); vec_free (ecm->connect_uri); } @@ -530,6 +574,7 @@ ec_cleanup (ec_main_t *ecm) ecm->pacing_window_len = 1; if (ecm->barrier_acq_needed) vlib_worker_thread_barrier_sync (ecm->vlib_main); + clib_spinlock_free (&ecm->rtt_stats.w_lock); } static int @@ -1392,6 +1437,21 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, /* * Done. Compute stats */ + if (ecm->transport_proto == TRANSPORT_PROTO_TCP || + (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes)) + { + /* display rtt stats in milliseconds */ + if (ecm->rtt_stats.n_sum == 1) + ec_cli ("%.05fms roundtrip", ecm->rtt_stats.min_rtt * 1000); + else if (ecm->rtt_stats.n_sum > 1) + ec_cli ("%.05fms/%.05fms/%.05fms min/avg/max roundtrip", + ecm->rtt_stats.min_rtt * 1000, + ecm->rtt_stats.sum_rtt / ecm->rtt_stats.n_sum * 1000, + ecm->rtt_stats.max_rtt * 1000); + else + ec_cli ("error measuring roundtrip time"); + } + delta = ecm->test_end_time - ecm->test_start_time; if (delta == 0.0) { diff --git a/src/plugins/hs_apps/echo_client.h b/src/plugins/hs_apps/echo_client.h index d0eed6bd73..01b807a946 100644 --- a/src/plugins/hs_apps/echo_client.h +++ b/src/plugins/hs_apps/echo_client.h @@ -22,6 +22,15 @@ #include #include +typedef struct ec_rttstat_ +{ + f64 min_rtt; + f64 max_rtt; + f64 sum_rtt; + u32 n_sum; + clib_spinlock_t w_lock; +} ec_rttstat_t; + typedef struct ec_session_ { CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); @@ -38,6 +47,8 @@ typedef struct ec_session_ f64 time_to_send; u64 bytes_paced_target; u64 bytes_paced_current; + f64 send_rtt; + u8 rtt_stat; } ec_session_t; typedef struct ec_worker_ @@ -67,6 +78,7 @@ typedef struct f64 test_end_time; u32 prev_conns; u32 repeats; + ec_rttstat_t rtt_stats; f64 pacing_window_len; /**< Time between data chunk sends when limiting tput */ @@ -141,6 +153,12 @@ typedef enum ec_cli_signal_ EC_CLI_TEST_DONE } ec_cli_signal_t; +typedef enum ec_rtt_stat_ +{ + EC_UDP_RTT_TX_FLAG = 1, + EC_UDP_RTT_RX_FLAG = 2 +} ec_rtt_stat; + void ec_program_connects (void); #endif /* __included_echo_client_h__ */ From 44f8190ebff278c0b25666bd89c5ed67b3e7fa33 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 16 Jul 2025 14:48:25 -0700 Subject: [PATCH 138/313] hsa: do not configure cert for tls ao No need to configure cert for tls active opens as we don't support mtls for now. Type: improvement Change-Id: Ied0a0152ec965e10f1a743a15188a4679ebb0b11 Signed-off-by: Florin Coras --- src/plugins/hs_apps/proxy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index a4b1aeaea4..2c7535dd82 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -686,7 +686,8 @@ proxy_session_start_connect (proxy_session_side_ctx_t *sc, session_t *s) transport_endpt_ext_cfg_t *ext_cfg = session_endpoint_add_ext_cfg ( &a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, sizeof (transport_endpt_crypto_cfg_t)); - ext_cfg->crypto.ckpair_index = pm->ckpair_index; + ext_cfg->crypto.crypto_engine = CRYPTO_ENGINE_NONE; + /* mTLS not supported, so no cert configured for now */ } proxy_program_connect (a); From 23a4d6df38d4a4a49838bb14a9c53042c8664d2d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 16 Jul 2025 11:01:31 -0400 Subject: [PATCH 139/313] http: h2 client handle GOAWAY NO_ERROR Type: improvement Change-Id: I244528cdab03c3f17d17f98fd9182399b2ac6a17 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 9060a73bb1..f10113e80e 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -104,6 +104,7 @@ typedef struct http2_conn_ctx_ u8 *unparsed_headers; /* temporary storing rx fragmented headers */ u8 *unsent_headers; /* temporary storing tx fragmented headers */ u32 unsent_headers_offset; + u32 client_req_index; } http2_conn_ctx_t; typedef struct http2_worker_ctx_ @@ -403,11 +404,17 @@ http2_connection_error (http_conn_t *hc, http2_error_t error, http_io_ts_write (hc, response, vec_len (response), sp); http_io_ts_after_write (hc, 1); - hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ - req = http2_req_get (req_index, hc->c_thread_index); - if (req->stream_state != HTTP2_STREAM_STATE_CLOSED) - session_transport_reset_notify (&req->base.connection); - })); + if (hc->flags & HTTP_CONN_F_IS_SERVER) + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + if (req->stream_state != HTTP2_STREAM_STATE_CLOSED) + session_transport_reset_notify (&req->base.connection); + })); + else + { + req = http2_req_get (h2c->client_req_index, hc->c_thread_index); + session_transport_reset_notify (&req->base.connection); + } if (clib_llist_elt_is_linked (h2c, sched_list)) clib_llist_remove (wrk->conn_pool, sched_list, h2c); http_shutdown_transport (hc); @@ -2248,6 +2255,8 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) h2c->flags &= ~HTTP2_CONN_F_EXPECT_SERVER_SETTINGS; /* client connection is now established */ req = http2_conn_alloc_req (hc); + h2c->client_req_index = + ((http_req_handle_t) req->base.hr_req_handle).req_index; http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); if (http_conn_established (hc, &req->base)) return HTTP2_ERROR_INTERNAL_ERROR; @@ -2351,9 +2360,17 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) HTTP_DBG (1, "received GOAWAY %U, last stream id %u", format_http2_error, error_code, last_stream_id); + h2c = http2_conn_ctx_get_w_thread (hc); if (error_code == HTTP2_ERROR_NO_ERROR) { - /* TODO: graceful shutdown (no new streams) */ + /* graceful shutdown (no new streams for client) */ + if (!(hc->flags & HTTP_CONN_F_IS_SERVER)) + { + req = http2_req_get (h2c->client_req_index, hc->c_thread_index); + if (!req) + return HTTP2_ERROR_NO_ERROR; + session_transport_closed_notify (&req->base.connection); + } } else { @@ -2362,7 +2379,6 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) rx_buf + HTTP2_GOAWAY_MIN_SIZE, fh->length - HTTP2_GOAWAY_MIN_SIZE); /* connection error */ - h2c = http2_conn_ctx_get_w_thread (hc); hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ req = http2_req_get (req_index, hc->c_thread_index); session_transport_reset_notify (&req->base.connection); @@ -2921,8 +2937,11 @@ http2_conn_cleanup_callback (http_conn_t *hc) HTTP_DBG (1, "hc [%u]%x", hc->c_thread_index, hc->hc_hc_index); h2c = http2_conn_ctx_get_w_thread (hc); - hash_foreach (stream_id, req_index, h2c->req_by_stream_id, - ({ vec_add1 (req_indices, req_index); })); + if (hc->flags & HTTP_CONN_F_IS_SERVER) + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, + ({ vec_add1 (req_indices, req_index); })); + else + vec_add1 (req_indices, h2c->client_req_index); vec_foreach (req_index_p, req_indices) { From 3e7485f3e9eda21b654420f870e37426a48f91fc Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 16 Jul 2025 12:09:51 -0400 Subject: [PATCH 140/313] hsa: http client send signal on transport reset we don't need to wait for timeout Type: improvement Change-Id: I7aef2581c1ac48d4b147914ccae8aa5bedb363ca Signed-off-by: Matus Fabian --- src/plugins/hs_apps/http_client.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 6ddf0b52fe..b6462beff8 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -361,6 +361,7 @@ hc_session_transport_closed_callback (session_t *s) hc_main_t *hcm = &hc_main; hc_worker_t *wrk = hc_worker_get (s->thread_index); + HTTP_DBG (1, "transport closed"); clib_spinlock_lock_if_init (&hcm->lock); if (s->session_state == SESSION_STATE_TRANSPORT_CLOSED) { @@ -390,8 +391,12 @@ hc_session_reset_callback (session_t *s) hc_main_t *hcm = &hc_main; hc_session_t *hc_session; vnet_disconnect_args_t _a = { 0 }, *a = &_a; + hc_worker_t *wrk = hc_worker_get (s->thread_index); int rv; + HTTP_DBG (1, "transport reset"); + vlib_process_signal_event_mt (wrk->vlib_main, hcm->cli_node_index, + HC_TRANSPORT_CLOSED, 0); hc_session = hc_session_get (s->opaque, s->thread_index); hc_session->session_flags |= HC_S_FLAG_IS_CLOSED; From e7a26fb11b25cd72055f33a64f06ae01c853d69b Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 17 Jul 2025 02:22:14 -0400 Subject: [PATCH 141/313] session: remove unused attach options - ACCEPT_COOKIE - PRIVATE_SEGMENT_COUNT They are not handled by session layer even if configured by applications. Type: improvement Change-Id: I673f8da6e83181be018489b85082dfd1b1dab87b Signed-off-by: Florin Coras --- src/plugins/hs_apps/echo_client.c | 2 -- src/plugins/hs_apps/echo_server.c | 1 - src/plugins/hs_apps/proxy.c | 3 --- src/vnet/session/application_interface.h | 4 ++-- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 4bc464b7b8..2612e2a389 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -1000,12 +1000,10 @@ ec_attach () prealloc_fifos = ecm->prealloc_fifos ? ecm->expected_connections : 1; - options[APP_OPTIONS_ACCEPT_COOKIE] = 0x12345678; options[APP_OPTIONS_SEGMENT_SIZE] = ecm->private_segment_size; options[APP_OPTIONS_ADD_SEGMENT_SIZE] = ecm->private_segment_size; options[APP_OPTIONS_RX_FIFO_SIZE] = ecm->fifo_size; options[APP_OPTIONS_TX_FIFO_SIZE] = ecm->fifo_size; - options[APP_OPTIONS_PRIVATE_SEGMENT_COUNT] = ecm->private_segment_count; options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = prealloc_fifos; options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; options[APP_OPTIONS_TLS_ENGINE] = ecm->tls_engine; diff --git a/src/plugins/hs_apps/echo_server.c b/src/plugins/hs_apps/echo_server.c index 61b8676976..0585132991 100644 --- a/src/plugins/hs_apps/echo_server.c +++ b/src/plugins/hs_apps/echo_server.c @@ -512,7 +512,6 @@ echo_server_attach (u8 * appns_id, u64 appns_flags, u64 appns_secret) a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = esm->private_segment_size; a->options[APP_OPTIONS_RX_FIFO_SIZE] = esm->fifo_size; a->options[APP_OPTIONS_TX_FIFO_SIZE] = esm->fifo_size; - a->options[APP_OPTIONS_PRIVATE_SEGMENT_COUNT] = esm->private_segment_count; a->options[APP_OPTIONS_TLS_ENGINE] = esm->tls_engine; a->options[APP_OPTIONS_PCT_FIRST_ALLOC] = 100; a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index 2c7535dd82..c26f4698aa 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -1198,7 +1198,6 @@ proxy_server_attach () a->options[APP_OPTIONS_MAX_FIFO_SIZE] = pm->max_fifo_size; a->options[APP_OPTIONS_HIGH_WATERMARK] = (u64) pm->high_watermark; a->options[APP_OPTIONS_LOW_WATERMARK] = (u64) pm->low_watermark; - a->options[APP_OPTIONS_PRIVATE_SEGMENT_COUNT] = pm->private_segment_count; a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = pm->prealloc_fifos ? pm->prealloc_fifos : 0; @@ -1229,14 +1228,12 @@ active_open_attach (void) a->session_cb_vft = &active_open_clients; a->name = format (0, "proxy-active-open"); - options[APP_OPTIONS_ACCEPT_COOKIE] = 0x12345678; options[APP_OPTIONS_SEGMENT_SIZE] = 512 << 20; options[APP_OPTIONS_RX_FIFO_SIZE] = pm->fifo_size; options[APP_OPTIONS_TX_FIFO_SIZE] = pm->fifo_size; options[APP_OPTIONS_MAX_FIFO_SIZE] = pm->max_fifo_size; options[APP_OPTIONS_HIGH_WATERMARK] = (u64) pm->high_watermark; options[APP_OPTIONS_LOW_WATERMARK] = (u64) pm->low_watermark; - options[APP_OPTIONS_PRIVATE_SEGMENT_COUNT] = pm->private_segment_count; options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = pm->prealloc_fifos ? pm->prealloc_fifos : 0; diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index e9284b9806..342d9cfb68 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -181,7 +181,7 @@ typedef enum APP_OPTIONS_EVT_QUEUE_SIZE, APP_OPTIONS_SEGMENT_SIZE, APP_OPTIONS_ADD_SEGMENT_SIZE, - APP_OPTIONS_PRIVATE_SEGMENT_COUNT, + APP_OPTIONS_UNUSED1, APP_OPTIONS_RX_FIFO_SIZE, APP_OPTIONS_TX_FIFO_SIZE, APP_OPTIONS_PREALLOC_FIFO_PAIRS, @@ -189,7 +189,7 @@ typedef enum APP_OPTIONS_NAMESPACE, APP_OPTIONS_NAMESPACE_SECRET, APP_OPTIONS_PROXY_TRANSPORT, - APP_OPTIONS_ACCEPT_COOKIE, + APP_OPTIONS_UNUSED2, APP_OPTIONS_TLS_ENGINE, APP_OPTIONS_MAX_FIFO_SIZE, APP_OPTIONS_HIGH_WATERMARK, From 723564aab645c6154dfaf8b90541fc23d51f66bd Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 11 Jul 2025 17:53:35 -0400 Subject: [PATCH 142/313] tls session: track internal tls objects in ckpair Type: improvement Change-Id: Ia38d355facd7af407a26c386327e8ea1a9aec116 Signed-off-by: Florin Coras --- src/plugins/tlsopenssl/tls_openssl.c | 141 ++++++++++++++------------ src/plugins/tlsopenssl/tls_openssl.h | 2 - src/vnet/session/application_crypto.c | 17 ++++ src/vnet/session/application_crypto.h | 31 ++++++ src/vnet/tls/tls_record.h | 4 +- 5 files changed, 124 insertions(+), 71 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 020971d0b6..7305fe6afd 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -687,13 +687,58 @@ openssl_ctx_read (tls_ctx_t *ctx, session_t *ts) return openssl_ctx_read_dtls (ctx, ts); } +static void +openssl_cleanup_certkey_int_ctx (app_certkey_int_ctx_t *cki) +{ + X509_free (cki->cert); + EVP_PKEY_free (cki->key); +} + +static app_certkey_int_ctx_t * +openssl_init_certkey_init_ctx (app_cert_key_pair_t *ckpair, + clib_thread_index_t thread_index) +{ + app_certkey_int_ctx_t *cki = 0; + EVP_PKEY *pkey; + X509 *cert; + BIO *bio; + + cki = app_certkey_alloc_int_ctx (ckpair, thread_index); + bio = BIO_new (BIO_s_mem ()); + BIO_write (bio, ckpair->cert, vec_len (ckpair->cert)); + cert = PEM_read_bio_X509 (bio, NULL, NULL, NULL); + if (!cert) + { + clib_warning ("unable to parse certificate"); + BIO_free (bio); + return 0; + } + cki->cert = cert; + BIO_free (bio); + + bio = BIO_new (BIO_s_mem ()); + BIO_write (bio, ckpair->key, vec_len (ckpair->key)); + pkey = PEM_read_bio_PrivateKey (bio, NULL, NULL, NULL); + if (!pkey) + { + clib_warning ("unable to parse pkey"); + BIO_free (bio); + return 0; + } + cki->key = pkey; + BIO_free (bio); + + cki->cleanup_cb = openssl_cleanup_certkey_int_ctx; + + return cki; +} + static int -openssl_set_ckpair (SSL *ssl, u32 ckpair_index) +openssl_set_ckpair (SSL *ssl, u32 ckpair_index, + clib_thread_index_t thread_index) { app_cert_key_pair_t *ckpair; - BIO *cert_bio; - EVP_PKEY *pkey; - X509 *srvcert; + app_certkey_int_ctx_t *cki; /* Configure a ckpair index only if non-default/test provided */ if (ckpair_index == 0) @@ -708,32 +753,21 @@ openssl_set_ckpair (SSL *ssl, u32 ckpair_index) TLS_DBG (1, "tls cert and/or key not configured"); return -1; } - /* - * Set the key and cert - */ - cert_bio = BIO_new (BIO_s_mem ()); - BIO_write (cert_bio, ckpair->cert, vec_len (ckpair->cert)); - srvcert = PEM_read_bio_X509 (cert_bio, NULL, NULL, NULL); - if (!srvcert) - { - clib_warning ("unable to parse certificate"); - return -1; - } - SSL_use_certificate (ssl, srvcert); - BIO_free (cert_bio); - X509_free (srvcert); - cert_bio = BIO_new (BIO_s_mem ()); - BIO_write (cert_bio, ckpair->key, vec_len (ckpair->key)); - pkey = PEM_read_bio_PrivateKey (cert_bio, NULL, NULL, NULL); - if (!pkey) + cki = app_certkey_get_int_ctx (ckpair, thread_index); + if (!cki || !cki->cert) { - clib_warning ("unable to parse pkey"); - return -1; + cki = openssl_init_certkey_init_ctx (ckpair, thread_index); + if (!cki) + { + clib_warning ("unable to initialize certificate/key pair"); + return -1; + } } - SSL_use_PrivateKey (ssl, pkey); - BIO_free (cert_bio); - EVP_PKEY_free (pkey); + + SSL_use_certificate (ssl, cki->cert); + SSL_use_PrivateKey (ssl, cki->key); + TLS_DBG (1, "TLS client using ckpair index: %d", ckpair_index); return 0; } @@ -858,7 +892,7 @@ openssl_ctx_init_client (tls_ctx_t * ctx) TLS_DBG (1, "ERROR:verify init failed:%d", rv); return -1; } - if (openssl_set_ckpair (oc->ssl, ctx->ckpair_index)) + if (openssl_set_ckpair (oc->ssl, ctx->ckpair_index, ctx->c_thread_index)) { TLS_DBG (1, "Couldn't set client certificate-key pair"); } @@ -922,12 +956,10 @@ openssl_start_listen (tls_ctx_t * lctx) const SSL_METHOD *method; SSL_CTX *ssl_ctx; int rv; - BIO *cert_bio; - X509 *srvcert; - EVP_PKEY *pkey; u32 olc_index; openssl_listen_ctx_t *olc; app_cert_key_pair_t *ckpair; + app_certkey_int_ctx_t *cki; long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; openssl_main_t *om = &openssl_main; @@ -1014,52 +1046,31 @@ openssl_start_listen (tls_ctx_t * lctx) /* * Set the key and cert */ - cert_bio = BIO_new (BIO_s_mem ()); - if (!cert_bio) + cki = app_certkey_get_int_ctx (ckpair, lctx->c_thread_index); + if (!cki || !cki->cert) { - clib_warning ("unable to allocate memory"); - return -1; - } - BIO_write (cert_bio, ckpair->cert, vec_len (ckpair->cert)); - srvcert = PEM_read_bio_X509 (cert_bio, NULL, NULL, NULL); - if (!srvcert) - { - clib_warning ("unable to parse certificate"); - goto err; + cki = openssl_init_certkey_init_ctx (ckpair, lctx->c_thread_index); + if (!cki) + { + clib_warning ("unable to initialize certificate/key pair"); + return -1; + } } - rv = SSL_CTX_use_certificate (ssl_ctx, srvcert); + + rv = SSL_CTX_use_certificate (ssl_ctx, cki->cert); if (rv != 1) { clib_warning ("unable to use SSL certificate"); goto err; } - BIO_free (cert_bio); - X509_free (srvcert); - - cert_bio = BIO_new (BIO_s_mem ()); - if (!cert_bio) - { - clib_warning ("unable to allocate memory"); - return -1; - } - BIO_write (cert_bio, ckpair->key, vec_len (ckpair->key)); - pkey = PEM_read_bio_PrivateKey (cert_bio, NULL, NULL, NULL); - if (!pkey) - { - clib_warning ("unable to parse pkey"); - goto err; - } - rv = SSL_CTX_use_PrivateKey (ssl_ctx, pkey); + rv = SSL_CTX_use_PrivateKey (ssl_ctx, cki->key); if (rv != 1) { clib_warning ("unable to use SSL PrivateKey"); goto err; } - BIO_free (cert_bio); - EVP_PKEY_free (pkey); - if (lctx->alpn_list) SSL_CTX_set_alpn_select_cb (ssl_ctx, openssl_alpn_select_cb, (void *) lctx->alpn_list); @@ -1067,8 +1078,6 @@ openssl_start_listen (tls_ctx_t * lctx) olc_index = openssl_listen_ctx_alloc (); olc = openssl_lctx_get (olc_index); olc->ssl_ctx = ssl_ctx; - olc->srvcert = srvcert; - olc->pkey = pkey; /* store SSL_CTX into TLS level structure */ lctx->tls_ssl_ctx = olc_index; @@ -1076,8 +1085,6 @@ openssl_start_listen (tls_ctx_t * lctx) return 0; err: - if (cert_bio) - BIO_free (cert_bio); return -1; } diff --git a/src/plugins/tlsopenssl/tls_openssl.h b/src/plugins/tlsopenssl/tls_openssl.h index d52524161a..f18b579bdb 100644 --- a/src/plugins/tlsopenssl/tls_openssl.h +++ b/src/plugins/tlsopenssl/tls_openssl.h @@ -66,8 +66,6 @@ typedef struct tls_listen_ctx_opensl_ u32 openssl_lctx_index; SSL_CTX *ssl_ctx; SSL *ssl; - X509 *srvcert; - EVP_PKEY *pkey; } openssl_listen_ctx_t; typedef struct openssl_main_ diff --git a/src/vnet/session/application_crypto.c b/src/vnet/session/application_crypto.c index a48ee07b4c..b15799c89e 100644 --- a/src/vnet/session/application_crypto.c +++ b/src/vnet/session/application_crypto.c @@ -68,6 +68,21 @@ vnet_app_add_cert_key_interest (u32 index, u32 app_index) return 0; } +static void +app_certkey_free_int_ctx (app_cert_key_pair_t *ck) +{ + app_certkey_int_ctx_t *cki; + + vec_foreach (cki, ck->cki) + { + if (cki->cleanup_cb) + (cki->cleanup_cb) (cki); + cki->cert = 0; + cki->key = 0; + } + vec_free (ck->cki); +} + int vnet_app_del_cert_key_pair (u32 index) { @@ -78,6 +93,8 @@ vnet_app_del_cert_key_pair (u32 index) if (!(ckpair = app_cert_key_pair_get_if_valid (index))) return SESSION_E_INVALID; + app_certkey_free_int_ctx (ckpair); + vec_foreach (app_index, ckpair->app_interests) { if ((app = application_get_if_valid (*app_index)) && diff --git a/src/vnet/session/application_crypto.h b/src/vnet/session/application_crypto.h index 20e385e4ae..f0587eaf38 100644 --- a/src/vnet/session/application_crypto.h +++ b/src/vnet/session/application_crypto.h @@ -7,12 +7,25 @@ #include +struct app_certkey_int_; + +typedef void (*app_certkey_cleanup_it_ctx_fn) (struct app_certkey_int_ *cki); + +typedef struct app_certkey_int_ +{ + void *cert; /**< cert of cert chain, possible format X509 */ + void *key; /**< key, possible format EVP_PKEY */ + u32 ckpair_index; /**< parent certkey */ + app_certkey_cleanup_it_ctx_fn cleanup_cb; /**< cleanup callback */ +} app_certkey_int_ctx_t; + typedef struct certificate_ { u32 *app_interests; /* vec of application index asking for deletion cb */ u32 cert_key_index; /* index in cert & key pool */ u8 *key; u8 *cert; + app_certkey_int_ctx_t *cki; /**< per-thread internal cert/key */ } app_cert_key_pair_t; typedef enum crypto_engine_type_ @@ -55,6 +68,24 @@ int vnet_app_add_cert_key_pair (vnet_app_add_cert_key_pair_args_t *a); int vnet_app_add_cert_key_interest (u32 index, u32 app_index); int vnet_app_del_cert_key_pair (u32 index); +static inline app_certkey_int_ctx_t * +app_certkey_get_int_ctx (app_cert_key_pair_t *ck, + clib_thread_index_t thread_index) +{ + if (vec_len (ck->cki) <= thread_index) + return 0; + return vec_elt_at_index (ck->cki, thread_index); +} + +static inline app_certkey_int_ctx_t * +app_certkey_alloc_int_ctx (app_cert_key_pair_t *ck, + clib_thread_index_t thread_index) +{ + if (!ck->cki) + vec_validate (ck->cki, vlib_num_workers () + 1); + return vec_elt_at_index (ck->cki, thread_index); +} + /* * Crypto engine management */ diff --git a/src/vnet/tls/tls_record.h b/src/vnet/tls/tls_record.h index 3f7723f06d..8ad0cba6d8 100644 --- a/src/vnet/tls/tls_record.h +++ b/src/vnet/tls/tls_record.h @@ -100,7 +100,7 @@ tls_handshake_message_len (tls_handshake_msg_t *msg) /** * https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ -#define foreach_tls_hanshake_extensions \ +#define foreach_tls_handshake_extensions \ _ (SERVER_NAME, 0) \ _ (MAX_FRAGMENT_LENGTH, 1) \ _ (STATUS_REQUEST, 5) \ @@ -133,7 +133,7 @@ tls_handshake_message_len (tls_handshake_msg_t *msg) typedef enum tls_handshake_extension_type_ { #define _(sym, val) TLS_EXT_##sym = val, - foreach_tls_hanshake_extensions + foreach_tls_handshake_extensions #undef _ } tls_handshake_ext_type_t; From 8704709d321c0d8e01723e152668deb67fd3490c Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Thu, 17 Jul 2025 16:00:49 -0400 Subject: [PATCH 143/313] build: update openssl to 3.5.1 in vpp-opt-deps Type: improvement Change-Id: Ibe396e088c6d9620bbd2fdf85629de8c139ef650 Signed-off-by: Dave Wallace --- build/optional/packages/openssl.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/optional/packages/openssl.mk b/build/optional/packages/openssl.mk index 1288ab12f6..26a696c707 100644 --- a/build/optional/packages/openssl.mk +++ b/build/optional/packages/openssl.mk @@ -11,9 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -openssl_version := 3.5.0 +openssl_version := 3.5.1 openssl_tarball := openssl-$(openssl_version).tar.gz -openssl_tarball_sha256sum := 344d0a79f1a9b08029b0744e2cc401a43f9c90acd1044d09a530b4885a8e9fc0 +openssl_tarball_sha256sum := 529043b15cffa5f36077a4d0af83f3de399807181d607441d734196d889b641f openssl_tarball_strip_dirs := 1 openssl_url := https://github.com/openssl/openssl/releases/download/openssl-$(openssl_version)/$(openssl_tarball) From 6abc5dc83bfbe2529d31b857c29619ebae0a63eb Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 17 Jul 2025 08:57:09 -0400 Subject: [PATCH 144/313] http: h2 client error handling improvement Type: improvement Change-Id: If989e6f1f2e23abae0214f467e8a285256ab1d17 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 54 ++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index f10113e80e..89479f3c73 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -396,9 +396,13 @@ http2_connection_error (http_conn_t *hc, http2_error_t error, u32 req_index, stream_id; http2_conn_ctx_t *h2c; http2_req_t *req; + app_worker_t *app_wrk; h2c = http2_conn_ctx_get_w_thread (hc); + HTTP_DBG (1, "hc [%u]%x connection error %U (last streamId %u)", + hc->c_thread_index, hc->hc_hc_index, format_http2_error, error, + h2c->last_processed_stream_id); response = http_get_tx_buf (hc); http2_frame_write_goaway (error, h2c->last_processed_stream_id, &response); http_io_ts_write (hc, response, vec_len (response), sp); @@ -412,8 +416,22 @@ http2_connection_error (http_conn_t *hc, http2_error_t error, })); else { - req = http2_req_get (h2c->client_req_index, hc->c_thread_index); - session_transport_reset_notify (&req->base.connection); + if (h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS) + { + HTTP_DBG (1, "error before server preface received"); + app_wrk = app_worker_get_if_valid (hc->hc_pa_wrk_index); + if (app_wrk) + app_worker_connect_notify (app_wrk, 0, SESSION_E_UNKNOWN, + hc->hc_pa_app_api_ctx); + } + else + { + if (hc->flags & HTTP_CONN_F_HAS_REQUEST) + { + req = http2_req_get (h2c->client_req_index, hc->c_thread_index); + session_transport_reset_notify (&req->base.connection); + } + } } if (clib_llist_elt_is_linked (h2c, sched_list)) clib_llist_remove (wrk->conn_pool, sched_list, h2c); @@ -426,6 +444,8 @@ http2_send_stream_error (http_conn_t *hc, u32 stream_id, http2_error_t error, { u8 *response; + HTTP_DBG (1, "hc [%u]%x streamId %u error %U", hc->c_thread_index, + hc->hc_hc_index, stream_id, format_http2_error, error); response = http_get_tx_buf (hc); http2_frame_write_rst_stream (error, stream_id, &response); http_io_ts_write (hc, response, vec_len (response), sp); @@ -1736,13 +1756,6 @@ http2_req_state_wait_app_method (http_conn_t *hc, http2_req_t *req, h2c = http2_conn_ctx_get_w_thread (hc); - if (!(hc->flags & HTTP_CONN_F_HAS_REQUEST)) - { - hc->flags |= HTTP_CONN_F_HAS_REQUEST; - hpack_dynamic_table_init (&h2c->decoder_dynamic_table, - http2_default_conn_settings.header_table_size); - } - /* add response to stream scheduler */ HTTP_DBG (1, "adding to headers queue req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); @@ -2047,16 +2060,17 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) http2_error_t rv; http2_conn_ctx_t *h2c; + if (fh->stream_id == 0) + { + HTTP_DBG (1, "DATA frame with stream id 0"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + req = http2_conn_get_req (hc, fh->stream_id); h2c = http2_conn_ctx_get_w_thread (hc); if (!req) { - if (fh->stream_id == 0) - { - HTTP_DBG (1, "DATA frame with stream id 0"); - return HTTP2_ERROR_PROTOCOL_ERROR; - } if (fh->stream_id <= h2c->last_opened_stream_id) { HTTP_DBG (1, "stream closed, ignoring frame"); @@ -2253,10 +2267,14 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) if (h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS) { h2c->flags &= ~HTTP2_CONN_F_EXPECT_SERVER_SETTINGS; - /* client connection is now established */ + HTTP_DBG (1, "client connection established"); req = http2_conn_alloc_req (hc); h2c->client_req_index = ((http_req_handle_t) req->base.hr_req_handle).req_index; + hc->flags |= HTTP_CONN_F_HAS_REQUEST; + hpack_dynamic_table_init ( + &h2c->decoder_dynamic_table, + http2_default_conn_settings.header_table_size); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); if (http_conn_established (hc, &req->base)) return HTTP2_ERROR_INTERNAL_ERROR; @@ -2366,6 +2384,7 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) /* graceful shutdown (no new streams for client) */ if (!(hc->flags & HTTP_CONN_F_IS_SERVER)) { + ASSERT (hc->flags & HTTP_CONN_F_HAS_REQUEST); req = http2_req_get (h2c->client_req_index, hc->c_thread_index); if (!req) return HTTP2_ERROR_NO_ERROR; @@ -2941,7 +2960,10 @@ http2_conn_cleanup_callback (http_conn_t *hc) hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ vec_add1 (req_indices, req_index); })); else - vec_add1 (req_indices, h2c->client_req_index); + { + if (hc->flags & HTTP_CONN_F_HAS_REQUEST) + vec_add1 (req_indices, h2c->client_req_index); + } vec_foreach (req_index_p, req_indices) { From 0d6c4beaf6db76ce05f261bc83ebac9235219581 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 17 Jul 2025 08:58:08 -0400 Subject: [PATCH 145/313] hs-test: h2 client testing with h2spec Type: test Change-Id: If588fa070adebb631bf71fc8c3e82a00ba628a69 Signed-off-by: Matus Fabian --- extras/hs-test/infra/hst_suite.go | 60 ++++++++++ extras/hs-test/infra/suite_http2.go | 176 +++++++++++++++++++++++++++- 2 files changed, 232 insertions(+), 4 deletions(-) diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index b610b66b61..d7d9eb89f7 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -93,6 +93,65 @@ var reservedPorts = []string{ "6633", "6081", "53053", + // reserved for h2specd + "30000", + "30001", + "30002", + "30003", + "30004", + "30005", + "30006", + "30007", + "30008", + "30009", + "30010", + "30011", + "30012", + "30013", + "30014", + "30015", + "30016", + "30017", + "30018", + "30019", + "30020", + "30021", + "30022", + "30023", + "30024", + "30025", + "30026", + "30027", + "30028", + "30029", + "30030", + "30031", + "30032", + "30033", + "30034", + "30035", + "30036", + "30037", + "30038", + "30039", + "30040", + "30041", + "30042", + "30043", + "30044", + "30045", + "30046", + "30047", + "30048", + "30049", + "30050", + "30051", + "30052", + "30053", + "30054", + "30055", + "30056", + "30080", } // used for colorful ReportEntry @@ -610,6 +669,7 @@ func (s *HstSuite) GeneratePort() string { } reservedPorts = append(reservedPorts, port) s.numOfNewPorts++ + s.Log("generated port " + port) return port } diff --git a/extras/hs-test/infra/suite_http2.go b/extras/hs-test/infra/suite_http2.go index d67399b577..b26ffd9e97 100644 --- a/extras/hs-test/infra/suite_http2.go +++ b/extras/hs-test/infra/suite_http2.go @@ -2,7 +2,9 @@ package hst import ( "bytes" + "fmt" "io" + "net/http" "os" "reflect" "runtime" @@ -13,6 +15,7 @@ import ( "fd.io/hs-test/h2spec_extras" . "fd.io/hs-test/infra/common" . "github.com/onsi/ginkgo/v2" + "github.com/summerwind/h2spec" "github.com/summerwind/h2spec/config" "github.com/summerwind/h2spec/generic" "github.com/summerwind/h2spec/hpack" @@ -24,6 +27,11 @@ var h2Tests = map[string][]func(s *Http2Suite){} var h2SoloTests = map[string][]func(s *Http2Suite){} var h2MWTests = map[string][]func(s *Http2Suite){} +const ( + h2specdFromPort int = 30000 + h2specdReportPort int = 30080 +) + type Http2Suite struct { HstSuite Interfaces struct { @@ -36,9 +44,10 @@ type Http2Suite struct { NginxServer *Container } Ports struct { - Port1 string - Port1AsInt int - Port2 string + Port1 string + Port1AsInt int + Port2 string + H2specdAsInt int } } @@ -396,7 +405,6 @@ var specs = []struct { {ExtrasTestGroup, extrasTests}, } -// Marked as pending since http plugin is not build with http/2 enabled by default var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { var s Http2Suite BeforeAll(func() { @@ -472,3 +480,163 @@ var _ = Describe("H2SpecSuite", Ordered, ContinueOnFailure, func() { } } }) + +func h2specdVerifyResult(s Http2Suite, nExecuted int) bool { + client := NewHttpClient(time.Second*5, false) + uri := fmt.Sprintf("http://%s:%d/report", s.HostAddr(), h2specdReportPort) + req, err := http.NewRequest("GET", uri, nil) + s.AssertNil(err, fmt.Sprint(err)) + resp, err := client.Do(req) + s.AssertNil(err, fmt.Sprint(err)) + defer resp.Body.Close() + report, err := io.ReadAll(resp.Body) + s.AssertContains(string(report), "0 failed") + expected := fmt.Sprintf("
%d tests, %d passed", nExecuted, nExecuted) + if !strings.Contains(string(report), expected) { + return false + } + return true +} + +var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() { + var s Http2Suite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + testCases := []struct { + desc string + portOffset int + }{ + {desc: "client/1/1", portOffset: 0}, + {desc: "client/4.1/1", portOffset: 1}, + {desc: "client/4.1/2", portOffset: 2}, + {desc: "client/4.1/3", portOffset: 3}, + // TODO: message framing without content length using END_STREAM flag + //{desc: "client/4.2/1", portOffset: 4}, + //{desc: "client/4.2/2", portOffset: 5}, + {desc: "client/4.2/3", portOffset: 6}, + {desc: "client/4.3/1", portOffset: 7}, + {desc: "client/5.1/1", portOffset: 8}, + {desc: "client/5.1/2", portOffset: 9}, + {desc: "client/5.1/3", portOffset: 10}, + {desc: "client/5.1/4", portOffset: 11}, + // TODO: message framing without content length using END_STREAM flag + //{desc: "client/5.1/5", portOffset: 12}, + //{desc: "client/5.1/6", portOffset: 13}, + //{desc: "client/5.1/7", portOffset: 14}, + {desc: "client/5.1/8", portOffset: 15}, + {desc: "client/5.1/9", portOffset: 16}, + {desc: "client/5.1/10", portOffset: 17}, + {desc: "client/5.1.1/1", portOffset: 18}, + {desc: "client/5.4.1/1", portOffset: 19}, + {desc: "client/5.4.1/2", portOffset: 20}, + {desc: "client/5.5/1", portOffset: 21}, + {desc: "client/6.1/1", portOffset: 22}, + {desc: "client/6.1/2", portOffset: 23}, + {desc: "client/6.1/3", portOffset: 24}, + {desc: "client/6.2/1", portOffset: 25}, + {desc: "client/6.2/2", portOffset: 26}, + {desc: "client/6.2/3", portOffset: 27}, + // PRIORITY is deprecated + //{desc: "client/6.3/1", portOffset: 28}, + //{desc: "client/6.3/2", portOffset: 29}, + {desc: "client/6.4/1", portOffset: 30}, + {desc: "client/6.4/2", portOffset: 31}, + {desc: "client/6.4/3", portOffset: 32}, + {desc: "client/6.5/1", portOffset: 33}, + {desc: "client/6.5/2", portOffset: 34}, + {desc: "client/6.5/3", portOffset: 35}, + {desc: "client/6.5.2/1", portOffset: 36}, + {desc: "client/6.5.2/2", portOffset: 37}, + {desc: "client/6.5.2/3", portOffset: 38}, + {desc: "client/6.5.2/4", portOffset: 39}, + {desc: "client/6.5.3/1", portOffset: 40}, + {desc: "client/6.7/1", portOffset: 41}, + {desc: "client/6.7/2", portOffset: 42}, + {desc: "client/6.7/3", portOffset: 43}, + {desc: "client/6.7/4", portOffset: 44}, + {desc: "client/6.8/1", portOffset: 45}, + {desc: "client/6.9/1", portOffset: 46}, + // TODO: message framing without content length using END_STREAM flag + //{desc: "client/6.9/2", portOffset: 47}, + {desc: "client/6.9/3", portOffset: 48}, + {desc: "client/6.9.1/1", portOffset: 49}, + // TODO: message framing without content length using END_STREAM flag + //{desc: "client/6.9.1/2", portOffset: 50}, + {desc: "client/6.10/1", portOffset: 51}, + {desc: "client/6.10/2", portOffset: 52}, + {desc: "client/6.10/3", portOffset: 53}, + {desc: "client/6.10/4", portOffset: 54}, + {desc: "client/6.10/5", portOffset: 55}, + {desc: "client/6.10/6", portOffset: 56}, + } + + nExecuted := 0 + for _, test := range testCases { + test := test + testName := "http2_test.go/h2spec_" + strings.ReplaceAll(test.desc, "/", "_") + It(testName, func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + nExecuted++ + serverAddress := s.HostAddr() + wd, _ := os.Getwd() + conf := &config.Config{ + Host: serverAddress, + Port: h2specdReportPort, + Timeout: 20 * time.Second, + MaxHeaderLen: 4096, + TLS: true, + CertFile: wd + "/resources/cert/localhost.crt", + CertKeyFile: wd + "/resources/cert/localhost.key", + Verbose: true, + DryRun: false, + Exec: "", + FromPort: h2specdFromPort, + Sections: []string{}, + } + //capture h2spec output so it will be in log + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + go h2spec.RunClientSpec(conf) + + cmd := fmt.Sprintf("http client timeout 5 verbose uri https://%s:%d/", serverAddress, h2specdFromPort+test.portOffset) + res := s.Containers.Vpp.VppInstance.Vppctl(cmd) + s.Log(res) + s.AssertNotContains(res, "error: timeout") + + oChan := make(chan string) + go func() { + var buf bytes.Buffer + io.Copy(&buf, r) + oChan <- buf.String() + }() + + //restore to normal state + w.Close() + os.Stdout = oldStdout + o := <-oChan + s.Log(o) + + //read report + for nTries := 0; nTries < 30; nTries++ { + if h2specdVerifyResult(s, nExecuted) { + break + } + time.Sleep(1 * time.Second) + } + + }, SpecTimeout(TestTimeout)) + } +}) From 114563a4645c57b215ca8f7f66409d145a605c7d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 17 Jul 2025 09:42:54 -0400 Subject: [PATCH 146/313] http: h2 coverity fix Type: fix Change-Id: Ib5ac44ae95a7e0c62830b8a93803312cbc875f88 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 89479f3c73..77a9c661a2 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1580,10 +1580,7 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req, else { /* we are done wait for the next app request */ - http_req_state_change (&req->base, - hc->flags & HTTP_CONN_F_IS_SERVER ? - HTTP_REQ_STATE_WAIT_APP_REPLY : - HTTP_REQ_STATE_WAIT_APP_METHOD); + http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); transport_connection_reschedule (&req->base.connection); h2c = http2_conn_ctx_get_w_thread (hc); http2_conn_reset_req (h2c, req, hc->c_thread_index); From 2839c1e9512074b3f5b2c5c5a760d518e3cee769 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 18 Jul 2025 05:43:10 -0400 Subject: [PATCH 147/313] hsa: http client set connect_sep to null on init Set connect_sep in http (cli) client to SESSION_ENDPOINT_CFG_NULL on init to be sure that parrent_handle is invalid if not used. Type: improvement Change-Id: Ia42cbe8ea90402d20b07f933b5308642a098235f Signed-off-by: Matus Fabian --- src/plugins/hs_apps/http_client.c | 3 +++ src/plugins/hs_apps/http_client_cli.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index b6462beff8..97dbca788e 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -1182,7 +1182,10 @@ static clib_error_t * hc_main_init () { hc_main_t *hcm = &hc_main; + session_endpoint_cfg_t sep_null = SESSION_ENDPOINT_CFG_NULL; + hcm->app_index = APP_INVALID_INDEX; + hcm->connect_sep = sep_null; return 0; } diff --git a/src/plugins/hs_apps/http_client_cli.c b/src/plugins/hs_apps/http_client_cli.c index f6247ea7c5..3980cd9acc 100644 --- a/src/plugins/hs_apps/http_client_cli.c +++ b/src/plugins/hs_apps/http_client_cli.c @@ -654,9 +654,11 @@ static clib_error_t * hcc_main_init (vlib_main_t *vm) { hcc_main_t *hcm = &hcc_main; + session_endpoint_cfg_t sep_null = SESSION_ENDPOINT_CFG_NULL; hcm->app_index = ~0; hcm->vlib_main = vm; + hcm->connect_sep = sep_null; return 0; } From d51170ddc4dc50eec339c3ba345fc10054abd18b Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 18 Jul 2025 14:40:01 -0400 Subject: [PATCH 148/313] session: add new error MAX_STREAMS_HIT Type: improvement Change-Id: I2e5a153a30b566d7d20d8ceeec26b80075dfec80 Signed-off-by: Matus Fabian --- src/vnet/session/session_types.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vnet/session/session_types.h b/src/vnet/session/session_types.h index 47a77449ba..dda76c4b5e 100644 --- a/src/vnet/session/session_types.h +++ b/src/vnet/session/session_types.h @@ -521,7 +521,8 @@ STATIC_ASSERT (sizeof (session_dgram_hdr_t) == (SESSION_CONN_ID_LEN + 10), _ (LOCAL_CONNECT, "could not connect with local scope") \ _ (WRONG_NS_SECRET, "wrong ns secret") \ _ (SYSCALL, "system call error") \ - _ (TRANSPORT_NO_REG, "transport was not registered") + _ (TRANSPORT_NO_REG, "transport was not registered") \ + _ (MAX_STREAMS_HIT, "max streams hit") typedef enum session_error_p_ { From 7a08d92004a9ec68297cd2d3eb9a66e2b58ae22b Mon Sep 17 00:00:00 2001 From: Alexander Maltsev Date: Sat, 19 Jul 2025 21:50:45 +0500 Subject: [PATCH 149/313] session: set maximum memory for fifos Add APP_OPTIONS_MAX_FIFO_MEMORY to set upper limit on how much memory could be used by fifos. It is rounded upwards to nearest segment size. If not specified, no limit is set. Type: feature Change-Id: I7fb87808973e761b7c40b734b18d55b8a8d8296e Signed-off-by: Alexander Maltsev --- src/vnet/session/application.c | 15 ++++++++++++--- src/vnet/session/application_interface.h | 2 +- src/vnet/session/segment_manager.c | 7 +++++++ src/vnet/session/segment_manager.h | 1 + 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index 1ba4604a97..2d1b58dbe6 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -855,6 +855,13 @@ application_alloc_and_init (app_init_args_t *a) props->low_watermark = opts[APP_OPTIONS_LOW_WATERMARK]; if (opts[APP_OPTIONS_PCT_FIRST_ALLOC]) props->pct_first_alloc = opts[APP_OPTIONS_PCT_FIRST_ALLOC]; + if (opts[APP_OPTIONS_MAX_FIFO_MEMORY]) + { + /* Round upwards to nearest segment_size */ + props->max_segments = + (opts[APP_OPTIONS_MAX_FIFO_MEMORY] + props->segment_size - 1) / + props->segment_size; + } props->segment_type = seg_type; if (opts[APP_OPTIONS_FLAGS] & APP_OPTIONS_FLAGS_EVT_COLLECTOR) @@ -1825,9 +1832,11 @@ format_application (u8 * s, va_list * args) s = format (s, "app-name %v app-index %u ns-index %u seg-size %U\n", app_name, app->app_index, app->ns_index, format_memory_size, props->add_segment_size); - s = format (s, "rx-fifo-size %U tx-fifo-size %U workers:\n", - format_memory_size, props->rx_fifo_size, - format_memory_size, props->tx_fifo_size); + s = + format (s, "rx-fifo-size %U tx-fifo-size %U max-fifo-memory %U workers:\n", + format_memory_size, props->rx_fifo_size, format_memory_size, + props->tx_fifo_size, format_memory_size, + props->max_segments * props->segment_size); pool_foreach (wrk_map, app->worker_maps) { app_wrk = app_worker_get (wrk_map->wrk_index); diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index 342d9cfb68..b1569f076f 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -181,7 +181,7 @@ typedef enum APP_OPTIONS_EVT_QUEUE_SIZE, APP_OPTIONS_SEGMENT_SIZE, APP_OPTIONS_ADD_SEGMENT_SIZE, - APP_OPTIONS_UNUSED1, + APP_OPTIONS_MAX_FIFO_MEMORY, APP_OPTIONS_RX_FIFO_SIZE, APP_OPTIONS_TX_FIFO_SIZE, APP_OPTIONS_PREALLOC_FIFO_PAIRS, diff --git a/src/vnet/session/segment_manager.c b/src/vnet/session/segment_manager.c index 341b70086d..f7f88cb140 100644 --- a/src/vnet/session/segment_manager.c +++ b/src/vnet/session/segment_manager.c @@ -115,6 +115,13 @@ segment_manager_add_segment_inline (segment_manager_t *sm, uword segment_size, if (need_lock) clib_rwlock_writer_lock (&sm->segments_rwlock); + if (props->max_segments && pool_elts (sm->segments) >= props->max_segments) + { + SESSION_DBG ( + "max number of segments allocated, can't allocate new segment"); + goto done; + } + pool_get_zero (sm->segments, fs); /* diff --git a/src/vnet/session/segment_manager.h b/src/vnet/session/segment_manager.h index 0fb957a091..7f73b7964b 100644 --- a/src/vnet/session/segment_manager.h +++ b/src/vnet/session/segment_manager.h @@ -41,6 +41,7 @@ typedef struct _segment_manager_props u8 low_watermark; /**< memory usage low watermark % */ u8 pct_first_alloc; /**< pct of fifo size to alloc */ u8 huge_page; /**< use hugepage */ + u32 max_segments; /**< max number of segments, 0 for unlimited */ } segment_manager_props_t; #define foreach_seg_manager_flag \ From 4b7cdbf2d5c7a1a9abed996354c98d36cd51f102 Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Thu, 5 Jun 2025 12:39:58 +0200 Subject: [PATCH 150/313] ip: common icmp echo structure Move the replicated ICMP echo structure from the NAT implementations to the common IP ICMP header. Type: improvement Change-Id: I3770d2609c2366859b014ae66096cd545f4b1262 Signed-off-by: Ole Troan --- src/plugins/nat/det44/det44.h | 6 ------ src/plugins/nat/nat44-ei/nat44_ei.h | 6 ------ src/plugins/nat/nat64/nat64.h | 6 ------ src/vnet/ip/icmp46_packet.h | 6 ++++++ 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/plugins/nat/det44/det44.h b/src/plugins/nat/det44/det44.h index 683f554f03..ae57631111 100644 --- a/src/plugins/nat/det44/det44.h +++ b/src/plugins/nat/det44/det44.h @@ -63,12 +63,6 @@ typedef enum #define DET44_SES_PER_USER 1000 -typedef struct -{ - u16 identifier; - u16 sequence; -} icmp_echo_header_t; - typedef struct { u16 src_port, dst_port; diff --git a/src/plugins/nat/nat44-ei/nat44_ei.h b/src/plugins/nat/nat44-ei/nat44_ei.h index 786fb0cfc2..d399fc8a59 100644 --- a/src/plugins/nat/nat44-ei/nat44_ei.h +++ b/src/plugins/nat/nat44-ei/nat44_ei.h @@ -83,12 +83,6 @@ typedef int (nat44_ei_alloc_out_addr_and_port_function_t) ( nat_protocol_t proto, ip4_address_t s_addr, ip4_address_t *addr, u16 *port, u16 port_per_thread, u32 snat_thread_index); -typedef struct -{ - u16 identifier; - u16 sequence; -} icmp_echo_header_t; - typedef struct { u16 src_port, dst_port; diff --git a/src/plugins/nat/nat64/nat64.h b/src/plugins/nat/nat64/nat64.h index 2577880c7a..1e144ca0d6 100644 --- a/src/plugins/nat/nat64/nat64.h +++ b/src/plugins/nat/nat64/nat64.h @@ -34,12 +34,6 @@ #include -typedef struct -{ - u16 identifier; - u16 sequence; -} icmp_echo_header_t; - typedef struct { u16 src_port, dst_port; diff --git a/src/vnet/ip/icmp46_packet.h b/src/vnet/ip/icmp46_packet.h index 08e73f6cd7..55b5b4e749 100644 --- a/src/vnet/ip/icmp46_packet.h +++ b/src/vnet/ip/icmp46_packet.h @@ -355,4 +355,10 @@ typedef CLIB_PACKED (struct link_layer_option; }) icmp6_neighbor_solicitation_header_t; +typedef struct +{ + u16 identifier; + u16 sequence; +} icmp_echo_header_t; + #endif /* included_vnet_icmp46_packet_h */ From f22e84b9b9ff70a45f8d5e47f6d516324b81f8c8 Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Thu, 17 Jul 2025 22:33:55 +0000 Subject: [PATCH 151/313] papi: fix deprecated call to setup.py Type: fix Change-Id: I0e5e61395b619e28cf75cde1770d631b47e8c0b7 Signed-off-by: Damjan Marion --- src/vpp-api/python/CMakeLists.txt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vpp-api/python/CMakeLists.txt b/src/vpp-api/python/CMakeLists.txt index 3059619ff2..7e6d7a63c2 100644 --- a/src/vpp-api/python/CMakeLists.txt +++ b/src/vpp-api/python/CMakeLists.txt @@ -19,13 +19,17 @@ install( CODE " execute_process( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMAND ${PYTHON_EXECUTABLE} ./setup.py - install + COMMAND ${PYTHON_EXECUTABLE} -m pip + install . + --disable-pip-version-check --root=\$ENV{DESTDIR}/ --prefix=${CMAKE_INSTALL_PREFIX} - --single-version-externally-managed - bdist_egg - OUTPUT_QUIET - )" + --no-deps + RESULT_VARIABLE _pip_result + ) + if(NOT _pip_result EQUAL 0) + message(FATAL_ERROR \"pip install failed with code: \${_pip_result}\") + endif() + " COMPONENT vpp-api-python ) From 6a038c3aa9abd78032a0abd5d297db62d23bea8c Mon Sep 17 00:00:00 2001 From: Jeff Shaw Date: Wed, 23 Jul 2025 11:13:54 -0700 Subject: [PATCH 152/313] dpdk: bump to DPDK 25.07 and rdma-core 58.0 Type: feature Change-Id: Ia9c378e144057128f123b2dc08c6f631c39f8e97 Signed-off-by: Jeff Shaw --- build/external/mlx_rdma_dpdk_matrix.txt | 1 + build/external/packages/dpdk.mk | 4 ++-- build/external/packages/rdma-core.mk | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/external/mlx_rdma_dpdk_matrix.txt b/build/external/mlx_rdma_dpdk_matrix.txt index 184a2bede1..6c193e1702 100644 --- a/build/external/mlx_rdma_dpdk_matrix.txt +++ b/build/external/mlx_rdma_dpdk_matrix.txt @@ -2,3 +2,4 @@ rdma=49.0 dpdk=23.11 rdma=51.0 dpdk=24.03 rdma=55.0 dpdk=24.07 rdma=55.0 dpdk=24.11.1 +rdma=58.0 dpdk=25.07 diff --git a/build/external/packages/dpdk.mk b/build/external/packages/dpdk.mk index da6802e480..9a908c1e1d 100644 --- a/build/external/packages/dpdk.mk +++ b/build/external/packages/dpdk.mk @@ -21,11 +21,11 @@ DPDK_MLX_IBV_LINK ?= static # On most of the systems, default value for max lcores is 128 DPDK_MAX_LCORES ?= -dpdk_version ?= 24.11.1 +dpdk_version ?= 25.07 dpdk_base_url ?= http://fast.dpdk.org/rel dpdk_tarball := dpdk-$(dpdk_version).tar.xz -dpdk_tarball_sha256sum_24.11.1 := bcae7d42c449fc456dfb279feabcbe0599a29bebb2fe2905761e187339d96b8e +dpdk_tarball_sha256sum_25.07 := 6886cbedc350bb8cbef347d10367d6259e36435627fbb27d578adbdc0d3b410d dpdk_tarball_sha256sum := $(dpdk_tarball_sha256sum_$(dpdk_version)) dpdk_url := $(dpdk_base_url)/$(dpdk_tarball) diff --git a/build/external/packages/rdma-core.mk b/build/external/packages/rdma-core.mk index 6107ec815b..1afe56dae7 100644 --- a/build/external/packages/rdma-core.mk +++ b/build/external/packages/rdma-core.mk @@ -23,9 +23,9 @@ RDMA_CORE_DEBUG?=n # 2. Verify that the file build/external/dpdk_mlx_default.sh was generated # and contains 'DPDK_MLX_DEFAULT=y' # -rdma-core_version := 55.0 +rdma-core_version := 58.0 rdma-core_tarball := rdma-core-$(rdma-core_version).tar.gz -rdma-core_tarball_sha256sum_55.0 := 6f8b97267807cdae54845f542ee3d75de80fdc24fe2632f5db1573ecef132d0f +rdma-core_tarball_sha256sum_58.0 := 88d67897b793f42d2004eec2629ab8464e425e058f22afabd29faac0a2f54ce4 rdma-core_tarball_sha256sum := $(rdma-core_tarball_sha256sum_$(rdma-core_version)) rdma-core_tarball_strip_dirs := 1 rdma-core_url := http://github.com/linux-rdma/rdma-core/releases/download/v$(rdma-core_version)/$(rdma-core_tarball) From 2a422e1d85218031a89d0bc9053b36ca5a64bbdc Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 24 Jul 2025 11:27:16 -0700 Subject: [PATCH 153/313] vcl: revert allow reads after transport cleanup Type: fix Fixes: 784410190eb96fa8dea9fa67a086aa0044fe9025 Change-Id: Iad0f083c4eceb9778f5dc8099c4a0da3fa230623 Signed-off-by: Florin Coras --- src/vcl/vppcom.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index ffa29bac34..9da68c11c0 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -2129,12 +2129,9 @@ vppcom_session_read_internal (uint32_t session_handle, void *buf, int n, VDBG (0, "session %u[0x%llx] is not open! state 0x%x (%s)", s->session_index, s->vpp_handle, s->session_state, vcl_session_state_str (s->session_state)); - rx_fifo = vcl_session_is_ct (s) ? s->ct_rx_fifo : s->rx_fifo; - /* If application closed, e.g., mt app, or no data return error */ - if (s->session_state == VCL_STATE_CLOSED || - (s->flags & VCL_SESSION_F_APP_CLOSING) || - svm_fifo_is_empty_cons (rx_fifo)) - return vcl_session_closed_error (s); + /* We can't be sure vpp did not unmap the segment, so if the + * session is detached just return an error */ + return vcl_session_closed_error (s); } if (PREDICT_FALSE (s->flags & VCL_SESSION_F_RD_SHUTDOWN)) From 23b3775c414f9186dc5880ebe35e5b7894efe846 Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Wed, 19 Mar 2025 11:46:26 -0700 Subject: [PATCH 154/313] l2: stale cached value input->bd_seq_num bd_seq_num is cached to l2_input_config to save a few cycles for lookup. However, when the value is changed, we forgot to update the cache. Type: fix Fixes: 47a3d9975fa3af7a7537b565d6511dadc0df61fb Change-Id: I553c61f4b072c74a6bc7fe1bf56d98ec881c833f Signed-off-by: Steven Luong --- src/vnet/l2/l2_fib.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/vnet/l2/l2_fib.c b/src/vnet/l2/l2_fib.c index 3dcd1e7ae2..33728bdc5e 100644 --- a/src/vnet/l2/l2_fib.c +++ b/src/vnet/l2/l2_fib.c @@ -893,14 +893,22 @@ l2fib_flush_int_mac (vlib_main_t * vm, u32 sw_if_index) l2fib_start_ager_scan (vm); } +static void +l2fib_bd_seq_num_inc (u32 bd_index) +{ + l2_bridge_domain_t *bd_config = l2input_bd_config (bd_index); + + bd_config->seq_num += 1; + bd_input_walk (bd_index, l2input_recache, NULL); +} + /** Flush all non static MACs in a bridge domain */ void l2fib_flush_bd_mac (vlib_main_t * vm, u32 bd_index) { - l2_bridge_domain_t *bd_config = l2input_bd_config (bd_index); - bd_config->seq_num += 1; + l2fib_bd_seq_num_inc (bd_index); l2fib_start_ager_scan (vm); } @@ -910,10 +918,16 @@ l2fib_flush_bd_mac (vlib_main_t * vm, u32 bd_index) void l2fib_flush_all_mac (vlib_main_t * vm) { + l2input_main_t *mp = &l2input_main; l2_bridge_domain_t *bd_config; - vec_foreach (bd_config, l2input_main.bd_configs) + u32 bd_index; + + vec_foreach_index (bd_index, mp->bd_configs) + { + bd_config = vec_elt_at_index (mp->bd_configs, bd_index); if (bd_is_valid (bd_config)) - bd_config->seq_num += 1; + l2fib_bd_seq_num_inc (bd_index); + } l2fib_start_ager_scan (vm); } From a23d3e2b260c270596ff9906d67e3ede11759976 Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Thu, 17 Jul 2025 17:11:03 -0700 Subject: [PATCH 155/313] docs: correct deleting an interface ip address The del keyword must be specified after ip address, not at the end as the example shows. DBGvpp# sh int addr sh int addr enp134s0f0np0/0 (up): L3 10.10.10.32/24 local0 (dn): DBGvpp# set interface ip address enp134s0f0np0/0 10.10.10.32/24 del set interface ip address enp134s0f0np0/0 10.10.10.32/24 del set interface ip address: failed to add 10.10.10.32/24 on enp134s0f0np0/0 which conflicts with 10.10.10.32/24 for interface enp134s0f0np0/0 DBGvpp# sh int addr sh int addr enp134s0f0np0/0 (up): L3 10.10.10.32/24 local0 (dn): DBGvpp# set interface ip address del enp134s0f0np0/0 10.10.10.32/24 set interface ip address del enp134s0f0np0/0 10.10.10.32/24 DBGvpp# sh int addr sh int addr enp134s0f0np0/0 (up): local0 (dn): DBGvpp# Type: fix Change-Id: Icd85cbbc5b149c67d411f837f0cd9b76beddc5b6 Signed-off-by: Steven Luong --- docs/cli-reference/interface/setinterface.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli-reference/interface/setinterface.rst b/docs/cli-reference/interface/setinterface.rst index 7eb14adf45..03bbf52b06 100644 --- a/docs/cli-reference/interface/setinterface.rst +++ b/docs/cli-reference/interface/setinterface.rst @@ -56,7 +56,7 @@ To delete a specific interface ip address: .. code:: console - vpp# set interface ip address GigabitEthernet2/0/0 172.16.2.12/24 del + vpp# set interface ip address del GigabitEthernet2/0/0 172.16.2.12/24 To delete all interfaces addresses (IPv4 and IPv6): From 1a131df02fd038ab88e77399379dedae0e63e2d2 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 21 Jul 2025 08:10:36 -0400 Subject: [PATCH 156/313] http: h2 flow control improvement resereve half of the app fifo size for headers to avoid full fifo when first data frame is received Type: improvement Change-Id: I366b9a5495b3b6303125260b340b600fd212be58 Signed-off-by: Matus Fabian --- extras/hs-test/h2spec_extras/h2spec_extras.go | 3 ++- src/plugins/http/http2/http2.c | 10 ++++++++-- src/plugins/http/http_private.h | 7 +++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/extras/hs-test/h2spec_extras/h2spec_extras.go b/extras/hs-test/h2spec_extras/h2spec_extras.go index a48cdac137..b2c5c85fb6 100644 --- a/extras/hs-test/h2spec_extras/h2spec_extras.go +++ b/extras/hs-test/h2spec_extras/h2spec_extras.go @@ -186,8 +186,9 @@ func FlowControl() *spec.TestGroup { } conn.WriteHeaders(hp) // we send window update on stream when app read data from rx fifo, so send DATA frame and wait for WINDOW_UPDATE frame + // first increment is bigger because half of the fifo size was reserved for headers conn.WriteData(streamID, false, []byte("AAAA")) - err = VerifyWindowUpdate(conn, streamID, 4) + err = VerifyWindowUpdate(conn, streamID, 4+conn.Settings[http2.SettingMaxHeaderListSize]) if err != nil { return err } diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 77a9c661a2..52147b8a1e 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -159,8 +159,11 @@ http2_conn_ctx_alloc_w_thread (http_conn_t *hc) h2c->our_window = HTTP2_CONNECTION_WINDOW_SIZE; h2c->settings = h2m->settings; /* adjust settings according to app rx_fifo size */ + h2c->settings.max_header_list_size = + clib_min (h2c->settings.max_header_list_size, (hc->app_rx_fifo_size >> 1)); h2c->settings.initial_window_size = - clib_min (h2c->settings.initial_window_size, hc->app_rx_fifo_size); + clib_min (h2c->settings.initial_window_size, + (hc->app_rx_fifo_size - h2c->settings.max_header_list_size)); h2c->req_by_stream_id = hash_create (0, sizeof (uword)); h2c->new_tx_streams = clib_llist_make_head (wrk->req_pool, sched_list); h2c->old_tx_streams = clib_llist_make_head (wrk->req_pool, sched_list); @@ -478,7 +481,10 @@ http2_stream_error (http_conn_t *hc, http2_req_t *req, http2_error_t error, if (req->flags & HTTP2_REQ_F_APP_CLOSED) session_transport_closed_notify (&req->base.connection); else - session_transport_closing_notify (&req->base.connection); + { + http_io_as_drain_unread (&req->base); + session_transport_closing_notify (&req->base.connection); + } h2c = http2_conn_ctx_get_w_thread (hc); session_transport_delete_notify (&req->base.connection); diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index 51fdfd65d3..b6a63b9711 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -651,6 +651,13 @@ http_io_as_drain_all (http_req_t *req) req->as_fifo_offset = 0; } +always_inline void +http_io_as_drain_unread (http_req_t *req) +{ + session_t *as = session_get_from_handle (req->hr_pa_session_handle); + svm_fifo_dequeue_drop_all (as->rx_fifo); +} + /* Abstraction of transport session fifo operations */ always_inline u32 From 4e94627dc3d7d8cdb382b54cb54fc3b0b97d4f18 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 18 Jul 2025 13:04:07 -0400 Subject: [PATCH 157/313] http: h2 client multiplexing Type: improvement Change-Id: I768df864cbda26b0901528789b52a33e788c2258 Signed-off-by: Matus Fabian --- extras/hs-test/http2_test.go | 66 +++++- extras/hs-test/infra/suite_http2.go | 31 +-- .../hs-test/resources/nginx/nginx_server.conf | 5 +- src/plugins/hs_apps/http_client.c | 217 +++++++++++++++--- src/plugins/http/http.c | 129 ++++++++++- src/plugins/http/http1.c | 2 +- src/plugins/http/http2/http2.c | 92 +++++--- src/plugins/http/http_private.h | 26 ++- 8 files changed, 484 insertions(+), 84 deletions(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 238d335be8..26605f2140 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -6,13 +6,15 @@ import ( "strings" "time" + "github.com/edwarnicke/exechelper" + . "fd.io/hs-test/infra" ) func init() { RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest, - Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest) - RegisterH2MWTests(Http2MultiplexingMWTest) + Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest, Http2ClientMultiplexingTest) + RegisterH2MWTests(Http2MultiplexingMWTest, Http2ClientMultiplexingMWTest) RegisterVethTests(Http2CliTlsTest, Http2ClientContinuationTest) } @@ -233,6 +235,66 @@ func Http2ClientGetRepeatTest(s *Http2Suite) { s.Log(o) } +func Http2ClientMultiplexingTest(s *Http2Suite) { + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.HostAddr() + ":" + s.Ports.Port2 + + s.CreateNginxServer() + s.AssertNil(s.Containers.NginxServer.Start()) + + uri := "https://" + serverAddress + "/httpTestFile" + cmd := fmt.Sprintf("http client http2 streams %d repeat %d uri %s", 10, 20, uri) + o := vpp.Vppctl(cmd) + s.Log(o) + s.AssertContains(o, "20 request(s)") + logPath := s.Containers.NginxServer.GetHostWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log" + logContents, err := exechelper.Output("cat " + logPath) + s.Log(string(logContents)) + s.AssertNil(err) + s.AssertContains(string(logContents), "conn_reqs=20") + + /* test session cleanup */ + httpStreamCleanupDone := false + tcpSessionCleanupDone := false + for nTries := 0; nTries < 30; nTries++ { + o := vpp.Vppctl("show session verbose") + if !strings.Contains(o, "[T]") { + 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 cleaned up") + s.AssertEqual(true, httpStreamCleanupDone, "HTTP/2 stream not cleaned up") +} + +func Http2ClientMultiplexingMWTest(s *Http2Suite) { + s.CpusPerVppContainer = 3 + s.SetupTest() + + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.HostAddr() + ":" + s.Ports.Port2 + + s.CreateNginxServer() + s.AssertNil(s.Containers.NginxServer.Start()) + + uri := "https://" + serverAddress + "/httpTestFile" + cmd := fmt.Sprintf("http client http2 sessions 2 streams %d repeat %d uri %s", 5, 20, uri) + o := vpp.Vppctl(cmd) + s.Log(o) + s.AssertContains(o, "20 request(s)") + logPath := s.Containers.NginxServer.GetHostWorkDir() + "/" + s.Containers.NginxServer.Name + "-access.log" + logContents, err := exechelper.Output("cat " + logPath) + s.Log(string(logContents)) + s.AssertNil(err) + s.AssertEqual(2, strings.Count(string(logContents), "conn_reqs=10")) +} + func Http2ClientContinuationTest(s *VethsSuite) { serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1 diff --git a/extras/hs-test/infra/suite_http2.go b/extras/hs-test/infra/suite_http2.go index b26ffd9e97..69739bc8a6 100644 --- a/extras/hs-test/infra/suite_http2.go +++ b/extras/hs-test/infra/suite_http2.go @@ -98,7 +98,12 @@ func (s *Http2Suite) SetupTest() { } func (s *Http2Suite) TeardownTest() { - s.HstSuite.TeardownTest() + defer s.HstSuite.TeardownTest() + vpp := s.Containers.Vpp.VppInstance + if CurrentSpecReport().Failed() { + s.Log(vpp.Vppctl("show session verbose 2")) + s.Log(vpp.Vppctl("show error")) + } } func (s *Http2Suite) VppAddr() string { @@ -514,9 +519,11 @@ var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() { }) testCases := []struct { - desc string - portOffset int + desc string + portOffset int + clientExtraArgs string }{ + // some tests are testing error conditions after request is completed so in this run http client with repeat {desc: "client/1/1", portOffset: 0}, {desc: "client/4.1/1", portOffset: 1}, {desc: "client/4.1/2", portOffset: 2}, @@ -535,8 +542,8 @@ var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() { //{desc: "client/5.1/6", portOffset: 13}, //{desc: "client/5.1/7", portOffset: 14}, {desc: "client/5.1/8", portOffset: 15}, - {desc: "client/5.1/9", portOffset: 16}, - {desc: "client/5.1/10", portOffset: 17}, + {desc: "client/5.1/9", portOffset: 16, clientExtraArgs: "repeat 2 "}, + {desc: "client/5.1/10", portOffset: 17, clientExtraArgs: "repeat 2 "}, {desc: "client/5.1.1/1", portOffset: 18}, {desc: "client/5.4.1/1", portOffset: 19}, {desc: "client/5.4.1/2", portOffset: 20}, @@ -552,7 +559,7 @@ var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() { //{desc: "client/6.3/2", portOffset: 29}, {desc: "client/6.4/1", portOffset: 30}, {desc: "client/6.4/2", portOffset: 31}, - {desc: "client/6.4/3", portOffset: 32}, + {desc: "client/6.4/3", portOffset: 32, clientExtraArgs: "repeat 2 "}, {desc: "client/6.5/1", portOffset: 33}, {desc: "client/6.5/2", portOffset: 34}, {desc: "client/6.5/3", portOffset: 35}, @@ -573,11 +580,11 @@ var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() { {desc: "client/6.9.1/1", portOffset: 49}, // TODO: message framing without content length using END_STREAM flag //{desc: "client/6.9.1/2", portOffset: 50}, - {desc: "client/6.10/1", portOffset: 51}, + {desc: "client/6.10/1", portOffset: 51, clientExtraArgs: "repeat 2 "}, {desc: "client/6.10/2", portOffset: 52}, {desc: "client/6.10/3", portOffset: 53}, - {desc: "client/6.10/4", portOffset: 54}, - {desc: "client/6.10/5", portOffset: 55}, + {desc: "client/6.10/4", portOffset: 54, clientExtraArgs: "repeat 2 "}, + {desc: "client/6.10/5", portOffset: 55, clientExtraArgs: "repeat 2 "}, {desc: "client/6.10/6", portOffset: 56}, } @@ -611,10 +618,8 @@ var _ = Describe("H2SpecClientSuite", Ordered, Serial, func() { go h2spec.RunClientSpec(conf) - cmd := fmt.Sprintf("http client timeout 5 verbose uri https://%s:%d/", serverAddress, h2specdFromPort+test.portOffset) - res := s.Containers.Vpp.VppInstance.Vppctl(cmd) - s.Log(res) - s.AssertNotContains(res, "error: timeout") + cmd := fmt.Sprintf("http client timeout 5 %s uri https://%s:%d/", test.clientExtraArgs, serverAddress, h2specdFromPort+test.portOffset) + s.Log(s.Containers.Vpp.VppInstance.Vppctl(cmd)) oChan := make(chan string) go func() { diff --git a/extras/hs-test/resources/nginx/nginx_server.conf b/extras/hs-test/resources/nginx/nginx_server.conf index a40ed7c309..d161e3c416 100644 --- a/extras/hs-test/resources/nginx/nginx_server.conf +++ b/extras/hs-test/resources/nginx/nginx_server.conf @@ -13,6 +13,9 @@ 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'; keepalive_timeout 300s; keepalive_requests 1000000; client_body_timeout {{.Timeout}}s; @@ -20,7 +23,7 @@ http { send_timeout {{.Timeout}}s; sendfile on; server { - access_log /tmp/nginx/{{.LogPrefix}}-access.log; + access_log /tmp/nginx/{{.LogPrefix}}-access.log access_log_fmt; listen {{.Port}}; listen {{.PortSsl}} ssl; server_name {{.Address}}; diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 97dbca788e..40eb1d8c51 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -15,7 +15,8 @@ #define foreach_hc_s_flag \ _ (1, IS_CLOSED) \ _ (2, PRINTABLE_BODY) \ - _ (4, CHUNKED_BODY) + _ (4, CHUNKED_BODY) \ + _ (8, IS_PARENT) typedef enum hc_s_flag_ { @@ -26,7 +27,7 @@ typedef enum hc_s_flag_ typedef struct { - u64 req_per_wrk; + u64 max_req; u64 request_count; f64 start, end; f64 elapsed_time; @@ -46,6 +47,12 @@ typedef struct u8 *http_response; u8 *response_status; FILE *file_ptr; + union + { + u32 child_count; + u32 parent_index; + }; + u32 http_session_index; } hc_session_t; typedef struct @@ -83,7 +90,8 @@ typedef struct bool verbose; f64 timeout; http_req_method_t req_method; - u64 repeat_count; + u64 reqs_per_session; + u64 reqs_remainder; f64 duration; bool repeat; bool multi_session; @@ -91,6 +99,7 @@ typedef struct u32 connected_counter; u32 worker_index; u32 max_sessions; + u32 max_streams; u32 private_segment_size; u32 prealloc_fifos; u32 fifo_size; @@ -112,6 +121,7 @@ typedef enum HC_GENERIC_ERR, HC_FOPEN_FAILED, HC_REPEAT_DONE, + HC_MAX_STREAMS_HIT, } hc_cli_signal_t; #define mime_printable_max_len 35 @@ -151,6 +161,7 @@ hc_session_alloc (hc_worker_t *wrk) pool_get_zero (wrk->sessions, s); s->session_index = s - wrk->sessions; s->thread_index = wrk->thread_index; + HTTP_DBG (1, "[%u]%u", s->thread_index, s->session_index); return s; } @@ -221,32 +232,110 @@ hc_request (session_t *s, hc_worker_t *wrk, hc_session_t *hc_session, return 0; } +typedef struct +{ + u64 parent_handle; + u32 parent_index; +} hc_connect_streams_args_t; + +static void +hc_connect_streams_rpc (void *rpc_args) +{ + hc_connect_streams_args_t *args = rpc_args; + hc_main_t *hcm = &hc_main; + vnet_connect_args_t _a, *a = &_a; + hc_worker_t *wrk; + hc_session_t *ho_hs; + u32 i; + int rv; + + clib_memset (a, 0, sizeof (*a)); + clib_memcpy (&a->sep_ext, &hcm->connect_sep, sizeof (hcm->connect_sep)); + a->sep_ext.parent_handle = args->parent_handle; + a->app_index = hcm->app_index; + + for (i = 0; i < (hcm->max_streams - 1); i++) + { + /* allocate half-open session */ + wrk = hc_worker_get (transport_cl_thread ()); + ho_hs = hc_session_alloc (wrk); + ho_hs->parent_index = args->parent_index; + a->api_context = ho_hs->session_index; + + rv = vnet_connect (a); + if (rv) + clib_warning (0, "connect returned: %U", format_session_error, rv); + } + vec_free (args); +} + +static void +hc_connect_streams (u64 parent_handle, u32 parent_index) +{ + hc_connect_streams_args_t *args = 0; + + vec_validate (args, 0); + args->parent_handle = parent_handle; + args->parent_index = parent_index; + + session_send_rpc_evt_to_thread_force (transport_cl_thread (), + hc_connect_streams_rpc, args); +} + static int -hc_session_connected_callback (u32 app_index, u32 hc_session_index, - session_t *s, session_error_t err) +hc_session_connected_callback (u32 app_index, u32 ho_index, session_t *s, + session_error_t err) { hc_main_t *hcm = &hc_main; hc_worker_t *wrk; - hc_session_t *hc_session; + hc_session_t *hc_session, *ho_session, *parent_session; hc_http_header_t *header; + http_version_t http_version; u8 *f = 0; + u32 s_index; if (err) { - clib_warning ("hc_session_index[%d] connected error: %U", - hc_session_index, format_session_error, err); - vlib_process_signal_event_mt (vlib_get_main (), hcm->cli_node_index, - HC_CONNECT_FAILED, 0); + clib_warning ("connected error: %U", format_session_error, err); + if (err == SESSION_E_MAX_STREAMS_HIT) + vlib_process_signal_event_mt (vlib_get_main (), hcm->cli_node_index, + HC_MAX_STREAMS_HIT, 0); + else + vlib_process_signal_event_mt (vlib_get_main (), hcm->cli_node_index, + HC_CONNECT_FAILED, 0); return -1; } + ho_session = hc_session_get (ho_index, transport_cl_thread ()); wrk = hc_worker_get (s->thread_index); - hc_session = hc_session_alloc (wrk); + s_index = hc_session->session_index; + clib_memcpy_fast (hc_session, ho_session, sizeof (*hc_session)); + hc_session->session_index = s_index; + hc_session->thread_index = s->thread_index; + hc_session->http_session_index = s->session_index; + clib_spinlock_lock_if_init (&hcm->lock); hcm->connected_counter++; clib_spinlock_unlock_if_init (&hcm->lock); + if (hc_session->session_flags & HC_S_FLAG_IS_PARENT) + { + http_version = http_session_get_version (s); + if (http_version == HTTP_VERSION_2 && hcm->max_streams > 1) + { + HTTP_DBG (1, "parent connected, going to open %u streams", + hcm->max_streams - 1); + hc_connect_streams (session_handle (s), hc_session->session_index); + } + } + else + { + parent_session = + hc_session_get (hc_session->parent_index, hc_session->thread_index); + parent_session->child_count++; + } + hc_session->thread_index = s->thread_index; hc_session->body_recv = 0; s->opaque = hc_session->session_index; @@ -254,19 +343,18 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index, if (hcm->multi_session) { - hc_session->stats.req_per_wrk = hcm->repeat_count / hcm->max_sessions; + hc_session->stats.max_req = hcm->reqs_per_session; clib_spinlock_lock_if_init (&hcm->lock); /* add remaining requests to the first connected session */ if (hcm->connected_counter == 1) { - hc_session->stats.req_per_wrk += - hcm->repeat_count % hcm->max_sessions; + hc_session->stats.max_req += hcm->reqs_remainder; } clib_spinlock_unlock_if_init (&hcm->lock); } else { - hc_session->stats.req_per_wrk = hcm->repeat_count; + hc_session->stats.max_req = hcm->reqs_per_session; hcm->worker_index = s->thread_index; } if (hcm->filename) @@ -369,7 +457,7 @@ hc_session_transport_closed_callback (session_t *s) } /* send an event when all sessions are closed */ - if (hcm->done_count >= hcm->max_sessions) + if (hcm->done_count >= (hcm->max_sessions * hcm->max_streams)) { if (hcm->was_transport_closed) { @@ -428,7 +516,10 @@ hc_rx_callback (session_t *s) max_deq = svm_fifo_max_dequeue_cons (s->rx_fifo); if (PREDICT_FALSE (max_deq == 0)) - goto done; + { + HTTP_DBG (1, "no data to deq"); + return 0; + } if (hc_session->to_recv == 0) { @@ -444,6 +535,10 @@ hc_rx_callback (session_t *s) return -1; } + HTTP_DBG (1, "hc_session_index[%u]%u %U content-length: %lu", + s->thread_index, s->opaque, format_http_status_code, msg.code, + msg.data.body_len); + if (msg.data.headers_len) { http_init_header_table_buf (&hc_session->resp_headers, msg); @@ -519,6 +614,7 @@ hc_rx_callback (session_t *s) svm_fifo_max_dequeue (s->rx_fifo)); if (!max_deq) { + HTTP_DBG (1, "body not yet received"); goto done; } u32 n_deq = clib_min (hc_session->to_recv, max_deq); @@ -543,6 +639,7 @@ hc_rx_callback (session_t *s) ASSERT (hc_session->to_recv >= rv); hc_session->to_recv -= rv; hc_session->body_recv += rv; + HTTP_DBG (1, "read %u, left to recv %u", n_deq, hc_session->to_recv); if (hcm->filename) { if (hc_session->file_ptr == NULL) @@ -566,10 +663,28 @@ hc_rx_callback (session_t *s) hc_session->stats.request_count++; if (hc_session->stats.elapsed_time >= hcm->duration && - hc_session->stats.request_count >= hc_session->stats.req_per_wrk) + hc_session->stats.request_count >= hc_session->stats.max_req) { HTTP_DBG (1, "repeat done"); - hc_session_disconnect_callback (s); + if (hc_session->session_flags & HC_S_FLAG_IS_PARENT) + { + /* parent must be closed last */ + if (hc_session->child_count != 0) + hc_session->session_flags |= HC_S_FLAG_IS_CLOSED; + else + hc_session_disconnect_callback (s); + } + else + { + hc_session_disconnect_callback (s); + hc_session_t *parent = hc_session_get ( + hc_session->parent_index, hc_session->thread_index); + parent->child_count--; + if (parent->child_count == 0 && + parent->session_flags & HC_S_FLAG_IS_CLOSED) + hc_session_disconnect_callback (session_get ( + parent->http_session_index, parent->thread_index)); + } } else { @@ -619,6 +734,14 @@ hc_tx_callback (session_t *s) return 0; } +static void +hc_ho_cleanup_callback (session_t *s) +{ + HTTP_DBG (1, "ho index %u", s->opaque); + hc_worker_t *wrk = hc_worker_get (transport_cl_thread ()); + pool_put_index (wrk->sessions, s->opaque); +} + static session_cb_vft_t hc_session_cb_vft = { .session_connected_callback = hc_session_connected_callback, .session_disconnect_callback = hc_session_disconnect_callback, @@ -626,6 +749,7 @@ static session_cb_vft_t hc_session_cb_vft = { .session_reset_callback = hc_session_reset_callback, .builtin_app_rx_callback = hc_rx_callback, .builtin_app_tx_callback = hc_tx_callback, + .half_open_cleanup_callback = hc_ho_cleanup_callback, }; static clib_error_t * @@ -683,24 +807,30 @@ hc_attach () return 0; } -static int +static void hc_connect_rpc (void *rpc_args) { vnet_connect_args_t *a = rpc_args; int rv = ~0; hc_main_t *hcm = &hc_main; + hc_worker_t *wrk; + hc_session_t *ho_hs; for (u32 i = 0; i < hcm->max_sessions; i++) { + /* allocate half-open session */ + wrk = hc_worker_get (transport_cl_thread ()); + ho_hs = hc_session_alloc (wrk); + ho_hs->session_flags |= HC_S_FLAG_IS_PARENT; + a->api_context = ho_hs->session_index; + rv = vnet_connect (a); - if (rv > 0) + if (rv) clib_warning (0, "connect returned: %U", format_session_error, rv); } session_endpoint_free_ext_cfgs (&a->sep_ext); vec_free (a); - - return rv; } static void @@ -710,6 +840,7 @@ hc_connect () vnet_connect_args_t *a = 0; transport_endpt_ext_cfg_t *ext_cfg; transport_endpt_cfg_http_t http_cfg = { (u32) hcm->timeout, 0 }; + vec_validate (a, 0); clib_memset (a, 0, sizeof (a[0])); clib_memcpy (&a->sep_ext, &hcm->connect_sep, sizeof (hcm->connect_sep)); @@ -731,7 +862,7 @@ hc_connect () ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_1_1; break; case HTTP_VERSION_2: - ext_cfg->crypto.alpn_protos[1] = TLS_ALPN_PROTO_HTTP_2; + ext_cfg->crypto.alpn_protos[0] = TLS_ALPN_PROTO_HTTP_2; break; default: break; @@ -753,7 +884,7 @@ hc_get_req_stats (vlib_main_t *vm) hc_session_t *hc_session; vec_foreach (wrk, hcm->wrk) { - vec_foreach (hc_session, wrk->sessions) + pool_foreach (hc_session, wrk->sessions) { hc_stats.request_count += hc_session->stats.request_count; hc_session->stats.request_count = 0; @@ -809,6 +940,9 @@ hc_get_event (vlib_main_t *vm) case HC_CONNECT_FAILED: err = clib_error_return (0, "error: failed to connect"); break; + case HC_MAX_STREAMS_HIT: + err = clib_error_return (0, "error: max streams hit"); + break; case HC_TRANSPORT_CLOSED: err = clib_error_return (0, "error: transport closed"); break; @@ -922,7 +1056,7 @@ hc_worker_cleanup (hc_worker_t *wrk) HTTP_DBG (1, "worker and worker sessions cleanup"); vec_free (wrk->headers_buf); - vec_foreach (hc_session, wrk->sessions) + pool_foreach (hc_session, wrk->sessions) { http_free_header_table (&hc_session->resp_headers); vec_free (hc_session->http_response); @@ -963,7 +1097,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hc_main_t *hcm = &hc_main; clib_error_t *err = 0; unformat_input_t _line_input, *line_input = &_line_input; - u64 mem_size; + u64 mem_size, repeat_count = 0; u8 *appns_id = 0; u8 *path = 0; u8 *file_data; @@ -972,13 +1106,13 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, u8 *value; int rv; hcm->timeout = 10; - hcm->repeat_count = 0; hcm->duration = 0; hcm->repeat = false; hcm->multi_session = false; hcm->done_count = 0; hcm->connected_counter = 0; hcm->max_sessions = 1; + hcm->max_streams = 1; hcm->prealloc_fifos = 0; hcm->private_segment_size = 0; hcm->fifo_size = 0; @@ -1034,7 +1168,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, hcm->verbose = true; else if (unformat (line_input, "timeout %f", &hcm->timeout)) ; - else if (unformat (line_input, "repeat %d", &hcm->repeat_count)) + else if (unformat (line_input, "repeat %d", &repeat_count)) { hcm->repeat = true; } @@ -1049,6 +1183,14 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } } + else if (unformat (line_input, "streams %d", &hcm->max_streams)) + { + if (hcm->max_streams <= 1) + { + err = clib_error_return (0, "streams must be > 1"); + goto done; + } + } else if (unformat (line_input, "prealloc-fifos %d", &hcm->prealloc_fifos)) ; @@ -1099,7 +1241,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, } } - if (hcm->duration && hcm->repeat_count) + if (hcm->duration && repeat_count) { err = clib_error_return ( 0, "combining duration and repeat is not supported"); @@ -1113,6 +1255,21 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } + if (hcm->max_streams > 1 && !hcm->repeat) + { + err = clib_error_return ( + 0, "multiple streams are only supported with request repeating"); + goto done; + } + + if (repeat_count) + { + hcm->reqs_per_session = + repeat_count / (hcm->max_sessions * hcm->max_streams); + hcm->reqs_remainder = + repeat_count % (hcm->max_sessions * hcm->max_streams); + } + if ((rv = parse_target ((char **) &hcm->uri, (char **) &hcm->target))) { err = clib_error_return (0, "target parse error: %U", diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index ccf987a6ad..fc1fc81ff2 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -788,6 +788,7 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) http_main_t *hm = &http_main; u32 num_threads, i; http_engine_vft_t *http_version; + http_worker_t *wrk; if (!is_en) { @@ -828,6 +829,10 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) } vec_validate (hm->wrk, num_threads - 1); + vec_foreach (wrk, hm->wrk) + { + clib_spinlock_init (&wrk->pending_stream_connects_lock); + } vec_validate (hm->rx_bufs, num_threads - 1); vec_validate (hm->tx_bufs, num_threads - 1); vec_validate (hm->app_header_lists, num_threads - 1); @@ -858,11 +863,10 @@ http_transport_enable (vlib_main_t *vm, u8 is_en) } static int -http_transport_connect (transport_endpoint_cfg_t *tep) +http_connect_connection (session_endpoint_cfg_t *sep) { vnet_connect_args_t _cargs, *cargs = &_cargs; http_main_t *hm = &http_main; - session_endpoint_cfg_t *sep = (session_endpoint_cfg_t *) tep; application_t *app; http_conn_t *hc; int error; @@ -942,6 +946,127 @@ http_transport_connect (transport_endpoint_cfg_t *tep) return 0; } +static int +http_connect_stream (u64 parent_handle, u32 opaque) +{ + session_t *hs; + http_req_handle_t rh; + u32 hc_index; + http_conn_t *hc; + + hs = session_get_from_handle (parent_handle); + if (session_type_transport_proto (hs->session_type) != TRANSPORT_PROTO_HTTP) + { + HTTP_DBG (1, "received incompatible session"); + return -1; + } + + rh.as_u32 = hs->connection_index; + if (rh.version != HTTP_VERSION_2) + { + HTTP_DBG (1, "%U multiplexing not supported", format_http_version, + rh.version); + return -1; + } + + hc_index = http_vfts[rh.version].hc_index_get_by_req_index ( + rh.req_index, hs->thread_index); + HTTP_DBG (1, "hc [%u]%x", hs->thread_index, hc_index); + + hc = http_conn_get_w_thread (hc_index, hs->thread_index); + + if (hc->state == HTTP_CONN_STATE_CLOSED) + { + HTTP_DBG (1, "conn closed"); + return -1; + } + + return http_vfts[rh.version].conn_connect_stream_callback (hc, opaque); +} + +static void +http_handle_stream_connects_rpc (void *args) +{ + clib_thread_index_t thread_index = pointer_to_uword (args); + http_worker_t *wrk; + u32 n_pending, max_connects, n_connects = 0; + http_pending_connect_stream_t *pc; + + wrk = http_worker_get (thread_index); + + clib_spinlock_lock (&wrk->pending_stream_connects_lock); + + n_pending = clib_fifo_elts (wrk->pending_connect_streams); + max_connects = clib_min (32, n_pending); + vec_validate (wrk->burst_connect_streams, max_connects); + + while (n_connects < max_connects) + clib_fifo_sub1 (wrk->pending_connect_streams, + wrk->burst_connect_streams[n_connects++]); + + clib_spinlock_unlock (&wrk->pending_stream_connects_lock); + + n_connects = 0; + while (n_connects < max_connects) + { + pc = &wrk->burst_connect_streams[n_connects++]; + http_connect_stream (pc->parent_handle, pc->opaque); + } + + /* more work to do? */ + if (max_connects < n_pending) + session_send_rpc_evt_to_thread_force ( + thread_index, http_handle_stream_connects_rpc, + uword_to_pointer ((uword) thread_index, void *)); +} + +static int +http_program_connect_stream (session_endpoint_cfg_t *sep) +{ + clib_thread_index_t parent_thread_index = + session_thread_from_handle (sep->parent_handle); + http_worker_t *wrk; + u32 n_pending; + + ASSERT (session_vlib_thread_is_cl_thread ()); + + /* if we are already on same worker as parent, handle connect */ + if (parent_thread_index == transport_cl_thread ()) + return http_connect_stream (sep->parent_handle, sep->opaque); + + /* if not on same worker as parent, queue request */ + wrk = http_worker_get (parent_thread_index); + + clib_spinlock_lock (&wrk->pending_stream_connects_lock); + + http_pending_connect_stream_t p = { .parent_handle = sep->parent_handle, + .opaque = sep->opaque }; + clib_fifo_add1 (wrk->pending_connect_streams, p); + n_pending = clib_fifo_elts (wrk->pending_connect_streams); + + clib_spinlock_unlock (&wrk->pending_stream_connects_lock); + + if (n_pending == 1) + session_send_rpc_evt_to_thread_force ( + parent_thread_index, http_handle_stream_connects_rpc, + uword_to_pointer ((uword) parent_thread_index, void *)); + + return 0; +} + +static int +http_transport_connect (transport_endpoint_cfg_t *tep) +{ + session_endpoint_cfg_t *sep = (session_endpoint_cfg_t *) tep; + session_t *hs; + + hs = session_get_from_handle_if_valid (sep->parent_handle); + if (hs) + return http_program_connect_stream (sep); + else + return http_connect_connection (sep); +} + static u32 http_start_listen (u32 app_listener_index, transport_endpoint_cfg_t *tep) { diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index e7ddaf350b..d8e313bed6 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -1954,7 +1954,7 @@ http1_transport_connected_callback (http_conn_t *hc) req = http1_conn_alloc_req (hc); http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); - return http_conn_established (hc, req); + return http_conn_established (hc, req, hc->hc_pa_app_api_ctx); } static void diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 52147b8a1e..4a3ccf37bd 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -104,7 +104,7 @@ typedef struct http2_conn_ctx_ u8 *unparsed_headers; /* temporary storing rx fragmented headers */ u8 *unsent_headers; /* temporary storing tx fragmented headers */ u32 unsent_headers_offset; - u32 client_req_index; + u32 req_num; } http2_conn_ctx_t; typedef struct http2_worker_ctx_ @@ -197,6 +197,7 @@ http2_conn_ctx_free (http_conn_t *hc) h2c = http2_conn_ctx_get_w_thread (hc); HTTP_DBG (1, "h2c [%u]%x", hc->c_thread_index, h2c - wrk->conn_pool); + ASSERT (h2c->req_num == 0); hash_free (h2c->req_by_stream_id); if (hc->flags & HTTP_CONN_F_HAS_REQUEST) hpack_dynamic_table_free (&h2c->decoder_dynamic_table); @@ -238,16 +239,19 @@ http2_conn_alloc_req (http_conn_t *hc) h2c - wrk->conn_pool, req_index); req->peer_window = h2c->peer_settings.initial_window_size; req->our_window = h2c->settings.initial_window_size; + h2c->req_num++; return req; } static_always_inline void http2_req_set_stream_id (http2_req_t *req, http2_conn_ctx_t *h2c, - u32 stream_id) + u32 stream_id, u8 unset_old) { HTTP_DBG (1, "req_index [%u]%x stream_id %u", req->base.c_thread_index, ((http_req_handle_t) req->base.hr_req_handle).req_index, stream_id); + if (unset_old && req->stream_id) + hash_unset (h2c->req_by_stream_id, req->stream_id); req->stream_id = stream_id; hash_set (h2c->req_by_stream_id, stream_id, ((http_req_handle_t) req->base.hr_req_handle).req_index); @@ -273,6 +277,7 @@ http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req, if (CLIB_DEBUG) memset (req, 0xba, sizeof (*req)); pool_put (wrk->req_pool, req); + h2c->req_num--; } static inline void @@ -284,9 +289,7 @@ http2_conn_reset_req (http2_conn_ctx_t *h2c, http2_req_t *req, if (clib_llist_elt_is_linked (req, sched_list)) clib_llist_remove (wrk->req_pool, sched_list, req); http_buffer_free (&req->base.tx_buf); - if (req->stream_id) - hash_unset (h2c->req_by_stream_id, req->stream_id); - req->flags = 0; + req->flags &= ~HTTP2_REQ_F_NEED_WINDOW_UPDATE; req->stream_state = HTTP2_STREAM_STATE_IDLE; req->peer_window = h2c->peer_settings.initial_window_size; req->our_window = h2c->settings.initial_window_size; @@ -429,11 +432,11 @@ http2_connection_error (http_conn_t *hc, http2_error_t error, } else { - if (hc->flags & HTTP_CONN_F_HAS_REQUEST) - { - req = http2_req_get (h2c->client_req_index, hc->c_thread_index); - session_transport_reset_notify (&req->base.connection); - } + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + session_transport_reset_notify ( + &req->base.connection); + })); } } if (clib_llist_elt_is_linked (h2c, sched_list)) @@ -1088,7 +1091,7 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); stream_id = http2_conn_get_next_stream_id (h2c); - http2_req_set_stream_id (req, h2c, stream_id); + http2_req_set_stream_id (req, h2c, stream_id, 1); http_io_as_dequeue_notify (&req->base, n_deq); @@ -1590,6 +1593,7 @@ http2_req_state_transport_io_more_data (http_conn_t *hc, http2_req_t *req, transport_connection_reschedule (&req->base.connection); h2c = http2_conn_ctx_get_w_thread (hc); http2_conn_reset_req (h2c, req, hc->c_thread_index); + http_io_as_del_want_read_ntf (&req->base); } } http_io_as_write (&req->base, req->payload, req->payload_len); @@ -1639,8 +1643,8 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, http2_error_t *error) { int rv; - u8 payload_offset; - u64 payload_len; + u8 payload_offset = 0; + u64 payload_len = 0; session_dgram_hdr_t hdr; HTTP_DBG (1, "udp tunnel received data from client"); @@ -1898,8 +1902,7 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) return HTTP2_ERROR_STREAM_CLOSED; } h2c->last_opened_stream_id = fh->stream_id; - if (hash_elts (h2c->req_by_stream_id) == - h2c->settings.max_concurrent_streams) + if (h2c->req_num == h2c->settings.max_concurrent_streams) { HTTP_DBG (1, "SETTINGS_MAX_CONCURRENT_STREAMS exceeded"); http_io_ts_drain (hc, fh->length); @@ -1908,7 +1911,7 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) return HTTP2_ERROR_NO_ERROR; } req = http2_conn_alloc_req (hc); - http2_req_set_stream_id (req, h2c, fh->stream_id); + http2_req_set_stream_id (req, h2c, fh->stream_id, 0); req->dispatch_headers_cb = http2_sched_dispatch_resp_headers; http_conn_accept_request (hc, &req->base); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_TRANSPORT_METHOD); @@ -2150,7 +2153,9 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) req->our_window -= fh->length; h2c->our_window -= fh->length; - HTTP_DBG (1, "run state machine"); + HTTP_DBG (1, "run state machine '%U' req_index %x", format_http_req_state, + req->base.state, + ((http_req_handle_t) req->base.hr_req_handle).req_index); return http2_req_run_state_machine (hc, req, 0, 0); } @@ -2272,14 +2277,13 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) h2c->flags &= ~HTTP2_CONN_F_EXPECT_SERVER_SETTINGS; HTTP_DBG (1, "client connection established"); req = http2_conn_alloc_req (hc); - h2c->client_req_index = - ((http_req_handle_t) req->base.hr_req_handle).req_index; + req->flags |= HTTP2_REQ_F_IS_PARENT; hc->flags |= HTTP_CONN_F_HAS_REQUEST; hpack_dynamic_table_init ( &h2c->decoder_dynamic_table, http2_default_conn_settings.header_table_size); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); - if (http_conn_established (hc, &req->base)) + if (http_conn_established (hc, &req->base, hc->hc_pa_app_api_ctx)) return HTTP2_ERROR_INTERNAL_ERROR; } @@ -2388,10 +2392,11 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) if (!(hc->flags & HTTP_CONN_F_IS_SERVER)) { ASSERT (hc->flags & HTTP_CONN_F_HAS_REQUEST); - req = http2_req_get (h2c->client_req_index, hc->c_thread_index); - if (!req) - return HTTP2_ERROR_NO_ERROR; - session_transport_closed_notify (&req->base.connection); + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + session_transport_closed_notify ( + &req->base.connection); + })); } } else @@ -2635,6 +2640,12 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, { HTTP_DBG (1, "nothing more to send, confirm close"); session_transport_closed_notify (&req->base.connection); + if (req->flags & HTTP2_REQ_F_IS_PARENT) + { + HTTP_DBG (1, "client app closed parent, closing connection"); + ASSERT (!(hc->flags & HTTP_CONN_F_IS_SERVER)); + http_shutdown_transport (hc); + } } else if (req->base.is_tunnel) { @@ -2950,6 +2961,28 @@ http2_conn_accept_callback (http_conn_t *hc) h2c->flags |= HTTP2_CONN_F_PREFACE_VERIFIED; } +static int +http2_conn_connect_stream_callback (http_conn_t *hc, u32 parent_app_api_ctx) +{ + http2_conn_ctx_t *h2c; + http2_req_t *req; + app_worker_t *app_wrk; + + HTTP_DBG (1, "hc [%u]%x", hc->c_thread_index, hc->hc_hc_index); + h2c = http2_conn_ctx_get_w_thread (hc); + ASSERT (!(hc->flags & HTTP_CONN_F_IS_SERVER)); + ASSERT (!(h2c->flags & HTTP2_CONN_F_EXPECT_SERVER_SETTINGS)); + app_wrk = app_worker_get_if_valid (hc->hc_pa_wrk_index); + if (!app_wrk) + return -1; + if (h2c->req_num == h2c->settings.max_concurrent_streams) + return app_worker_connect_notify (app_wrk, 0, SESSION_E_MAX_STREAMS_HIT, + hc->hc_pa_app_api_ctx); + req = http2_conn_alloc_req (hc); + http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); + return http_conn_established (hc, &req->base, parent_app_api_ctx); +} + static void http2_conn_cleanup_callback (http_conn_t *hc) { @@ -2959,14 +2992,8 @@ http2_conn_cleanup_callback (http_conn_t *hc) HTTP_DBG (1, "hc [%u]%x", hc->c_thread_index, hc->hc_hc_index); h2c = http2_conn_ctx_get_w_thread (hc); - if (hc->flags & HTTP_CONN_F_IS_SERVER) - hash_foreach (stream_id, req_index, h2c->req_by_stream_id, - ({ vec_add1 (req_indices, req_index); })); - else - { - if (hc->flags & HTTP_CONN_F_HAS_REQUEST) - vec_add1 (req_indices, h2c->client_req_index); - } + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, + ({ vec_add1 (req_indices, req_index); })); vec_foreach (req_index_p, req_indices) { @@ -3074,6 +3101,7 @@ const static http_engine_vft_t http2_engine = { .transport_conn_reschedule_callback = http2_transport_conn_reschedule_callback, .conn_accept_callback = http2_conn_accept_callback, + .conn_connect_stream_callback = http2_conn_connect_stream_callback, .conn_cleanup_callback = http2_conn_cleanup_callback, .enable_callback = http2_enable_callback, .unformat_cfg_callback = http2_unformat_config_callback, diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index b6a63b9711..f6666af81e 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -209,9 +209,18 @@ typedef struct http_tc_ void *opaque; /* version specific data */ } http_conn_t; +typedef struct http_pending_connect_stream_ +{ + u64 parent_handle; + u32 opaque; +} http_pending_connect_stream_t; + typedef struct http_worker_ { http_conn_t *conn_pool; + clib_spinlock_t pending_stream_connects_lock; + http_pending_connect_stream_t *pending_connect_streams; + http_pending_connect_stream_t *burst_connect_streams; } http_worker_t; typedef struct http_main_ @@ -265,6 +274,8 @@ typedef struct http_engine_vft_ 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 */ + int (*conn_connect_stream_callback) (http_conn_t *hc, + u32 parent_app_api_ctx); /* optional */ void (*conn_cleanup_callback) (http_conn_t *hc); void (*enable_callback) (void); /* optional */ uword (*unformat_cfg_callback) (unformat_input_t *input); /* optional */ @@ -556,6 +567,14 @@ http_io_as_add_want_read_ntf (http_req_t *req) SVM_FIFO_WANT_DEQ_NOTIF_IF_EMPTY); } +always_inline void +http_io_as_del_want_read_ntf (http_req_t *req) +{ + session_t *as = session_get_from_handle (req->hr_pa_session_handle); + svm_fifo_del_want_deq_ntf (as->rx_fifo, SVM_FIFO_WANT_DEQ_NOTIF_IF_FULL | + SVM_FIFO_WANT_DEQ_NOTIF_IF_EMPTY); +} + always_inline void http_io_as_reset_has_read_ntf (http_req_t *req) { @@ -859,7 +878,8 @@ http_conn_accept_request (http_conn_t *hc, http_req_t *req) } always_inline int -http_conn_established (http_conn_t *hc, http_req_t *req) +http_conn_established (http_conn_t *hc, http_req_t *req, + u32 parent_app_api_ctx) { session_t *as; app_worker_t *app_wrk; @@ -873,7 +893,7 @@ http_conn_established (http_conn_t *hc, http_req_t *req) as->app_wrk_index = hc->hc_pa_wrk_index; as->connection_index = req->hr_req_handle; as->session_state = SESSION_STATE_READY; - as->opaque = hc->hc_pa_app_api_ctx; + as->opaque = parent_app_api_ctx; ts = session_get_from_handle (hc->hc_tc_session_handle); as->session_type = session_type_from_proto_and_ip ( TRANSPORT_PROTO_HTTP, session_type_is_ip4 (ts->session_type)); @@ -895,7 +915,7 @@ http_conn_established (http_conn_t *hc, http_req_t *req) return rv; } - app_worker_connect_notify (app_wrk, as, 0, hc->hc_pa_app_api_ctx); + app_worker_connect_notify (app_wrk, as, 0, parent_app_api_ctx); req->hr_pa_session_handle = session_handle (as); req->hr_pa_wrk_index = as->app_wrk_index; From 2c146c243adbf3b68e9bf49fd8a9d55467206164 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 25 Jul 2025 05:39:44 -0400 Subject: [PATCH 158/313] http: starting http/2 with prior knowledge 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 --- extras/hs-test/http2_test.go | 29 ++++++++++++++++++- .../hs-test/resources/nginx/nginx_server.conf | 3 +- src/plugins/hs_apps/http_client.c | 15 ++++++---- src/plugins/http/http.c | 13 +++++---- src/plugins/http/http.h | 18 ++++++++++++ src/plugins/http/http_private.h | 8 +++++ 6 files changed, 74 insertions(+), 12 deletions(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 26605f2140..3cc0304d8f 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -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) { diff --git a/extras/hs-test/resources/nginx/nginx_server.conf b/extras/hs-test/resources/nginx/nginx_server.conf index d161e3c416..f5e4f9e485 100644 --- a/extras/hs-test/resources/nginx/nginx_server.conf +++ b/extras/hs-test/resources/nginx/nginx_server.conf @@ -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; diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c index 40eb1d8c51..8a047fc9c3 100644 --- a/src/plugins/hs_apps/http_client.c +++ b/src/plugins/hs_apps/http_client.c @@ -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); diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index fc1fc81ff2..b387b61ae0 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -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); diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 61c387bf78..c106038806 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -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 diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index f6666af81e..b76f4f6b50 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -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)); From 3ea1f8f72c1a92d7a256d92746073801d19c2c6c Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 25 Jul 2025 09:09:27 -0400 Subject: [PATCH 159/313] hs-test: h2 client memory leak test Type: test Change-Id: Ie8c49e84031d0da06c9770de25e94ff8166457f9 Signed-off-by: Matus Fabian --- extras/hs-test/http2_test.go | 52 ++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 3cc0304d8f..82230a0643 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -14,7 +14,7 @@ import ( func init() { RegisterH2Tests(Http2TcpGetTest, Http2TcpPostTest, Http2MultiplexingTest, Http2TlsTest, Http2ContinuationTxTest, Http2ServerMemLeakTest, Http2ClientGetTest, Http2ClientPostTest, Http2ClientPostPtrTest, Http2ClientGetRepeatTest, Http2ClientMultiplexingTest, - Http2ClientH2cTest) + Http2ClientH2cTest, Http2ClientMemLeakTest) RegisterH2MWTests(Http2MultiplexingMWTest, Http2ClientMultiplexingMWTest) RegisterVethTests(Http2CliTlsTest, Http2ClientContinuationTest) } @@ -139,10 +139,17 @@ func Http2ServerMemLeakTest(s *Http2Suite) { /* no goVPP less noise */ vpp.Disconnect() - /* warmup request (FIB) */ + /* warmup requests (FIB, pools) */ args := fmt.Sprintf("--max-time 10 --noproxy '*' --http2-prior-knowledge -z %s %s %s %s", target, target, target, target) _, log := s.RunCurlContainer(s.Containers.Curl, args) s.AssertContains(log, "HTTP/2 200") + for i := 0; i < 10; i++ { + time.Sleep(time.Second * 1) + s.AssertNil(s.Containers.Curl.Start()) + } + + /* let's give it some time to clean up sessions, so pool elements can be reused and we have less noise */ + time.Sleep(time.Second * 15) vpp.EnableMemoryTrace() traces1, err := vpp.GetMemoryTrace() @@ -260,6 +267,7 @@ func Http2ClientGetRepeatTest(s *Http2Suite) { cmd := fmt.Sprintf("http client http2 repeat %d uri %s", 10, uri) o := vpp.Vppctl(cmd) s.Log(o) + s.AssertContains(o, "10 request(s)") } func Http2ClientMultiplexingTest(s *Http2Suite) { @@ -333,3 +341,43 @@ func Http2ClientContinuationTest(s *VethsSuite) { s.AssertContains(o, "HTTP/2 200 OK") s.AssertGreaterThan(strings.Count(o, "x"), 32768) } + +func Http2ClientMemLeakTest(s *Http2Suite) { + s.SkipUnlessLeakCheck() + + vpp := s.Containers.Vpp.VppInstance + serverAddress := s.HostAddr() + ":" + s.Ports.Port1 + + s.CreateNginxServer() + s.AssertNil(s.Containers.NginxServer.Start()) + + uri := "http://" + serverAddress + "/64B" + + /* no goVPP less noise */ + vpp.Disconnect() + + /* warmup requests (FIB, pools) */ + cmd := fmt.Sprintf("http client verbose http2 uri %s", uri) + o := vpp.Vppctl(cmd) + s.AssertContains(o, "HTTP/2 200 OK") + /* do second request because pool is at threshold and will grow again */ + o = vpp.Vppctl(cmd) + s.AssertContains(o, "HTTP/2 200 OK") + + /* let's give it some time to clean up sessions, so pool elements can be reused and we have less noise */ + time.Sleep(time.Second * 15) + + vpp.EnableMemoryTrace() + traces1, err := vpp.GetMemoryTrace() + s.AssertNil(err, fmt.Sprint(err)) + + o = vpp.Vppctl(cmd) + s.AssertContains(o, "HTTP/2 200 OK") + + /* let's give it some time to clean up sessions */ + time.Sleep(time.Second * 15) + + traces2, err := vpp.GetMemoryTrace() + s.AssertNil(err, fmt.Sprint(err)) + vpp.MemLeakCheck(traces1, traces2) +} From 9b9a7b3ab6b5a41c9cef43be1c20a5ee2b383656 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 25 Jul 2025 11:50:00 -0400 Subject: [PATCH 160/313] http: validate h2 frame lengeth in handlers Type: fix Change-Id: I741a80a44c4355849e354c9b17d9213ac90a381d Signed-off-by: Matus Fabian --- src/plugins/http/http2/frame.c | 20 ++++++++----------- src/plugins/http/http2/frame.h | 2 ++ src/plugins/http/http2/http2.c | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/plugins/http/http2/frame.c b/src/plugins/http/http2/frame.c index fe593a0015..c6551ee48b 100644 --- a/src/plugins/http/http2/frame.c +++ b/src/plugins/http/http2/frame.c @@ -131,14 +131,12 @@ http2_frame_write_settings (http2_settings_entry_t *settings, u8 **dst) } } -#define WINDOW_UPDATE_LENGTH 4 - __clib_export http2_error_t http2_frame_read_window_update (u32 *increment, u8 *payload, u32 payload_len) { u32 *value; - if (payload_len != WINDOW_UPDATE_LENGTH) + if (payload_len != HTTP2_WINDOW_UPDATE_LENGTH) return HTTP2_ERROR_FRAME_SIZE_ERROR; value = (u32 *) payload; @@ -159,24 +157,22 @@ http2_frame_write_window_update (u32 increment, u32 stream_id, u8 **dst) ASSERT (increment > 0 && increment <= 0x7FFFFFFF); http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_WINDOW_UPDATE, - .length = WINDOW_UPDATE_LENGTH, + .length = HTTP2_WINDOW_UPDATE_LENGTH, .stream_id = stream_id }; p = http2_frame_header_alloc (dst); http2_frame_header_write (&fh, p); - vec_add2 (*dst, p, WINDOW_UPDATE_LENGTH); + vec_add2 (*dst, p, HTTP2_WINDOW_UPDATE_LENGTH); value = clib_host_to_net_u32 (increment); - clib_memcpy_fast (p, &value, WINDOW_UPDATE_LENGTH); + clib_memcpy_fast (p, &value, HTTP2_WINDOW_UPDATE_LENGTH); } -#define RST_STREAM_LENGTH 4 - __clib_export http2_error_t http2_frame_read_rst_stream (u32 *error_code, u8 *payload, u32 payload_len) { u32 *value; - if (payload_len != RST_STREAM_LENGTH) + if (payload_len != HTTP2_RST_STREAM_LENGTH) return HTTP2_ERROR_FRAME_SIZE_ERROR; value = (u32 *) payload; @@ -195,14 +191,14 @@ http2_frame_write_rst_stream (http2_error_t error_code, u32 stream_id, ASSERT (stream_id > 0 && stream_id <= 0x7FFFFFFF); http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_RST_STREAM, - .length = RST_STREAM_LENGTH, + .length = HTTP2_RST_STREAM_LENGTH, .stream_id = stream_id }; p = http2_frame_header_alloc (dst); http2_frame_header_write (&fh, p); - vec_add2 (*dst, p, RST_STREAM_LENGTH); + vec_add2 (*dst, p, HTTP2_RST_STREAM_LENGTH); value = clib_host_to_net_u32 ((u32) error_code); - clib_memcpy_fast (p, &value, RST_STREAM_LENGTH); + clib_memcpy_fast (p, &value, HTTP2_RST_STREAM_LENGTH); } __clib_export http2_error_t diff --git a/src/plugins/http/http2/frame.h b/src/plugins/http/http2/frame.h index e19cfa7a69..cfa3e850a6 100644 --- a/src/plugins/http/http2/frame.h +++ b/src/plugins/http/http2/frame.h @@ -12,6 +12,8 @@ #define HTTP2_FRAME_HEADER_SIZE 9 #define HTTP2_PING_PAYLOAD_LEN 8 #define HTTP2_GOAWAY_MIN_SIZE 8 +#define HTTP2_WINDOW_UPDATE_LENGTH 4 +#define HTTP2_RST_STREAM_LENGTH 4 #define foreach_http2_frame_type \ _ (0x00, DATA, "DATA") \ diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 4a3ccf37bd..d92d52a047 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1885,6 +1885,12 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) http2_error_t rv; http2_conn_ctx_t *h2c; + if (fh->length < 1) + { + HTTP_DBG (1, "zero length payload"); + return HTTP2_ERROR_FRAME_SIZE_ERROR; + } + h2c = http2_conn_ctx_get_w_thread (hc); if (hc->flags & HTTP_CONN_F_IS_SERVER) @@ -2025,6 +2031,12 @@ http2_handle_continuation_frame (http_conn_t *hc, http2_frame_header_t *fh) u8 *p; http2_error_t rv = HTTP2_ERROR_NO_ERROR; + if (fh->length < 1) + { + HTTP_DBG (1, "zero length payload"); + return HTTP2_ERROR_FRAME_SIZE_ERROR; + } + h2c = http2_conn_ctx_get_w_thread (hc); if (!(h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION)) @@ -2141,6 +2153,12 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) HTTP2_STREAM_STATE_CLOSED; } + if (fh->length == 0) + { + HTTP_DBG (1, "zero length payload"); + return HTTP2_ERROR_NO_ERROR; + } + rx_buf = http_get_rx_buf (hc); vec_validate (rx_buf, fh->length - 1); http_io_ts_read (hc, rx_buf, fh->length, 0); @@ -2169,6 +2187,12 @@ http2_handle_window_update_frame (http_conn_t *hc, http2_frame_header_t *fh) http2_req_t *req; http2_conn_ctx_t *h2c; + if (fh->length != HTTP2_WINDOW_UPDATE_LENGTH) + { + HTTP_DBG (1, "invalid payload length"); + return HTTP2_ERROR_FRAME_SIZE_ERROR; + } + h2c = http2_conn_ctx_get_w_thread (hc); rx_buf = http_get_rx_buf (hc); @@ -2327,6 +2351,12 @@ http2_handle_rst_stream_frame (http_conn_t *hc, http2_frame_header_t *fh) u32 error_code; http2_conn_ctx_t *h2c; + if (fh->length != HTTP2_RST_STREAM_LENGTH) + { + HTTP_DBG (1, "invalid payload length"); + return HTTP2_ERROR_FRAME_SIZE_ERROR; + } + if (fh->stream_id == 0) return HTTP2_ERROR_PROTOCOL_ERROR; @@ -2370,6 +2400,12 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) http2_conn_ctx_t *h2c; http2_req_t *req; + if (fh->length < HTTP2_GOAWAY_MIN_SIZE) + { + HTTP_DBG (1, "invalid payload length"); + return HTTP2_ERROR_FRAME_SIZE_ERROR; + } + if (fh->stream_id != 0) return HTTP2_ERROR_PROTOCOL_ERROR; From 1fb5ffc9200a8ab6b75af35c7e593e764909e7ee Mon Sep 17 00:00:00 2001 From: Damjan Marion Date: Wed, 23 Jul 2025 12:41:16 +0000 Subject: [PATCH 161/313] vlib: remove timer when sched node is dispatched by interrupt Type: fix Change-Id: I48a0da4a3af50101e2d2c1ed3cd8734b7e907f77 Signed-off-by: Damjan Marion --- src/vlib/main.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vlib/main.c b/src/vlib/main.c index ffa4a69642..121d70b54a 100644 --- a/src/vlib/main.c +++ b/src/vlib/main.c @@ -1597,6 +1597,8 @@ vlib_main_or_worker_loop (vlib_main_t * vm, int is_main) { vlib_node_runtime_t *n; n = vec_elt_at_index (nm->nodes_by_type[nt], int_num); + if (n->stop_timer_handle_plus_1) + vlib_node_unschedule (vm, n->node_index); cpu_time_now = dispatch_node ( vm, n, nt, /* frame */ 0, VLIB_NODE_DISPATCH_REASON_INTERRUPT, @@ -1615,11 +1617,14 @@ vlib_main_or_worker_loop (vlib_main_t * vm, int is_main) { vlib_node_runtime_t *nr = vlib_node_get_runtime (vm, n->index); - nr->stop_timer_handle_plus_1 = 0; - cpu_time_now = dispatch_node ( - vm, nr, VLIB_NODE_TYPE_SCHED, - /* frame */ 0, VLIB_NODE_DISPATCH_REASON_SCHED, - cpu_time_now); + if (nr->stop_timer_handle_plus_1) + { + nr->stop_timer_handle_plus_1 = 0; + cpu_time_now = dispatch_node ( + vm, nr, VLIB_NODE_TYPE_SCHED, + /* frame */ 0, VLIB_NODE_DISPATCH_REASON_SCHED, + cpu_time_now); + } } } vec_reset_length (nm->sched_node_pending); From 7538c8644631825cec2c5be8ed307538b721ff63 Mon Sep 17 00:00:00 2001 From: Vladimir Zhigulin Date: Fri, 4 Jul 2025 11:10:44 +0200 Subject: [PATCH 162/313] vlib: fix typo in ASAN stack size calculation Type: fix Change-Id: I771ca783854f704fc333a6dd857831ffe5d70bd3 Signed-off-by: Vladimir Zhigulin --- src/vlib/node_funcs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vlib/node_funcs.h b/src/vlib/node_funcs.h index 17677ee7ae..a5096911d3 100644 --- a/src/vlib/node_funcs.h +++ b/src/vlib/node_funcs.h @@ -59,7 +59,7 @@ vlib_process_start_switch_stack (vlib_main_t * vm, vlib_process_t * p) #ifdef CLIB_SANITIZE_ADDR void *stack = p ? (void *) p->stack : vlib_thread_stacks[vm->thread_index]; u32 stack_bytes = - p ? (1ULL < p->log2_n_stack_bytes) : VLIB_THREAD_STACK_SIZE; + p ? (1ULL << p->log2_n_stack_bytes) : VLIB_THREAD_STACK_SIZE; __sanitizer_start_switch_fiber (&vm->asan_stack_save, stack, stack_bytes); #endif } From 6086803e136a84fef1cd022d583e3c21dd56167d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 28 Jul 2025 04:42:35 -0400 Subject: [PATCH 163/313] http: huffman decoder invalid EOS handling fix Handle EOS longer than 7 bits Type: fix Change-Id: I4cb3ba37efe17dad9245c4d433eac987354d225c Signed-off-by: Matus Fabian --- src/plugins/http/http2/hpack.c | 7 +++++-- src/plugins/http/test/http_test.c | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 8af061eb75..b94e8ebc22 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -198,6 +198,9 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) * encoding” */ hpack_huffman_group_t *hg = hpack_huffman_get_group (tmp); + /* this might happen with invalid EOS (longer than 7 bits) */ + if (hg->code_len > accumulator_len) + return HTTP2_ERROR_COMPRESSION_ERROR; /* trim code to correct length */ u32 code = (accumulator >> (accumulator_len - hg->code_len)) & ((1 << hg->code_len) - 1); @@ -215,7 +218,7 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) /* there might be one more symbol encoded with short code */ if (accumulator_len >= 5) { - /* first check EOF case */ + /* first check EOS case */ if (((1 << accumulator_len) - 1) == (accumulator & ((1 << accumulator_len) - 1))) break; @@ -235,7 +238,7 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) if (accumulator_len == 0) break; } - /* we must end with EOF here */ + /* we must end with EOS here */ if (((1 << accumulator_len) - 1) != (accumulator & ((1 << accumulator_len) - 1))) return HTTP2_ERROR_COMPRESSION_ERROR; diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index 0d2e3a7b49..cf04fc1a2a 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -994,6 +994,9 @@ http_test_hpack (vlib_main_t *vm) N_TEST ("\x7Fprivate", HTTP2_ERROR_COMPRESSION_ERROR); /* invalid EOF */ N_TEST ("\x81\x8C", HTTP2_ERROR_COMPRESSION_ERROR); + N_TEST ("\x98\xDC\x53\xFF\xFF\xFF\xDF\xFF\xFF\xFF\x14\xFF\xFF\xFF\xF7\xFF" + "\xFF\xFF\xC5\x3F\xFF\xFF\xFD\xFF\xFF", + HTTP2_ERROR_COMPRESSION_ERROR); /* not enough space for decoding */ N_TEST ( "\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04\x0B\x81\x66" From 3421e906074ffa6242d00267cda73329cb8848a3 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 28 Jul 2025 12:49:35 -0400 Subject: [PATCH 164/313] nsim: fix uninitialized variables othervise user is not informed of missing parameter and demons fly out of your nose Type: fix Change-Id: I54db587c99f193de1884fd7a0e292ae7128da5d7 Signed-off-by: Matus Fabian --- src/plugins/nsim/nsim.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/nsim/nsim.c b/src/plugins/nsim/nsim.c index 6339ecf099..8b9e1b66a1 100644 --- a/src/plugins/nsim/nsim.c +++ b/src/plugins/nsim/nsim.c @@ -696,7 +696,8 @@ static clib_error_t * set_nsim_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { - f64 drop_fraction = 0.0, reorder_fraction = 0.0, delay, bandwidth; + f64 drop_fraction = 0.0, reorder_fraction = 0.0, delay = 0.0, + bandwidth = 0.0; u32 packets_per_drop, packets_per_reorder, packet_size = 1500; nsim_main_t *nsm = &nsim_main; int rv; From 6adc4d8c6014f35ff8d74aa16bb20b11bd7f55ef Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Tue, 29 Jul 2025 00:56:49 -0400 Subject: [PATCH 165/313] hsa: cl udp app wait for pthreads to exit Type: improvement Change-Id: I31367de6fe3dde38d4379069b5c6fe589bd12e76 Signed-off-by: Florin Coras --- src/plugins/hs_apps/vcl/vcl_test_cl_udp.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c b/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c index a0df4c228c..1325941129 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c +++ b/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c @@ -342,6 +342,9 @@ main (int argc, char **argv) while (!vt_clu_test_done (vclum)) ; + /* Wait for pthreads to cleanup before signaling */ + usleep (100e3); + for (int i = 1; i < vclum->num_workers; i++) { pthread_kill (vclum->worker_threads[i], SIGUSR1); From ae0d6fac457297001dfdc584577786081c95bfc7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 29 Jul 2025 09:18:30 -0400 Subject: [PATCH 166/313] hs-test: fix nsim interface in TcpWithLoss tests Type: test Change-Id: I94b3f052fa7a482083ae7459f1cfea067453c9b5 Signed-off-by: Matus Fabian --- extras/hs-test/echo_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 24ebb6d3d8..ee510545da 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -108,10 +108,10 @@ func TcpWithLossTest(s *VethsSuite) { clientVpp := s.Containers.ClientVpp.VppInstance // Add loss of packets with Network Delay Simulator - clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + - " packet-size 1400 packets-per-drop 1000") + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + + " packet-size 1400 packets-per-drop 1000")) - clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Server.Name()) + s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) // Do echo test from client-vpp container output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes bytes 50m", @@ -130,10 +130,10 @@ func TcpWithLoss6Test(s *Veths6Suite) { clientVpp := s.Containers.ClientVpp.VppInstance // Add loss of packets with Network Delay Simulator - clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + - " packet-size 1400 packets-per-drop 1000") + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + + " packet-size 1400 packets-per-drop 1000")) - clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Server.Name()) + s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) // Do echo test from client-vpp container output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes bytes 50m", From 04b1c066078b1e72bae69e5c07a85a0596021b96 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Mon, 21 Jul 2025 14:08:14 +0200 Subject: [PATCH 167/313] hs-test: containerize ginkgo - ginkgo will run in a container - replaced '--network=host' with '--network=container:ginkgo' - removed --privileged flags - removed network namespaces from HST - updated goimports - fixed state hashes Type: improvement Change-Id: I5f4c5aa93437f40b36a8eb2ba3d6486cdfe23e09 Signed-off-by: Adrian Villin --- .gitignore | 2 +- extras/hs-test/Makefile | 72 +++++++++++++-------- extras/hs-test/docker/Dockerfile.base | 8 ++- extras/hs-test/docker/Dockerfile.ginkgo | 19 ++++++ extras/hs-test/hs_test.sh | 9 ++- extras/hs-test/infra/container.go | 27 +++++--- extras/hs-test/infra/suite_iperf_linux.go | 2 - extras/hs-test/ldp_test.go | 6 +- extras/hs-test/script/build-images.sh | 3 + extras/hs-test/script/build_hst.sh | 5 +- extras/hs-test/topo-network/2peerVeth.yaml | 6 -- extras/hs-test/topo-network/2peerVeth6.yaml | 6 -- 12 files changed, 108 insertions(+), 57 deletions(-) create mode 100644 extras/hs-test/docker/Dockerfile.ginkgo mode change 100644 => 100755 extras/hs-test/hs_test.sh diff --git a/.gitignore b/.gitignore index 17b48e307c..67415d8441 100644 --- a/.gitignore +++ b/.gitignore @@ -143,10 +143,10 @@ compile_commands.json /extras/hs-test/.build.ok /extras/hs-test/.build.cov.ok /extras/hs-test/.last_hst_ppid -/extras/hs-test/.goimports.ok /extras/hs-test/summary/ /extras/hs-test/.last_state_hash /extras/hs-test/.kind_deps.ok +/extras/hs-test/.go_cache/ # ./configure /CMakeFiles diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 6b94fe682c..4d33529959 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -73,9 +73,27 @@ ifeq ($(GINKGO_TIMEOUT),) GINKGO_TIMEOUT=3h endif +CORE_PATTERN := $(shell cat /proc/sys/kernel/core_pattern) +CORE_VOLUME:= +DOCKER_TTY:= + +ifeq ($(shell expr "$(CORE_PATTERN)" : '^/'), 1) +CORE_VOLUME := -v $(CORE_PATTERN):$(CORE_PATTERN) +endif + +ifeq ($(shell tty -s && echo $$?), 0) +DOCKER_TTY := -it +endif + FORCE_BUILD?=true BUILD_AS:=$(strip $(shell echo $${SUDO_USER:-$${USER:-root}})) +DOCKER_CAPABILITIES:=--cap-add=NET_ADMIN --cap-add=SYS_RESOURCE --cap-add=IPC_LOCK +DOCKER_DEVICES:=--device /dev/vhost-net:/dev/vhost-net --device /dev/net/tun:/dev/net/tun +DOCKER_VOLUMES:=-v $(WS_ROOT):$(WS_ROOT) -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/hs-test:/tmp/hs-test \ + -v /etc/localtime:/etc/localtime:ro $(CORE_VOLUME) -v $(HS_ROOT)/.go_cache/mod:/root/go/pkg/mod \ + -v $(HS_ROOT)/.go_cache/build:/root/.cache/go-build + .PHONY: help help: @echo "Make targets:" @@ -159,21 +177,25 @@ build-vpp-gcov: build-msg .PHONY: test test: FORCE_BUILD=false test: .deps.ok .build.ok - @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ + docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ + .$(HS_ROOT)/hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --cpu0=$(CPU0) \ --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT) \ - --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ + --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS) --hs_root=$(HS_ROOT); \ ./script/compress.sh $$? .PHONY: test-debug test-debug: FORCE_BUILD=false test-debug: .deps.ok .build_debug.ok - @bash ./hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ + docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ + .$(HS_ROOT)/hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --parallel=$(PARALLEL) --repeat=$(REPEAT) --debug_build=true \ --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) --timeout=$(TIMEOUT) \ - --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ + --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS) --hs_root=$(HS_ROOT); \ ./script/compress.sh $$? .PHONY: wipe-lcov @@ -183,18 +205,22 @@ wipe-lcov: .PHONY: test-cov test-cov: FORCE_BUILD=false test-cov: .deps.ok .build.cov.ok wipe-lcov - -@bash ./hs_test.sh --coverage=true --persist=$(PERSIST) --verbose=$(VERBOSE) \ + -docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ + .$(HS_ROOT)/hs_test.sh --coverage=true --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST-HS) --cpus=$(CPUS) \ --vppsrc=$(VPPSRC) --cpu0=$(CPU0) --dryrun=$(DRYRUN) --skip=$(SKIP) --no_color=$(NO_COLOR) \ - --timeout=$(TIMEOUT) --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); \ + --timeout=$(TIMEOUT) --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS) --hs_root=$(HS_ROOT); \ ./script/compress.sh $$? $(MAKE) -C ../.. test-cov-post-standalone HS_TEST=1 .PHONY: test-leak test-leak: FORCE_BUILD=false test-leak: .deps.ok .build_debug.ok - @bash ./hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) \ - --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS); + docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ + .$(HS_ROOT)/hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) \ + --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS) --hs_root=$(HS_ROOT); .PHONY: test-perf test-perf: FORCE_BUILD=false @@ -207,34 +233,35 @@ test-perf: .deps.ok .build.ok .PHONY: release-cluster release-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh setup_release + @bash ./kubernetes/setupCluster.sh release-cluster .PHONY: master-cluster master-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh setup_master + @bash ./kubernetes/setupCluster.sh master-cluster .PHONY: rebuild-master-cluster rebuild-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh rebuild_master + @bash ./kubernetes/setupCluster.sh rebuild-master-cluster +# this is executed in a container by hs-test.sh .PHONY: build-go build-go: - go build ./tools/http_server + go build --buildvcs=false ./tools/http_server .PHONY: build -build: .deps.ok build-vpp-release build-go +build: .deps.ok build-vpp-release @rm -f .build.ok bash ./script/build_hst.sh release $(FORCE_BUILD) $(BUILD_AS) @touch .build.ok .PHONY: build-cov -build-cov: .deps.ok build-vpp-gcov build-go +build-cov: .deps.ok build-vpp-gcov @rm -f .build.cov.ok bash ./script/build_hst.sh gcov $(FORCE_BUILD) $(BUILD_AS) @touch .build.cov.ok .PHONY: build-debug -build-debug: .deps.ok build-vpp-debug build-go +build-debug: .deps.ok build-vpp-debug @rm -f .build.ok bash ./script/build_hst.sh debug $(FORCE_BUILD) $(BUILD_AS) @touch .build.ok @@ -291,15 +318,9 @@ install-deps: @sudo -E apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin @touch .deps.ok -.goimports.ok: - @rm -f .goimports.ok - go install golang.org/x/tools/cmd/goimports@v0.25.0 - @touch .goimports.ok - .PHONY: checkstyle-go -checkstyle-go: .goimports.ok - $(eval GOPATH := $(shell go env GOPATH)) - @output=$$($(GOPATH)/bin/goimports -d $${WS_ROOT}); \ +checkstyle-go: + @output=$$(find . -type f -name '*.go' -not -path './.go_cache/*' -exec go run golang.org/x/tools/cmd/goimports@v0.35.0 -d {} +); \ status=$$?; \ if [ $$status -ne 0 ]; then \ exit $$status; \ @@ -316,10 +337,9 @@ checkstyle-go: .goimports.ok fi .PHONY: fixstyle-go -fixstyle-go: .goimports.ok - $(eval GOPATH := $(shell go env GOPATH)) +fixstyle-go: @echo "Modified files:" - @$(GOPATH)/bin/goimports -w -l $(WS_ROOT) + @find . -type f -name '*.go' -not -path './.go_cache/*' -exec go run golang.org/x/tools/cmd/goimports@v0.35.0 -w -l {} + @go mod tidy @echo "*******************************************************************" @echo "Fixstyle done." diff --git a/extras/hs-test/docker/Dockerfile.base b/extras/hs-test/docker/Dockerfile.base index 12b7c0588a..e6e19f9f39 100644 --- a/extras/hs-test/docker/Dockerfile.base +++ b/extras/hs-test/docker/Dockerfile.base @@ -37,7 +37,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Tools moved from derived images apache2-utils \ nghttp2 \ - wrk + wrk \ + # Utilities needed for running tests + sudo \ + make \ + ethtool \ + bridge-utils \ + jq # Because of http/3 we can't use stock curl in ubuntu 24.04 ARG TARGETARCH diff --git a/extras/hs-test/docker/Dockerfile.ginkgo b/extras/hs-test/docker/Dockerfile.ginkgo new file mode 100644 index 0000000000..2ec1e107ab --- /dev/null +++ b/extras/hs-test/docker/Dockerfile.ginkgo @@ -0,0 +1,19 @@ +FROM localhost:5001/vpp-test-base:latest + +# We don't need to install these packages as they're in the base image +# Just install anything specific to VPP that isn't in the base + +ARG UBUNTU_VERSION +ARG TARGETARCH +ARG CODENAME +ARG GO_VERSION=1.23.10 +RUN echo "I'm building for ${TARGETARCH}" \ +&& wget "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" -O /tmp/go.tar.gz \ +&& tar -xzf /tmp/go.tar.gz -C /usr/local \ +&& rm /tmp/go.tar.gz + +RUN wget "https://download.docker.com/linux/ubuntu/dists/${CODENAME}/pool/stable/${TARGETARCH}/docker-ce-cli_28.3.2-1~ubuntu.${UBUNTU_VERSION}~${CODENAME}_${TARGETARCH}.deb" \ +-O /tmp/docker-ce-cli.deb +RUN dpkg -i /tmp/docker-ce-cli.deb + +RUN ln -s /usr/local/go/bin/go /usr/bin/go diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh old mode 100644 new mode 100755 index a6c70a2bd3..c30a75966e --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -17,6 +17,7 @@ skip_names=() dryrun= no_color= perf= +hs_root= for i in "$@" do @@ -125,6 +126,9 @@ case "${i}" in --timeout=*) args="$args -timeout ${i#*=}" ;; + --hs_root=*) + hs_root="${i#*=}" + cd $hs_root esac done @@ -183,9 +187,12 @@ if [ $leak_check_set -eq 1 ]; then fi if [ -n "${BUILD_NUMBER}" ]; then - ginkgo_args="$ginkgo_args --no-color" + ginkgo_args="$ginkgo_args --no-color" fi +mkdir -p .go_cache +make build-go + mkdir -p summary # shellcheck disable=SC2086 CMD="sudo -E go run github.com/onsi/ginkgo/v2/ginkgo --json-report=summary/report.json $ginkgo_args -- $args" diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go index 0426516ae1..5a3f9f3aa2 100644 --- a/extras/hs-test/infra/container.go +++ b/extras/hs-test/infra/container.go @@ -129,7 +129,8 @@ func (c *Container) GetContainerWorkDir() (res string) { } func (c *Container) getContainerArguments() string { - args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host" + args := "--ulimit nofile=90000:90000 --cap-add=NET_ADMIN --cap-add=SYS_RESOURCE " + + "--cap-add=IPC_LOCK --device /dev/net/tun:/dev/net/tun --device /dev/vhost-net:/dev/vhost-net" args += c.getVolumesAsCliOption() args += c.getEnvVarsAsCliOption() if *VppSourceFileDir != "" { @@ -170,10 +171,9 @@ func (c *Container) Create() error { resp, err := c.Suite.Docker.ContainerCreate( c.ctx, &containerTypes.Config{ - Hostname: c.Name, - Image: c.Image, - Env: c.getEnvVars(), - Cmd: strings.Split(c.ExtraRunningArgs, " "), + Image: c.Image, + Env: c.getEnvVars(), + Cmd: strings.Split(c.ExtraRunningArgs, " "), }, &containerTypes.HostConfig{ Resources: containerTypes.Resources{ @@ -185,10 +185,21 @@ func (c *Container) Create() error { }, }, CpusetCpus: cpuSet, + Devices: []containerTypes.DeviceMapping{ + { + PathOnHost: "/dev/net/tun", + PathInContainer: "/dev/net/tun", + CgroupPermissions: "rwm", + }, + { + PathOnHost: "/dev/vhost-net", + PathInContainer: "dev/vhost-net", + CgroupPermissions: "rwm", + }, + }, }, - CapAdd: []string{"ALL"}, - Privileged: true, - NetworkMode: "host", + CapAdd: []string{"NET_ADMIN", "SYS_RESOURCE", "IPC_LOCK"}, + NetworkMode: "container:ginkgo", Binds: c.getVolumesAsSlice(), }, nil, diff --git a/extras/hs-test/infra/suite_iperf_linux.go b/extras/hs-test/infra/suite_iperf_linux.go index be41e5082a..0e31c9c17a 100644 --- a/extras/hs-test/infra/suite_iperf_linux.go +++ b/extras/hs-test/infra/suite_iperf_linux.go @@ -4,7 +4,6 @@ import ( "reflect" "runtime" "strings" - "time" . "fd.io/hs-test/infra/common" . "github.com/onsi/ginkgo/v2" @@ -36,7 +35,6 @@ func RegisterIperfSoloTests(tests ...func(s *IperfSuite)) { } func (s *IperfSuite) SetupSuite() { - time.Sleep(1 * time.Second) s.HstSuite.SetupSuite() s.ConfigureNetworkTopology("2taps") s.LoadContainerTopology("2containers") diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 73f10ed422..1c057b081b 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -28,16 +28,14 @@ func LdpIperfUdpVppInterruptModeTest(s *LdpSuite) { } func ldpIperfTcpReorder(s *LdpSuite, netInterface *NetInterface, extraIperfArgs string) { - cmd := exec.Command("ip", "netns", "exec", netInterface.Peer.NetworkNamespace, - "tc", "qdisc", "del", "dev", netInterface.Peer.Name(), + cmd := exec.Command("tc", "qdisc", "del", "dev", netInterface.Peer.Name(), "root") s.Log("defer '%s'", cmd.String()) defer cmd.Run() // "10% of packets (with a correlation of 50%) will get sent immediately, others will be delayed by 10ms" // https://www.man7.org/linux/man-pages/man8/tc-netem.8.html - cmd = exec.Command("ip", "netns", "exec", netInterface.Peer.NetworkNamespace, - "tc", "qdisc", "add", "dev", netInterface.Peer.Name(), + cmd = exec.Command("tc", "qdisc", "add", "dev", netInterface.Peer.Name(), "root", "netem", "delay", "10ms", "reorder", "10%", "50%") s.Log(cmd.String()) o, err := cmd.CombinedOutput() diff --git a/extras/hs-test/script/build-images.sh b/extras/hs-test/script/build-images.sh index 2ec7de3746..a989b2fa75 100755 --- a/extras/hs-test/script/build-images.sh +++ b/extras/hs-test/script/build-images.sh @@ -5,6 +5,7 @@ set -e # Get default architecture for multi-arch builds ARCH=${OS_ARCH:-$(dpkg --print-architecture)} +CODENAME=$(lsb_release -cs) # Set up buildx configuration DOCKER_BUILD_DIR="/scratch/docker-build" @@ -84,6 +85,7 @@ build_image() { docker build \ --build-arg UBUNTU_VERSION="${UBUNTU_VERSION:-22.04}" \ --build-arg OS_ARCH="$ARCH" \ + --build-arg CODENAME="$CODENAME" \ --build-arg http_proxy="$HTTP_PROXY" \ --build-arg https_proxy="$HTTP_PROXY" \ --build-arg HTTP_PROXY="$HTTP_PROXY" \ @@ -107,6 +109,7 @@ build_image "Dockerfile.h2load" "hs-test/h2load" build_image "Dockerfile.curl" "hs-test/curl" build_image "Dockerfile.ab" "hs-test/ab" build_image "Dockerfile.wrk" "hs-test/wrk" +build_image "Dockerfile.ginkgo" "hs-test/ginkgo" # Build HTTP/3 nginx if available echo "=== Building HTTP/3 nginx image ===" diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh index 77d9dc7fef..f0ad9ae7f9 100755 --- a/extras/hs-test/script/build_hst.sh +++ b/extras/hs-test/script/build_hst.sh @@ -22,8 +22,9 @@ fi LAST_STATE_FILE=".last_state_hash" # get current state hash and ubuntu version -current_state_hash=$(ls -l "$VPP_BUILD_ROOT"/.mu_build_install_timestamp; ls -l docker | sha1sum | awk '{print $1}') -current_state_hash=$current_state_hash$UBUNTU_VERSION$1 +ctime_hash1=$(stat -c %Z "$VPP_BUILD_ROOT"/.mu_build_install_timestamp | sha1sum | awk '{print $1}') +ctime_hash2=$(stat -c %Z docker/* | sha1sum | awk '{print $1}') +current_state_hash=$ctime_hash1-$ctime_hash2-$UBUNTU_VERSION$1 if [ -f "$LAST_STATE_FILE" ]; then last_state_hash=$(cat "$LAST_STATE_FILE") diff --git a/extras/hs-test/topo-network/2peerVeth.yaml b/extras/hs-test/topo-network/2peerVeth.yaml index f991d8b370..7e5f60b3db 100644 --- a/extras/hs-test/topo-network/2peerVeth.yaml +++ b/extras/hs-test/topo-network/2peerVeth.yaml @@ -1,24 +1,18 @@ --- devices: - - name: "hsns" - type: "netns" - - name: "srv" type: "veth" preset-hw-address: "00:00:5e:00:53:01" peer: name: "srv_veth" - netns: "hsns" - name: "cln" type: "veth" peer: name: "cln_veth" - netns: "hsns" - name: "br" type: "bridge" - netns: "hsns" interfaces: - srv_veth - cln_veth diff --git a/extras/hs-test/topo-network/2peerVeth6.yaml b/extras/hs-test/topo-network/2peerVeth6.yaml index 139d9964d0..f95183152f 100644 --- a/extras/hs-test/topo-network/2peerVeth6.yaml +++ b/extras/hs-test/topo-network/2peerVeth6.yaml @@ -1,26 +1,20 @@ --- devices: - - name: "hsns" - type: "netns" - - name: "srv" ipv6: true type: "veth" preset-hw-address: "00:00:5e:00:53:01" peer: name: "srv_veth" - netns: "hsns" - name: "cln" ipv6: true type: "veth" peer: name: "cln_veth" - netns: "hsns" - name: "br" type: "bridge" - netns: "hsns" interfaces: - srv_veth - cln_veth From 68e50d8dc7d80642b107a9acf18441f7905a137f Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Fri, 25 Jul 2025 16:11:20 +0200 Subject: [PATCH 168/313] hs-test: add nginx mirroring KinD test, cleanup Type: test Change-Id: I15e145023226ea0d442f69e42da4cdc586366873 Signed-off-by: Adrian Villin --- extras/hs-test/infra/kind/deployment.go | 3 +- extras/hs-test/infra/kind/pod.go | 85 ++++++++++++++++++++++- extras/hs-test/infra/kind/suite_kind.go | 77 ++++++++++++++------ extras/hs-test/infra/kind/utils.go | 57 --------------- extras/hs-test/kind_test.go | 72 +++++++++++-------- extras/hs-test/kubernetes/setupCluster.sh | 2 +- 6 files changed, 183 insertions(+), 113 deletions(-) diff --git a/extras/hs-test/infra/kind/deployment.go b/extras/hs-test/infra/kind/deployment.go index 6404056500..20fc6c80c3 100644 --- a/extras/hs-test/infra/kind/deployment.go +++ b/extras/hs-test/infra/kind/deployment.go @@ -49,6 +49,7 @@ func (s *KindSuite) deleteNamespace(namespace string) error { } func (s *KindSuite) DeployPod(pod *Pod) { + pod.suite = s s.CurrentlyRunning = append(s.CurrentlyRunning, pod.Name) pod.CreatedPod = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -100,5 +101,5 @@ func (s *KindSuite) DeployPod(pod *Pod) { } } - s.Log("IP: %s", pod.IpAddress) + s.Log("IP: %s\n", pod.IpAddress) } diff --git a/extras/hs-test/infra/kind/pod.go b/extras/hs-test/infra/kind/pod.go index 1b37047149..f323130af2 100644 --- a/extras/hs-test/infra/kind/pod.go +++ b/extras/hs-test/infra/kind/pod.go @@ -1,8 +1,20 @@ package hst_kind -import corev1 "k8s.io/api/core/v1" +import ( + "bytes" + "context" + "os" + "os/exec" + "text/template" + "time" + + . "fd.io/hs-test/infra/common" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/remotecommand" +) type Pod struct { + suite *KindSuite Name string Image string ContainerName string @@ -21,6 +33,7 @@ func (s *KindSuite) initPods() { clientCont := "client" serverCont := "server" + // TODO: load from file s.images = append(s.images, vppImg, nginxLdpImg, abImg) s.Namespace = "namespace" + s.Ppid @@ -47,4 +60,74 @@ func (s *KindSuite) initPods() { s.Pods.Nginx.Image = nginxLdpImg s.Pods.Nginx.ContainerName = serverCont s.Pods.Nginx.Worker = wrk2 + + s.Pods.NginxProxy = new(Pod) + s.Pods.NginxProxy.Name = "nginx-proxy" + s.Ppid + s.Pods.NginxProxy.Image = nginxLdpImg + s.Pods.NginxProxy.ContainerName = serverCont + s.Pods.NginxProxy.Worker = wrk2 +} + +func (pod *Pod) CopyToPod(namespace string, src string, dst string) { + cmd := exec.Command("kubectl", "--kubeconfig="+pod.suite.KubeconfigPath, "cp", src, namespace+"/"+pod.Name+":"+dst) + out, err := cmd.CombinedOutput() + pod.suite.AssertNil(err, string(out)) +} + +func (pod *Pod) Exec(command []string) (string, error) { + var stdout, stderr bytes.Buffer + + // Prepare the request + req := pod.suite.ClientSet.CoreV1().RESTClient().Post(). + Resource("pods"). + Name(pod.Name). + Namespace(pod.suite.Namespace). + SubResource("exec"). + Param("container", pod.ContainerName). + Param("stdout", "true"). + Param("stderr", "true"). + Param("tty", "true") + + for _, cmd := range command { + req = req.Param("command", cmd) + } + pod.suite.Log("%s: %s\n", pod.Name, command) + + executor, err := remotecommand.NewSPDYExecutor(pod.suite.Config, "POST", req.URL()) + if err != nil { + pod.suite.Log("Error creating executor: %s", err.Error()) + } + + ctx, cancel := context.WithTimeout(context.Background(), 500*time.Second) + defer cancel() + + err = executor.StreamWithContext(ctx, remotecommand.StreamOptions{ + Stdout: &stdout, + Stderr: &stderr, + Tty: true, + }) + + output := stdout.String() + stderr.String() + + if err != nil { + return output, err + } + + return output, nil +} + +func (pod *Pod) CreateConfigFromTemplate(targetConfigName string, templateName string, values any) { + template := template.Must(template.ParseFiles(templateName)) + + f, err := os.CreateTemp(LogDir, "hst-config") + pod.suite.AssertNil(err, err) + defer os.Remove(f.Name()) + + err = template.Execute(f, values) + pod.suite.AssertNil(err, err) + + err = f.Close() + pod.suite.AssertNil(err, err) + + pod.CopyToPod(pod.suite.Namespace, f.Name(), targetConfigName) } diff --git a/extras/hs-test/infra/kind/suite_kind.go b/extras/hs-test/infra/kind/suite_kind.go index 45b24fc3cf..1754853f78 100644 --- a/extras/hs-test/infra/kind/suite_kind.go +++ b/extras/hs-test/infra/kind/suite_kind.go @@ -2,12 +2,10 @@ package hst_kind import ( "fmt" - "os" "reflect" "regexp" "runtime" "strings" - "text/template" . "fd.io/hs-test/infra/common" . "github.com/onsi/ginkgo/v2" @@ -30,12 +28,32 @@ type KindSuite struct { ServerGeneric *Pod ClientGeneric *Pod Nginx *Pod + NginxProxy *Pod Ab *Pod } } var kindTests = map[string][]func(s *KindSuite){} +const VclConfIperf = "echo \"vcl {\n" + + "rx-fifo-size 4000000\n" + + "tx-fifo-size 4000000\n" + + "app-scope-local\n" + + "app-scope-global\n" + + "app-socket-api abstract:vpp/session\n" + + "}\" > /vcl.conf" + +const VclConfNginx = "echo \"vcl {\n" + + "heapsize 64M\n" + + "rx-fifo-size 4000000\n" + + "tx-fifo-size 4000000\n" + + "segment-size 4000000000\n" + + "add-segment-size 4000000000\n" + + "event-queue-size 100000\n" + + "use-mq-eventfd\n" + + "app-socket-api abstract:vpp/session\n" + + "}\" > /vcl.conf" + func RegisterKindTests(tests ...func(s *KindSuite)) { kindTests[GetTestFilename()] = tests } @@ -74,6 +92,7 @@ func (s *KindSuite) SetupSuite() { func (s *KindSuite) TeardownTest() { s.HstCommon.TeardownTest() if len(s.CurrentlyRunning) != 0 { + s.Log("Removing:") for _, pod := range s.CurrentlyRunning { s.Log(" %s", pod) s.deletePod(s.Namespace, pod) @@ -83,7 +102,7 @@ func (s *KindSuite) TeardownTest() { func (s *KindSuite) TeardownSuite() { s.HstCommon.TeardownSuite() - s.Log(" %s", s.Namespace) + s.Log("Removing:\n %s", s.Namespace) s.AssertNil(s.deleteNamespace(s.Namespace)) } @@ -91,7 +110,7 @@ func (s *KindSuite) TeardownSuite() { // and searches for the first version string, then creates symlinks. func (s *KindSuite) FixVersionNumber(pods ...*Pod) { regex := regexp.MustCompile(`lib.*\.so\.([0-9]+\.[0-9]+)`) - o, _ := s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", + o, _ := s.Pods.ServerGeneric.Exec([]string{"/bin/bash", "-c", "ldd /usr/lib/libvcl_ldpreload.so"}) match := regex.FindStringSubmatch(o) @@ -106,7 +125,7 @@ func (s *KindSuite) FixVersionNumber(pods ...*Pod) { "fi\n"+ "done", version) for _, pod := range pods { - s.Exec(pod, []string{"/bin/bash", "-c", cmd}) + pod.Exec([]string{"/bin/bash", "-c", cmd}) } } else { @@ -114,23 +133,7 @@ func (s *KindSuite) FixVersionNumber(pods ...*Pod) { } } -func (s *KindSuite) CreateConfigFromTemplate(targetConfigName string, templateName string, values any) { - template := template.Must(template.ParseFiles(templateName)) - - f, err := os.CreateTemp(LogDir, "hst-config") - s.AssertNil(err, err) - defer os.Remove(f.Name()) - - err = template.Execute(f, values) - s.AssertNil(err, err) - - err = f.Close() - s.AssertNil(err, err) - - s.CopyToPod(s.Pods.Nginx.Name, s.Namespace, f.Name(), targetConfigName) -} - -func (s *KindSuite) CreateNginxConfig() { +func (s *KindSuite) CreateNginxConfig(pod *Pod) { values := struct { Workers uint8 Port uint16 @@ -138,13 +141,41 @@ func (s *KindSuite) CreateNginxConfig() { Workers: 1, Port: 8081, } - s.CreateConfigFromTemplate( + pod.CreateConfigFromTemplate( "/nginx.conf", "./resources/nginx/nginx.conf", values, ) } +func (s *KindSuite) CreateNginxProxyConfig(pod *Pod) { + pod.Exec([]string{"/bin/bash", "-c", "mkdir -p /tmp/nginx"}) + values := struct { + Workers uint8 + LogPrefix string + Proxy string + Server string + Port uint16 + Upstream1 string + Upstream2 string + Upstream3 string + }{ + Workers: 1, + LogPrefix: s.Pods.NginxProxy.Name, + Proxy: s.Pods.NginxProxy.IpAddress, + Server: s.Pods.Nginx.IpAddress, + Port: 8080, + Upstream1: "8081", + Upstream2: "8081", + Upstream3: "8081", + } + pod.CreateConfigFromTemplate( + "/nginx.conf", + "./resources/nginx/nginx_proxy_mirroring.conf", + values, + ) +} + var _ = Describe("KindSuite", Ordered, ContinueOnFailure, Label("Perf"), func() { var s KindSuite BeforeAll(func() { diff --git a/extras/hs-test/infra/kind/utils.go b/extras/hs-test/infra/kind/utils.go index d6f37194b8..ea82085f32 100644 --- a/extras/hs-test/infra/kind/utils.go +++ b/extras/hs-test/infra/kind/utils.go @@ -1,62 +1,5 @@ package hst_kind -import ( - "bytes" - "context" - "os/exec" - "time" - - "k8s.io/client-go/tools/remotecommand" -) - -func (s *KindSuite) CopyToPod(podName string, namespace string, src string, dst string) { - cmd := exec.Command("kubectl", "--kubeconfig="+s.KubeconfigPath, "cp", src, namespace+"/"+podName+":"+dst) - out, err := cmd.CombinedOutput() - s.AssertNil(err, string(out)) -} - -func (s *KindSuite) Exec(pod *Pod, command []string) (string, error) { - var stdout, stderr bytes.Buffer - - // Prepare the request - req := s.ClientSet.CoreV1().RESTClient().Post(). - Resource("pods"). - Name(pod.Name). - Namespace(s.Namespace). - SubResource("exec"). - Param("container", pod.ContainerName). - Param("stdout", "true"). - Param("stderr", "true"). - Param("tty", "true") - - for _, cmd := range command { - req = req.Param("command", cmd) - } - s.Log("%s: %s", pod.Name, command) - - executor, err := remotecommand.NewSPDYExecutor(s.Config, "POST", req.URL()) - if err != nil { - s.Log("Error creating executor: %s", err.Error()) - } - - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Second) - defer cancel() - - err = executor.StreamWithContext(ctx, remotecommand.StreamOptions{ - Stdout: &stdout, - Stderr: &stderr, - Tty: true, - }) - - output := stdout.String() + stderr.String() - - if err != nil { - return output, err - } - - return output, nil -} - func boolPtr(b bool) *bool { return &b } diff --git a/extras/hs-test/kind_test.go b/extras/hs-test/kind_test.go index 0e0431d0c2..bc09fb7c3e 100644 --- a/extras/hs-test/kind_test.go +++ b/extras/hs-test/kind_test.go @@ -8,7 +8,7 @@ import ( ) func init() { - RegisterKindTests(KindIperfVclTest, NginxRpsTest) + RegisterKindTests(KindIperfVclTest, NginxRpsTest, NginxProxyMirroringTest) } const vcl string = "VCL_CONFIG=/vcl.conf" @@ -18,58 +18,70 @@ func KindIperfVclTest(s *KindSuite) { s.DeployPod(s.Pods.ClientGeneric) s.DeployPod(s.Pods.ServerGeneric) - vclConf := "echo \"vcl {\n" + - "rx-fifo-size 4000000\n" + - "tx-fifo-size 4000000\n" + - "app-scope-local\n" + - "app-scope-global\n" + - "app-socket-api abstract:vpp/session\n" + - "}\" > /vcl.conf" - - _, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", vclConf}) + _, err := s.Pods.ClientGeneric.Exec([]string{"/bin/bash", "-c", VclConfIperf}) s.AssertNil(err) - _, err = s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", vclConf}) + _, err = s.Pods.ServerGeneric.Exec([]string{"/bin/bash", "-c", VclConfIperf}) s.AssertNil(err) s.FixVersionNumber(s.Pods.ClientGeneric, s.Pods.ServerGeneric) - o, err := s.Exec(s.Pods.ServerGeneric, []string{"/bin/bash", "-c", + o, err := s.Pods.ServerGeneric.Exec([]string{"/bin/bash", "-c", vcl + " " + ldp + " iperf3 -s -D -4"}) s.AssertNil(err, o) - output, err := s.Exec(s.Pods.ClientGeneric, []string{"/bin/bash", "-c", + o, err = s.Pods.ClientGeneric.Exec([]string{"/bin/bash", "-c", vcl + " " + ldp + " iperf3 -l 1460 -b 10g -c " + s.Pods.ServerGeneric.IpAddress}) - s.Log(output) + s.Log(o) s.AssertNil(err) } func NginxRpsTest(s *KindSuite) { s.DeployPod(s.Pods.Nginx) s.DeployPod(s.Pods.Ab) - s.CreateNginxConfig() - - vclConf := "echo \"vcl {\n" + - "heapsize 64M\n" + - "rx-fifo-size 4000000\n" + - "tx-fifo-size 4000000\n" + - "segment-size 4000000000\n" + - "add-segment-size 4000000000\n" + - "event-queue-size 100000\n" + - "use-mq-eventfd\n" + - "app-socket-api abstract:vpp/session\n" + - "}\" > /vcl.conf" - - out, err := s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", vclConf}) + s.CreateNginxConfig(s.Pods.Nginx) + + out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", VclConfNginx}) + s.AssertNil(err, out) + + go func() { + defer GinkgoRecover() + out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"}) + s.AssertNil(err, out) + }() + + // wait for nginx to start up + time.Sleep(time.Second * 2) + out, err = s.Pods.Ab.Exec([]string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":8081/64B.json"}) + s.Log(out) + s.AssertNil(err) +} + +func NginxProxyMirroringTest(s *KindSuite) { + s.DeployPod(s.Pods.Nginx) + s.DeployPod(s.Pods.NginxProxy) + s.DeployPod(s.Pods.ClientGeneric) + s.CreateNginxConfig(s.Pods.Nginx) + s.CreateNginxProxyConfig(s.Pods.NginxProxy) + + out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", VclConfNginx}) s.AssertNil(err, out) + out, err = s.Pods.NginxProxy.Exec([]string{"/bin/bash", "-c", VclConfNginx}) + s.AssertNil(err, out) + + go func() { + defer GinkgoRecover() + out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"}) + s.AssertNil(err, out) + }() go func() { defer GinkgoRecover() - out, err := s.Exec(s.Pods.Nginx, []string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"}) + out, err := s.Pods.NginxProxy.Exec([]string{"/bin/bash", "-c", "nginx -c /nginx.conf"}) s.AssertNil(err, out) }() // wait for nginx to start up time.Sleep(time.Second * 2) - out, err = s.Exec(s.Pods.Ab, []string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":8081/64B.json"}) + out, err = s.Pods.ClientGeneric.Exec([]string{"curl", "-v", "--noproxy", "'*'", "--insecure", "http://" + s.Pods.NginxProxy.IpAddress + ":8080/64B.json"}) s.Log(out) s.AssertNil(err) } diff --git a/extras/hs-test/kubernetes/setupCluster.sh b/extras/hs-test/kubernetes/setupCluster.sh index 3c176f6e98..4eba189fba 100755 --- a/extras/hs-test/kubernetes/setupCluster.sh +++ b/extras/hs-test/kubernetes/setupCluster.sh @@ -67,7 +67,7 @@ setup_master() { make -C $CALICOVPP_DIR kind-new-cluster N_KIND_WORKERS=2 kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml make -C $CALICOVPP_DIR cherry-vpp FORCE=y BASE=origin/master VPP_DIR=$VPP_DIR - make build-vpp-release + make -C $VPP_DIR/extras/hs-test build-vpp-release make -C $CALICOVPP_DIR dev-kind make -C $CALICOVPP_DIR load-kind $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up From f2f1a11678d819d97c7c94af33217ab194a4d59b Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 26 Jul 2025 18:35:11 -0400 Subject: [PATCH 169/313] vcl: favor wr to vls_mt_spool_rwlock Type: improvement In a multi-thread single worker app, there can be contention between many readers and the writers. The writers, which can be close operations that potentially influence the app logic, could be starved if the numbers of readers is significant. To avoid this, always favor writers. Might require fine tuning in the future. Change-Id: I5ef529195a31114bb95e19e5880247f58d454370 Signed-off-by: Florin Coras --- src/vcl/vcl_locked.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index 576bbdb007..bedeb7efc6 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -140,6 +140,7 @@ typedef struct vls_local_ clib_rwlock_t vls_pool_lock; /**< per process/wrk vls pool locks */ pthread_mutex_t vls_mt_mq_mlock; /**< vcl mq lock */ pthread_rwlock_t vls_mt_spool_rwlock; /**< vcl select or pool rwlock */ + volatile u32 vls_mt_spool_pending_wr; /**< pending writers */ volatile u8 select_mp_check; /**< flag set if select checks done */ struct sigaction old_sa; /**< old sigaction to restore */ } vls_process_local_t; @@ -408,6 +409,10 @@ vls_mt_mq_unlock (void) static inline void vls_mt_spool_rlock (void) { + /* Favor writers as they can be close operations that hold off all other + * operations */ + while (vlsl->vls_mt_spool_pending_wr) + CLIB_PAUSE (); pthread_rwlock_rdlock (&vlsl->vls_mt_spool_rwlock); vlspt->locks_acq |= VLS_MT_RLOCK_SPOOL; } @@ -415,8 +420,10 @@ vls_mt_spool_rlock (void) static inline void vls_mt_spool_wlock (void) { + vlsl->vls_mt_spool_pending_wr += 1; pthread_rwlock_wrlock (&vlsl->vls_mt_spool_rwlock); vlspt->locks_acq |= VLS_MT_WLOCK_SPOOL; + vlsl->vls_mt_spool_pending_wr -= 1; } static inline void From 28707cb5a1b43de81169bfb41bb48e2d09ecf84f Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sun, 27 Jul 2025 18:10:50 -0400 Subject: [PATCH 170/313] vcl: atomics for tracking num workers in vls Type: improvement Change-Id: I272be20ae56e4bb8a25d754926d14e45460bd920 Signed-off-by: Florin Coras --- src/vcl/vcl_locked.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vcl/vcl_locked.c b/src/vcl/vcl_locked.c index bedeb7efc6..cd38f568fe 100644 --- a/src/vcl/vcl_locked.c +++ b/src/vcl/vcl_locked.c @@ -361,8 +361,11 @@ typedef enum static void vls_mt_add (void) { + u32 n_threads; + vlsl->vls_mt_needs_locks = 1; - vlsl->vls_mt_n_threads += 1; + n_threads = + __atomic_add_fetch (&vlsl->vls_mt_n_threads, 1, __ATOMIC_RELAXED); vlspt->locks_acq = 0; /* If multi-thread workers are supported, for each new thread register a new @@ -377,7 +380,7 @@ vls_mt_add (void) vcl_set_worker_index (vlsl->vls_wrk_index); /* Only allow new pthread to be cancled in vls_mt_mq_lock */ - if (vlsl->vls_mt_n_threads >= 2) + if (n_threads >= 2) pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); if (pthread_setspecific (vls_mt_pthread_stop_key, vcl_worker_get_current ())) @@ -469,6 +472,7 @@ static void vls_mt_del (void *arg) { vcl_worker_t *wrk = (vcl_worker_t *) arg; + u32 n_threads; VDBG (0, "vls worker %u vcl worker %u nthreads %u cleaning up pthread", vlsl->vls_wrk_index, vcl_get_worker_index (), vlsl->vls_mt_n_threads); @@ -480,7 +484,8 @@ vls_mt_del (void *arg) return; } - vlsl->vls_mt_n_threads -= 1; + n_threads = + __atomic_sub_fetch (&vlsl->vls_mt_n_threads, 1, __ATOMIC_RELAXED); /* drop locks if any held */ vls_mt_rel_locks (); @@ -492,7 +497,7 @@ vls_mt_del (void *arg) } else { - if (!vlsl->vls_mt_n_threads) + if (!n_threads) vppcom_worker_unregister (); } } From 344dab5a22e1d8020aae6fb331d3e7cfeb6b7a7a Mon Sep 17 00:00:00 2001 From: Denys Haryachyy Date: Tue, 29 Jul 2025 17:39:29 +0300 Subject: [PATCH 171/313] linux-cp: fix multicast route updates on address add/del Ensure multicast routes are only added when the first IPv4 address is configured on an interface, and only removed when the last address is deleted. This prevents premature removal or redundant addition of multicast routes during address changes. Type: fix Change-Id: Id191cd88abfc2c4c354fceec4aaabc030ed938cc Signed-off-by: Denys Haryachyy --- src/plugins/linux-cp/lcp_router.c | 49 +++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/plugins/linux-cp/lcp_router.c b/src/plugins/linux-cp/lcp_router.c index 4c13c360e0..342cafc8d1 100644 --- a/src/plugins/linux-cp/lcp_router.c +++ b/src/plugins/linux-cp/lcp_router.c @@ -641,6 +641,51 @@ lcp_router_mk_addr46 (const struct nl_addr *rna, ip46_address_t *ia) return (fproto); } +static u32 +lcp_router_count_interface_addresses (u32 sw_if_index, u8 address_family) +{ + ip_lookup_main_t *lm = NULL; + ip_interface_address_t *ia = NULL; + u32 count = 0; + + if (address_family == AF_IP4) + lm = &ip4_main.lookup_main; + else if (address_family == AF_IP6) + lm = &ip6_main.lookup_main; + else + return 0; + + foreach_ip_interface_address (lm, ia, sw_if_index, 1 /* honor unnumbered */, + ({ + (void) ia; + count++; + })); + + return count; +} + +static void +lcp_router_update_mroutes_ip4_on_addr_change (u32 sw_if_index, int is_del) +{ + u32 count_after; + + count_after = lcp_router_count_interface_addresses (sw_if_index, AF_IP4); + + if ((!is_del && count_after == 1) || (is_del && count_after == 0)) + lcp_router_ip4_mroutes_add_del (sw_if_index, !is_del); +} + +static void +lcp_router_update_mroutes_ip6_on_addr_change (u32 sw_if_index, int is_del) +{ + u32 count_after; + + count_after = lcp_router_count_interface_addresses (sw_if_index, AF_IP6); + + if ((!is_del && count_after == 1) || (is_del && count_after == 0)) + lcp_router_ip6_mroutes_add_del (sw_if_index, !is_del); +} + static void lcp_router_link_addr_add_del (struct rtnl_addr *rla, int is_del) { @@ -659,7 +704,7 @@ lcp_router_link_addr_add_del (struct rtnl_addr *rla, int is_del) ip4_add_del_interface_address ( vlib_get_main (), sw_if_index, &ip_addr_v4 (&nh), rtnl_addr_get_prefixlen (rla), is_del); - lcp_router_ip4_mroutes_add_del (sw_if_index, !is_del); + lcp_router_update_mroutes_ip4_on_addr_change (sw_if_index, is_del); } else if (AF_IP6 == ip_addr_version (&nh)) { @@ -675,7 +720,7 @@ lcp_router_link_addr_add_del (struct rtnl_addr *rla, int is_del) ip6_add_del_interface_address ( vlib_get_main (), sw_if_index, &ip_addr_v6 (&nh), rtnl_addr_get_prefixlen (rla), is_del); - lcp_router_ip6_mroutes_add_del (sw_if_index, !is_del); + lcp_router_update_mroutes_ip6_on_addr_change (sw_if_index, is_del); } LCP_ROUTER_DBG ("link-addr: %U %U/%d", format_vnet_sw_if_index_name, From 554afb281edad3da62929c0d8dce39f05ed18e6c Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 29 Jul 2025 05:55:37 -0400 Subject: [PATCH 172/313] http: h2 free stream scheduler heads with conn Type: fix Change-Id: I1cddb7547d08a0668f6e536d77094911d9b26a6e Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index d92d52a047..4b73fca591 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -198,6 +198,8 @@ http2_conn_ctx_free (http_conn_t *hc) h2c = http2_conn_ctx_get_w_thread (hc); HTTP_DBG (1, "h2c [%u]%x", hc->c_thread_index, h2c - wrk->conn_pool); ASSERT (h2c->req_num == 0); + pool_put_index (wrk->req_pool, h2c->new_tx_streams); + pool_put_index (wrk->req_pool, h2c->old_tx_streams); hash_free (h2c->req_by_stream_id); if (hc->flags & HTTP_CONN_F_HAS_REQUEST) hpack_dynamic_table_free (&h2c->decoder_dynamic_table); From 9f9cdde5325472a3e2b0b2810120145cbc650cfc Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 29 Jul 2025 11:26:24 -0400 Subject: [PATCH 173/313] hs-test: nsim loss rate test Type: test Change-Id: I592c755c7bdc5daf422655892b171c42f19e9a7c Signed-off-by: Matus Fabian --- extras/hs-test/nsim_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 extras/hs-test/nsim_test.go diff --git a/extras/hs-test/nsim_test.go b/extras/hs-test/nsim_test.go new file mode 100644 index 0000000000..c47e83455e --- /dev/null +++ b/extras/hs-test/nsim_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "strings" + + . "fd.io/hs-test/infra" +) + +func init() { + RegisterVethTests(NsimLossTest) +} +func NsimLossTest(s *VethsSuite) { + clientVpp := s.Containers.ClientVpp.VppInstance + + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit packet-size 1400 drop-fraction 0.1")) + s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) + o := clientVpp.Vppctl("show nsim") + s.AssertNotContains(o, "nsim not enabled") + o = clientVpp.Vppctl("ping " + s.Interfaces.Server.Ip4AddressString() + " repeat 10000 interval 0.0001") + lines := strings.Split(o, "\n") + stats := lines[len(lines)-2] + s.Log(stats) + s.AssertContains(stats, "10% packet loss") +} From cfaee7741ce5602a01afa3001d0084949b9eb0ca Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 30 Jul 2025 09:23:40 -0400 Subject: [PATCH 174/313] http: format_http_transport_half_open fix with postponed ho clenup underlying transport ho session might not exist anymore Type: fix Change-Id: Iadb7f2b901d13e26f0d39bffcd11cdebc2f9357f Signed-off-by: Matus Fabian --- src/plugins/http/http.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index b387b61ae0..094260f1ed 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -1375,11 +1375,18 @@ format_http_transport_half_open (u8 *s, va_list *args) session_t *tcp_ho; ho_hc = http_ho_conn_get (ho_index); - tcp_ho = session_get_from_handle (ho_hc->hc_tc_session_handle); + tcp_ho = session_get_from_handle_if_valid (ho_hc->hc_tc_session_handle); - s = format (s, "[%d:%d][H] half-open app_wrk %u ts %d:%d", + if (tcp_ho) + s = + format (s, "[%d:%d][H] half-open app_wrk %u ts %d:%d", ho_hc->c_thread_index, ho_hc->c_s_index, ho_hc->hc_pa_wrk_index, tcp_ho->thread_index, tcp_ho->session_index); + else + s = + format (s, "[%d:%d][H] half-open app_wrk %u (postponed cleanup)", + ho_hc->c_thread_index, ho_hc->c_s_index, ho_hc->hc_pa_wrk_index); + return s; } From 3698e995de86421b29ecc238781c6f3b603d2d99 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 30 Jul 2025 12:22:41 -0400 Subject: [PATCH 175/313] hs-test: run containers wit --cap-add=SYS_PTRACE otherwise you might not be able attach gdb to vpp running in container Type: test Change-Id: I4507971123ee67939b3a843916a6d523a2a09801 Signed-off-by: Matus Fabian --- extras/hs-test/infra/container.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go index 5a3f9f3aa2..8ea694dace 100644 --- a/extras/hs-test/infra/container.go +++ b/extras/hs-test/infra/container.go @@ -130,7 +130,7 @@ func (c *Container) GetContainerWorkDir() (res string) { func (c *Container) getContainerArguments() string { args := "--ulimit nofile=90000:90000 --cap-add=NET_ADMIN --cap-add=SYS_RESOURCE " + - "--cap-add=IPC_LOCK --device /dev/net/tun:/dev/net/tun --device /dev/vhost-net:/dev/vhost-net" + "--cap-add=IPC_LOCK --cap-add=SYS_PTRACE --device /dev/net/tun:/dev/net/tun --device /dev/vhost-net:/dev/vhost-net" args += c.getVolumesAsCliOption() args += c.getEnvVarsAsCliOption() if *VppSourceFileDir != "" { @@ -198,7 +198,7 @@ func (c *Container) Create() error { }, }, }, - CapAdd: []string{"NET_ADMIN", "SYS_RESOURCE", "IPC_LOCK"}, + CapAdd: []string{"NET_ADMIN", "SYS_RESOURCE", "IPC_LOCK", "SYS_PTRACE"}, NetworkMode: "container:ginkgo", Binds: c.getVolumesAsSlice(), }, From 8eb748cfb696e5bdf298c93da44f711e049d06c6 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 28 Jul 2025 11:27:04 -0400 Subject: [PATCH 176/313] http: h2 connect for client apps Type: improvement Change-Id: If851b7802809f747b7613ba00c2ae31d944fb0d2 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 75 +++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 4b73fca591..ebe49e35cd 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1051,19 +1051,33 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, request = http_get_tx_buf (hc); control_data.method = msg.method_type; - control_data.parsed_bitmap = HPACK_PSEUDO_HEADER_SCHEME_PARSED; - control_data.scheme = HTTP_URL_SCHEME_HTTPS; - control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; - control_data.path = http_get_app_target (&req->base, &msg); - control_data.path_len = msg.data.target_path_len; - control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; - control_data.authority = hc->host; - control_data.authority_len = vec_len (hc->host); + control_data.parsed_bitmap = HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + if (msg.method_type == HTTP_REQ_CONNECT) + { + req->base.is_tunnel = 1; + control_data.authority = http_get_app_target (&req->base, &msg); + control_data.authority_len = msg.data.target_path_len; + HTTP_DBG (1, "opening tunnel to %U", format_http_bytes, + control_data.authority, control_data.authority_len); + } + else + { + control_data.authority = hc->host; + control_data.authority_len = vec_len (hc->host); + control_data.parsed_bitmap = HPACK_PSEUDO_HEADER_SCHEME_PARSED; + control_data.scheme = + http_get_transport_proto (hc) == TRANSPORT_PROTO_TLS ? + HTTP_URL_SCHEME_HTTPS : + HTTP_URL_SCHEME_HTTP; + control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; + control_data.path = http_get_app_target (&req->base, &msg); + control_data.path_len = msg.data.target_path_len; + HTTP_DBG (1, "%U %U", format_http_method, control_data.method, + format_http_bytes, control_data.path, control_data.path_len); + } control_data.user_agent = hc->app_name; control_data.user_agent_len = vec_len (hc->app_name); - HTTP_DBG (1, "%U %U", format_http_method, control_data.method, - format_http_bytes, control_data.path, control_data.path_len); if (msg.data.body_len) { control_data.content_len = msg.data.body_len; @@ -1072,7 +1086,7 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, else { control_data.content_len = HPACK_ENCODER_SKIP_CONTENT_LEN; - flags |= HTTP2_FRAME_FLAG_END_STREAM; + flags |= req->base.is_tunnel ? 0 : HTTP2_FRAME_FLAG_END_STREAM; } if (msg.data.headers_len) @@ -1241,6 +1255,7 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, http_msg_t msg; int rv; http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); + http_req_state_t new_state = HTTP_REQ_STATE_WAIT_APP_METHOD; h2c = http2_conn_ctx_get_w_thread (hc); @@ -1270,7 +1285,15 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, return HTTP_SM_STOP; } - if (control_data.content_len_header_index != ~0) + if (req->base.is_tunnel && + http_status_code_str[req->base.status_code][0] == '2') + { + new_state = HTTP_REQ_STATE_TUNNEL; + http_io_as_add_want_read_ntf (&req->base); + /* cleanup some stuff we don't need anymore in tunnel mode */ + vec_free (req->base.headers); + } + else if (control_data.content_len_header_index != ~0) { req->base.content_len_header_index = control_data.content_len_header_index; @@ -1280,10 +1303,20 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, http2_stream_error (hc, req, HTTP2_ERROR_PROTOCOL_ERROR, sp); return HTTP_SM_STOP; } + new_state = HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA; + http_io_as_add_want_read_ntf (&req->base); } + else + { + /* we are done wait for the next app request */ + transport_connection_reschedule (&req->base.connection); + http2_conn_reset_req (h2c, req, hc->c_thread_index); + } + /* TODO: message framing without content length using END_STREAM flag */ if (req->base.body_len == 0 && - req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED) + req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED && + !req->base.is_tunnel) { HTTP_DBG (1, "no content-length and DATA frame expected"); *error = HTTP2_ERROR_INTERNAL_ERROR; @@ -1306,22 +1339,8 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, HTTP_DBG (3, "%U", format_http_bytes, wrk->header_list, req->base.control_data_len); http_io_as_write_segs (&req->base, segs, 2); - - if (req->base.body_len) - { - http_req_state_change (&req->base, - HTTP_REQ_STATE_TRANSPORT_IO_MORE_DATA); - http_io_as_add_want_read_ntf (&req->base); - } - else - { - /* we are done wait for the next app request */ - http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); - transport_connection_reschedule (&req->base.connection); - http2_conn_reset_req (h2c, req, hc->c_thread_index); - } - http_app_worker_rx_notify (&req->base); + http_req_state_change (&req->base, new_state); return HTTP_SM_STOP; } From 8be938f5cc3ce6c3d4f79983f67d01630564c374 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 30 Jul 2025 11:00:27 -0400 Subject: [PATCH 177/313] http: h2 connect-udp for client apps Type: improvement Change-Id: Ib02f8494fe26be0abae8ceb4eba50866a2b08de1 Signed-off-by: Matus Fabian --- src/plugins/http/http2/hpack.c | 5 ++++ src/plugins/http/http2/http2.c | 48 ++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index b94e8ebc22..0dd68b6a5b 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -1457,6 +1457,11 @@ hpack_serialize_request (u8 *app_headers, u32 app_headers_len, if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED) p = hpack_encode_path (p, control_data->path, control_data->path_len); + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_PROTOCOL_PARSED) + p = hpack_encode_custom_header (p, (u8 *) ":protocol", 9, + control_data->protocol, + control_data->protocol_len); + p = hpack_encode_authority (p, control_data->authority, control_data->authority_len); diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index ebe49e35cd..880d31bbaf 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -133,6 +133,12 @@ typedef enum #define HTTP2_SCHED_MAX_EMISSIONS 32 +static http_token_t http2_ext_connect_proto[] = { { http_token_lit ("bug") }, +#define _(sym, str) { http_token_lit (str) }, + foreach_http_upgrade_proto +#undef _ +}; + static http2_main_t http2_main; static_always_inline http2_worker_ctx_t * @@ -1055,10 +1061,38 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, if (msg.method_type == HTTP_REQ_CONNECT) { req->base.is_tunnel = 1; - control_data.authority = http_get_app_target (&req->base, &msg); - control_data.authority_len = msg.data.target_path_len; - HTTP_DBG (1, "opening tunnel to %U", format_http_bytes, - control_data.authority, control_data.authority_len); + req->dispatch_data_cb = http2_sched_dispatch_tunnel; + if (msg.data.upgrade_proto != HTTP_UPGRADE_PROTO_NA) + { + if (hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + req->dispatch_data_cb = http2_sched_dispatch_udp_tunnel; + control_data.authority = hc->host; + control_data.authority_len = vec_len (hc->host); + control_data.parsed_bitmap = HPACK_PSEUDO_HEADER_SCHEME_PARSED; + control_data.scheme = + http_get_transport_proto (hc) == TRANSPORT_PROTO_TLS ? + HTTP_URL_SCHEME_HTTPS : + HTTP_URL_SCHEME_HTTP; + control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; + control_data.path = http_get_app_target (&req->base, &msg); + control_data.path_len = msg.data.target_path_len; + control_data.parsed_bitmap |= HPACK_PSEUDO_HEADER_PROTOCOL_PARSED; + control_data.protocol = + (u8 *) http2_ext_connect_proto[msg.data.upgrade_proto].base; + control_data.protocol_len = + http2_ext_connect_proto[msg.data.upgrade_proto].len; + HTTP_DBG (1, "extended connect %s %U", + http2_ext_connect_proto[msg.data.upgrade_proto].base, + format_http_bytes, control_data.path, + control_data.path_len); + } + else + { + control_data.authority = http_get_app_target (&req->base, &msg); + control_data.authority_len = msg.data.target_path_len; + HTTP_DBG (1, "opening tunnel to %U", format_http_bytes, + control_data.authority, control_data.authority_len); + } } else { @@ -1288,7 +1322,11 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, if (req->base.is_tunnel && http_status_code_str[req->base.status_code][0] == '2') { - new_state = HTTP_REQ_STATE_TUNNEL; + if (req->base.upgrade_proto == HTTP_UPGRADE_PROTO_CONNECT_UDP && + hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + new_state = HTTP_REQ_STATE_UDP_TUNNEL; + else + new_state = HTTP_REQ_STATE_TUNNEL; http_io_as_add_want_read_ntf (&req->base); /* cleanup some stuff we don't need anymore in tunnel mode */ vec_free (req->base.headers); From a43464a84fb44523d1aedb2c2f7ea21979438342 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 1 Aug 2025 06:13:44 -0400 Subject: [PATCH 178/313] hs-test: PromMemLeakTest improvement do warmup with same nuber of requests and sleep before main test section to prevent false positives Type: test Change-Id: Ie0ffbb27be58693f0c35c9a7ffed216ec4be1cb4 Signed-off-by: Matus Fabian --- extras/hs-test/http1_test.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/extras/hs-test/http1_test.go b/extras/hs-test/http1_test.go index 045109c895..6e0f9d7a80 100644 --- a/extras/hs-test/http1_test.go +++ b/extras/hs-test/http1_test.go @@ -904,8 +904,14 @@ func PromMemLeakTest(s *Http1Suite) { s.Log(vpp.Vppctl("prom enable")) time.Sleep(time.Second * 3) - /* warmup request (FIB) */ - promReq(s, url, defaultHttpTimeout) + /* warmup requests (FIB, pool allocations) */ + for i := 0; i < 5; i++ { + time.Sleep(time.Second * 1) + promReq(s, url, defaultHttpTimeout) + } + + /* let's give it some time to clean up sessions, so pool elements can be reused and we have less noise */ + time.Sleep(time.Second * 12) vpp.EnableMemoryTrace() traces1, err := vpp.GetMemoryTrace() @@ -918,7 +924,7 @@ func PromMemLeakTest(s *Http1Suite) { } /* let's give it some time to clean up sessions */ - time.Sleep(time.Second * 5) + time.Sleep(time.Second * 12) traces2, err := vpp.GetMemoryTrace() s.AssertNil(err, fmt.Sprint(err)) From a5ca1f955fbee091bda35bfc00fb0ebc78d70129 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sun, 20 Jul 2025 20:57:29 -0400 Subject: [PATCH 179/313] session tls: scaffolding for async cert retrieval Basic experimental infrastructure for server async retrieval. Type: improvement Change-Id: Iec48a0a30e5968a42237b810ec5e6c4e9d633728 Signed-off-by: Florin Coras --- src/plugins/tlsopenssl/tls_openssl.c | 185 ++++++++++++++++++++--- src/plugins/tlsopenssl/tls_openssl.h | 1 + src/vnet/session/application.c | 5 + src/vnet/session/application.h | 9 ++ src/vnet/session/application_crypto.c | 89 ++++++++++- src/vnet/session/application_crypto.h | 86 +++++++++++ src/vnet/session/application_interface.h | 3 + src/vnet/session/transport_types.h | 6 +- src/vnet/tls/tls.h | 3 +- 9 files changed, 359 insertions(+), 28 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 7305fe6afd..08ae12a1f1 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -953,29 +953,42 @@ openssl_alpn_select_cb (SSL *ssl, const unsigned char **out, static int openssl_start_listen (tls_ctx_t * lctx) { + u64 flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; + openssl_main_t *om = &openssl_main; const SSL_METHOD *method; SSL_CTX *ssl_ctx; int rv; u32 olc_index; openssl_listen_ctx_t *olc; app_cert_key_pair_t *ckpair; - app_certkey_int_ctx_t *cki; + app_certkey_int_ctx_t *cki = 0; - long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; - openssl_main_t *om = &openssl_main; + if (lctx->ckpair_index) + { + ckpair = app_cert_key_pair_get_if_valid (lctx->ckpair_index); + if (!ckpair) + return -1; - ckpair = app_cert_key_pair_get_if_valid (lctx->ckpair_index); - if (!ckpair) - return -1; + if (!ckpair->cert || !ckpair->key) + { + TLS_DBG (1, "tls cert and/or key not configured %d", + lctx->parent_app_wrk_index); + return -1; + } - if (!ckpair->cert || !ckpair->key) - { - TLS_DBG (1, "tls cert and/or key not configured %d", - lctx->parent_app_wrk_index); - return -1; + cki = app_certkey_get_int_ctx (ckpair, lctx->c_thread_index); + if (!cki || !cki->cert) + { + cki = openssl_init_certkey_init_ctx (ckpair, lctx->c_thread_index); + if (!cki) + { + clib_warning ("unable to initialize certificate/key pair"); + return -1; + } + } } - method = lctx->tls_type == TRANSPORT_PROTO_TLS ? SSLv23_server_method () : + method = lctx->tls_type == TRANSPORT_PROTO_TLS ? TLS_server_method () : DTLS_server_method (); ssl_ctx = SSL_CTX_new (method); if (!ssl_ctx) @@ -1046,22 +1059,18 @@ openssl_start_listen (tls_ctx_t * lctx) /* * Set the key and cert */ - cki = app_certkey_get_int_ctx (ckpair, lctx->c_thread_index); - if (!cki || !cki->cert) + if (cki) { - cki = openssl_init_certkey_init_ctx (ckpair, lctx->c_thread_index); - if (!cki) + rv = SSL_CTX_use_certificate (ssl_ctx, cki->cert); + if (rv != 1) { - clib_warning ("unable to initialize certificate/key pair"); - return -1; + clib_warning ("unable to use SSL certificate"); + goto err; } } - - rv = SSL_CTX_use_certificate (ssl_ctx, cki->cert); - if (rv != 1) + else { - clib_warning ("unable to use SSL certificate"); - goto err; + lctx->flags |= TLS_CONN_F_ASYNC_CERT; } rv = SSL_CTX_use_PrivateKey (ssl_ctx, cki->key); @@ -1103,6 +1112,126 @@ openssl_stop_listen (tls_ctx_t * lctx) return 0; } +static void +openssl_server_async_cert_cb (app_crypto_async_reply_t *reply) +{ + app_crypto_async_req_handle_t handle = reply->handle; + clib_thread_index_t thread_index; + app_cert_key_pair_t *ckpair; + app_certkey_int_ctx_t *cki; + openssl_ctx_t *oc; + tls_ctx_t *ctx; + + thread_index = handle.thread_index; + ASSERT (thread_index == vlib_get_thread_index ()); + ctx = openssl_ctx_get_w_thread (handle.opaque & TLS_IDX_MASK, + handle.thread_index); + oc = (openssl_ctx_t *) ctx; + + ckpair = app_cert_key_pair_get_if_valid (reply->async_cert.ckpair_index); + if (!ckpair || !ckpair->cert || !ckpair->key) + { + TLS_DBG (1, "Invalid certificate/key pair %u", ckpair_index); + goto error; + } + + /* Get or initialize certificate context */ + cki = app_certkey_get_int_ctx (ckpair, thread_index); + if (!cki || !cki->cert) + { + cki = openssl_init_certkey_init_ctx (ckpair, thread_index); + if (!cki) + { + TLS_DBG (1, "Failed to initialize certificate context"); + goto error; + } + } + + /* Set the certificate and private key */ + if (SSL_use_certificate (oc->ssl, cki->cert) != 1) + { + TLS_DBG (1, "Failed to set certificate"); + goto error; + } + + if (SSL_use_PrivateKey (oc->ssl, cki->key) != 1) + { + TLS_DBG (1, "Failed to set private key"); + goto error; + } + + /* Verify that the private key matches the certificate */ + if (SSL_check_private_key (oc->ssl) != 1) + { + TLS_DBG (1, "Private key does not match certificate"); + goto error; + } + + TLS_DBG (2, "Successfully set certificate for server %s", + SSL_get_servername (oc->ssl, TLSEXT_NAMETYPE_host_name)); + + /* Continue handshake */ + while (1) + { + int rv, err; + rv = SSL_do_handshake (oc->ssl); + err = SSL_get_error (oc->ssl, rv); + if (openssl_main.async && err == SSL_ERROR_WANT_ASYNC) + break; + + if (err != SSL_ERROR_WANT_WRITE) + break; + } + + return; + +error: + /* Do not free ctx yet, in case we have pending rx events */ + ctx->flags |= TLS_CONN_F_NO_APP_SESSION; + tls_disconnect_transport (ctx); +} + +static int +openssl_server_cert_callback (SSL *ssl, void *arg) +{ + u32 ctx_index = (u32) pointer_to_uword (arg); + tls_ctx_t *ctx = openssl_ctx_get (ctx_index); + openssl_ctx_t *oc = (openssl_ctx_t *) ctx; + const char *servername; + + TLS_DBG (2, "Server certificate callback invoked for ctx %u", + oc->openssl_ctx_index); + + /* Get the requested server name from SNI extension */ + servername = SSL_get_servername (ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) + { + /* TODO maybe handle using default cert, if provided */ + TLS_DBG (1, "No SNI provided"); + return 0; + } + + TLS_DBG (2, "SNI requested server name: %s", servername); + + app_crypto_async_req_t req = { .req_type = APP_CRYPTO_ASYNC_REQ_TYPE_CERT, + .handle = { + .thread_index = ctx->c_thread_index, + .opaque = ctx->tls_ctx_handle, + }, + .cb = openssl_server_async_cert_cb, + .app_wrk_index = ctx->parent_app_wrk_index, + .async_cert = { + .servername = (const u8 *) servername, + } }; + + oc->req_ticket = app_crypto_async_req (&req); + if (oc->req_ticket.as_u64 == APP_CRYPTO_ASYNC_INVALID_TICKET.as_u64) + return 0; + + /* Certificate selection in progress */ + return -1; +} + static int openssl_ctx_init_server (tls_ctx_t * ctx) { @@ -1145,6 +1274,16 @@ openssl_ctx_init_server (tls_ctx_t * ctx) openssl_ctx_handshake_rx (ctx, tls_session); } + /* Set up certificate callback for async certificate retrieval */ + if (ctx->flags & TLS_CONN_F_ASYNC_CERT) + { + uword oc_index = oc->openssl_ctx_index; + TLS_DBG (2, "Set async cert callback for ctx %u", oc_index); + SSL_set_cert_cb (oc->ssl, openssl_server_cert_callback, + uword_to_pointer (oc_index, void *)); + return 0; + } + while (1) { rv = SSL_do_handshake (oc->ssl); diff --git a/src/plugins/tlsopenssl/tls_openssl.h b/src/plugins/tlsopenssl/tls_openssl.h index f18b579bdb..bf3440120d 100644 --- a/src/plugins/tlsopenssl/tls_openssl.h +++ b/src/plugins/tlsopenssl/tls_openssl.h @@ -59,6 +59,7 @@ typedef struct tls_ctx_openssl_ tls_async_ctx_t async_ctx; BIO *rbio; BIO *wbio; + app_crypto_async_req_ticket_t req_ticket; } openssl_ctx_t; typedef struct tls_listen_ctx_opensl_ diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index 2d1b58dbe6..c27e58efcb 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -873,6 +873,8 @@ application_alloc_and_init (app_init_args_t *a) else application_name_table_add (app); + app_crypto_ctx_init (&app->crypto_ctx); + a->app_index = app->app_index; APP_DBG ("New app name: %v api index: %u index %u", app->name, @@ -925,6 +927,9 @@ application_free (application_t * app) */ if (application_is_builtin (app)) application_name_table_del (app); + + app_crypto_ctx_free (&app->crypto_ctx); + vec_free (app->name); pool_put (app_main.app_pool, app); } diff --git a/src/vnet/session/application.h b/src/vnet/session/application.h index 92c182f63b..b16e42dcad 100644 --- a/src/vnet/session/application.h +++ b/src/vnet/session/application.h @@ -169,8 +169,17 @@ typedef struct application_ /** collector index, if any */ u32 evt_collector_index; + + /** app crypto state */ + app_crypto_ctx_t crypto_ctx; } application_t; +static inline app_crypto_wrk_t * +app_crypto_wrk_get (application_t *app, clib_thread_index_t thread_index) +{ + return vec_elt_at_index (app->crypto_ctx.wrk, thread_index); +} + typedef struct app_rx_mq_handle_ { union diff --git a/src/vnet/session/application_crypto.c b/src/vnet/session/application_crypto.c index b15799c89e..c8d26670f6 100644 --- a/src/vnet/session/application_crypto.c +++ b/src/vnet/session/application_crypto.c @@ -108,6 +108,93 @@ vnet_app_del_cert_key_pair (u32 index) return 0; } +app_crypto_async_req_ticket_t +app_crypto_async_req (app_crypto_async_req_t *areq) +{ + app_crypto_async_req_t *req; + app_crypto_wrk_t *crypto_wrk; + app_worker_t *app_wrk; + application_t *app; + app_crypto_async_req_ticket_t ticket; + + app_wrk = app_worker_get (areq->app_wrk_index); + app = application_get (app_wrk->app_index); + if (!app->cb_fns.app_crypto_async) + return APP_CRYPTO_ASYNC_INVALID_TICKET; + + crypto_wrk = app_crypto_wrk_get (app, areq->handle.thread_index); + + /* TODO(fcoras) caching layer */ + + pool_get (crypto_wrk->reqs, req); + *req = *areq; + req->req_index = req - crypto_wrk->reqs; + req->cancelled = 0; + ticket.app_index = app->app_index; + ticket.req_index = req->req_index; + + /* Hand over request to app */ + if (app->cb_fns.app_crypto_async (req)) + return APP_CRYPTO_ASYNC_INVALID_TICKET; + + return ticket; +} + +void +app_crypto_async_cancel_req (app_crypto_async_req_ticket_t ticket) +{ + application_t *app = application_get (ticket.app_index); + clib_thread_index_t thread_index = vlib_get_thread_index (); + app_crypto_async_req_t *req; + app_crypto_wrk_t *crypto_wrk; + + crypto_wrk = app_crypto_wrk_get (app, thread_index); + + if (pool_is_free_index (crypto_wrk->reqs, ticket.req_index)) + return; + req = pool_elt_at_index (crypto_wrk->reqs, ticket.req_index); + req->cancelled = 1; +} + +void +app_crypto_async_reply (app_crypto_async_reply_t *reply) +{ + application_t *app = application_get (reply->app_index); + clib_thread_index_t thread_index = reply->handle.thread_index; + app_crypto_wrk_t *crypto_wrk; + app_crypto_async_req_t *req; + + ASSERT (thread_index == vlib_get_thread_index ()); + + crypto_wrk = app_crypto_wrk_get (app, thread_index); + req = pool_elt_at_index (crypto_wrk->reqs, reply->req_index); + + if (req->cancelled) + goto done; + + reply->handle = req->handle; + req->cb (reply); + +done: + pool_put (crypto_wrk->reqs, req); +} + +void +app_crypto_ctx_init (app_crypto_ctx_t *crypto_ctx) +{ + vec_validate (crypto_ctx->wrk, vlib_num_workers ()); +} + +void +app_crypto_ctx_free (app_crypto_ctx_t *crypto_ctx) +{ + app_crypto_wrk_t *crypto_wrk; + + vec_foreach (crypto_wrk, crypto_ctx->wrk) + pool_free (crypto_wrk->reqs); + vec_free (crypto_ctx->wrk); +} + u8 * format_cert_key_pair (u8 *s, va_list *args) { @@ -199,7 +286,7 @@ application_crypto_init () { app_crypto_main_t *acm = &app_crypto_main; - /* Index 0 was originally used by legacy apis, maintain as invalid */ + /* Index 0 is invalid, used to indicate that no cert was provided */ app_cert_key_pair_alloc (); acm->last_crypto_engine = CRYPTO_ENGINE_LAST; diff --git a/src/vnet/session/application_crypto.h b/src/vnet/session/application_crypto.h index f0587eaf38..743c3592fe 100644 --- a/src/vnet/session/application_crypto.h +++ b/src/vnet/session/application_crypto.h @@ -56,6 +56,88 @@ typedef struct crypto_ctx_ void *data; /**< protocol specific data */ } crypto_context_t; +typedef union +{ + struct + { + u32 app_index; + u32 req_index; + }; + u64 as_u64; +} app_crypto_async_req_ticket_t; + +#define APP_CRYPTO_ASYNC_INVALID_TICKET \ + ((app_crypto_async_req_ticket_t){ { .app_index = ~0, .req_index = ~0 } }) + +typedef union +{ + struct + { + u32 opaque; /**< opaque metadata */ + clib_thread_index_t thread_index; /**< thread on which req was made */ + }; + u64 handle; +} app_crypto_async_req_handle_t; + +struct app_crypto_async_reply_; + +typedef void (*app_crypto_async_req_cb) ( + struct app_crypto_async_reply_ *reply); + +#define foreach_app_crypto_async_req_type _ (CERT, "async-cert") + +typedef enum app_crypto_req_type_ +{ +#define _(a, b) APP_CRYPTO_ASYNC_REQ_TYPE_##a, + foreach_app_crypto_async_req_type +#undef _ +} app_crypto_async_req_type_t; + +typedef struct app_crypto_async_req_ +{ + app_crypto_async_req_type_t req_type; /**< request type */ + app_crypto_async_req_handle_t handle; /**< async request handle */ + app_crypto_async_req_cb cb; /**< callback to invoke on completion */ + u32 app_wrk_index; /**< application worker index */ + u32 req_index; /**< index in crypto worker's request pool */ + u8 cancelled; /**< flag to indicate if cancelled */ + union + { + struct + { + const u8 *servername; /**< server name for SNI */ + } async_cert; /**< async cert request data */ + }; +} app_crypto_async_req_t; + +typedef struct app_crypto_async_reply_ +{ + u32 app_index; /**< app that resolved the request */ + u32 req_index; /**< request index in app crypto pool */ + app_crypto_async_req_handle_t handle; /**< request handle */ + app_crypto_async_req_type_t req_type; /**< request type */ + union + { + struct + { + u32 ckpair_index; /**< certificate key-pair index */ + } async_cert; /**< async cert reply data */ + }; +} app_crypto_async_reply_t; + +typedef struct app_crypto_wrk_ +{ + app_crypto_async_req_t *reqs; +} app_crypto_wrk_t; + +typedef struct app_crypto_ctx_ +{ + app_crypto_wrk_t *wrk; +} app_crypto_ctx_t; + +void app_crypto_ctx_init (app_crypto_ctx_t *crypto_ctx); +void app_crypto_ctx_free (app_crypto_ctx_t *crypto_ctx); + /* * Certificate key-pair management */ @@ -86,6 +168,10 @@ app_certkey_alloc_int_ctx (app_cert_key_pair_t *ck, return vec_elt_at_index (ck->cki, thread_index); } +app_crypto_async_req_ticket_t +app_crypto_async_req (app_crypto_async_req_t *req); +void app_crypto_async_cancel_req (app_crypto_async_req_ticket_t ticket); + /* * Crypto engine management */ diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index b1569f076f..2896392376 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -78,6 +78,9 @@ typedef struct session_cb_vft_ /** Collect and export session logs */ int (*app_evt_callback) (session_t *s); + + /** Callback to handle async crypto requests */ + int (*app_crypto_async) (app_crypto_async_req_t *req); } session_cb_vft_t; #define foreach_app_init_args \ diff --git a/src/vnet/session/transport_types.h b/src/vnet/session/transport_types.h index 0e41293943..5088303369 100644 --- a/src/vnet/session/transport_types.h +++ b/src/vnet/session/transport_types.h @@ -290,10 +290,10 @@ typedef enum transport_endpt_ext_cfg_type_ typedef struct transport_endpt_crypto_cfg_ { - u32 ckpair_index; + u32 ckpair_index; /**< index of ck pair in application crypto layer */ u8 alpn_protos[4]; /**< ordered by preference for server */ - u8 crypto_engine; - u8 hostname[256]; /**< full domain len is 255 as per rfc 3986 */ + u8 crypto_engine; /**< crypto engine requested */ + u8 hostname[256]; /**< full domain len is 255 as per rfc 3986 */ } transport_endpt_crypto_cfg_t; typedef struct transport_endpt_ext_cfg_ diff --git a/src/vnet/tls/tls.h b/src/vnet/tls/tls.h index 04b5d75949..3ee00667e2 100644 --- a/src/vnet/tls/tls.h +++ b/src/vnet/tls/tls.h @@ -82,7 +82,8 @@ STATIC_ASSERT (sizeof (tls_ctx_id_t) <= TRANSPORT_CONN_ID_LEN, _ (RESUME, "resume") \ _ (HS_DONE, "handshake-done") \ _ (ASYNC_RD, "async-read") \ - _ (SHUTDOWN_TRANSPORT, "shutdown-transport") + _ (SHUTDOWN_TRANSPORT, "shutdown-transport") \ + _ (ASYNC_CERT, "async-cert") typedef enum tls_conn_flags_bit_ { From 0ab1d2edf73393948173f13db1188168036be845 Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Fri, 1 Aug 2025 15:26:49 -0700 Subject: [PATCH 180/313] tcp: conditionally initializing log track for unbind elog track is initialized when tcp open, bind, or syn rcvd message is received. Logging tcp unbind event requires and expects elog track is already initialized. If tcp debug is enabled after the aforementioned 3 messages and the first event that tcp logs is unbind, we crash because elog track is not initialized. Let's check the elog track is initialized yet or not prior to logging an unbind event. If not, we initialize it. Type: fix Change-Id: Ib8fde694dc48f30a87aa3ecd378c1960857e8c38 Signed-off-by: Steven Luong --- src/vnet/tcp/tcp_debug.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vnet/tcp/tcp_debug.h b/src/vnet/tcp/tcp_debug.h index 04e921cd60..8ae4565500 100644 --- a/src/vnet/tcp/tcp_debug.h +++ b/src/vnet/tcp/tcp_debug.h @@ -289,17 +289,17 @@ ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, _e, \ TCP_EVT_STATE_CHANGE_HANDLER(_tc); \ } -#define TCP_EVT_UNBIND_HANDLER(_tc, ...) \ -{ \ - TCP_EVT_DEALLOC_HANDLER(_tc); \ - ELOG_TYPE_DECLARE (_e) = \ - { \ - .format = "unbind: listener %d", \ - }; \ - TCP_DECLARE_ETD(_tc, _e, 1); \ - ed->data[0] = _tc->c_c_index; \ - TCP_EVT_DEALLOC_HANDLER(_tc); \ -} +#define TCP_EVT_UNBIND_HANDLER(_tc, ...) \ + { \ + if (_tc->c_elog_track.name == 0) \ + TCP_EVT_INIT_HANDLER (_tc, 1); \ + ELOG_TYPE_DECLARE (_e) = { \ + .format = "unbind: listener %d", \ + }; \ + TCP_DECLARE_ETD (_tc, _e, 1); \ + ed->data[0] = _tc->c_c_index; \ + TCP_EVT_DEALLOC_HANDLER (_tc); \ + } #define TCP_EVT_DELETE_HANDLER(_tc, ...) \ { \ From 28253d97157cec75dba7b74cb2574ce11e8937f6 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 1 Aug 2025 19:06:04 -0400 Subject: [PATCH 181/313] hs-test: glean proxy environment variables is present Type: test Change-Id: I2dc9136b12444f0fd2ef725b2d778a54538e5b17 Signed-off-by: Florin Coras --- extras/hs-test/Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 4d33529959..b264795d23 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -93,6 +93,7 @@ DOCKER_DEVICES:=--device /dev/vhost-net:/dev/vhost-net --device /dev/net/tun:/de DOCKER_VOLUMES:=-v $(WS_ROOT):$(WS_ROOT) -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/hs-test:/tmp/hs-test \ -v /etc/localtime:/etc/localtime:ro $(CORE_VOLUME) -v $(HS_ROOT)/.go_cache/mod:/root/go/pkg/mod \ -v $(HS_ROOT)/.go_cache/build:/root/.cache/go-build +DOCKER_PROXY:=-e HTTP_PROXY=$(HTTP_PROXY) -e HTTPS_PROXY=$(HTTPS_PROXY) -e NO_PROXY=$(NO_PROXY) .PHONY: help help: @@ -177,7 +178,7 @@ build-vpp-gcov: build-msg .PHONY: test test: FORCE_BUILD=false test: .deps.ok .build.ok - docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) $(DOCKER_PROXY)\ -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ .$(HS_ROOT)/hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ @@ -189,7 +190,7 @@ test: .deps.ok .build.ok .PHONY: test-debug test-debug: FORCE_BUILD=false test-debug: .deps.ok .build_debug.ok - docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) $(DOCKER_PROXY)\ -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ .$(HS_ROOT)/hs_test.sh --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST) --cpus=$(CPUS) \ @@ -205,7 +206,7 @@ wipe-lcov: .PHONY: test-cov test-cov: FORCE_BUILD=false test-cov: .deps.ok .build.cov.ok wipe-lcov - -docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + -docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) $(DOCKER_PROXY)\ -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ .$(HS_ROOT)/hs_test.sh --coverage=true --persist=$(PERSIST) --verbose=$(VERBOSE) \ --unconfigure=$(UNCONFIGURE) --debug=$(DEBUG) --test=$(TEST-HS) --cpus=$(CPUS) \ @@ -217,7 +218,7 @@ test-cov: .deps.ok .build.cov.ok wipe-lcov .PHONY: test-leak test-leak: FORCE_BUILD=false test-leak: .deps.ok .build_debug.ok - docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) \ + docker run $(DOCKER_TTY) --rm $(DOCKER_CAPABILITIES) $(DOCKER_DEVICES) $(DOCKER_PROXY)\ -e BUILD_NUMBER=$(BUILD_NUMBER) $(DOCKER_VOLUMES) --name ginkgo hs-test/ginkgo \ .$(HS_ROOT)/hs_test.sh --test=$(TEST) --debug_build=true --leak_check=true --vppsrc=$(VPPSRC) --timeout=$(TIMEOUT) \ --ginkgo_timeout=$(GINKGO_TIMEOUT) --vpp_cpus=$(VPP_CPUS) --hs_root=$(HS_ROOT); From 80a5d48c767a9890b64954b6aa4bff41db4117c2 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 1 Aug 2025 19:32:40 -0400 Subject: [PATCH 182/313] tcp: fix persist handler in closed state Do not rechedule connection if persist handler is called after connection is closed. Type: fix Change-Id: Ibe7c3924c6fb107a36e4d459f32a39e49a0b7ae1 Signed-off-by: Florin Coras --- src/vnet/tcp/tcp_output.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vnet/tcp/tcp_output.c b/src/vnet/tcp/tcp_output.c index 120ad6c533..d68c86b233 100644 --- a/src/vnet/tcp/tcp_output.c +++ b/src/vnet/tcp/tcp_output.c @@ -1536,9 +1536,11 @@ tcp_timer_persist_handler (tcp_connection_t * tc) tcp_worker_stats_inc (wrk, to_persist, 1); + if (tc->state == TCP_STATE_CLOSED) + return; + /* Problem already solved or worse */ - if (tc->state == TCP_STATE_CLOSED || tc->snd_wnd > tc->snd_mss - || (tc->flags & TCP_CONN_FINSNT)) + if (tc->snd_wnd > tc->snd_mss || (tc->flags & TCP_CONN_FINSNT)) goto update_scheduler; available_bytes = transport_max_tx_dequeue (&tc->connection); From 513c6bf8f1e53fde08a6bcbaf82d09f1fc48d9aa Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Wed, 2 Jul 2025 10:48:11 +0000 Subject: [PATCH 183/313] pg: add support for checksum offload Type: improvement This patch adds support for checksum offload. There has been also added show packet-generator interface cli. Change-Id: I55462df45ea54b577c110e1cc4e3512d70bcfa90 Signed-off-by: Mohsin Kazmi --- src/vnet/pg/cli.c | 80 +++++++++++++++++++++++++++++++++++++++- src/vnet/pg/input.c | 21 +++++++---- src/vnet/pg/output.c | 35 +++++++++++++++++- src/vnet/pg/pg.api | 29 +++++++++++++++ src/vnet/pg/pg.h | 30 +++++++++++++++ src/vnet/pg/pg_api.c | 30 +++++++++++++++ src/vnet/pg/stream.c | 74 +++++++++++++++++++++++++++++++++---- test/framework.py | 24 +++++++----- test/test_gso.py | 12 ++++-- test/test_pg.py | 80 ++++++++++++++++++++++++++++++++++++---- test/vpp_pg_interface.py | 18 ++++++++- 11 files changed, 392 insertions(+), 41 deletions(-) diff --git a/src/vnet/pg/cli.c b/src/vnet/pg/cli.c index 147824394a..da10884574 100644 --- a/src/vnet/pg/cli.c +++ b/src/vnet/pg/cli.c @@ -382,7 +382,13 @@ new_stream (vlib_main_t * vm, else if (unformat (input, "buffer-flags %U", unformat_vnet_buffer_flags, &s.buffer_flags)) - ; + { + if (s.buffer_flags & VNET_BUFFER_F_GSO) + { + if (unformat (input, "gso-size %u", &s.gso_size)) + ; + } + } else if (unformat (input, "buffer-offload-flags %U", unformat_vnet_buffer_offload_flags, &s.buffer_oflags)) ; @@ -694,6 +700,8 @@ create_pg_if_cmd_fn (vlib_main_t * vm, goto done; } } + else if (unformat (line_input, "csum-offload-enabled")) + args.flags |= PG_INTERFACE_FLAG_CSUM_OFFLOAD; else if (unformat (line_input, "hw-addr %U", unformat_ethernet_address, args.hw_addr.bytes)) args.hw_addr_set = 1; @@ -722,7 +730,7 @@ VLIB_CLI_COMMAND (create_pg_if_cmd, static) = { .short_help = "create packet-generator interface " " [hw-addr ] [gso-enabled gso-size [coalesce-enabled]]" - " [mode ]", + " [csum-offload-enabled] [mode ]", .function = create_pg_if_cmd_fn, }; @@ -773,6 +781,74 @@ VLIB_CLI_COMMAND (delete_pg_if_cmd, static) = { .function = delete_pg_if_cmd_fn, }; +static u8 * +format_pg_interface_mode (u8 *s, va_list *va) +{ + pg_interface_mode_t mode = va_arg (*va, pg_interface_mode_t); + + if (mode == PG_MODE_IP4) + s = format (s, "ip4"); + else if (mode == PG_MODE_IP6) + s = format (s, "ip6"); + else // mode == PG_MODE_ETHERNET + s = format (s, "ethernet"); + return s; +} + +static clib_error_t * +show_pg_if_cmd_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 sw_if_index = ~0; + pg_interface_details_t pid = { 0 }; + int rv = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "sw_if_index %d", &sw_if_index)) + ; + else if (unformat (input, "%U", unformat_vnet_sw_interface, vnm, + &sw_if_index)) + ; + else + { + return clib_error_create ("unknown input `%U'", + format_unformat_error, input); + } + } + + if (sw_if_index == ~0) + return clib_error_return (0, + "please specify interface name or sw_if_index"); + + rv = pg_interface_details (sw_if_index, &pid); + if (rv == VNET_API_ERROR_INVALID_SW_IF_INDEX) + return clib_error_return (0, "not a pg interface"); + + vlib_cli_output (vm, "Interface: %U", format_vnet_hw_if_index_name, vnm, + pid.hw_if_index); + vlib_cli_output (vm, " id: %d", pid.id); + vlib_cli_output (vm, " mode: %U", format_pg_interface_mode, pid.mode); + if ((pid.mode & PG_MODE_ETHERNET) == PG_MODE_ETHERNET) + vlib_cli_output (vm, " hw-addr: %U", format_ethernet_address, + pid.hw_addr.bytes); + vlib_cli_output (vm, " csum_offload_enabled: %d", pid.csum_offload_enabled); + vlib_cli_output (vm, " gso_enabled: %d", pid.gso_enabled); + if (pid.gso_enabled) + vlib_cli_output (vm, " gso_size: %d", pid.gso_size); + vlib_cli_output (vm, " coalesce_enabled: %d", pid.coalesce_enabled); + + return 0; +} + +VLIB_CLI_COMMAND (show_pg_if_cmd, static) = { + .path = "show packet-generator interface", + .short_help = "show packet-generator interface { | " + "sw_if_index }", + .function = show_pg_if_cmd_fn, +}; + /* Dummy init function so that we can be linked in. */ static clib_error_t * pg_cli_init (vlib_main_t * vm) diff --git a/src/vnet/pg/input.c b/src/vnet/pg/input.c index 4f89c73a93..18ad6db3ae 100644 --- a/src/vnet/pg/input.c +++ b/src/vnet/pg/input.c @@ -1548,7 +1548,8 @@ pg_input_trace (pg_main_t * pg, static_always_inline void fill_buffer_offload_flags (vlib_main_t *vm, u32 next_index, u32 *buffers, - u32 n_buffers, u32 buffer_oflags, int gso_enabled, + u32 n_buffers, u32 buffer_oflags, + int csum_offload_enabled, int gso_enabled, u32 gso_size) { for (int i = 0; i < n_buffers; i++) @@ -1605,7 +1606,8 @@ fill_buffer_offload_flags (vlib_main_t *vm, u32 next_index, u32 *buffers, (VNET_BUFFER_F_IS_IP4 | VNET_BUFFER_F_L2_HDR_OFFSET_VALID | VNET_BUFFER_F_L3_HDR_OFFSET_VALID | VNET_BUFFER_F_L4_HDR_OFFSET_VALID); - if (buffer_oflags & VNET_BUFFER_OFFLOAD_F_IP_CKSUM || gso_enabled) + if (buffer_oflags & VNET_BUFFER_OFFLOAD_F_IP_CKSUM || gso_enabled || + csum_offload_enabled) oflags |= VNET_BUFFER_OFFLOAD_F_IP_CKSUM; } else if (PREDICT_TRUE (ethertype == ETHERNET_TYPE_IP6)) @@ -1622,7 +1624,8 @@ fill_buffer_offload_flags (vlib_main_t *vm, u32 next_index, u32 *buffers, if (l4_proto == IP_PROTOCOL_TCP) { - if (buffer_oflags & VNET_BUFFER_OFFLOAD_F_TCP_CKSUM || gso_enabled) + if (buffer_oflags & VNET_BUFFER_OFFLOAD_F_TCP_CKSUM || gso_enabled || + csum_offload_enabled) oflags |= VNET_BUFFER_OFFLOAD_F_TCP_CKSUM; /* only set GSO flag for chained buffers */ @@ -1637,7 +1640,8 @@ fill_buffer_offload_flags (vlib_main_t *vm, u32 next_index, u32 *buffers, } else if (l4_proto == IP_PROTOCOL_UDP) { - if (buffer_oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM) + if (buffer_oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM || + csum_offload_enabled) oflags |= VNET_BUFFER_OFFLOAD_F_UDP_CKSUM; } @@ -1746,15 +1750,16 @@ pg_generate_packets (vlib_node_runtime_t * node, vnet_buffer (b)->feature_arc_index = feature_arc_index; } - if (pi->gso_enabled || (s->buffer_flags & VNET_BUFFER_F_OFFLOAD)) + if (pi->gso_enabled || pi->csum_offload_enabled || + (s->buffer_flags & VNET_BUFFER_F_OFFLOAD)) { /* we use s->next_index and not next_index on purpose here: we want * the original node set by the user (typically ethernet-input, * ip4-input or ip6-input) whereas next_index can be overwritten by * device-input features */ - fill_buffer_offload_flags (vm, s->next_index, to_next, n_this_frame, - s->buffer_oflags, pi->gso_enabled, - pi->gso_size); + fill_buffer_offload_flags ( + vm, s->next_index, to_next, n_this_frame, s->buffer_oflags, + pi->csum_offload_enabled, pi->gso_enabled, pi->gso_size); } n_trace = vlib_get_trace_count (vm, node); diff --git a/src/vnet/pg/output.c b/src/vnet/pg/output.c index 5287f3eb8e..f9df00e803 100644 --- a/src/vnet/pg/output.c +++ b/src/vnet/pg/output.c @@ -44,6 +44,16 @@ #include #include +static_always_inline void +pg_interface_counter_inline (vlib_main_t *vm, pg_interface_t *pif, + uword node_index, u16 n, pg_tx_func_error_t error) +{ + vlib_error_count (vm, node_index, error, n); + vlib_increment_simple_counter (vnet_main.interface_main.sw_if_counters + + VNET_INTERFACE_COUNTER_DROP, + vm->thread_index, pif->sw_if_index, n); +} + uword pg_output (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) { @@ -52,7 +62,7 @@ pg_output (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) uword n_buffers = frame->n_vectors; uword n_left = n_buffers; u32 to[GRO_TO_VECTOR_SIZE (n_buffers)]; - uword n_to = 0; + uword n_to = 0, n_gso_drop = 0, n_csum_offload_drop = 0; vnet_interface_output_runtime_t *rd = (void *) node->runtime_data; pg_interface_t *pif = pool_elt_at_index (pg->interfaces, rd->dev_instance); @@ -74,6 +84,21 @@ pg_output (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) vlib_buffer_t *b = vlib_get_buffer (vm, bi0); buffers++; + if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_GSO)) + { + if (!pif->gso_enabled) + { + n_gso_drop++; + } + } + else if (PREDICT_FALSE (b->flags & VNET_BUFFER_F_OFFLOAD)) + { + if (!pif->csum_offload_enabled) + { + n_csum_offload_drop++; + } + } + if (b->flags & VLIB_BUFFER_IS_TRACED) { pg_output_trace_t *t = vlib_add_trace (vm, node, b, sizeof (*t)); @@ -101,6 +126,14 @@ pg_output (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame) pif->pcap_main.n_packets_to_capture) pcap_close (&pif->pcap_main); + if (n_gso_drop) + pg_interface_counter_inline (vm, pif, node->node_index, n_gso_drop, + PG_TX_ERROR_GSO_PACKET_DROP); + if (n_csum_offload_drop) + pg_interface_counter_inline (vm, pif, node->node_index, + n_csum_offload_drop, + PG_TX_ERROR_CSUM_OFFLOAD_PACKET_DROP); + if (PREDICT_FALSE (pif->coalesce_enabled)) { n_buffers = n_to; diff --git a/src/vnet/pg/pg.api b/src/vnet/pg/pg.api index 7c6fdcc97c..cea37603e2 100644 --- a/src/vnet/pg/pg.api +++ b/src/vnet/pg/pg.api @@ -29,6 +29,13 @@ enum pg_interface_mode : u8 PG_API_MODE_IP6, }; +enum pg_interface_flags : u32 { + PG_API_FLAG_NONE = 0, + PG_API_FLAG_CSUM_OFFLOAD = 1, /* enable checksum offload without gso on the interface */ + PG_API_FLAG_GSO = 2, /* enable gso on the interface */ + PG_API_FLAG_GRO_COALESCE = 4, /* enable packet coalescing on tx side, provided gso enabled */ +}; + /** \brief PacketGenerator create interface request @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @@ -46,8 +53,11 @@ define pg_create_interface bool gso_enabled; u32 gso_size; }; + define pg_create_interface_v2 { + option deprecated; + u32 client_index; u32 context; vl_api_interface_index_t interface_id; @@ -56,6 +66,16 @@ define pg_create_interface_v2 vl_api_pg_interface_mode_t mode; }; +autoendian define pg_create_interface_v3 +{ + u32 client_index; + u32 context; + vl_api_interface_index_t interface_id; + vl_api_pg_interface_flags_t pg_flags; + u32 gso_size; + vl_api_pg_interface_mode_t mode; +}; + /** \brief PacketGenerator create interface response @param context - sender context, to match reply w/ request @param retval - return value for request @@ -69,6 +89,15 @@ define pg_create_interface_reply vl_api_interface_index_t sw_if_index; }; define pg_create_interface_v2_reply +{ + option deprecated; + + u32 context; + i32 retval; + vl_api_interface_index_t sw_if_index; +}; + +autoendian define pg_create_interface_v3_reply { u32 context; i32 retval; diff --git a/src/vnet/pg/pg.h b/src/vnet/pg/pg.h index 7c5d698349..172c279a97 100644 --- a/src/vnet/pg/pg.h +++ b/src/vnet/pg/pg.h @@ -128,6 +128,9 @@ typedef struct pg_stream_t /* Buffer flags to set in each packet e.g. l2 valid flags */ u32 buffer_flags; + /* GSO size to set in each packet, when buffer is gso-ed */ + u32 gso_size; + /* Buffer offload flags to set in each packet e.g. checksum offload flags */ u32 buffer_oflags; @@ -332,6 +335,19 @@ pg_free_edit_group (pg_stream_t * s) vec_set_len (s->edit_groups, i); } +#define foreach_pg_tx_func_error \ + _ (GSO_PACKET_DROP, "gso disabled on itf -- gso packet drop") \ + _ (CSUM_OFFLOAD_PACKET_DROP, \ + "checksum offload disabled on itf -- csum offload packet drop") + +typedef enum +{ +#define _(f, s) PG_TX_ERROR_##f, + foreach_pg_tx_func_error +#undef _ + PG_TX_N_ERROR, +} pg_tx_func_error_t; + typedef enum pg_interface_mode_t_ { PG_MODE_ETHERNET, @@ -387,6 +403,7 @@ typedef struct u8 coalesce_enabled; gro_flow_table_t *flow_table; u8 gso_enabled; + u8 csum_offload_enabled; u32 gso_size; pcap_main_t pcap_main; char *pcap_file_name; @@ -395,6 +412,18 @@ typedef struct mac_address_t *allowed_mcast_macs; } pg_interface_t; +typedef struct +{ + u32 hw_if_index; + u32 id; + mac_address_t hw_addr; + u8 coalesce_enabled; + u8 gso_enabled; + u8 csum_offload_enabled; + u32 gso_size; + pg_interface_mode_t mode; +} pg_interface_details_t; + /* Per VLIB node data. */ typedef struct { @@ -453,6 +482,7 @@ void pg_interface_enable_disable_coalesce (pg_interface_t * pi, u8 enable, u32 pg_interface_add_or_get (pg_main_t *pg, pg_interface_args_t *args); int pg_interface_delete (u32 sw_if_index); +int pg_interface_details (u32 sw_if_index, pg_interface_details_t *pid); always_inline pg_node_t * pg_get_node (uword node_index) diff --git a/src/vnet/pg/pg_api.c b/src/vnet/pg/pg_api.c index 68953533b0..997a56d1d1 100644 --- a/src/vnet/pg/pg_api.c +++ b/src/vnet/pg/pg_api.c @@ -89,6 +89,36 @@ vl_api_pg_create_interface_v2_t_handler (vl_api_pg_create_interface_v2_t *mp) ({ rmp->sw_if_index = ntohl (pi->sw_if_index); })); } +static void +vl_api_pg_create_interface_v3_t_handler (vl_api_pg_create_interface_v3_t *mp) +{ + vl_api_pg_create_interface_v3_reply_t *rmp; + pg_main_t *pg = &pg_main; + pg_interface_t *pi; + pg_interface_args_t args = { 0 }; + u32 pg_if_id = ~0; + int rv; + + args.mode = (pg_interface_mode_t) mp->mode; + if (mp->pg_flags & PG_API_FLAG_CSUM_OFFLOAD) + args.flags = PG_INTERFACE_FLAG_CSUM_OFFLOAD; + else if (mp->pg_flags & PG_API_FLAG_GSO) + { + args.flags = PG_INTERFACE_FLAG_GSO; + args.gso_size = mp->gso_size; + if (mp->pg_flags & PG_API_FLAG_GRO_COALESCE) + args.flags |= PG_INTERFACE_FLAG_GRO_COALESCE; + } + args.if_id = mp->interface_id; + + pg_if_id = pg_interface_add_or_get (pg, &args); + pi = pool_elt_at_index (pg->interfaces, pg_if_id); + + rv = args.rv; + REPLY_MACRO2_END (VL_API_PG_CREATE_INTERFACE_V3_REPLY, + ({ rmp->sw_if_index = pi->sw_if_index; })); +} + static void vl_api_pg_delete_interface_t_handler (vl_api_pg_delete_interface_t *mp) { diff --git a/src/vnet/pg/stream.c b/src/vnet/pg/stream.c index 31a41e8cc8..f843036fdb 100644 --- a/src/vnet/pg/stream.c +++ b/src/vnet/pg/stream.c @@ -94,6 +94,12 @@ pg_stream_enable_disable (pg_main_t * pg, pg_stream_t * s, int want_enabled) s->time_last_generate = 0; } +static char *pg_tx_func_error_strings[] = { +#define _(n, s) s, + foreach_pg_tx_func_error +#undef _ +}; + static u8 * format_pg_output_trace (u8 * s, va_list * va) { @@ -183,6 +189,8 @@ VNET_DEVICE_CLASS (pg_dev_class) = { .tx_function = pg_output, .format_device_name = format_pg_interface_name, .format_tx_trace = format_pg_output_trace, + .tx_function_n_errors = PG_TX_N_ERROR, + .tx_function_error_strings = pg_tx_func_error_strings, .admin_up_down_function = pg_interface_admin_up_down, .mac_addr_add_del_function = pg_add_del_mac_address, }; @@ -243,10 +251,7 @@ format_pg_tun_tx_trace (u8 *s, va_list *args) VNET_HW_INTERFACE_CLASS (pg_tun_hw_interface_class) = { .name = "PG-tun", - //.format_header = format_gre_header_with_length, - //.unformat_header = unformat_gre_header, .build_rewrite = NULL, - //.update_adjacency = gre_update_adj, .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P, .tx_hash_fn_type = VNET_HASH_FN_TYPE_IP, }; @@ -257,6 +262,7 @@ pg_interface_add_or_get (pg_main_t *pg, pg_interface_args_t *args) vnet_main_t *vnm = vnet_get_main (); pg_interface_t *pi; vnet_hw_interface_t *hi; + vnet_hw_if_caps_change_t cc = { .mask = 0, .val = 0 }; uword *p; u32 i; @@ -295,9 +301,15 @@ pg_interface_add_or_get (pg_main_t *pg, pg_interface_args_t *args) break; } hi = vnet_get_hw_interface (vnm, pi->hw_if_index); + cc.mask = VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_IP4_CKSUM | + VNET_HW_IF_CAP_TX_TCP_CKSUM | VNET_HW_IF_CAP_TX_UDP_CKSUM | + VNET_HW_IF_CAP_TX_FIXED_OFFSET; if (args->flags & PG_INTERFACE_FLAG_GSO) { - vnet_hw_if_set_caps (vnm, pi->hw_if_index, VNET_HW_IF_CAP_TCP_GSO); + cc.val = VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_TX_IP4_CKSUM | + VNET_HW_IF_CAP_TX_TCP_CKSUM | VNET_HW_IF_CAP_TX_UDP_CKSUM | + VNET_HW_IF_CAP_TX_FIXED_OFFSET; + pi->gso_enabled = 1; pi->gso_size = args->gso_size; if (args->flags & PG_INTERFACE_FLAG_GRO_COALESCE) @@ -305,6 +317,15 @@ pg_interface_add_or_get (pg_main_t *pg, pg_interface_args_t *args) pg_interface_enable_disable_coalesce (pi, 1, hi->tx_node_index); } } + else if (args->flags & PG_INTERFACE_FLAG_CSUM_OFFLOAD) + { + cc.val = VNET_HW_IF_CAP_TX_IP4_CKSUM | VNET_HW_IF_CAP_TX_TCP_CKSUM | + VNET_HW_IF_CAP_TX_UDP_CKSUM | + VNET_HW_IF_CAP_TX_FIXED_OFFSET; + pi->csum_offload_enabled = 1; + } + + vnet_hw_if_change_caps (vnm, pi->hw_if_index, &cc); pi->sw_if_index = hi->sw_if_index; hash_set (pg->if_index_by_if_id, pi->id, i); @@ -367,6 +388,32 @@ pg_interface_delete (u32 sw_if_index) return 0; } +int +pg_interface_details (u32 sw_if_index, pg_interface_details_t *pid) +{ + vnet_main_t *vnm = vnet_get_main (); + pg_main_t *pm = &pg_main; + pg_interface_t *pi; + vnet_hw_interface_t *hw; + + hw = vnet_get_sup_hw_interface_api_visible_or_null (vnm, sw_if_index); + if (hw == NULL || pg_dev_class.index != hw->dev_class_index) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + pi = pool_elt_at_index (pm->interfaces, hw->dev_instance); + + pid->hw_if_index = pi->hw_if_index; + pid->id = pi->id; + pid->mode = pi->mode; + mac_address_from_bytes (&pid->hw_addr, pi->hw_addr.bytes); + pid->csum_offload_enabled = pi->csum_offload_enabled; + pid->gso_enabled = pi->gso_enabled; + pid->gso_size = pi->gso_size; + pid->coalesce_enabled = pi->coalesce_enabled; + + return 0; +} + static void do_edit (pg_stream_t * stream, pg_edit_group_t * g, pg_edit_t * e, uword want_commit) @@ -525,6 +572,8 @@ pg_stream_add (pg_main_t * pg, pg_stream_t * s_init) { vlib_main_t *vm = vlib_get_main (); pg_stream_t *s; + pg_interface_flags_t flags = 0; + u32 gso_size = 0; uword *p; if (!pg->stream_index_by_name) @@ -583,12 +632,23 @@ pg_stream_add (pg_main_t * pg, pg_stream_t * s_init) vec_resize (s->buffer_indices, n); } + if (s->buffer_flags & VNET_BUFFER_F_GSO) + { + flags = PG_INTERFACE_FLAG_GSO; + gso_size = s->gso_size; + } + else if (s->buffer_flags & VNET_BUFFER_F_OFFLOAD) + { + flags = PG_INTERFACE_FLAG_CSUM_OFFLOAD; + } + pg_interface_args_t args = { .if_id = s->if_id, .mode = PG_MODE_ETHERNET, - .flags = 0, /* gso_enabled and coalesce_enabled */ - .gso_size = 0, /* gso_size */ - .hw_addr_set = 0, /* mac address set */ + .flags = + flags, /* csum_offload_enabled, gso_enabled and/or coalesce_enabled */ + .gso_size = gso_size, /* gso_size */ + .hw_addr_set = 0, /* mac address set */ }; /* Find an interface to use. */ diff --git a/test/framework.py b/test/framework.py index 05d1018567..6879115240 100644 --- a/test/framework.py +++ b/test/framework.py @@ -158,7 +158,9 @@ def pg_start(cls, trace=True, traceFilter=False): cls._pcaps = [] @classmethod - def create_pg_interfaces_internal(cls, interfaces, gso=0, gso_size=0, mode=None): + def create_pg_interfaces_internal( + cls, interfaces, csum_offload=0, gso=0, gso_size=0, mode=None + ): """ Create packet-generator interfaces. @@ -168,50 +170,52 @@ def create_pg_interfaces_internal(cls, interfaces, gso=0, gso_size=0, mode=None) """ result = [] for i in interfaces: - intf = VppPGInterface(cls, i, gso, gso_size, mode) + intf = VppPGInterface(cls, i, csum_offload, gso, gso_size, mode) setattr(cls, intf.name, intf) result.append(intf) cls.pg_interfaces = result return result @classmethod - def create_pg_ip4_interfaces(cls, interfaces, gso=0, gso_size=0): + def create_pg_ip4_interfaces(cls, interfaces, csum_offload=0, gso=0, gso_size=0): if not hasattr(cls, "vpp"): cls.pg_interfaces = [] return cls.pg_interfaces pgmode = VppEnum.vl_api_pg_interface_mode_t return cls.create_pg_interfaces_internal( - interfaces, gso, gso_size, pgmode.PG_API_MODE_IP4 + interfaces, csum_offload, gso, gso_size, pgmode.PG_API_MODE_IP4 ) @classmethod - def create_pg_ip6_interfaces(cls, interfaces, gso=0, gso_size=0): + def create_pg_ip6_interfaces(cls, interfaces, csum_offload=0, gso=0, gso_size=0): if not hasattr(cls, "vpp"): cls.pg_interfaces = [] return cls.pg_interfaces pgmode = VppEnum.vl_api_pg_interface_mode_t return cls.create_pg_interfaces_internal( - interfaces, gso, gso_size, pgmode.PG_API_MODE_IP6 + interfaces, csum_offload, gso, gso_size, pgmode.PG_API_MODE_IP6 ) @classmethod - def create_pg_interfaces(cls, interfaces, gso=0, gso_size=0): + def create_pg_interfaces(cls, interfaces, csum_offload=0, gso=0, gso_size=0): if not hasattr(cls, "vpp"): cls.pg_interfaces = [] return cls.pg_interfaces pgmode = VppEnum.vl_api_pg_interface_mode_t return cls.create_pg_interfaces_internal( - interfaces, gso, gso_size, pgmode.PG_API_MODE_ETHERNET + interfaces, csum_offload, gso, gso_size, pgmode.PG_API_MODE_ETHERNET ) @classmethod - def create_pg_ethernet_interfaces(cls, interfaces, gso=0, gso_size=0): + def create_pg_ethernet_interfaces( + cls, interfaces, csum_offload=0, gso=0, gso_size=0 + ): if not hasattr(cls, "vpp"): cls.pg_interfaces = [] return cls.pg_interfaces pgmode = VppEnum.vl_api_pg_interface_mode_t return cls.create_pg_interfaces_internal( - interfaces, gso, gso_size, pgmode.PG_API_MODE_ETHERNET + interfaces, csum_offload, gso, gso_size, pgmode.PG_API_MODE_ETHERNET ) @classmethod diff --git a/test/test_gso.py b/test/test_gso.py index 63b42c9530..39a08bc2d7 100644 --- a/test/test_gso.py +++ b/test/test_gso.py @@ -49,9 +49,15 @@ def __init__(self, *args): def setUpClass(self): super(TestGSO, self).setUpClass() res = self.create_pg_interfaces(range(2)) - res_gso1 = self.create_pg_interfaces(range(2, 3), 1, 1460) - res_gso2 = self.create_pg_interfaces(range(3, 4), 1, 1440) - self.pg_interfaces = self.create_pg_interfaces(range(4, 5), 1, 8940) + res_gso1 = self.create_pg_interfaces( + range(2, 3), csum_offload=0, gso=1, gso_size=1460 + ) + res_gso2 = self.create_pg_interfaces( + range(3, 4), csum_offload=0, gso=1, gso_size=1440 + ) + self.pg_interfaces = self.create_pg_interfaces( + range(4, 5), csum_offload=0, gso=1, gso_size=8940 + ) self.pg_interfaces.append(res[0]) self.pg_interfaces.append(res[1]) self.pg_interfaces.append(res_gso1[0]) diff --git a/test/test_pg.py b/test/test_pg.py index 14e149b5bc..21ae2c17b9 100644 --- a/test/test_pg.py +++ b/test/test_pg.py @@ -4,42 +4,64 @@ from scapy.packet import Raw from scapy.layers.l2 import Ether -from scapy.layers.inet import IP, UDP +from scapy.layers.inet import IP, UDP, TCP from scapy.layers.inet6 import IPv6 from framework import VppTestCase from asfframework import VppTestRunner -class TestPgTun(VppTestCase): +class TestPg(VppTestCase): """PG Test Case""" + def __init__(self, *args): + VppTestCase.__init__(self, *args) + + @classmethod + def setUpClass(self): + super(TestPg, self).setUpClass() + + @classmethod + def tearDownClass(self): + super(TestPg, self).tearDownClass() + def setUp(self): - super(TestPgTun, self).setUp() + super(TestPg, self).setUp() + # create pg interfaces - # create 3 pg interfaces - one each ethernet, ip4-tun, ip6-tun. + # ethernet self.create_pg_interfaces(range(0, 1)) + # ip4-tun self.pg_interfaces += self.create_pg_ip4_interfaces(range(1, 2)) + # ip6-tun self.pg_interfaces += self.create_pg_ip6_interfaces(range(2, 3)) + # ethernet with checksum offload + self.pg_interfaces += self.create_pg_interfaces(range(3, 4), 1) + # ethernet with gso offload + self.pg_interfaces += self.create_pg_interfaces(range(4, 5), 0, 1, 1458) for i in self.pg_interfaces: i.admin_up() - for i in [self.pg0, self.pg1]: + for i in [self.pg0, self.pg1, self.pg3, self.pg4]: i.config_ip4() for i in [self.pg0, self.pg2]: i.config_ip6() self.pg0.resolve_arp() + self.pg3.resolve_arp() + self.pg4.resolve_arp() self.pg0.resolve_ndp() def tearDown(self): - for i in self.pg_interfaces: + super(TestPg, self).tearDown() + for i in [self.pg0, self.pg1, self.pg3, self.pg4]: i.unconfig_ip4() + for i in [self.pg0, self.pg2]: + i.unconfig_ip6() + for i in self.pg_interfaces: i.admin_down() - i.remove_vpp_config() - super(TestPgTun, self).tearDown() def test_pg_tun(self): """IP[46] Tunnel Mode PG""" @@ -102,6 +124,48 @@ def test_pg_tun(self): self.assertFalse(rx.haslayer(Ether)) self.assertEqual(rx[IPv6].dst, self.pg2.remote_ip6) + def test_pg_offload(self): + """PG Interface Offload""" + + N_PKTS = 31 + + p03 = ( + Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) + / IP(src=self.pg3.remote_ip4, dst=self.pg0.remote_ip4) + / UDP(sport=1234, dport=1234, chksum=0) + / Raw("0" * 48) + ) + + rxs = self.send_and_expect(self.pg3, p03 * N_PKTS, self.pg0) + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg0.local_mac) + self.assertEqual(rx[Ether].dst, self.pg0.remote_mac) + self.assert_ip_checksum_valid(rx) + self.assert_udp_checksum_valid(rx, ignore_zero_checksum=False) + self.assertEqual(rx[IP].dst, self.pg0.remote_ip4) + + p04 = ( + Ether(dst=self.pg4.local_mac, src=self.pg4.remote_mac) + / IP(src=self.pg4.remote_ip4, dst=self.pg3.remote_ip4, flags="DF") + / TCP(sport=1234, dport=1234) + / Raw(b"\xa5" * 65200) + ) + + rxs = self.send_and_expect(self.pg4, p04 * N_PKTS, self.pg3) + for rx in rxs: + self.assertEqual(rx[Ether].src, self.pg3.local_mac) + self.assertEqual(rx[Ether].dst, self.pg3.remote_mac) + self.assertEqual(rx[IP].dst, self.pg3.remote_ip4) + + r = self.vapi.cli_return_response("show errors") + self.assertTrue(r.retval == 0) + self.assertTrue(hasattr(r, "reply")) + rv = r.reply + outcome = rv.find( + "31 pg3-tx gso disabled on itf -- gso packet error" + ) + self.assertNotEqual(outcome, -1) + if __name__ == "__main__": unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_pg_interface.py b/test/vpp_pg_interface.py index cd99818caa..1c82ebae8e 100644 --- a/test/vpp_pg_interface.py +++ b/test/vpp_pg_interface.py @@ -57,6 +57,13 @@ def pg_index(self): """packet-generator interface index assigned by VPP""" return self._pg_index + @property + def csum_offload_enabled(self): + """csum offload enabled on packet-generator interface""" + if self._csum_offload_enabled == 0: + return "csum-offload-disabled" + return "csum-offload-enabled" + @property def gso_enabled(self): """gso enabled on packet-generator interface""" @@ -125,17 +132,24 @@ def out_history_counter(self): self._out_history_counter += 1 return v - def __init__(self, test, pg_index, gso, gso_size, mode): + def __init__(self, test, pg_index, csum_offload, gso, gso_size, mode): """Create VPP packet-generator interface""" super().__init__(test) - r = test.vapi.pg_create_interface_v2(pg_index, gso, gso_size, mode) + pg_flags = VppEnum.vl_api_pg_interface_flags_t.PG_API_FLAG_NONE + pgflags = VppEnum.vl_api_pg_interface_flags_t + if csum_offload: + pg_flags = pgflags.PG_API_FLAG_CSUM_OFFLOAD + if gso: + pg_flags = pgflags.PG_API_FLAG_GSO + r = test.vapi.pg_create_interface_v3(pg_index, pg_flags, gso_size, mode) self.set_sw_if_index(r.sw_if_index) self._in_history_counter = 0 self._out_history_counter = 0 self._out_assert_counter = 0 self._pg_index = pg_index + self._csum_offload_enabled = csum_offload self._gso_enabled = gso self._gso_size = gso_size self._coalesce_enabled = 0 From 576e6b555b918c9e2bbe2624096a762c14d46056 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Fri, 25 Jul 2025 19:06:41 -0400 Subject: [PATCH 184/313] quic: reregister transport proto when no quic engine registered - fixes incomplete implementation from original patch to fix crash on startup when no quic engine is enabled Type: fix Fixes: 19e0d0ef8 Change-Id: I8508c28dc2e3d6201fd5de296386d0309fc0edd1 Signed-off-by: Dave Wallace --- src/plugins/quic/quic.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/quic/quic.c b/src/plugins/quic/quic.c index 3a6ae06baf..64b0050ff3 100644 --- a/src/plugins/quic/quic.c +++ b/src/plugins/quic/quic.c @@ -863,6 +863,11 @@ quic_enable (vlib_main_t *vm, u8 is_en) /* Prevent crash in transport layer callbacks with no quic engine */ quic_proto.connect = 0; quic_proto.start_listen = 0; + transport_register_protocol (TRANSPORT_PROTO_QUIC, &quic_proto, + FIB_PROTOCOL_IP4, ~0); + transport_register_protocol (TRANSPORT_PROTO_QUIC, &quic_proto, + FIB_PROTOCOL_IP6, ~0); + clib_warning ( "ERROR: NO QUIC ENGINE PLUGIN ENABLED!" "\nEnable a quic engine plugin in the startup configuration."); From c0b9cbd81517612a51753478ab1eb7ebe8545a2d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 1 Aug 2025 12:08:59 -0400 Subject: [PATCH 185/313] hs-test: add hsi and transparent proxy test Type: test Change-Id: Ie7fdc987ee300cf0d3dc5aed31dd28a972f0c394 Signed-off-by: Matus Fabian --- extras/hs-test/Makefile | 3 +- extras/hs-test/docker/Dockerfile.vpp | 1 + extras/hs-test/hsi_test.go | 31 ++++ extras/hs-test/infra/suite_hsi.go | 223 +++++++++++++++++++++++++++ extras/hs-test/infra/vppinstance.go | 1 + extras/hs-test/topo-network/ns.yaml | 4 - 6 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 extras/hs-test/hsi_test.go create mode 100644 extras/hs-test/infra/suite_hsi.go diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index b264795d23..5406b69120 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -88,7 +88,8 @@ endif FORCE_BUILD?=true BUILD_AS:=$(strip $(shell echo $${SUDO_USER:-$${USER:-root}})) -DOCKER_CAPABILITIES:=--cap-add=NET_ADMIN --cap-add=SYS_RESOURCE --cap-add=IPC_LOCK +# privileged is needed for "ip netns" otherwise we are not able to create namespace +DOCKER_CAPABILITIES:=--privileged DOCKER_DEVICES:=--device /dev/vhost-net:/dev/vhost-net --device /dev/net/tun:/dev/net/tun DOCKER_VOLUMES:=-v $(WS_ROOT):$(WS_ROOT) -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/hs-test:/tmp/hs-test \ -v /etc/localtime:/etc/localtime:ro $(CORE_VOLUME) -v $(HS_ROOT)/.go_cache/mod:/root/go/pkg/mod \ diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp index 1e69e2cf2f..cf620ee114 100644 --- a/extras/hs-test/docker/Dockerfile.vpp +++ b/extras/hs-test/docker/Dockerfile.vpp @@ -17,6 +17,7 @@ COPY \ $DIR/unittest_plugin.so \ $DIR/quic_plugin.so \ $DIR/quic_quicly_plugin.so \ + $DIR/hsi_plugin.so \ $DIR/http_static_plugin.so \ $DIR/ping_plugin.so \ $DIR/nsim_plugin.so \ diff --git a/extras/hs-test/hsi_test.go b/extras/hs-test/hsi_test.go new file mode 100644 index 0000000000..b5a8e61f34 --- /dev/null +++ b/extras/hs-test/hsi_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + "strconv" + + . "fd.io/hs-test/infra" + . "github.com/onsi/ginkgo/v2" +) + +func init() { + RegisterHsiSoloTests(HsiTransparentProxyTest) +} + +func HsiTransparentProxyTest(s *HsiSuite) { + s.SetupNginxServer() + vpp := s.Containers.Vpp.VppInstance + s.Log(vpp.Vppctl("set interface feature host-" + s.Interfaces.Client.Name() + " hsi4-in arc ip4-unicast")) + s.Log(vpp.Vppctl("set interface feature host-" + s.Interfaces.Server.Name() + " hsi4-in arc ip4-unicast")) + s.Log(vpp.Vppctl("test proxy server server-uri tcp://0.0.0.0:%d client-uri tcp://%s:%d", + s.Ports.Server, s.ServerAddr(), s.Ports.Server)) + + query := "httpTestFile" + finished := make(chan error, 1) + defer os.Remove(query) + go func() { + defer GinkgoRecover() + s.StartWget(finished, s.ServerAddr(), strconv.Itoa(int(s.Ports.Server)), query, s.NetNamespaces.Client) + }() + s.AssertNil(<-finished) +} diff --git a/extras/hs-test/infra/suite_hsi.go b/extras/hs-test/infra/suite_hsi.go new file mode 100644 index 0000000000..0575577047 --- /dev/null +++ b/extras/hs-test/infra/suite_hsi.go @@ -0,0 +1,223 @@ +package hst + +import ( + "fmt" + "os/exec" + "reflect" + "runtime" + "strings" + + . "fd.io/hs-test/infra/common" + . "github.com/onsi/ginkgo/v2" +) + +type HsiSuite struct { + HstSuite + maxTimeout int + Interfaces struct { + Client *NetInterface + Server *NetInterface + } + Containers struct { + Vpp *Container + NginxServerTransient *Container + } + Ports struct { + Server uint16 + ServerSsl uint16 + } + NetNamespaces struct { + Client string + } +} + +var hsiTests = map[string][]func(s *HsiSuite){} +var hsiSoloTests = map[string][]func(s *HsiSuite){} +var hsiMWTests = map[string][]func(s *HsiSuite){} + +func RegisterHsiTests(tests ...func(s *HsiSuite)) { + hsiTests[GetTestFilename()] = tests +} + +func RegisterHsiSoloTests(tests ...func(s *HsiSuite)) { + hsiSoloTests[GetTestFilename()] = tests +} + +func RegisterHsiMWTests(tests ...func(s *HsiSuite)) { + hsiMWTests[GetTestFilename()] = tests +} + +func (s *HsiSuite) SetupSuite() { + s.HstSuite.SetupSuite() + s.ConfigureNetworkTopology("ns") + s.LoadContainerTopology("single") + s.Ports.Server = s.GeneratePortAsInt() + s.Ports.ServerSsl = s.GeneratePortAsInt() + + if *IsVppDebug { + s.maxTimeout = 600 + } else { + s.maxTimeout = 60 + } + s.Interfaces.Client = s.GetInterfaceByName("hclnvpp") + s.Interfaces.Server = s.GetInterfaceByName("hsrvvpp") + s.NetNamespaces.Client = s.GetNetNamespaceByName("cln") + s.Containers.NginxServerTransient = s.GetTransientContainerByName("nginx-server") + s.Containers.Vpp = s.GetContainerByName("vpp") +} + +func (s *HsiSuite) SetupTest() { + s.HstSuite.SetupTest() + + vpp, err := s.Containers.Vpp.newVppInstance(s.Containers.Vpp.AllocatedCpus) + s.AssertNotNil(vpp, fmt.Sprint(err)) + + s.AssertNil(vpp.Start()) + idx, err := vpp.createAfPacket(s.Interfaces.Client, false) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertNotEqual(0, idx) + idx, err = vpp.createAfPacket(s.Interfaces.Server, false) + s.AssertNil(err, fmt.Sprint(err)) + s.AssertNotEqual(0, idx) + + s.Log(vpp.Vppctl("set interface feature host-" + s.Interfaces.Client.Name() + " hsi4-in arc ip4-unicast")) + s.Log(vpp.Vppctl("set interface feature host-" + s.Interfaces.Server.Name() + " hsi4-in arc ip4-unicast")) + + // let the host know howto get to the server + cmd := exec.Command("ip", "netns", "exec", s.NetNamespaces.Client, "ip", "route", "add", + s.ServerAddr(), "via", s.Interfaces.Client.Ip4AddressString()) + s.Log(cmd.String()) + _, err = cmd.CombinedOutput() + s.AssertNil(err, fmt.Sprint(err)) + + if *DryRun { + s.LogStartedContainers() + s.Skip("Dry run mode = true") + } +} + +func (s *HsiSuite) TeardownTest() { + defer s.HstSuite.TeardownTest() + vpp := s.Containers.Vpp.VppInstance + if CurrentSpecReport().Failed() { + s.Log(vpp.Vppctl("show session verbose 2")) + s.Log(vpp.Vppctl("show error")) + s.CollectNginxLogs(s.Containers.NginxServerTransient) + } +} + +func (s *HsiSuite) SetupNginxServer() { + s.AssertNil(s.Containers.NginxServerTransient.Create()) + nginxSettings := struct { + LogPrefix string + Address string + Port uint16 + PortSsl uint16 + Http2 string + Timeout int + }{ + LogPrefix: s.Containers.NginxServerTransient.Name, + Address: s.ServerAddr(), + Port: s.Ports.Server, + PortSsl: s.Ports.ServerSsl, + Http2: "off", + Timeout: s.maxTimeout, + } + s.Containers.NginxServerTransient.CreateConfigFromTemplate( + "/nginx.conf", + "./resources/nginx/nginx_server.conf", + nginxSettings, + ) + s.AssertNil(s.Containers.NginxServerTransient.Start()) +} + +func (s *HsiSuite) ServerAddr() string { + return s.Interfaces.Server.Peer.Ip4AddressString() +} + +var _ = Describe("HsiSuite", Ordered, ContinueOnFailure, func() { + var s HsiSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range hsiTests { + 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)) + } + } +}) + +var _ = Describe("HsiSoloSuite", Ordered, ContinueOnFailure, Serial, func() { + var s HsiSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SetupTest() + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range hsiSoloTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) + +var _ = Describe("HsiMWSuite", Ordered, ContinueOnFailure, Serial, func() { + var s HsiSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + for filename, tests := range hsiMWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go index b601e46fef..2e9c7567aa 100644 --- a/extras/hs-test/infra/vppinstance.go +++ b/extras/hs-test/infra/vppinstance.go @@ -63,6 +63,7 @@ plugins { plugin quic_quicly_plugin.so { enable } plugin af_packet_plugin.so { enable } plugin hs_apps_plugin.so { enable } + plugin hsi_plugin.so { enable } plugin http_plugin.so { enable } plugin http_unittest_plugin.so { enable } plugin http_static_plugin.so { enable } diff --git a/extras/hs-test/topo-network/ns.yaml b/extras/hs-test/topo-network/ns.yaml index 018c329f77..233ab498dd 100644 --- a/extras/hs-test/topo-network/ns.yaml +++ b/extras/hs-test/topo-network/ns.yaml @@ -3,9 +3,6 @@ devices: - name: "cln" type: "netns" - - name: "srv" - type: "netns" - - name: "hclnvpp" type: "veth" peer: @@ -18,6 +15,5 @@ devices: type: "veth" peer: name: "srv" - netns: "srv" ip4: network: 2 From 5fda9546142574c72582abb2c0234d538df5d925 Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Mon, 4 Aug 2025 13:41:00 -0700 Subject: [PATCH 186/313] session: session debug crashes with elog track session debug uses elog track in the the transport. However, not all transports initialize elog track. If they don't, session debug crashes. The fix is to use non elog track if elog track is not initialized in the transport. Type: fix Change-Id: I123f71b39a4262cd68e35634dd5c7ec0e7b88cb0 Signed-off-by: Steven Luong --- src/vnet/session/session_debug.h | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vnet/session/session_debug.h b/src/vnet/session/session_debug.h index d433ef47fb..70909979a5 100644 --- a/src/vnet/session/session_debug.h +++ b/src/vnet/session/session_debug.h @@ -149,14 +149,16 @@ extern session_dbg_main_t session_dbg_main; #define SESSION_DBG(_fmt, _args...) clib_warning (_fmt, ##_args) -#define DEC_SESSION_ETD(_s, _e, _size) \ - struct \ - { \ - u32 data[_size]; \ - } * ed; \ - transport_connection_t *_tc = session_get_transport (_s); \ - ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, \ - _e, _tc->elog_track) +#define DEC_SESSION_ETD(_s, _e, _size) \ + struct \ + { \ + u32 data[_size]; \ + } *ed; \ + transport_connection_t *_tc = session_get_transport (_s); \ + if (_tc->elog_track.name == 0) \ + ed = ELOG_DATA (&vlib_global_main.elog_main, _e); \ + else \ + ed = ELOG_TRACK_DATA (&vlib_global_main.elog_main, _e, _tc->elog_track) #define DEC_SESSION_ED(_e, _size) \ struct \ From 8cb1294c000eb8ce977962c384a509c1cd5408ec Mon Sep 17 00:00:00 2001 From: Naveen Joy Date: Mon, 21 Jul 2025 14:59:34 -0700 Subject: [PATCH 187/313] tests: af_xdp interface tests Type: test Change-Id: I81019d2ac8b793c11b22deab8a103b37b88eb75e Signed-off-by: Naveen Joy --- test/test_vm_af_xdp_l2.py | 63 +++++++++ test/test_vm_af_xdp_l3.py | 63 +++++++++ test/vm_test_config.py | 22 +++ test/vm_vpp_interfaces.py | 275 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 409 insertions(+), 14 deletions(-) create mode 100644 test/test_vm_af_xdp_l2.py create mode 100644 test/test_vm_af_xdp_l3.py diff --git a/test/test_vm_af_xdp_l2.py b/test/test_vm_af_xdp_l2.py new file mode 100644 index 0000000000..2515495953 --- /dev/null +++ b/test/test_vm_af_xdp_l2.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +import unittest +from framework import VppTestCase +from vm_vpp_interfaces import ( + TestSelector, + TestVPPInterfacesQemu, + generate_vpp_interface_tests, +) +from asfframework import VppTestRunner +from vm_test_config import test_config +import fcntl +import time + + +class TestVPPInterfacesQemuAfXDPL2(TestVPPInterfacesQemu, VppTestCase): + """Test af_xdp interfaces in L2 mode for IPv4/v6.""" + + # Set test_id(s) to run from vm_test_config + # The expansion of these numbers are included in the test docstring + tests_to_run = "28" + + @classmethod + def setUpClass(cls): + # Create lock file to prevent concurrent test runs of af_xdp tests + # as they interfere with each other + cls.lock_file_path = "/tmp/vpp_af_xdp_test.lock" + cls.lock_file = open(cls.lock_file_path, "w") + + # Wait for lock + attempt = 0 + while True: + try: + fcntl.flock(cls.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) + break + except IOError: + attempt += 1 + if attempt > 120: # Wait up to 2 minutes + raise Exception("Could not acquire lock for AF_XDP tests") + time.sleep(1) + + super(TestVPPInterfacesQemuAfXDPL2, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + try: + super(TestVPPInterfacesQemuAfXDPL2, cls).tearDownClass() + finally: + # Release lock + if hasattr(cls, "lock_file"): + fcntl.flock(cls.lock_file, fcntl.LOCK_UN) + cls.lock_file.close() + + def tearDown(self): + super(TestVPPInterfacesQemuAfXDPL2, self).tearDown() + + +SELECTED_TESTS = TestVPPInterfacesQemuAfXDPL2.tests_to_run +tests = filter(TestSelector(SELECTED_TESTS).filter_tests, test_config["tests"]) +generate_vpp_interface_tests(tests, TestVPPInterfacesQemuAfXDPL2) + +if __name__ == "__main__": + unittest.main(testRunner=VppTestRunner) diff --git a/test/test_vm_af_xdp_l3.py b/test/test_vm_af_xdp_l3.py new file mode 100644 index 0000000000..08d04a297a --- /dev/null +++ b/test/test_vm_af_xdp_l3.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +import unittest +from framework import VppTestCase +from vm_vpp_interfaces import ( + TestSelector, + TestVPPInterfacesQemu, + generate_vpp_interface_tests, +) +from asfframework import VppTestRunner +from vm_test_config import test_config +import fcntl +import time + + +class TestVPPInterfacesQemuAfXDPL3(TestVPPInterfacesQemu, VppTestCase): + """Test af_xdp interfaces in L3 mode for IPv4/v6.""" + + # Set test_id(s) to run from vm_test_config + # The expansion of these numbers are included in the test docstring + tests_to_run = "29" + + @classmethod + def setUpClass(cls): + # Create lock file to prevent concurrent test runs of af_xdp tests + # as they interfere with each other + cls.lock_file_path = "/tmp/vpp_af_xdp_test.lock" + cls.lock_file = open(cls.lock_file_path, "w") + + # Wait for lock + attempt = 0 + while True: + try: + fcntl.flock(cls.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) + break + except IOError: + attempt += 1 + if attempt > 120: # Wait up to 2 minutes + raise Exception("Could not acquire lock for AF_XDP tests") + time.sleep(1) + + super(TestVPPInterfacesQemuAfXDPL3, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + try: + super(TestVPPInterfacesQemuAfXDPL3, cls).tearDownClass() + finally: + # Release lock + if hasattr(cls, "lock_file"): + fcntl.flock(cls.lock_file, fcntl.LOCK_UN) + cls.lock_file.close() + + def tearDown(self): + super(TestVPPInterfacesQemuAfXDPL3, self).tearDown() + + +SELECTED_TESTS = TestVPPInterfacesQemuAfXDPL3.tests_to_run +tests = filter(TestSelector(SELECTED_TESTS).filter_tests, test_config["tests"]) +generate_vpp_interface_tests(tests, TestVPPInterfacesQemuAfXDPL3) + +if __name__ == "__main__": + unittest.main(testRunner=VppTestRunner) diff --git a/test/vm_test_config.py b/test/vm_test_config.py index 60db4d1f18..17a33e9975 100644 --- a/test/vm_test_config.py +++ b/test/vm_test_config.py @@ -13,6 +13,12 @@ "iprf_client_interface_on_vpp": "vppclientout", "iprf_server_interface_on_vpp": "vppserverout", }, + "af_xdp": { + "iprf_client_interface_on_linux": "afxdp", + "iprf_server_interface_on_linux": "afxdps", + "iprf_client_interface_on_vpp": "vafxdp", + "iprf_server_interface_on_vpp": "vafxdps", + }, "L2": { "client_ip4_prefix": "10.0.0.101/24", "server_ip4_prefix": "10.0.0.102/24", @@ -328,5 +334,21 @@ "server_if_checksum_offload": 0, "x_connect_mode": "L2", }, + { + "id": 28, + "client_if_type": "af_xdp", + "client_if_version": 3, + "server_if_type": "af_xdp", + "server_if_version": 3, + "x_connect_mode": "L2", + }, + { + "id": 29, + "client_if_type": "af_xdp", + "client_if_version": 3, + "server_if_type": "af_xdp", + "server_if_version": 3, + "x_connect_mode": "L3", + }, ], } diff --git a/test/vm_vpp_interfaces.py b/test/vm_vpp_interfaces.py index 66f88969cc..c247f24582 100644 --- a/test/vm_vpp_interfaces.py +++ b/test/vm_vpp_interfaces.py @@ -20,6 +20,9 @@ import sys import os from vm_test_config import test_config +import random +import string +import subprocess # # Tests for: @@ -73,6 +76,7 @@ def filter_tests(self, test): # Test Config variables af_packet_config = test_config["af_packet"] +af_xdp_config = test_config["af_xdp"] layer2 = test_config["L2"] layer3 = test_config["L3"] @@ -147,9 +151,22 @@ def generate_vpp_interface_tests(tests, test_class): generated tests are set as attributes. """ + def get_valid_mtus(test): + client_if_types = test["client_if_type"].split(",") + server_if_types = test["server_if_type"].split(",") + contains_af_xdp = any( + if_type == "af_xdp" for if_type in client_if_types + server_if_types + ) + + # MTU <= 2048 Bytes for af_xdp interfaces + if contains_af_xdp: + return [mtu for mtu in test_config["mtus"] if mtu <= 2048] + else: + return test_config["mtus"] + for test in tests: for ip_version in test_config["ip_versions"]: - for mtu in test_config["mtus"]: + for mtu in get_valid_mtus(test): test_name = ( f"test_id_{test['id']}_" + f"client_{test['client_if_type']}" @@ -355,6 +372,23 @@ def setUpTestToplogy(self, test, ip_version): self.memif_interfaces.append(self.ingress_if_idx) self.ingress_if_idxes.append(self.ingress_if_idx) self.vpp_interfaces.append(self.ingress_if_idx) + elif client_if_type == "af_xdp": + self.ingress_if_idx = self.create_af_xdp( + namespace=self.client_namespace, + host_side_name=af_xdp_config["iprf_client_interface_on_linux"], + vpp_side_name=af_xdp_config["iprf_client_interface_on_vpp"], + ip4_prefix=( + layer2["client_ip4_prefix"] + if x_connect_mode == "L2" + else layer3["client_ip4_prefix"] + ), + ip6_prefix=( + layer2["client_ip6_prefix"] + if x_connect_mode == "L2" + else layer3["client_ip6_prefix"] + ), + version=client_if_version, + ) else: print( f"Unsupported client interface type: {client_if_type} " @@ -429,6 +463,15 @@ def setUpTestToplogy(self, test, ip_version): self.memif_interfaces.append(self.egress_if_idx) self.egress_if_idxes.append(self.egress_if_idx) self.vpp_interfaces.append(self.egress_if_idx) + elif server_if_type == "af_xdp": + self.egress_if_idx = self.create_af_xdp( + namespace=self.server_namespace, + host_side_name=af_xdp_config["iprf_server_interface_on_linux"], + vpp_side_name=af_xdp_config["iprf_server_interface_on_vpp"], + ip4_prefix=server_ip4_prefix, + ip6_prefix=server_ip6_prefix, + version=server_if_version, + ) else: print( f"Unsupported server interface type: {server_if_type} " @@ -474,28 +517,55 @@ def setUpTestToplogy(self, test, ip_version): time.sleep(2) def tearDown(self): - try: - for interface_if_idx in self.tap_interfaces: + # Delete tap interfaces + for interface_if_idx in self.tap_interfaces: + try: self.vapi.tap_delete_v2(sw_if_index=interface_if_idx) - except Exception: - pass - try: - for interface_if_idx in self.memif_interfaces: + except Exception: + pass + + # Delete memif interfaces + for interface_if_idx in self.memif_interfaces: + try: self.vapi.memif_delete(sw_if_index=interface_if_idx) - except Exception: - pass + except Exception: + pass + + # Delete af_packet interfaces try: for interface in self.vapi.af_packet_dump(): if interface.host_if_name == self.iprf_client_host_interface_on_vpp: - self.vapi.af_packet_delete(self.iprf_client_host_interface_on_vpp) + try: + self.vapi.af_packet_delete( + self.iprf_client_host_interface_on_vpp + ) + except Exception: + pass elif interface.host_if_name == self.iprf_server_host_interface_on_vpp: - self.vapi.af_packet_delete(self.iprf_server_host_interface_on_vpp) + try: + self.vapi.af_packet_delete( + self.iprf_server_host_interface_on_vpp + ) + except Exception: + pass except Exception: pass + + # Delete AF_XDP interfaces + if hasattr(self, "af_xdp_interfaces"): + for interface_if_idx in self.af_xdp_interfaces: + try: + self.vapi.af_xdp_delete(interface_if_idx) + except Exception: + pass + + # Delete host interfaces try: delete_all_host_interfaces(self.if_history_name) except Exception: pass + + # Delete VRF tables try: self.vapi.ip_table_add_del_v2( is_add=0, table={"table_id": layer3["ip4_vrf"]} @@ -508,11 +578,21 @@ def tearDown(self): ) except Exception: pass + + # Delete bridge domains try: - self.vapi.bridge_domain_add_del_v2(bd_id=1, is_add=0) - self.vapi.bridge_domain_add_del_v2(bd_id=2, is_add=0) + # Check if bridge domains exist before trying to delete them + for bd_id in [1, 2]: + try: + bd_details = self.vapi.bridge_domain_dump(bd_id=bd_id) + if len(bd_details) > 0: + self.vapi.bridge_domain_add_del_v2(bd_id=bd_id, is_add=0) + except Exception: + pass except Exception: pass + + # Clean up namespaces and processes try: delete_all_namespaces(self.ns_history_file) except Exception: @@ -523,7 +603,7 @@ def tearDown(self): except Exception: pass try: - if self.memif_process: + if hasattr(self, "memif_process") and self.memif_process: self.memif_process.terminate() self.memif_process.join() except Exception: @@ -740,6 +820,173 @@ def set_interfaces_mtu(self, mtu, ip_version, **kwargs): else: return False + def create_af_xdp( + self, namespace, host_side_name, vpp_side_name, ip4_prefix, ip6_prefix, version + ): + """Create an AF_XDP interface and configure it in VPP and Linux.""" + try: + # Generate unique random suffixes for interface names to prevent conflicts + random_suffix = "".join( + random.choices(string.ascii_lowercase + string.digits, k=8) + ) + unique_host_side_name = f"{host_side_name}{random_suffix}" + unique_vpp_side_name = f"{vpp_side_name}{random_suffix}" + + self.logger.debug( + f"Creating AF_XDP interfaces with names: {unique_host_side_name} and {unique_vpp_side_name}" + ) + + # Clean up any existing interfaces with the same name + os.system( + f"ip netns exec {namespace} ip link del {unique_host_side_name} 2>/dev/null || true" + ) + os.system(f"ip link del {unique_vpp_side_name} 2>/dev/null || true") + + # Create the host interface + create_host_interface( + self.if_history_name, + namespace, + ip4_prefix, + ip6_prefix, + vpp_if_name=unique_vpp_side_name, + host_if_name=unique_host_side_name, + ) + + # Verify that the host interfaces were created successfully + max_check_attempts = 5 + check_interval = 0.5 + + # Check for VPP-side interface + for attempt in range(max_check_attempts): + vpp_if_check = subprocess.run( + ["ip", "link", "show", "dev", unique_vpp_side_name], + capture_output=True, + text=True, + ) + if vpp_if_check.returncode == 0: + self.logger.debug( + f"VPP-side interface {unique_vpp_side_name} exists" + ) + break + self.logger.warning( + f"VPP-side interface {unique_vpp_side_name} not ready, attempt {attempt+1}/{max_check_attempts}" + ) + time.sleep(check_interval) + else: + raise Exception( + f"VPP-side interface {unique_vpp_side_name} does not exist after {max_check_attempts} checks" + ) + + # Check for namespace-side interface + for attempt in range(max_check_attempts): + host_if_check = subprocess.run( + [ + "ip", + "netns", + "exec", + namespace, + "ip", + "link", + "show", + "dev", + unique_host_side_name, + ], + capture_output=True, + text=True, + ) + if host_if_check.returncode == 0: + self.logger.debug( + f"Host-side interface {unique_host_side_name} exists in namespace {namespace}" + ) + break + self.logger.warning( + f"Host-side interface {unique_host_side_name} not ready, attempt {attempt+1}/{max_check_attempts}" + ) + time.sleep(check_interval) + else: + raise Exception( + f"Host-side interface {unique_host_side_name} does not exist in namespace {namespace} after {max_check_attempts} checks" + ) + + # Add delay to ensure host interface is fully initialized + time.sleep(1) + + api_args = { + "host_if": unique_vpp_side_name, + "rxq_num": 1, + } + + # Clean any stale XDP sockets + os.system( + f"rm -f /dev/shm/vpp_*{unique_vpp_side_name}* 2>/dev/null || true" + ) + + # Set retry mechanism to ensure correct AF_XDP creation + retries = 3 + last_error = None + + for attempt in range(retries): + try: + if version == 1: + result = self.vapi.af_xdp_create(**api_args) + elif version == 2: + result = self.vapi.af_xdp_create_v2(**api_args) + elif version == 3: + result = self.vapi.af_xdp_create_v3(**api_args) + else: + raise ValueError(f"Unsupported AF_XDP version: {version}") + break + except Exception as e: + last_error = e + self.logger.warning( + f"AF_XDP creation attempt {attempt+1} failed: {e}" + ) + time.sleep(1) # Wait before retry + else: + # All retries failed + raise Exception( + f"Failed to create AF_XDP interface after {retries} attempts: {last_error}" + ) + + sw_if_index = result.sw_if_index + + # Set interface up + self.vapi.sw_interface_set_flags(sw_if_index=sw_if_index, flags=1) + + # Add the interface to the VPP interface list + self.vpp_interfaces.append(sw_if_index) + self.linux_interfaces.append(["", unique_vpp_side_name]) + self.linux_interfaces.append([namespace, unique_host_side_name]) + + # Track AF_XDP interfaces for tearDown + if not hasattr(self, "af_xdp_interfaces"): + self.af_xdp_interfaces = [] + self.af_xdp_interfaces.append(sw_if_index) + + # Add to ingress/egress lists based on namespace + if namespace == self.client_namespace: + self.ingress_if_idxes.append(sw_if_index) + elif namespace == self.server_namespace: + self.egress_if_idxes.append(sw_if_index) + + # AF_XDP doesn't support GSO/checksum offload, so disable them + disable_interface_gso("", unique_vpp_side_name) + disable_interface_gso(namespace, unique_host_side_name) + + return sw_if_index + + except Exception as e: + self.logger.error(f"Error creating AF_XDP interface: {e}") + # Cleanup on failure + try: + os.system( + f"ip netns exec {namespace} ip link del {unique_host_side_name} 2>/dev/null || true" + ) + os.system(f"ip link del {unique_vpp_side_name} 2>/dev/null || true") + except: + pass + raise + if __name__ == "__main__": unittest.main(testRunner=VppTestRunner) From a15bc8501b9cc095864931415e4baf32dd42379f Mon Sep 17 00:00:00 2001 From: Joel Godfrey-Smith Date: Thu, 17 Jul 2025 10:49:08 -0400 Subject: [PATCH 188/313] build: updated to build on RHEL-8 Type: improvement extras/rpm/vpp.spec: updated to ensure crypto libraries are copied into lib RPM but other, conflicting system libraries are not Change-Id: I786b61d17141e7ed2f8c88f9646e2bdad09c93fb Signed-off-by: Joel Godfrey-Smith --- extras/rpm/vpp.spec | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/extras/rpm/vpp.spec b/extras/rpm/vpp.spec index 6bfb12471e..26c6ed29b2 100644 --- a/extras/rpm/vpp.spec +++ b/extras/rpm/vpp.spec @@ -100,6 +100,7 @@ svm - vm library vlib - vector processing library vlib-api - binary API library vnet - network stack library +vpp_crypto_engines - cryptography libraries %package devel Summary: VPP header files, static libraries @@ -202,6 +203,7 @@ install -p -m 644 %{_mu_build_dir}/../src/vpp/conf/80-vpp.conf %{buildroot}/etc/ mkdir -p -m755 %{buildroot}%{_libdir} mkdir -p -m755 %{buildroot}/etc/bash_completion.d mkdir -p -m755 %{buildroot}/usr/share/vpp +mkdir -p -m755 %{buildroot}/usr/%{_lib}/vpp_crypto_engines for file in $(find %{_mu_build_dir}/%{_vpp_install_dir}/*/lib* -type f -name '*.so.*.*' -print ) do install -p -m 755 $file %{buildroot}%{_libdir} @@ -218,6 +220,11 @@ for file in $(find %{_mu_build_dir}/%{_vpp_install_dir}/vpp/share/vpp/api -type do install -p -m 644 $file %{buildroot}/usr/share/vpp/api done +for file in $(cd %{_mu_build_dir}/%{_vpp_install_dir}/vpp/%{_lib}/vpp_crypto_engines && find -type f -print) +do + install -p -m 755 %{_mu_build_dir}/%{_vpp_install_dir}/vpp/%{_lib}/vpp_crypto_engines/$file \ + %{buildroot}/usr/%{_lib}/vpp_crypto_engines/$file +done # Lua bindings mkdir -p -m755 %{buildroot}/usr/share/doc/vpp/examples/lua/examples/cli @@ -385,8 +392,14 @@ fi %exclude %{_libdir}/vpp_plugins %exclude %{_libdir}/vpp_api_test_plugins %exclude %{_libdir}/vat2_plugins +%exclude %{_libdir}/libefa.so* +%exclude %{_libdir}/libibverbs.so* +%exclude %{_libdir}/libmana.so* +%exclude %{_libdir}/libmlx4.so* +%exclude %{_libdir}/libmlx5.so* %{_libdir}/* /usr/share/vpp/api/* +/usr/%{_lib}/vpp_crypto_engines/* %files api-lua %defattr(644,root,root,644) @@ -416,4 +429,4 @@ fi /usr/%{_lib}/vpp_plugins/* /usr/%{_lib}/vpp_api_test_plugins/* /usr/%{_lib}/vat2_plugins/* -/usr/share/vpp/api/* +/usr/share/vpp/api/* \ No newline at end of file From 0cd6e7c4d75dd46c9b2350a926927c28a875f5b6 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 1 Aug 2025 05:51:44 -0400 Subject: [PATCH 189/313] http: http2_transport_rx_callback hardening When we receive extra data bytes handle it as connection error to prevent data leakage. Type: improvement Change-Id: I1316d019b252faa29a818b4aeff5d1d5752719e2 Signed-off-by: Matus Fabian --- extras/hs-test/h2spec_extras/h2spec_extras.go | 18 ++++++++++++++++++ extras/hs-test/infra/suite_http2.go | 1 + src/plugins/http/http2/http2.c | 8 ++++++++ 3 files changed, 27 insertions(+) diff --git a/extras/hs-test/h2spec_extras/h2spec_extras.go b/extras/hs-test/h2spec_extras/h2spec_extras.go index b2c5c85fb6..db7d4d514c 100644 --- a/extras/hs-test/h2spec_extras/h2spec_extras.go +++ b/extras/hs-test/h2spec_extras/h2spec_extras.go @@ -35,6 +35,7 @@ func Spec() *spec.TestGroup { tg.AddTestGroup(FlowControl()) tg.AddTestGroup(ConnectMethod()) tg.AddTestGroup(ExtendedConnectMethod()) + tg.AddTestGroup(PingAnomaly()) return tg } @@ -937,3 +938,20 @@ func ConnectUdp() *spec.TestGroup { return tg } + +func PingAnomaly() *spec.TestGroup { + tg := NewTestGroup("4", "Data Leakage") + tg.AddTestCase(&spec.TestCase{ + Desc: "1-byte extra", + Requirement: "The endpoint MUST terminate the connection with a connection error of type PROTOCOL_ERROR.", + Run: func(c *config.Config, conn *spec.Conn) error { + err := conn.Handshake() + if err != nil { + return err + } + conn.Send([]byte("\x00\x00\x08\x06\x00\x00\x00\x00\x00\x00\xDE\xAD\xBE\xEF\xDE\xAD\xBE\xEF")) + return spec.VerifyConnectionError(conn, http2.ErrCodeProtocol) + }, + }) + return tg +} diff --git a/extras/hs-test/infra/suite_http2.go b/extras/hs-test/infra/suite_http2.go index 69739bc8a6..69b6bfdf56 100644 --- a/extras/hs-test/infra/suite_http2.go +++ b/extras/hs-test/infra/suite_http2.go @@ -391,6 +391,7 @@ var http2Tests = []h2specTest{ var extrasTests = []h2specTest{ {desc: "extras/1/1"}, {desc: "extras/1/2"}, + {desc: "extras/4/1"}, } const ( diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 880d31bbaf..45e2f821fe 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2882,6 +2882,14 @@ http2_transport_rx_callback (http_conn_t *hc) http_io_ts_drain (hc, HTTP2_FRAME_HEADER_SIZE); to_deq -= fh.length; + /* to prevent data leakage */ + if (to_deq && to_deq < HTTP2_FRAME_HEADER_SIZE) + { + HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq); + http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); + return; + } + HTTP_DBG (1, "frame type 0x%02x len %u", fh.type, fh.length); if ((h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION) && From d21309c22b070fd0c148596a63a8c353ed442c71 Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Wed, 23 Jul 2025 09:18:23 -0700 Subject: [PATCH 190/313] af_xdp: bump xdp-tools to 1.5.5 Type: feature Change-Id: Ia6d32305045f51b859c4b888f5912f8d4cbbd766 Signed-off-by: Steven Luong --- build/external/packages/xdp-tools.mk | 4 +-- ...ibxdp-add-fPIC-with-static-lib-build.patch | 26 +++++++++++++++++++ ...maybe-uninitialized-compiler-warning.patch | 26 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 build/external/patches/xdp-tools_1.5.5/0001-libxdp-add-fPIC-with-static-lib-build.patch create mode 100644 build/external/patches/xdp-tools_1.5.5/0003-libxdp-fix-maybe-uninitialized-compiler-warning.patch diff --git a/build/external/packages/xdp-tools.mk b/build/external/packages/xdp-tools.mk index 57f5e0ae83..f33c220e5b 100644 --- a/build/external/packages/xdp-tools.mk +++ b/build/external/packages/xdp-tools.mk @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -xdp-tools_version := 1.2.9 +xdp-tools_version := 1.5.5 xdp-tools_tarball := xdp-tools-$(xdp-tools_version).tar.gz -xdp-tools_tarball_sha256sum_1.2.9 := 3f8d30bd2e00c522103d224014f59a95400144aba1f3b322c6ad473541a0f99e +xdp-tools_tarball_sha256sum_1.5.5 := 9a4339ffc40df178c4ddf919cb2b23585a75b3023517c75e82c4dfb0899249c7 xdp-tools_tarball_sha256sum := $(xdp-tools_tarball_sha256sum_$(xdp-tools_version)) xdp-tools_tarball_strip_dirs := 1 diff --git a/build/external/patches/xdp-tools_1.5.5/0001-libxdp-add-fPIC-with-static-lib-build.patch b/build/external/patches/xdp-tools_1.5.5/0001-libxdp-add-fPIC-with-static-lib-build.patch new file mode 100644 index 0000000000..e0e6050de7 --- /dev/null +++ b/build/external/patches/xdp-tools_1.5.5/0001-libxdp-add-fPIC-with-static-lib-build.patch @@ -0,0 +1,26 @@ +From e83f80443a2f23a68037bf4c7ba16b3723d193a4 Mon Sep 17 00:00:00 2001 +From: Yulong +Date: Tue, 3 Jan 2023 14:16:17 +0000 +Subject: [PATCH] libxdp: add fPIC with static lib build + +Signed-off-by: Yulong +--- + lib/libxdp/Makefile | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/libxdp/Makefile b/lib/libxdp/Makefile +index 4716fb0..403c2d9 100644 +--- a/lib/libxdp/Makefile ++++ b/lib/libxdp/Makefile +@@ -87,7 +87,7 @@ $(SHARED_OBJDIR): + $(Q)mkdir -p $(SHARED_OBJDIR) + + $(STATIC_OBJDIR)/%.o: %.c $(EXTRA_LIB_DEPS) | $(STATIC_OBJDIR) +- $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(STATIC_CFLAGS) -Wall -I../../headers -c $< -o $@ ++ $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(STATIC_CFLAGS) -fPIC -Wall -I../../headers -c $< -o $@ + + $(SHARED_OBJDIR)/%.o: %.c $(EXTRA_LIB_DEPS) | $(SHARED_OBJDIR) + $(QUIET_CC)$(CC) $(CFLAGS) $(CPPFLAGS) $(SHARED_CFLAGS) -Wall -I../../headers -c $< -o $@ +-- +2.25.1 + diff --git a/build/external/patches/xdp-tools_1.5.5/0003-libxdp-fix-maybe-uninitialized-compiler-warning.patch b/build/external/patches/xdp-tools_1.5.5/0003-libxdp-fix-maybe-uninitialized-compiler-warning.patch new file mode 100644 index 0000000000..3927c534f4 --- /dev/null +++ b/build/external/patches/xdp-tools_1.5.5/0003-libxdp-fix-maybe-uninitialized-compiler-warning.patch @@ -0,0 +1,26 @@ +From 3033b9bdbcdb270f15373b27933d554f847e01d4 Mon Sep 17 00:00:00 2001 +From: Yulong +Date: Fri, 6 Jan 2023 14:31:24 +0000 +Subject: [PATCH 3/3] libxdp: fix maybe-uninitialized compiler warning + +Signed-off-by: Yulong +--- + lib/common.mk | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/common.mk b/lib/common.mk +index ce24c48..e964bd8 100644 +--- a/lib/common.mk ++++ b/lib/common.mk +@@ -104,7 +104,7 @@ $(LIB_OBJS): %.o: %.c %.h $(LIB_H) + + ALL_EXEC_TARGETS=$(USER_TARGETS) $(TEST_TARGETS) + $(ALL_EXEC_TARGETS): %: %.c $(OBJECT_LIBBPF) $(OBJECT_LIBXDP) $(LIBMK) $(LIB_OBJS) $(KERN_USER_H) $(EXTRA_DEPS) $(EXTRA_USER_DEPS) $(BPF_SKEL_H) $(USER_EXTRA_C) +- $(QUIET_CC)$(CC) -Wall $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(LIB_OBJS) \ ++ $(QUIET_CC)$(CC) -Wall -Wno-maybe-uninitialized $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(LIB_OBJS) \ + $< $(USER_EXTRA_C) $(LDLIBS) + + $(XDP_OBJ): %.o: %.c $(KERN_USER_H) $(EXTRA_DEPS) $(BPF_HEADERS) $(LIBMK) +-- +2.25.1 + From 4e93a82bf57e606ba3abf845e87891342f0a3d0f Mon Sep 17 00:00:00 2001 From: Steven Luong Date: Wed, 9 Jul 2025 15:59:59 -0700 Subject: [PATCH 191/313] af_xdp: processing free buffer af_xdp does not support big packets and they got dropped. When that happens, we need to bump up the buffer pointer and decrement packet count prior to going back to the while loop. Type: fix Change-Id: I4722b15eb7ba5467f37e52ab532998c35caa3e2e Signed-off-by: Steven Luong --- src/plugins/af_xdp/output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/af_xdp/output.c b/src/plugins/af_xdp/output.c index a59c01ca6e..00dc0e50c2 100644 --- a/src/plugins/af_xdp/output.c +++ b/src/plugins/af_xdp/output.c @@ -209,6 +209,8 @@ af_xdp_device_output_tx_try (vlib_main_t * vm, af_xdp_log (VLIB_LOG_LEVEL_ERR, ad, "vlib_buffer_chain_linearize failed"); vlib_buffer_free_one (vm, vlib_get_buffer_index (vm, b[0])); + b += 1; + n -= 1; continue; } } From ccd16e3f7b5525dafdc29f043e0ba8faef57eb12 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Tue, 22 Jul 2025 13:47:30 +0000 Subject: [PATCH 192/313] session: enable sending segmented dgrams Split app_send_dgram_raw_gso into app_send_dgram_segs_raw and app_gen_dgram_header. The former sends out previously prepared segmented dgram, while the latter fills in the header in a prepared segment array. Additionally, app_send_dgram_segs was introduced as an easier API for sending out segmented data. Type: improvement Change-Id: I3afdd9b974a7dbf936b7b7c07873ac72262525d9 Signed-off-by: Semir Sionek --- src/vnet/session/application_interface.h | 87 ++++++++++++++++++------ 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index 2896392376..fe83265d3e 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -627,32 +627,44 @@ app_send_io_evt_to_vpp (svm_msg_q_t * mq, u32 session_index, u8 evt_type, } } -#define app_send_dgram_raw(f, at, vpp_evt_q, data, len, evt_type, do_evt, \ - noblock) \ - app_send_dgram_raw_gso (f, at, vpp_evt_q, data, len, 0, evt_type, do_evt, \ - noblock) +/* NOTE: Make sure the segs parameter has the first element of the array empty +to write-in the header */ +always_inline u32 +app_gen_dgram_header (svm_fifo_seg_t *segs, u32 data_len, + app_session_transport_t *at, u16 gso_size) +{ + session_dgram_hdr_t *hdr = (session_dgram_hdr_t *) segs[0].data; + u32 dgram_len = data_len; + + ASSERT (segs != NULL); + ASSERT (segs[0].data != NULL); + ASSERT (segs[0].len == sizeof (session_dgram_hdr_t)); + + hdr->data_length = data_len; + hdr->data_offset = 0; + clib_memcpy_fast (&hdr->rmt_ip, &at->rmt_ip, sizeof (ip46_address_t)); + hdr->is_ip4 = at->is_ip4; + hdr->rmt_port = at->rmt_port; + clib_memcpy_fast (&hdr->lcl_ip, &at->lcl_ip, sizeof (ip46_address_t)); + hdr->lcl_port = at->lcl_port; + hdr->gso_size = gso_size; + dgram_len += segs[0].len; + + return dgram_len; +} always_inline int -app_send_dgram_raw_gso (svm_fifo_t *f, app_session_transport_t *at, - svm_msg_q_t *vpp_evt_q, u8 *data, u32 len, - u16 gso_size, u8 evt_type, u8 do_evt, u8 noblock) +app_send_dgram_segs_raw (svm_fifo_t *f, app_session_transport_t *at, + svm_msg_q_t *vpp_evt_q, svm_fifo_seg_t *segs, + u32 nsegs, u32 seg_len, u8 evt_type, u8 do_evt, + u8 noblock) { - session_dgram_hdr_t hdr; int rv; - if (svm_fifo_max_enqueue_prod (f) < (sizeof (session_dgram_hdr_t) + len)) + + if (svm_fifo_max_enqueue_prod (f) < seg_len) return 0; - hdr.data_length = len; - hdr.data_offset = 0; - clib_memcpy_fast (&hdr.rmt_ip, &at->rmt_ip, sizeof (ip46_address_t)); - hdr.is_ip4 = at->is_ip4; - hdr.rmt_port = at->rmt_port; - clib_memcpy_fast (&hdr.lcl_ip, &at->lcl_ip, sizeof (ip46_address_t)); - hdr.lcl_port = at->lcl_port; - hdr.gso_size = gso_size; - svm_fifo_seg_t segs[2] = {{ (u8 *) &hdr, sizeof (hdr) }, { data, len }}; - - rv = svm_fifo_enqueue_segments (f, segs, 2, 0 /* allow partial */ ); + rv = svm_fifo_enqueue_segments (f, segs, nsegs, 0 /* allow partial */); if (PREDICT_FALSE (rv < 0)) return 0; @@ -662,9 +674,26 @@ app_send_dgram_raw_gso (svm_fifo_t *f, app_session_transport_t *at, app_send_io_evt_to_vpp (vpp_evt_q, f->vpp_session_index, evt_type, noblock); } - return len; + return seg_len - sizeof (session_dgram_hdr_t); +} + +always_inline int +app_send_dgram_raw_gso (svm_fifo_t *f, app_session_transport_t *at, + svm_msg_q_t *vpp_evt_q, u8 *data, u32 len, + u16 gso_size, u8 evt_type, u8 do_evt, u8 noblock) +{ + session_dgram_hdr_t hdr; + svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, { data, len } }; + u32 seg_len = app_gen_dgram_header (segs, len, at, gso_size); + return app_send_dgram_segs_raw (f, at, vpp_evt_q, segs, 2, seg_len, evt_type, + do_evt, noblock); } +#define app_send_dgram_raw(f, at, vpp_evt_q, data, len, evt_type, do_evt, \ + noblock) \ + app_send_dgram_raw_gso (f, at, vpp_evt_q, data, len, 0, evt_type, do_evt, \ + noblock) + always_inline int app_send_dgram (app_session_t * s, u8 * data, u32 len, u8 noblock) { @@ -673,6 +702,22 @@ app_send_dgram (app_session_t * s, u8 * data, u32 len, u8 noblock) noblock); } +always_inline int +app_send_dgram_segs (app_session_t *s, svm_fifo_seg_t *segs, u32 data_nsegs, + u32 data_len, u8 noblock) +{ + session_dgram_hdr_t hdr; + ASSERT (segs != NULL); + ASSERT (segs[0].len == 0); + segs[0].data = (u8 *) &hdr; + segs[0].len = sizeof (hdr); + + u32 seg_len = app_gen_dgram_header (segs, data_len, &s->transport, 0); + return app_send_dgram_segs_raw (s->tx_fifo, &s->transport, s->vpp_evt_q, + segs, data_nsegs, seg_len, SESSION_IO_EVT_TX, + 1 /* do_evt */, noblock); +} + always_inline int app_send_stream_raw (svm_fifo_t * f, svm_msg_q_t * vpp_evt_q, u8 * data, u32 len, u8 evt_type, u8 do_evt, u8 noblock) From ae77ea9360809290b215607f88d7490a204c6e7d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 5 Aug 2025 10:58:02 -0400 Subject: [PATCH 193/313] http: h2 client tunnel fix Type: fix Change-Id: I7e86578c0430bc90cdff4668f8469a303d0bbc7e Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 45e2f821fe..103f149a21 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1160,7 +1160,8 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, } else { - req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; + if (!req->base.is_tunnel) + req->stream_state = HTTP2_STREAM_STATE_HALF_CLOSED; http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_TRANSPORT_REPLY); } @@ -1328,6 +1329,7 @@ http2_req_state_wait_transport_reply (http_conn_t *hc, http2_req_t *req, else new_state = HTTP_REQ_STATE_TUNNEL; http_io_as_add_want_read_ntf (&req->base); + transport_connection_reschedule (&req->base.connection); /* cleanup some stuff we don't need anymore in tunnel mode */ vec_free (req->base.headers); } @@ -2185,7 +2187,7 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) HTTP_DBG (1, "END_STREAM flag set"); if (req->base.is_tunnel) { - /* client can initiate or confirm tunnel close */ + /* peer can initiate or confirm tunnel close */ req->stream_state = req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED ? HTTP2_STREAM_STATE_CLOSED : From 1709c217b37524a0e1a0232f26605d04e2a3bab3 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 5 Aug 2025 15:40:55 -0400 Subject: [PATCH 194/313] http: h2 client connect-udp fix Type: fix Change-Id: I1ce22f70ecd609368ebaee351cbe1d260a6c2536 Signed-off-by: Matus Fabian --- src/plugins/http/http.c | 1 + src/plugins/http/http2/http2.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 094260f1ed..7341bdf7e7 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -896,6 +896,7 @@ 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; + hc->udp_tunnel_mode = http_cfg->udp_tunnel_mode; if (http_cfg->flags & HTTP_ENDPT_CFG_F_HTTP2_PRIOR_KNOWLEDGE) { HTTP_DBG (1, "app want http2 with prior knowledge"); diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 103f149a21..24d183d818 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1062,6 +1062,7 @@ http2_sched_dispatch_req_headers (http2_req_t *req, http_conn_t *hc, { req->base.is_tunnel = 1; req->dispatch_data_cb = http2_sched_dispatch_tunnel; + req->base.upgrade_proto = msg.data.upgrade_proto; if (msg.data.upgrade_proto != HTTP_UPGRADE_PROTO_NA) { if (hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) From 84d52285afd1b478d616026a3d63a714abb29f13 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Wed, 16 Jul 2025 12:54:25 +0000 Subject: [PATCH 195/313] hsa: show tx/rx stats for UDP on timeout and fix test-bytes for UDP We shouldn't assume that all the dgrams will be transported between server and client. This patch addresses this issue in two places: 1. Now if we hit the timeout on UDP client rx (not all bytes transported), instead of an error - we display tx/rx byte stats. 2. For test-bytes mode, we now include a 4 byte buffer offset with each dgram to check against for data corruption. Previous solution was based on the assumption that data will arrive in sequence, which is just not the case for UDP. Type: fix Change-Id: I1a2ac8afe4180830b32f4ea67b4b477f167e0800 Signed-off-by: Semir Sionek --- extras/hs-test/echo_test.go | 21 ++++++++- src/plugins/hs_apps/echo_client.c | 76 +++++++++++++++++++++++++++---- src/plugins/hs_apps/echo_client.h | 1 + src/plugins/hs_apps/echo_server.c | 20 +++++--- 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index ee510545da..65bd49f825 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -8,7 +8,7 @@ import ( ) func init() { - RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest) + RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest, EchoBuiltinTestbytesTest) RegisterSoloVethTests(TcpWithLossTest) RegisterVeth6Tests(TcpWithLoss6Test) } @@ -99,6 +99,25 @@ func EchoBuiltinEchobytesTest(s *VethsSuite) { s.AssertNotContains(o, "test echo clients: failed: timeout with 1 sessions") } +func EchoBuiltinTestbytesTest(s *VethsSuite) { + serverVpp := s.Containers.ServerVpp.VppInstance + + serverVpp.Vppctl("test echo server " + + " uri udp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + + clientVpp := s.Containers.ClientVpp.VppInstance + + // Add loss of packets with Network Delay Simulator + clientVpp.Vppctl("set nsim poll-main-thread delay 0.1 ms bandwidth 10 mbps packet-size 1460 packets-per-drop 125") + clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name()) + + o := clientVpp.Vppctl("test echo client echo-bytes test-bytes verbose bytes 32k test-timeout 1 uri" + + " udp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + s.Log(o) + s.AssertNotContains(o, "failed") + s.AssertContains(o, " bytes out of 32768 sent (32768 target)") +} + func TcpWithLossTest(s *VethsSuite) { serverVpp := s.Containers.ServerVpp.VppInstance diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 2612e2a389..99d69280bd 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -95,9 +95,9 @@ static void send_data_chunk (ec_main_t *ecm, ec_session_t *es) { u8 *test_data = ecm->connect_test_data; - int test_buf_len, test_buf_offset, rv; + int test_buf_len, rv; u64 bytes_to_send; - u32 bytes_this_chunk; + u32 bytes_this_chunk, test_buf_offset; svm_fifo_t *f = es->tx_fifo; test_buf_len = vec_len (test_data); @@ -160,9 +160,24 @@ send_data_chunk (ec_main_t *ecm, ec_session_t *es) bytes_this_chunk = clib_min (bytes_this_chunk, max_enqueue); if (!ecm->throughput) bytes_this_chunk = clib_min (bytes_this_chunk, 1460); - rv = - app_send_dgram ((app_session_t *) es, test_data + test_buf_offset, - bytes_this_chunk, 0); + if (ecm->cfg.test_bytes) + { + /* Include buffer offset info to also be able to verify + * out-of-order packets */ + svm_fifo_seg_t data_segs[3] = { + { NULL, 0 }, + { (u8 *) &test_buf_offset, sizeof (u32) }, + { test_data + test_buf_offset, bytes_this_chunk } + }; + rv = app_send_dgram_segs ((app_session_t *) es, data_segs, 2, + bytes_this_chunk + sizeof (u32), 0); + if (rv) + rv -= sizeof (u32); + } + else + rv = app_send_dgram ((app_session_t *) es, + test_data + test_buf_offset, bytes_this_chunk, + 0); } } @@ -207,11 +222,19 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) svm_fifo_t *rx_fifo = es->rx_fifo; session_dgram_pre_hdr_t ph; int n_read, i; + u8 *rx_buf_start = wrk->rx_buf; + u32 test_buf_offset = es->bytes_received; if (ecm->cfg.test_bytes) { n_read = app_recv ((app_session_t *) es, wrk->rx_buf, vec_len (wrk->rx_buf)); + if (ecm->transport_proto != TRANSPORT_PROTO_TCP) + { + test_buf_offset = *(u32 *) wrk->rx_buf; + rx_buf_start = wrk->rx_buf + sizeof (u32); + n_read -= sizeof (u32); + } } else { @@ -261,11 +284,11 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) { for (i = 0; i < n_read; i++) { - if (wrk->rx_buf[i] != ((es->bytes_received + i) & 0xff)) + if (rx_buf_start[i] != ((test_buf_offset + i) & 0xff)) { ec_err ("read %d error at byte %lld, 0x%x not 0x%x", n_read, - es->bytes_received + i, wrk->rx_buf[i], - ((es->bytes_received + i) & 0xff)); + test_buf_offset + i, rx_buf_start[i], + ((test_buf_offset + i) & 0xff)); ecm->test_failed = 1; } } @@ -802,6 +825,9 @@ ec_session_connected_callback (u32 app_index, u32 api_context, session_t *s, es->vpp_session_index = s->session_index; es->bytes_paced_target = ~0; es->bytes_paced_current = ~0; + if (ecm->transport_proto != TRANSPORT_PROTO_TCP && ecm->cfg.test_bytes) + vec_validate (es->test_send_buffer, + ecm->max_chunk_bytes + sizeof (int) + 1); s->opaque = es->session_index; vec_add1 (wrk->conn_indices, es->session_index); @@ -843,8 +869,14 @@ static void ec_session_disconnect_callback (session_t *s) { ec_main_t *ecm = &ec_main; + ec_worker_t *wrk; + ec_session_t *es; vnet_disconnect_args_t _a = { 0 }, *a = &_a; + wrk = ec_worker_get (s->thread_index); + es = ec_session_get (wrk, s->opaque); + vec_free (es->test_send_buffer); + if (session_handle (s) == ecm->ctrl_session_handle) { ec_dbg ("ctrl session disconnect"); @@ -861,7 +893,14 @@ void ec_session_disconnect (session_t *s) { ec_main_t *ecm = &ec_main; + ec_worker_t *wrk; + ec_session_t *es; vnet_disconnect_args_t _a = { 0 }, *a = &_a; + + wrk = ec_worker_get (s->thread_index); + es = ec_session_get (wrk, s->opaque); + vec_free (es->test_send_buffer); + a->handle = session_handle (s); a->app_index = ecm->app_index; vnet_disconnect_session (a); @@ -1221,6 +1260,8 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, int rv, timed_run_conflict = 0, tput_conflict = 0, had_config = 1; u64 total_bytes; f64 delta; + ec_worker_t *wrk; + ec_session_t *sess; if (ecm->test_client_attached) return clib_error_return (0, "failed: already running!"); @@ -1416,8 +1457,23 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, case ~0: ec_cli ("Timeout at %.6f with %d sessions still active...", vlib_time_now (ecm->vlib_main), ecm->ready_connections); - error = clib_error_return (0, "failed: timeout with %d sessions", - ecm->ready_connections); + if (ecm->transport_proto == TRANSPORT_PROTO_UDP) + { + u64 received_bytes = 0; + u64 sent_bytes = 0; + vec_foreach (wrk, ecm->wrk) + pool_foreach (sess, wrk->sessions) + { + received_bytes += sess->bytes_received; + sent_bytes += sess->bytes_sent; + } + ec_cli ("Received %llu bytes out of %llu sent (%llu target)", + received_bytes, sent_bytes, + ecm->bytes_to_send * ecm->n_clients); + } + else + error = clib_error_return (0, "failed: timeout with %d sessions", + ecm->ready_connections); goto stop_test; case EC_CLI_TEST_DONE: diff --git a/src/plugins/hs_apps/echo_client.h b/src/plugins/hs_apps/echo_client.h index 01b807a946..3b3fb7626e 100644 --- a/src/plugins/hs_apps/echo_client.h +++ b/src/plugins/hs_apps/echo_client.h @@ -49,6 +49,7 @@ typedef struct ec_session_ u64 bytes_paced_current; f64 send_rtt; u8 rtt_stat; + u8 *test_send_buffer; } ec_session_t; typedef struct ec_worker_ diff --git a/src/plugins/hs_apps/echo_server.c b/src/plugins/hs_apps/echo_server.c index 0585132991..abf557c33e 100644 --- a/src/plugins/hs_apps/echo_server.c +++ b/src/plugins/hs_apps/echo_server.c @@ -352,18 +352,17 @@ echo_server_builtin_server_rx_callback_no_echo (session_t * s) } static void -es_test_bytes (es_worker_t *wrk, es_session_t *es, int actual_transfer) +es_test_bytes (u8 *rx_buf, int actual_transfer, u32 offset) { int i; for (i = 0; i < actual_transfer; i++) { - if (wrk->rx_buf[i] != ((es->byte_index + i) & 0xff)) + if (rx_buf[i] != ((offset + i) & 0xff)) { - es_err ("at %lld expected %d got %d", es->byte_index + i, - (es->byte_index + i) & 0xff, wrk->rx_buf[i]); + es_err ("at %lld expected %d got %d", offset + i, + (offset + i) & 0xff, rx_buf[i]); } } - es->byte_index += actual_transfer; } int @@ -439,11 +438,20 @@ echo_server_rx_callback (session_t * s) vec_validate (wrk->rx_buf, max_transfer); actual_transfer = app_recv ((app_session_t *) es, wrk->rx_buf, max_transfer); + if (!actual_transfer) + return 0; ASSERT (actual_transfer == max_transfer); if (esm->cfg.test_bytes) { - es_test_bytes (wrk, es, actual_transfer); + if (esm->transport_proto == TRANSPORT_PROTO_TCP) + { + es_test_bytes (wrk->rx_buf, actual_transfer, es->byte_index); + es->byte_index += actual_transfer; + } + else + es_test_bytes ((wrk->rx_buf + sizeof (u32)), + actual_transfer - sizeof (u32), *(u32 *) wrk->rx_buf); } /* From 8adb8a7ce296426c9081ff56b90f5eb972f2dfe3 Mon Sep 17 00:00:00 2001 From: Hadi Rayan Al-Sandid Date: Tue, 10 Dec 2024 11:05:26 +0100 Subject: [PATCH 196/313] api: fix crash in pcap capture api pcap api cleanup can cause crash if invalid sw_if_index is provided. This fix is similar to https://gerrit.fd.io/r/c/vpp/+/41936 , which only fixed the invalid filename use-case. Type: fix Change-Id: I34ce178a4cb7fc92b4e4bb399a3fabd4efc85159 Signed-off-by: Hadi Rayan Al-Sandid --- src/vnet/interface_api.c | 4 ++-- test/test_pcap.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vnet/interface_api.c b/src/vnet/interface_api.c index d835a36f46..ada1245439 100644 --- a/src/vnet/interface_api.c +++ b/src/vnet/interface_api.c @@ -1628,8 +1628,6 @@ vl_api_pcap_trace_on_t_handler (vl_api_pcap_trace_on_t *mp) vnet_pcap_dispatch_trace_args_t capture_args; int rv = 0; - VALIDATE_SW_IF_INDEX (mp); - unformat_init_cstring (&filename, (char *) mp->filename); if (!unformat_user (&filename, unformat_vlib_tmpfile, &capture_args.filename)) @@ -1638,6 +1636,8 @@ vl_api_pcap_trace_on_t_handler (vl_api_pcap_trace_on_t *mp) goto out; } + VALIDATE_SW_IF_INDEX (mp); + capture_args.rx_enable = mp->capture_rx; capture_args.tx_enable = mp->capture_tx; capture_args.preallocate_data = mp->preallocate_data; diff --git a/test/test_pcap.py b/test/test_pcap.py index 94418081ac..4daf23a7fa 100644 --- a/test/test_pcap.py +++ b/test/test_pcap.py @@ -190,6 +190,16 @@ def test_pcap_trace_api(self): sw_if_index=0, ) + # Attempting to start a trace with an invalid sw_if_index should return an error + with self.vapi.assert_negative_api_retval(): + self.vapi.pcap_trace_on( + capture_rx=True, + capture_tx=True, + filter=True, + max_packets=1000, + sw_if_index=0xFFFFFF, + ) + if __name__ == "__main__": unittest.main(testRunner=VppTestRunner) From 8941f9ade958d10cdfe0d94de84fd215ebc3968d Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Mon, 4 Aug 2025 14:02:56 +0200 Subject: [PATCH 197/313] hs-test: load k8s pod definitions from a file - improved pod teardown, fixed obtaining IP addresses from pods - added contexts for better goroutine management Type: improvement Change-Id: Iab8bd76598465c44626188d47b57d561469e279b Signed-off-by: Adrian Villin --- extras/hs-test/infra/kind/deployment.go | 5 +- extras/hs-test/infra/kind/pod.go | 119 +++++++++++------- extras/hs-test/infra/kind/suite_kind.go | 22 ++-- extras/hs-test/kind_test.go | 46 ++++--- .../hs-test/kubernetes/pod-definitions.yaml | 75 +++++++++++ 5 files changed, 195 insertions(+), 72 deletions(-) create mode 100644 extras/hs-test/kubernetes/pod-definitions.yaml diff --git a/extras/hs-test/infra/kind/deployment.go b/extras/hs-test/infra/kind/deployment.go index 20fc6c80c3..847e960576 100644 --- a/extras/hs-test/infra/kind/deployment.go +++ b/extras/hs-test/infra/kind/deployment.go @@ -41,6 +41,7 @@ func (s *KindSuite) createNamespace(name string) { } func (s *KindSuite) deletePod(namespace string, podName string) error { + delete(s.CurrentlyRunning, podName) return s.ClientSet.CoreV1().Pods(namespace).Delete(context.TODO(), podName, metav1.DeleteOptions{GracePeriodSeconds: int64Ptr(0)}) } @@ -49,8 +50,6 @@ func (s *KindSuite) deleteNamespace(namespace string) error { } func (s *KindSuite) DeployPod(pod *Pod) { - pod.suite = s - s.CurrentlyRunning = append(s.CurrentlyRunning, pod.Name) pod.CreatedPod = &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Namespace: s.Namespace, @@ -86,10 +85,12 @@ func (s *KindSuite) DeployPod(pod *Pod) { // Create the Pod _, err := s.ClientSet.CoreV1().Pods(s.Namespace).Create(context.TODO(), pod.CreatedPod, metav1.CreateOptions{}) s.AssertNil(err) + s.CurrentlyRunning[pod.Name] = pod s.Log("Pod '%s' created", pod.Name) // Get IP s.Log("Obtaining IP from '%s'", pod.Name) + pod.IpAddress = "" counter := 1 for pod.IpAddress == "" { pod.CreatedPod, err = s.ClientSet.CoreV1().Pods(s.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) diff --git a/extras/hs-test/infra/kind/pod.go b/extras/hs-test/infra/kind/pod.go index f323130af2..4a8d501133 100644 --- a/extras/hs-test/infra/kind/pod.go +++ b/extras/hs-test/infra/kind/pod.go @@ -5,10 +5,11 @@ import ( "context" "os" "os/exec" + "slices" "text/template" - "time" . "fd.io/hs-test/infra/common" + "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/tools/remotecommand" ) @@ -19,53 +20,79 @@ type Pod struct { Image string ContainerName string Worker string + Namespace string IpAddress string CreatedPod *corev1.Pod } -// Sets pod names, image names, namespace name +type Image struct { + Name string `yaml:"name"` +} +type Container struct { + Name string `yaml:"name"` +} +type Worker struct { + Name string `yaml:"name"` +} +type Namespace struct { + Name string `yaml:"name"` +} +type PodYaml struct { + Name string `yaml:"name"` + Image []Image `yaml:"image"` + Container []Container `yaml:"container"` + Worker []Worker `yaml:"worker"` + Namespace []Namespace `yaml:"namespace"` +} +type Config struct { + Pods []PodYaml `yaml:"pods"` +} + +func (s *KindSuite) LoadPodConfigs() { + data, err := os.ReadFile("kubernetes/pod-definitions.yaml") + s.AssertNil(err) + + var config Config + err = yaml.Unmarshal(data, &config) + s.AssertNil(err) + + for _, podData := range config.Pods { + newPod(s, podData) + } +} + +func newPod(suite *KindSuite, input PodYaml) (*Pod, error) { + var pod = new(Pod) + pod.suite = suite + pod.Name = input.Name + suite.Ppid + pod.Image = input.Image[0].Name + pod.ContainerName = input.Container[0].Name + pod.Worker = input.Worker[0].Name + pod.Namespace = input.Namespace[0].Name + suite.Ppid + + if suite.AllPods == nil { + suite.AllPods = make(map[string]*Pod) + suite.Namespace = pod.Namespace + } + + suite.AllPods[pod.Name] = pod + if !slices.Contains(suite.images, pod.Image) { + suite.images = append(suite.images, pod.Image) + } + + return pod, nil +} + func (s *KindSuite) initPods() { - wrk1 := "kind-worker" - wrk2 := "kind-worker2" - vppImg := "hs-test/vpp:latest" - nginxLdpImg := "hs-test/nginx-ldp:latest" - abImg := "hs-test/ab:latest" - clientCont := "client" - serverCont := "server" - - // TODO: load from file - s.images = append(s.images, vppImg, nginxLdpImg, abImg) - s.Namespace = "namespace" + s.Ppid - - s.Pods.ClientGeneric = new(Pod) - s.Pods.ClientGeneric.Name = "client" + s.Ppid - s.Pods.ClientGeneric.Image = vppImg - s.Pods.ClientGeneric.ContainerName = clientCont - s.Pods.ClientGeneric.Worker = wrk1 - - s.Pods.ServerGeneric = new(Pod) - s.Pods.ServerGeneric.Name = "server" + s.Ppid - s.Pods.ServerGeneric.Image = vppImg - s.Pods.ServerGeneric.ContainerName = serverCont - s.Pods.ServerGeneric.Worker = wrk2 - - s.Pods.Ab = new(Pod) - s.Pods.Ab.Name = "ab" + s.Ppid - s.Pods.Ab.Image = abImg - s.Pods.Ab.ContainerName = clientCont - s.Pods.Ab.Worker = wrk1 - - s.Pods.Nginx = new(Pod) - s.Pods.Nginx.Name = "nginx-ldp" + s.Ppid - s.Pods.Nginx.Image = nginxLdpImg - s.Pods.Nginx.ContainerName = serverCont - s.Pods.Nginx.Worker = wrk2 - - s.Pods.NginxProxy = new(Pod) - s.Pods.NginxProxy.Name = "nginx-proxy" + s.Ppid - s.Pods.NginxProxy.Image = nginxLdpImg - s.Pods.NginxProxy.ContainerName = serverCont - s.Pods.NginxProxy.Worker = wrk2 + s.Pods.Ab = s.getPodsByName("ab") + s.Pods.ClientGeneric = s.getPodsByName("client-generic") + s.Pods.ServerGeneric = s.getPodsByName("server-generic") + s.Pods.Nginx = s.getPodsByName("nginx-ldp") + s.Pods.NginxProxy = s.getPodsByName("nginx-proxy") +} + +func (s *KindSuite) getPodsByName(podName string) *Pod { + return s.AllPods[podName+s.Ppid] } func (pod *Pod) CopyToPod(namespace string, src string, dst string) { @@ -74,7 +101,7 @@ func (pod *Pod) CopyToPod(namespace string, src string, dst string) { pod.suite.AssertNil(err, string(out)) } -func (pod *Pod) Exec(command []string) (string, error) { +func (pod *Pod) Exec(ctx context.Context, command []string) (string, error) { var stdout, stderr bytes.Buffer // Prepare the request @@ -98,9 +125,6 @@ func (pod *Pod) Exec(command []string) (string, error) { pod.suite.Log("Error creating executor: %s", err.Error()) } - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Second) - defer cancel() - err = executor.StreamWithContext(ctx, remotecommand.StreamOptions{ Stdout: &stdout, Stderr: &stderr, @@ -125,7 +149,6 @@ func (pod *Pod) CreateConfigFromTemplate(targetConfigName string, templateName s err = template.Execute(f, values) pod.suite.AssertNil(err, err) - err = f.Close() pod.suite.AssertNil(err, err) diff --git a/extras/hs-test/infra/kind/suite_kind.go b/extras/hs-test/infra/kind/suite_kind.go index 1754853f78..ecbbe1dc35 100644 --- a/extras/hs-test/infra/kind/suite_kind.go +++ b/extras/hs-test/infra/kind/suite_kind.go @@ -1,6 +1,7 @@ package hst_kind import ( + "context" "fmt" "reflect" "regexp" @@ -22,8 +23,9 @@ type KindSuite struct { Config *rest.Config Namespace string KubeconfigPath string - CurrentlyRunning []string + CurrentlyRunning map[string]*Pod images []string + AllPods map[string]*Pod Pods struct { ServerGeneric *Pod ClientGeneric *Pod @@ -31,6 +33,7 @@ type KindSuite struct { NginxProxy *Pod Ab *Pod } + MainContext context.Context } var kindTests = map[string][]func(s *KindSuite){} @@ -59,6 +62,7 @@ func RegisterKindTests(tests ...func(s *KindSuite)) { } func (s *KindSuite) SetupTest() { + s.MainContext = context.Background() s.HstCommon.SetupTest() } @@ -68,9 +72,10 @@ func (s *KindSuite) SetupSuite() { Fail(message, callerSkip...) }) + s.CurrentlyRunning = make(map[string]*Pod) + s.LoadPodConfigs() s.initPods() s.loadDockerImages() - var err error if *SudoUser == "root" { s.KubeconfigPath = "/.kube/config" @@ -94,14 +99,17 @@ func (s *KindSuite) TeardownTest() { if len(s.CurrentlyRunning) != 0 { s.Log("Removing:") for _, pod := range s.CurrentlyRunning { - s.Log(" %s", pod) - s.deletePod(s.Namespace, pod) + s.Log(" %s", pod.Name) + s.deletePod(s.Namespace, pod.Name) } } } func (s *KindSuite) TeardownSuite() { s.HstCommon.TeardownSuite() + if len(s.CurrentlyRunning) == 0 { + return + } s.Log("Removing:\n %s", s.Namespace) s.AssertNil(s.deleteNamespace(s.Namespace)) } @@ -110,7 +118,7 @@ func (s *KindSuite) TeardownSuite() { // and searches for the first version string, then creates symlinks. func (s *KindSuite) FixVersionNumber(pods ...*Pod) { regex := regexp.MustCompile(`lib.*\.so\.([0-9]+\.[0-9]+)`) - o, _ := s.Pods.ServerGeneric.Exec([]string{"/bin/bash", "-c", + o, _ := s.Pods.ServerGeneric.Exec(context.TODO(), []string{"/bin/bash", "-c", "ldd /usr/lib/libvcl_ldpreload.so"}) match := regex.FindStringSubmatch(o) @@ -125,7 +133,7 @@ func (s *KindSuite) FixVersionNumber(pods ...*Pod) { "fi\n"+ "done", version) for _, pod := range pods { - pod.Exec([]string{"/bin/bash", "-c", cmd}) + pod.Exec(context.TODO(), []string{"/bin/bash", "-c", cmd}) } } else { @@ -149,7 +157,7 @@ func (s *KindSuite) CreateNginxConfig(pod *Pod) { } func (s *KindSuite) CreateNginxProxyConfig(pod *Pod) { - pod.Exec([]string{"/bin/bash", "-c", "mkdir -p /tmp/nginx"}) + pod.Exec(context.TODO(), []string{"/bin/bash", "-c", "mkdir -p /tmp/nginx"}) values := struct { Workers uint8 LogPrefix string diff --git a/extras/hs-test/kind_test.go b/extras/hs-test/kind_test.go index bc09fb7c3e..12f6b3972f 100644 --- a/extras/hs-test/kind_test.go +++ b/extras/hs-test/kind_test.go @@ -1,6 +1,8 @@ package main import ( + "context" + "errors" "time" . "fd.io/hs-test/infra/kind" @@ -15,73 +17,87 @@ const vcl string = "VCL_CONFIG=/vcl.conf" const ldp string = "LD_PRELOAD=/usr/lib/libvcl_ldpreload.so" func KindIperfVclTest(s *KindSuite) { + ctx, cancel := context.WithTimeout(s.MainContext, time.Second*30) + defer cancel() s.DeployPod(s.Pods.ClientGeneric) s.DeployPod(s.Pods.ServerGeneric) - _, err := s.Pods.ClientGeneric.Exec([]string{"/bin/bash", "-c", VclConfIperf}) + _, err := s.Pods.ClientGeneric.Exec(ctx, []string{"/bin/bash", "-c", VclConfIperf}) s.AssertNil(err) - _, err = s.Pods.ServerGeneric.Exec([]string{"/bin/bash", "-c", VclConfIperf}) + _, err = s.Pods.ServerGeneric.Exec(ctx, []string{"/bin/bash", "-c", VclConfIperf}) s.AssertNil(err) s.FixVersionNumber(s.Pods.ClientGeneric, s.Pods.ServerGeneric) - o, err := s.Pods.ServerGeneric.Exec([]string{"/bin/bash", "-c", + o, err := s.Pods.ServerGeneric.Exec(ctx, []string{"/bin/bash", "-c", vcl + " " + ldp + " iperf3 -s -D -4"}) s.AssertNil(err, o) - o, err = s.Pods.ClientGeneric.Exec([]string{"/bin/bash", "-c", + o, err = s.Pods.ClientGeneric.Exec(ctx, []string{"/bin/bash", "-c", vcl + " " + ldp + " iperf3 -l 1460 -b 10g -c " + s.Pods.ServerGeneric.IpAddress}) s.Log(o) s.AssertNil(err) } func NginxRpsTest(s *KindSuite) { + ctx, cancel := context.WithCancel(s.MainContext) + defer cancel() + s.DeployPod(s.Pods.Nginx) s.DeployPod(s.Pods.Ab) s.CreateNginxConfig(s.Pods.Nginx) - out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", VclConfNginx}) + out, err := s.Pods.Nginx.Exec(ctx, []string{"/bin/bash", "-c", VclConfNginx}) s.AssertNil(err, out) go func() { defer GinkgoRecover() - out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"}) - s.AssertNil(err, out) + out, err := s.Pods.Nginx.Exec(ctx, []string{"/bin/bash", "-c", "nginx -c /nginx.conf"}) + if !errors.Is(err, context.Canceled) { + s.AssertNil(err, out) + } }() // wait for nginx to start up time.Sleep(time.Second * 2) - out, err = s.Pods.Ab.Exec([]string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":8081/64B.json"}) + out, err = s.Pods.Ab.Exec(ctx, []string{"ab", "-k", "-r", "-n", "1000000", "-c", "1000", "http://" + s.Pods.Nginx.IpAddress + ":8081/64B.json"}) s.Log(out) s.AssertNil(err) } func NginxProxyMirroringTest(s *KindSuite) { + ctx, cancel := context.WithCancel(s.MainContext) + defer cancel() + s.DeployPod(s.Pods.Nginx) s.DeployPod(s.Pods.NginxProxy) s.DeployPod(s.Pods.ClientGeneric) s.CreateNginxConfig(s.Pods.Nginx) s.CreateNginxProxyConfig(s.Pods.NginxProxy) - out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", VclConfNginx}) + out, err := s.Pods.Nginx.Exec(ctx, []string{"/bin/bash", "-c", VclConfNginx}) s.AssertNil(err, out) - out, err = s.Pods.NginxProxy.Exec([]string{"/bin/bash", "-c", VclConfNginx}) + out, err = s.Pods.NginxProxy.Exec(ctx, []string{"/bin/bash", "-c", VclConfNginx}) s.AssertNil(err, out) go func() { defer GinkgoRecover() - out, err := s.Pods.Nginx.Exec([]string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"}) - s.AssertNil(err, out) + out, err := s.Pods.Nginx.Exec(ctx, []string{"/bin/bash", "-c", ldp + " " + vcl + " nginx -c /nginx.conf"}) + if !errors.Is(err, context.Canceled) { + s.AssertNil(err, out) + } }() go func() { defer GinkgoRecover() - out, err := s.Pods.NginxProxy.Exec([]string{"/bin/bash", "-c", "nginx -c /nginx.conf"}) - s.AssertNil(err, out) + out, err := s.Pods.NginxProxy.Exec(ctx, []string{"/bin/bash", "-c", "nginx -c /nginx.conf"}) + if !errors.Is(err, context.Canceled) { + s.AssertNil(err, out) + } }() // wait for nginx to start up time.Sleep(time.Second * 2) - out, err = s.Pods.ClientGeneric.Exec([]string{"curl", "-v", "--noproxy", "'*'", "--insecure", "http://" + s.Pods.NginxProxy.IpAddress + ":8080/64B.json"}) + out, err = s.Pods.ClientGeneric.Exec(ctx, []string{"curl", "-v", "--noproxy", "'*'", "--insecure", "http://" + s.Pods.NginxProxy.IpAddress + ":8080/64B.json"}) s.Log(out) s.AssertNil(err) } diff --git a/extras/hs-test/kubernetes/pod-definitions.yaml b/extras/hs-test/kubernetes/pod-definitions.yaml new file mode 100644 index 0000000000..f15e552fbe --- /dev/null +++ b/extras/hs-test/kubernetes/pod-definitions.yaml @@ -0,0 +1,75 @@ +definitions: + image-names: + - image: &hs-test + name: "hs-test/vpp:latest" + - image: &nginx-ldp + name: "hs-test/nginx-ldp:latest" + - image: &ab + name: "hs-test/ab:latest" + + container-names: + - container: &client + name: "client" + - container: &server + name: "server" + + namespace-names: + - namespace: &defaultNs + name: "hs-test" + + worker-names: + - worker: &worker1 + name: "kind-worker" + - worker: &worker2 + name: "kind-worker2" + +pods: + - name: "client-generic" + image: + - <<: *hs-test + container: + - <<: *client + worker: + - <<: *worker1 + namespace: + - <<: *defaultNs + + - name: "server-generic" + image: + - <<: *hs-test + container: + - <<: *server + worker: + - <<: *worker2 + namespace: + - <<: *defaultNs + + - name: "ab" + image: + - <<: *ab + container: + - <<: *client + worker: + - <<: *worker1 + namespace: + - <<: *defaultNs + + - name: "nginx-ldp" + image: + - <<: *nginx-ldp + container: + - <<: *server + worker: + - <<: *worker2 + namespace: + - <<: *defaultNs + + - name: "nginx-proxy" + image: + - <<: *nginx-ldp + container: + - <<: *server + worker: + - <<: *worker2 + namespace: + - <<: *defaultNs From acdc1d64c9a8eb97974905d49cde25d183f7c52f Mon Sep 17 00:00:00 2001 From: Shubing Guo Date: Tue, 1 Jul 2025 10:36:50 +0800 Subject: [PATCH 198/313] vppinfra: fix cpu time on riscv Starting with Linux 6.6 [1], RDCYCLE is a privileged instruction on RISC-V and can't be used directly from userland.Use RDTIME instead, which while less accurate has the advantage of being synchronized between CPU (and thus monotonic) and of constant frequency. Type: fix Change-Id: I53ab9cd1d0fcdd8f0e88d189840ba1d3b5ddd67a Signed-off-by: Shubing Guo --- src/vppinfra/time.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vppinfra/time.h b/src/vppinfra/time.h index 761dbed3fe..05e82f8895 100644 --- a/src/vppinfra/time.h +++ b/src/vppinfra/time.h @@ -192,13 +192,13 @@ clib_cpu_time_now (void) return result; } -#elif defined(__riscv) +#elif defined(__riscv) && defined(__riscv_xlen) && (__riscv_xlen == 64) always_inline u64 clib_cpu_time_now (void) { u64 result; - asm volatile("rdcycle %0\n" : "=r"(result)); + asm volatile ("rdtime %0\n" : "=r"(result)); return result; } #else From 73354a2410de9ce542cfdff8ef8ca9a527792d2e Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 7 Aug 2025 09:52:35 -0400 Subject: [PATCH 199/313] session: session_cb_vft add proxy_write_early_data Allow zc proxy app write data to fifo before UDP transport on accepted connection. Type: feature Change-Id: I93657de68d344f5d2d327dd4e36cc331043fdf98 Signed-off-by: Matus Fabian --- src/vnet/session/application_interface.h | 3 +++ src/vnet/session/application_worker.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index fe83265d3e..f58b0fa798 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -76,6 +76,9 @@ typedef struct session_cb_vft_ /** Custom fifo allocation for proxy */ int (*proxy_alloc_session_fifos) (session_t *s); + /** Allow proxy to write early data */ + int (*proxy_write_early_data) (session_t *s); + /** Collect and export session logs */ int (*app_evt_callback) (session_t *s); diff --git a/src/vnet/session/application_worker.c b/src/vnet/session/application_worker.c index c919cc10f7..8b389bee78 100644 --- a/src/vnet/session/application_worker.c +++ b/src/vnet/session/application_worker.c @@ -409,6 +409,9 @@ app_worker_init_accepted (session_t * s) if (app_worker_alloc_session_fifos (sm, s)) return -1; + if (application_is_builtin_proxy (app)) + return app->cb_fns.proxy_write_early_data (s); + return 0; } From b497d9ffe3f143aec2e37cbde5bbcd1fa5052e11 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Wed, 6 Aug 2025 11:46:00 -0400 Subject: [PATCH 200/313] tests: add vpp-opt-deps to vpp LD_LIBRARY_PATH - When vpp-opt-deps is installed, add it to LD_LIBRARY_PATH when invoking vpp for 'make test' and 'make run' targets overriding the dynamically linked distro library version. Type: test Change-Id: Id2feb74b7232cca1c1870599c4b3d409e2d0aa59 Signed-off-by: Dave Wallace --- Makefile | 8 ++++++-- test/Makefile | 13 ++++++++++++- test/config.py | 7 +++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 792d5c4bf8..a7ce9986e5 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,10 @@ MACHINE=$(shell uname -m) SUDO?=sudo -E DPDK_CONFIG?=no-pci +ifeq ($(shell test -d /opt/vpp/optional/${MACHINE}/lib64 && echo yes),yes) +VPP_OPT_DEPS_LIBRARY_PATH?=/opt/vpp/optional/${MACHINE}/lib64 +endif + # we prefer clang by default ifeq ($(CC),cc) CC=clang @@ -737,12 +741,12 @@ define run @echo "WARNING: STARTUP_CONF not defined or file doesn't exist." @echo " Running with minimal startup config: $(MINIMAL_STARTUP_CONF)\n" @cd $(STARTUP_DIR) && \ - $(SUDO) $(2) $(1)/vpp/bin/vpp $(MINIMAL_STARTUP_CONF) + $(SUDO) LD_LIBRARY_PATH=$(VPP_OPT_DEPS_LIBRARY_PATH) $(2) $(1)/vpp/bin/vpp $(MINIMAL_STARTUP_CONF) endef else define run @cd $(STARTUP_DIR) && \ - $(SUDO) $(2) $(1)/vpp/bin/vpp $(shell cat $(STARTUP_CONF) | sed -e 's/#.*//') + $(SUDO) LD_LIBRARY_PATH=$(VPP_OPT_DEPS_LIBRARY_PATH) $(2) $(1)/vpp/bin/vpp $(shell cat $(STARTUP_CONF) | sed -e 's/#.*//') endef endif diff --git a/test/Makefile b/test/Makefile index 445087d7ff..3949d31c51 100644 --- a/test/Makefile +++ b/test/Makefile @@ -73,6 +73,11 @@ ifeq ($(V),) V=0 endif +MACHINE=$(shell uname -m) +ifeq ($(shell test -d /opt/vpp/optional/${MACHINE}/lib64 && echo yes),yes) +VPP_OPT_DEPS_LIBRARY_PATH?=/opt/vpp/optional/${MACHINE}/lib64 +endif + PYTHON_VERSION=$(shell $(PYTHON_INTERP) -c 'import sys; print(sys.version_info.major)') PIP_VERSION=25.0.1 # Keep in sync with requirements.txt @@ -266,6 +271,12 @@ else ARG19= endif +ifneq ($(VPP_OPT_DEPS_LIBRARY_PATH),) +ARG20=--vpp-opt-deps-library-path=$(VPP_OPT_DEPS_LIBRARY_PATH) +else +ARG20= +endif + EXC_PLUGINS_ARG= ifneq ($(VPP_EXCLUDED_PLUGINS),) # convert the comma-separated list into N invocations of the argument to exclude a plugin @@ -274,7 +285,7 @@ endif -EXTRA_ARGS=$(ARG0) $(ARG1) $(ARG2) $(ARG3) $(ARG4) $(ARG5) $(ARG6) $(ARG7) $(ARG8) $(ARG9) $(ARG10) $(ARG11) $(ARG12) $(ARG13) $(ARG14) $(ARG15) $(ARG16) $(ARG17) $(ARG18) $(ARG19) +EXTRA_ARGS=$(ARG0) $(ARG1) $(ARG2) $(ARG3) $(ARG4) $(ARG5) $(ARG6) $(ARG7) $(ARG8) $(ARG9) $(ARG10) $(ARG11) $(ARG12) $(ARG13) $(ARG14) $(ARG15) $(ARG16) $(ARG17) $(ARG18) $(ARG19) $(ARG20) RUN_TESTS_ARGS=--failed-dir=$(FAILED_DIR) --verbose=$(V) --jobs=$(TEST_JOBS) --filter=$(TEST) --skip-filter=$(SKIP_TESTS) --retries=$(RETRIES) --venv-dir=$(VENV_PATH) --vpp-ws-dir=$(WS_ROOT) --vpp-tag=$(TAG) --rnd-seed=$(RND_SEED) --vpp-worker-count="$(VPP_WORKER_COUNT)" --keep-pcaps $(PLUGIN_PATH_ARGS) $(EXC_PLUGINS_ARG) $(TEST_PLUGIN_PATH_ARGS) $(EXTRA_ARGS) RUN_SCRIPT_ARGS=--python-opts=$(PYTHON_OPTS) diff --git a/test/config.py b/test/config.py index 2870c55df0..6819bf2cb4 100644 --- a/test/config.py +++ b/test/config.py @@ -349,6 +349,13 @@ def directory_verify_or_create(v): help="max cpus used by vpp", ) +parser.add_argument( + "--vpp-opt-deps-library-path", + action="store", + default=None, + help="path to vpp-opt-deps library directory", +) + variant_help_string = """\ specify which march node variant to unit test e.g. --variant=skx - test the skx march variants From ead3da8afd539440bb11100fc5ddc5cccdc0a808 Mon Sep 17 00:00:00 2001 From: Mohsin Kazmi Date: Mon, 28 Apr 2025 17:17:24 +0000 Subject: [PATCH 201/313] gso: add support for ipip tso for phyiscal interfaces Type: improvement This patch also improves software GSO handling for tunnel interfaces. Signed-off-by: Mohsin Kazmi Change-Id: I210bf74a65feef0bb39b5925bf8632ef4d627eb5 --- src/plugins/dpdk/device/common.c | 5 ++-- src/plugins/dpdk/device/device.c | 18 +++++++++++- src/vnet/gso/node.c | 49 +++++++++++++++++++------------- src/vnet/interface.h | 2 +- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/src/plugins/dpdk/device/common.c b/src/plugins/dpdk/device/common.c index 0049ee8445..f496226c89 100644 --- a/src/plugins/dpdk/device/common.c +++ b/src/plugins/dpdk/device/common.c @@ -41,7 +41,8 @@ static struct { RTE_ETH_TX_OFFLOAD_OUTER_IPV4_CKSUM, VNET_HW_IF_CAP_TX_IP4_OUTER_CKSUM }, { RTE_ETH_TX_OFFLOAD_OUTER_UDP_CKSUM, VNET_HW_IF_CAP_TX_UDP_OUTER_CKSUM }, { RTE_ETH_TX_OFFLOAD_TCP_TSO, VNET_HW_IF_CAP_TCP_GSO }, - { RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO, VNET_HW_IF_CAP_VXLAN_TNL_GSO } + { RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO, VNET_HW_IF_CAP_VXLAN_TNL_GSO }, + { RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO, VNET_HW_IF_CAP_IPIP_TNL_GSO } }; void @@ -119,7 +120,7 @@ dpdk_device_setup (dpdk_device_t * xd) /* per-device offload config */ if (xd->conf.enable_tso) txo |= RTE_ETH_TX_OFFLOAD_TCP_CKSUM | RTE_ETH_TX_OFFLOAD_TCP_TSO | - RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO; + RTE_ETH_TX_OFFLOAD_VXLAN_TNL_TSO | RTE_ETH_TX_OFFLOAD_IPIP_TNL_TSO; if (xd->conf.disable_rx_scatter) rxo &= ~RTE_ETH_RX_OFFLOAD_SCATTER; diff --git a/src/plugins/dpdk/device/device.c b/src/plugins/dpdk/device/device.c index 5fd936d174..ffa1d7a7bf 100644 --- a/src/plugins/dpdk/device/device.c +++ b/src/plugins/dpdk/device/device.c @@ -199,7 +199,7 @@ dpdk_buffer_tx_offload (dpdk_device_t * xd, vlib_buffer_t * b, int is_ip4 = b->flags & VNET_BUFFER_F_IS_IP4; u32 tso = b->flags & VNET_BUFFER_F_GSO, max_pkt_len; u32 ip_cksum, tcp_cksum, udp_cksum, outer_hdr_len = 0; - u32 outer_ip_cksum, vxlan_tunnel; + u32 outer_ip_cksum, vxlan_tunnel, ipip_tunnel; u64 ol_flags; vnet_buffer_oflags_t oflags = 0; @@ -213,6 +213,7 @@ dpdk_buffer_tx_offload (dpdk_device_t * xd, vlib_buffer_t * b, udp_cksum = oflags & VNET_BUFFER_OFFLOAD_F_UDP_CKSUM; outer_ip_cksum = oflags & VNET_BUFFER_OFFLOAD_F_OUTER_IP_CKSUM; vxlan_tunnel = oflags & VNET_BUFFER_OFFLOAD_F_TNL_VXLAN; + ipip_tunnel = oflags & VNET_BUFFER_OFFLOAD_F_TNL_IPIP; ol_flags = is_ip4 ? RTE_MBUF_F_TX_IPV4 : RTE_MBUF_F_TX_IPV6; ol_flags |= ip_cksum ? RTE_MBUF_F_TX_IP_CKSUM : 0; @@ -235,6 +236,21 @@ dpdk_buffer_tx_offload (dpdk_device_t * xd, vlib_buffer_t * b, vnet_buffer2 (b)->outer_l3_hdr_offset; outer_hdr_len = mb->outer_l2_len + mb->outer_l3_len; } + else if (ipip_tunnel) + { + ol_flags |= outer_ip_cksum ? + RTE_MBUF_F_TX_OUTER_IPV4 | RTE_MBUF_F_TX_OUTER_IP_CKSUM : + RTE_MBUF_F_TX_OUTER_IPV6; + ol_flags |= RTE_MBUF_F_TX_TUNNEL_IPIP; + mb->l2_len = 0; + mb->l3_len = + vnet_buffer (b)->l4_hdr_offset - vnet_buffer (b)->l3_hdr_offset; + mb->outer_l2_len = + vnet_buffer2 (b)->outer_l3_hdr_offset - b->current_data; + mb->outer_l3_len = + vnet_buffer (b)->l3_hdr_offset - vnet_buffer2 (b)->outer_l3_hdr_offset; + outer_hdr_len = mb->outer_l2_len + mb->outer_l3_len; + } else { mb->l2_len = vnet_buffer (b)->l3_hdr_offset - b->current_data; diff --git a/src/vnet/gso/node.c b/src/vnet/gso/node.c index 1fabe44d2a..20c2e20cf9 100644 --- a/src/vnet/gso/node.c +++ b/src/vnet/gso/node.c @@ -486,12 +486,10 @@ drop_one_buffer_and_count (vlib_main_t * vm, vnet_main_t * vnm, } static_always_inline uword -vnet_gso_node_inline (vlib_main_t * vm, - vlib_node_runtime_t * node, - vlib_frame_t * frame, - vnet_main_t * vnm, - vnet_hw_interface_t * hi, - int is_l2, int is_ip4, int is_ip6, int do_segmentation) +vnet_gso_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_frame_t *frame, vnet_main_t *vnm, + vnet_hw_interface_t *hi, u32 supported_caps, int is_l2, + int is_ip4, int is_ip6, int do_segmentation) { u32 *to_next; u32 next_index = node->cached_next_index; @@ -544,29 +542,25 @@ vnet_gso_node_inline (vlib_main_t * vm, if (PREDICT_FALSE (hi->sw_if_index != swif0)) { hi0 = vnet_get_sup_hw_interface (vnm, swif0); - if ((hi0->caps & VNET_HW_IF_CAP_TCP_GSO) == 0 && - (b[0]->flags & VNET_BUFFER_F_GSO)) + if ((hi0->caps & supported_caps) != supported_caps) break; } if (PREDICT_FALSE (hi->sw_if_index != swif1)) { hi1 = vnet_get_sup_hw_interface (vnm, swif1); - if (!(hi1->caps & VNET_HW_IF_CAP_TCP_GSO) && - (b[1]->flags & VNET_BUFFER_F_GSO)) + if ((hi1->caps & supported_caps) != supported_caps) break; } if (PREDICT_FALSE (hi->sw_if_index != swif2)) { hi2 = vnet_get_sup_hw_interface (vnm, swif2); - if ((hi2->caps & VNET_HW_IF_CAP_TCP_GSO) == 0 && - (b[2]->flags & VNET_BUFFER_F_GSO)) + if ((hi2->caps & supported_caps) != supported_caps) break; } if (PREDICT_FALSE (hi->sw_if_index != swif3)) { hi3 = vnet_get_sup_hw_interface (vnm, swif3); - if (!(hi3->caps & VNET_HW_IF_CAP_TCP_GSO) && - (b[3]->flags & VNET_BUFFER_F_GSO)) + if ((hi3->caps & supported_caps) != supported_caps) break; } @@ -635,18 +629,31 @@ vnet_gso_node_inline (vlib_main_t * vm, vnet_hw_interface_t *hi0; u32 next0 = 0; u32 do_segmentation0 = 0; + u32 caps = hi->caps; swif0 = vnet_buffer (b[0])->sw_if_index[VLIB_TX]; if (PREDICT_FALSE (hi->sw_if_index != swif0)) { hi0 = vnet_get_sup_hw_interface (vnm, swif0); - if ((hi0->caps & VNET_HW_IF_CAP_TCP_GSO) == 0 && - (b[0]->flags & VNET_BUFFER_F_GSO)) + caps = hi0->caps; + if ((hi0->caps & supported_caps) != supported_caps) do_segmentation0 = 1; } else do_segmentation0 = do_segmentation; + if (do_segmentation0) + { + u8 oflags = vnet_buffer (b[0])->oflags; + if ((caps & VNET_HW_IF_CAP_TCP_GSO) == VNET_HW_IF_CAP_TCP_GSO) + { + if (((oflags & VNET_BUFFER_OFFLOAD_F_TNL_VXLAN) != + VNET_BUFFER_OFFLOAD_F_TNL_VXLAN) && + ((oflags & VNET_BUFFER_OFFLOAD_F_TNL_IPIP) != + VNET_BUFFER_OFFLOAD_F_TNL_IPIP)) + do_segmentation0 = 0; + } + } /* speculatively enqueue b0 to the current next frame */ to_next[0] = bi0 = from[0]; to_next += 1; @@ -764,15 +771,19 @@ vnet_gso_inline (vlib_main_t * vm, { u32 *from = vlib_frame_vector_args (frame); vlib_buffer_t *b = vlib_get_buffer (vm, from[0]); + u32 supported_caps = + (VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_VXLAN_TNL_GSO | + VNET_HW_IF_CAP_IPIP_TNL_GSO); + hi = vnet_get_sup_hw_interface (vnm, vnet_buffer (b)->sw_if_index[VLIB_TX]); - if (hi->caps & (VNET_HW_IF_CAP_TCP_GSO | VNET_HW_IF_CAP_VXLAN_TNL_GSO)) - return vnet_gso_node_inline (vm, node, frame, vnm, hi, + if ((hi->caps & supported_caps) == supported_caps) + return vnet_gso_node_inline (vm, node, frame, vnm, hi, supported_caps, is_l2, is_ip4, is_ip6, /* do_segmentation */ 0); else - return vnet_gso_node_inline (vm, node, frame, vnm, hi, + return vnet_gso_node_inline (vm, node, frame, vnm, hi, supported_caps, is_l2, is_ip4, is_ip6, /* do_segmentation */ 1); } diff --git a/src/vnet/interface.h b/src/vnet/interface.h index 8c5c5b177b..c6d0185905 100644 --- a/src/vnet/interface.h +++ b/src/vnet/interface.h @@ -571,7 +571,7 @@ typedef enum vnet_hw_interface_flags_t_ typedef enum vnet_hw_if_caps_t_ { - VNET_HW_INTERFACE_CAP_NONE, + VNET_HW_IF_CAP_NONE, #define _(bit, sfx, str) VNET_HW_IF_CAP_##sfx = (1 << (bit)), foreach_vnet_hw_if_caps #undef _ From 9fd3c84e82cb6ed8be81cbb0984442a092e60af9 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 8 Aug 2025 19:57:23 -0400 Subject: [PATCH 202/313] virtio: fix cli parser Fixes: 84f09f4 Type: fix Change-Id: If1d42dc7899cf87f016b12dc0eabff944ff20bea Signed-off-by: Florin Coras --- src/vnet/devices/virtio/cli.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vnet/devices/virtio/cli.c b/src/vnet/devices/virtio/cli.c index d363882738..c9467fc238 100644 --- a/src/vnet/devices/virtio/cli.c +++ b/src/vnet/devices/virtio/cli.c @@ -33,6 +33,10 @@ virtio_pci_create_command_fn (vlib_main_t * vm, unformat_input_t * input, u32 buffering_size = 0; u32 txq_size = 0; + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) { if (unformat (line_input, "%U", unformat_vlib_pci_addr, &args.addr)) From 016e389ff63a42c22a637fb008121b5c8019b4d1 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Thu, 7 Aug 2025 15:15:36 +0000 Subject: [PATCH 203/313] hsa: fix potential udp echo server test-bytes int overflow Make sure that when running echo server over UDP with test-bytes enabled, we're getting at least the buffer offset and some data. Type: fix Fixes: 84d52285afd1b478d616026a3d63a714abb29f13 Change-Id: I9375e00a76caa9d043f0b5531169fb6f2ca07db7 Signed-off-by: Semir Sionek --- src/plugins/hs_apps/echo_server.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/plugins/hs_apps/echo_server.c b/src/plugins/hs_apps/echo_server.c index abf557c33e..41f5ac8791 100644 --- a/src/plugins/hs_apps/echo_server.c +++ b/src/plugins/hs_apps/echo_server.c @@ -450,8 +450,13 @@ echo_server_rx_callback (session_t * s) es->byte_index += actual_transfer; } else - es_test_bytes ((wrk->rx_buf + sizeof (u32)), - actual_transfer - sizeof (u32), *(u32 *) wrk->rx_buf); + { + /* Sanity check, in case of a broken dgram */ + if (actual_transfer < sizeof (u32) + 1) + return 0; + es_test_bytes ((wrk->rx_buf + sizeof (u32)), + actual_transfer - sizeof (u32), *(u32 *) wrk->rx_buf); + } } /* From ae4c21faacdd84f3a2cc2b51e87e0638181062e4 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Fri, 1 Aug 2025 12:16:52 +0000 Subject: [PATCH 204/313] session: restore cl session dscp support Included dscp in session_listen messages and propagated it up to the listener struct, which is used for connection info in cl sessions. Additionally, included a switch in vcl_test_cl_udp allowing to select dscp values for the session. Type: fix Change-Id: I6dffe12b66b25617dc9e3b6d4008f1097ff150cf Signed-off-by: Semir Sionek --- extras/hs-test/docker/Dockerfile.vpp | 1 + extras/hs-test/infra/vppinstance.go | 1 + extras/hs-test/vcl_test.go | 33 ++++++++++++++++++++++- src/plugins/hs_apps/vcl/vcl_test_cl_udp.c | 22 ++++++++++++++- src/vcl/vppcom.c | 1 + src/vnet/session/application_interface.h | 1 + src/vnet/session/session_node.c | 1 + src/vnet/udp/udp.c | 1 + 8 files changed, 59 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp index cf620ee114..20d2c65064 100644 --- a/extras/hs-test/docker/Dockerfile.vpp +++ b/extras/hs-test/docker/Dockerfile.vpp @@ -24,6 +24,7 @@ COPY \ $DIR/prom_plugin.so \ $DIR/tlsopenssl_plugin.so \ $DIR/mactime_plugin.so \ + $DIR/arping_plugin.so \ /usr/lib/$OS_ARCH-linux-gnu/vpp_plugins/ COPY vpp-data/bin/vpp /usr/bin/ diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go index 2e9c7567aa..cfb39592f9 100644 --- a/extras/hs-test/infra/vppinstance.go +++ b/extras/hs-test/infra/vppinstance.go @@ -72,6 +72,7 @@ plugins { plugin ping_plugin.so { enable } plugin nsim_plugin.so { enable } plugin mactime_plugin.so { enable } + plugin arping_plugin.so { enable } } logging { diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go index e00937b2a4..f184d78257 100644 --- a/extras/hs-test/vcl_test.go +++ b/extras/hs-test/vcl_test.go @@ -9,7 +9,7 @@ import ( func init() { RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest, - XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest) + XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest, VclClUdpDscpTest) RegisterSoloVethTests(VclRetryAttachTest) } @@ -156,3 +156,34 @@ func testRetryAttach(s *VethsSuite, proto string) { s.AssertNil(err, o) s.Log("Done.") } + +func VclClUdpDscpTest(s *VethsSuite) { + srvVppCont := s.Containers.ServerVpp + srvAppCont := s.Containers.ServerApp + srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont)) + srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") + serverVethAddress := s.Interfaces.Server.Ip4AddressString() + + // DSCP 40 - Class selector 5 - Signalling + vclSrvCmd := fmt.Sprintf("vcl_test_cl_udp -s %s -d 40", serverVethAddress) + srvAppCont.ExecServer(true, vclSrvCmd) + + cliVppCont := s.Containers.ClientVpp + cliAppCont := s.Containers.ClientApp + cliAppCont.CreateFile("/vcl.conf", getVclConfig(cliVppCont)) + cliAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") + cliVppCont.VppInstance.Vppctl("arping %s host-%s", serverVethAddress, s.Interfaces.Client.Name()) + + cliVppCont.VppInstance.Vppctl("trace add af-packet-input 10") + srvVppCont.VppInstance.Vppctl("trace add af-packet-input 10") + + // DSCP 16 - Class selector 2 - Network operations + cliSrvCmd := fmt.Sprintf("vcl_test_cl_udp -c %s -d 16", serverVethAddress) + o, err := cliAppCont.Exec(true, cliSrvCmd) + s.AssertNil(err, o) + + o = srvVppCont.VppInstance.Vppctl("show trace") + s.AssertContains(o, "dscp CS2") + o = cliVppCont.VppInstance.Vppctl("show trace") + s.AssertContains(o, "dscp CS5") +} diff --git a/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c b/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c index 1325941129..7d27db0212 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c +++ b/src/plugins/hs_apps/vcl/vcl_test_cl_udp.c @@ -44,6 +44,7 @@ typedef struct vtclu_main_ struct sockaddr_storage clnt_addr; }; uint16_t port; + uint8_t dscp; int num_workers; pthread_t *worker_threads; int thread_id_counter; @@ -51,6 +52,7 @@ typedef struct vtclu_main_ } vt_clu_main_t; static vt_clu_main_t vt_clu_main; +static const uint32_t dscplen = 1; typedef struct vtclu_worker_args_ { @@ -62,13 +64,15 @@ static void vt_clu_parse_args (vt_clu_main_t *vclum, int argc, char **argv) { int c; + int temp; memset (vclum, 0, sizeof (*vclum)); vclum->port = VCL_TEST_SERVER_PORT; vclum->num_workers = 1; + vclum->dscp = 0; opterr = 0; - while ((c = getopt (argc, argv, "s:c:w:")) != -1) + while ((c = getopt (argc, argv, "s:c:w:d:")) != -1) switch (c) { case 's': @@ -93,6 +97,15 @@ vt_clu_parse_args (vt_clu_main_t *vclum, int argc, char **argv) vclum->num_workers = 1; } break; + case 'd': + temp = atoi (optarg); + if (temp <= 0 || temp > 63) + { + vtwrn ("invalid dscp value %s", optarg); + vclum->dscp = 0; + } + vclum->dscp = temp; + break; } if (vclum->app_type == VT_CLU_TYPE_NONE) @@ -169,6 +182,10 @@ vt_clu_server_worker (void *arg) return NULL; } + if (vclum->dscp) + vppcom_session_attr (vcl_sh, VPPCOM_ATTR_SET_DSCP, &vclum->dscp, + (uint32_t *) &dscplen); + /* Bind to the same endpoint as main thread */ rv = vppcom_session_bind (vcl_sh, &vclum->endpt); if (rv < 0) @@ -241,6 +258,9 @@ vt_clu_client_worker (void *arg) vterr ("vppcom_session_create()", vcl_sh); return NULL; } + if (vclum->dscp) + vppcom_session_attr (vcl_sh, VPPCOM_ATTR_SET_DSCP, &vclum->dscp, + (uint32_t *) &dscplen); char message[buflen]; int msg_len = diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 9da68c11c0..08879704a0 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -69,6 +69,7 @@ vcl_send_session_listen (vcl_worker_t *wrk, vcl_session_t *s) mp->port = s->transport.lcl_port; mp->proto = s->session_type; mp->vrf = s->vrf; + mp->dscp = s->dscp; if (s->flags & VCL_SESSION_F_CONNECTED) mp->flags = TRANSPORT_CFG_F_CONNECTED; if (s->ext_config) diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index f58b0fa798..284c1544bf 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -312,6 +312,7 @@ typedef struct session_listen_msg_ ip46_address_t ip; u8 flags; uword ext_config; + u8 dscp; } __clib_packed session_listen_msg_t; STATIC_ASSERT (sizeof (session_listen_msg_t) <= SESSION_CTRL_MSG_MAX_SIZE, diff --git a/src/vnet/session/session_node.c b/src/vnet/session/session_node.c index fb4c6252bb..379ae53fb4 100644 --- a/src/vnet/session/session_node.c +++ b/src/vnet/session/session_node.c @@ -132,6 +132,7 @@ session_mq_listen_handler (session_worker_t *wrk, session_evt_elt_t *elt) a->sep.fib_index = mp->vrf; a->sep.sw_if_index = ENDPOINT_INVALID_INDEX; a->sep.transport_proto = mp->proto; + a->sep.dscp = mp->dscp; a->app_index = app->app_index; a->wrk_map_index = mp->wrk_index; a->sep_ext.transport_flags = mp->flags; diff --git a/src/vnet/udp/udp.c b/src/vnet/udp/udp.c index 0848936367..e1cd98af79 100644 --- a/src/vnet/udp/udp.c +++ b/src/vnet/udp/udp.c @@ -193,6 +193,7 @@ udp_session_bind (u32 session_index, transport_endpoint_cfg_t *lcl) listener->c_proto = TRANSPORT_PROTO_UDP; listener->c_s_index = session_index; listener->c_fib_index = lcl->fib_index; + listener->c_dscp = lcl->dscp; listener->mss = lcl->mss ? lcl->mss : udp_default_mtu (um, listener->c_is_ip4); listener->flags |= UDP_CONN_F_OWNS_PORT | UDP_CONN_F_LISTEN; From fca7910e5ed19713977116b3ab8aeeea1212e5e1 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sun, 3 Aug 2025 19:11:03 -0400 Subject: [PATCH 205/313] tls: fix coverity warning Type: fix Change-Id: I97e08e93b02271599e52462ab83767168a2eb1cb Signed-off-by: Florin Coras --- src/plugins/tlsopenssl/tls_openssl.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 08ae12a1f1..3d0c52230f 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -1067,19 +1067,18 @@ openssl_start_listen (tls_ctx_t * lctx) clib_warning ("unable to use SSL certificate"); goto err; } + rv = SSL_CTX_use_PrivateKey (ssl_ctx, cki->key); + if (rv != 1) + { + clib_warning ("unable to use SSL PrivateKey"); + goto err; + } } else { lctx->flags |= TLS_CONN_F_ASYNC_CERT; } - rv = SSL_CTX_use_PrivateKey (ssl_ctx, cki->key); - if (rv != 1) - { - clib_warning ("unable to use SSL PrivateKey"); - goto err; - } - if (lctx->alpn_list) SSL_CTX_set_alpn_select_cb (ssl_ctx, openssl_alpn_select_cb, (void *) lctx->alpn_list); From 48d3d697d526d19415aaf8130805dc6496e39fe0 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 11 Aug 2025 05:50:51 -0400 Subject: [PATCH 206/313] tls: zero hdr.gso_size in openssl_ctx_read_dtls Type: fix Change-Id: I621c846b3d2f3803c2799240f00b36979017bbec Signed-off-by: Matus Fabian --- src/plugins/tlsopenssl/tls_openssl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 3d0c52230f..7db06fb8de 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -660,6 +660,7 @@ openssl_ctx_read_dtls (tls_ctx_t *ctx, session_t *us) hdr.data_length = read; hdr.data_offset = 0; + hdr.gso_size = 0; svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, { buf, read } }; From 0b8d9d3707b7404e81bfb9692253171b353c9ce6 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 11 Aug 2025 05:38:52 -0400 Subject: [PATCH 207/313] hsa: echo client zero hdr.gso_size Type: fix Change-Id: I0efff82f942bf63af5bbad1d713c0a5ff90f00a2 Signed-off-by: Matus Fabian --- src/plugins/hs_apps/echo_client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index 99d69280bd..d5896aecc7 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -144,6 +144,7 @@ send_data_chunk (ec_main_t *ecm, ec_session_t *es) hdr.data_length = rv; hdr.data_offset = 0; + hdr.gso_size = 0; clib_memcpy_fast (&hdr.rmt_ip, &at->rmt_ip, sizeof (ip46_address_t)); hdr.is_ip4 = at->is_ip4; From 576095fd84ccbd9e323b6fab27df8f483631c8bd Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 11 Aug 2025 05:43:05 -0400 Subject: [PATCH 208/313] srtp: zero hdr.gso_size in srtp_ctx_read Type: fix Change-Id: I895b5f35b451c80c2ff2f3d7e7710e67aa546742 Signed-off-by: Matus Fabian --- src/plugins/srtp/srtp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/srtp/srtp.c b/src/plugins/srtp/srtp.c index f86b7be980..d7173b11aa 100644 --- a/src/plugins/srtp/srtp.c +++ b/src/plugins/srtp/srtp.c @@ -360,6 +360,7 @@ srtp_ctx_read (srtp_tc_t *ctx, session_t *us) hdr.data_length = len; hdr.data_offset = 0; + hdr.gso_size = 0; svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, { buf, len } }; From 92ceeb34c805f26acb484acccc081d2d365fa7f7 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 11 Aug 2025 05:27:24 -0400 Subject: [PATCH 209/313] http: zero hdr.gso_size in udp_tunnel_rx Type: fix Change-Id: I78fb6263af14b37e2aee42386850f23bbacf40fb Signed-off-by: Matus Fabian --- src/plugins/http/http1.c | 1 + src/plugins/http/http2/http2.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index d8e313bed6..146ee8d3d0 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -1228,6 +1228,7 @@ http1_req_state_udp_tunnel_rx (http_conn_t *hc, http_req_t *req, hdr.data_length = payload_len; hdr.data_offset = 0; + hdr.gso_size = 0; /* send datagram header and payload */ svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 24d183d818..b56518a841 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1745,6 +1745,7 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, hdr.data_length = payload_len; hdr.data_offset = 0; + hdr.gso_size = 0; /* send datagram header and payload */ svm_fifo_seg_t segs[2] = { { (u8 *) &hdr, sizeof (hdr) }, From 421964a8d1e07ea1e59d79e7e7420d03150b4cee Mon Sep 17 00:00:00 2001 From: Vladimir Zhigulin Date: Fri, 4 Jul 2025 10:31:44 +0200 Subject: [PATCH 210/313] af_xdp: fix missing recvmsg argument Because of missing argument syscall was failing before reaching xdp system what could result in degrated performance Type: fix Change-Id: I0ae1ce1bacf68457a02a31b3508e9c5100a754e6 Signed-off-by: Vladimir Zhigulin --- src/plugins/af_xdp/input.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plugins/af_xdp/input.c b/src/plugins/af_xdp/input.c index 9177b3ffc5..72c3738fa2 100644 --- a/src/plugins/af_xdp/input.c +++ b/src/plugins/af_xdp/input.c @@ -88,7 +88,12 @@ af_xdp_device_input_refill_db (vlib_main_t * vm, if (clib_spinlock_trylock_if_init (&rxq->syscall_lock)) { - int ret = recvmsg (rxq->xsk_fd, 0, MSG_DONTWAIT); + struct msghdr msg = { 0 }; + struct iovec iov = { 0 }; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + int ret = recvmsg (rxq->xsk_fd, &msg, MSG_DONTWAIT); clib_spinlock_unlock_if_init (&rxq->syscall_lock); if (PREDICT_FALSE (ret < 0)) { From b386cbb0650de044100332f93aa987c26ecfd784 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Thu, 7 Aug 2025 11:06:13 +0200 Subject: [PATCH 211/313] hs-test: KinD cluster setup improvements - script cleanup - clarified Makefile build-msg - release-cluster: versions can be specified - master-cluster: local repo gets properly reset to its original state, fixed rebuild-master-cluster Type: improvement Change-Id: I81f813d2cd0227eaac3b996590152ba7e1173e38 Signed-off-by: Adrian Villin --- .gitignore | 1 + extras/hs-test/Makefile | 17 ++-- ...onfig.yaml => calico-config-template.yaml} | 4 +- .../{setupCluster.sh => setup-cluster.sh} | 88 +++++++++++++------ 4 files changed, 77 insertions(+), 33 deletions(-) rename extras/hs-test/kubernetes/{calico-config.yaml => calico-config-template.yaml} (98%) rename extras/hs-test/kubernetes/{setupCluster.sh => setup-cluster.sh} (64%) diff --git a/.gitignore b/.gitignore index 67415d8441..c8e3edf732 100644 --- a/.gitignore +++ b/.gitignore @@ -147,6 +147,7 @@ compile_commands.json /extras/hs-test/.last_state_hash /extras/hs-test/.kind_deps.ok /extras/hs-test/.go_cache/ +/extras/hs-test/kubernetes/calico-config.yaml # ./configure /CMakeFiles diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 5406b69120..c88d5a3c36 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -107,6 +107,7 @@ help: @echo " build-cov - coverage build of VPP and Docker images" @echo " build-debug - build test infra (vpp debug image)" @echo " build-go - just build golang files" + @echo " cluster-help - print info about KinD cluster setup (perf testing)" @echo " master-cluster - setup KinD cluster for performance testing (master CalicoVPP + VPP)" @echo " rebuild-master-cluster - rebuild VPP and update related pods without shutting down the cluster" @echo " release-cluster - setup KinD cluster for performance testing (latest CalicoVPP release)" @@ -152,7 +153,9 @@ list-tests: build-msg: @echo "Building VPP as '$(BUILD_AS)'" @echo "****************************************************************" - @echo "If you wish to build VPP as root, use 'SUDO_USER=root make ...'" + @echo "If you wish to build VPP as root, use 'make ... BUILD_AS=root'" + @echo "or" + @echo "'[sudo] SUDO_USER=root make ...'" @echo "****************************************************************" .PHONY: build-vpp-release @@ -233,17 +236,21 @@ test-perf: .deps.ok .build.ok --ginkgo_timeout=$(GINKGO_TIMEOUT); \ ./script/compress.sh $$? +.PHONY: cluster-help +cluster-help: + @bash ./kubernetes/setup-cluster.sh help + .PHONY: release-cluster release-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh release-cluster + @bash ./kubernetes/setup-cluster.sh release-cluster .PHONY: master-cluster master-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh master-cluster + @bash ./kubernetes/setup-cluster.sh master-cluster .PHONY: rebuild-master-cluster -rebuild-cluster: .kind_deps.ok - @bash ./kubernetes/setupCluster.sh rebuild-master-cluster +rebuild-master-cluster: .kind_deps.ok + @bash ./kubernetes/setup-cluster.sh rebuild-master-cluster # this is executed in a container by hs-test.sh .PHONY: build-go diff --git a/extras/hs-test/kubernetes/calico-config.yaml b/extras/hs-test/kubernetes/calico-config-template.yaml similarity index 98% rename from extras/hs-test/kubernetes/calico-config.yaml rename to extras/hs-test/kubernetes/calico-config-template.yaml index ea6fad102a..395e404eab 100644 --- a/extras/hs-test/kubernetes/calico-config.yaml +++ b/extras/hs-test/kubernetes/calico-config-template.yaml @@ -230,7 +230,7 @@ spec: envFrom: - configMapRef: name: calico-vpp-config - image: docker.io/calicovpp/agent:latest + image: docker.io/calicovpp/agent:${CALICOVPP_VERSION} imagePullPolicy: IfNotPresent name: agent resources: @@ -262,7 +262,7 @@ spec: envFrom: - configMapRef: name: calico-vpp-config - image: docker.io/calicovpp/vpp:latest + image: docker.io/calicovpp/vpp:${CALICOVPP_VERSION} imagePullPolicy: IfNotPresent name: vpp resources: diff --git a/extras/hs-test/kubernetes/setupCluster.sh b/extras/hs-test/kubernetes/setup-cluster.sh similarity index 64% rename from extras/hs-test/kubernetes/setupCluster.sh rename to extras/hs-test/kubernetes/setup-cluster.sh index 4eba189fba..95ef3ebf6e 100755 --- a/extras/hs-test/kubernetes/setupCluster.sh +++ b/extras/hs-test/kubernetes/setup-cluster.sh @@ -5,8 +5,9 @@ COMMAND=$1 CALICOVPP_DIR="$HOME/vpp-dataplane" VPP_DIR=$(pwd) VPP_DIR=${VPP_DIR%extras*} -STASH_SAVED=0 +COMMIT_HASH=$(git rev-parse HEAD) +export DOCKER_BUILD_PROXY=$HTTP_PROXY # ---------------- images ---------------- export CALICO_AGENT_IMAGE=localhost:5000/calicovpp/agent:latest export CALICO_VPP_IMAGE=localhost:5000/calicovpp/vpp:latest @@ -44,15 +45,52 @@ export CALICOVPP_ENABLE_VCL=true help() { echo "Usage:" - echo " make master-cluster | rebuild-master-cluster | release-cluster" - echo "or" - echo " ./kubernetes/setupCluster.sh [master-cluster | rebuild-master-cluster | release-cluster]" - echo "" + echo -e " make master-cluster | rebuild-master-cluster | release-cluster\n" + echo "'master-cluster' pulls CalicoVPP and builds VPP from this directory, then brings up a KinD cluster." echo "'rebuild-master-cluster' stops CalicoVPP pods, rebuilds VPP and restarts CalicoVPP pods. Cluster keeps running." - echo "'release-cluster' starts up a KinD cluster and uses latest CalicoVPP release (e.g. v3.29)" - echo "" - echo "To shut down the cluster, use 'kind delete cluster'" + echo "'release-cluster' starts up a KinD cluster and uses latest CalicoVPP release (e.g. v3.29), + or you can override versions by using env variables 'CALICOVPP_VERSION' and 'TIGERA_VERSION': + CALICOVPP_VERSION: latest | v[x].[y].[z] (default=latest) + TIGERA_VERSION: master | v[x].[y].[z] (default=v3.28.3)" + + echo -e "\nTo shut down the cluster, use 'kind delete cluster'" +} + +cherry_pick() { + STASHED_CHANGES=0 + echo "checkpoint: $COMMIT_HASH" + # chery-vpp hard resets the repo to a commit - we want to keep our changes + if ! git diff-index --quiet HEAD --; then + echo "Saving stash" + git stash save "HST: temp stash" + STASHED_CHANGES=1 + fi + make -C $CALICOVPP_DIR cherry-vpp FORCE=y BASE=origin/master VPP_DIR=$VPP_DIR + + # pop the stash to build VPP with CalicoVPP's patches + our changes + if [ $STASHED_CHANGES -eq 1 ]; then + git stash pop + fi +} + +build_load_start_cni() { + make -C $VPP_DIR/extras/hs-test build-vpp-release + make -C $CALICOVPP_DIR dev-kind + make -C $CALICOVPP_DIR load-kind + $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up +} + +restore_repo() { + # stash changes, reset local repo to the original state and unstash changes (removes CalicoVPP's patches) + if ! git diff-index --quiet HEAD --; then + echo "Saving stash" + git stash save "HST: temp stash" + git reset --hard $COMMIT_HASH + git stash pop + else + git reset --hard $COMMIT_HASH + fi } setup_master() { @@ -66,34 +104,32 @@ setup_master() { make -C $CALICOVPP_DIR kind-new-cluster N_KIND_WORKERS=2 kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml - make -C $CALICOVPP_DIR cherry-vpp FORCE=y BASE=origin/master VPP_DIR=$VPP_DIR - make -C $VPP_DIR/extras/hs-test build-vpp-release - make -C $CALICOVPP_DIR dev-kind - make -C $CALICOVPP_DIR load-kind - $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up - if ! git diff-index --quiet HEAD --; then - echo "Saving stash" - git stash save "HST: temp stash" - git reset --hard origin/master - git stash pop - fi + + cherry_pick + build_load_start_cni + restore_repo } rebuild_master() { echo "Shutting down pods may take some time, timeout is set to 1m." timeout 1m $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh dn || true - make build-vpp-release - make -C $CALICOVPP_DIR dev-kind - make -C $CALICOVPP_DIR load-kind - $CALICOVPP_DIR/yaml/overlays/dev/kustomize.sh up + cherry_pick + build_load_start_cni + restore_repo } setup_release() { + CALICOVPP_VERSION="${CALICOVPP_VERSION:-latest}" + TIGERA_VERSION="${TIGERA_VERSION:-v3.28.3}" + echo "CALICOVPP_VERSION=$CALICOVPP_VERSION" + echo "TIGERA_VERSION=$TIGERA_VERSION" + envsubst < kubernetes/calico-config-template.yaml > kubernetes/calico-config.yaml + kind create cluster --config kubernetes/kind-config.yaml - kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.3/manifests/tigera-operator.yaml + kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/$TIGERA_VERSION/manifests/tigera-operator.yaml - echo "Sleeping for 10s, waiting for tigera operator to start up." - sleep 10 + echo "Waiting for tigera-operator pod to start up." + kubectl -n tigera-operator wait --for=condition=Ready pod --all --timeout=1m kubectl create -f https://raw.githubusercontent.com/projectcalico/vpp-dataplane/master/yaml/calico/installation-default.yaml kubectl create -f kubernetes/calico-config.yaml From 4a5304f18e45423167625f935c8f6966e8c4c249 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 13 Aug 2025 13:44:57 +0200 Subject: [PATCH 212/313] hs-test: fix envsubst not working Type: fix Change-Id: Iffb2a77ebd49d9d4784be80757c5a97a6ad98bb3 Signed-off-by: Adrian Villin --- extras/hs-test/kubernetes/setup-cluster.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/kubernetes/setup-cluster.sh b/extras/hs-test/kubernetes/setup-cluster.sh index 95ef3ebf6e..d64777bb06 100755 --- a/extras/hs-test/kubernetes/setup-cluster.sh +++ b/extras/hs-test/kubernetes/setup-cluster.sh @@ -119,8 +119,8 @@ rebuild_master() { } setup_release() { - CALICOVPP_VERSION="${CALICOVPP_VERSION:-latest}" - TIGERA_VERSION="${TIGERA_VERSION:-v3.28.3}" + export CALICOVPP_VERSION="${CALICOVPP_VERSION:-latest}" + export TIGERA_VERSION="${TIGERA_VERSION:-v3.28.3}" echo "CALICOVPP_VERSION=$CALICOVPP_VERSION" echo "TIGERA_VERSION=$TIGERA_VERSION" envsubst < kubernetes/calico-config-template.yaml > kubernetes/calico-config.yaml From 8216bbd749e29ea731363c70baba4646ac69cc7d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 13 Aug 2025 10:10:08 -0400 Subject: [PATCH 213/313] http: req_state_udp_tunnel_tx fix Type: fix Change-Id: Ibf7afb69ba709dd28254807a34644ecc5bf892e8 Signed-off-by: Matus Fabian --- src/plugins/http/http1.c | 4 ++-- src/plugins/http/http2/http2.c | 25 ++++++++++++++++++------- src/plugins/http/http_private.h | 18 +++--------------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 146ee8d3d0..9d458aad5a 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -1706,7 +1706,7 @@ http1_req_state_udp_tunnel_tx (http_conn_t *hc, http_req_t *req, while (to_deq > 0) { /* read datagram header */ - http_io_as_read (req, (u8 *) &hdr, sizeof (hdr), 1); + http_io_as_peek (req, (u8 *) &hdr, sizeof (hdr), 0); ASSERT (hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN); dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN; ASSERT (to_deq >= dgram_size); @@ -1722,7 +1722,7 @@ http1_req_state_udp_tunnel_tx (http_conn_t *hc, http_req_t *req, payload = http_encap_udp_payload_datagram (buf, hdr.data_length); capsule_size = (payload - buf) + hdr.data_length; /* read payload */ - http_io_as_read (req, payload, hdr.data_length, 1); + http_io_as_peek (req, payload, hdr.data_length, sizeof (hdr)); http_io_as_drain (req, dgram_size); /* send capsule */ http_io_ts_write (hc, buf, capsule_size, sp); diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index b56518a841..fb70613238 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -719,6 +719,7 @@ http2_sched_dispatch_tunnel (http2_req_t *req, http_conn_t *hc, http_io_as_drain (&req->base, n_written); req->peer_window -= n_written; h2c->peer_window -= n_written; + HTTP_DBG (1, "written %lu", n_written); if (req->peer_window == 0) { @@ -759,7 +760,8 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, return; } /* read datagram header */ - http_io_as_read (&req->base, (u8 *) &hdr, sizeof (hdr), 1); + http_io_as_peek (&req->base, (u8 *) &hdr, sizeof (hdr), 0); + HTTP_DBG (1, "datagram len %lu", hdr.data_length); ASSERT (hdr.data_length <= HTTP_UDP_PAYLOAD_MAX_LEN); dgram_size = hdr.data_length + SESSION_CONN_HDR_LEN; ASSERT (max_read >= dgram_size); @@ -771,7 +773,7 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, h2c->peer_settings.max_frame_size)) { /* drop datagram if not fit into frame */ - HTTP_DBG (1, "datagram too large, dropped"); + HTTP_DBG (1, "datagram larger than maximum frame size, dropped"); http_io_as_drain (&req->base, dgram_size); return; } @@ -779,9 +781,11 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, if (req->peer_window < (hdr.data_length + HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD)) { + HTTP_DBG (1, "not enough space in stream window (%lu) for capsule", + req->peer_window); /* mark that we need window update on stream */ - HTTP_DBG (1, "not enough space in stream window for capsule"); req->flags |= HTTP2_REQ_F_NEED_WINDOW_UPDATE; + return; } max_write = http_io_ts_max_write (hc, 0); @@ -802,7 +806,7 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, payload = http_encap_udp_payload_datagram (buf, hdr.data_length); capsule_size = (payload - buf) + hdr.data_length; /* read payload */ - http_io_as_read (&req->base, payload, hdr.data_length, 1); + http_io_as_peek (&req->base, payload, hdr.data_length, sizeof (hdr)); http_io_as_drain (&req->base, dgram_size); req->peer_window -= capsule_size; @@ -814,6 +818,7 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, { buf, capsule_size } }; n_written = http_io_ts_write_segs (hc, segs, 2, 0); ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + capsule_size)); + HTTP_DBG (1, "capsule payload len %lu", hdr.data_length); if (max_read - dgram_size) { @@ -1670,7 +1675,7 @@ http2_req_state_tunnel_rx (http_conn_t *hc, http2_req_t *req, { u32 max_enq; - HTTP_DBG (1, "tunnel received data from client"); + HTTP_DBG (1, "tunnel received data from peer"); max_enq = http_io_as_max_write (&req->base); if (max_enq < req->payload_len) @@ -1848,7 +1853,7 @@ http2_req_state_tunnel_tx (http_conn_t *hc, http2_req_t *req, ASSERT (!clib_llist_elt_is_linked (req, sched_list)); - HTTP_DBG (1, "tunnel received data from target"); + HTTP_DBG (1, "tunnel received data from app"); /* add data back to stream scheduler */ HTTP_DBG (1, "adding to data queue req_index %x", @@ -2700,7 +2705,7 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, HTTP_DBG (1, "received app read notification stream id %u", req->stream_id); /* send stream window update if app read data in rx fifo and we expect more * data (stream is still open) */ - expected_state = hc->flags & HTTP_CONN_F_IS_SERVER ? + expected_state = (hc->flags & HTTP_CONN_F_IS_SERVER || req->base.is_tunnel) ? HTTP2_STREAM_STATE_OPEN : HTTP2_STREAM_STATE_HALF_CLOSED; if (req->stream_state == expected_state) @@ -2852,6 +2857,12 @@ http2_transport_rx_callback (http_conn_t *hc) if (PREDICT_FALSE (to_deq < HTTP2_FRAME_HEADER_SIZE)) { HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq); +#if HTTP_DEBUG + u8 *tmp = 0; + vec_validate (tmp, to_deq - 1); + http_io_ts_read (hc, tmp, to_deq, 0); + clib_warning ("%U", format_hex_bytes, tmp, to_deq); +#endif http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); return; } diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index b76f4f6b50..daa9d0a919 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -94,8 +94,6 @@ typedef struct http_req_ #define hr_hc_index c_http_req_id.hc_index #define hr_req_handle connection.c_index - u32 as_fifo_offset; /* for peek */ - http_req_state_t state; /* state-machine state */ http_buffer_t tx_buf; /* message body from app to be sent */ @@ -629,21 +627,13 @@ http_io_as_write_segs (http_req_t *req, const svm_fifo_seg_t segs[], } always_inline u32 -http_io_as_read (http_req_t *req, u8 *buf, u32 len, u8 peek) +http_io_as_peek (http_req_t *req, u8 *buf, u32 len, u32 offset) { int n_read; session_t *as = session_get_from_handle (req->hr_pa_session_handle); - if (peek) - { - n_read = svm_fifo_peek (as->tx_fifo, req->as_fifo_offset, len, buf); - ASSERT (n_read > 0); - req->as_fifo_offset += len; - return (u32) n_read; - } - - n_read = svm_fifo_dequeue (as->tx_fifo, len, buf); - ASSERT (n_read == len); + n_read = svm_fifo_peek (as->tx_fifo, offset, len, buf); + ASSERT (n_read > 0); return (u32) n_read; } @@ -662,7 +652,6 @@ http_io_as_drain (http_req_t *req, u32 len) { session_t *as = session_get_from_handle (req->hr_pa_session_handle); svm_fifo_dequeue_drop (as->tx_fifo, len); - req->as_fifo_offset = 0; } always_inline void @@ -670,7 +659,6 @@ http_io_as_drain_all (http_req_t *req) { session_t *as = session_get_from_handle (req->hr_pa_session_handle); svm_fifo_dequeue_drop_all (as->tx_fifo); - req->as_fifo_offset = 0; } always_inline void From a03a2c26327898648ea24c546afb809580db99cf Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 13 Aug 2025 13:10:00 -0400 Subject: [PATCH 214/313] http: h2 client conn error handling fix Notify only parent app session in case of http/2 connection error. Type: fix Change-Id: Ia02fe8863cd88df8a8dc7361655322c1335bbbd3 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 75 ++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index fb70613238..59476b467b 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -105,6 +105,7 @@ typedef struct http2_conn_ctx_ u8 *unsent_headers; /* temporary storing tx fragmented headers */ u32 unsent_headers_offset; u32 req_num; + u32 parent_req_index; } http2_conn_ctx_t; typedef struct http2_worker_ctx_ @@ -175,6 +176,7 @@ http2_conn_ctx_alloc_w_thread (http_conn_t *hc) h2c->old_tx_streams = clib_llist_make_head (wrk->req_pool, sched_list); h2c->sched_list.next = CLIB_LLIST_INVALID_INDEX; h2c->sched_list.prev = CLIB_LLIST_INVALID_INDEX; + h2c->parent_req_index = SESSION_INVALID_INDEX; hc->opaque = uword_to_pointer (h2c - wrk->conn_pool, void *); cnt = clib_atomic_fetch_add_relax (&h2m->n_sessions, 1); /* (re)start stream tx scheduler if this is first connection */ @@ -221,7 +223,7 @@ http2_conn_ctx_free (http_conn_t *hc) } static inline http2_req_t * -http2_conn_alloc_req (http_conn_t *hc) +http2_conn_alloc_req (http_conn_t *hc, u8 is_parent) { http2_worker_ctx_t *wrk = http2_get_worker (hc->c_thread_index); http2_conn_ctx_t *h2c; @@ -248,6 +250,11 @@ http2_conn_alloc_req (http_conn_t *hc) req->peer_window = h2c->peer_settings.initial_window_size; req->our_window = h2c->settings.initial_window_size; h2c->req_num++; + if (is_parent) + { + req->flags |= HTTP2_REQ_F_IS_PARENT; + h2c->parent_req_index = req_index; + } return req; } @@ -282,6 +289,8 @@ http2_conn_free_req (http2_conn_ctx_t *h2c, http2_req_t *req, http_buffer_free (&req->base.tx_buf); if (req->stream_id) hash_unset (h2c->req_by_stream_id, req->stream_id); + if (req->flags & HTTP2_REQ_F_IS_PARENT) + h2c->parent_req_index = SESSION_INVALID_INDEX; if (CLIB_DEBUG) memset (req, 0xba, sizeof (*req)); pool_put (wrk->req_pool, req); @@ -440,11 +449,8 @@ http2_connection_error (http_conn_t *hc, http2_error_t error, } else { - hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ - req = http2_req_get (req_index, hc->c_thread_index); - session_transport_reset_notify ( - &req->base.connection); - })); + req = http2_req_get (h2c->parent_req_index, hc->c_thread_index); + session_transport_reset_notify (&req->base.connection); } } if (clib_llist_elt_is_linked (h2c, sched_list)) @@ -1984,7 +1990,7 @@ http2_handle_headers_frame (http_conn_t *hc, http2_frame_header_t *fh) HTTP2_ERROR_REFUSED_STREAM, 0); return HTTP2_ERROR_NO_ERROR; } - req = http2_conn_alloc_req (hc); + req = http2_conn_alloc_req (hc, 0); http2_req_set_stream_id (req, h2c, fh->stream_id, 0); req->dispatch_headers_cb = http2_sched_dispatch_resp_headers; http_conn_accept_request (hc, &req->base); @@ -2368,8 +2374,7 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) { h2c->flags &= ~HTTP2_CONN_F_EXPECT_SERVER_SETTINGS; HTTP_DBG (1, "client connection established"); - req = http2_conn_alloc_req (hc); - req->flags |= HTTP2_REQ_F_IS_PARENT; + req = http2_conn_alloc_req (hc, 1); hc->flags |= HTTP_CONN_F_HAS_REQUEST; hpack_dynamic_table_init ( &h2c->decoder_dynamic_table, @@ -2510,10 +2515,19 @@ http2_handle_goaway_frame (http_conn_t *hc, http2_frame_header_t *fh) rx_buf + HTTP2_GOAWAY_MIN_SIZE, fh->length - HTTP2_GOAWAY_MIN_SIZE); /* connection error */ - hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ - req = http2_req_get (req_index, hc->c_thread_index); - session_transport_reset_notify (&req->base.connection); - })); + if (hc->flags & HTTP_CONN_F_IS_SERVER) + { + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + session_transport_reset_notify ( + &req->base.connection); + })); + } + else + { + req = http2_req_get (h2c->parent_req_index, hc->c_thread_index); + session_transport_reset_notify (&req->base.connection); + } if (clib_llist_elt_is_linked (h2c, sched_list)) clib_llist_remove (wrk->conn_pool, sched_list, h2c); http_shutdown_transport (hc); @@ -3033,14 +3047,23 @@ http2_transport_reset_callback (http_conn_t *hc) return; h2c = http2_conn_ctx_get_w_thread (hc); - hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ - req = http2_req_get (req_index, hc->c_thread_index); - if (req->stream_state != HTTP2_STREAM_STATE_CLOSED) - { - HTTP_DBG (1, "req_index %x", req_index); - session_transport_reset_notify (&req->base.connection); - } - })); + if (hc->flags & HTTP_CONN_F_IS_SERVER) + { + hash_foreach (stream_id, req_index, h2c->req_by_stream_id, ({ + req = http2_req_get (req_index, hc->c_thread_index); + if (req->stream_state != HTTP2_STREAM_STATE_CLOSED) + { + HTTP_DBG (1, "req_index %x", req_index); + session_transport_reset_notify ( + &req->base.connection); + } + })); + } + else + { + req = http2_req_get (h2c->parent_req_index, hc->c_thread_index); + session_transport_reset_notify (&req->base.connection); + } if (clib_llist_elt_is_linked (h2c, sched_list)) clib_llist_remove (wrk->conn_pool, sched_list, h2c); } @@ -3096,7 +3119,7 @@ http2_conn_connect_stream_callback (http_conn_t *hc, u32 parent_app_api_ctx) if (h2c->req_num == h2c->settings.max_concurrent_streams) return app_worker_connect_notify (app_wrk, 0, SESSION_E_MAX_STREAMS_HIT, hc->hc_pa_app_api_ctx); - req = http2_conn_alloc_req (hc); + req = http2_conn_alloc_req (hc, 0); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); return http_conn_established (hc, &req->base, parent_app_api_ctx); } @@ -3119,6 +3142,14 @@ http2_conn_cleanup_callback (http_conn_t *hc) session_transport_delete_notify (&req->base.connection); http2_conn_free_req (h2c, req, hc->c_thread_index); } + /* in case client app didn't use parent */ + if (!(hc->flags & HTTP_CONN_F_IS_SERVER) && + (h2c->parent_req_index != SESSION_INVALID_INDEX)) + { + req = http2_req_get (h2c->parent_req_index, hc->c_thread_index); + session_transport_delete_notify (&req->base.connection); + http2_conn_free_req (h2c, req, hc->c_thread_index); + } vec_free (req_indices); http2_conn_ctx_free (hc); From 6feb869037d91028aba70cdb831a586a71ddb563 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 14 Aug 2025 06:09:01 -0400 Subject: [PATCH 215/313] http: h2 connect-udp dgram mode flow control fix keep some space for dgram headers Type: fix Change-Id: I91002022cf0a9878d23c4e98dfcfbb6089073cb7 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 59476b467b..cde1a33c54 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -14,6 +14,7 @@ /* connection-level flow control window kind of mirrors TCP flow control */ /* TODO: configurable? */ #define HTTP2_CONNECTION_WINDOW_SIZE (10 << 20) +#define HTTP2_CONNECT_UDP_FIFO_THRESH (2 << 10) #define foreach_http2_stream_state \ _ (IDLE, "IDLE") \ @@ -810,6 +811,7 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, buf = http_get_tx_buf (hc); /* create capsule header */ payload = http_encap_udp_payload_datagram (buf, hdr.data_length); + ASSERT ((payload - buf) <= HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD); capsule_size = (payload - buf) + hdr.data_length; /* read payload */ http_io_as_peek (&req->base, payload, hdr.data_length, sizeof (hdr)); @@ -1696,11 +1698,11 @@ http2_req_state_tunnel_rx (http_conn_t *hc, http2_req_t *req, switch (req->stream_state) { case HTTP2_STREAM_STATE_HALF_CLOSED: - HTTP_DBG (1, "client want to close tunnel"); + HTTP_DBG (1, "peer want to close tunnel"); session_transport_closing_notify (&req->base.connection); break; case HTTP2_STREAM_STATE_CLOSED: - HTTP_DBG (1, "client closed tunnel"); + HTTP_DBG (1, "peer closed tunnel"); http2_stream_close (req, hc); break; default: @@ -1720,12 +1722,13 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, u64 payload_len = 0; session_dgram_hdr_t hdr; - HTTP_DBG (1, "udp tunnel received data from client"); + HTTP_DBG (1, "udp tunnel received data from peer"); rv = http_decap_udp_payload_datagram (req->payload, req->payload_len, &payload_offset, &payload_len); HTTP_DBG (1, "rv=%d, payload_offset=%u, payload_len=%llu", rv, payload_offset, payload_len); + ASSERT (payload_offset <= HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD); if (PREDICT_FALSE (rv != 0)) { if (rv < 0) @@ -1749,7 +1752,10 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, } if (http_io_as_max_write (&req->base) < (sizeof (hdr) + payload_len)) { - clib_warning ("app's rx fifo full"); + /* should only happen when we don't keep enough space for dgram hdr */ + clib_warning ("not enough space in app fifo (%lu) for dgram (%lu)", + http_io_as_max_write (&req->base), + sizeof (hdr) + payload_len); http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, sp); return HTTP_SM_STOP; } @@ -1766,7 +1772,7 @@ http2_req_state_udp_tunnel_rx (http_conn_t *hc, http2_req_t *req, if (req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED) { - HTTP_DBG (1, "client want to close tunnel"); + HTTP_DBG (1, "peer want to close tunnel"); session_transport_closing_notify (&req->base.connection); } @@ -2210,12 +2216,12 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) { if (req->stream_state == HTTP2_STREAM_STATE_CLOSED) { - HTTP_DBG (1, "client closed tunnel"); + HTTP_DBG (1, "peer closed tunnel"); http2_stream_close (req, hc); } else { - HTTP_DBG (1, "client want to close tunnel"); + HTTP_DBG (1, "peer want to close tunnel"); session_transport_closing_notify (&req->base.connection); } return HTTP2_ERROR_NO_ERROR; @@ -2727,6 +2733,9 @@ http2_app_rx_evt_callback (http_conn_t *hc, u32 req_index, http_io_as_reset_has_read_ntf (&req->base); response = http_get_tx_buf (hc); increment = http_io_as_max_write (&req->base) - req->our_window; + /* keep some space for dgram headers */ + if (req->base.is_tunnel && hc->udp_tunnel_mode == HTTP_UDP_TUNNEL_DGRAM) + increment -= clib_min (increment, HTTP2_CONNECT_UDP_FIFO_THRESH); HTTP_DBG (1, "stream window increment %u", increment); if (increment == 0) return; @@ -2784,7 +2793,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, } break; case HTTP2_STREAM_STATE_HALF_CLOSED: - HTTP_DBG (1, "proxy confirmed close"); + HTTP_DBG (1, "app confirmed close"); http2_tunnel_send_close (hc, req); http2_stream_close (req, hc); break; @@ -2915,6 +2924,12 @@ http2_transport_rx_callback (http_conn_t *hc) if (to_deq && to_deq < HTTP2_FRAME_HEADER_SIZE) { HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq); +#if HTTP_DEBUG + u8 *tmp = 0; + vec_validate (tmp, to_deq - 1); + http_io_ts_read (hc, tmp, to_deq, 0); + clib_warning ("%U", format_hex_bytes, tmp, to_deq); +#endif http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); return; } @@ -2969,6 +2984,7 @@ http2_transport_rx_callback (http_conn_t *hc) case HTTP2_FRAME_TYPE_PRIORITY: /* deprecated */ default: /* ignore unknown frame type */ + HTTP_DBG (1, "unknown frame type, dropped"); http_io_ts_drain (hc, fh.length); rv = HTTP2_ERROR_NO_ERROR; break; From e92d67843a2d114fbc33e4c76de8145139aeea2f Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 14 Aug 2025 15:27:07 -0400 Subject: [PATCH 216/313] http: h2 fix handling of incomplete frame headers Type: fix Change-Id: I47155fd7d453b0e9bdcf39f5bc14139323a9a42e Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index cde1a33c54..834d79ec3a 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2880,13 +2880,6 @@ http2_transport_rx_callback (http_conn_t *hc) if (PREDICT_FALSE (to_deq < HTTP2_FRAME_HEADER_SIZE)) { HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq); -#if HTTP_DEBUG - u8 *tmp = 0; - vec_validate (tmp, to_deq - 1); - http_io_ts_read (hc, tmp, to_deq, 0); - clib_warning ("%U", format_hex_bytes, tmp, to_deq); -#endif - http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); return; } @@ -2920,20 +2913,6 @@ http2_transport_rx_callback (http_conn_t *hc) http_io_ts_drain (hc, HTTP2_FRAME_HEADER_SIZE); to_deq -= fh.length; - /* to prevent data leakage */ - if (to_deq && to_deq < HTTP2_FRAME_HEADER_SIZE) - { - HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq); -#if HTTP_DEBUG - u8 *tmp = 0; - vec_validate (tmp, to_deq - 1); - http_io_ts_read (hc, tmp, to_deq, 0); - clib_warning ("%U", format_hex_bytes, tmp, to_deq); -#endif - http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); - return; - } - HTTP_DBG (1, "frame type 0x%02x len %u", fh.type, fh.length); if ((h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION) && @@ -2973,6 +2952,14 @@ http2_transport_rx_callback (http_conn_t *hc) rv = http2_handle_goaway_frame (hc, &fh); break; case HTTP2_FRAME_TYPE_PING: + /* to prevent information leakage, PING frames can be sent from any + * endpoint and is expected to be sent with higher priority */ + if (to_deq && to_deq < HTTP2_FRAME_HEADER_SIZE) + { + HTTP_DBG (1, "to_deq %u is less than frame header size", to_deq); + http2_connection_error (hc, HTTP2_ERROR_PROTOCOL_ERROR, 0); + return; + } rv = http2_handle_ping_frame (hc, &fh); break; case HTTP2_FRAME_TYPE_CONTINUATION: From 94feaa85df8d38957b02480e0491981559f55492 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 13 Aug 2025 17:44:07 +0200 Subject: [PATCH 217/313] hs-test: remove non-root user build enforcement Type: improvement Change-Id: I2e85eeea0d65c7a369a0ca7be3e5508d3a2cf90a Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 28 +++++++-------------- extras/hs-test/hs_test.sh | 3 +-- extras/hs-test/infra/common/suite_common.go | 2 +- extras/hs-test/infra/kind/suite_kind.go | 6 ++--- extras/hs-test/script/build_hst.sh | 6 ++--- 5 files changed, 16 insertions(+), 29 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index c88d5a3c36..bc52c22d0a 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -86,7 +86,6 @@ DOCKER_TTY := -it endif FORCE_BUILD?=true -BUILD_AS:=$(strip $(shell echo $${SUDO_USER:-$${USER:-root}})) # privileged is needed for "ip netns" otherwise we are not able to create namespace DOCKER_CAPABILITIES:=--privileged @@ -149,26 +148,17 @@ list-tests: @go run github.com/onsi/ginkgo/v2/ginkgo --dry-run -v --no-color --seed=2 | head -n -1 | grep 'test.go' | \ sed 's/^/* /; s/\(Suite\) /\1\//g' -.PHONY: build-msg -build-msg: - @echo "Building VPP as '$(BUILD_AS)'" - @echo "****************************************************************" - @echo "If you wish to build VPP as root, use 'make ... BUILD_AS=root'" - @echo "or" - @echo "'[sudo] SUDO_USER=root make ...'" - @echo "****************************************************************" - .PHONY: build-vpp-release -build-vpp-release: build-msg - @sudo -u $(BUILD_AS) $(MAKE) -C ../.. build-release +build-vpp-release: + @$(MAKE) -C ../.. build-release .PHONY: build-vpp-debug -build-vpp-debug: build-msg - @sudo -u $(BUILD_AS) $(MAKE) -C ../.. build +build-vpp-debug: + @$(MAKE) -C ../.. build .PHONY: build-vpp-gcov -build-vpp-gcov: build-msg - @sudo -u $(BUILD_AS) $(MAKE) -C ../.. build-gcov +build-vpp-gcov: + @$(MAKE) -C ../.. build-gcov .build.ok: build @touch .build.ok @@ -260,19 +250,19 @@ build-go: .PHONY: build build: .deps.ok build-vpp-release @rm -f .build.ok - bash ./script/build_hst.sh release $(FORCE_BUILD) $(BUILD_AS) + bash ./script/build_hst.sh release $(FORCE_BUILD) @touch .build.ok .PHONY: build-cov build-cov: .deps.ok build-vpp-gcov @rm -f .build.cov.ok - bash ./script/build_hst.sh gcov $(FORCE_BUILD) $(BUILD_AS) + bash ./script/build_hst.sh gcov $(FORCE_BUILD) @touch .build.cov.ok .PHONY: build-debug build-debug: .deps.ok build-vpp-debug @rm -f .build.ok - bash ./script/build_hst.sh debug $(FORCE_BUILD) $(BUILD_AS) + bash ./script/build_hst.sh debug $(FORCE_BUILD) @touch .build.ok .deps.ok: diff --git a/extras/hs-test/hs_test.sh b/extras/hs-test/hs_test.sh index c30a75966e..ac6b40760d 100755 --- a/extras/hs-test/hs_test.sh +++ b/extras/hs-test/hs_test.sh @@ -171,8 +171,7 @@ if [ $focused_test -eq 0 ] && [ $debug_set -eq 1 ]; then exit 2 fi -sudo_user="${SUDO_USER:-${USER:-root}}" -args="$args -sudo_user $sudo_user" +args="$args -whoami $(whoami)" if [ $leak_check_set -eq 1 ]; then if [ $focused_test -eq 0 ]; then diff --git a/extras/hs-test/infra/common/suite_common.go b/extras/hs-test/infra/common/suite_common.go index 97dd691767..2b7560dacc 100644 --- a/extras/hs-test/infra/common/suite_common.go +++ b/extras/hs-test/infra/common/suite_common.go @@ -18,7 +18,7 @@ import ( var IsCoverage = flag.Bool("coverage", false, "use coverage run config") var IsPersistent = flag.Bool("persist", false, "persists topology config") var IsVerbose = flag.Bool("verbose", false, "verbose test output") -var SudoUser = flag.String("sudo_user", "root", "what user ran hs-test with sudo") +var WhoAmI = flag.String("whoami", "root", "what user ran hs-test") var ParallelTotal = flag.Lookup("ginkgo.parallel.total") var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp") var DryRun = flag.Bool("dryrun", false, "set up containers but don't run tests") diff --git a/extras/hs-test/infra/kind/suite_kind.go b/extras/hs-test/infra/kind/suite_kind.go index ecbbe1dc35..1faedc7e06 100644 --- a/extras/hs-test/infra/kind/suite_kind.go +++ b/extras/hs-test/infra/kind/suite_kind.go @@ -77,12 +77,12 @@ func (s *KindSuite) SetupSuite() { s.initPods() s.loadDockerImages() var err error - if *SudoUser == "root" { + if *WhoAmI == "root" { s.KubeconfigPath = "/.kube/config" } else { - s.KubeconfigPath = "/home/" + *SudoUser + "/.kube/config" + s.KubeconfigPath = "/home/" + *WhoAmI + "/.kube/config" } - s.Log("User: '%s'", *SudoUser) + s.Log("User: '%s'", *WhoAmI) s.Log("Config path: '%s'", s.KubeconfigPath) s.Config, err = clientcmd.BuildConfigFromFlags("", s.KubeconfigPath) diff --git a/extras/hs-test/script/build_hst.sh b/extras/hs-test/script/build_hst.sh index f0ad9ae7f9..955e143f0a 100755 --- a/extras/hs-test/script/build_hst.sh +++ b/extras/hs-test/script/build_hst.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -BUILD_AS=$3 - if [ "$(lsb_release -is)" != Ubuntu ]; then echo "Host stack test framework is supported only on Ubuntu" exit 1 @@ -74,9 +72,9 @@ rm -rf vpp-data/bin/* || true rm -rf vpp-data/lib/* || true declare -i res=0 -sudo -u $BUILD_AS cp ${VPP_BUILD_ROOT}/bin/* ${bin} +cp ${VPP_BUILD_ROOT}/bin/* ${bin} res+=$? -sudo -u $BUILD_AS cp -r ${VPP_BUILD_ROOT}/lib/"${OS_ARCH}"-linux-gnu/* ${lib} +cp -r ${VPP_BUILD_ROOT}/lib/"${OS_ARCH}"-linux-gnu/* ${lib} res+=$? if [ "$res" -ne 0 ]; then echo "Failed to copy VPP files. Is VPP built? Try running 'make build' in VPP directory." From a53d4afd9c0869b8f3064bb3a4c60f5bfd66404b Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Fri, 15 Aug 2025 09:10:23 +0200 Subject: [PATCH 218/313] hs-test: KindIperfVclTest fix - fixed test failure when pods take slightly longer to deploy Type: fix Change-Id: I76c93213563a38b0165720986def9b60c4b671ac Signed-off-by: Adrian Villin --- extras/hs-test/kind_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/kind_test.go b/extras/hs-test/kind_test.go index 12f6b3972f..8039e6b062 100644 --- a/extras/hs-test/kind_test.go +++ b/extras/hs-test/kind_test.go @@ -17,10 +17,10 @@ const vcl string = "VCL_CONFIG=/vcl.conf" const ldp string = "LD_PRELOAD=/usr/lib/libvcl_ldpreload.so" func KindIperfVclTest(s *KindSuite) { - ctx, cancel := context.WithTimeout(s.MainContext, time.Second*30) - defer cancel() s.DeployPod(s.Pods.ClientGeneric) s.DeployPod(s.Pods.ServerGeneric) + ctx, cancel := context.WithTimeout(s.MainContext, time.Second*40) + defer cancel() _, err := s.Pods.ClientGeneric.Exec(ctx, []string{"/bin/bash", "-c", VclConfIperf}) s.AssertNil(err) From 98399d0b9ed6964c1e9b4c7268b31d0c7303ceb6 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 15 Aug 2025 04:52:56 -0400 Subject: [PATCH 219/313] http: http2_sched_dispatch_tunnel fix Type: fix Change-Id: I06f0da0d9805014b21c67da6113f2fc1d91a5408 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 12 +++++++----- src/plugins/http/http_private.h | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 834d79ec3a..5ba094bcf3 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -706,7 +706,6 @@ http2_sched_dispatch_tunnel (http2_req_t *req, http_conn_t *hc, max_write = clib_min (max_write, (u32) req->peer_window); max_write = clib_min (max_write, h2c->peer_window); max_write = clib_min (max_write, h2c->peer_settings.max_frame_size); - n_read = clib_min (max_write, max_read); if (req->stream_state == HTTP2_STREAM_STATE_HALF_CLOSED && max_write >= max_read) @@ -715,13 +714,16 @@ http2_sched_dispatch_tunnel (http2_req_t *req, http_conn_t *hc, session_transport_closed_notify (&req->base.connection); flags = HTTP2_FRAME_FLAG_END_STREAM; } + + max_read = clib_min (max_write, max_read); + n_read = http_io_as_read_segs (&req->base, segs + 1, &n_segs, max_read); + http2_frame_write_data_header (n_read, req->stream_id, flags, fh); segs[0].len = HTTP2_FRAME_HEADER_SIZE; segs[0].data = fh; - http_io_as_read_segs (&req->base, segs + 1, &n_segs, n_read); - n_written = http_io_ts_write_segs (hc, segs, n_segs + 1, 0); + ASSERT (n_written == (HTTP2_FRAME_HEADER_SIZE + n_read)); n_written -= HTTP2_FRAME_HEADER_SIZE; http_io_as_drain (&req->base, n_written); req->peer_window -= n_written; @@ -1683,12 +1685,12 @@ http2_req_state_tunnel_rx (http_conn_t *hc, http2_req_t *req, { u32 max_enq; - HTTP_DBG (1, "tunnel received data from peer"); + HTTP_DBG (1, "tunnel received data from peer %lu", req->payload_len); max_enq = http_io_as_max_write (&req->base); if (max_enq < req->payload_len) { - clib_warning ("app's rx fifo full"); + clib_warning ("not enough space in app fifo (%lu)", max_enq); http2_stream_error (hc, req, HTTP2_ERROR_INTERNAL_ERROR, sp); return HTTP_SM_STOP; } diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index daa9d0a919..6036b5ad3d 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -637,7 +637,7 @@ http_io_as_peek (http_req_t *req, u8 *buf, u32 len, u32 offset) return (u32) n_read; } -always_inline void +always_inline u32 http_io_as_read_segs (http_req_t *req, svm_fifo_seg_t *segs, u32 *n_segs, u32 max_bytes) { @@ -645,6 +645,7 @@ http_io_as_read_segs (http_req_t *req, svm_fifo_seg_t *segs, u32 *n_segs, session_t *as = session_get_from_handle (req->hr_pa_session_handle); n_read = svm_fifo_segments (as->tx_fifo, 0, segs, n_segs, max_bytes); ASSERT (n_read > 0); + return (u32) n_read; } always_inline void From 12e4a453f27322bac2dc66c00911b997f209796e Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 15 Aug 2025 05:34:21 -0400 Subject: [PATCH 220/313] hs-test: bump curl version to 8.15.0 http/3 (quic) works better Type: test Change-Id: I1196ec70b0c07b01f13b3230664136935e06e0ac Signed-off-by: Matus Fabian --- extras/hs-test/script/build_curl.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/script/build_curl.sh b/extras/hs-test/script/build_curl.sh index ec89379605..d3fc84a357 100755 --- a/extras/hs-test/script/build_curl.sh +++ b/extras/hs-test/script/build_curl.sh @@ -1,5 +1,6 @@ #!/bin/bash set -x -wget https://github.com/stunnel/static-curl/releases/download/8.5.0/curl-static-"$TARGETARCH"-8.5.0.tar.xz -tar -xvf ./curl-static-"$TARGETARCH"-8.5.0.tar.xz +OS_ARCH="$(uname -m)" +wget https://github.com/stunnel/static-curl/releases/download/8.15.0/curl-linux-"${OS_ARCH}"-glibc-8.15.0.tar.xz +tar -xvf ./curl-linux-"${OS_ARCH}"-glibc-8.15.0.tar.xz cp curl /usr/bin/curl \ No newline at end of file From 465dc0b175719558ad55064ee590f7dee4d0f089 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 15 Aug 2025 09:09:28 -0400 Subject: [PATCH 221/313] hs-test: bump nginx version to 1.28 Type: test Change-Id: I37aafefa2290af48607779a5cb0036c92f67b2b5 Signed-off-by: Florin Coras --- extras/hs-test/docker/Dockerfile.base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/hs-test/docker/Dockerfile.base b/extras/hs-test/docker/Dockerfile.base index e6e19f9f39..8186d7b307 100644 --- a/extras/hs-test/docker/Dockerfile.base +++ b/extras/hs-test/docker/Dockerfile.base @@ -59,7 +59,7 @@ RUN echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ RUN bash -c 'echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ | tee /etc/apt/preferences.d/99nginx' -RUN apt update && apt install -y nginx=1.26.2* \ +RUN apt update && apt install -y nginx=1.28.0* \ # Clean up && apt-get clean \ && rm -rf /var/lib/apt/lists/* From 8760b33c189e041f86a3a750223626c0ef3f74bc Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Thu, 14 Aug 2025 18:56:57 +0200 Subject: [PATCH 222/313] vnet: add eeprom_read_function Introduce a device-class function to read EEPROM from interfaces. Implement it for the DPDK plugin. Type: improvement Change-Id: I8723937a5618fada2d87d7bded33bf9fc85836f3 Signed-off-by: pim@ipng.nl --- src/plugins/dpdk/device/common.c | 64 +++++++++++++ src/plugins/dpdk/device/device.c | 1 + src/plugins/dpdk/device/dpdk.h | 3 + src/vnet/interface.h | 17 ++++ src/vnet/interface_cli.c | 154 +++++++++++++++++++++++++++++++ 5 files changed, 239 insertions(+) diff --git a/src/plugins/dpdk/device/common.c b/src/plugins/dpdk/device/common.c index f496226c89..9de39b07f6 100644 --- a/src/plugins/dpdk/device/common.c +++ b/src/plugins/dpdk/device/common.c @@ -515,6 +515,70 @@ dpdk_get_vmbus_device (const struct rte_eth_dev_info *info) } #endif /* __linux__ */ +clib_error_t * +dpdk_read_eeprom (vnet_main_t *vnm, vnet_hw_interface_t *hi, + vnet_interface_eeprom_t **eeprom) +{ + dpdk_main_t *dm = &dpdk_main; + vnet_interface_main_t *im = &vnm->interface_main; + dpdk_device_t *xd; + vnet_device_class_t *dc; + struct rte_eth_dev_module_info mi = { 0 }; + struct rte_dev_eeprom_info ei = { 0 }; + + dc = vec_elt_at_index (im->device_classes, hi->dev_class_index); + *eeprom = NULL; + + if (dc->index != dpdk_device_class.index) + { + return clib_error_return (0, "Interface %v is not a DPDK interface", + hi->name); + } + + if (hi->dev_instance >= vec_len (dm->devices)) + { + return clib_error_return (0, "Invalid device instance %u", + hi->dev_instance); + } + + xd = vec_elt_at_index (dm->devices, hi->dev_instance); + + /* Get module info */ + if (rte_eth_dev_get_module_info (xd->port_id, &mi) != 0) + { + return clib_error_return ( + 0, "Module info not available for interface %v", hi->name); + } + if (mi.eeprom_len > 1024) + { + return clib_error_return (0, "EEPROM invalid length: %u bytes", + mi.eeprom_len); + } + + /* Allocate EEPROM structure */ + *eeprom = clib_mem_alloc (sizeof (vnet_interface_eeprom_t)); + if (!*eeprom) + { + return clib_error_return (0, "Memory allocation failed"); + } + + /* Get EEPROM data */ + ei.length = mi.eeprom_len; + ei.data = (*eeprom)->eeprom_raw; + + if (rte_eth_dev_get_module_eeprom (xd->port_id, &ei) != 0) + { + clib_mem_free (*eeprom); + *eeprom = NULL; + return clib_error_return (0, "EEPROM read error for interface %v", + hi->name); + } + + (*eeprom)->eeprom_len = mi.eeprom_len; + (*eeprom)->eeprom_type = mi.type; + return 0; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/plugins/dpdk/device/device.c b/src/plugins/dpdk/device/device.c index ffa1d7a7bf..6b3edb0155 100644 --- a/src/plugins/dpdk/device/device.c +++ b/src/plugins/dpdk/device/device.c @@ -778,6 +778,7 @@ VNET_DEVICE_CLASS (dpdk_device_class) = { .flow_ops_function = dpdk_flow_ops_fn, .set_rss_queues_function = dpdk_interface_set_rss_queues, .rx_mode_change_function = dpdk_interface_rx_mode_change, + .eeprom_read_function = dpdk_read_eeprom, }; #define UP_DOWN_FLAG_EVENT 1 diff --git a/src/plugins/dpdk/device/dpdk.h b/src/plugins/dpdk/device/dpdk.h index 70d9cc715d..347418ca00 100644 --- a/src/plugins/dpdk/device/dpdk.h +++ b/src/plugins/dpdk/device/dpdk.h @@ -461,6 +461,9 @@ vnet_flow_dev_ops_function_t dpdk_flow_ops_fn; clib_error_t *unformat_rss_fn (unformat_input_t * input, uword * rss_fn); +clib_error_t *dpdk_read_eeprom (vnet_main_t *vnm, vnet_hw_interface_t *hi, + vnet_interface_eeprom_t **eeprom); + struct rte_pci_device *dpdk_get_pci_device (const struct rte_eth_dev_info *info); struct rte_vmbus_device * diff --git a/src/vnet/interface.h b/src/vnet/interface.h index c6d0185905..c9778f9bac 100644 --- a/src/vnet/interface.h +++ b/src/vnet/interface.h @@ -98,6 +98,20 @@ typedef clib_error_t *(vnet_interface_rss_queues_set_t) (struct vnet_main_t * vnm, struct vnet_hw_interface_t * hi, clib_bitmap_t * bitmap); +/* EEPROM structure for physical network devices */ +typedef struct +{ + u32 eeprom_type; /* from linux/ethtool.h */ + u32 eeprom_len; + u8 eeprom_raw[1024]; +} vnet_interface_eeprom_t; + +/* Interface EEPROM read function */ +typedef clib_error_t *( + vnet_interface_eeprom_read_t) (struct vnet_main_t *vnm, + struct vnet_hw_interface_t *hi, + vnet_interface_eeprom_t **eeprom); + typedef enum { VNET_FLOW_DEV_OP_ADD_FLOW, @@ -306,6 +320,9 @@ typedef struct _vnet_device_class /* Interface to set rss queues of the interface */ vnet_interface_rss_queues_set_t *set_rss_queues_function; + /* Function to read EEPROM data from physical network device */ + vnet_interface_eeprom_read_t *eeprom_read_function; + } vnet_device_class_t; u32 vnet_register_device_class (vlib_main_t *, vnet_device_class_t *); diff --git a/src/vnet/interface_cli.c b/src/vnet/interface_cli.c index 4d3c98f6aa..d0b6630485 100644 --- a/src/vnet/interface_cli.c +++ b/src/vnet/interface_cli.c @@ -2658,6 +2658,160 @@ VLIB_CLI_COMMAND (cmd_show_tx_hash, static) = { .function = show_tx_hash, }; +static void +show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, + u8 show_module, u8 show_diag, + u8 show_eeprom, u8 is_terse) +{ + clib_error_t *error = 0; + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_eeprom_t *eeprom = 0; + vnet_interface_main_t *im = &vnm->interface_main; + vnet_device_class_t *dc = + vec_elt_at_index (im->device_classes, hi->dev_class_index); + + if (!dc->eeprom_read_function) + { + error = clib_error_return ( + 0, "interface %v does not support EEPROM reading", hi->name); + goto done; + } + + error = dc->eeprom_read_function (vnm, hi, &eeprom); + if (error) + goto done; + + if (!eeprom) + { + error = clib_error_return ( + 0, "no EEPROM data available for interface %v", hi->name); + goto done; + } + + vlib_cli_output (vm, "Interface: %v", hi->name); + + /* Default to module if none are set */ + if (!show_module && !show_diag && !show_eeprom) + show_module = 1; + + if (show_eeprom) + { + vlib_cli_output (vm, " EEPROM Type: 0x%x", eeprom->eeprom_type); + vlib_cli_output (vm, " EEPROM Length: %u bytes", eeprom->eeprom_len); + vlib_cli_output (vm, " EEPROM Data:"); + + /* Print hexdump */ + for (u32 offset = 0; offset < eeprom->eeprom_len; offset += 16) + { + u8 *line = format (0, " %04x: ", offset); + + /* Print hex bytes */ + for (u32 j = 0; j < 16 && (offset + j) < eeprom->eeprom_len; j++) + { + line = format (line, "%02x ", eeprom->eeprom_raw[offset + j]); + } + + /* Pad to align ASCII section */ + for (u32 j = (offset + 16 > eeprom->eeprom_len) ? + eeprom->eeprom_len - offset : + 16; + j < 16; j++) + { + line = format (line, " "); + } + + line = format (line, " |"); + + /* Print ASCII representation */ + for (u32 j = 0; j < 16 && (offset + j) < eeprom->eeprom_len; j++) + { + u8 c = eeprom->eeprom_raw[offset + j]; + line = format (line, "%c", (c >= 32 && c <= 126) ? c : '.'); + } + + line = format (line, "|"); + vlib_cli_output (vm, "%v", line); + vec_free (line); + } + + vlib_cli_output (vm, ""); + } + + if (show_module) + { + vlib_cli_output (vm, " module: not implemented yet"); + } + + if (show_diag) + { + vlib_cli_output (vm, " diag: not implemented yet"); + } + +done: + if (eeprom) + clib_mem_free (eeprom); +} + +static clib_error_t * +show_interface_transceiver (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_main_t *im = &vnm->interface_main; + u32 hw_if_index = (u32) ~0; + vnet_hw_interface_t *hi; + u8 is_terse = 1; + u8 show_diag = 0; + u8 show_module = 0; + u8 show_eeprom = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "diag")) + show_diag = 1; + else if (unformat (input, "module")) + show_module = 1; + else if (unformat (input, "eeprom")) + show_eeprom = 1; + else if (unformat (input, "verbose")) + is_terse = 0; + else if (unformat (input, "%U", unformat_vnet_hw_interface, vnm, + &hw_if_index)) + ; + else + { + return clib_error_return (0, "parse error: '%U'", + format_unformat_error, input); + } + } + + pool_foreach (hi, im->hw_interfaces) + { + if (hw_if_index == ~0 || hw_if_index == hi->hw_if_index) + { + show_interface_transceiver_output (vm, hi, show_module, show_diag, + show_eeprom, is_terse); + } + } + return 0; +} + +/*? + * This command displays the transceiver EEPROM data for a given interface. + * The EEPROM data is read from the physical transceiver module (SFP, QSFP, + * etc.) and displayed as a hexadecimal dump. + * + * @cliexpar + * Example of how to display transceiver EEPROM data: + * @cliexcmd{show interface transceiver GigabitEthernet0/8/0 module diag} + ?*/ +VLIB_CLI_COMMAND (cmd_show_interface_transceiver, static) = { + .path = "show interface transceiver", + .short_help = "show interface transceiver [] [module] [diag] " + "[eeprom] [verbose]", + .function = show_interface_transceiver, +}; + /* * fd.io coding-style-patch-verification: ON * From d282e2c1991bf5ae1ce82b22def4a8685c8611bd Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Sat, 16 Aug 2025 08:23:49 -0400 Subject: [PATCH 223/313] vcl: fix read ready for cl udp On fork, cl listeners are not ready, i.e., listen and fifos are pending, so read ready crashes. Type: fix Change-Id: I965bbd7f72706831767615564a88f5dbad38b11e Signed-off-by: Florin Coras --- src/vcl/vcl_private.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/vcl/vcl_private.c b/src/vcl/vcl_private.c index dcd827f2a6..714ecdadb6 100644 --- a/src/vcl/vcl_private.c +++ b/src/vcl/vcl_private.c @@ -403,6 +403,10 @@ vcl_session_read_ready (vcl_session_t * s) session_dgram_pre_hdr_t ph; u32 max_deq; + /* CL listener that's not yet ready */ + if (vcl_session_is_cl (s) && !s->rx_fifo) + return 0; + max_deq = svm_fifo_max_dequeue_cons (s->rx_fifo); if (max_deq <= SESSION_CONN_HDR_LEN) return 0; @@ -442,6 +446,10 @@ vcl_session_read_ready2 (vcl_session_t *s) if (s->is_dgram) { + /* CL listener that's not yet ready */ + if (vcl_session_is_cl (s) && !s->rx_fifo) + return 0; + if (svm_fifo_max_dequeue_cons (s->rx_fifo) <= SESSION_CONN_HDR_LEN) return 0; From 0ba16819fba6af5c681bf3343b989e8fa675926c Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 18 Aug 2025 04:41:47 -0400 Subject: [PATCH 224/313] hs-test: turn off quic_retry in nginx config otherwise NginxHttp3Test fail from time to time because quic connection between curl and nginx is closed during handshake Type: test Change-Id: Ia74fad1a357a10532857f3568e8104ed352c58c7 Signed-off-by: Matus Fabian --- extras/hs-test/resources/nginx/nginx_http3.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extras/hs-test/resources/nginx/nginx_http3.conf b/extras/hs-test/resources/nginx/nginx_http3.conf index 814568842b..c09867576d 100644 --- a/extras/hs-test/resources/nginx/nginx_http3.conf +++ b/extras/hs-test/resources/nginx/nginx_http3.conf @@ -12,7 +12,7 @@ events { http { quic_gso on; - quic_retry on; + quic_retry off; access_log /tmp/nginx/{{.LogPrefix}}-access.log; keepalive_timeout 300s; From 0bfd9cd5fe9f40c7b714ac8a12786aeee6ad4a5b Mon Sep 17 00:00:00 2001 From: kejan Date: Mon, 18 Aug 2025 10:23:41 +0200 Subject: [PATCH 225/313] rpm-packaging: add vcl client and server to vpp binaries (rpm) This fixes: Installed (but unpackaged) file(s) found: /usr/bin/vcl_test_client /usr/bin/vcl_test_server Type: fix Change-Id: Ibe515751f718c56b1fb932ac7132edc34cfe791c Signed-off-by: kejan --- extras/rpm/vpp.spec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extras/rpm/vpp.spec b/extras/rpm/vpp.spec index 26c6ed29b2..a18f57107a 100644 --- a/extras/rpm/vpp.spec +++ b/extras/rpm/vpp.spec @@ -376,6 +376,8 @@ fi /usr/bin/vat2 /usr/bin/vpp* /usr/bin/svm* +/usr/bin/vcl_test_client +/usr/bin/vcl_test_server %config(noreplace) /etc/sysctl.d/80-vpp.conf %config(noreplace) /etc/vpp/startup.conf /usr/share/vpp/api/* From 030f7b7339460dcbc67b74a5f9e40cffdf9237f1 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 18 Aug 2025 05:07:57 -0400 Subject: [PATCH 226/313] hs-test: NginxHttp3Test add worker crash check Type: test Change-Id: Ic214c4af369c93e88b03405f1961dd24f15a873b Signed-off-by: Matus Fabian --- extras/hs-test/nginx_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extras/hs-test/nginx_test.go b/extras/hs-test/nginx_test.go index 6a219e9568..ef810674a1 100644 --- a/extras/hs-test/nginx_test.go +++ b/extras/hs-test/nginx_test.go @@ -6,6 +6,7 @@ import ( "strings" . "fd.io/hs-test/infra" + "github.com/edwarnicke/exechelper" . "github.com/onsi/ginkgo/v2" ) @@ -38,6 +39,12 @@ func NginxHttp3Test(s *NoTopoSuite) { s.AssertNotContains(stats, "refused") s.AssertContains(stats, "100") s.AssertContains(body, "", " not found in the result!") + + // check worker crash + logPath := s.Containers.NginxHttp3.GetHostWorkDir() + "/" + s.Containers.NginxHttp3.Name + "-error.log" + logContents, err := exechelper.Output("cat " + logPath) + s.AssertNil(err) + s.AssertNotContains(string(logContents), "signal 17 (SIGCHLD) received from") } func NginxAsServerTest(s *NoTopoSuite) { From 61deae6250d924e0b13ab45f351811e4cbc630ed Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 18 Aug 2025 04:40:51 -0400 Subject: [PATCH 227/313] session udp: fix cl hash computation Type: fix Change-Id: I5da034ad860a4f7cf7578f7026566a27e1a43960 Signed-off-by: Florin Coras --- src/vnet/session/application.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index c27e58efcb..ee66d755ad 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -1068,7 +1068,7 @@ app_listener_select_wrk_cl_session (session_t *ls, session_dgram_hdr_t *hdr) if (al->workers[0] != 1) { u32 hash = app_listener_cl_flow_hash (hdr); - hash %= vec_len (al->workers) * sizeof (uword); + hash %= vec_len (al->workers); wrk_map_index = clib_bitmap_next_set (al->workers, hash); if (wrk_map_index == ~0) wrk_map_index = clib_bitmap_first_set (al->workers); From 3102059a9f744855299b57410b6ac8d43ab4a018 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 18 Aug 2025 05:17:19 -0400 Subject: [PATCH 228/313] vcl: ldp infra for get/set SO_COOKIE Stub infrastructure for storing and retrieving SO_COOKIE. Not storing the actual cookie to avoid increasing vcl session size. Type: improvement Change-Id: Ibdcb31eb33ceab9a0250eae94694ba69e4ecf502 Signed-off-by: Florin Coras --- src/vcl/ldp.c | 7 +++++++ src/vcl/vppcom.c | 23 +++++++++++++++++++++++ src/vcl/vppcom.h | 2 ++ 3 files changed, 32 insertions(+) diff --git a/src/vcl/ldp.c b/src/vcl/ldp.c index ffd7b045de..1de549b1c4 100644 --- a/src/vcl/ldp.c +++ b/src/vcl/ldp.c @@ -2117,6 +2117,9 @@ getsockopt (int fd, int level, int optname, case SO_BINDTODEVICE: rv = 0; break; + case SO_COOKIE: + rv = vls_attr (vlsh, VPPCOM_ATTR_GET_COOKIE, optval, optlen); + break; default: LDBG (0, "ERROR: fd %d: getsockopt SOL_SOCKET: vlsh %u " "optname %d unsupported!", fd, vlsh, optname); @@ -2222,6 +2225,10 @@ setsockopt (int fd, int level, int optname, case SO_LINGER: rv = 0; break; + case SO_COOKIE: + rv = vls_attr (vlsh, VPPCOM_ATTR_SET_COOKIE, (void *) optval, + &optlen); + break; default: LDBG (0, "ERROR: fd %d: setsockopt SOL_SOCKET: vlsh %u " "optname %d unsupported!", fd, vlsh, optname); diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 08879704a0..7b1a5f1cf4 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -4189,6 +4189,29 @@ vppcom_session_attr (uint32_t session_handle, uint32_t op, rv = VPPCOM_EINVAL; break; + case VPPCOM_ATTR_GET_COOKIE: + if (buffer && buflen && (*buflen == sizeof (u64))) + { + /* VPP-TBD */ + *(u64 *) buffer = 0; + + VDBG (2, "VPPCOM_ATTR_GET_COOKIE: %d, buflen %d, #VPP-TBD#", + *(int *) buffer, *buflen); + } + else + rv = VPPCOM_EINVAL; + break; + + case VPPCOM_ATTR_SET_COOKIE: + if (buffer && buflen && (*buflen == sizeof (u64))) + { + VDBG (2, "VPPCOM_ATTR_SET_COOKIE: %d, buflen %d, #VPP-TBD#", + *(u64 *) buffer, *buflen); + } + else + rv = VPPCOM_EINVAL; + break; + case VPPCOM_ATTR_GET_V6ONLY: if (buffer && buflen && (*buflen >= sizeof (int))) { diff --git a/src/vcl/vppcom.h b/src/vcl/vppcom.h index ad1c1f4158..b88a60384c 100644 --- a/src/vcl/vppcom.h +++ b/src/vcl/vppcom.h @@ -187,6 +187,8 @@ typedef enum VPPCOM_ATTR_GET_ORIGINAL_DST, VPPCOM_ATTR_GET_NWRITEQ, VPPCOM_ATTR_GET_EXT_ENDPT, + VPPCOM_ATTR_GET_COOKIE, + VPPCOM_ATTR_SET_COOKIE, } vppcom_attr_op_t; typedef struct _vcl_poll From 16bd854df5e5bf9275dd658429356309c4ed135f Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 18 Aug 2025 07:49:59 -0400 Subject: [PATCH 229/313] http: h2 tunnel dispatch add as dequeue notify Type: fix Change-Id: I7845a4dee6b6931defc83ab6052a64f913321349 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 5ba094bcf3..7723e54c59 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -746,6 +746,7 @@ http2_sched_dispatch_tunnel (http2_req_t *req, http_conn_t *hc, else transport_connection_reschedule (&req->base.connection); + http_io_as_dequeue_notify (&req->base, n_written); http_io_ts_after_write (hc, 0); } @@ -840,6 +841,7 @@ http2_sched_dispatch_udp_tunnel (http2_req_t *req, http_conn_t *hc, else transport_connection_reschedule (&req->base.connection); + http_io_as_dequeue_notify (&req->base, dgram_size); http_io_ts_after_write (hc, 0); } From 1bb5ddb11aed8b08fdb81c6bd299510cea0c7bfb Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Mon, 18 Aug 2025 13:07:11 +0000 Subject: [PATCH 230/313] hsa: fix typo in vcl_test_client arg check Type: fix Change-Id: Ifd0ef3dfcfc2cfd1bc13f2a1727c5f4a36d4a746 Signed-off-by: Semir Sionek --- src/plugins/hs_apps/vcl/vcl_test_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/hs_apps/vcl/vcl_test_client.c b/src/plugins/hs_apps/vcl/vcl_test_client.c index 8bac1f00b9..a5ea90dc8a 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_client.c +++ b/src/plugins/hs_apps/vcl/vcl_test_client.c @@ -1227,7 +1227,7 @@ vtc_process_opts (vcl_test_client_main_t * vcm, int argc, char **argv) print_usage_and_exit (); } - if (argc > (optind + 2)) + if (argc < (optind + 2)) { vtwrn ("Invalid number of arguments!"); print_usage_and_exit (); From 8d84125e93309812968762bd55c72df33ed9ed97 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Fri, 15 Aug 2025 14:52:53 +0200 Subject: [PATCH 231/313] hs-test: iperf improvements - added AssertIperfMinTransfer to KindIperfVclTest - fixed 'make cleanup-perf' Type: test Change-Id: I50f7d837b1d4d842cb7464a5ee08cbecdea7adcd Signed-off-by: Adrian Villin --- extras/hs-test/Makefile | 4 ++-- extras/hs-test/infra/common/utils_common.go | 21 +++++++++++++++++++++ extras/hs-test/kind_test.go | 9 ++++++--- extras/hs-test/ldp_test.go | 20 +------------------- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index bc52c22d0a..902f3d922f 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -376,10 +376,10 @@ cleanup-perf: fi @echo "****************************" @echo "Removing kubernetes pods:" - @kubectl delete pods --all --grace-period=0 -n namespace$$(cat .last_hst_ppid) + @kubectl delete pods --all --grace-period=0 -n hs-test$$(cat .last_hst_ppid) @echo "****************************" @echo "Removing kubernetes namespace:" - @kubectl delete namespace namespace$$(cat .last_hst_ppid) + @kubectl delete namespace hs-test$$(cat .last_hst_ppid) @echo "****************************" @echo "Done." @echo "****************************" diff --git a/extras/hs-test/infra/common/utils_common.go b/extras/hs-test/infra/common/utils_common.go index 5b3d718174..15db60b387 100644 --- a/extras/hs-test/infra/common/utils_common.go +++ b/extras/hs-test/infra/common/utils_common.go @@ -66,6 +66,27 @@ type IPerfResult struct { func (s *HstCommon) ParseJsonIperfOutput(jsonResult []byte) IPerfResult { var result IPerfResult + + // VCL/LDP debugging can pollute output so find the first occurrence of a curly brace to locate the start of JSON data + jsonStart := -1 + jsonEnd := len(jsonResult) + braceCount := 0 + for i := 0; i < len(jsonResult); i++ { + if jsonResult[i] == '{' { + if jsonStart == -1 { + jsonStart = i + } + braceCount++ + } else if jsonResult[i] == '}' { + braceCount-- + if braceCount == 0 { + jsonEnd = i + 1 + break + } + } + } + jsonResult = jsonResult[jsonStart:jsonEnd] + // remove iperf warning line if present if strings.Contains(string(jsonResult), "warning") { index := strings.Index(string(jsonResult), "\n") diff --git a/extras/hs-test/kind_test.go b/extras/hs-test/kind_test.go index 8039e6b062..aaac1b5248 100644 --- a/extras/hs-test/kind_test.go +++ b/extras/hs-test/kind_test.go @@ -30,12 +30,15 @@ func KindIperfVclTest(s *KindSuite) { s.FixVersionNumber(s.Pods.ClientGeneric, s.Pods.ServerGeneric) o, err := s.Pods.ServerGeneric.Exec(ctx, []string{"/bin/bash", "-c", - vcl + " " + ldp + " iperf3 -s -D -4"}) + vcl + " " + ldp + " iperf3 -s -D -4 -B " + s.Pods.ServerGeneric.IpAddress}) s.AssertNil(err, o) o, err = s.Pods.ClientGeneric.Exec(ctx, []string{"/bin/bash", "-c", - vcl + " " + ldp + " iperf3 -l 1460 -b 10g -c " + s.Pods.ServerGeneric.IpAddress}) - s.Log(o) + vcl + " " + ldp + " iperf3 -J -l 1460 -b 10g -c " + s.Pods.ServerGeneric.IpAddress}) + s.AssertNil(err) + result := s.ParseJsonIperfOutput([]byte(o)) + s.LogJsonIperfOutput(result) + s.AssertIperfMinTransfer(result, 2000) } func NginxRpsTest(s *KindSuite) { diff --git a/extras/hs-test/ldp_test.go b/extras/hs-test/ldp_test.go index 1c057b081b..b0c6276236 100644 --- a/extras/hs-test/ldp_test.go +++ b/extras/hs-test/ldp_test.go @@ -117,25 +117,7 @@ func ldPreloadIperf(s *LdpSuite, extraClientArgs string) IPerfResult { s.AssertChannelClosed(time.Minute*4, clnCh) output := <-clnRes - // VCL/LDP debugging can pollute output so find the first occurrence of a curly brace to locate the start of JSON data - jsonStart := -1 - jsonEnd := len(output) - braceCount := 0 - for i := 0; i < len(output); i++ { - if output[i] == '{' { - if jsonStart == -1 { - jsonStart = i - } - braceCount++ - } else if output[i] == '}' { - braceCount-- - if braceCount == 0 { - jsonEnd = i + 1 - break - } - } - } - result := s.ParseJsonIperfOutput(output[jsonStart:jsonEnd]) + result := s.ParseJsonIperfOutput(output) s.LogJsonIperfOutput(result) return result From d2f48f46a8f5cb3cc733632b4ad3990dd7638c50 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 20 Aug 2025 08:49:45 -0400 Subject: [PATCH 232/313] http: h2 client multiplexing fix do not try to cleanup ho in http_conn_established when called from http2_conn_connect_stream_callback Type: fix Change-Id: I91422297b69cfef82b622113e194596205b44075 Signed-off-by: Matus Fabian --- src/plugins/http/http1.c | 2 +- src/plugins/http/http2/http2.c | 4 ++-- src/plugins/http/http_private.h | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 9d458aad5a..7d781e4d0d 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -1955,7 +1955,7 @@ http1_transport_connected_callback (http_conn_t *hc) req = http1_conn_alloc_req (hc); http_req_state_change (req, HTTP_REQ_STATE_WAIT_APP_METHOD); - return http_conn_established (hc, req, hc->hc_pa_app_api_ctx); + return http_conn_established (hc, req, hc->hc_pa_app_api_ctx, 0); } static void diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 7723e54c59..5ecf8cc6fc 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2390,7 +2390,7 @@ http2_handle_settings_frame (http_conn_t *hc, http2_frame_header_t *fh) &h2c->decoder_dynamic_table, http2_default_conn_settings.header_table_size); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); - if (http_conn_established (hc, &req->base, hc->hc_pa_app_api_ctx)) + if (http_conn_established (hc, &req->base, hc->hc_pa_app_api_ctx, 0)) return HTTP2_ERROR_INTERNAL_ERROR; } @@ -3128,7 +3128,7 @@ http2_conn_connect_stream_callback (http_conn_t *hc, u32 parent_app_api_ctx) hc->hc_pa_app_api_ctx); req = http2_conn_alloc_req (hc, 0); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); - return http_conn_established (hc, &req->base, parent_app_api_ctx); + return http_conn_established (hc, &req->base, parent_app_api_ctx, 1); } static void diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index 6036b5ad3d..5b8f2c4894 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -871,7 +871,7 @@ http_conn_accept_request (http_conn_t *hc, http_req_t *req) always_inline int http_conn_established (http_conn_t *hc, http_req_t *req, - u32 parent_app_api_ctx) + u32 parent_app_api_ctx, u8 is_stream) { session_t *as; app_worker_t *app_wrk; @@ -879,9 +879,12 @@ http_conn_established (http_conn_t *hc, http_req_t *req, 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); + if (!is_stream) + { + 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); From 3d23fc20402110d4d29f20aa45ca8aeff9399ec2 Mon Sep 17 00:00:00 2001 From: Aritra Basu Date: Thu, 21 Aug 2025 15:32:49 -0700 Subject: [PATCH 233/313] vnet: fix negative table-id Type: fix Change-Id: Ic29bdd854b2b218d2b56446cd58ea185d4e485ae Signed-off-by: Aritra Basu --- src/vnet/interface_cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vnet/interface_cli.c b/src/vnet/interface_cli.c index d0b6630485..4b1828dc85 100644 --- a/src/vnet/interface_cli.c +++ b/src/vnet/interface_cli.c @@ -468,7 +468,7 @@ show_sw_interfaces (vlib_main_t * vm, ip4_address_t *r4 = ip_interface_address_get_address (lm4, ia); if (fib4->hash.table_id) vlib_cli_output ( - vm, " L3 %U/%d ip4 table-id %d fib-idx %d", format_ip4_address, + vm, " L3 %U/%d ip4 table-id %u fib-idx %d", format_ip4_address, r4, ia->address_length, fib4->hash.table_id, ip4_fib_index_from_table_id (fib4->hash.table_id)); else From d877375b3fd1df2ff6cbb15b76245d3b188bcf88 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 20 Aug 2025 05:05:41 -0400 Subject: [PATCH 234/313] tls: avoid reallocating default ssl ctx for clients Type: improvement Change-Id: I8933478342789034509f6b8dd141baf706970082 Signed-off-by: Florin Coras --- src/plugins/tlsopenssl/tls_openssl.c | 109 +++++++++++++++++---------- src/plugins/tlsopenssl/tls_openssl.h | 3 +- 2 files changed, 71 insertions(+), 41 deletions(-) diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 7db06fb8de..a791158285 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -85,7 +85,6 @@ openssl_ctx_free (tls_ctx_t * ctx) SSL_free (oc->ssl); vec_free (ctx->srv_hostname); - SSL_CTX_free (oc->client_ssl_ctx); } pool_put_index (openssl_main.ctx_pool[ctx->c_thread_index], @@ -807,70 +806,110 @@ openssl_client_init_verify (SSL *ssl, const char *srv_hostname, } static int -openssl_ctx_init_client (tls_ctx_t * ctx) +openssl_init_client_ctx (transport_proto_t proto) { long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; - openssl_ctx_t *oc = (openssl_ctx_t *) ctx; openssl_main_t *om = &openssl_main; const SSL_METHOD *method; - int rv, err; + SSL_CTX *client_ssl_ctx; + int rv; - method = ctx->tls_type == TRANSPORT_PROTO_TLS ? SSLv23_client_method () : - DTLS_client_method (); - if (method == NULL) - { - TLS_DBG (1, "(D)TLS_method returned null"); - return -1; - } + method = proto == TRANSPORT_PROTO_TLS ? TLS_client_method () : + DTLS_client_method (); - oc->client_ssl_ctx = SSL_CTX_new (method); - if (oc->client_ssl_ctx == NULL) + client_ssl_ctx = SSL_CTX_new (method); + if (client_ssl_ctx == NULL) { TLS_DBG (1, "SSL_CTX_new returned null"); return -1; } - SSL_CTX_set_ecdh_auto (oc->client_ssl_ctx, 1); - SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_ecdh_auto (client_ssl_ctx, 1); + SSL_CTX_set_mode (client_ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); #ifdef HAVE_OPENSSL_ASYNC if (om->async) { - SSL_CTX_set_mode (oc->client_ssl_ctx, SSL_MODE_ASYNC); - SSL_CTX_set_async_callback (oc->client_ssl_ctx, - tls_async_openssl_callback); + SSL_CTX_set_mode (client_ssl_ctx, SSL_MODE_ASYNC); + SSL_CTX_set_async_callback (client_ssl_ctx, tls_async_openssl_callback); } #endif - rv = - SSL_CTX_set_cipher_list (oc->client_ssl_ctx, (const char *) om->ciphers); + rv = SSL_CTX_set_cipher_list (client_ssl_ctx, (const char *) om->ciphers); if (rv != 1) { TLS_DBG (1, "Couldn't set cipher"); return -1; } - SSL_CTX_set_options (oc->client_ssl_ctx, flags); - SSL_CTX_set1_cert_store (oc->client_ssl_ctx, om->cert_store); + SSL_CTX_set_options (client_ssl_ctx, flags); + SSL_CTX_set1_cert_store (client_ssl_ctx, om->cert_store); - if (ctx->alpn_list) + /* Set TLS Record size */ + if (om->record_size) { - rv = SSL_CTX_set_alpn_protos (oc->client_ssl_ctx, - (const unsigned char *) ctx->alpn_list, - (unsigned int) vec_len (ctx->alpn_list)); - if (rv != 0) + rv = SSL_CTX_set_max_send_fragment (client_ssl_ctx, om->record_size); + if (rv != 1) { - TLS_DBG (1, "Couldn't set alpn protos"); + TLS_DBG (1, "Couldn't set TLS record-size"); return -1; } + TLS_DBG (1, "Using TLS record-size of %d", om->record_size); } - oc->ssl = SSL_new (oc->client_ssl_ctx); + if (proto == TRANSPORT_PROTO_TLS) + om->default_client_ssl_ctx = client_ssl_ctx; + else + om->default_dtls_client_ssl_ctx = client_ssl_ctx; + + return 0; +} + +static inline SSL_CTX * +openssl_get_client_ssl_ctx (transport_proto_t proto) +{ + openssl_main_t *om = &openssl_main; + + /* One time init of the default client ssl ctxs */ + if (PREDICT_FALSE (!om->default_client_ssl_ctx)) + { + openssl_init_client_ctx (TRANSPORT_PROTO_TLS); + openssl_init_client_ctx (TRANSPORT_PROTO_DTLS); + } + + return proto == TRANSPORT_PROTO_TLS ? om->default_client_ssl_ctx : + om->default_dtls_client_ssl_ctx; +} + +static int +openssl_ctx_init_client (tls_ctx_t *ctx) +{ + openssl_ctx_t *oc = (openssl_ctx_t *) ctx; + SSL_CTX *client_ssl_ctx; + int rv, err; + + client_ssl_ctx = openssl_get_client_ssl_ctx (ctx->tls_type); + if (PREDICT_FALSE (!client_ssl_ctx)) + return -1; + + oc->ssl = SSL_new (client_ssl_ctx); if (oc->ssl == NULL) { TLS_DBG (1, "Couldn't initialize ssl struct"); return -1; } + if (ctx->alpn_list) + { + rv = + SSL_set_alpn_protos (oc->ssl, (const unsigned char *) ctx->alpn_list, + (unsigned int) vec_len (ctx->alpn_list)); + if (rv != 0) + { + TLS_DBG (1, "Couldn't set alpn protos"); + return -1; + } + } + if (ctx->tls_type == TRANSPORT_PROTO_TLS) { oc->rbio = BIO_new_tls (ctx->tls_session_handle); @@ -897,17 +936,7 @@ openssl_ctx_init_client (tls_ctx_t * ctx) { TLS_DBG (1, "Couldn't set client certificate-key pair"); } - /* Set TLS Record size */ - if (om->record_size) - { - rv = SSL_CTX_set_max_send_fragment (oc->client_ssl_ctx, om->record_size); - if (rv != 1) - { - TLS_DBG (1, "Couldn't set TLS record-size"); - return -1; - } - TLS_DBG (1, "Using TLS record-size of %d", om->record_size); - } + /* * 2. Do the first steps in the handshake. */ diff --git a/src/plugins/tlsopenssl/tls_openssl.h b/src/plugins/tlsopenssl/tls_openssl.h index bf3440120d..ae2197cd7b 100644 --- a/src/plugins/tlsopenssl/tls_openssl.h +++ b/src/plugins/tlsopenssl/tls_openssl.h @@ -54,7 +54,6 @@ typedef struct tls_ctx_openssl_ { tls_ctx_t ctx; /**< First */ u32 openssl_ctx_index; - SSL_CTX *client_ssl_ctx; SSL *ssl; tls_async_ctx_t async_ctx; BIO *rbio; @@ -73,6 +72,8 @@ typedef struct openssl_main_ { openssl_ctx_t ***ctx_pool; openssl_listen_ctx_t *lctx_pool; + SSL_CTX *default_client_ssl_ctx; + SSL_CTX *default_dtls_client_ssl_ctx; u8 **rx_bufs; u8 **tx_bufs; From a73c5f01fbf4947eda7110fe6dd21e90d3ba6545 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Sun, 24 Aug 2025 10:36:15 -0400 Subject: [PATCH 235/313] hs-test: limit wget retries in setup scripts by default wget do 20 retries, we want ti fails fast if internet connectivity is broken Type: test Change-Id: Ia7222c8ac6f7b5571dc1c29949aa2b8de2786afe Signed-off-by: Matus Fabian --- extras/hs-test/Makefile | 2 +- extras/hs-test/docker/Dockerfile.ginkgo | 4 ++-- extras/hs-test/script/build_curl.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extras/hs-test/Makefile b/extras/hs-test/Makefile index 902f3d922f..8788d82383 100644 --- a/extras/hs-test/Makefile +++ b/extras/hs-test/Makefile @@ -302,7 +302,7 @@ install-deps: go version; \ else \ echo "Installing Go 1.23"; \ - wget https://go.dev/dl/go1.23.10.linux-$(ARCH).tar.gz -O /tmp/go1.23.10.linux-$(ARCH).tar.gz && sudo tar -C /usr/local -xzf /tmp/go1.23.10.linux-$(ARCH).tar.gz; \ + wget -t 2 https://go.dev/dl/go1.23.10.linux-$(ARCH).tar.gz -O /tmp/go1.23.10.linux-$(ARCH).tar.gz && sudo tar -C /usr/local -xzf /tmp/go1.23.10.linux-$(ARCH).tar.gz; \ sudo ln -s /usr/local/go/bin/go /usr/bin/go ; \ fi @sudo -E apt-get update diff --git a/extras/hs-test/docker/Dockerfile.ginkgo b/extras/hs-test/docker/Dockerfile.ginkgo index 2ec1e107ab..4017b33c49 100644 --- a/extras/hs-test/docker/Dockerfile.ginkgo +++ b/extras/hs-test/docker/Dockerfile.ginkgo @@ -8,11 +8,11 @@ ARG TARGETARCH ARG CODENAME ARG GO_VERSION=1.23.10 RUN echo "I'm building for ${TARGETARCH}" \ -&& wget "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" -O /tmp/go.tar.gz \ +&& wget -t 2 "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" -O /tmp/go.tar.gz \ && tar -xzf /tmp/go.tar.gz -C /usr/local \ && rm /tmp/go.tar.gz -RUN wget "https://download.docker.com/linux/ubuntu/dists/${CODENAME}/pool/stable/${TARGETARCH}/docker-ce-cli_28.3.2-1~ubuntu.${UBUNTU_VERSION}~${CODENAME}_${TARGETARCH}.deb" \ +RUN wget -t 2 "https://download.docker.com/linux/ubuntu/dists/${CODENAME}/pool/stable/${TARGETARCH}/docker-ce-cli_28.3.2-1~ubuntu.${UBUNTU_VERSION}~${CODENAME}_${TARGETARCH}.deb" \ -O /tmp/docker-ce-cli.deb RUN dpkg -i /tmp/docker-ce-cli.deb diff --git a/extras/hs-test/script/build_curl.sh b/extras/hs-test/script/build_curl.sh index d3fc84a357..31ff030f24 100755 --- a/extras/hs-test/script/build_curl.sh +++ b/extras/hs-test/script/build_curl.sh @@ -1,6 +1,6 @@ #!/bin/bash set -x OS_ARCH="$(uname -m)" -wget https://github.com/stunnel/static-curl/releases/download/8.15.0/curl-linux-"${OS_ARCH}"-glibc-8.15.0.tar.xz +wget -t 2 https://github.com/stunnel/static-curl/releases/download/8.15.0/curl-linux-"${OS_ARCH}"-glibc-8.15.0.tar.xz tar -xvf ./curl-linux-"${OS_ARCH}"-glibc-8.15.0.tar.xz cp curl /usr/bin/curl \ No newline at end of file From 6abc19720fff1c45b5a248a32c0b3de3fe8586ab Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Fri, 22 Aug 2025 09:13:59 -0400 Subject: [PATCH 236/313] hs-test: fix core file detection Type: test Change-Id: I7d6c494d66a053124d176a261b65dabe1a125215 Signed-off-by: Matus Fabian --- extras/hs-test/docker/Dockerfile.base | 1 + extras/hs-test/infra/utils.go | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/extras/hs-test/docker/Dockerfile.base b/extras/hs-test/docker/Dockerfile.base index 8186d7b307..e6e3b44276 100644 --- a/extras/hs-test/docker/Dockerfile.base +++ b/extras/hs-test/docker/Dockerfile.base @@ -9,6 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Basic utilities ca-certificates \ wget \ + file \ gnupg \ gnupg2 \ git \ diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index 14d084014b..1d84a3f61e 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -313,8 +313,12 @@ func (s *HstSuite) StartClientApp(c *Container, cmd string, } func (s *HstSuite) GetCoreProcessName(file string) (string, bool) { - cmd := fmt.Sprintf("sudo file -b %s", file) - output, _ := exechelper.Output(cmd) + cmd := fmt.Sprintf("file -b %s", file) + output, err := exechelper.Output(cmd) + if err != nil { + s.Log(fmt.Sprint(err)) + return "", false + } outputStr := string(output) // ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from 'vpp -c /tmp/server/etc/vpp/startup.conf', real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, execfn: '/usr/bin/vpp', platform: 'x86_64' if !strings.Contains(outputStr, "core file") { From b30374d4f25cc03fd5be9eb5f917440783b87c4e Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Sun, 24 Aug 2025 12:01:19 -0400 Subject: [PATCH 237/313] http: remove assert in http2_req_state_tunnel_tx proxy app can send new payload before we were able to send previous Type: fix Change-Id: I7d0c2605da38e7495e3e26705f58a5370d1e296f Signed-off-by: Matus Fabian --- src/plugins/http/http.c | 8 ++++---- src/plugins/http/http2/http2.c | 11 +++++++++-- src/plugins/http/http_private.h | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c index 7341bdf7e7..7bfe5ea32b 100644 --- a/src/plugins/http/http.c +++ b/src/plugins/http/http.c @@ -570,8 +570,8 @@ http_ts_connected_callback (u32 http_app_index, u32 ho_hc_index, session_t *ts, hc_handle.conn_index = new_hc_index; ts->opaque = hc_handle.as_u32; - HTTP_DBG (1, "half-open hc index %x, hc [%u]%x", ho_hc_index, - ts->thread_index, new_hc_index); + HTTP_DBG (1, "half-open hc index %x, hc [%u]%x", ts->thread_index, + ho_hc_index, new_hc_index); if ((rv = http_vfts[hc->version].transport_connected_callback (hc))) { @@ -1257,7 +1257,7 @@ http_app_tx_callback (void *session, transport_send_params_t *sp) hc_index = http_vfts[hr_handle.version].hc_index_get_by_req_index ( hr_handle.req_index, as->thread_index); - HTTP_DBG (1, "hc [%u]%x", hc_index, as->connection_index); + HTTP_DBG (1, "hc [%u]%x", as->thread_index, hc_index); hc = http_conn_get_w_thread (hc_index, as->thread_index); @@ -1289,7 +1289,7 @@ http_app_rx_evt_cb (transport_connection_t *tc) http_conn_t *hc; http_req_handle_t hr_handle; - HTTP_DBG (1, "hc [%u]%x", vlib_get_thread_index (), req->hr_hc_index); + HTTP_DBG (1, "hc [%u]%x", req->c_thread_index, req->hr_hc_index); hr_handle.as_u32 = req->hr_req_handle; hc = http_conn_get_w_thread (req->hr_hc_index, req->c_thread_index); diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 5ecf8cc6fc..5b3b8536fd 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -1867,10 +1867,17 @@ http2_req_state_tunnel_tx (http_conn_t *hc, http2_req_t *req, { http2_conn_ctx_t *h2c; - ASSERT (!clib_llist_elt_is_linked (req, sched_list)); - HTTP_DBG (1, "tunnel received data from app"); + /* zero-copy proxy app can program new tx event before we were able to send + * previous payload, because fifos are shared, UDP/TCP clears evt on rx and + * proxy app program new tx evt for http */ + if (clib_llist_elt_is_linked (req, sched_list)) + { + http_req_deschedule (&req->base, sp); + return HTTP_SM_STOP; + } + /* add data back to stream scheduler */ HTTP_DBG (1, "adding to data queue req_index %x", ((http_req_handle_t) req->base.hr_req_handle).req_index); diff --git a/src/plugins/http/http_private.h b/src/plugins/http/http_private.h index 5b8f2c4894..1c906715b5 100644 --- a/src/plugins/http/http_private.h +++ b/src/plugins/http/http_private.h @@ -828,7 +828,7 @@ http_conn_accept_request (http_conn_t *hc, http_req_t *req) app_worker_t *app_wrk; int rv; - HTTP_DBG (1, "hc [%u]%x req %x", hc->hc_hc_index, hc->c_thread_index, + HTTP_DBG (1, "hc [%u]%x req %x", hc->c_thread_index, hc->hc_hc_index, req->hr_req_handle); /* allocate app session and initialize */ From 9530f52b44df0c8673f9e2070b3e4591a51b77d3 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 20 Aug 2025 12:46:12 +0200 Subject: [PATCH 238/313] hs-test: monitor performance of some loss tests + updated asserts Type: test Change-Id: I4aec44dde3e6c0472fca9fe6a7f80c068070f92e Signed-off-by: Adrian Villin --- extras/hs-test/echo_test.go | 105 ++++++++++++++++---- extras/hs-test/http1_test.go | 4 +- extras/hs-test/http2_test.go | 4 +- extras/hs-test/infra/common/suite_common.go | 50 ++++++---- extras/hs-test/infra/suite_veth.go | 38 ++++++- extras/hs-test/infra/suite_veth6.go | 4 +- extras/hs-test/infra/utils.go | 11 ++ extras/hs-test/nsim_test.go | 16 ++- extras/hs-test/proxy_test.go | 4 +- 9 files changed, 188 insertions(+), 48 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 65bd49f825..0d6c5eb13a 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -9,8 +9,8 @@ import ( func init() { RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest, EchoBuiltinTestbytesTest) - RegisterSoloVethTests(TcpWithLossTest) - RegisterVeth6Tests(TcpWithLoss6Test) + RegisterVethMWTests(TcpWithLossTest) + RegisterSoloVeth6Tests(TcpWithLoss6Test) } func EchoBuiltinTest(s *VethsSuite) { @@ -118,7 +118,7 @@ func EchoBuiltinTestbytesTest(s *VethsSuite) { s.AssertContains(o, " bytes out of 32768 sent (32768 target)") } -func TcpWithLossTest(s *VethsSuite) { +func tcpWithoutLoss(s *VethsSuite) string { serverVpp := s.Containers.ServerVpp.VppInstance serverVpp.Vppctl("test echo server uri tcp://%s/"+s.Ports.Port1, @@ -126,38 +126,107 @@ func TcpWithLossTest(s *VethsSuite) { clientVpp := s.Containers.ClientVpp.VppInstance - // Add loss of packets with Network Delay Simulator - s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + - " packet-size 1400 packets-per-drop 1000")) - - s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) - // Do echo test from client-vpp container - output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes bytes 50m", + output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes run-time 10 test-timeout 20", s.Interfaces.Server.Ip4AddressString(), s.Ports.Port1) s.Log(output) s.AssertNotEqual(len(output), 0) s.AssertNotContains(output, "failed", output) + + return output } -func TcpWithLoss6Test(s *Veths6Suite) { +func TcpWithLossTest(s *VethsSuite) { + s.CpusPerVppContainer = 2 + s.CpusPerContainer = 1 + s.SetupTest() + clientVpp := s.Containers.ClientVpp.VppInstance serverVpp := s.Containers.ServerVpp.VppInstance - serverVpp.Vppctl("test echo server uri tcp://%s/%s", - s.Interfaces.Server.Ip6AddressString(), s.Ports.Port1) + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 10 ms bandwidth 40 gbit")) + s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) - clientVpp := s.Containers.ClientVpp.VppInstance + s.Log(" * running TcpWithoutLoss") + output := tcpWithoutLoss(s) + baseline, err := s.ParseEchoClientTransfer(output) + s.AssertNil(err) - // Add loss of packets with Network Delay Simulator - s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 0.01 ms bandwidth 40 gbit" + - " packet-size 1400 packets-per-drop 1000")) + clientVpp.Disconnect() + clientVpp.Stop() + s.SetupClientVpp() + serverVpp.Disconnect() + serverVpp.Stop() + s.SetupServerVpp() + // Add loss of packets with Network Delay Simulator + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 10 ms bandwidth 40 gbit" + + " packet-size 1400 drop-fraction 0.033")) s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) + s.Log(" * running TcpWithLoss") + output = tcpWithoutLoss(s) + + withLoss, err := s.ParseEchoClientTransfer(output) + s.AssertNil(err) + + if !s.CoverageRun { + s.Log("\nBaseline: %v gbit/s\nWith loss: %v gbit/s", baseline, withLoss) + s.AssertGreaterEqual(baseline, withLoss) + s.AssertGreaterEqual(withLoss, baseline*0.2) + } +} + +func tcpWithoutLoss6(s *Veths6Suite) string { + serverVpp := s.Containers.ServerVpp.VppInstance + + serverVpp.Vppctl("test echo server uri tcp://%s/"+s.Ports.Port1, + s.Interfaces.Server.Ip6AddressString()) + + clientVpp := s.Containers.ClientVpp.VppInstance + // Do echo test from client-vpp container - output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes bytes 50m", + output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes run-time 10 test-timeout 20", s.Interfaces.Server.Ip6AddressString(), s.Ports.Port1) s.Log(output) s.AssertNotEqual(len(output), 0) s.AssertNotContains(output, "failed", output) + + return output +} + +func TcpWithLoss6Test(s *Veths6Suite) { + clientVpp := s.Containers.ClientVpp.VppInstance + serverVpp := s.Containers.ServerVpp.VppInstance + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 10 ms bandwidth 40 gbit")) + s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) + + s.Log(" * running TcpWithoutLoss") + output := tcpWithoutLoss6(s) + baseline, err := s.ParseEchoClientTransfer(output) + s.AssertNil(err) + + clientVpp.Disconnect() + clientVpp.Stop() + s.SetupClientVpp() + serverVpp.Disconnect() + serverVpp.Stop() + s.SetupServerVpp() + + // Add loss of packets with Network Delay Simulator + s.Log(clientVpp.Vppctl("set nsim poll-main-thread delay 10 ms bandwidth 40 gbit" + + " packet-size 1400 drop-fraction 0.033")) + + s.Log(clientVpp.Vppctl("nsim output-feature enable-disable host-" + s.Interfaces.Client.Name())) + + s.Log(" * running TcpWithLoss") + output = tcpWithoutLoss6(s) + + withLoss, err := s.ParseEchoClientTransfer(output) + s.AssertNil(err) + + if !s.CoverageRun { + s.Log("Baseline: %v gbit/s\nWith loss: %v gbit/s", baseline, withLoss) + s.AssertGreaterEqual(baseline, withLoss) + s.AssertGreaterEqual(withLoss, baseline*0.2) + } } diff --git a/extras/hs-test/http1_test.go b/extras/hs-test/http1_test.go index 6e0f9d7a80..dbbd5d54b8 100644 --- a/extras/hs-test/http1_test.go +++ b/extras/hs-test/http1_test.go @@ -698,7 +698,7 @@ func httpClientRepeat(s *Http1Suite, requestMethod string, clientArgs string) { s.Log("Server response count: %d", replyCountInt) s.AssertNotNil(o) s.AssertNotContains(o, "error") - s.AssertGreaterThan(replyCountInt, 15000) + s.AssertGreaterEqual(replyCountInt, 15000) replyCount = "" cmd = fmt.Sprintf("http client %s %s repeat %d header Hello:World uri %s", @@ -835,7 +835,7 @@ func HttpStaticPromTest(s *Http1Suite) { s.Log(DumpHttpResp(resp, false)) s.AssertHttpStatus(resp, 200) s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/plain") - s.AssertGreaterThan(resp.ContentLength, 0) + s.AssertGreaterEqual(resp.ContentLength, 0) _, err = io.ReadAll(resp.Body) s.AssertNil(err, fmt.Sprint(err)) } diff --git a/extras/hs-test/http2_test.go b/extras/hs-test/http2_test.go index 82230a0643..eaf18a91a1 100644 --- a/extras/hs-test/http2_test.go +++ b/extras/hs-test/http2_test.go @@ -125,7 +125,7 @@ func Http2ContinuationTxTest(s *Http2Suite) { s.AssertContains(log, "[64 bytes data]") sizeHeader, err := strconv.Atoi(strings.ReplaceAll(writeOut, "\x00", "")) s.AssertNil(err, fmt.Sprint(err)) - s.AssertGreaterThan(sizeHeader, 32768) + s.AssertGreaterEqual(sizeHeader, 32768) } func Http2ServerMemLeakTest(s *Http2Suite) { @@ -339,7 +339,7 @@ func Http2ClientContinuationTest(s *VethsSuite) { o := s.Containers.ClientVpp.VppInstance.Vppctl("http client fifo-size 64k verbose save-to response.txt uri " + uri) s.Log(o) s.AssertContains(o, "HTTP/2 200 OK") - s.AssertGreaterThan(strings.Count(o, "x"), 32768) + s.AssertGreaterEqual(strings.Count(o, "x"), 32768) } func Http2ClientMemLeakTest(s *Http2Suite) { diff --git a/extras/hs-test/infra/common/suite_common.go b/extras/hs-test/infra/common/suite_common.go index 2b7560dacc..be9a0a6ee3 100644 --- a/extras/hs-test/infra/common/suite_common.go +++ b/extras/hs-test/infra/common/suite_common.go @@ -105,71 +105,83 @@ func (s *HstCommon) Log(log any, arg ...any) { } } -func (s *HstCommon) AssertNil(object interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertNil(object any, msgAndArgs ...any) { ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...) } -func (s *HstCommon) AssertNotNil(object interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertNotNil(object any, msgAndArgs ...any) { ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...) } -func (s *HstCommon) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertEqual(expected, actual any, msgAndArgs ...any) { ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...) } -func (s *HstCommon) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertNotEqual(expected, actual any, msgAndArgs ...any) { ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...) } -func (s *HstCommon) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertContains(testString, contains any, msgAndArgs ...any) { ExpectWithOffset(2, strings.ToLower(fmt.Sprint(testString))).To(ContainSubstring(strings.ToLower(fmt.Sprint(contains))), msgAndArgs...) } -func (s *HstCommon) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertNotContains(testString, contains any, msgAndArgs ...any) { ExpectWithOffset(2, strings.ToLower(fmt.Sprint(testString))).ToNot(ContainSubstring(strings.ToLower(fmt.Sprint(contains))), msgAndArgs...) } -func (s *HstCommon) AssertEmpty(object interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertEmpty(object any, msgAndArgs ...any) { ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...) } -func (s *HstCommon) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertNotEmpty(object any, msgAndArgs ...any) { ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...) } -func (s *HstCommon) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertMatchError(actual, expected error, msgAndArgs ...any) { ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...) } -func (s *HstCommon) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertGreaterEqual(actual, expected any, msgAndArgs ...any) { ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...) } -func (s *HstCommon) AssertEqualWithinThreshold(actual, expected, threshold interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertGreaterThan(actual, expected any, msgAndArgs ...any) { + ExpectWithOffset(2, actual).Should(BeNumerically(">", expected), msgAndArgs...) +} + +func (s *HstCommon) AssertLessEqual(actual, expected any, msgAndArgs ...any) { + ExpectWithOffset(2, actual).Should(BeNumerically("<=", expected), msgAndArgs...) +} + +func (s *HstCommon) AssertLessThan(actual, expected any, msgAndArgs ...any) { + ExpectWithOffset(2, actual).Should(BeNumerically("<", expected), msgAndArgs...) +} + +func (s *HstCommon) AssertEqualWithinThreshold(actual, expected, threshold any, msgAndArgs ...any) { ExpectWithOffset(2, actual).Should(BeNumerically("~", expected, threshold), msgAndArgs...) } -func (s *HstCommon) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...any) { ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...) } -func (s *HstCommon) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...any) { ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...) } -func (s *HstCommon) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertHttpHeaderWithValue(resp *http.Response, key string, value any, msgAndArgs ...any) { ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...) } -func (s *HstCommon) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...any) { ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...) } -func (s *HstCommon) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...any) { ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...) } -func (s *HstCommon) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) { +func (s *HstCommon) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...any) { ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...) } @@ -190,8 +202,8 @@ func (s *HstCommon) AssertIperfMinTransfer(result IPerfResult, minTransferred in return } if result.Start.Details.Protocol == "TCP" { - s.AssertGreaterThan(result.End.TcpReceived.MBytes, minTransferred) + s.AssertGreaterEqual(result.End.TcpReceived.MBytes, minTransferred) } else { - s.AssertGreaterThan(result.End.Udp.MBytes, minTransferred) + s.AssertGreaterEqual(result.End.Udp.MBytes, minTransferred) } } diff --git a/extras/hs-test/infra/suite_veth.go b/extras/hs-test/infra/suite_veth.go index 65506c448b..37907f1fcb 100644 --- a/extras/hs-test/infra/suite_veth.go +++ b/extras/hs-test/infra/suite_veth.go @@ -13,6 +13,7 @@ import ( var vethTests = map[string][]func(s *VethsSuite){} var vethSoloTests = map[string][]func(s *VethsSuite){} +var vethMWTests = map[string][]func(s *VethsSuite){} type VethsSuite struct { HstSuite @@ -38,6 +39,9 @@ func RegisterVethTests(tests ...func(s *VethsSuite)) { func RegisterSoloVethTests(tests ...func(s *VethsSuite)) { vethSoloTests[GetTestFilename()] = tests } +func RegisterVethMWTests(tests ...func(s *VethsSuite)) { + vethMWTests[GetTestFilename()] = tests +} func (s *VethsSuite) SetupSuite() { time.Sleep(1 * time.Second) @@ -83,7 +87,7 @@ func (s *VethsSuite) SetupTest() { s.AssertNotNil(clientVpp, fmt.Sprint(err)) s.SetupServerVpp() - s.setupClientVpp() + s.SetupClientVpp() if *DryRun { s.LogStartedContainers() s.Skip("Dry run mode = true") @@ -99,7 +103,7 @@ func (s *VethsSuite) SetupServerVpp() { s.AssertNotEqual(0, idx) } -func (s *VethsSuite) setupClientVpp() { +func (s *VethsSuite) SetupClientVpp() { clientVpp := s.GetContainerByName("client-vpp").VppInstance s.AssertNil(clientVpp.Start()) @@ -168,3 +172,33 @@ var _ = Describe("VethsSuiteSolo", Ordered, ContinueOnFailure, Serial, func() { } } }) + +var _ = Describe("VethsSuiteMW", Ordered, ContinueOnFailure, Serial, func() { + var s VethsSuite + BeforeAll(func() { + s.SetupSuite() + }) + BeforeEach(func() { + s.SkipIfNotEnoguhCpus = true + }) + AfterAll(func() { + s.TeardownSuite() + }) + AfterEach(func() { + s.TeardownTest() + }) + + // https://onsi.github.io/ginkgo/#dynamically-generating-specs + for filename, tests := range vethMWTests { + for _, test := range tests { + test := test + pc := reflect.ValueOf(test).Pointer() + funcValue := runtime.FuncForPC(pc) + testName := filename + "/" + strings.Split(funcValue.Name(), ".")[2] + It(testName, Label("SOLO", "VPP Multi-Worker"), func(ctx SpecContext) { + s.Log(testName + ": BEGIN") + test(&s) + }, SpecTimeout(TestTimeout)) + } + } +}) diff --git a/extras/hs-test/infra/suite_veth6.go b/extras/hs-test/infra/suite_veth6.go index 2e3fe98c3b..a57a7526da 100644 --- a/extras/hs-test/infra/suite_veth6.go +++ b/extras/hs-test/infra/suite_veth6.go @@ -78,7 +78,7 @@ func (s *Veths6Suite) SetupTest() { s.AssertNotNil(clientVpp, fmt.Sprint(err)) s.SetupServerVpp() - s.setupClientVpp() + s.SetupClientVpp() if *DryRun { s.LogStartedContainers() s.Skip("Dry run mode = true") @@ -94,7 +94,7 @@ func (s *Veths6Suite) SetupServerVpp() { s.AssertNotEqual(0, idx) } -func (s *Veths6Suite) setupClientVpp() { +func (s *Veths6Suite) SetupClientVpp() { clientVpp := s.GetContainerByName("client-vpp").VppInstance s.AssertNil(clientVpp.Start()) diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index 1d84a3f61e..c00cb9bc40 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -367,3 +367,14 @@ func (s *HstSuite) StartUdpEchoServer(addr string, port int) *net.UDPConn { s.Log("* started udp echo server " + addr + ":" + strconv.Itoa(port)) return conn } + +// Parses transfer speed from the last line ("N gbit/second full-duplex") +func (s *HstSuite) ParseEchoClientTransfer(stats string) (float64, error) { + lines := strings.Split(strings.TrimSpace(stats), "\n") + parts := strings.Fields(lines[len(lines)-1]) + if len(parts) == 0 { + return 0, errors.New("check format of stats") + } + number, err := strconv.ParseFloat(parts[0], 64) + return number, err +} diff --git a/extras/hs-test/nsim_test.go b/extras/hs-test/nsim_test.go index c47e83455e..ed9aac585a 100644 --- a/extras/hs-test/nsim_test.go +++ b/extras/hs-test/nsim_test.go @@ -1,6 +1,9 @@ package main import ( + "errors" + "regexp" + "strconv" "strings" . "fd.io/hs-test/infra" @@ -20,5 +23,16 @@ func NsimLossTest(s *VethsSuite) { lines := strings.Split(o, "\n") stats := lines[len(lines)-2] s.Log(stats) - s.AssertContains(stats, "10% packet loss") + + re := regexp.MustCompile(`(\d+\.?\d*)\s*%\s*packet loss`) + matches := re.FindStringSubmatch(stats) + if len(matches) < 2 { + s.AssertNil(errors.New("Error when parsing stats.")) + } + packetLossStr := matches[1] + packetLoss, err := strconv.ParseFloat(packetLossStr, 64) + s.AssertNil(err) + if !s.CoverageRun { + s.AssertEqual(packetLoss, float64(10), "Packet loss != 10%%") + } } diff --git a/extras/hs-test/proxy_test.go b/extras/hs-test/proxy_test.go index 183cca7252..2168134536 100644 --- a/extras/hs-test/proxy_test.go +++ b/extras/hs-test/proxy_test.go @@ -323,7 +323,7 @@ func vppConnectProxyStressLoad(s *VppProxySuite, proxyPort string) { report += fmt.Sprintf("Errors: timeout %d, read %d, write %d, invalid data received %d, connection %d\n", timeout.Load(), readError.Load(), writeError.Load(), invalidData.Load(), connectError.Load()) report += fmt.Sprintf("Successes ratio: %.2f%%\n", successRatio) AddReportEntry(summary, report) - s.AssertGreaterThan(successRatio, 90.0) + s.AssertGreaterEqual(successRatio, 90.0) } func VppConnectProxyStressTest(s *VppProxySuite) { @@ -627,7 +627,7 @@ func vppConnectUdpStressLoad(s *VppUdpProxySuite) { report += fmt.Sprintf("Errors: timeout %d, read %d, write %d, invalid data received %d, connection %d\n", timeout.Load(), readError.Load(), writeError.Load(), invalidData.Load(), connectError.Load()) report += fmt.Sprintf("Successes ratio: %.2f%%\n", successRatio) AddReportEntry(summary, report) - s.AssertGreaterThan(successRatio, 90.0) + s.AssertGreaterEqual(successRatio, 90.0) } func VppConnectUdpStressTest(s *VppUdpProxySuite) { From e360fe768aa6564d7b4d13cf1cc843cbfd33a25b Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 25 Aug 2025 08:54:58 -0400 Subject: [PATCH 239/313] hsa: fix test client args check to allow default port Type: fix Change-Id: Ic81370ba1c4cb83793b29fc9478eee8537136aa7 Signed-off-by: Florin Coras --- src/plugins/hs_apps/vcl/vcl_test_client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/hs_apps/vcl/vcl_test_client.c b/src/plugins/hs_apps/vcl/vcl_test_client.c index a5ea90dc8a..95698b9da1 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_client.c +++ b/src/plugins/hs_apps/vcl/vcl_test_client.c @@ -1227,7 +1227,7 @@ vtc_process_opts (vcl_test_client_main_t * vcm, int argc, char **argv) print_usage_and_exit (); } - if (argc < (optind + 2)) + if (argc < (optind + 1)) { vtwrn ("Invalid number of arguments!"); print_usage_and_exit (); From ec91f875ef38bae2a035c823fa96523d551c2758 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 25 Aug 2025 09:11:19 -0400 Subject: [PATCH 240/313] session: fix app listener free if listen returns If listen fails, regrab app listener in case transports reallocated the app listener pool. Type: fix Change-Id: Ifeb512ececacd9bd8f201eac2e08ab4ee6f1cdd5 Signed-off-by: Florin Coras --- src/vnet/session/application.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vnet/session/application.c b/src/vnet/session/application.c index ee66d755ad..afe7a5eb3d 100644 --- a/src/vnet/session/application.c +++ b/src/vnet/session/application.c @@ -221,6 +221,7 @@ app_listener_alloc_and_init (application_t * app, if ((rv = session_listen (ls, sep))) { ls = listen_session_get_from_handle (lh); + app_listener = app_listener_get (al_index); session_free (ls); app_listener_free (app, app_listener); return rv; From 28bf91fd3192ed5687ffa053f2af77893ebdd27f Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Mon, 25 Aug 2025 10:39:27 -0400 Subject: [PATCH 241/313] hsa: fix echo server rx callback crash Handle rx after close. Type: fix Change-Id: I9507857ba8903b674e249797d2837981382b4aec Signed-off-by: Florin Coras --- src/plugins/hs_apps/echo_server.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/plugins/hs_apps/echo_server.c b/src/plugins/hs_apps/echo_server.c index 41f5ac8791..9a0dd284c0 100644 --- a/src/plugins/hs_apps/echo_server.c +++ b/src/plugins/hs_apps/echo_server.c @@ -372,11 +372,15 @@ echo_server_rx_callback (session_t * s) int actual_transfer; svm_fifo_t *tx_fifo, *rx_fifo; echo_server_main_t *esm = &echo_server_main; - clib_thread_index_t thread_index = vlib_get_thread_index (); + clib_thread_index_t thread_index = s->thread_index; es_worker_t *wrk; es_session_t *es; - ASSERT (s->thread_index == thread_index); + ASSERT (thread_index == vlib_get_thread_index ()); + + /* Closes are treated as half-closes by session layer */ + if (PREDICT_FALSE (s->flags & SESSION_F_APP_CLOSED)) + return 0; rx_fifo = s->rx_fifo; tx_fifo = s->tx_fifo; From ac73c858e807629e91f761436f25006dd849417c Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 25 Aug 2025 16:42:13 -0400 Subject: [PATCH 242/313] http: http2_app_close_callback free req when done Type: fix Change-Id: I1bb8730cc4f6e759eebec37ef7c887b7e20faf3a Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 5b3b8536fd..1a9208437a 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2183,6 +2183,7 @@ http2_handle_data_frame (http_conn_t *hc, http2_frame_header_t *fh) if (fh->stream_id <= h2c->last_opened_stream_id) { HTTP_DBG (1, "stream closed, ignoring frame"); + http_io_ts_drain (hc, fh->length); http2_send_stream_error (hc, fh->stream_id, HTTP2_ERROR_STREAM_CLOSED, 0); return HTTP2_ERROR_NO_ERROR; @@ -2777,7 +2778,7 @@ http2_app_close_callback (http_conn_t *hc, u32 req_index, hc->state == HTTP_CONN_STATE_CLOSED) { HTTP_DBG (1, "nothing more to send, confirm close"); - session_transport_closed_notify (&req->base.connection); + http2_stream_close (req, hc); if (req->flags & HTTP2_REQ_F_IS_PARENT) { HTTP_DBG (1, "client app closed parent, closing connection"); @@ -2924,7 +2925,8 @@ http2_transport_rx_callback (http_conn_t *hc) http_io_ts_drain (hc, HTTP2_FRAME_HEADER_SIZE); to_deq -= fh.length; - HTTP_DBG (1, "frame type 0x%02x len %u", fh.type, fh.length); + HTTP_DBG (1, "frame type 0x%02x len %u flags 0x%01x", fh.type, fh.length, + fh.flags); if ((h2c->flags & HTTP2_CONN_F_EXPECT_CONTINUATION) && fh.type != HTTP2_FRAME_TYPE_CONTINUATION) From 632d9f9dbaf038f75b32bf302345e1a9e7529c26 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Mon, 25 Aug 2025 13:05:02 -0400 Subject: [PATCH 243/313] http: http2_format_req print stream state instead of connection state Type: improvement Change-Id: Iaf03a3cb3f78b05d3002f0edee753797c213e651 Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index 1a9208437a..ba6e927b0c 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -2645,6 +2645,25 @@ format_http2_req (u8 *s, va_list *args) return s; } +static u8 * +format_http2_stream_state (u8 *s, va_list *args) +{ + http2_stream_state_t state = va_arg (*args, http2_stream_state_t); + u8 *t = 0; + + switch (state) + { +#define _(s, str) \ + case HTTP2_STREAM_STATE_##s: \ + t = (u8 *) str; \ + break; + foreach_http2_stream_state +#undef _ + default : return format (s, "unknown"); + } + return format (s, "%s", t); +} + static u8 * http2_format_req (u8 *s, va_list *args) { @@ -2659,8 +2678,8 @@ http2_format_req (u8 *s, va_list *args) s = format (s, "%-" SESSION_CLI_ID_LEN "U", format_http2_req, req, hc); if (verbose) { - s = - format (s, "%-" SESSION_CLI_STATE_LEN "U", format_http_conn_state, hc); + s = format (s, "%-" SESSION_CLI_STATE_LEN "U", format_http2_stream_state, + req->stream_state); if (verbose > 1) s = format (s, "\n"); } From a651798373386f61c99974716736b9200c1befe9 Mon Sep 17 00:00:00 2001 From: Adrian Villin Date: Wed, 27 Aug 2025 12:39:53 +0200 Subject: [PATCH 244/313] hs-test: parse bytes/sec in TcpWithLoss tests Type: test Change-Id: I29ff3caa6a1f573a7e285c17416a24a74f4767f7 Signed-off-by: Adrian Villin --- extras/hs-test/echo_test.go | 6 +++--- extras/hs-test/infra/utils.go | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 0d6c5eb13a..3dada0eda5 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -170,7 +170,7 @@ func TcpWithLossTest(s *VethsSuite) { s.AssertNil(err) if !s.CoverageRun { - s.Log("\nBaseline: %v gbit/s\nWith loss: %v gbit/s", baseline, withLoss) + s.Log("\nBaseline: %.2f bytes/s\nWith loss: %.2f bytes/s", baseline, withLoss) s.AssertGreaterEqual(baseline, withLoss) s.AssertGreaterEqual(withLoss, baseline*0.2) } @@ -225,8 +225,8 @@ func TcpWithLoss6Test(s *Veths6Suite) { s.AssertNil(err) if !s.CoverageRun { - s.Log("Baseline: %v gbit/s\nWith loss: %v gbit/s", baseline, withLoss) + s.Log("\nBaseline: %.2f bytes/s\nWith loss: %.2f bytes/s", baseline, withLoss) s.AssertGreaterEqual(baseline, withLoss) - s.AssertGreaterEqual(withLoss, baseline*0.2) + s.AssertGreaterEqual(withLoss, baseline*0.15) } } diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go index c00cb9bc40..e63b8e44a3 100644 --- a/extras/hs-test/infra/utils.go +++ b/extras/hs-test/infra/utils.go @@ -368,13 +368,19 @@ func (s *HstSuite) StartUdpEchoServer(addr string, port int) *net.UDPConn { return conn } -// Parses transfer speed from the last line ("N gbit/second full-duplex") +// Parses transfer speed ("N bytes/second full-duplex") func (s *HstSuite) ParseEchoClientTransfer(stats string) (float64, error) { lines := strings.Split(strings.TrimSpace(stats), "\n") - parts := strings.Fields(lines[len(lines)-1]) - if len(parts) == 0 { - return 0, errors.New("check format of stats") + for i := len(lines) - 1; i >= 0; i-- { + line := strings.TrimSpace(lines[i]) + if strings.Contains(line, "bytes/second") { + parts := strings.Fields(line) + if len(parts) == 0 { + return 0, errors.New("check format of stats") + } + num := strings.ReplaceAll(parts[0], ",", "") + return strconv.ParseFloat(num, 64) + } } - number, err := strconv.ParseFloat(parts[0], 64) - return number, err + return 0, errors.New(`"bytes/second" not found`) } From 3fdc92762f1e808ccd35e5431d2e887c4683fda8 Mon Sep 17 00:00:00 2001 From: Konstantin Kogdenko Date: Sat, 23 Aug 2025 12:09:04 +0300 Subject: [PATCH 245/313] vhost: move tracing out of processing loop Before this fix vlib_trace_buffer was being called on not fully initialized vlib_buffer_t (current_length == 0). This caused trace filter functions to work incorrectly. Type: fix Change-Id: Ia8974af90ce91022ce1436ab54ab630bacce5bcf Signed-off-by: Konstantin Kogdenko --- src/plugins/vhost/vhost_user_input.c | 46 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/plugins/vhost/vhost_user_input.c b/src/plugins/vhost/vhost_user_input.c index 5dc1eedf52..328da7296b 100644 --- a/src/plugins/vhost/vhost_user_input.c +++ b/src/plugins/vhost/vhost_user_input.c @@ -381,6 +381,33 @@ vhost_user_input_setup_frame (vlib_main_t * vm, vlib_node_runtime_t * node, } } +static_always_inline void +vhost_user_if_input_trace (vlib_main_t *vm, vlib_node_runtime_t *node, + u32 n_trace, vhost_user_intf_t *vui, u16 qid, + u32 next_index, u16 last_avail_idx, u32 n_left, + const u32 *bi) +{ + vhost_user_vring_t *txvq = &vui->vrings[VHOST_VRING_IDX_TX (qid)]; + + while (n_trace && n_left) + { + vlib_buffer_t *b = vlib_get_buffer (vm, bi[0]); + + if (PREDICT_FALSE (vlib_trace_buffer (vm, node, next_index, b, 0))) + { + vhost_trace_t *t0 = vlib_add_trace (vm, node, b, sizeof (t0[0])); + vhost_user_rx_trace (t0, vui, qid, b, txvq, last_avail_idx); + n_trace--; + } + + n_left--; + last_avail_idx++; + bi++; + } + + vlib_set_trace_count (vm, node, n_trace); +} + static_always_inline u32 vhost_user_if_input (vlib_main_t *vm, vhost_user_main_t *vum, vhost_user_intf_t *vui, u16 qid, @@ -549,17 +576,6 @@ vhost_user_if_input (vlib_main_t *vm, vhost_user_main_t *vum, b_head->total_length_not_including_first_buffer = 0; b_head->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID; - if (PREDICT_FALSE - (n_trace > 0 && vlib_trace_buffer (vm, node, next_index, b_head, - /* follow_chain */ 0))) - { - vhost_trace_t *t0 = - vlib_add_trace (vm, node, b_head, sizeof (t0[0])); - vhost_user_rx_trace (t0, vui, qid, b_head, txvq, last_avail_idx); - n_trace--; - vlib_set_trace_count (vm, node, n_trace); - } - /* This depends on the setup but is very consistent * So I think the CPU branch predictor will make a pretty good job * at optimizing the decision. */ @@ -737,6 +753,14 @@ vhost_user_if_input (vlib_main_t *vm, vhost_user_main_t *vum, VHOST_USER_INPUT_FUNC_ERROR_MMAP_FAIL, 1); } + /* packet trace if enabled */ + if (PREDICT_FALSE (n_trace)) + { + vhost_user_if_input_trace (vm, node, n_trace, vui, qid, next_index, + last_avail_idx - n_rx_packets, n_rx_packets, + to_next - n_rx_packets); + } + /* give buffers back to driver */ CLIB_MEMORY_STORE_BARRIER (); txvq->used->idx = txvq->last_used_idx; From f41b05b2f3d5e63f5da019eede04afcc3501252f Mon Sep 17 00:00:00 2001 From: Changbin Park Date: Wed, 23 Jul 2025 14:39:58 +0900 Subject: [PATCH 246/313] tcp: handle SYN while CLOSED state Existing code handled SYN packets in TIME_WAIT state already. And, according to the comment it should handle for CLOSED state as well. But, it only checks for the TIME_WAIT state. docs: Handle SYN packets in the CLOSED state as well Issue: https://github.com/FDio/vpp/issues/3617 Type: fix Signed-off-by: Changbin Park Change-Id: I478e3560ed509fb282d6cdb4a16b6f4663effb31 --- src/plugins/unittest/session_test.c | 210 ++++++++++++++++++++++++++++ src/vnet/tcp/tcp_input.c | 28 +++- 2 files changed, 231 insertions(+), 7 deletions(-) diff --git a/src/plugins/unittest/session_test.c b/src/plugins/unittest/session_test.c index d54655e645..e782f6266a 100644 --- a/src/plugins/unittest/session_test.c +++ b/src/plugins/unittest/session_test.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -2687,6 +2688,213 @@ session_test_ext_cfg (vlib_main_t *vm, unformat_input_t *input) return 0; } +static int +session_test_reconn_while_closed (vlib_main_t *vm, unformat_input_t *input) +{ + u64 options[APP_OPTIONS_N_OPTIONS], placeholder_secret = 1234; + u32 server_index, client_index, sw_if_index[2], tries = 0; + u16 placeholder_server_port = 1234, placeholder_client_port = 5678; + session_endpoint_cfg_t server_sep = SESSION_ENDPOINT_CFG_NULL; + session_endpoint_cfg_t client_sep = SESSION_ENDPOINT_CFG_NULL; + u32 client_vrf = 0, server_vrf = 1; + ip4_address_t intf_addr[2]; + u8 *appns_id; + int error; + + ST_DBG ("session_test_reconn_while_closed"); + + /* Reset global state */ + connected_session_index = connected_session_thread = ~0; + accepted_session_index = accepted_session_thread = ~0; + placeholder_accept = 0; + app_session_error = 0; + + /* Create the loopbacks */ + intf_addr[0].as_u32 = clib_host_to_net_u32 (0x03030303); + session_create_lookpback (client_vrf, &sw_if_index[0], &intf_addr[0]); + intf_addr[1].as_u32 = clib_host_to_net_u32 (0x04040404); + session_create_lookpback (server_vrf, &sw_if_index[1], &intf_addr[1]); + session_add_del_route_via_lookup_in_table ( + client_vrf, server_vrf, &intf_addr[1], 32, 1 /* is_add */); + session_add_del_route_via_lookup_in_table ( + server_vrf, client_vrf, &intf_addr[0], 32, 1 /* is_add */); + + /* Insert namespace */ + appns_id = format (0, "appns_server"); + vnet_app_namespace_add_del_args_t ns_args = { .ns_id = appns_id, + .secret = placeholder_secret, + .sw_if_index = sw_if_index[1], + .ip4_fib_id = 0, + .is_add = 1 }; + error = vnet_app_namespace_add_del (&ns_args); + SESSION_TEST ((error == 0), "app ns insertion should succeed: %d", error); + + /* Attach client/server */ + clib_memset (options, 0, sizeof (options)); + options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; + options[APP_OPTIONS_FLAGS] |= APP_OPTIONS_FLAGS_USE_GLOBAL_SCOPE; + + vnet_app_attach_args_t attach_args = { + .api_client_index = ~0, + .options = options, + .namespace_id = 0, + .session_cb_vft = &placeholder_session_cbs, + .name = format (0, "session_test_client"), + }; + + error = vnet_application_attach (&attach_args); + SESSION_TEST ((error == 0), "client app attached: %U", format_session_error, + error); + client_index = attach_args.app_index; + vec_free (attach_args.name); + + attach_args.name = format (0, "session_test_server"); + attach_args.namespace_id = appns_id; + attach_args.options[APP_OPTIONS_ADD_SEGMENT_SIZE] = 32 << 20; + attach_args.options[APP_OPTIONS_NAMESPACE_SECRET] = placeholder_secret; + error = vnet_application_attach (&attach_args); + SESSION_TEST ((error == 0), "server app attached: %U", format_session_error, + error); + vec_free (attach_args.name); + server_index = attach_args.app_index; + + /* Listen on server */ + server_sep.is_ip4 = 1; + server_sep.port = placeholder_server_port; + vnet_listen_args_t bind_args = { + .sep_ext = server_sep, + .app_index = server_index, + }; + error = vnet_listen (&bind_args); + SESSION_TEST ((error == 0), "server is listening"); + + /* First connection: Connect with fixed 5-tuple */ + client_sep.is_ip4 = 1; + client_sep.ip.ip4.as_u32 = intf_addr[1].as_u32; + client_sep.port = placeholder_server_port; + client_sep.peer.is_ip4 = 1; + client_sep.peer.ip.ip4.as_u32 = intf_addr[0].as_u32; + client_sep.peer.port = placeholder_client_port; + client_sep.transport_proto = TRANSPORT_PROTO_TCP; + + vnet_connect_args_t connect_args = { + .sep_ext = client_sep, + .app_index = client_index, + }; + + connected_session_index = connected_session_thread = ~0; + accepted_session_index = accepted_session_thread = ~0; + error = vnet_connect (&connect_args); + SESSION_TEST ((error == 0), "connecting first session"); + + /* Wait for connection establishment */ + tries = 0; + while (placeholder_accept == 0 && ++tries < 100) + { + vlib_worker_thread_barrier_release (vm); + vlib_process_suspend (vm, 100e-3); + vlib_worker_thread_barrier_sync (vm); + } + SESSION_TEST ((accepted_session_index != ~0), + "first session is accepted: %u", accepted_session_index); + while (connected_session_index == ~0 && ++tries < 100) + { + vlib_worker_thread_barrier_release (vm); + vlib_process_suspend (vm, 100e-3); + vlib_worker_thread_barrier_sync (vm); + } + SESSION_TEST ((connected_session_index != ~0), + "first session is connected: %u", connected_session_index); + + /* Acquire server side connections prior to disconnect for later use */ + transport_connection_t *tc = session_get_transport ( + session_get (accepted_session_index, accepted_session_thread)); + + /* Close the first connection from server side */ + vnet_disconnect_args_t disconnect_args = { + .handle = + session_make_handle (accepted_session_index, accepted_session_thread), + .app_index = server_index, + }; + error = vnet_disconnect_session (&disconnect_args); + SESSION_TEST ((error == 0), "first session is being disconnected by server"); + + /* Wait for disconnection of client */ + tries = 0; + while (connected_session_index != ~0 && ++tries < 100) + { + vlib_worker_thread_barrier_release (vm); + vlib_process_suspend (vm, 100e-3); + vlib_worker_thread_barrier_sync (vm); + } + SESSION_TEST ((connected_session_index == ~0), + "the client connection is disconnected"); + + /* force server side to get CLOSED state */ + transport_reset (tc->proto, tc->c_index, tc->thread_index); + tcp_connection_t *tcp = tcp_connection_get (tc->c_index, tc->thread_index); + SESSION_TEST ((tcp && tcp->state == TCP_STATE_CLOSED), + "the server connection is in CLOSED"); + + /* Second connection: attempt to reconnect with same 5-tuple */ + ST_DBG ("Trying to reconnect to CLOSED Server session"); + placeholder_accept = 0; + + /* Use identical 5-tuple */ + error = vnet_connect (&connect_args); + SESSION_TEST (error == 0, "immediate second connect should not fail: %U", + format_session_error, error); + /* If connect succeeds, wait a bit to see if connection actually establishes + */ + tries = 0; + while (connected_session_index == ~0 && ++tries < 100) + { + vlib_worker_thread_barrier_release (vm); + vlib_process_suspend (vm, 10e-3); + vlib_worker_thread_barrier_sync (vm); + } + SESSION_TEST (connected_session_index != ~0, + "immediate second connection should establish"); + + /* Clean up the second connection */ + if (connected_session_index != ~0) + { + disconnect_args.handle = session_make_handle (connected_session_index, + connected_session_thread); + disconnect_args.app_index = client_index; + error = vnet_disconnect_session (&disconnect_args); + SESSION_TEST ((error == 0), "second disconnect should work"); + } + + /* Cleanup */ + vnet_app_detach_args_t detach_args = { + .app_index = server_index, + .api_client_index = ~0, + }; + vnet_application_detach (&detach_args); + detach_args.app_index = client_index; + vnet_application_detach (&detach_args); + + ns_args.is_add = 0; + error = vnet_app_namespace_add_del (&ns_args); + SESSION_TEST ((error == 0), "app ns delete should succeed: %d", error); + + /* Allow cleanup to finish */ + vlib_process_suspend (vm, 100e-3); + + session_add_del_route_via_lookup_in_table ( + client_vrf, server_vrf, &intf_addr[1], 32, 0 /* is_add */); + session_add_del_route_via_lookup_in_table ( + server_vrf, client_vrf, &intf_addr[0], 32, 0 /* is_add */); + + session_delete_loopback (sw_if_index[0]); + session_delete_loopback (sw_if_index[1]); + + vec_free (appns_id); + + return 0; +} + static clib_error_t * session_test (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd_arg) @@ -2719,6 +2927,8 @@ session_test (vlib_main_t * vm, res = session_test_sdl (vm, input); else if (unformat (input, "ext-cfg")) res = session_test_ext_cfg (vm, input); + else if (unformat (input, "reconn-while-closed")) + res = session_test_reconn_while_closed (vm, input); else if (unformat (input, "all")) { if ((res = session_test_basic (vm, input))) diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c index f1b34290af..b8b4c3432a 100644 --- a/src/vnet/tcp/tcp_input.c +++ b/src/vnet/tcp/tcp_input.c @@ -2583,25 +2583,39 @@ tcp46_listen_inline (vlib_main_t *vm, vlib_node_runtime_t *node, /* Probably we are in time-wait or closed state */ else { + u32 fib_index; tcp_connection_t *tc; tc = tcp_connection_get (vnet_buffer (b[0])->tcp.connection_index, thread_index); - if (!tc || tc->state != TCP_STATE_TIME_WAIT) + if (!tc) { - tcp_inc_counter (listen, TCP_ERROR_CREATE_EXISTS, 1); + tcp_inc_counter (listen, TCP_ERROR_INVALID_CONNECTION, 1); goto done; } - if (PREDICT_FALSE (!syn_during_timewait (tc, b[0], &tw_iss))) + fib_index = tc->c_fib_index; + switch (tc->state) { - /* This SYN can't be accepted */ + case TCP_STATE_TIME_WAIT: + if (PREDICT_FALSE (!syn_during_timewait (tc, b[0], &tw_iss))) + { + /* This SYN can't be accepted */ + tcp_inc_counter (listen, TCP_ERROR_CREATE_EXISTS, 1); + goto done; + } + /* clean up the old session */ + tcp_connection_cleanup_and_notify (tc); + break; + case TCP_STATE_CLOSED: + session_lookup_del_connection (&tc->connection); + tc->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; + break; + default: tcp_inc_counter (listen, TCP_ERROR_CREATE_EXISTS, 1); goto done; } - lc = tcp_lookup_listener (b[0], tc->c_fib_index, is_ip4); - /* clean up the old session */ - tcp_connection_cleanup_and_notify (tc); + lc = tcp_lookup_listener (b[0], fib_index, is_ip4); /* listener was cleaned up */ if (!lc) { From 8d41c6f7d84f17c55ec9901f8701371a6eb644e4 Mon Sep 17 00:00:00 2001 From: Artem Glazychev Date: Mon, 18 Aug 2025 11:18:05 +0700 Subject: [PATCH 247/313] fib: change the order of adding interface routes The order in which fib_table_entry is added affects the update of adjacency. If you assign a /31 address to the interface, an ARP request is sent to the peer with the source address from the glean adjacency, which is incorrect before adding the local address route. Steps to reproduce the problem: create tap set interface state tap0 up set interface ip address tap0 192.168.100.1/31 In this case, ARP-Request was sent with an incorrect sender IP: Sender IP Address: 192.168.100.0 Target IP Address: 192.168.100.0 Type: fix Change-Id: I8712bcf9fd0cb96788fe4a6c4b4827af774c0ab9 Signed-off-by: Artem Glazychev --- src/vnet/ip/ip4_forward.c | 7 ++++--- src/vnet/ip/ip6_forward.c | 9 +++++---- test/test_ip4.py | 27 +++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/vnet/ip/ip4_forward.c b/src/vnet/ip/ip4_forward.c index 4760e29a6f..fb96159b4c 100644 --- a/src/vnet/ip/ip4_forward.c +++ b/src/vnet/ip/ip4_forward.c @@ -452,9 +452,6 @@ ip4_add_interface_routes (u32 sw_if_index, .fp_addr.ip4 = *address, }; - /* set special routes for the prefix if needed */ - ip4_add_interface_prefix_routes (im, sw_if_index, fib_index, a); - if (sw_if_index < vec_len (lm->classify_table_index_by_sw_if_index)) { u32 classify_table_index = @@ -487,6 +484,10 @@ ip4_add_interface_routes (u32 sw_if_index, ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); + + /* set special routes for the prefix if needed */ + if (a->address_length < 32) + ip4_add_interface_prefix_routes (im, sw_if_index, fib_index, a); } static void diff --git a/src/vnet/ip/ip6_forward.c b/src/vnet/ip/ip6_forward.c index 803396f583..1a77bd702c 100644 --- a/src/vnet/ip/ip6_forward.c +++ b/src/vnet/ip/ip6_forward.c @@ -132,10 +132,6 @@ ip6_add_interface_routes (vnet_main_t * vnm, u32 sw_if_index, .fp_addr.ip6 = *address, }; - /* set special routes for the prefix if needed */ - ip6_add_interface_prefix_routes (im, sw_if_index, fib_index, - address, a->address_length); - pfx.fp_len = 128; if (sw_if_index < vec_len (lm->classify_table_index_by_sw_if_index)) { @@ -166,6 +162,11 @@ ip6_add_interface_routes (vnet_main_t * vnm, u32 sw_if_index, &pfx.fp_addr, sw_if_index, ~0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE); + + /* set special routes for the prefix if needed */ + if (a->address_length < 128) + ip6_add_interface_prefix_routes (im, sw_if_index, fib_index, address, + a->address_length); } static void diff --git a/test/test_ip4.py b/test/test_ip4.py index 9d5db0d38c..5f8aafad47 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -1194,11 +1194,33 @@ def test_ip_sub_nets(self): # A /31 is a special case where the 'other-side' is an attached host # packets to that peer generate ARP requests # - ip_addr_n = socket.inet_pton(socket.AF_INET, "10.10.10.10") + # Let's consider two cases: + # Case 1: interface has the second address of /31 + # + self.pg_enable_capture(self.pg_interfaces) + self.vapi.sw_interface_add_del_address( + sw_if_index=self.pg0.sw_if_index, prefix="10.10.10.11/31" + ) + + rx = self.pg0.get_capture(1) + self.assertEqual(rx[0][ARP].psrc, "10.10.10.11") + self.assertEqual(rx[0][ARP].pdst, "10.10.10.10") + # remove the sub-net + self.vapi.sw_interface_add_del_address( + sw_if_index=self.pg0.sw_if_index, prefix="10.10.10.11/31", is_add=0 + ) + + # + # Case 2: interface has the first address of /31 + # + self.pg_enable_capture(self.pg_interfaces) self.vapi.sw_interface_add_del_address( sw_if_index=self.pg0.sw_if_index, prefix="10.10.10.10/31" ) + rx = self.pg0.get_capture(1) + self.assertEqual(rx[0][ARP].psrc, "10.10.10.10") + self.assertEqual(rx[0][ARP].pdst, "10.10.10.11") pn = ( Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac) @@ -1211,7 +1233,8 @@ def test_ip_sub_nets(self): self.pg_enable_capture(self.pg_interfaces) self.pg_start() rx = self.pg0.get_capture(1) - rx[ARP] + self.assertEqual(rx[0][ARP].psrc, "10.10.10.10") + self.assertEqual(rx[0][ARP].pdst, "10.10.10.11") # remove the sub-net and we are forwarding via the cover again self.vapi.sw_interface_add_del_address( From 273d20cd71d9adaabe2df8d13e01e3c61fb05add Mon Sep 17 00:00:00 2001 From: Monendra Singh Kushwaha Date: Thu, 21 Aug 2025 07:29:43 +0000 Subject: [PATCH 248/313] octeon: add L4 checksum flags This patch adds L4 checksum related flags in vlib buffer. Type: feature Change-Id: Ic01940e7191b2f97f4cadb19a15b4575977aa2e3 Signed-off-by: Monendra Singh Kushwaha --- src/plugins/dev_octeon/rx_node.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/plugins/dev_octeon/rx_node.c b/src/plugins/dev_octeon/rx_node.c index f70c07642a..3651952db5 100644 --- a/src/plugins/dev_octeon/rx_node.c +++ b/src/plugins/dev_octeon/rx_node.c @@ -107,9 +107,13 @@ oct_rx_batch (vlib_main_t *vm, oct_rx_node_ctx_t *ctx, u32 b0_err_flags = 0, b1_err_flags = 0; u32 b2_err_flags = 0, b3_err_flags = 0; u32 n_left, err_flags = 0; + u32 err_flag_x4, err_flag; oct_nix_rx_cqe_desc_t *d = ctx->next_desc; vlib_buffer_t *b[4]; + bt.flags |= + (VNET_BUFFER_F_L4_CHECKSUM_COMPUTED | VNET_BUFFER_F_L4_CHECKSUM_CORRECT); + for (n_left = n; n_left >= 8; d += 4, n_left -= 4, ctx->to_next += 4) { u32 segs = 0; @@ -153,7 +157,24 @@ oct_rx_batch (vlib_main_t *vm, oct_rx_node_ctx_t *ctx, b2_err_flags = (d[2].parse.w[0] >> 20) & 0xFFF; b3_err_flags = (d[3].parse.w[0] >> 20) & 0xFFF; - err_flags |= b0_err_flags | b1_err_flags | b2_err_flags | b3_err_flags; + err_flag_x4 = b0_err_flags | b1_err_flags | b2_err_flags | b3_err_flags; + + if (PREDICT_FALSE (err_flag_x4)) + { + if (b0_err_flags) + b[0]->flags &= ~(VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + if (b1_err_flags) + b[1]->flags &= ~(VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + if (b2_err_flags) + b[2]->flags &= ~(VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + if (b3_err_flags) + b[3]->flags &= ~(VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + err_flags |= err_flag_x4; + } } for (; n_left; d += 1, n_left -= 1, ctx->to_next += 1) @@ -167,7 +188,13 @@ oct_rx_batch (vlib_main_t *vm, oct_rx_node_ctx_t *ctx, if (d[0].sg0.segs > 1) oct_rx_attach_tail (vm, ctx, b[0], d + 0); - err_flags |= ((d[0].parse.w[0] >> 20) & 0xFFF); + err_flag = ((d[0].parse.w[0] >> 20) & 0xFFF); + if (PREDICT_FALSE (err_flag)) + { + b[0]->flags &= ~(VNET_BUFFER_F_L4_CHECKSUM_CORRECT | + VNET_BUFFER_F_L4_CHECKSUM_COMPUTED); + err_flags |= err_flag; + } } plt_write64 ((crq->cq.wdata | n), crq->cq.door); From 870c029bd24c4a9fd96350a3fc84f162b29ca108 Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Thu, 14 Aug 2025 00:02:34 +0200 Subject: [PATCH 249/313] vnet: add SFF8472 and SFF8636 diagnostics Using device-class.eeprom_read_function, extract the type of EEPROM, and create a parser for SFF8472 (SFP+/SFP/SFP28) and SFF8636 (QSFP+/QSFP28/QSFP-DD) to show module diagnostics. When available, read EEPROM page A2h and report on DDM values: show int transceiver eeprom show int transceiver module [verbose] show int transceiver diag [verbose] Type: improvement Change-Id: Iae41b9753f31bc1a8d32b2c42d396cd743864147 Signed-off-by: pim@ipng.nl --- src/vnet/CMakeLists.txt | 4 + src/vnet/ethernet/sfp.c | 173 ++++++++++++++++++++++- src/vnet/ethernet/sfp.h | 88 ++++++++++-- src/vnet/ethernet/sfp_sff8472.c | 242 ++++++++++++++++++++++++++++++++ src/vnet/ethernet/sfp_sff8472.h | 86 ++++++++++++ src/vnet/ethernet/sfp_sff8636.c | 176 +++++++++++++++++++++++ src/vnet/ethernet/sfp_sff8636.h | 125 +++++++++++++++++ src/vnet/interface.c | 18 +++ src/vnet/interface.h | 19 ++- src/vnet/interface_cli.c | 8 +- 10 files changed, 919 insertions(+), 20 deletions(-) create mode 100644 src/vnet/ethernet/sfp_sff8472.c create mode 100644 src/vnet/ethernet/sfp_sff8472.h create mode 100644 src/vnet/ethernet/sfp_sff8636.c create mode 100644 src/vnet/ethernet/sfp_sff8636.h diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt index e3abe93da2..03dbe0370d 100644 --- a/src/vnet/CMakeLists.txt +++ b/src/vnet/CMakeLists.txt @@ -160,6 +160,8 @@ list(APPEND VNET_SOURCES ethernet/node.c ethernet/pg.c ethernet/sfp.c + ethernet/sfp_sff8472.c + ethernet/sfp_sff8636.c ethernet/p2p_ethernet.c ethernet/p2p_ethernet_input.c ethernet/p2p_ethernet_api.c @@ -177,6 +179,8 @@ list(APPEND VNET_HEADERS ethernet/packet.h ethernet/types.def ethernet/sfp.h + ethernet/sfp_sff8472.h + ethernet/sfp_sff8636.h ethernet/p2p_ethernet.h ethernet/arp_packet.h ) diff --git a/src/vnet/ethernet/sfp.c b/src/vnet/ethernet/sfp.c index 182fdbf1d5..44de49b096 100644 --- a/src/vnet/ethernet/sfp.c +++ b/src/vnet/ethernet/sfp.c @@ -14,6 +14,8 @@ */ #include +#include +#include static u8 * format_space_terminated (u8 * s, va_list * args) @@ -28,8 +30,8 @@ format_space_terminated (u8 * s, va_list * args) return s; } -static u8 * -format_sfp_id (u8 * s, va_list * args) +u8 * +format_sfp_id (u8 *s, va_list *args) { u32 id = va_arg (*args, u32); char *t = 0; @@ -44,6 +46,42 @@ format_sfp_id (u8 * s, va_list * args) return format (s, "%s", t); } +u8 * +format_sfp_connector (u8 *s, va_list *args) +{ + u32 connector = va_arg (*args, u32); + char *t = 0; + switch (connector) + { +#define _(v, str) \ + case v: \ + t = str; \ + break; + foreach_sfp_connector +#undef _ + default : return format (s, "unknown 0x%x", connector); + } + return format (s, "%s", t); +} + +u8 * +format_sfp_encoding (u8 *s, va_list *args) +{ + u32 encoding = va_arg (*args, u32); + char *t = 0; + switch (encoding) + { +#define _(v, str) \ + case v: \ + t = str; \ + break; + foreach_sfp_encoding +#undef _ + default : return format (s, "unknown 0x%x", encoding); + } + return format (s, "%s", t); +} + static u8 * format_sfp_compatibility (u8 * s, va_list * args) { @@ -60,8 +98,8 @@ format_sfp_compatibility (u8 * s, va_list * args) return format (s, "%s", t); } -u32 -sfp_is_comatible (sfp_eeprom_t * e, sfp_compatibility_t c) +static u32 +sfp_is_compatible (sfp_eeprom_t *e, sfp_compatibility_t c) { static struct { @@ -88,7 +126,7 @@ format_sfp_eeprom (u8 * s, va_list * args) s = format (s, "compatibility:"); for (i = 0; i < SFP_N_COMPATIBILITY; i++) - if (sfp_is_comatible (e, i)) + if (sfp_is_compatible (e, i)) s = format (s, " %U", format_sfp_compatibility, i); s = format (s, "\n%Uvendor: %U, part %U", @@ -111,6 +149,131 @@ format_sfp_eeprom (u8 * s, va_list * args) return s; } +void +sfp_eeprom_decode_base (vlib_main_t *vm, sfp_eeprom_t *se, u8 is_terse, + vnet_interface_eeprom_type_t eeprom_type) +{ + u8 vendor_name[17] = { 0 }; + u8 vendor_pn[17] = { 0 }; + u8 vendor_rev[5] = { 0 }; + u8 vendor_sn[17] = { 0 }; + u8 date_code[9] = { 0 }; + u16 wavelength; + + vlib_cli_output (vm, " Module Base Information:"); + /* Vendor information */ + clib_memcpy (vendor_name, se->vendor_name, 16); + /* Trim trailing spaces */ + for (int i = 15; i >= 0 && vendor_name[i] == ' '; i--) + vendor_name[i] = '\0'; + vlib_cli_output (vm, " Vendor Name: %s", vendor_name); + + vlib_cli_output (vm, " Vendor OUI: %02x:%02x:%02x", se->vendor_oui[0], + se->vendor_oui[1], se->vendor_oui[2]); + + clib_memcpy (vendor_pn, se->vendor_part_number, 16); + /* Trim trailing spaces */ + for (int i = 15; i >= 0 && vendor_pn[i] == ' '; i--) + vendor_pn[i] = '\0'; + vlib_cli_output (vm, " Vendor Part Number: %s", vendor_pn); + + clib_memcpy (vendor_sn, se->vendor_serial_number, 16); + /* Trim trailing spaces */ + for (int i = 15; i >= 0 && vendor_sn[i] == ' '; i--) + vendor_sn[i] = '\0'; + vlib_cli_output (vm, " Vendor Serial Number: %s", vendor_sn); + + if (is_terse) + return; + + vlib_cli_output (vm, " Identifier: 0x%02x (%U)", se->id, format_sfp_id, + se->id); + vlib_cli_output (vm, " Extended Identifier: 0x%02x", se->extended_id); + vlib_cli_output (vm, " Connector: 0x%02x (%U)", se->connector_type, + format_sfp_connector, se->connector_type); + vlib_cli_output (vm, " Encoding: 0x%02x (%U)", se->encoding, + format_sfp_encoding, se->encoding); + vlib_cli_output (vm, " Nominal Bit Rate: %u00 Mbps", + se->nominal_bit_rate_100mbits_per_sec); + + /* Length information */ + if (se->length[0]) + vlib_cli_output (vm, " Length (SMF): %u km", se->length[0]); + if (se->length[1]) + vlib_cli_output (vm, " Length (SMF): %u00 m", se->length[1]); + if (se->length[2]) + vlib_cli_output (vm, " Length (OM2 50um): %u0 m", se->length[2]); + if (se->length[3]) + vlib_cli_output (vm, " Length (OM1 62.5um): %u0 m", se->length[3]); + if (se->length[4]) + vlib_cli_output (vm, " Length (Copper/OM3): %u m", se->length[4]); + + /* NOTE(pim): SFF8472 specifies a 4 byte vendor revision followed by a 2-byte + * wavelength_or_att. However SFF8636 specifies a 2 byte vendor revision + * followed by a 2 byte wavelength_or_att, followed by a 2 byte + * wavelength_tolerance */ + if (eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8472) + { + clib_memcpy (vendor_rev, se->vendor_revision, 4); + /* Trim trailing spaces */ + for (int i = 3; i >= 0 && vendor_rev[i] == ' '; i--) + vendor_rev[i] = '\0'; + vlib_cli_output (vm, " Vendor Revision: %s", vendor_rev); + + wavelength = clib_net_to_host_u16 (se->wavelength_or_att); + if (wavelength) + vlib_cli_output (vm, " Wavelength: %u nm", wavelength); + } + if (eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8636 || + eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8436) + { + + clib_memcpy (vendor_rev, se->vendor_revision, 2); + /* Trim trailing spaces */ + for (int i = 1; i >= 0 && vendor_rev[i] == ' '; i--) + vendor_rev[i] = '\0'; + vlib_cli_output (vm, " Vendor Revision: %s", vendor_rev); + + u16 sff8636_wavelength_or_att = + (se->vendor_revision[2] << 8) + se->vendor_revision[3]; + f64 wl_nm = ((f64) sff8636_wavelength_or_att) * 0.05; + vlib_cli_output (vm, " Wavelength: %.3f nm", wl_nm); + } + + clib_memcpy (date_code, se->vendor_date_code, 8); + vlib_cli_output (vm, " Date Code: %.8s", date_code); +} + +void +sfp_eeprom_module (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + sfp_eeprom_t *se = (sfp_eeprom_t *) eeprom->eeprom_raw; + if (eeprom->eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8636 || + eeprom->eeprom_type == VNET_INTERFACE_EEPROM_TYPE_SFF8436) + { + se = (sfp_eeprom_t *) (eeprom->eeprom_raw + 0x80); + } + + return sfp_eeprom_decode_base (vm, se, is_terse, eeprom->eeprom_type); +} + +void +sfp_eeprom_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + switch (eeprom->eeprom_type) + { + case VNET_INTERFACE_EEPROM_TYPE_SFF8472: + return sff8472_decode_diagnostics (vm, eeprom, is_terse); + case VNET_INTERFACE_EEPROM_TYPE_SFF8436: + case VNET_INTERFACE_EEPROM_TYPE_SFF8636: + return sff8636_decode_diagnostics (vm, eeprom, is_terse); + default: + vlib_cli_output (vm, " Module Diagnostics: not availalbe"); + } +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vnet/ethernet/sfp.h b/src/vnet/ethernet/sfp.h index f4c62aaa0c..b19cba3171 100644 --- a/src/vnet/ethernet/sfp.h +++ b/src/vnet/ethernet/sfp.h @@ -17,6 +17,7 @@ #define included_vnet_optics_sfp_h #include +#include #define foreach_sfp_id \ _ (UNKNOWN, "unknown") \ @@ -66,18 +67,24 @@ typedef struct u8 ext_module_codes; u8 vendor_oui[3]; u8 vendor_part_number[16]; - u8 vendor_revision[2]; - /* 16 bit value network byte order. */ - u8 wavelength_or_att[2]; - u8 wavelength_tolerance_or_att[2]; - u8 max_case_temp; - u8 cc_base; - - u8 link_codes; - u8 options[3]; + + /* NOTE: SFF8472 defines vendor revision as 4 bytes, followed by 2 bytes of + * wavelength_or_att SFF8636 defines vendor revision as 2 bytes, followed by + * u16 wavelength_or_att, then u16 wavelength_tolerance + */ + u8 vendor_revision[4]; /* SFF8636 has wavelength in vendor_revision[2+3] */ + u16 wavelength_or_att; /* SFF8472 has wavelength here; SFF8636 has + wavelength_tolerance here */ + + u8 reserved_62; /* Byte 62: Reserved */ + u8 cc_base; /* Byte 63: checksum for first 64 bytes */ + u8 option_values[2]; + u8 signalling_rate_max; + u8 signalling_rate_min; u8 vendor_serial_number[16]; u8 vendor_date_code[8]; - u8 reserved92[3]; + u8 diag_monitoring_type; /* Byte 92 */ + u8 reserved93[2]; u8 checksum_63_to_94; u8 vendor_specific[32]; u8 reserved128[384]; @@ -96,6 +103,16 @@ sfp_eeprom_is_valid (sfp_eeprom_t * e) return sum == e->cc_base; } +/* Show the EEPROM module information */ +void sfp_eeprom_module (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse); +void sfp_eeprom_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse); + +/* Base SFP EEPROM decoding function */ +void sfp_eeprom_decode_base (vlib_main_t *vm, sfp_eeprom_t *se, u8 is_terse, + vnet_interface_eeprom_type_t eeprom_type); + /* _ (byte_index, bit_index, name) */ #define foreach_sfp_compatibility \ _ (0, 0, 40g_active_cable) \ @@ -128,9 +145,58 @@ typedef enum SFP_N_COMPATIBILITY, } sfp_compatibility_t; -u32 sfp_is_comatible (sfp_eeprom_t * e, sfp_compatibility_t c); +#define foreach_sfp_encoding \ + _ (0x01, "8B/10B") \ + _ (0x02, "4B/5B") \ + _ (0x03, "NRZ") \ + _ (0x04, "4B/5B (FC-100)") \ + _ (0x05, "Manchester") \ + _ (0x06, "64B/66B") \ + _ (0x07, "256B/257B") \ + _ (0x08, "PAM4") + +typedef enum +{ +#define _(v, s) SFP_ENCODING_##v = v, + foreach_sfp_encoding +#undef _ +} sfp_encoding_t; + +#define foreach_sfp_connector \ + _ (0x01, "SC") \ + _ (0x02, "Fibre Channel Style 1 copper") \ + _ (0x03, "Fibre Channel Style 2 copper") \ + _ (0x04, "BNC/TNC") \ + _ (0x05, "Fibre Channel coaxial") \ + _ (0x06, "Fiber Jack") \ + _ (0x07, "LC") \ + _ (0x08, "MT-RJ") \ + _ (0x09, "MU") \ + _ (0x0A, "SG") \ + _ (0x0B, "Optical pigtail") \ + _ (0x0C, "MPO 1x12 Parallel Optic") \ + _ (0x0D, "MPO 2x16 Parallel Optic") \ + _ (0x20, "HSSDC II") \ + _ (0x21, "Copper pigtail") \ + _ (0x22, "RJ45") \ + _ (0x23, "No separable connector") \ + _ (0x24, "MXC 2x16") \ + _ (0x25, "CS optical connector") \ + _ (0x26, "SN optical connector") \ + _ (0x27, "MPO 2x12 Parallel Optic") \ + _ (0x28, "MPO 1x16 Parallel Optic") + +typedef enum +{ +#define _(v, s) SFP_CONNECTOR_##v = v, + foreach_sfp_connector +#undef _ +} sfp_connector_t; format_function_t format_sfp_eeprom; +format_function_t format_sfp_id; +format_function_t format_sfp_encoding; +format_function_t format_sfp_connector; #endif /* included_vnet_optics_sfp_h */ diff --git a/src/vnet/ethernet/sfp_sff8472.c b/src/vnet/ethernet/sfp_sff8472.c new file mode 100644 index 0000000000..a4467162ad --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8472.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed 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 +#include +#include +#include + +static f64 +sff8472_convert_temperature (u16 raw_temp) +{ + i16 temp = (i16) raw_temp; + return (f64) temp / 256.0; +} + +static f64 +sff8472_convert_voltage (u16 raw_voltage) +{ + return (f64) raw_voltage / 10000.0; +} + +static f64 +sff8472_convert_current (u16 raw_current) +{ + return (f64) raw_current * 2.0 / 1000.0; +} + +static f64 +sff8472_convert_power (u16 raw_power) +{ + return (f64) raw_power / 10000.0; +} + +static f64 +sff8472_mw_to_dbm (f64 power_mw) +{ + if (power_mw <= 0.0) + return -40.0; /* Use -40 dBm for zero/negative power to avoid log(0) */ + return 10.0 * log10 (power_mw); +} + +static f64 +sff8472_ieee754_to_f64 (u32 ieee754_be) +{ + union + { + u32 u; + f32 f; + } converter; + converter.u = clib_net_to_host_u32 (ieee754_be); + return (f64) converter.f; +} + +static void +sff8472_apply_calibration (sff8472_diag_t *diag, f64 *temp, f64 *vcc, + f64 *tx_bias, f64 *tx_power, f64 *rx_power) +{ + f64 temp_slope, temp_offset; + f64 vcc_slope, vcc_offset; + f64 bias_slope, bias_offset; + f64 txpwr_slope, txpwr_offset; + f64 rx_raw; + f64 rxpwr_cal[5]; + int i; + + /* Extract calibration constants */ + temp_slope = (f64) clib_net_to_host_u16 (diag->temp_slope) / 256.0; + temp_offset = (f64) (i16) clib_net_to_host_u16 (diag->temp_offset); + *temp = (*temp * temp_slope) + temp_offset; + + vcc_slope = (f64) clib_net_to_host_u16 (diag->voltage_slope) / 256.0; + vcc_offset = (f64) (i16) clib_net_to_host_u16 (diag->voltage_offset); + *vcc = (*vcc * vcc_slope) + vcc_offset; + + bias_slope = (f64) clib_net_to_host_u16 (diag->tx_bias_slope) / 256.0; + bias_offset = (f64) (i16) clib_net_to_host_u16 (diag->tx_bias_offset); + *tx_bias = (*tx_bias * bias_slope) + bias_offset; + + txpwr_slope = (f64) clib_net_to_host_u16 (diag->tx_power_slope) / 256.0; + txpwr_offset = (f64) (i16) clib_net_to_host_u16 (diag->tx_power_offset); + *tx_power = (*tx_power * txpwr_slope) + txpwr_offset; + + /* Apply polynomial calibration for RX Power + * SFF-8472 section 9.3 External Calibration + */ + for (i = 0; i < 5; i++) + { + rxpwr_cal[i] = sff8472_ieee754_to_f64 (diag->rx_power_cal[i]); + } + + rx_raw = *rx_power; + *rx_power = rxpwr_cal[0] + (rx_raw * rxpwr_cal[1]) + + (rx_raw * rx_raw * rxpwr_cal[2]) + + (rx_raw * rx_raw * rx_raw * rxpwr_cal[3]) + + (rx_raw * rx_raw * rx_raw * rx_raw * rxpwr_cal[4]); +} + +void +sff8472_decode_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + f64 temp, vcc, tx_bias, tx_power, rx_power; + f64 temp_high_alarm, temp_low_alarm, temp_high_warn, temp_low_warn; + f64 vcc_high_alarm, vcc_low_alarm, vcc_high_warn, vcc_low_warn; + f64 bias_high_alarm, bias_low_alarm, bias_high_warn, bias_low_warn; + f64 tx_power_high_alarm, tx_power_low_alarm, tx_power_high_warn, + tx_power_low_warn; + f64 rx_power_high_alarm, rx_power_low_alarm, rx_power_high_warn, + rx_power_low_warn; + + vlib_cli_output (vm, " Module Diagnostics:"); + if (eeprom->eeprom_len <= 256) + { + vlib_cli_output (vm, " Not supported (no A2h data)"); + return; + } + + u8 *eeprom_data_a2 = eeprom->eeprom_raw; + /* Check if we have A0h+A2h (512 bytes) */ + if (eeprom->eeprom_len >= 512) + { + eeprom_data_a2 = eeprom->eeprom_raw + 256; /* A2 starts at byte 256 */ + } + + sff8472_diag_t *diag = (sff8472_diag_t *) eeprom_data_a2; + temp = + sff8472_convert_temperature (clib_net_to_host_u16 (diag->temperature)); + vcc = sff8472_convert_voltage (clib_net_to_host_u16 (diag->vcc)); + tx_bias = sff8472_convert_current (clib_net_to_host_u16 (diag->tx_bias)); + tx_power = sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power)); + rx_power = sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power)); + + /* Check if external calibration is required (A0 page byte 92, bit 4) */ + if (eeprom->eeprom_len >= 512 && (eeprom->eeprom_raw[92] & 0x10)) + { + sff8472_apply_calibration (diag, &temp, &vcc, &tx_bias, &tx_power, + &rx_power); + } + + vlib_cli_output (vm, " Current Values:"); + vlib_cli_output (vm, " Temperature: %.2f °C", temp); + vlib_cli_output (vm, " Supply Voltage: %.4f V", vcc); + vlib_cli_output (vm, " TX Bias Current: %.2f mA", tx_bias); + vlib_cli_output (vm, " TX Average Power: %.4f mW (%.2f dBm)", tx_power, + sff8472_mw_to_dbm (tx_power)); + vlib_cli_output (vm, " RX Average Power: %.4f mW (%.2f dBm)", rx_power, + sff8472_mw_to_dbm (rx_power)); + + if (is_terse || eeprom->eeprom_len <= 256 + sizeof (sff8472_diag_t)) + { + return; + } + + temp_high_alarm = + sff8472_convert_temperature (clib_net_to_host_u16 (diag->temp_high_alarm)); + temp_low_alarm = + sff8472_convert_temperature (clib_net_to_host_u16 (diag->temp_low_alarm)); + temp_high_warn = sff8472_convert_temperature ( + clib_net_to_host_u16 (diag->temp_high_warning)); + temp_low_warn = sff8472_convert_temperature ( + clib_net_to_host_u16 (diag->temp_low_warning)); + + vcc_high_alarm = + sff8472_convert_voltage (clib_net_to_host_u16 (diag->voltage_high_alarm)); + vcc_low_alarm = + sff8472_convert_voltage (clib_net_to_host_u16 (diag->voltage_low_alarm)); + vcc_high_warn = sff8472_convert_voltage ( + clib_net_to_host_u16 (diag->voltage_high_warning)); + vcc_low_warn = + sff8472_convert_voltage (clib_net_to_host_u16 (diag->voltage_low_warning)); + + bias_high_alarm = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_high_alarm)); + bias_low_alarm = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_low_alarm)); + bias_high_warn = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_high_warning)); + bias_low_warn = + sff8472_convert_current (clib_net_to_host_u16 (diag->bias_low_warning)); + + tx_power_high_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_high_alarm)); + tx_power_low_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_low_alarm)); + tx_power_high_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_high_warning)); + tx_power_low_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->tx_power_low_warning)); + + rx_power_high_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_high_alarm)); + rx_power_low_alarm = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_low_alarm)); + rx_power_high_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_high_warning)); + rx_power_low_warn = + sff8472_convert_power (clib_net_to_host_u16 (diag->rx_power_low_warning)); + + vlib_cli_output (vm, " Alarm Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_alarm, temp_low_alarm); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_alarm, vcc_low_alarm); + vlib_cli_output (vm, " Bias Current High: %.2f mA, Low: %.2f mA", + bias_high_alarm, bias_low_alarm); + vlib_cli_output ( + vm, " TX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + tx_power_high_alarm, sff8472_mw_to_dbm (tx_power_high_alarm), + tx_power_low_alarm, sff8472_mw_to_dbm (tx_power_low_alarm)); + vlib_cli_output ( + vm, " RX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + rx_power_high_alarm, sff8472_mw_to_dbm (rx_power_high_alarm), + rx_power_low_alarm, sff8472_mw_to_dbm (rx_power_low_alarm)); + + vlib_cli_output (vm, " Warning Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_warn, temp_low_warn); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_warn, vcc_low_warn); + vlib_cli_output (vm, " Bias Current High: %.2f mA, Low: %.2f mA", + bias_high_warn, bias_low_warn); + vlib_cli_output ( + vm, " TX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + tx_power_high_warn, sff8472_mw_to_dbm (tx_power_high_warn), + tx_power_low_warn, sff8472_mw_to_dbm (tx_power_low_warn)); + vlib_cli_output ( + vm, " RX Power High: %.4f mW (%.2f dBm), Low: %.4f mW (%.2f dBm)", + rx_power_high_warn, sff8472_mw_to_dbm (rx_power_high_warn), + rx_power_low_warn, sff8472_mw_to_dbm (rx_power_low_warn)); +} diff --git a/src/vnet/ethernet/sfp_sff8472.h b/src/vnet/ethernet/sfp_sff8472.h new file mode 100644 index 0000000000..e54005a3cf --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8472.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed 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. + */ + +#ifndef __included_ethernet_sff8472_h__ +#define __included_ethernet_sff8472_h__ + +#include +#include +#include + +/* SFF-8472 A2 page - Diagnostic fields */ +typedef struct +{ + /* Alarm and warning thresholds (bytes 0-55) */ + u16 temp_high_alarm; /* 0-1: Temperature high alarm */ + u16 temp_low_alarm; /* 2-3: Temperature low alarm */ + u16 temp_high_warning; /* 4-5: Temperature high warning */ + u16 temp_low_warning; /* 6-7: Temperature low warning */ + u16 voltage_high_alarm; /* 8-9: Voltage high alarm */ + u16 voltage_low_alarm; /* 10-11: Voltage low alarm */ + u16 voltage_high_warning; /* 12-13: Voltage high warning */ + u16 voltage_low_warning; /* 14-15: Voltage low warning */ + u16 bias_high_alarm; /* 16-17: Bias high alarm */ + u16 bias_low_alarm; /* 18-19: Bias low alarm */ + u16 bias_high_warning; /* 20-21: Bias high warning */ + u16 bias_low_warning; /* 22-23: Bias low warning */ + u16 tx_power_high_alarm; /* 24-25: TX power high alarm */ + u16 tx_power_low_alarm; /* 26-27: TX power low alarm */ + u16 tx_power_high_warning; /* 28-29: TX power high warning */ + u16 tx_power_low_warning; /* 30-31: TX power low warning */ + u16 rx_power_high_alarm; /* 32-33: RX power high alarm */ + u16 rx_power_low_alarm; /* 34-35: RX power low alarm */ + u16 rx_power_high_warning; /* 36-37: RX power high warning */ + u16 rx_power_low_warning; /* 38-39: RX power low warning */ + u8 reserved_40_55[16]; /* 40-55: Reserved/Other fields */ + /* Calibration constants for external calibration (bytes 56-95) */ + u32 rx_power_cal[5]; /* 56-75: RX power calibration coefficients (IEEE 754 + float) */ + u16 tx_bias_slope; /* 76-77: TX bias slope calibration */ + u16 tx_bias_offset; /* 78-79: TX bias offset calibration */ + u16 tx_power_slope; /* 80-81: TX power slope calibration */ + u16 tx_power_offset; /* 82-83: TX power offset calibration */ + u16 temp_slope; /* 84-85: Temperature slope calibration */ + u16 temp_offset; /* 86-87: Temperature offset calibration */ + u16 voltage_slope; /* 88-89: Voltage slope calibration */ + u16 voltage_offset; /* 90-91: Voltage offset calibration */ + u8 reserved_92_95[4]; /* 92-95: Reserved */ + /* Real-time diagnostic values (bytes 96-105) */ + u16 temperature; /* 96-97: Temperature */ + u16 vcc; /* 98-99: Supply voltage */ + u16 tx_bias; /* 100-101: TX bias current */ + u16 tx_power; /* 102-103: TX average optical power */ + u16 rx_power; /* 104-105: RX average optical power */ + u8 reserved_106_109[4]; /* 106-109: Reserved */ + u8 status_control[2]; /* 110-111: Status/Control */ + u8 alarm1; /* 112: Temp, VCC, TX Bias, TX Power alarms */ + u8 alarm2; /* 113: Rx, Laser Temp, TEC current alarms */ + u8 tx_input_equalization; /* 114: Tx Input equalization HIGH / LOW */ + u8 rx_output_emphasis; /* 115: Rx output emphasis HIGH / LOW */ + u8 warning1; /* 116: Temp, VCC, TX Bias, TX power warnings */ + u8 warning2; /* 117: Rx, Laser Temp, TEC current warning */ + u8 emc_status[2]; /* 118-119: Extended Module Control status */ + u8 reserved_120_126[7]; /* 120-126: Vendor specific Locations */ + u8 page_select; /* 127: Page Select */ +} sff8472_diag_t; + +STATIC_ASSERT (sizeof (sff8472_diag_t) == 128, + "sff8472_diag_t must be 128 bytes"); + +/* Function declarations */ +void sff8472_decode_diagnostics (vlib_main_t *vm, + vnet_interface_eeprom_t *eeprom, u8 is_terse); + +#endif /* __included_ethernet_sff8472_h__ */ diff --git a/src/vnet/ethernet/sfp_sff8636.c b/src/vnet/ethernet/sfp_sff8636.c new file mode 100644 index 0000000000..7c13dd99ed --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8636.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed 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 +#include +#include +#include +#include + +static f64 +sff8636_convert_temperature (u16 raw_temp) +{ + i16 temp = (i16) raw_temp; + return (f64) temp / 256.0; +} + +static f64 +sff8636_convert_voltage (u16 raw_voltage) +{ + /* SFF-8636: 100 µV per LSB */ + return (f64) raw_voltage / 10000.0; +} + +static f64 +sff8636_convert_current (u16 raw_current) +{ + /* SFF-8636: 2 µA per LSB */ + return (f64) raw_current * 2.0 / 1000.0; +} + +static f64 +sff8636_convert_power (u16 raw_power) +{ + /* SFF-8636: 0.1 µW per LSB */ + return (f64) raw_power * 0.1 / 1000.0; +} + +static f64 +sff8636_mw_to_dbm (f64 power_mw) +{ + if (power_mw <= 0.0) + return -40.0; /* Use -40 dBm for zero/negative power to avoid log(0) */ + return 10.0 * log10 (power_mw); +} + +void +sff8636_decode_diagnostics (vlib_main_t *vm, vnet_interface_eeprom_t *eeprom, + u8 is_terse) +{ + f64 temp, vcc; + f64 temp_high_alarm, temp_low_alarm, temp_high_warn, temp_low_warn; + f64 vcc_high_alarm, vcc_low_alarm, vcc_high_warn, vcc_low_warn; + int i; + + vlib_cli_output (vm, " Module Diagnostics:"); + + if (eeprom->eeprom_len < sizeof (sff8636_eeprom_t)) + { + vlib_cli_output (vm, " Not supported (insufficient data)"); + return; + } + + sff8636_eeprom_t *diag = (sff8636_eeprom_t *) eeprom->eeprom_raw; + temp = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temperature)); + vcc = sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc)); + + vlib_cli_output (vm, " Current Values:"); + vlib_cli_output (vm, " Temperature: %.2f °C", temp); + vlib_cli_output (vm, " Supply Voltage: %.4f V", vcc); + + /* Per-lane values */ + for (i = 0; i < 4; i++) + { + f64 tx_bias, tx_power, rx_power; + tx_bias = + sff8636_convert_current (clib_net_to_host_u16 (diag->tx_bias[i])); + rx_power = + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power[i])); + tx_power = + sff8636_convert_power (clib_net_to_host_u16 (diag->tx_power[i])); + + vlib_cli_output (vm, " Lane %d:", i + 1); + vlib_cli_output (vm, " TX Bias Current: %.2f mA", tx_bias); + vlib_cli_output (vm, " TX Average Power: %.4f mW (%.2f dBm)", + tx_power, sff8636_mw_to_dbm (tx_power)); + vlib_cli_output (vm, " RX Average Power: %.4f mW (%.2f dBm)", + rx_power, sff8636_mw_to_dbm (rx_power)); + } + + if (is_terse) + { + return; + } + + /* Temperature thresholds at bytes 512-519 */ + temp_high_alarm = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_high_alarm)); + temp_low_alarm = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_low_alarm)); + temp_high_warn = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_high_warn)); + temp_low_warn = + sff8636_convert_temperature (clib_net_to_host_u16 (diag->temp_low_warn)); + + /* Voltage thresholds at bytes 520-527 */ + vcc_high_alarm = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_high_alarm)); + vcc_low_alarm = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_low_alarm)); + vcc_high_warn = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_high_warn)); + vcc_low_warn = + sff8636_convert_voltage (clib_net_to_host_u16 (diag->vcc_low_warn)); + + vlib_cli_output (vm, ""); + vlib_cli_output (vm, " Alarm Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_alarm, temp_low_alarm); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_alarm, vcc_low_alarm); + + vlib_cli_output ( + vm, " Bias Current High: %.2f mA, Low: %.2f mA", + sff8636_convert_current (clib_net_to_host_u16 (diag->tx_bias_high_alarm)), + sff8636_convert_current (clib_net_to_host_u16 (diag->tx_bias_low_alarm))); + vlib_cli_output ( + vm, + " TX Power High: %.4f mW (%.2f dBm), Low: " + "%.4f mW (%.2f dBm)", + sff8636_convert_power (clib_net_to_host_u16 (diag->tx_power_high_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->tx_power_high_alarm))), + sff8636_convert_power (clib_net_to_host_u16 (diag->tx_power_low_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->tx_power_low_alarm)))); + vlib_cli_output ( + vm, + " RX Power High: %.4f mW (%.2f dBm), Low: " + "%.4f mW (%.2f dBm)", + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_high_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->rx_power_high_alarm))), + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_low_alarm)), + sff8636_mw_to_dbm (sff8636_convert_power ( + clib_net_to_host_u16 (diag->rx_power_low_alarm)))); + + vlib_cli_output (vm, ""); + vlib_cli_output (vm, " Warning Thresholds:"); + vlib_cli_output (vm, " Temperature High: %.2f °C, Low: %.2f °C", + temp_high_warn, temp_low_warn); + vlib_cli_output (vm, " Voltage High: %.4f V, Low: %.4f V", + vcc_high_warn, vcc_low_warn); + vlib_cli_output ( + vm, + " RX Power High: %.4f mW (%.2f dBm), Low: " + "%.4f mW (%.2f dBm)", + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_high_warn)), + sff8636_mw_to_dbm ( + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_high_warn))), + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_low_warn)), + sff8636_mw_to_dbm ( + sff8636_convert_power (clib_net_to_host_u16 (diag->rx_power_low_warn)))); +} diff --git a/src/vnet/ethernet/sfp_sff8636.h b/src/vnet/ethernet/sfp_sff8636.h new file mode 100644 index 0000000000..9adf90df56 --- /dev/null +++ b/src/vnet/ethernet/sfp_sff8636.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025 Cisco and/or its affiliates. + * Licensed 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. + */ + +#ifndef __included_ethernet_sff8636_h__ +#define __included_ethernet_sff8636_h__ + +#include +#include + +/* SFF-8636 is a 640 byte EEPROM. + * Any SFF-8436-compliant module will generally respond correctly to an + * SFF-8636 reader, because the lower and upper Page 00h structures are largely + * preserved. + */ + +typedef struct +{ + u8 identifier; // Byte 0 + u8 status[2]; // Bytes 1-2 (see Table 6-2) + u8 interrupt_flags[19]; // Bytes 3-21 (Tables 6-4, 6-5, 6-6) + + // ---------------- Device Free Side Monitors ---------------- + u16 temperature; // Bytes 22-23 (1/256 °C per LSB) + u8 reserved_fsm[2]; // Bytes 24-25 (vendor-specific / reserved) + u16 vcc; // Bytes 26-27 (100 µV per LSB) + u8 reserved_fsm2[6]; // Bytes 28-33 (vendor-specific / reserved) + + // ---------------- Per-lane channel monitors ---------------- + // Lane 1–4, each has 4 bytes: TX bias, TX power, RX power, reserved + u16 rx_power[4]; // Bytes 34-41 (2 bytes per lane, 0.1 µW scaling) + u16 tx_bias[4]; // Bytes 42-49 (2 bytes per lane, µA scaling) + u16 tx_power[4]; // Bytes 50-57 (2 bytes per lane, 0.1 µW scaling) + u16 reserved_lane[4]; // Bytes 58-65 (reserved/future use) + u8 reserved_66_85[20]; // Bytes 66-85 + u8 control[13]; // Bytes 86-98 (Table 6-9; includes App Select bytes per + // Table 6-12) + u8 reserved_99; // Byte 99 + u8 hw_int_mask[7]; // Bytes 100-106 (Table 6-14) + u8 reserved_107; // Byte 107 + u8 device_props[7]; // Bytes 108-114 (Table 6-15; incl. PCIe use at 111-112 + // per Fig 6-1) + u8 reserved_115_118[4]; // Bytes 115-118 + u8 password_change[4]; // Bytes 119-122 (optional, write-only in device) + u8 password_entry[4]; // Bytes 123-126 (optional, write-only in device) + u8 page_select; // Byte 127 (Page Select) + + // 128–255 (Base ID portion) + u8 base_id[128]; // this is the standard sfp_eeprom_t at offset 0x80 + + // 256-511 (page01, page02) + u8 reserved_page01[128]; // 256-383 + u8 reserved_page02[128]; // 384-511 + + // 512–559 : Free-Side Device Thresholds and Channel Thresholds + // Typically: temp high alarm/warn, low warn/alarm; Vcc high/low thresholds, + // etc. + u16 temp_high_alarm; // 512-513 (0.00390625 °C/LSB) + u16 temp_low_alarm; // 514-515 + u16 temp_high_warn; // 516-517 + u16 temp_low_warn; // 518-519 + u8 reserved_fsdct[8]; // 520-527 + + u16 vcc_high_alarm; // 528-529 (100 µV/LSB) + u16 vcc_low_alarm; // 530-531 + u16 vcc_high_warn; // 532-533 + u16 vcc_low_warn; // 534-535 + u8 reserved_fsdct2[8]; // 536-543 + + u8 reserved_device_thresh[16]; // 544-559 + + // 560–607 : Channel Thresholds (48 bytes) + // Per-channel: TX bias, TX power, RX power alarms/warns + u16 rx_power_high_alarm; // 560-56 (0.1 µW/LSB) + u16 rx_power_low_alarm; // 562-563 + u16 rx_power_high_warn; // 564-565 + u16 rx_power_low_warn; // 566-567 + + u16 tx_bias_high_alarm; // 568-569 (µA/LSB) + u16 tx_bias_low_alarm; // 570-571 + u16 tx_bias_high_warn; // 572-573 + u16 tx_bias_low_warn; // 574-575 + // + u16 tx_power_high_alarm; // 576-577 (0.1 µW/LSB) + u16 tx_power_low_alarm; // 578-579 + u16 tx_power_high_warn; // 580-581 + u16 tx_power_low_warn; // 582-583 + + u8 reserved_ct[24]; // 584-607 + + // 608–613 : TX EQ / RX Output / Temp Control (6 bytes) + u8 tx_eq_settings[4]; // 608-611 (per-channel EQ/emphasis) + u8 rx_output_settings; // 612 + u8 temp_control_settings; // 613 + + // 614–625 : Channel Controls (12 bytes) + u8 channel_controls[12]; // 614-625 (enable, polarity, squelch, etc.) + + // 626–635 : Channel Monitor Masks (10 bytes) + u8 channel_monitor_masks[10]; // 626-635 (interrupt mask bits per + // channel/parameter) + + // 636–639 : Reserved + u8 reserved_636_639[4]; // 636-639 +} sff8636_eeprom_t; + +STATIC_ASSERT (sizeof (sff8636_eeprom_t) == 640, + "sff8636_eeprom_t must be 640 bytes"); + +/* Function declarations */ +void sff8636_decode_diagnostics (vlib_main_t *vm, + vnet_interface_eeprom_t *eeprom, u8 is_terse); + +#endif /* __included_ethernet_sff8636_h__ */ diff --git a/src/vnet/interface.c b/src/vnet/interface.c index 5fb2ff65fa..b6c48dc28f 100644 --- a/src/vnet/interface.c +++ b/src/vnet/interface.c @@ -1794,6 +1794,24 @@ default_build_rewrite (vnet_main_t * vnm, return (NULL); } +u8 * +format_vnet_interface_eeprom_type (u8 *s, va_list *args) +{ + u32 eeprom_type = va_arg (*args, u32); + char *t = 0; + switch (eeprom_type) + { +#define _(n, v, str) \ + case v: \ + t = str; \ + break; + foreach_vnet_interface_eeprom_type +#undef _ + default : return format (s, "unknown 0x%x", eeprom_type); + } + return format (s, "%s", t); +} + void default_update_adjacency (vnet_main_t * vnm, u32 sw_if_index, u32 ai) { diff --git a/src/vnet/interface.h b/src/vnet/interface.h index c9778f9bac..93df4f096d 100644 --- a/src/vnet/interface.h +++ b/src/vnet/interface.h @@ -98,10 +98,25 @@ typedef clib_error_t *(vnet_interface_rss_queues_set_t) (struct vnet_main_t * vnm, struct vnet_hw_interface_t * hi, clib_bitmap_t * bitmap); +/* Interface EEPROM types */ +#define foreach_vnet_interface_eeprom_type \ + _ (UNKNOWN, 0x00, "unknown") \ + _ (SFF8079, 0x01, "SFF-8079") \ + _ (SFF8472, 0x02, "SFF-8472") \ + _ (SFF8636, 0x03, "SFF-8636") \ + _ (SFF8436, 0x04, "SFF-8436") + +typedef enum +{ +#define _(n, v, s) VNET_INTERFACE_EEPROM_TYPE_##n = v, + foreach_vnet_interface_eeprom_type +#undef _ +} vnet_interface_eeprom_type_t; + /* EEPROM structure for physical network devices */ typedef struct { - u32 eeprom_type; /* from linux/ethtool.h */ + vnet_interface_eeprom_type_t eeprom_type; /* from linux/ethtool.h */ u32 eeprom_len; u8 eeprom_raw[1024]; } vnet_interface_eeprom_t; @@ -1137,6 +1152,8 @@ typedef struct int vnet_pcap_dispatch_trace_configure (vnet_pcap_dispatch_trace_args_t *); +u8 *format_vnet_interface_eeprom_type (u8 *s, va_list *args); + extern vlib_node_registration_t vnet_interface_output_node; extern vlib_node_registration_t vnet_interface_output_arc_end_node; diff --git a/src/vnet/interface_cli.c b/src/vnet/interface_cli.c index 4b1828dc85..b49ad24c0d 100644 --- a/src/vnet/interface_cli.c +++ b/src/vnet/interface_cli.c @@ -56,6 +56,7 @@ #include #include #include +#include static int compare_interface_names (void *a1, void *a2) @@ -2689,6 +2690,8 @@ show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, } vlib_cli_output (vm, "Interface: %v", hi->name); + vlib_cli_output (vm, " EEPROM Type: 0x%02x (%U)", eeprom->eeprom_type, + format_vnet_interface_eeprom_type, eeprom->eeprom_type); /* Default to module if none are set */ if (!show_module && !show_diag && !show_eeprom) @@ -2696,7 +2699,6 @@ show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, if (show_eeprom) { - vlib_cli_output (vm, " EEPROM Type: 0x%x", eeprom->eeprom_type); vlib_cli_output (vm, " EEPROM Length: %u bytes", eeprom->eeprom_len); vlib_cli_output (vm, " EEPROM Data:"); @@ -2739,12 +2741,12 @@ show_interface_transceiver_output (vlib_main_t *vm, vnet_hw_interface_t *hi, if (show_module) { - vlib_cli_output (vm, " module: not implemented yet"); + sfp_eeprom_module (vm, eeprom, is_terse); } if (show_diag) { - vlib_cli_output (vm, " diag: not implemented yet"); + sfp_eeprom_diagnostics (vm, eeprom, is_terse); } done: From 0d0a3a50652b625655c03d230db6fc030622abb6 Mon Sep 17 00:00:00 2001 From: Hadi Rayan Al-Sandid Date: Fri, 1 Aug 2025 14:00:04 +0200 Subject: [PATCH 250/313] vnet: install full reassembly headers Type: improvement Change-Id: I7185f5f7ca2287bc194d06faf259c09c1bf19140 Signed-off-by: Hadi Rayan Al-Sandid --- src/vnet/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt index 03dbe0370d..891ed71d4d 100644 --- a/src/vnet/CMakeLists.txt +++ b/src/vnet/CMakeLists.txt @@ -463,6 +463,8 @@ list(APPEND VNET_HEADERS ip/punt.h ip/reass/ip4_sv_reass.h ip/reass/ip6_sv_reass.h + ip/reass/ip4_full_reass.h + ip/reass/ip6_full_reass.h ) list(APPEND VNET_API_FILES From d23c6284f9fae3d216046a891eae6d76c515114a Mon Sep 17 00:00:00 2001 From: Maxime Peim Date: Wed, 13 Aug 2025 17:30:16 +0200 Subject: [PATCH 251/313] vlib: add possibility to disable pager For the current running cli process, it can be useful to be able to disable the pager in case of long running command (e.g. ping). For a small window, the pager will prompt and hide the results of the command. Type: improvement Change-Id: I8fdde7f581683a8d9fd702a744fb95b5c58e14ff Signed-off-by: Maxime Peim --- src/vlib/unix/cli.c | 15 +++++++++++++++ src/vlib/unix/unix.h | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c index 8f0f00c8b9..e835a5295d 100644 --- a/src/vlib/unix/cli.c +++ b/src/vlib/unix/cli.c @@ -3287,6 +3287,21 @@ unix_cli_file_if_interactive (unix_cli_main_t * cm) return cf; } +u8 +vlib_unix_cli_enable_disable_pager (u8 enable) +{ + unix_cli_main_t *cm = &unix_cli_main; + unix_cli_file_t *cf = unix_cli_file_if_interactive (cm); + u8 old; + + if (!cf) + return ~0; + + old = !cf->no_pager; + cf->no_pager = !enable; + return old; +} + /** CLI command to quit the terminal session. * @note If this is a stdin session then this will * shutdown VPP also. diff --git a/src/vlib/unix/unix.h b/src/vlib/unix/unix.h index d0b7a4c700..e3aad366f3 100644 --- a/src/vlib/unix/unix.h +++ b/src/vlib/unix/unix.h @@ -140,6 +140,8 @@ int vlib_unix_main (int argc, char *argv[]); /* Set prompt for CLI. */ void vlib_unix_cli_set_prompt (char *prompt); +u8 vlib_unix_cli_enable_disable_pager (u8 enable); + static inline unix_main_t * vlib_unix_get_main (void) { From b6eabe6c080fa3272874b51fe1ef5f762955aafe Mon Sep 17 00:00:00 2001 From: Jay Wang Date: Mon, 20 May 2024 14:30:22 +0000 Subject: [PATCH 252/313] vppinfra: add ARM neoverse-v2 support It is recommended to use -mcpu instead of -march or -mtune on Grace because the -mcpu flag specifies both the appropriate architecture and the tuning strategy[1]. Type: improvement [1] https://github.com/NVIDIA/grace-cpu-benchmarking-guide/blob/main/src/developer/languages/c-c++.md#recommended-compiler-flags Signed-off-by: Jay Wang Change-Id: I09b52e605f218e633c4b59519038646ac6eec870 --- src/cmake/cpu.cmake | 7 +++++++ src/vppinfra/cpu.c | 1 + src/vppinfra/cpu.h | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/cmake/cpu.cmake b/src/cmake/cpu.cmake index b1b802a550..68f1a71671 100644 --- a/src/cmake/cpu.cmake +++ b/src/cmake/cpu.cmake @@ -241,6 +241,13 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*)") CACHE_PREFETCH_BYTES 64 OFF ) + + add_vpp_march_variant(neoversev2 + FLAGS -mcpu=neoverse-v2+crypto + N_PREFETCHES 6 + CACHE_PREFETCH_BYTES 64 + ) + endif() macro(vpp_library_set_multiarch_sources lib) diff --git a/src/vppinfra/cpu.c b/src/vppinfra/cpu.c index 385a4e2540..4e8f19b3ba 100644 --- a/src/vppinfra/cpu.c +++ b/src/vppinfra/cpu.c @@ -80,6 +80,7 @@ _ (0x41, 0xd0b, "ARM", "Cortex-A76", 0) \ _ (0x41, 0xd0c, "ARM", "Neoverse-N1", 0) \ _ (0x41, 0xd49, "ARM", "Neoverse-N2", 0) \ + _ (0x41, 0xd4f, "ARM", "Neoverse-V2", 0) \ _ (0x41, 0xd4a, "ARM", "Neoverse-E1", 0) \ _ (0x43, 0x0a1, "Marvell", "THUNDERX CN88XX", 0) \ _ (0x43, 0x0a2, "Marvell", "OCTEON TX CN81XX", 0) \ diff --git a/src/vppinfra/cpu.h b/src/vppinfra/cpu.h index b3743d4c26..12e302e3ac 100644 --- a/src/vppinfra/cpu.h +++ b/src/vppinfra/cpu.h @@ -37,7 +37,8 @@ _ (qdf24xx, "Qualcomm CentriqTM 2400") \ _ (cortexa72, "ARM Cortex-A72") \ _ (neoversen1, "ARM Neoverse N1") \ - _ (neoversen2, "ARM Neoverse N2") + _ (neoversen2, "ARM Neoverse N2") \ + _ (neoversev2, "ARM Neoverse V2") #else #define foreach_march_variant #endif @@ -355,6 +356,7 @@ const clib_cpu_info_t *clib_get_cpu_info (); #define AARCH64_CPU_PART_CORTEXA72 0xd08 #define AARCH64_CPU_PART_NEOVERSEN1 0xd0c #define AARCH64_CPU_PART_NEOVERSEN2 0xd49 +#define AARCH64_CPU_PART_NEOVERSEV2 0xd4f /*cavium */ #define AARCH64_CPU_IMPLEMENTER_CAVIUM 0x43 @@ -451,6 +453,20 @@ clib_cpu_march_priority_neoversen2 () return -1; } +static inline int +clib_cpu_march_priority_neoversev2 () +{ + const clib_cpu_info_t *info = clib_get_cpu_info (); + + if (!info || info->aarch64.implementer != AARCH64_CPU_IMPLEMENTER_ARM) + return -1; + + if (info->aarch64.part_num == AARCH64_CPU_PART_NEOVERSEV2) + return 10; + + return -1; +} + #ifdef CLIB_MARCH_VARIANT #define CLIB_MARCH_FN_PRIORITY() CLIB_MARCH_SFX(clib_cpu_march_priority)() #else From 61ee7875f660a90be59b121760e1c463968f5cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Ganne?= Date: Wed, 20 Aug 2025 10:13:52 +0200 Subject: [PATCH 253/313] vnet: fix set interfaces rx-placement cli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - unformat() parses 32-bit integers whereas clib_thread_t is a 16-bit integers, yielding stack overflow - no need to pass thread_index when parsing main - validate thread_index otherwise ~0 + 1 arithmetic done in set_hw_interface_rx_placement() selects main thread by default Type: fix Fixes: fc7b794758fbdd9bcae337e90255c1fc1e548808 Change-Id: I36dcba977573a32f6f91e6c5470966f35cce30ed Signed-off-by: Benoît Ganne --- src/vnet/interface_cli.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vnet/interface_cli.c b/src/vnet/interface_cli.c index b49ad24c0d..a92718b3f2 100644 --- a/src/vnet/interface_cli.c +++ b/src/vnet/interface_cli.c @@ -1732,7 +1732,7 @@ set_interface_rx_placement (vlib_main_t *vm, unformat_input_t *input, vnet_main_t *vnm = vnet_get_main (); u32 hw_if_index = (u32) ~ 0; u32 queue_id = (u32) 0; - clib_thread_index_t thread_index = CLIB_INVALID_THREAD_INDEX; + u32 thread_index = (u32) ~0; u8 is_main = 0; if (!unformat_user (input, unformat_line_input, line_input)) @@ -1745,7 +1745,7 @@ set_interface_rx_placement (vlib_main_t *vm, unformat_input_t *input, ; else if (unformat (line_input, "queue %d", &queue_id)) ; - else if (unformat (line_input, "main", &thread_index)) + else if (unformat (line_input, "main")) is_main = 1; else if (unformat (line_input, "worker %d", &thread_index)) ; @@ -1763,6 +1763,9 @@ set_interface_rx_placement (vlib_main_t *vm, unformat_input_t *input, if (hw_if_index == (u32) ~ 0) return clib_error_return (0, "please specify valid interface name"); + if (thread_index == (u32) ~0 && !is_main) + return clib_error_return (0, "please specify valid worker thread or main"); + error = set_hw_interface_rx_placement (hw_if_index, queue_id, thread_index, is_main); From 646a23812303a5a222adefb50c27e9fd47f106ff Mon Sep 17 00:00:00 2001 From: Hadi Rayan Al-Sandid Date: Fri, 1 Aug 2025 12:34:11 +0200 Subject: [PATCH 254/313] ip: reassembly - enable registering custom next nodes for v6 This function adds the ability to register next nodes for the custom ip6 full-reassembly node, similarly to custom v4. Type: improvement Change-Id: I65fd323192db0b96bb7ca301f8c890a4683f48fe Signed-off-by: Hadi Rayan Al-Sandid --- src/vnet/ip/reass/ip6_full_reass.c | 7 +++++++ src/vnet/ip/reass/ip6_full_reass.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/vnet/ip/reass/ip6_full_reass.c b/src/vnet/ip/reass/ip6_full_reass.c index 2764798587..e70c2698c9 100644 --- a/src/vnet/ip/reass/ip6_full_reass.c +++ b/src/vnet/ip/reass/ip6_full_reass.c @@ -1553,6 +1553,13 @@ VLIB_REGISTER_NODE (ip6_full_reass_node_custom) = { }; #ifndef CLIB_MARCH_VARIANT +uword +ip6_full_reass_custom_register_next_node (uword node_index) +{ + return vlib_node_add_next (vlib_get_main (), + ip6_full_reass_node_custom.index, node_index); +} + static u32 ip6_full_reass_get_nbuckets () { diff --git a/src/vnet/ip/reass/ip6_full_reass.h b/src/vnet/ip/reass/ip6_full_reass.h index f66cb67d79..d06d462f26 100644 --- a/src/vnet/ip/reass/ip6_full_reass.h +++ b/src/vnet/ip/reass/ip6_full_reass.h @@ -46,6 +46,8 @@ vnet_api_error_t ip6_full_reass_enable_disable (u32 sw_if_index, int ip6_full_reass_enable_disable_with_refcnt (u32 sw_if_index, int is_enable); +uword ip6_full_reass_custom_register_next_node (uword node_index); + void ip6_local_full_reass_enable_disable (int enable); int ip6_local_full_reass_enabled (); #endif /* __included_ip6_full_reass_h */ From 60338677e6b05b7a8fe642bf352d2a6e8ed65cac Mon Sep 17 00:00:00 2001 From: Hadi Rayan Al-Sandid Date: Thu, 14 Aug 2025 21:13:09 +0200 Subject: [PATCH 255/313] ip: 'format_ip6_header' - re-enable recurse into next proto layer Type: improvement Change-Id: I4d04466a9a6e94b460fb3c29543435dd9bceab99 Signed-off-by: Hadi Rayan Al-Sandid --- src/vnet/ip/ip6_format.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vnet/ip/ip6_format.c b/src/vnet/ip/ip6_format.c index 1a1bef26aa..54f4fd9981 100644 --- a/src/vnet/ip/ip6_format.c +++ b/src/vnet/ip/ip6_format.c @@ -288,7 +288,7 @@ format_ip6_header (u8 * s, va_list * args) "\n%Utos 0x%02x, flow label 0x%x, hop limit %d, payload length %d", format_white_space, indent, traffic_class, flow_label, ip->hop_limit, clib_net_to_host_u16 (ip->payload_length)); -#if 0 + /* Recurse into next protocol layer. */ if (max_header_bytes != 0 && sizeof (ip[0]) < max_header_bytes) { @@ -301,7 +301,6 @@ format_ip6_header (u8 * s, va_list * args) /* next protocol header */ (void *) (ip + 1), max_header_bytes - sizeof (ip[0])); } -#endif return s; } From 32a2287ba13e4e636482f4a376d0acbc47ccdfe6 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Fri, 22 Aug 2025 10:49:05 -0400 Subject: [PATCH 256/313] udp: add input nolookup node Feature parity with tcp. Useful when other nodes like hsi do the udp session lookup before sending to udp. Type: feature Change-Id: Ib5f39a86cb316d326f0e19f7426b3b97c1bdecf7 Signed-off-by: Florin Coras --- src/vnet/buffer.h | 13 ++++ src/vnet/udp/udp_error.def | 1 + src/vnet/udp/udp_input.c | 118 ++++++++++++++++++++++++++++++------- 3 files changed, 111 insertions(+), 21 deletions(-) diff --git a/src/vnet/buffer.h b/src/vnet/buffer.h index c9005b70da..01bd69b5e5 100644 --- a/src/vnet/buffer.h +++ b/src/vnet/buffer.h @@ -404,6 +404,19 @@ typedef struct u8 flags; } tcp; + struct + { + union + { + struct + { + u32 session_index; + clib_thread_index_t thread_index; + }; + u64 session_handle; + }; + } udp; + /* SNAT */ struct { diff --git a/src/vnet/udp/udp_error.def b/src/vnet/udp/udp_error.def index 843aacfc6e..011e59cba4 100644 --- a/src/vnet/udp/udp_error.def +++ b/src/vnet/udp/udp_error.def @@ -29,3 +29,4 @@ udp_error (MQ_FULL, mq_full, ERROR, "Application msg queue full") udp_error (INVALID_CONNECTION, invalid_connection, ERROR, "Invalid connection") udp_error (PKTS_SENT, pkts_sent, INFO, "Packets sent") udp_error (CONNECTED, connected, INFO, "Connected session") +udp_error (CREATE_EXISTS, create_exists, ERROR, "Connection already exists") diff --git a/src/vnet/udp/udp_input.c b/src/vnet/udp/udp_input.c index e982895d2d..4ca09b5920 100644 --- a/src/vnet/udp/udp_input.c +++ b/src/vnet/udp/udp_input.c @@ -197,8 +197,8 @@ udp_connection_enqueue (udp_connection_t *uc0, session_t *s0, } always_inline session_t * -udp_parse_and_lookup_buffer (vlib_buffer_t * b, session_dgram_hdr_t * hdr, - u8 is_ip4) +udp_parse_and_lookup_buffer (vlib_buffer_t *b, session_dgram_hdr_t *hdr, + u8 is_ip4, u8 is_nolookup) { udp_header_t *udp; u32 fib_index; @@ -224,9 +224,10 @@ udp_parse_and_lookup_buffer (vlib_buffer_t * b, session_dgram_hdr_t * hdr, ip_set (&hdr->rmt_ip, &ip4->src_address, 1); hdr->data_length = clib_net_to_host_u16 (ip4->length); hdr->data_length -= sizeof (ip4_header_t) + sizeof (udp_header_t); - s = session_lookup_safe4 (fib_index, &ip4->dst_address, - &ip4->src_address, udp->dst_port, - udp->src_port, TRANSPORT_PROTO_UDP); + if (!is_nolookup) + s = session_lookup_safe4 (fib_index, &ip4->dst_address, + &ip4->src_address, udp->dst_port, + udp->src_port, TRANSPORT_PROTO_UDP); } else { @@ -237,11 +238,15 @@ udp_parse_and_lookup_buffer (vlib_buffer_t * b, session_dgram_hdr_t * hdr, ip_set (&hdr->rmt_ip, &ip60->src_address, 0); hdr->data_length = clib_net_to_host_u16 (ip60->payload_length); hdr->data_length -= sizeof (udp_header_t); - s = session_lookup_safe6 (fib_index, &ip60->dst_address, - &ip60->src_address, udp->dst_port, - udp->src_port, TRANSPORT_PROTO_UDP); + if (!is_nolookup) + s = session_lookup_safe6 (fib_index, &ip60->dst_address, + &ip60->src_address, udp->dst_port, + udp->src_port, TRANSPORT_PROTO_UDP); } + if (is_nolookup) + s = session_get_from_handle (vnet_buffer (b)->udp.session_handle); + /* Set the sw_if_index[VLIB_RX] to the interface we received * the connection on (the local interface) */ vnet_buffer (b)->sw_if_index[VLIB_RX] = vnet_buffer (b)->ip.rx_sw_if_index; @@ -255,9 +260,26 @@ udp_parse_and_lookup_buffer (vlib_buffer_t * b, session_dgram_hdr_t * hdr, return s; } +static u8 +udp_connection_is_accepted (u32 fib_index, session_dgram_hdr_t *hdr, u8 is_ip4) +{ + session_t *s; + + if (is_ip4) + s = + session_lookup_safe4 (fib_index, &hdr->lcl_ip.ip4, &hdr->rmt_ip.ip4, + hdr->lcl_port, hdr->rmt_port, TRANSPORT_PROTO_UDP); + else + s = + session_lookup_safe6 (fib_index, &hdr->lcl_ip.ip6, &hdr->rmt_ip.ip6, + hdr->lcl_port, hdr->rmt_port, TRANSPORT_PROTO_UDP); + + return (s && s->session_state != SESSION_STATE_LISTENING); +} + always_inline uword -udp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * frame, u8 is_ip4) +udp46_input_inline (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_frame_t *frame, u8 is_ip4, u8 is_nolookup) { clib_thread_index_t thread_index = vm->thread_index; u32 n_left_from, *from, *first_buffer; @@ -277,7 +299,7 @@ udp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, udp_connection_t *uc0; session_t *s0; - s0 = udp_parse_and_lookup_buffer (b[0], &hdr0, is_ip4); + s0 = udp_parse_and_lookup_buffer (b[0], &hdr0, is_ip4, is_nolookup); if (PREDICT_FALSE (!s0)) { error0 = UDP_ERROR_NO_LISTENER; @@ -327,6 +349,16 @@ udp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, uc0 = udp_connection_from_transport (session_get_transport (s0)); if (uc0->flags & UDP_CONN_F_CONNECTED) { + if (is_nolookup) + { + /* Make sure connection was not already accepted */ + if (udp_connection_is_accepted ( + vnet_buffer (b[0])->ip.fib_index, &hdr0, is_ip4)) + { + error0 = UDP_ERROR_CREATE_EXISTS; + goto done; + } + } uc0 = udp_connection_accept (uc0, &hdr0, thread_index); if (!uc0) { @@ -361,16 +393,15 @@ udp46_input_inline (vlib_main_t * vm, vlib_node_runtime_t * node, return frame->n_vectors; } -static uword -udp4_input (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * frame) +VLIB_NODE_FN (udp4_input_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) { - return udp46_input_inline (vm, node, frame, 1); + return udp46_input_inline (vm, node, frame, 1 /* is_ip4 */, + 0 /* is_nolookup */); } VLIB_REGISTER_NODE (udp4_input_node) = { - .function = udp4_input, .name = "udp4-input", .vector_size = sizeof (u32), .format_trace = format_udp_input_trace, @@ -385,16 +416,38 @@ VLIB_REGISTER_NODE (udp4_input_node) = }, }; -static uword -udp6_input (vlib_main_t * vm, vlib_node_runtime_t * node, - vlib_frame_t * frame) +VLIB_NODE_FN (udp4_input_nolookup_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) { - return udp46_input_inline (vm, node, frame, 0); + return udp46_input_inline (vm, node, frame, 1 /* is_ip4 */, + 1 /* is_nolookup */); +} + +VLIB_REGISTER_NODE (udp4_input_nolookup_node) = +{ + .name = "udp4-input-nolookup", + .vector_size = sizeof (u32), + .format_trace = format_udp_input_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = UDP_N_ERROR, + .error_counters = udp_error_counters, + .n_next_nodes = UDP_INPUT_N_NEXT, + .next_nodes = { +#define _(s, n) [UDP_INPUT_NEXT_##s] = n, + foreach_udp_input_next +#undef _ + }, +}; + +VLIB_NODE_FN (udp6_input_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return udp46_input_inline (vm, node, frame, 0 /* is_ip4 */, + 0 /* is_nolookup */); } VLIB_REGISTER_NODE (udp6_input_node) = { - .function = udp6_input, .name = "udp6-input", .vector_size = sizeof (u32), .format_trace = format_udp_input_trace, @@ -409,6 +462,29 @@ VLIB_REGISTER_NODE (udp6_input_node) = }, }; +VLIB_NODE_FN (udp6_input_nolookup_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return udp46_input_inline (vm, node, frame, 0 /* is_ip4 */, + 1 /* is_nolookup */); +} + +VLIB_REGISTER_NODE (udp6_input_nolookup_node) = +{ + .name = "udp6-input-nolookup", + .vector_size = sizeof (u32), + .format_trace = format_udp_input_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = UDP_N_ERROR, + .error_counters = udp_error_counters, + .n_next_nodes = UDP_INPUT_N_NEXT, + .next_nodes = { +#define _(s, n) [UDP_INPUT_NEXT_##s] = n, + foreach_udp_input_next +#undef _ + }, +}; + /* * fd.io coding-style-patch-verification: ON * From f391d158fbf5aa9e98206a4d3d67f4fa2c11847d Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Wed, 20 Aug 2025 13:33:24 -0400 Subject: [PATCH 257/313] hsi: support to intercept all proto traffic Type: improvement Change-Id: Ieebc0a8383f8c86756e4bd36501c2faa9df87a98 Signed-off-by: Florin Coras --- src/plugins/hsi/hsi.c | 157 ++++++++++++++++++++++++++++++++++----- src/plugins/hsi/hsi.h | 2 + src/vnet/tcp/tcp_input.c | 2 +- 3 files changed, 143 insertions(+), 18 deletions(-) diff --git a/src/plugins/hsi/hsi.c b/src/plugins/hsi/hsi.c index 0fea0a3f28..e55ef52b91 100644 --- a/src/plugins/hsi/hsi.c +++ b/src/plugins/hsi/hsi.c @@ -18,6 +18,30 @@ #include #include +#include + +typedef struct hsi_main_ +{ + u8 intercept_type; + + /* ipv4 and ipv6 for tcp and udp */ + session_handle_t intercept_listeners[2][2]; +} hsi_main_t; + +static hsi_main_t hsi_main; + +static inline u8 +hsi_intercept_proto_flag (transport_proto_t proto, u8 is_ip4) +{ + /* This leverages the fact that TCP is 0 and UDP is 1 */ + return (1 << (proto << 1 | is_ip4)); +} + +static inline u8 +hsi_have_intercept_proto (transport_proto_t proto, u8 is_ip4) +{ + return (hsi_main.intercept_type & hsi_intercept_proto_flag (proto, is_ip4)); +} char *hsi_error_strings[] = { #define hsi_error(n, s) s, @@ -28,20 +52,26 @@ char *hsi_error_strings[] = { typedef enum hsi_input_next_ { HSI_INPUT_NEXT_UDP_INPUT, + HSI_INPUT_NEXT_UDP_INPUT_NOLOOKUP, HSI_INPUT_NEXT_TCP_INPUT, HSI_INPUT_NEXT_TCP_INPUT_NOLOOKUP, + HSI_INPUT_NEXT_TCP_LISTEN, HSI_INPUT_N_NEXT } hsi_input_next_t; #define foreach_hsi4_input_next \ _ (UDP_INPUT, "udp4-input") \ + _ (UDP_INPUT_NOLOOKUP, "udp4-input-nolookup") \ _ (TCP_INPUT, "tcp4-input") \ - _ (TCP_INPUT_NOLOOKUP, "tcp4-input-nolookup") + _ (TCP_INPUT_NOLOOKUP, "tcp4-input-nolookup") \ + _ (TCP_LISTEN, "tcp4-listen") #define foreach_hsi6_input_next \ _ (UDP_INPUT, "udp6-input") \ + _ (UDP_INPUT_NOLOOKUP, "udp6-input-nolookup") \ _ (TCP_INPUT, "tcp6-input") \ - _ (TCP_INPUT_NOLOOKUP, "tcp6-input-nolookup") + _ (TCP_INPUT_NOLOOKUP, "tcp6-input-nolookup") \ + _ (TCP_LISTEN, "tcp6-listen") typedef struct { @@ -62,7 +92,7 @@ format_hsi_trace (u8 *s, va_list *args) return s; } -always_inline u8 +always_inline session_t * hsi_udp_lookup (vlib_buffer_t *b, void *ip_hdr, u8 is_ip4) { udp_header_t *hdr; @@ -85,7 +115,7 @@ hsi_udp_lookup (vlib_buffer_t *b, void *ip_hdr, u8 is_ip4) hdr->dst_port, hdr->src_port, TRANSPORT_PROTO_UDP); } - return s ? 1 : 0; + return s; } always_inline transport_connection_t * @@ -120,10 +150,11 @@ hsi_tcp_lookup (vlib_buffer_t *b, void *ip_hdr, tcp_header_t **rhdr, u8 is_ip4) always_inline void hsi_lookup_and_update (vlib_buffer_t *b, u32 *next, u8 is_ip4, u8 is_input) { - u8 proto, state, have_udp; + u8 proto, state; tcp_header_t *tcp_hdr = 0; tcp_connection_t *tc; u32 rw_len = 0; + session_t *s; void *ip_hdr; if (is_input) @@ -176,23 +207,49 @@ hsi_lookup_and_update (vlib_buffer_t *b, u32 *next, u8 is_ip4, u8 is_input) } else { - vnet_feature_next (next, b); + u32 error = 0; + + if (!hsi_have_intercept_proto (TRANSPORT_PROTO_TCP, is_ip4) || + !tcp_syn (tcp_hdr)) + { + vnet_feature_next (next, b); + break; + } + + /* force parsing of buffer in preparation for tcp-listen */ + tcp_input_lookup_buffer (b, vlib_get_thread_index (), &error, is_ip4, + 1 /* is_nolookup*/); + if (error) + { + vnet_feature_next (next, b); + break; + } + + vnet_buffer (b)->tcp.connection_index = + hsi_main.intercept_listeners[!is_ip4][TRANSPORT_PROTO_TCP]; + vnet_buffer (b)->tcp.flags = TCP_STATE_LISTEN; + + *next = HSI_INPUT_NEXT_TCP_LISTEN; } break; case IP_PROTOCOL_UDP: - have_udp = hsi_udp_lookup (b, ip_hdr, is_ip4); - if (have_udp) + s = hsi_udp_lookup (b, ip_hdr, is_ip4); + if (!s) { - *next = HSI_INPUT_NEXT_UDP_INPUT; - /* Emulate udp-local and consume headers up to udp payload */ - rw_len += is_ip4 ? sizeof (ip4_header_t) : sizeof (ip6_header_t); - rw_len += sizeof (udp_header_t); - vlib_buffer_advance (b, rw_len); - } - else - { - vnet_feature_next (next, b); + if (!hsi_have_intercept_proto (TRANSPORT_PROTO_UDP, is_ip4)) + { + vnet_feature_next (next, b); + break; + } + s = session_get_from_handle ( + hsi_main.intercept_listeners[!is_ip4][TRANSPORT_PROTO_UDP]); } + *next = HSI_INPUT_NEXT_UDP_INPUT_NOLOOKUP; + /* Emulate udp-local and consume headers up to udp payload */ + rw_len += is_ip4 ? sizeof (ip4_header_t) : sizeof (ip6_header_t); + rw_len += sizeof (udp_header_t); + vlib_buffer_advance (b, rw_len); + vnet_buffer (b)->udp.session_handle = s->handle; break; default: vnet_feature_next (next, b); @@ -389,6 +446,72 @@ VNET_FEATURE_INIT (hsi6_out_feature, static) = { .runs_before = VNET_FEATURES ("interface-output"), }; +void +hsi_intercept_proto (transport_proto_t proto, u8 is_ip4) +{ + hsi_main_t *hm = &hsi_main; + session_endpoint_t sep = { .transport_proto = proto, .is_ip4 = is_ip4 }; + session_t *ls; + + ls = session_lookup_listener_wildcard (0, &sep); + if (ls) + { + if (proto == TRANSPORT_PROTO_TCP) + hm->intercept_listeners[!is_ip4][proto] = ls->connection_index; + else + hm->intercept_listeners[!is_ip4][proto] = ls->handle; + hm->intercept_type |= hsi_intercept_proto_flag (proto, is_ip4); + } +} + +static clib_error_t * +hsi_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "intercept tcp")) + { + hsi_intercept_proto (TRANSPORT_PROTO_TCP, 1); + hsi_intercept_proto (TRANSPORT_PROTO_TCP, 0); + } + else if (unformat (line_input, "intercept udp")) + { + hsi_intercept_proto (TRANSPORT_PROTO_UDP, 1); + hsi_intercept_proto (TRANSPORT_PROTO_UDP, 0); + } + else if (unformat (line_input, "intercept all")) + { + hsi_intercept_proto (TRANSPORT_PROTO_TCP, 1); + hsi_intercept_proto (TRANSPORT_PROTO_TCP, 0); + hsi_intercept_proto (TRANSPORT_PROTO_UDP, 1); + hsi_intercept_proto (TRANSPORT_PROTO_UDP, 0); + } + else + { + error = clib_error_return (0, "unknown input `%U'", + format_unformat_error, line_input); + goto done; + } + } + +done: + unformat_free (line_input); + return error; +} + +VLIB_CLI_COMMAND (hsi_command, static) = { + .path = "hsi", + .short_help = "hsi [intercept [tcp | udp | all]]", + .function = hsi_command_fn, +}; + VLIB_PLUGIN_REGISTER () = { .version = VPP_BUILD_VER, .description = "Host Stack Intercept (HSI)", diff --git a/src/plugins/hsi/hsi.h b/src/plugins/hsi/hsi.h index 1eee1565ef..89172635d3 100644 --- a/src/plugins/hsi/hsi.h +++ b/src/plugins/hsi/hsi.h @@ -26,4 +26,6 @@ typedef enum _hsi_error HSI_N_ERROR, } hsi_error_t; +__clib_export void hsi_intercept_proto (transport_proto_t proto, u8 is_ip4); + #endif /* SRC_PLUGINS_HSI_HSI_H_ */ diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c index b8b4c3432a..cbb589d472 100644 --- a/src/vnet/tcp/tcp_input.c +++ b/src/vnet/tcp/tcp_input.c @@ -2627,7 +2627,7 @@ tcp46_listen_inline (vlib_main_t *vm, vlib_node_runtime_t *node, /* Make sure connection wasn't just created */ child = tcp_lookup_connection (lc->c_fib_index, b[0], thread_index, is_ip4); - if (PREDICT_FALSE (child->state != TCP_STATE_LISTEN)) + if (PREDICT_FALSE (child && child->state != TCP_STATE_LISTEN)) { tcp_inc_counter (listen, TCP_ERROR_CREATE_EXISTS, 1); goto done; From 72b1b85bf3abf620699922c286941911e2df095d Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 27 Aug 2025 16:38:39 -0400 Subject: [PATCH 258/313] http: http2_conn_connect_stream_callback fix pass correct opaque to app_worker_connect_notify on max streams hit Type: fix Change-Id: Ib320eddc90eb30a60914c7769faef779f6911a3f Signed-off-by: Matus Fabian --- src/plugins/http/http2/http2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/http/http2/http2.c b/src/plugins/http/http2/http2.c index ba6e927b0c..b65e33440a 100644 --- a/src/plugins/http/http2/http2.c +++ b/src/plugins/http/http2/http2.c @@ -3153,7 +3153,7 @@ http2_conn_connect_stream_callback (http_conn_t *hc, u32 parent_app_api_ctx) return -1; if (h2c->req_num == h2c->settings.max_concurrent_streams) return app_worker_connect_notify (app_wrk, 0, SESSION_E_MAX_STREAMS_HIT, - hc->hc_pa_app_api_ctx); + parent_app_api_ctx); req = http2_conn_alloc_req (hc, 0); http_req_state_change (&req->base, HTTP_REQ_STATE_WAIT_APP_METHOD); return http_conn_established (hc, &req->base, parent_app_api_ctx, 1); From f413b265d5a5d334092d403625916e95a5022668 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Mon, 1 Sep 2025 13:45:08 +0000 Subject: [PATCH 259/313] session: fix app_send_dgram_segs enqueueing n-1 segments Type: fix Change-Id: Iad1c4d967a10e2b5a2e951b119ff4b89716c3d50 Signed-off-by: Semir Sionek --- src/vnet/session/application_interface.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h index 284c1544bf..c29e67f974 100644 --- a/src/vnet/session/application_interface.h +++ b/src/vnet/session/application_interface.h @@ -718,8 +718,8 @@ app_send_dgram_segs (app_session_t *s, svm_fifo_seg_t *segs, u32 data_nsegs, u32 seg_len = app_gen_dgram_header (segs, data_len, &s->transport, 0); return app_send_dgram_segs_raw (s->tx_fifo, &s->transport, s->vpp_evt_q, - segs, data_nsegs, seg_len, SESSION_IO_EVT_TX, - 1 /* do_evt */, noblock); + segs, data_nsegs + 1, seg_len, + SESSION_IO_EVT_TX, 1 /* do_evt */, noblock); } always_inline int From d264f3cbe95898068a660c1054f3a6ec5987e14b Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Wed, 20 Aug 2025 14:16:29 +0000 Subject: [PATCH 260/313] hsa: add periodic reports to builtin echo client report-interval now enables periodic reports, which display TX/RX stats, as well as throughput and sent/recv datagrams if running over UDP. It also features a -total variant, which displays same data, but as totals counted from test start time. Type: improvement Change-Id: I28dd1a0c8bfbcaf2882a102433ce4acf28b3d6ef Signed-off-by: Semir Sionek --- extras/hs-test/echo_test.go | 82 +++++++- src/plugins/hs_apps/echo_client.c | 329 +++++++++++++++++++++++------- src/plugins/hs_apps/echo_client.h | 10 + src/vppinfra/format.h | 3 + src/vppinfra/std-formats.c | 35 ++++ 5 files changed, 377 insertions(+), 82 deletions(-) diff --git a/extras/hs-test/echo_test.go b/extras/hs-test/echo_test.go index 3dada0eda5..070ab964b0 100644 --- a/extras/hs-test/echo_test.go +++ b/extras/hs-test/echo_test.go @@ -8,7 +8,7 @@ import ( ) func init() { - RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest, EchoBuiltinTestbytesTest) + RegisterVethTests(EchoBuiltinTest, EchoBuiltinBandwidthTest, EchoBuiltinEchobytesTest, EchoBuiltinRoundtripTest, EchoBuiltinTestbytesTest, EchoBuiltinPeriodicReportTest, EchoBuiltinPeriodicReportTotalTest) RegisterVethMWTests(TcpWithLossTest) RegisterSoloVeth6Tests(TcpWithLoss6Test) } @@ -37,7 +37,7 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { clientVpp := s.Containers.ClientVpp.VppInstance - o := clientVpp.Vppctl("test echo client nclients 4 bytes 8m throughput 16m" + + o := clientVpp.Vppctl("test echo client nclients 4 bytes 2m throughput 32m" + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) s.Log(o) s.AssertContains(o, "Test started") @@ -46,9 +46,9 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { matches := regex.FindStringSubmatch(o) if len(matches) != 0 { seconds, _ := strconv.ParseFloat(matches[1], 32) - // Make sure that we are within 0.1 of the targeted + // Make sure that we are within 0.25 of the targeted // 2 seconds of runtime - s.AssertEqualWithinThreshold(seconds, 2, 0.1) + s.AssertEqualWithinThreshold(seconds, 2, 0.25) } else { s.AssertEmpty("invalid echo test client output") } @@ -57,6 +57,76 @@ func EchoBuiltinBandwidthTest(s *VethsSuite) { } } +func EchoBuiltinPeriodicReportTotalTest(s *VethsSuite) { + regex := regexp.MustCompile(`(\d?\.\d)\s+(\d+\.\d+)M\s+0\s+\d+\.\d+Mb/s`) + serverVpp := s.Containers.ServerVpp.VppInstance + + serverVpp.Vppctl("test echo server " + + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + + clientVpp := s.Containers.ClientVpp.VppInstance + + o := clientVpp.Vppctl("test echo client bytes 7900k throughput 16m report-interval-total 1" + + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + s.Log(o) + s.AssertContains(o, "Test started") + s.AssertContains(o, "Test finished") + if regex.MatchString(o) { + matches := regex.FindAllStringSubmatch(o, -1) + // Check we got a correct number of reports + s.AssertEqual(4, len(matches)) + // Verify TX numbers + for i := 0; i < 4; i++ { + mbytes, _ := strconv.ParseFloat(matches[i][2], 32) + s.AssertEqualWithinThreshold(mbytes, 2*(i+1), 0.1) + } + // Verify reporting times + s.AssertEqual(matches[0][1], "1.0") + s.AssertEqual(matches[1][1], "2.0") + s.AssertEqual(matches[2][1], "3.0") + s.AssertEqual(matches[3][1], "4.0") + } else { + s.AssertEmpty("invalid echo test client output") + } +} + +func EchoBuiltinPeriodicReportTest(s *VethsSuite) { + regex := regexp.MustCompile(`(\d?\.\d)-(\d?.\d)\s+(\d+\.\d+)M\s+0\s+\d+\.\d+Mb/s`) + serverVpp := s.Containers.ServerVpp.VppInstance + + serverVpp.Vppctl("test echo server " + + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + + clientVpp := s.Containers.ClientVpp.VppInstance + + o := clientVpp.Vppctl("test echo client bytes 7900k throughput 16m report-interval 1" + + " uri tcp://" + s.Interfaces.Server.Ip4AddressString() + "/" + s.Ports.Port1) + s.Log(o) + s.AssertContains(o, "Test started") + s.AssertContains(o, "Test finished") + if regex.MatchString(o) { + matches := regex.FindAllStringSubmatch(o, -1) + // Check we got a correct number of reports + s.AssertEqual(4, len(matches)) + // Verify TX numbers + for i := 0; i < 4; i++ { + mbytes, _ := strconv.ParseFloat(matches[i][3], 32) + s.AssertEqualWithinThreshold(mbytes, 2, 0.1) + } + // Verify time interval numbers + s.AssertEqual(matches[0][1], "0.0") + s.AssertEqual(matches[0][2], "1.0") + s.AssertEqual(matches[1][1], "1.0") + s.AssertEqual(matches[1][2], "2.0") + s.AssertEqual(matches[2][1], "2.0") + s.AssertEqual(matches[2][2], "3.0") + s.AssertEqual(matches[3][1], "3.0") + s.AssertEqual(matches[3][2], "4.0") + } else { + s.AssertEmpty("invalid echo test client output") + } +} + func EchoBuiltinRoundtripTest(s *VethsSuite) { regex := regexp.MustCompile(`(\.\d+)ms roundtrip`) serverVpp := s.Containers.ServerVpp.VppInstance @@ -127,7 +197,7 @@ func tcpWithoutLoss(s *VethsSuite) string { clientVpp := s.Containers.ClientVpp.VppInstance // Do echo test from client-vpp container - output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes run-time 10 test-timeout 20", + output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes run-time 10", s.Interfaces.Server.Ip4AddressString(), s.Ports.Port1) s.Log(output) s.AssertNotEqual(len(output), 0) @@ -185,7 +255,7 @@ func tcpWithoutLoss6(s *Veths6Suite) string { clientVpp := s.Containers.ClientVpp.VppInstance // Do echo test from client-vpp container - output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes run-time 10 test-timeout 20", + output := clientVpp.Vppctl("test echo client uri tcp://%s/%s verbose echo-bytes run-time 10", s.Interfaces.Server.Ip6AddressString(), s.Ports.Port1) s.Log(output) s.AssertNotEqual(len(output), 0) diff --git a/src/plugins/hs_apps/echo_client.c b/src/plugins/hs_apps/echo_client.c index d5896aecc7..c4c64bad2d 100644 --- a/src/plugins/hs_apps/echo_client.c +++ b/src/plugins/hs_apps/echo_client.c @@ -185,6 +185,8 @@ send_data_chunk (ec_main_t *ecm, ec_session_t *es) /* If we managed to enqueue data... */ if (rv > 0) { + if (es->is_dgram) + es->dgrams_sent++; /* Account for it... */ es->bytes_sent += rv; if (ecm->run_time) @@ -235,6 +237,7 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) test_buf_offset = *(u32 *) wrk->rx_buf; rx_buf_start = wrk->rx_buf + sizeof (u32); n_read -= sizeof (u32); + es->dgrams_received++; } } else @@ -255,6 +258,7 @@ receive_data_chunk (ec_worker_t *wrk, ec_session_t *es) svm_fifo_dequeue_drop (rx_fifo, ph.data_length + SESSION_CONN_HDR_LEN); n_read = ph.data_length; + es->dgrams_received++; } } @@ -484,6 +488,11 @@ ec_reset_runtime_config (ec_main_t *ecm) ecm->throughput = 0; ecm->pacing_window_len = 1; ecm->max_chunk_bytes = 128 << 10; + ecm->report_interval = 0; + ecm->report_interval_total = 0; + ecm->last_print_time = 0; + ecm->last_total_tx_bytes = 0; + ecm->last_total_rx_bytes = 0; clib_memset (&ecm->rtt_stats, 0, sizeof (ec_rttstat_t)); ecm->rtt_stats.min_rtt = CLIB_F64_MAX; if (ecm->rtt_stats.w_lock == NULL) @@ -1249,20 +1258,180 @@ ec_ctrl_test_stop () goto cleanup; \ } +static void +ec_print_timeout_stats (vlib_main_t *vm) +{ + ec_main_t *ecm = &ec_main; + u64 received_bytes = 0, sent_bytes = 0; + ec_worker_t *wrk; + ec_session_t *sess; + vec_foreach (wrk, ecm->wrk) + { + pool_foreach (sess, wrk->sessions) + { + received_bytes += sess->bytes_received; + sent_bytes += sess->bytes_sent; + } + } + ec_cli ("Timeout at %.6f with %d sessions still active...", + vlib_time_now (vm), ecm->ready_connections); + if (ecm->transport_proto == TRANSPORT_PROTO_UDP) + { + ec_cli ("Received %llu bytes out of %llu sent (%llu target)", + received_bytes, sent_bytes, ecm->bytes_to_send * ecm->n_clients); + } +} + +static void +ec_print_periodic_stats (vlib_main_t *vm, bool print_header, bool print_footer) +{ + ec_main_t *ecm = &ec_main; + f64 time_now, print_delta, interval_start, interval_end; + u64 total_bytes, + received_bytes = 0, sent_bytes = 0, dgrams_sent = 0, dgrams_received = 0, + last_total_bytes = ecm->last_total_tx_bytes + ecm->last_total_rx_bytes; + ec_worker_t *wrk; + ec_session_t *sess; + vec_foreach (wrk, ecm->wrk) + { + pool_foreach (sess, wrk->sessions) + { + received_bytes += sess->bytes_received; + sent_bytes += sess->bytes_sent; + if (ecm->transport_proto == TRANSPORT_PROTO_UDP) + { + dgrams_received += sess->dgrams_received; + dgrams_sent += sess->dgrams_sent; + } + } + } + time_now = vlib_time_now (vm); + interval_end = time_now - ecm->test_start_time; + interval_start = ecm->last_print_time - ecm->test_start_time; + total_bytes = received_bytes + sent_bytes; + print_delta = time_now - ecm->last_print_time; + + if (ecm->transport_proto == TRANSPORT_PROTO_UDP) + { + u8 *tput = 0; + if (print_header) + { + ec_cli ("-----------------------------------------------------------" + "-------------"); + if (ecm->report_interval_total) + ec_cli ("Run time (s) Transmitted Received Throughput " + "Sent/received dgrams"); + else + ec_cli ("Interval (s) Transmitted Received Throughput " + "Sent/received dgrams"); + } + if (ecm->report_interval_total) + { + tput = + format (tput, "%Ub/s", format_base10, + flt_round_nearest ((f64) total_bytes / + (time_now - ecm->test_start_time)) * + 8); + ec_cli ("%-13.1f %-13U %-10U %-12s %llu/%llu", interval_end, + format_base10, sent_bytes, format_base10, received_bytes, + tput, dgrams_sent, dgrams_received); + } + else + { + tput = + format (tput, "%Ub/s", format_base10, + flt_round_nearest ((f64) (total_bytes - last_total_bytes) / + print_delta) * + 8); + ec_cli ("%.1f-%-9.1f %-13U %-10U %-12s %llu/%llu", interval_start, + interval_end, format_base10, + sent_bytes - ecm->last_total_tx_bytes, format_base10, + received_bytes - ecm->last_total_rx_bytes, tput, + (dgrams_sent - ecm->last_total_tx_dgrams), + (dgrams_received - ecm->last_total_rx_dgrams)); + } + if (print_footer) + ec_cli ("-------------------------------------------------------------" + "-----------"); + ecm->last_total_tx_dgrams = dgrams_sent; + ecm->last_total_rx_dgrams = dgrams_received; + vec_free (tput); + } + else + { + if (print_header) + { + ec_cli ("-------------------------------------------------"); + if (ecm->report_interval_total) + ec_cli ("Run time (s) Transmitted Received Throughput"); + else + ec_cli ("Interval (s) Transmitted Received Throughput"); + } + if (ecm->report_interval_total) + ec_cli ("%-13.1f %-13U %-10U %Ub/s", interval_end, format_base10, + sent_bytes, format_base10, received_bytes, format_base10, + flt_round_nearest ((f64) total_bytes / + (time_now - ecm->test_start_time)) * + 8); + else + ec_cli ("%.1f-%-9.1f %-13U %-10U %Ub/s", interval_start, interval_end, + format_base10, sent_bytes - ecm->last_total_tx_bytes, + format_base10, received_bytes - ecm->last_total_rx_bytes, + format_base10, + flt_round_nearest (((f64) (total_bytes - last_total_bytes)) / + print_delta) * + 8); + if (print_footer) + ec_cli ("-------------------------------------------------"); + } + ecm->last_print_time = time_now; + ecm->last_total_tx_bytes = sent_bytes; + ecm->last_total_rx_bytes = received_bytes; +} + +static void +ec_print_final_stats (vlib_main_t *vm, f64 total_delta) +{ + ec_main_t *ecm = &ec_main; + u64 total_bytes; + char *transfer_type; + + if (ecm->transport_proto == TRANSPORT_PROTO_TCP || + (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes)) + { + /* display rtt stats in milliseconds */ + if (ecm->rtt_stats.n_sum == 1) + ec_cli ("%.05fms roundtrip", ecm->rtt_stats.min_rtt * 1000); + else if (ecm->rtt_stats.n_sum > 1) + ec_cli ("%.05fms/%.05fms/%.05fms min/avg/max roundtrip", + ecm->rtt_stats.min_rtt * 1000, + ecm->rtt_stats.sum_rtt / ecm->rtt_stats.n_sum * 1000, + ecm->rtt_stats.max_rtt * 1000); + else + ec_cli ("error measuring roundtrip time"); + } + + total_bytes = (ecm->echo_bytes ? ecm->rx_total : ecm->tx_total); + transfer_type = ecm->echo_bytes ? "full-duplex" : "half-duplex"; + ec_cli ("%lld bytes (%lld mbytes, %lld gbytes) in %.2f seconds", total_bytes, + total_bytes / (1ULL << 20), total_bytes / (1ULL << 30), total_delta); + ec_cli ("%.2f bytes/second %s", ((f64) total_bytes) / (total_delta), + transfer_type); + ec_cli ("%.4f gbit/second %s", + (((f64) total_bytes * 8.0) / total_delta / 1e9), transfer_type); +} + static clib_error_t * ec_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) { unformat_input_t _line_input, *line_input = &_line_input; - char *default_uri = "tcp://6.0.1.1/1234", *transfer_type; + char *default_uri = "tcp://6.0.1.1/1234"; ec_main_t *ecm = &ec_main; uword *event_data = 0, event_type; clib_error_t *error = 0; int rv, timed_run_conflict = 0, tput_conflict = 0, had_config = 1; - u64 total_bytes; - f64 delta; - ec_worker_t *wrk; - ec_session_t *sess; + f64 delta, wait_time = 0; if (ecm->test_client_attached) return clib_error_return (0, "failed: already running!"); @@ -1291,11 +1460,11 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, &ecm->bytes_to_send)) timed_run_conflict++; else if (unformat (line_input, "test-timeout %f", &ecm->test_timeout)) - ; + timed_run_conflict++; else if (unformat (line_input, "syn-timeout %f", &ecm->syn_timeout)) ; else if (unformat (line_input, "run-time %f", &ecm->run_time)) - ; + ecm->test_timeout = ecm->run_time; else if (unformat (line_input, "echo-bytes")) ecm->echo_bytes = 1; else if (unformat (line_input, "fifo-size %U", unformat_memory_size, @@ -1307,9 +1476,9 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "private-segment-size %U", unformat_memory_size, &ecm->private_segment_size)) ; - else if (unformat (line_input, "throughput %U", unformat_memory_size, + else if (unformat (line_input, "throughput %U", unformat_base10, &ecm->throughput)) - ; + ecm->throughput /= 8; else if (unformat (line_input, "max-tx-chunk %U", unformat_memory_size, &ecm->max_chunk_bytes)) tput_conflict = 1; @@ -1320,6 +1489,14 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, else if (unformat (line_input, "client-batch %d", &ecm->connections_per_batch)) ; + else if (unformat (line_input, "report-interval %u", + &ecm->report_interval)) + ; + else if (unformat (line_input, "report-interval-total %u", + &ecm->report_interval)) + ecm->report_interval_total = 1; + else if (unformat (line_input, "report-interval")) + ecm->report_interval = 1; else if (unformat (line_input, "appns %_%v%_", &ecm->appns_id)) ; else if (unformat (line_input, "all-scope")) @@ -1437,75 +1614,76 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, } /* Testing officially starts now */ ecm->test_start_time = vlib_time_now (ecm->vlib_main); + if (ecm->report_interval) + ecm->last_print_time = ecm->test_start_time; ec_cli ("Test started at %.6f", ecm->test_start_time); /* - * If a timed run, wait and expire timer - */ - if (ecm->run_time) - { - vlib_process_suspend (vm, ecm->run_time); - ec_main.timer_expired = true; - } - - /* - * Wait for the sessions to finish or test_timeout seconds pass + * Wait for the sessions to finish or test_timeout (timeout or length + * of timed run) seconds pass. If providing periodic reports, wake up + * every now and then to print them and loop. */ - vlib_process_wait_for_event_or_clock (vm, ecm->test_timeout); - event_type = vlib_process_get_events (vm, &event_data); - switch (event_type) + u8 main_loop_done = false, print_header = true, report_timeout = false; + do { - case ~0: - ec_cli ("Timeout at %.6f with %d sessions still active...", - vlib_time_now (ecm->vlib_main), ecm->ready_connections); - if (ecm->transport_proto == TRANSPORT_PROTO_UDP) + if (ecm->report_interval) { - u64 received_bytes = 0; - u64 sent_bytes = 0; - vec_foreach (wrk, ecm->wrk) - pool_foreach (sess, wrk->sessions) - { - received_bytes += sess->bytes_received; - sent_bytes += sess->bytes_sent; - } - ec_cli ("Received %llu bytes out of %llu sent (%llu target)", - received_bytes, sent_bytes, - ecm->bytes_to_send * ecm->n_clients); + delta = vlib_time_now (vm) - ecm->test_start_time; + if (delta + (f64) ecm->report_interval > ecm->test_timeout) + { + report_timeout = true; + wait_time = ecm->test_timeout - delta; + } + else + wait_time = (f64) ecm->report_interval; } else - error = clib_error_return (0, "failed: timeout with %d sessions", - ecm->ready_connections); - goto stop_test; + { + report_timeout = true; + wait_time = (f64) ecm->test_timeout; + } - case EC_CLI_TEST_DONE: - ecm->test_end_time = vlib_time_now (vm); - ec_cli ("Test finished at %.6f", ecm->test_end_time); - break; + vlib_process_wait_for_event_or_clock (vm, wait_time); + event_type = vlib_process_get_events (vm, &event_data); + switch (event_type) + { + case ~0: + if (report_timeout) + { + if (ecm->run_time) + { + ecm->timer_expired = true; + ecm->test_timeout += 1; + break; + } + ec_print_timeout_stats (vm); + if (ecm->transport_proto != TRANSPORT_PROTO_UDP) + error = + clib_error_return (0, "failed: timeout with %d sessions", + ecm->ready_connections); + goto stop_test; + } + else + { + ec_print_periodic_stats (vm, print_header, false); + if (PREDICT_FALSE (print_header)) + print_header = false; + } + break; - default: - ec_cli ("unexpected event(3): %d", event_type); - error = - clib_error_return (0, "failed: unexpected event(3): %d", event_type); - goto stop_test; - } + case EC_CLI_TEST_DONE: + ecm->test_end_time = vlib_time_now (vm); + main_loop_done = true; + break; - /* - * Done. Compute stats - */ - if (ecm->transport_proto == TRANSPORT_PROTO_TCP || - (ecm->transport_proto == TRANSPORT_PROTO_UDP && ecm->echo_bytes)) - { - /* display rtt stats in milliseconds */ - if (ecm->rtt_stats.n_sum == 1) - ec_cli ("%.05fms roundtrip", ecm->rtt_stats.min_rtt * 1000); - else if (ecm->rtt_stats.n_sum > 1) - ec_cli ("%.05fms/%.05fms/%.05fms min/avg/max roundtrip", - ecm->rtt_stats.min_rtt * 1000, - ecm->rtt_stats.sum_rtt / ecm->rtt_stats.n_sum * 1000, - ecm->rtt_stats.max_rtt * 1000); - else - ec_cli ("error measuring roundtrip time"); + default: + ec_cli ("unexpected event(3): %d", event_type); + error = clib_error_return (0, "failed: unexpected event(3): %d", + event_type); + goto stop_test; + } } + while (!main_loop_done); delta = ecm->test_end_time - ecm->test_start_time; if (delta == 0.0) @@ -1514,15 +1692,12 @@ ec_command_fn (vlib_main_t *vm, unformat_input_t *input, error = clib_error_return (0, "failed: zero delta-t"); goto stop_test; } - - total_bytes = (ecm->echo_bytes ? ecm->rx_total : ecm->tx_total); - transfer_type = ecm->echo_bytes ? "full-duplex" : "half-duplex"; - ec_cli ("%lld bytes (%lld mbytes, %lld gbytes) in %.2f seconds", total_bytes, - total_bytes / (1ULL << 20), total_bytes / (1ULL << 30), delta); - ec_cli ("%.2f bytes/second %s", ((f64) total_bytes) / (delta), - transfer_type); - ec_cli ("%.4f gbit/second %s", (((f64) total_bytes * 8.0) / delta / 1e9), - transfer_type); + /* Print last interval */ + if (ecm->report_interval && + vlib_time_now (vm) - ecm->last_print_time >= 0.1f) + ec_print_periodic_stats (vm, print_header, true); + ec_cli ("Test finished at %.6f", ecm->test_end_time); + ec_print_final_stats (vm, delta); if (ecm->cfg.test_bytes && ecm->test_failed) error = clib_error_return (0, "failed: test bytes"); @@ -1578,7 +1753,9 @@ VLIB_CLI_COMMAND (ec_command, static) = { "[run-time