From 84d52285afd1b478d616026a3d63a714abb29f13 Mon Sep 17 00:00:00 2001 From: Semir Sionek Date: Wed, 16 Jul 2025 12:54:25 +0000 Subject: [PATCH] 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 ee510545dac..65bd49f825b 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 2612e2a3893..99d69280bde 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 @@ parse_config: 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 01b807a9466..3b3fb7626e8 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 05851329916..abf557c33e8 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); } /* -- 2.16.6