From b56475ebbcb2d4fc64b236a3cc24bee6328ff019 Mon Sep 17 00:00:00 2001 From: Marcos Schwarz Date: Thu, 7 Dec 2023 12:37:42 -0300 Subject: [PATCH 01/12] add UDP GSO/GRO support on Linux and --no-gsro option This change adds first-class support for Linux UDP Generic Segmentation Offload (GSO) and Generic Receive Offload (GRO) in iperf3. At configure time, the build detects availability of the UDP_SEGMENT and UDP_GRO socket options via and enables code paths accordingly. On capable systems, these features are now enabled by default for UDP tests. A new command-line flag, --no-gsro, allows users to disable GSO and GRO even when supported by the kernel. Help text is included in usage_longstr. Additional changes: - Updated iperf_settings to track GSO/GRO state and buffer/segment sizes. - Added a warning if the configured UDP block size exceeds the TCP MSS. - Ensured behavior is unchanged on systems without GSO/GRO support. GSO can reduce CPU overhead on send by offloading UDP segmentation to the kernel/NIC. GRO can reduce per-packet processing cost on receive by coalescing incoming UDP segments. Together they can improve throughput and efficiency in high-rate UDP tests on modern Linux systems. # Conflicts: # src/iperf_api.c # src/iperf_api.h --- configure | 72 ++++++++++++ configure.ac | 24 ++++ src/iperf.h | 12 ++ src/iperf_api.c | 74 +++++++++++- src/iperf_api.h | 1 + src/iperf_client_api.c | 56 +++++---- src/iperf_config.h.in | 6 + src/iperf_locale.c | 3 + src/iperf_udp.c | 261 ++++++++++++++++++++++++++++++++++++++++- src/net.c | 160 +++++++++++++++++++++++++ src/net.h | 6 + 11 files changed, 650 insertions(+), 25 deletions(-) diff --git a/configure b/configure index f4d488697..82ede8a5e 100755 --- a/configure +++ b/configure @@ -17399,6 +17399,78 @@ printf "%s\n" "#define HAVE_IPPROTO_MPTCP 1" >>confdefs.h fi +# Check for UDP_SEGMENT sockopt +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking UDP_SEGMENT socket option" >&5 +printf %s "checking UDP_SEGMENT socket option... " >&6; } +if test ${iperf3_cv_header_udp_segment+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +int foo = UDP_SEGMENT; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + iperf3_cv_header_udp_segment=yes +else case e in #( + e) iperf3_cv_header_udp_segment=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $iperf3_cv_header_udp_segment" >&5 +printf "%s\n" "$iperf3_cv_header_udp_segment" >&6; } +if test "x$iperf3_cv_header_udp_segment" = "xyes"; then + +printf "%s\n" "#define HAVE_UDP_SEGMENT 1" >>confdefs.h + +fi + +# Check for UDP_GRO sockopt +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking UDP_GRO socket option" >&5 +printf %s "checking UDP_GRO socket option... " >&6; } +if test ${iperf3_cv_header_udp_gro+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +int foo = UDP_GRO; + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + iperf3_cv_header_udp_gro=yes +else case e in #( + e) iperf3_cv_header_udp_gro=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $iperf3_cv_header_udp_gro" >&5 +printf "%s\n" "$iperf3_cv_header_udp_gro" >&6; } +if test "x$iperf3_cv_header_udp_gro" = "xyes"; then + +printf "%s\n" "#define HAVE_UDP_GRO 1" >>confdefs.h + +fi + # Check if we need -lrt for clock_gettime { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5 printf %s "checking for library containing clock_gettime... " >&6; } diff --git a/configure.ac b/configure.ac index 11f458690..27ce742f5 100644 --- a/configure.ac +++ b/configure.ac @@ -375,6 +375,30 @@ if test "x$iperf3_cv_header_ipproto_mptcp" = "xyes"; then AC_DEFINE([HAVE_IPPROTO_MPTCP], [1], [Have MPTCP protocol.]) fi +# Check for UDP_SEGMENT sockopt +AC_CACHE_CHECK([UDP_SEGMENT socket option], +[iperf3_cv_header_udp_segment], +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#include ]], + [[int foo = UDP_SEGMENT;]])], + iperf3_cv_header_udp_segment=yes, + iperf3_cv_header_udp_segment=no)) +if test "x$iperf3_cv_header_udp_segment" = "xyes"; then + AC_DEFINE([HAVE_UDP_SEGMENT], [1], [Have UDP_SEGMENT sockopt.]) +fi + +# Check for UDP_GRO sockopt +AC_CACHE_CHECK([UDP_GRO socket option], +[iperf3_cv_header_udp_gro], +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#include ]], + [[int foo = UDP_GRO;]])], + iperf3_cv_header_udp_gro=yes, + iperf3_cv_header_udp_gro=no)) +if test "x$iperf3_cv_header_udp_gro" = "xyes"; then + AC_DEFINE([HAVE_UDP_GRO], [1], [Have UDP_GRO sockopt.]) +fi + # Check if we need -lrt for clock_gettime AC_SEARCH_LIBS(clock_gettime, [rt posix4]) # Check for clock_gettime support diff --git a/src/iperf.h b/src/iperf.h index e1dd3326c..de40cb630 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -191,6 +191,15 @@ struct iperf_settings int cntl_ka_keepidle; /* Control TCP connection Keepalive idle time (TCP_KEEPIDLE) */ int cntl_ka_interval; /* Control TCP connection Keepalive interval between retries (TCP_KEEPINTV) */ int cntl_ka_count; /* Control TCP connection Keepalive number of retries (TCP_KEEPCNT) */ +#ifdef HAVE_UDP_SEGMENT + int gso; + int gso_dg_size; + int gso_bf_size; +#endif +#ifdef HAVE_UDP_GRO + int gro; + int gro_bf_size; +#endif }; struct iperf_test; @@ -486,4 +495,7 @@ extern int gerror; /* error value from getaddrinfo(3), for use in internal error /* In Reverse mode, maximum number of packets to wait for "accept" response - to handle out of order packets */ #define MAX_REVERSE_OUT_OF_ORDER_PACKETS 2 +#define GSO_BF_MAX_SIZE MAX_UDP_BLOCKSIZE +#define GRO_BF_MAX_SIZE MAX_UDP_BLOCKSIZE + #endif /* !__IPERF_H */ diff --git a/src/iperf_api.c b/src/iperf_api.c index c645ef5cf..5a7e63ca5 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1188,6 +1188,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) #endif /* HAVE_TCP_KEEPALIVE */ #if defined(HAVE_IPPROTO_MPTCP) {"mptcp", no_argument, NULL, 'm'}, +#endif +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) + {"no-gsro", no_argument, NULL, OPT_NO_GSRO}, #endif {"debug", optional_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, @@ -1789,6 +1792,17 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) set_protocol(test, Ptcp); test->mptcp = 1; break; +#endif +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) + case OPT_NO_GSRO: + /* Disable GSO/GRO which would otherwise be enabled by default */ +#ifdef HAVE_UDP_SEGMENT + test->settings->gso = 0; +#endif +#ifdef HAVE_UDP_GRO + test->settings->gro = 0; +#endif + break; #endif case 'h': usage_long(stdout); @@ -1810,6 +1824,8 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; } +/* GSO/GRO are enabled by default when available, disabled only via --no-gsro */ + #if defined(HAVE_SSL) if (test->role == 's' && (client_username || client_rsa_public_key)){ @@ -1914,6 +1930,20 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) i_errno = IEUDPBLOCKSIZE; return -1; } + +#ifdef HAVE_UDP_SEGMENT + if (test->protocol->id == Pudp && test->settings->gso) { + test->settings->gso_dg_size = blksize; + /* use the multiple of datagram size for the best efficiency. */ + if (test->settings->gso_dg_size > 0) { + test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; + } else { + /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ + test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */ + } + } +#endif + test->settings->blksize = blksize; if (!rate_flag) @@ -2580,6 +2610,18 @@ get_parameters(struct iperf_test *test) test->settings->socket_bufsize = j_p->valueint; if ((j_p = iperf_cJSON_GetObjectItemType(j, "len", cJSON_Number)) != NULL) test->settings->blksize = j_p->valueint; +#ifdef HAVE_UDP_SEGMENT + if (test->protocol->id == Pudp && test->settings->gso == 1) { + test->settings->gso_dg_size = test->settings->blksize; + /* use the multiple of datagram size for the best efficiency. */ + if (test->settings->gso_dg_size > 0) { + test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; + } else { + /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ + test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */ + } + } +#endif if ((j_p = iperf_cJSON_GetObjectItemType(j, "bandwidth", cJSON_Number)) != NULL) test->settings->rate = j_p->valueint; if ((j_p = iperf_cJSON_GetObjectItemType(j, "fqrate", cJSON_Number)) != NULL) @@ -3239,6 +3281,15 @@ iperf_defaults(struct iperf_test *testp) testp->settings->fqrate = 0; testp->settings->pacing_timer = DEFAULT_PACING_TIMER; testp->settings->burst = 0; +#ifdef HAVE_UDP_SEGMENT + testp->settings->gso = 1; /* Enable GSO by default */ + testp->settings->gso_dg_size = 0; + testp->settings->gso_bf_size = GSO_BF_MAX_SIZE; +#endif +#ifdef HAVE_UDP_GRO + testp->settings->gro = 1; /* Enable GRO by default */ + testp->settings->gro_bf_size = GRO_BF_MAX_SIZE; +#endif testp->settings->mss = 0; testp->settings->bytes = 0; testp->settings->blocks = 0; @@ -3552,6 +3603,13 @@ iperf_reset_test(struct iperf_test *test) test->settings->burst = 0; test->settings->mss = 0; test->settings->tos = 0; +#ifdef HAVE_UDP_SEGMENT + test->settings->gso_dg_size = 0; + test->settings->gso_bf_size = GSO_BF_MAX_SIZE; +#endif +#ifdef HAVE_UDP_GRO + test->settings->gro_bf_size = GRO_BF_MAX_SIZE; +#endif test->settings->dont_fragment = 0; test->zerocopy = 0; test->settings->skip_rx_copy = 0; @@ -4716,6 +4774,7 @@ iperf_new_stream(struct iperf_test *test, int s, int sender) { struct iperf_stream *sp; int ret = 0; + int size; char template[1024]; if (test->tmp_template) { @@ -4774,13 +4833,24 @@ iperf_new_stream(struct iperf_test *test, int s, int sender) free(sp); return NULL; } - if (ftruncate(sp->buffer_fd, test->settings->blksize) < 0) { + size = test->settings->blksize; +#ifdef HAVE_UDP_SEGMENT + if (test->protocol->id == Pudp && test->settings->gso && (size < test->settings->gso_bf_size)) + size = test->settings->gso_bf_size; +#endif +#ifdef HAVE_UDP_GRO + if (test->protocol->id == Pudp && test->settings->gro && (size < test->settings->gro_bf_size)) + size = test->settings->gro_bf_size; +#endif + if (sp->test->debug) + printf("Buffer %d bytes\n", size); + if (ftruncate(sp->buffer_fd, size) < 0) { i_errno = IECREATESTREAM; free(sp->result); free(sp); return NULL; } - sp->buffer = (char *) mmap(NULL, test->settings->blksize, PROT_READ|PROT_WRITE, MAP_SHARED, sp->buffer_fd, 0); + sp->buffer = (char *) mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, sp->buffer_fd, 0); if (sp->buffer == MAP_FAILED) { i_errno = IECREATESTREAM; free(sp->result); diff --git a/src/iperf_api.h b/src/iperf_api.h index dcb3381be..6c0a9af64 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -106,6 +106,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t; #define OPT_SKIP_RX_COPY 32 #define OPT_JSON_STREAM_FULL_OUTPUT 33 #define OPT_SERVER_MAX_DURATION 34 +#define OPT_NO_GSRO 35 /* states */ #define TEST_START 1 diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index 37d741f87..edc77ed23 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -505,29 +505,41 @@ iperf_connect(struct iperf_test *test) * the user always has the option to override. */ if (test->protocol->id == Pudp) { - if (test->settings->blksize == 0) { - if (test->ctrl_sck_mss) { - test->settings->blksize = test->ctrl_sck_mss; - } - else { - test->settings->blksize = DEFAULT_UDP_BLKSIZE; - } - if (test->verbose) { - printf("Setting UDP block size to %d\n", test->settings->blksize); - } - } + if (test->settings->blksize == 0) { + if (test->ctrl_sck_mss) { + test->settings->blksize = test->ctrl_sck_mss; + } + else { + test->settings->blksize = DEFAULT_UDP_BLKSIZE; + } + if (test->verbose) { + printf("Setting UDP block size to %d\n", test->settings->blksize); + } + } +#ifdef HAVE_UDP_SEGMENT + if (test->settings->gso) { + test->settings->gso_dg_size = test->settings->blksize; + /* use the multiple of datagram size for the best efficiency. */ + if (test->settings->gso_dg_size > 0) { + test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; + } else { + /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ + test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */ + } + } +#endif - /* - * Regardless of whether explicitly or implicitly set, if the - * block size is larger than the MSS, print a warning. - */ - if (test->ctrl_sck_mss > 0 && - test->settings->blksize > test->ctrl_sck_mss) { - char str[WARN_STR_LEN]; - snprintf(str, sizeof(str), - "UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss); - warning(str); - } + /* + * Regardless of whether explicitly or implicitly set, if the + * block size is larger than the MSS, print a warning. + */ + if (test->ctrl_sck_mss > 0 && + test->settings->blksize > test->ctrl_sck_mss) { + char str[WARN_STR_LEN]; + snprintf(str, sizeof(str), + "UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss); + warning(str); + } } return 0; diff --git a/src/iperf_config.h.in b/src/iperf_config.h.in index 393dcdfa2..fa22a17e0 100644 --- a/src/iperf_config.h.in +++ b/src/iperf_config.h.in @@ -132,6 +132,12 @@ /* Have TCP_USER_TIMEOUT sockopt. */ #undef HAVE_TCP_USER_TIMEOUT +/* Have UDP_GRO sockopt. */ +#undef HAVE_UDP_GRO + +/* Have UDP_SEGMENT sockopt. */ +#undef HAVE_UDP_SEGMENT + /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 8ed673866..20d404a46 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -219,6 +219,9 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" " --extra-data str data string to include in client and server JSON\n" " --get-server-output get results from server\n" " --udp-counters-64bit use 64-bit counters in UDP test packets\n" +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) + " --no-gsro disable UDP GSO/GRO (Generic Segmentation/Receive Offload)\n" +#endif " --repeating-payload use repeating pattern in payload, instead of\n" " randomized payload (like in iperf2)\n" #if defined(HAVE_DONT_FRAGMENT) diff --git a/src/iperf_udp.c b/src/iperf_udp.c index c8835e6d7..711f793b6 100644 --- a/src/iperf_udp.c +++ b/src/iperf_udp.c @@ -24,6 +24,8 @@ * This code is distributed under a BSD style license, see the LICENSE * file for complete information. */ +#include "iperf_config.h" + #include #include #include @@ -38,6 +40,9 @@ #include #include #include +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) +#include +#endif #include "iperf.h" #include "iperf_api.h" @@ -72,7 +77,42 @@ iperf_udp_recv(struct iperf_stream *sp) } #endif /* HAVE_MSG_TRUNC */ +#ifdef HAVE_UDP_GRO + int tmp_r; + int dgram_sz; + int cnt = 0; + char *dgram_buf; + char *dgram_buf_end; + const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ + + /* Initialize dgram_sz for both GRO enabled and disabled cases */ + dgram_sz = sp->settings->blksize; + + if (sp->test->settings->gro) { + size = sp->test->settings->gro_bf_size; + r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz); + if (dgram_sz == -1) { + /* + * For corner case where the socket configuration is + * successful but the kernel network layer doesn't provide + * GRO-format data or ancillary info. + */ + dgram_sz = sp->settings->blksize; + } + /* Validate dgram_sz against reasonable bounds */ + if (dgram_sz <= 0 || dgram_sz < min_pkt_size || dgram_sz > sp->test->settings->gro_bf_size) { + if (test->debug_level >= DEBUG_LEVEL_INFO) + printf("Invalid GRO dgram_sz %d, falling back to blksize %d\n", dgram_sz, sp->settings->blksize); + dgram_sz = sp->settings->blksize; + } + } else { + /* GRO available but disabled - use normal UDP receive and single packet size */ + r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); + dgram_sz = sp->settings->blksize; + } +#else r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); +#endif /* * If we got an error in the read, or if we didn't read anything @@ -96,6 +136,85 @@ iperf_udp_recv(struct iperf_stream *sp) sp->result->bytes_received += r; sp->result->bytes_received_this_interval += r; + if (sp->test->debug) + printf("received %d bytes of %d, total %" PRIu64 "\n", r, size, sp->result->bytes_received); + +#ifdef HAVE_UDP_GRO + if (sp->test->settings->gro) { + /* GRO enabled - process multiple datagrams */ + dgram_buf = sp->buffer; + dgram_buf_end = sp->buffer + r; + tmp_r = r; + + /* Ensure we process complete datagrams only */ + while (tmp_r >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) { + cnt++; + if (sp->test->debug) + printf("%d (%d) remaining %d\n", cnt, dgram_sz, tmp_r); + + /* Ensure we have enough bytes for the packet header */ + if (tmp_r < min_pkt_size) { + if (test->debug_level >= DEBUG_LEVEL_INFO) + printf("Incomplete packet header: %d bytes remaining\n", tmp_r); + break; + } + + if (sp->test->udp_counters_64bit) { + /* Verify we have enough space for 64-bit counter */ + if (tmp_r < sizeof(uint32_t) * 2 + sizeof(uint64_t)) { + if (test->debug_level >= DEBUG_LEVEL_INFO) + printf("Incomplete 64-bit packet: %d bytes remaining\n", tmp_r); + break; + } + memcpy(&sec, dgram_buf, sizeof(sec)); + memcpy(&usec, dgram_buf+4, sizeof(usec)); + memcpy(&pcount, dgram_buf+8, sizeof(pcount)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = be64toh(pcount); + sent_time.secs = sec; + sent_time.usecs = usec; + } + else { + uint32_t pc; + memcpy(&sec, dgram_buf, sizeof(sec)); + memcpy(&usec, dgram_buf+4, sizeof(usec)); + memcpy(&pc, dgram_buf+8, sizeof(pc)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = ntohl(pc); + sent_time.secs = sec; + sent_time.usecs = usec; + } + dgram_buf += dgram_sz; + tmp_r -= dgram_sz; + } // end while loop + } else { + /* GRO disabled - process as single normal UDP packet */ + /* Dig the various counters out of the incoming UDP packet */ + if (test->udp_counters_64bit) { + memcpy(&sec, sp->buffer, sizeof(sec)); + memcpy(&usec, sp->buffer+4, sizeof(usec)); + memcpy(&pcount, sp->buffer+8, sizeof(pcount)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = be64toh(pcount); + sent_time.secs = sec; + sent_time.usecs = usec; + } + else { + uint32_t pc; + memcpy(&sec, sp->buffer, sizeof(sec)); + memcpy(&usec, sp->buffer+4, sizeof(usec)); + memcpy(&pc, sp->buffer+8, sizeof(pc)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = ntohl(pc); + sent_time.secs = sec; + sent_time.usecs = usec; + } + } +#else /* Dig the various counters out of the incoming UDP packet */ if (test->udp_counters_64bit) { memcpy(&sec, sp->buffer, sizeof(sec)); @@ -118,6 +237,7 @@ iperf_udp_recv(struct iperf_stream *sp) sent_time.secs = sec; sent_time.usecs = usec; } +#endif /* HAVE_UDP_GRO */ if (test->debug_level >= DEBUG_LEVEL_DEBUG) fprintf(stderr, "pcount %" PRIu64 " packet_count %" PRIu64 "\n", pcount, sp->packet_count); @@ -214,6 +334,83 @@ iperf_udp_send(struct iperf_stream *sp) int size = sp->settings->blksize; struct iperf_time before; +#ifdef HAVE_UDP_SEGMENT + int dgram_sz; + int buf_sz; + int cnt = 0; + char *dgram_buf; + char *dgram_buf_end; + const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ + + if (sp->test->settings->gso) { + dgram_sz = sp->test->settings->gso_dg_size; + buf_sz = sp->test->settings->gso_bf_size; + /* Validate GSO parameters */ + if (dgram_sz <= 0 || dgram_sz < min_pkt_size || dgram_sz > buf_sz) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Invalid GSO dgram_sz %d for buf_sz %d, disabling GSO\n", dgram_sz, buf_sz); + dgram_sz = buf_sz = size; + sp->test->settings->gso = 0; /* Disable GSO for safety */ + } + } else { + dgram_sz = buf_sz = size; + } + + dgram_buf = sp->buffer; + dgram_buf_end = sp->buffer + buf_sz; + + while (buf_sz > 0 && dgram_buf + dgram_sz <= dgram_buf_end) { + cnt++; + + if (sp->test->debug) + printf("%d (%d) remaining %d\n", cnt, dgram_sz, buf_sz); + + /* Prevent buffer underflow */ + if (buf_sz < dgram_sz) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Buffer underflow protection: buf_sz %d < dgram_sz %d\n", buf_sz, dgram_sz); + break; + } + + iperf_time_now(&before); + ++sp->packet_count; + + if (sp->test->udp_counters_64bit) { + + uint32_t sec, usec; + uint64_t pcount; + + sec = htonl(before.secs); + usec = htonl(before.usecs); + pcount = htobe64(sp->packet_count); + + memcpy(dgram_buf, &sec, sizeof(sec)); + memcpy(dgram_buf+4, &usec, sizeof(usec)); + memcpy(dgram_buf+8, &pcount, sizeof(pcount)); + + } + else { + + uint32_t sec, usec, pcount; + + sec = htonl(before.secs); + usec = htonl(before.usecs); + pcount = htonl(sp->packet_count); + + memcpy(dgram_buf, &sec, sizeof(sec)); + memcpy(dgram_buf+4, &usec, sizeof(usec)); + memcpy(dgram_buf+8, &pcount, sizeof(pcount)); + + } + dgram_buf += dgram_sz; + buf_sz -= dgram_sz; + } + + /* Warn if we didn't process all the buffer due to size mismatch */ + if (buf_sz > 0 && sp->test->debug_level >= DEBUG_LEVEL_INFO) { + printf("GSO: %d bytes remaining unprocessed\n", buf_sz); + } +#else iperf_time_now(&before); ++sp->packet_count; @@ -245,7 +442,14 @@ iperf_udp_send(struct iperf_stream *sp) memcpy(sp->buffer+8, &pcount, sizeof(pcount)); } +#endif /* HAVE_UDP_SEGMENT */ +#ifdef HAVE_UDP_SEGMENT + if (sp->test->settings->gso) { + size = sp->test->settings->gso_bf_size; + r = Nwrite_gso(sp->socket, sp->buffer, size, Pudp, sp->test->settings->gso_dg_size); + } else +#endif r = Nwrite(sp->socket, sp->buffer, size, Pudp); if (r <= 0) { @@ -262,7 +466,7 @@ iperf_udp_send(struct iperf_stream *sp) sp->result->bytes_sent_this_interval += r; if (sp->test->debug_level >= DEBUG_LEVEL_DEBUG) - printf("sent %d bytes of %d, total %" PRIu64 "\n", r, sp->settings->blksize, sp->result->bytes_sent); + printf("sent %d bytes of %d, total %" PRIu64 "\n", r, size, sp->result->bytes_sent); return r; } @@ -372,6 +576,42 @@ iperf_udp_buffercheck(struct iperf_test *test, int s) return rc; } +#ifdef HAVE_UDP_SEGMENT +int +iperf_udp_gso(struct iperf_test *test, int s) +{ + int rc; + int gso = test->settings->gso_dg_size; + + rc = setsockopt(s, IPPROTO_UDP, UDP_SEGMENT, (char*) &gso, sizeof(gso)); + if (rc) { + iperf_printf(test, "No GSO (%d)\n", rc); + test->settings->gso = 0; + } else + iperf_printf(test, "GSO (%d)\n", gso); + + return rc; +} +#endif + +#ifdef HAVE_UDP_GRO +int +iperf_udp_gro(struct iperf_test *test, int s) +{ + int rc; + int gro = 1; + + rc = setsockopt(s, IPPROTO_UDP, UDP_GRO, (char*) &gro, sizeof(gro)); + if (rc) { + iperf_printf(test, "No GRO (%d)\n", rc); + test->settings->gro = 0; + } else + iperf_printf(test, "GRO\n"); + + return rc; +} +#endif + /* * iperf_udp_accept * @@ -431,6 +671,15 @@ iperf_udp_accept(struct iperf_test *test) } } +#ifdef HAVE_UDP_SEGMENT + if (test->settings->gso) + iperf_udp_gso(test, s); +#endif +#ifdef HAVE_UDP_GRO + if (test->settings->gro) + iperf_udp_gro(test, s); +#endif + #if defined(HAVE_SO_MAX_PACING_RATE) /* If socket pacing is specified, try it. */ if (test->settings->fqrate) { @@ -530,6 +779,16 @@ iperf_udp_connect(struct iperf_test *test) if (rc < 0) /* error */ return rc; + +#ifdef HAVE_UDP_SEGMENT + if (test->settings->gso) + iperf_udp_gso(test, s); +#endif +#ifdef HAVE_UDP_GRO + if (test->settings->gro) + iperf_udp_gro(test, s); +#endif + /* * If the socket buffer was too small, but it was the default * size, then try explicitly setting it to something larger. diff --git a/src/net.c b/src/net.c index aa6e8cad3..8fbc4a0a2 100644 --- a/src/net.c +++ b/src/net.c @@ -38,6 +38,11 @@ #include #include #include +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) +#include +#endif + +#include "iperf.h" #ifdef HAVE_SENDFILE #ifdef linux @@ -520,6 +525,88 @@ Nrecv_no_select(int fd, char *buf, size_t count, int prot, int sock_opt) return count - nleft; } +#ifdef HAVE_UDP_GRO +static int recv_msg_gro(int fd, char *buf, int len, int *gso_size) +{ + char control[CMSG_SPACE(sizeof(uint16_t))] = {0}; + struct msghdr msg = {0}; + struct iovec iov = {0}; + struct cmsghdr *cmsg; + uint16_t *gsosizeptr; + int ret; + + /* Input validation */ + if (!buf || len <= 0 || !gso_size) { + return -1; + } + + iov.iov_base = buf; + iov.iov_len = len; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + *gso_size = -1; + ret = recvmsg(fd, &msg, MSG_DONTWAIT); + + if (ret > 0) { + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_UDP && cmsg->cmsg_type == UDP_GRO) { + /* Validate cmsg data length */ + if (cmsg->cmsg_len >= CMSG_LEN(sizeof(uint16_t))) { + gsosizeptr = (uint16_t *) CMSG_DATA(cmsg); + *gso_size = *gsosizeptr; + /* Sanity check the gso_size value */ + if (*gso_size <= 0 || *gso_size > len) { + *gso_size = -1; /* Mark as invalid */ + } + } + break; + } + } + } + + return ret; +} + +int +Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz) +{ + register ssize_t r; + + /* Input validation */ + if (!buf || count <= 0 || !dgram_sz) { + return NET_HARDERROR; + } + + /* Limit maximum buffer size to prevent excessive memory usage */ + if (count > MAX_UDP_BLOCKSIZE) { + count = MAX_UDP_BLOCKSIZE; + } + + r = recv_msg_gro(fd, buf, count, dgram_sz); + + if (r < 0) { + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } else { + printf("\nUnexpected error (%d)\n", errno); + return NET_HARDERROR; + } + } + + /* Additional validation of returned dgram_sz */ + if (r > 0 && *dgram_sz > 0 && *dgram_sz > r) { + /* dgram_sz shouldn't be larger than actual received data */ + *dgram_sz = r; + } + + return r; +} +#endif /* HAVE_UDP_GRO */ /* * N W R I T E @@ -559,6 +646,79 @@ Nwrite(int fd, const char *buf, size_t count, int prot) return count; } +#ifdef HAVE_UDP_SEGMENT +static void udp_msg_gso(struct cmsghdr *cm, uint16_t gso_size) +{ + uint16_t *valp; + + cm->cmsg_level = IPPROTO_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(gso_size)); + valp = (void *) CMSG_DATA(cm); + *valp = gso_size; +} + +static int udp_sendmsg_gso(int fd, const char *buf, size_t count, uint16_t gso_size) +{ + char control[CMSG_SPACE(sizeof(gso_size))] = {0}; + struct msghdr msg = {0}; + struct iovec iov = {0}; + size_t msg_controllen; + struct cmsghdr *cmsg; + int ret; + + iov.iov_base = (void *) buf; + iov.iov_len = count; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + cmsg = CMSG_FIRSTHDR(&msg); + + udp_msg_gso(cmsg, gso_size); + + msg_controllen = CMSG_SPACE(sizeof(gso_size)); + msg.msg_controllen = msg_controllen; + + ret = sendmsg(fd, &msg, 0); + + if (ret != iov.iov_len) + printf("msg: %u != %llu\n", ret, (unsigned long long) iov.iov_len); + + return ret; +} + +int +Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size) +{ + register ssize_t r; + + r = udp_sendmsg_gso(fd, buf, count, gso_size); + + if (r < 0) { + switch (errno) { + case EINTR: + case EAGAIN: +#if (EAGAIN != EWOULDBLOCK) + case EWOULDBLOCK: +#endif + printf("\nerrono (%d)\n", errno); + return 0; + + case ENOBUFS: + printf("\nUnexpected error ENOBUFS (%d)\n", ENOBUFS); + return NET_SOFTERROR; + + default: + printf("\nUnexpected error (%d)\n", errno); + return NET_HARDERROR; + } + } + return r; +} +#endif /* HAVE_UDP_SEGMENT */ int has_sendfile(void) diff --git a/src/net.h b/src/net.h index 026dfd030..de2554fc8 100644 --- a/src/net.h +++ b/src/net.h @@ -41,6 +41,12 @@ int Nsendfile(int fromfd, int tofd, const char *buf, size_t count) /* __attribut int setnonblocking(int fd, int nonblocking); int getsockdomain(int sock); int parse_qos(const char *tos); +#ifdef HAVE_UDP_GRO +int Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz); +#endif +#ifdef HAVE_UDP_SEGMENT +int Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size); +#endif #define NET_SOFTERROR -1 #define NET_HARDERROR -2 From 67c74845362f8a6c3dfb6b5c75884875627921fb Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Thu, 4 Sep 2025 21:43:41 +0000 Subject: [PATCH 02/12] fix(GRO): fix per-datagram parsing and loss accounting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse coalesced GRO payloads using the negotiated blksize stride and account loss/jitter per datagram. Avoids inflated “loss” when kernel GRO hints are unreliable. No change to GSO send behavior. --- src/iperf_udp.c | 199 ++++++++++++++++-------------------------------- 1 file changed, 66 insertions(+), 133 deletions(-) diff --git a/src/iperf_udp.c b/src/iperf_udp.c index 711f793b6..cfe95ab13 100644 --- a/src/iperf_udp.c +++ b/src/iperf_udp.c @@ -89,22 +89,10 @@ iperf_udp_recv(struct iperf_stream *sp) dgram_sz = sp->settings->blksize; if (sp->test->settings->gro) { - size = sp->test->settings->gro_bf_size; - r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz); - if (dgram_sz == -1) { - /* - * For corner case where the socket configuration is - * successful but the kernel network layer doesn't provide - * GRO-format data or ancillary info. - */ - dgram_sz = sp->settings->blksize; - } - /* Validate dgram_sz against reasonable bounds */ - if (dgram_sz <= 0 || dgram_sz < min_pkt_size || dgram_sz > sp->test->settings->gro_bf_size) { - if (test->debug_level >= DEBUG_LEVEL_INFO) - printf("Invalid GRO dgram_sz %d, falling back to blksize %d\n", dgram_sz, sp->settings->blksize); - dgram_sz = sp->settings->blksize; - } + size = sp->test->settings->gro_bf_size; + r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz); + /* Use negotiated block size for GRO segment stride to ensure correct parsing. */ + dgram_sz = sp->settings->blksize; } else { /* GRO available but disabled - use normal UDP receive and single packet size */ r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); @@ -146,49 +134,66 @@ iperf_udp_recv(struct iperf_stream *sp) dgram_buf_end = sp->buffer + r; tmp_r = r; - /* Ensure we process complete datagrams only */ - while (tmp_r >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) { - cnt++; - if (sp->test->debug) - printf("%d (%d) remaining %d\n", cnt, dgram_sz, tmp_r); + /* Ensure we process complete datagrams only */ + while (tmp_r >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) { + cnt++; + + /* Ensure we have enough bytes for the packet header */ + if (tmp_r < min_pkt_size) + break; + + if (sp->test->udp_counters_64bit) { + /* Verify we have enough space for 64-bit counter */ + if (tmp_r < sizeof(uint32_t) * 2 + sizeof(uint64_t)) + break; + memcpy(&sec, dgram_buf, sizeof(sec)); + memcpy(&usec, dgram_buf+4, sizeof(usec)); + memcpy(&pcount, dgram_buf+8, sizeof(pcount)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = be64toh(pcount); + sent_time.secs = sec; + sent_time.usecs = usec; + } else { + uint32_t pc; + memcpy(&sec, dgram_buf, sizeof(sec)); + memcpy(&usec, dgram_buf+4, sizeof(usec)); + memcpy(&pc, dgram_buf+8, sizeof(pc)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = ntohl(pc); + sent_time.secs = sec; + sent_time.usecs = usec; + } - /* Ensure we have enough bytes for the packet header */ - if (tmp_r < min_pkt_size) { - if (test->debug_level >= DEBUG_LEVEL_INFO) - printf("Incomplete packet header: %d bytes remaining\n", tmp_r); - break; - } + /* Per-datagram loss/out-of-order accounting */ + if (pcount >= sp->packet_count + 1) { + if (pcount > sp->packet_count + 1) { + sp->cnt_error += (pcount - 1) - sp->packet_count; + } + sp->packet_count = pcount; + } else { + sp->outoforder_packets++; + if (sp->cnt_error > 0) + sp->cnt_error--; + } - if (sp->test->udp_counters_64bit) { - /* Verify we have enough space for 64-bit counter */ - if (tmp_r < sizeof(uint32_t) * 2 + sizeof(uint64_t)) { - if (test->debug_level >= DEBUG_LEVEL_INFO) - printf("Incomplete 64-bit packet: %d bytes remaining\n", tmp_r); - break; - } - memcpy(&sec, dgram_buf, sizeof(sec)); - memcpy(&usec, dgram_buf+4, sizeof(usec)); - memcpy(&pcount, dgram_buf+8, sizeof(pcount)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = be64toh(pcount); - sent_time.secs = sec; - sent_time.usecs = usec; - } - else { - uint32_t pc; - memcpy(&sec, dgram_buf, sizeof(sec)); - memcpy(&usec, dgram_buf+4, sizeof(usec)); - memcpy(&pc, dgram_buf+8, sizeof(pc)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = ntohl(pc); - sent_time.secs = sec; - sent_time.usecs = usec; - } - dgram_buf += dgram_sz; - tmp_r -= dgram_sz; - } // end while loop + /* Per-datagram jitter computation */ + iperf_time_now(&arrival_time); + iperf_time_diff(&arrival_time, &sent_time, &temp_time); + transit = iperf_time_in_secs(&temp_time); + if (first_packet) + sp->prev_transit = transit; + d = transit - sp->prev_transit; + if (d < 0) + d = -d; + sp->prev_transit = transit; + sp->jitter += (d - sp->jitter) / 16.0; + first_packet = 0; + + dgram_buf += dgram_sz; + tmp_r -= dgram_sz; + } // end while loop } else { /* GRO disabled - process as single normal UDP packet */ /* Dig the various counters out of the incoming UDP packet */ @@ -239,80 +244,7 @@ iperf_udp_recv(struct iperf_stream *sp) } #endif /* HAVE_UDP_GRO */ - if (test->debug_level >= DEBUG_LEVEL_DEBUG) - fprintf(stderr, "pcount %" PRIu64 " packet_count %" PRIu64 "\n", pcount, sp->packet_count); - - /* - * Try to handle out of order packets. The way we do this - * uses a constant amount of storage but might not be - * correct in all cases. In particular we seem to have the - * assumption that packets can't be duplicated in the network, - * because duplicate packets will possibly cause some problems here. - * - * First figure out if the sequence numbers are going forward. - * Note that pcount is the sequence number read from the packet, - * and sp->packet_count is the highest sequence number seen so - * far (so we're expecting to see the packet with sequence number - * sp->packet_count + 1 arrive next). - */ - if (pcount >= sp->packet_count + 1) { - - /* Forward, but is there a gap in sequence numbers? */ - if (pcount > sp->packet_count + 1) { - /* There's a gap so count that as a loss. */ - sp->cnt_error += (pcount - 1) - sp->packet_count; - if (test->debug_level >= DEBUG_LEVEL_INFO) - fprintf(stderr, "LOST %" PRIu64 " PACKETS - received packet %" PRIu64 " but expected sequence %" PRIu64 " on stream %d\n", (pcount - sp->packet_count + 1), pcount, sp->packet_count + 1, sp->socket); - } - /* Update the highest sequence number seen so far. */ - sp->packet_count = pcount; - } else { - - /* - * Sequence number went backward (or was stationary?!?). - * This counts as an out-of-order packet. - */ - sp->outoforder_packets++; - - /* - * If we have lost packets, then the fact that we are now - * seeing an out-of-order packet offsets a prior sequence - * number gap that was counted as a loss. So we can take - * away a loss. - */ - if (sp->cnt_error > 0) - sp->cnt_error--; - - /* Log the out-of-order packet */ - if (test->debug_level >= DEBUG_LEVEL_INFO) - fprintf(stderr, "OUT OF ORDER - received packet %" PRIu64 " but expected sequence %" PRIu64 " on stream %d\n", pcount, sp->packet_count + 1, sp->socket); - } - - /* - * jitter measurement - * - * This computation is based on RFC 1889 (specifically - * sections 6.3.1 and A.8). - * - * Note that synchronized clocks are not required since - * the source packet delta times are known. Also this - * computation does not require knowing the round-trip - * time. - */ - iperf_time_now(&arrival_time); - - iperf_time_diff(&arrival_time, &sent_time, &temp_time); - transit = iperf_time_in_secs(&temp_time); - - /* Hack to handle the first packet by initializing prev_transit. */ - if (first_packet) - sp->prev_transit = transit; - - d = transit - sp->prev_transit; - if (d < 0) - d = -d; - sp->prev_transit = transit; - sp->jitter += (d - sp->jitter) / 16.0; + /* For GRO case, loss and jitter were handled per datagram inside the loop. */ } else { if (test->debug_level >= DEBUG_LEVEL_INFO) @@ -343,8 +275,9 @@ iperf_udp_send(struct iperf_stream *sp) const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ if (sp->test->settings->gso) { - dgram_sz = sp->test->settings->gso_dg_size; - buf_sz = sp->test->settings->gso_bf_size; + dgram_sz = sp->test->settings->gso_dg_size; + /* Use full GSO buffer to pack multiple datagrams, as originally. */ + buf_sz = sp->test->settings->gso_bf_size; /* Validate GSO parameters */ if (dgram_sz <= 0 || dgram_sz < min_pkt_size || dgram_sz > buf_sz) { if (sp->test->debug_level >= DEBUG_LEVEL_INFO) From b679f78ade73a8af13d18626e0db849230ccb9c7 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Fri, 5 Sep 2025 17:05:41 +0000 Subject: [PATCH 03/12] udp gso/gro: replace hard-coded 1472 with DEFAULT_UDP_BLKSIZE fallback Use the existing DEFAULT_UDP_BLKSIZE for GSO datagram-size fallback instead of the literal 1472. Rationale: avoids a magic number and keeps a conservative, widely safe default across IPv4/IPv6 when the control socket MSS is unavailable. In normal operation UDP blksize is derived from the control TCP MSS; this constant fallback is only used when MSS cannot be determined or when the computed gso_dg_size ends up 0 (unlimited case). Behavior notes: - If the user sets -l/--length, that value drives both UDP block size and gso_dg_size. - Otherwise, gso_dg_size tracks the chosen blksize; it falls back to DEFAULT_UDP_BLKSIZE only when the computed value is 0. Files: - src/iperf_api.c: update two fallback sites - src/iperf_client_api.c: update fallback site --- src/iperf_api.c | 4 ++-- src/iperf_client_api.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/iperf_api.c b/src/iperf_api.c index 5a7e63ca5..a3e06f908 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1939,7 +1939,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; } else { /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ - test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */ + test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } #endif @@ -2618,7 +2618,7 @@ get_parameters(struct iperf_test *test) test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; } else { /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ - test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */ + test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } #endif diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index edc77ed23..b4280ecc0 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -524,7 +524,7 @@ iperf_connect(struct iperf_test *test) test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; } else { /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ - test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */ + test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } #endif From e1dbf76adfd615a75eed63dccc6f27efead17dce Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Fri, 5 Sep 2025 17:30:22 +0000 Subject: [PATCH 04/12] udp gso/gro: move policy to client; send explicit params; server accepts and applies locally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client remains the source of truth for UDP GSO/GRO policy (including --no-gsro). During parameter exchange, the client now sends GSO/GRO flags and sizes in JSON, and the server simply consumes those values without recomputing. Kernel capability gating stays local and authoritative: each endpoint attempts to enable GSO/GRO on its own sockets via setsockopt, and if the kernel rejects it, we log and flip the local flag off. This handles the case where only one side supports the feature (GSO for the sender, GRO for the receiver). Backward compatibility: if talking to an older client that doesn’t send GSO fields, the server derives gso_dg_size from blksize and adjusts gso_bf_size, falling back to DEFAULT_UDP_BLKSIZE if zero. This preserves previous behavior without overriding explicit client intent. Behavior details: - --no-gsro on the client sends gso=0 and gro=0, so the server won’t try to enable them. - If -l/--length is provided, blksize (and therefore gso_dg_size when enabled) follows that value; otherwise default logic applies. Files: src/iperf_api.c (send_parameters adds gso/gro fields; get_parameters reads them and removes server-side recompute unless needed for compatibility). --- src/iperf_api.c | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/iperf_api.c b/src/iperf_api.c index a3e06f908..19edc87b5 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -2487,6 +2487,22 @@ send_parameters(struct iperf_test *test) cJSON_AddNumberToObject(j, "pacing_timer", test->settings->pacing_timer); if (test->settings->burst) cJSON_AddNumberToObject(j, "burst", test->settings->burst); + +#ifdef HAVE_UDP_SEGMENT + /* Send UDP GSO settings from client to server */ + if (test->protocol->id == Pudp) { + cJSON_AddNumberToObject(j, "gso", test->settings->gso); + cJSON_AddNumberToObject(j, "gso_dg_size", test->settings->gso_dg_size); + cJSON_AddNumberToObject(j, "gso_bf_size", test->settings->gso_bf_size); + } +#endif +#ifdef HAVE_UDP_GRO + /* Send UDP GRO settings from client to server */ + if (test->protocol->id == Pudp) { + cJSON_AddNumberToObject(j, "gro", test->settings->gro); + cJSON_AddNumberToObject(j, "gro_bf_size", test->settings->gro_bf_size); + } +#endif if (test->settings->tos) cJSON_AddNumberToObject(j, "TOS", test->settings->tos); if (test->settings->flowlabel) @@ -2610,17 +2626,32 @@ get_parameters(struct iperf_test *test) test->settings->socket_bufsize = j_p->valueint; if ((j_p = iperf_cJSON_GetObjectItemType(j, "len", cJSON_Number)) != NULL) test->settings->blksize = j_p->valueint; + #ifdef HAVE_UDP_SEGMENT - if (test->protocol->id == Pudp && test->settings->gso == 1) { + /* Accept UDP GSO settings provided by the client */ + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gso", cJSON_Number)) != NULL) + test->settings->gso = j_p->valueint; + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gso_dg_size", cJSON_Number)) != NULL) + test->settings->gso_dg_size = j_p->valueint; + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gso_bf_size", cJSON_Number)) != NULL) + test->settings->gso_bf_size = j_p->valueint; + + /* Backward-compatibility: If client didn't send GSO params, derive from blksize. */ + if (test->protocol->id == Pudp && test->settings->gso == 1 && test->settings->gso_dg_size == 0) { test->settings->gso_dg_size = test->settings->blksize; - /* use the multiple of datagram size for the best efficiency. */ if (test->settings->gso_dg_size > 0) { test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; } else { - /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } +#endif +#ifdef HAVE_UDP_GRO + /* Accept UDP GRO settings provided by the client */ + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gro", cJSON_Number)) != NULL) + test->settings->gro = j_p->valueint; + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gro_bf_size", cJSON_Number)) != NULL) + test->settings->gro_bf_size = j_p->valueint; #endif if ((j_p = iperf_cJSON_GetObjectItemType(j, "bandwidth", cJSON_Number)) != NULL) test->settings->rate = j_p->valueint; From f559117c85df4e9251b6f016c1e89c21225e3345 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Tue, 10 Feb 2026 04:55:53 +0000 Subject: [PATCH 05/12] udp gso/gro: change to disabled-by-default with --gsro flag Change from enabled-by-default with --no-gsro to disabled-by-default with --gsro flag. This makes GSO/GRO opt-in rather than opt-out. Changes: - Rename --no-gsro to --gsro flag - Change default initialization from enabled (1) to disabled (0) - Update option handler to enable instead of disable - Update help text to reflect new behavior --- src/iperf_api.c | 16 ++++++++-------- src/iperf_api.h | 2 +- src/iperf_locale.c | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/iperf_api.c b/src/iperf_api.c index 19edc87b5..38322f7d1 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1190,7 +1190,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) {"mptcp", no_argument, NULL, 'm'}, #endif #if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) - {"no-gsro", no_argument, NULL, OPT_NO_GSRO}, + {"gsro", no_argument, NULL, OPT_GSRO}, #endif {"debug", optional_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, @@ -1794,13 +1794,13 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) break; #endif #if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) - case OPT_NO_GSRO: - /* Disable GSO/GRO which would otherwise be enabled by default */ + case OPT_GSRO: + /* Enable GSO/GRO which is disabled by default */ #ifdef HAVE_UDP_SEGMENT - test->settings->gso = 0; + test->settings->gso = 1; #endif #ifdef HAVE_UDP_GRO - test->settings->gro = 0; + test->settings->gro = 1; #endif break; #endif @@ -1824,7 +1824,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; } -/* GSO/GRO are enabled by default when available, disabled only via --no-gsro */ +/* GSO/GRO are disabled by default when available, enabled only via --gsro */ #if defined(HAVE_SSL) @@ -3313,12 +3313,12 @@ iperf_defaults(struct iperf_test *testp) testp->settings->pacing_timer = DEFAULT_PACING_TIMER; testp->settings->burst = 0; #ifdef HAVE_UDP_SEGMENT - testp->settings->gso = 1; /* Enable GSO by default */ + testp->settings->gso = 0; /* Disable GSO by default, enabled via --gsro */ testp->settings->gso_dg_size = 0; testp->settings->gso_bf_size = GSO_BF_MAX_SIZE; #endif #ifdef HAVE_UDP_GRO - testp->settings->gro = 1; /* Enable GRO by default */ + testp->settings->gro = 0; /* Disable GRO by default, enabled via --gsro */ testp->settings->gro_bf_size = GRO_BF_MAX_SIZE; #endif testp->settings->mss = 0; diff --git a/src/iperf_api.h b/src/iperf_api.h index 6c0a9af64..ff8003e96 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -106,7 +106,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t; #define OPT_SKIP_RX_COPY 32 #define OPT_JSON_STREAM_FULL_OUTPUT 33 #define OPT_SERVER_MAX_DURATION 34 -#define OPT_NO_GSRO 35 +#define OPT_GSRO 35 /* states */ #define TEST_START 1 diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 20d404a46..56200ab1e 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -220,7 +220,7 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" " --get-server-output get results from server\n" " --udp-counters-64bit use 64-bit counters in UDP test packets\n" #if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) - " --no-gsro disable UDP GSO/GRO (Generic Segmentation/Receive Offload)\n" + " --gsro enable UDP GSO/GRO (Generic Segmentation/Receive Offload)\n" #endif " --repeating-payload use repeating pattern in payload, instead of\n" " randomized payload (like in iperf2)\n" From c84bb199cc4299539313d23390d0a8004895c932 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Tue, 10 Feb 2026 05:01:13 +0000 Subject: [PATCH 06/12] docs: add --gsro flag to iperf3.1 man page Add documentation for the --gsro flag in the manual page, describing UDP GSO/GRO functionality and its benefits. --- src/iperf3.1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/iperf3.1 b/src/iperf3.1 index bb4607e6d..ec04fc5d9 100644 --- a/src/iperf3.1 +++ b/src/iperf3.1 @@ -514,6 +514,15 @@ or high-bitrate UDP tests. Both client and server need to be running at least version 3.1 for this option to work. It may become the default behavior at some point in the future. .TP +.BR --gsro +Enable UDP Generic Segmentation Offload (GSO) on the sender and +Generic Receive Offload (GRO) on the receiver, where supported by +the operating system and network hardware (currently Linux only). +GSO allows the network stack to aggregate multiple UDP datagrams +into larger packets, improving throughput and reducing CPU overhead. +This feature is disabled by default and must be explicitly enabled +with this flag. +.TP .BR --repeating-payload Use repeating pattern in payload, instead of random bytes. The same payload is used in iperf2 (ASCII '0..9' repeating). From 7589d289a5341c1fd44475ca1ca35944ce2c4527 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Tue, 10 Feb 2026 05:20:14 +0000 Subject: [PATCH 07/12] udp gso/gro: refactor to eliminate duplication and restore counters Refactor iperf_udp_send() and iperf_udp_recv() to use unified loops that handle both GSO/GRO-enabled and disabled cases in a single code path, eliminating code duplication. Key changes: - Configure loop parameters (dgram_sz, buf_sz) upfront based on GSO/GRO availability - Use single unified loop for packet processing regardless of GSO/GRO state - Restore loss counter increments and jitter computation outside #ifdef guards so they work when GSO/GRO is disabled - Gate diagnostic output in iperf_udp_gso() and iperf_udp_gro() behind test->debug checks to prevent unwanted verbosity This addresses maintainer feedback to eliminate separate code branches and ensure counters work correctly in all configurations. Tested: - Normal UDP mode: jitter and loss counters working correctly - --gsro mode: jitter and loss counters working correctly - Debug output properly gated behind -d flag --- src/iperf_udp.c | 328 ++++++++++++++++++------------------------------ 1 file changed, 122 insertions(+), 206 deletions(-) diff --git a/src/iperf_udp.c b/src/iperf_udp.c index cfe95ab13..f433016fa 100644 --- a/src/iperf_udp.c +++ b/src/iperf_udp.c @@ -66,8 +66,14 @@ iperf_udp_recv(struct iperf_stream *sp) int first_packet = 0; double transit = 0, d = 0; struct iperf_time sent_time, arrival_time, temp_time; - struct iperf_test *test = sp->test; + struct iperf_test *test = sp->test; int sock_opt = 0; + int dgram_sz; + int buf_sz; + int cnt = 0; + char *dgram_buf; + char *dgram_buf_end; + const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ #if defined(HAVE_MSG_TRUNC) // UDP recv() with MSG_TRUNC reads only the size bytes, but return the length of the full packet @@ -77,30 +83,22 @@ iperf_udp_recv(struct iperf_stream *sp) } #endif /* HAVE_MSG_TRUNC */ + /* Configure loop parameters based on GRO availability */ #ifdef HAVE_UDP_GRO - int tmp_r; - int dgram_sz; - int cnt = 0; - char *dgram_buf; - char *dgram_buf_end; - const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ - - /* Initialize dgram_sz for both GRO enabled and disabled cases */ - dgram_sz = sp->settings->blksize; - if (sp->test->settings->gro) { - size = sp->test->settings->gro_bf_size; - r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz); - /* Use negotiated block size for GRO segment stride to ensure correct parsing. */ - dgram_sz = sp->settings->blksize; - } else { - /* GRO available but disabled - use normal UDP receive and single packet size */ - r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); - dgram_sz = sp->settings->blksize; - } -#else - r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); + size = sp->test->settings->gro_bf_size; + r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz); + /* Use negotiated block size for GRO segment stride to ensure correct parsing. */ + dgram_sz = sp->settings->blksize; + buf_sz = r; + } else #endif + { + /* GRO disabled or unavailable - use normal UDP receive and single packet size */ + r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); + dgram_sz = sp->settings->blksize; + buf_sz = r; + } /* * If we got an error in the read, or if we didn't read anything @@ -127,124 +125,70 @@ iperf_udp_recv(struct iperf_stream *sp) if (sp->test->debug) printf("received %d bytes of %d, total %" PRIu64 "\n", r, size, sp->result->bytes_received); -#ifdef HAVE_UDP_GRO - if (sp->test->settings->gro) { - /* GRO enabled - process multiple datagrams */ - dgram_buf = sp->buffer; - dgram_buf_end = sp->buffer + r; - tmp_r = r; - - /* Ensure we process complete datagrams only */ - while (tmp_r >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) { - cnt++; - - /* Ensure we have enough bytes for the packet header */ - if (tmp_r < min_pkt_size) - break; - - if (sp->test->udp_counters_64bit) { - /* Verify we have enough space for 64-bit counter */ - if (tmp_r < sizeof(uint32_t) * 2 + sizeof(uint64_t)) - break; - memcpy(&sec, dgram_buf, sizeof(sec)); - memcpy(&usec, dgram_buf+4, sizeof(usec)); - memcpy(&pcount, dgram_buf+8, sizeof(pcount)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = be64toh(pcount); - sent_time.secs = sec; - sent_time.usecs = usec; - } else { - uint32_t pc; - memcpy(&sec, dgram_buf, sizeof(sec)); - memcpy(&usec, dgram_buf+4, sizeof(usec)); - memcpy(&pc, dgram_buf+8, sizeof(pc)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = ntohl(pc); - sent_time.secs = sec; - sent_time.usecs = usec; - } + /* Unified loop: processes single packet when GRO off, multiple when GRO on */ + dgram_buf = sp->buffer; + dgram_buf_end = sp->buffer + buf_sz; - /* Per-datagram loss/out-of-order accounting */ - if (pcount >= sp->packet_count + 1) { - if (pcount > sp->packet_count + 1) { - sp->cnt_error += (pcount - 1) - sp->packet_count; - } - sp->packet_count = pcount; - } else { - sp->outoforder_packets++; - if (sp->cnt_error > 0) - sp->cnt_error--; - } + while (buf_sz >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) { + cnt++; - /* Per-datagram jitter computation */ - iperf_time_now(&arrival_time); - iperf_time_diff(&arrival_time, &sent_time, &temp_time); - transit = iperf_time_in_secs(&temp_time); - if (first_packet) - sp->prev_transit = transit; - d = transit - sp->prev_transit; - if (d < 0) - d = -d; - sp->prev_transit = transit; - sp->jitter += (d - sp->jitter) / 16.0; - first_packet = 0; - - dgram_buf += dgram_sz; - tmp_r -= dgram_sz; - } // end while loop - } else { - /* GRO disabled - process as single normal UDP packet */ - /* Dig the various counters out of the incoming UDP packet */ - if (test->udp_counters_64bit) { - memcpy(&sec, sp->buffer, sizeof(sec)); - memcpy(&usec, sp->buffer+4, sizeof(usec)); - memcpy(&pcount, sp->buffer+8, sizeof(pcount)); + /* Ensure we have enough bytes for the packet header */ + if (buf_sz < min_pkt_size) + break; + + /* Extract packet headers */ + if (sp->test->udp_counters_64bit) { + /* Verify we have enough space for 64-bit counter */ + if (buf_sz < sizeof(uint32_t) * 2 + sizeof(uint64_t)) + break; + memcpy(&sec, dgram_buf, sizeof(sec)); + memcpy(&usec, dgram_buf+4, sizeof(usec)); + memcpy(&pcount, dgram_buf+8, sizeof(pcount)); sec = ntohl(sec); usec = ntohl(usec); pcount = be64toh(pcount); sent_time.secs = sec; sent_time.usecs = usec; - } - else { + } else { uint32_t pc; - memcpy(&sec, sp->buffer, sizeof(sec)); - memcpy(&usec, sp->buffer+4, sizeof(usec)); - memcpy(&pc, sp->buffer+8, sizeof(pc)); + memcpy(&sec, dgram_buf, sizeof(sec)); + memcpy(&usec, dgram_buf+4, sizeof(usec)); + memcpy(&pc, dgram_buf+8, sizeof(pc)); sec = ntohl(sec); usec = ntohl(usec); pcount = ntohl(pc); sent_time.secs = sec; sent_time.usecs = usec; } - } -#else - /* Dig the various counters out of the incoming UDP packet */ - if (test->udp_counters_64bit) { - memcpy(&sec, sp->buffer, sizeof(sec)); - memcpy(&usec, sp->buffer+4, sizeof(usec)); - memcpy(&pcount, sp->buffer+8, sizeof(pcount)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = be64toh(pcount); - sent_time.secs = sec; - sent_time.usecs = usec; - } - else { - uint32_t pc; - memcpy(&sec, sp->buffer, sizeof(sec)); - memcpy(&usec, sp->buffer+4, sizeof(usec)); - memcpy(&pc, sp->buffer+8, sizeof(pc)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = ntohl(pc); - sent_time.secs = sec; - sent_time.usecs = usec; - } -#endif /* HAVE_UDP_GRO */ - /* For GRO case, loss and jitter were handled per datagram inside the loop. */ + /* Loss/out-of-order accounting - now always executed */ + if (pcount >= sp->packet_count + 1) { + if (pcount > sp->packet_count + 1) { + sp->cnt_error += (pcount - 1) - sp->packet_count; + } + sp->packet_count = pcount; + } else { + sp->outoforder_packets++; + if (sp->cnt_error > 0) + sp->cnt_error--; + } + + /* Jitter computation - now always executed */ + iperf_time_now(&arrival_time); + iperf_time_diff(&arrival_time, &sent_time, &temp_time); + transit = iperf_time_in_secs(&temp_time); + if (first_packet) + sp->prev_transit = transit; + d = transit - sp->prev_transit; + if (d < 0) + d = -d; + sp->prev_transit = transit; + sp->jitter += (d - sp->jitter) / 16.0; + first_packet = 0; + + dgram_buf += dgram_sz; + buf_sz -= dgram_sz; + } } else { if (test->debug_level >= DEBUG_LEVEL_INFO) @@ -265,8 +209,6 @@ iperf_udp_send(struct iperf_stream *sp) int r; int size = sp->settings->blksize; struct iperf_time before; - -#ifdef HAVE_UDP_SEGMENT int dgram_sz; int buf_sz; int cnt = 0; @@ -274,10 +216,11 @@ iperf_udp_send(struct iperf_stream *sp) char *dgram_buf_end; const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ + /* Configure loop parameters based on GSO availability */ +#ifdef HAVE_UDP_SEGMENT if (sp->test->settings->gso) { - dgram_sz = sp->test->settings->gso_dg_size; - /* Use full GSO buffer to pack multiple datagrams, as originally. */ - buf_sz = sp->test->settings->gso_bf_size; + dgram_sz = sp->test->settings->gso_dg_size; + buf_sz = sp->test->settings->gso_bf_size; /* Validate GSO parameters */ if (dgram_sz <= 0 || dgram_sz < min_pkt_size || dgram_sz > buf_sz) { if (sp->test->debug_level >= DEBUG_LEVEL_INFO) @@ -285,97 +228,64 @@ iperf_udp_send(struct iperf_stream *sp) dgram_sz = buf_sz = size; sp->test->settings->gso = 0; /* Disable GSO for safety */ } - } else { + } else +#endif + { + /* GSO disabled or unavailable - single packet */ dgram_sz = buf_sz = size; } dgram_buf = sp->buffer; dgram_buf_end = sp->buffer + buf_sz; + /* Unified loop: processes single packet when GSO off, multiple when GSO on */ while (buf_sz > 0 && dgram_buf + dgram_sz <= dgram_buf_end) { - cnt++; - - if (sp->test->debug) - printf("%d (%d) remaining %d\n", cnt, dgram_sz, buf_sz); + cnt++; - /* Prevent buffer underflow */ - if (buf_sz < dgram_sz) { - if (sp->test->debug_level >= DEBUG_LEVEL_INFO) - printf("Buffer underflow protection: buf_sz %d < dgram_sz %d\n", buf_sz, dgram_sz); - break; - } - - iperf_time_now(&before); - ++sp->packet_count; - - if (sp->test->udp_counters_64bit) { + if (sp->test->debug) + printf("%d (%d) remaining %d\n", cnt, dgram_sz, buf_sz); - uint32_t sec, usec; - uint64_t pcount; + /* Prevent buffer underflow */ + if (buf_sz < dgram_sz) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Buffer underflow protection: buf_sz %d < dgram_sz %d\n", buf_sz, dgram_sz); + break; + } - sec = htonl(before.secs); - usec = htonl(before.usecs); - pcount = htobe64(sp->packet_count); + iperf_time_now(&before); + ++sp->packet_count; - memcpy(dgram_buf, &sec, sizeof(sec)); - memcpy(dgram_buf+4, &usec, sizeof(usec)); - memcpy(dgram_buf+8, &pcount, sizeof(pcount)); + if (sp->test->udp_counters_64bit) { + uint32_t sec, usec; + uint64_t pcount; - } - else { + sec = htonl(before.secs); + usec = htonl(before.usecs); + pcount = htobe64(sp->packet_count); - uint32_t sec, usec, pcount; + memcpy(dgram_buf, &sec, sizeof(sec)); + memcpy(dgram_buf+4, &usec, sizeof(usec)); + memcpy(dgram_buf+8, &pcount, sizeof(pcount)); + } else { + uint32_t sec, usec, pcount; - sec = htonl(before.secs); - usec = htonl(before.usecs); - pcount = htonl(sp->packet_count); + sec = htonl(before.secs); + usec = htonl(before.usecs); + pcount = htonl(sp->packet_count); - memcpy(dgram_buf, &sec, sizeof(sec)); - memcpy(dgram_buf+4, &usec, sizeof(usec)); - memcpy(dgram_buf+8, &pcount, sizeof(pcount)); + memcpy(dgram_buf, &sec, sizeof(sec)); + memcpy(dgram_buf+4, &usec, sizeof(usec)); + memcpy(dgram_buf+8, &pcount, sizeof(pcount)); + } - } - dgram_buf += dgram_sz; - buf_sz -= dgram_sz; + dgram_buf += dgram_sz; + buf_sz -= dgram_sz; } - + /* Warn if we didn't process all the buffer due to size mismatch */ if (buf_sz > 0 && sp->test->debug_level >= DEBUG_LEVEL_INFO) { printf("GSO: %d bytes remaining unprocessed\n", buf_sz); } -#else - iperf_time_now(&before); - - ++sp->packet_count; - - if (sp->test->udp_counters_64bit) { - - uint32_t sec, usec; - uint64_t pcount; - - sec = htonl(before.secs); - usec = htonl(before.usecs); - pcount = htobe64(sp->packet_count); - - memcpy(sp->buffer, &sec, sizeof(sec)); - memcpy(sp->buffer+4, &usec, sizeof(usec)); - memcpy(sp->buffer+8, &pcount, sizeof(pcount)); - - } - else { - - uint32_t sec, usec, pcount; - - sec = htonl(before.secs); - usec = htonl(before.usecs); - pcount = htonl(sp->packet_count); - - memcpy(sp->buffer, &sec, sizeof(sec)); - memcpy(sp->buffer+4, &usec, sizeof(usec)); - memcpy(sp->buffer+8, &pcount, sizeof(pcount)); - - } -#endif /* HAVE_UDP_SEGMENT */ #ifdef HAVE_UDP_SEGMENT if (sp->test->settings->gso) { @@ -518,10 +428,13 @@ iperf_udp_gso(struct iperf_test *test, int s) rc = setsockopt(s, IPPROTO_UDP, UDP_SEGMENT, (char*) &gso, sizeof(gso)); if (rc) { - iperf_printf(test, "No GSO (%d)\n", rc); + if (test->debug) + iperf_printf(test, "No GSO (%d)\n", rc); test->settings->gso = 0; - } else - iperf_printf(test, "GSO (%d)\n", gso); + } else { + if (test->debug) + iperf_printf(test, "GSO (%d)\n", gso); + } return rc; } @@ -536,10 +449,13 @@ iperf_udp_gro(struct iperf_test *test, int s) rc = setsockopt(s, IPPROTO_UDP, UDP_GRO, (char*) &gro, sizeof(gro)); if (rc) { - iperf_printf(test, "No GRO (%d)\n", rc); + if (test->debug) + iperf_printf(test, "No GRO (%d)\n", rc); test->settings->gro = 0; - } else - iperf_printf(test, "GRO\n"); + } else { + if (test->debug) + iperf_printf(test, "GRO\n"); + } return rc; } From bc92c1fb1352159a187373fb4edbda2199f75612 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Tue, 10 Feb 2026 11:26:40 -0800 Subject: [PATCH 08/12] udp gso/gro: make --gsro a client-only option Reject --gsro when used with -s (server mode) by returning IECLIENTONLY, matching the pattern used by other client-only flags. The server already receives GSO/GRO settings from the client via the JSON parameter exchange, so passing --gsro on the server command line has no effect. Update help text and man page accordingly. --- src/iperf3.1 | 2 ++ src/iperf_api.c | 10 ++++++++++ src/iperf_locale.c | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/iperf3.1 b/src/iperf3.1 index ec04fc5d9..33c0b3cf7 100644 --- a/src/iperf3.1 +++ b/src/iperf3.1 @@ -520,6 +520,8 @@ Generic Receive Offload (GRO) on the receiver, where supported by the operating system and network hardware (currently Linux only). GSO allows the network stack to aggregate multiple UDP datagrams into larger packets, improving throughput and reducing CPU overhead. +This is a client-only option; the client communicates the GSO/GRO +settings to the server automatically. This feature is disabled by default and must be explicitly enabled with this flag. .TP diff --git a/src/iperf_api.c b/src/iperf_api.c index 38322f7d1..3e821ee38 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1215,6 +1215,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) blksize = 0; server_flag = client_flag = rate_flag = duration_flag = rcv_timeout_flag = snd_timeout_flag =0; +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) + int gsro_flag = 0; +#endif #if defined(HAVE_SSL) char *client_username = NULL, *client_rsa_public_key = NULL, *server_rsa_private_key = NULL; FILE *ptr_file; @@ -1796,6 +1799,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) #if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) case OPT_GSRO: /* Enable GSO/GRO which is disabled by default */ + gsro_flag = 1; #ifdef HAVE_UDP_SEGMENT test->settings->gso = 1; #endif @@ -1823,6 +1827,12 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) i_errno = IECLIENTONLY; return -1; } +#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) + if (test->role == 's' && gsro_flag) { + i_errno = IECLIENTONLY; + return -1; + } +#endif /* GSO/GRO are disabled by default when available, enabled only via --gsro */ diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 56200ab1e..02d69b320 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -220,7 +220,7 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" " --get-server-output get results from server\n" " --udp-counters-64bit use 64-bit counters in UDP test packets\n" #if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) - " --gsro enable UDP GSO/GRO (Generic Segmentation/Receive Offload)\n" + " --gsro enable UDP GSO/GRO on both client and server (client-only option)\n" #endif " --repeating-payload use repeating pattern in payload, instead of\n" " randomized payload (like in iperf2)\n" From aafc57565403c5ad617ce2aceac8823858f31679 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Thu, 12 Feb 2026 17:20:40 +0000 Subject: [PATCH 09/12] udp gso/gro: make --gsro available regardless of local support Address PR feedback to allow clients without GSO/GRO support to request server-side enablement. Key changes: - Remove conditional compilation guards around --gsro option - Always define gso/gro fields in iperf_settings structure - Always send/receive GSO/GRO parameters in JSON protocol - Add warnings when --gsro requested but not supported locally - Socket options only applied when HAVE_UDP_SEGMENT/HAVE_UDP_GRO defined This allows a client compiled without GSO/GRO support to still use --gsro to enable these features on a capable server, improving flexibility for heterogeneous deployments. --- src/iperf.h | 5 +---- src/iperf_api.c | 48 +++++++++++++----------------------------- src/iperf_client_api.c | 3 +-- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/iperf.h b/src/iperf.h index de40cb630..142384ad1 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -191,15 +191,12 @@ struct iperf_settings int cntl_ka_keepidle; /* Control TCP connection Keepalive idle time (TCP_KEEPIDLE) */ int cntl_ka_interval; /* Control TCP connection Keepalive interval between retries (TCP_KEEPINTV) */ int cntl_ka_count; /* Control TCP connection Keepalive number of retries (TCP_KEEPCNT) */ -#ifdef HAVE_UDP_SEGMENT + /* GSO/GRO fields always present to allow client-server negotiation regardless of local support */ int gso; int gso_dg_size; int gso_bf_size; -#endif -#ifdef HAVE_UDP_GRO int gro; int gro_bf_size; -#endif }; struct iperf_test; diff --git a/src/iperf_api.c b/src/iperf_api.c index 3e821ee38..18b7d8573 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1189,9 +1189,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) #if defined(HAVE_IPPROTO_MPTCP) {"mptcp", no_argument, NULL, 'm'}, #endif -#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) {"gsro", no_argument, NULL, OPT_GSRO}, -#endif {"debug", optional_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} @@ -1215,9 +1213,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) blksize = 0; server_flag = client_flag = rate_flag = duration_flag = rcv_timeout_flag = snd_timeout_flag =0; -#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) int gsro_flag = 0; -#endif #if defined(HAVE_SSL) char *client_username = NULL, *client_rsa_public_key = NULL, *server_rsa_private_key = NULL; FILE *ptr_file; @@ -1796,18 +1792,20 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) test->mptcp = 1; break; #endif -#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) case OPT_GSRO: /* Enable GSO/GRO which is disabled by default */ + /* Flag is available regardless of local support to allow client to request server to use it */ gsro_flag = 1; -#ifdef HAVE_UDP_SEGMENT test->settings->gso = 1; -#endif -#ifdef HAVE_UDP_GRO test->settings->gro = 1; +#if !defined(HAVE_UDP_SEGMENT) && !defined(HAVE_UDP_GRO) + warning("--gsro requested but UDP GSO/GRO not supported on this client; will only be enabled on server if supported"); +#elif !defined(HAVE_UDP_SEGMENT) + warning("--gsro requested but UDP GSO not supported on this client; will be enabled on server if supported"); +#elif !defined(HAVE_UDP_GRO) + warning("--gsro requested but UDP GRO not supported on this client; will be enabled on server if supported"); #endif break; -#endif case 'h': usage_long(stdout); exit(0); @@ -1827,12 +1825,10 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) i_errno = IECLIENTONLY; return -1; } -#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) if (test->role == 's' && gsro_flag) { i_errno = IECLIENTONLY; return -1; } -#endif /* GSO/GRO are disabled by default when available, enabled only via --gsro */ @@ -2498,21 +2494,15 @@ send_parameters(struct iperf_test *test) if (test->settings->burst) cJSON_AddNumberToObject(j, "burst", test->settings->burst); -#ifdef HAVE_UDP_SEGMENT - /* Send UDP GSO settings from client to server */ + /* Send UDP GSO/GRO settings from client to server */ + /* Always send these fields to allow server to use GSO/GRO even if client doesn't support it */ if (test->protocol->id == Pudp) { cJSON_AddNumberToObject(j, "gso", test->settings->gso); cJSON_AddNumberToObject(j, "gso_dg_size", test->settings->gso_dg_size); cJSON_AddNumberToObject(j, "gso_bf_size", test->settings->gso_bf_size); - } -#endif -#ifdef HAVE_UDP_GRO - /* Send UDP GRO settings from client to server */ - if (test->protocol->id == Pudp) { cJSON_AddNumberToObject(j, "gro", test->settings->gro); cJSON_AddNumberToObject(j, "gro_bf_size", test->settings->gro_bf_size); } -#endif if (test->settings->tos) cJSON_AddNumberToObject(j, "TOS", test->settings->tos); if (test->settings->flowlabel) @@ -2637,8 +2627,8 @@ get_parameters(struct iperf_test *test) if ((j_p = iperf_cJSON_GetObjectItemType(j, "len", cJSON_Number)) != NULL) test->settings->blksize = j_p->valueint; -#ifdef HAVE_UDP_SEGMENT - /* Accept UDP GSO settings provided by the client */ + /* Accept UDP GSO/GRO settings provided by the client */ + /* Always accept these fields to allow server to use GSO/GRO based on its own support */ if ((j_p = iperf_cJSON_GetObjectItemType(j, "gso", cJSON_Number)) != NULL) test->settings->gso = j_p->valueint; if ((j_p = iperf_cJSON_GetObjectItemType(j, "gso_dg_size", cJSON_Number)) != NULL) @@ -2655,14 +2645,12 @@ get_parameters(struct iperf_test *test) test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } -#endif -#ifdef HAVE_UDP_GRO - /* Accept UDP GRO settings provided by the client */ + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gro", cJSON_Number)) != NULL) test->settings->gro = j_p->valueint; if ((j_p = iperf_cJSON_GetObjectItemType(j, "gro_bf_size", cJSON_Number)) != NULL) test->settings->gro_bf_size = j_p->valueint; -#endif + if ((j_p = iperf_cJSON_GetObjectItemType(j, "bandwidth", cJSON_Number)) != NULL) test->settings->rate = j_p->valueint; if ((j_p = iperf_cJSON_GetObjectItemType(j, "fqrate", cJSON_Number)) != NULL) @@ -3322,15 +3310,12 @@ iperf_defaults(struct iperf_test *testp) testp->settings->fqrate = 0; testp->settings->pacing_timer = DEFAULT_PACING_TIMER; testp->settings->burst = 0; -#ifdef HAVE_UDP_SEGMENT + /* Always initialize GSO/GRO fields to allow client-server negotiation */ testp->settings->gso = 0; /* Disable GSO by default, enabled via --gsro */ testp->settings->gso_dg_size = 0; testp->settings->gso_bf_size = GSO_BF_MAX_SIZE; -#endif -#ifdef HAVE_UDP_GRO testp->settings->gro = 0; /* Disable GRO by default, enabled via --gsro */ testp->settings->gro_bf_size = GRO_BF_MAX_SIZE; -#endif testp->settings->mss = 0; testp->settings->bytes = 0; testp->settings->blocks = 0; @@ -3644,13 +3629,10 @@ iperf_reset_test(struct iperf_test *test) test->settings->burst = 0; test->settings->mss = 0; test->settings->tos = 0; -#ifdef HAVE_UDP_SEGMENT + /* Always initialize GSO/GRO fields */ test->settings->gso_dg_size = 0; test->settings->gso_bf_size = GSO_BF_MAX_SIZE; -#endif -#ifdef HAVE_UDP_GRO test->settings->gro_bf_size = GRO_BF_MAX_SIZE; -#endif test->settings->dont_fragment = 0; test->zerocopy = 0; test->settings->skip_rx_copy = 0; diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index b4280ecc0..a599c3cc9 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -516,7 +516,7 @@ iperf_connect(struct iperf_test *test) printf("Setting UDP block size to %d\n", test->settings->blksize); } } -#ifdef HAVE_UDP_SEGMENT + /* Initialize GSO parameters when --gsro is used */ if (test->settings->gso) { test->settings->gso_dg_size = test->settings->blksize; /* use the multiple of datagram size for the best efficiency. */ @@ -527,7 +527,6 @@ iperf_connect(struct iperf_test *test) test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } -#endif /* * Regardless of whether explicitly or implicitly set, if the From 6fb1acb9ac95fc4131d91aec685259aed5cf6ffd Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Thu, 12 Feb 2026 17:46:14 +0000 Subject: [PATCH 10/12] udp gso/gro: remove remaining conditional guards and restore zerocopy Remove HAVE_UDP_SEGMENT/HAVE_UDP_GRO guards around: - GSO parameter initialization in iperf_parse_arguments - Buffer sizing logic in iperf_new_stream These fields are now always defined, allowing client/server negotiation regardless of local support. Guards remain only around actual socket operations. Also restore MAP_SHARED for mmap (was accidentally changed to MAP_PRIVATE during GSO/GRO merge in b56475e), fixing zerocopy functionality that was broken since PR #1949. --- src/iperf_api.c | 10 +------ src/iperf_client_api.c | 66 +++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/iperf_api.c b/src/iperf_api.c index 18b7d8573..e5bb6fa48 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1830,8 +1830,6 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; } -/* GSO/GRO are disabled by default when available, enabled only via --gsro */ - #if defined(HAVE_SSL) if (test->role == 's' && (client_username || client_rsa_public_key)){ @@ -1937,7 +1935,6 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; } -#ifdef HAVE_UDP_SEGMENT if (test->protocol->id == Pudp && test->settings->gso) { test->settings->gso_dg_size = blksize; /* use the multiple of datagram size for the best efficiency. */ @@ -1948,7 +1945,6 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; } } -#endif test->settings->blksize = blksize; @@ -4857,14 +4853,10 @@ iperf_new_stream(struct iperf_test *test, int s, int sender) return NULL; } size = test->settings->blksize; -#ifdef HAVE_UDP_SEGMENT if (test->protocol->id == Pudp && test->settings->gso && (size < test->settings->gso_bf_size)) size = test->settings->gso_bf_size; -#endif -#ifdef HAVE_UDP_GRO if (test->protocol->id == Pudp && test->settings->gro && (size < test->settings->gro_bf_size)) size = test->settings->gro_bf_size; -#endif if (sp->test->debug) printf("Buffer %d bytes\n", size); if (ftruncate(sp->buffer_fd, size) < 0) { @@ -4873,7 +4865,7 @@ iperf_new_stream(struct iperf_test *test, int s, int sender) free(sp); return NULL; } - sp->buffer = (char *) mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, sp->buffer_fd, 0); + sp->buffer = (char *) mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, sp->buffer_fd, 0); if (sp->buffer == MAP_FAILED) { i_errno = IECREATESTREAM; free(sp->result); diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index a599c3cc9..d8a79d125 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -505,40 +505,40 @@ iperf_connect(struct iperf_test *test) * the user always has the option to override. */ if (test->protocol->id == Pudp) { - if (test->settings->blksize == 0) { - if (test->ctrl_sck_mss) { - test->settings->blksize = test->ctrl_sck_mss; - } - else { - test->settings->blksize = DEFAULT_UDP_BLKSIZE; - } - if (test->verbose) { - printf("Setting UDP block size to %d\n", test->settings->blksize); - } - } - /* Initialize GSO parameters when --gsro is used */ - if (test->settings->gso) { - test->settings->gso_dg_size = test->settings->blksize; - /* use the multiple of datagram size for the best efficiency. */ - if (test->settings->gso_dg_size > 0) { - test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; - } else { - /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ - test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; - } - } + if (test->settings->blksize == 0) { + if (test->ctrl_sck_mss) { + test->settings->blksize = test->ctrl_sck_mss; + } + else { + test->settings->blksize = DEFAULT_UDP_BLKSIZE; + } + if (test->verbose) { + printf("Setting UDP block size to %d\n", test->settings->blksize); + } + } + /* Initialize GSO parameters when --gsro is used */ + if (test->settings->gso) { + test->settings->gso_dg_size = test->settings->blksize; + /* use the multiple of datagram size for the best efficiency. */ + if (test->settings->gso_dg_size > 0) { + test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size; + } else { + /* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */ + test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE; + } + } - /* - * Regardless of whether explicitly or implicitly set, if the - * block size is larger than the MSS, print a warning. - */ - if (test->ctrl_sck_mss > 0 && - test->settings->blksize > test->ctrl_sck_mss) { - char str[WARN_STR_LEN]; - snprintf(str, sizeof(str), - "UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss); - warning(str); - } + /* + * Regardless of whether explicitly or implicitly set, if the + * block size is larger than the MSS, print a warning. + */ + if (test->ctrl_sck_mss > 0 && + test->settings->blksize > test->ctrl_sck_mss) { + char str[WARN_STR_LEN]; + snprintf(str, sizeof(str), + "UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss); + warning(str); + } } return 0; From 61d7ff06d278ad2f57cb0cce52b0c59890646307 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Thu, 12 Feb 2026 18:02:23 +0000 Subject: [PATCH 11/12] udp gso/gro: clean up help text guards and debug output - Remove conditional guards around --gsro help text to match option availability (option is always available regardless of local support) - Remove debug printf statements from net.c (net layer doesn't log to console; returns error codes silently per existing pattern) - Remove duplicate iperf.h include in net.c This ensures --gsro appears in help on all systems and eliminates console spam from GSO/GRO error paths. --- src/iperf_locale.c | 2 -- src/net.c | 9 --------- 2 files changed, 11 deletions(-) diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 02d69b320..5d795e3d7 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -219,9 +219,7 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" " --extra-data str data string to include in client and server JSON\n" " --get-server-output get results from server\n" " --udp-counters-64bit use 64-bit counters in UDP test packets\n" -#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO) " --gsro enable UDP GSO/GRO on both client and server (client-only option)\n" -#endif " --repeating-payload use repeating pattern in payload, instead of\n" " randomized payload (like in iperf2)\n" #if defined(HAVE_DONT_FRAGMENT) diff --git a/src/net.c b/src/net.c index 8fbc4a0a2..f628710d6 100644 --- a/src/net.c +++ b/src/net.c @@ -42,8 +42,6 @@ #include #endif -#include "iperf.h" - #ifdef HAVE_SENDFILE #ifdef linux #include @@ -593,7 +591,6 @@ Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz) if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { return 0; } else { - printf("\nUnexpected error (%d)\n", errno); return NET_HARDERROR; } } @@ -684,9 +681,6 @@ static int udp_sendmsg_gso(int fd, const char *buf, size_t count, uint16_t gso_s ret = sendmsg(fd, &msg, 0); - if (ret != iov.iov_len) - printf("msg: %u != %llu\n", ret, (unsigned long long) iov.iov_len); - return ret; } @@ -704,15 +698,12 @@ Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size) #if (EAGAIN != EWOULDBLOCK) case EWOULDBLOCK: #endif - printf("\nerrono (%d)\n", errno); return 0; case ENOBUFS: - printf("\nUnexpected error ENOBUFS (%d)\n", ENOBUFS); return NET_SOFTERROR; default: - printf("\nUnexpected error (%d)\n", errno); return NET_HARDERROR; } } From 6d26be36ca9f4024a2f8ad8ec905caf156d80970 Mon Sep 17 00:00:00 2001 From: Guillaume Egles Date: Thu, 12 Feb 2026 18:39:56 +0000 Subject: [PATCH 12/12] udp gso/gro: refactor to use stub implementations and fix issues Replace compile-time feature guards in headers with stub implementations that return errors when features are unavailable: - Remove #ifdef guards from net.h function declarations - Reorganize net.h to group Nread_gro with read functions and Nwrite_gso with write functions - Add stub implementations in net.c and iperf_udp.c that return errors and set gso/gro flags to 0 when features not supported - Remove guards around ALL function calls (including in iperf_udp_connect) to ensure stubs run on platforms without GSO/GRO support - Keep guards only around setsockopt calls using platform constants Also fix: - Unused variable warning: remove cnt from iperf_udp_recv - Warning ordering: show platform support warnings only after client-only check passes (prevents confusing output with -s --gsro) This fixes the critical bug where --gsro on macOS caused zero traffic because ifdef guards prevented stubs from running, leaving gso=1 and triggering the unsupported GSO path. Benefits: - Cleaner API (no preprocessor clutter in headers) - Runtime feature detection via stubs - Code works correctly on all platforms - Better code organization and error messages --- src/iperf_api.c | 18 +++++++++++------- src/iperf_udp.c | 43 +++++++++++++++++++++---------------------- src/net.c | 14 ++++++++++++++ src/net.h | 8 ++------ 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/iperf_api.c b/src/iperf_api.c index e5bb6fa48..8eb834917 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -1798,13 +1798,6 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) gsro_flag = 1; test->settings->gso = 1; test->settings->gro = 1; -#if !defined(HAVE_UDP_SEGMENT) && !defined(HAVE_UDP_GRO) - warning("--gsro requested but UDP GSO/GRO not supported on this client; will only be enabled on server if supported"); -#elif !defined(HAVE_UDP_SEGMENT) - warning("--gsro requested but UDP GSO not supported on this client; will be enabled on server if supported"); -#elif !defined(HAVE_UDP_GRO) - warning("--gsro requested but UDP GRO not supported on this client; will be enabled on server if supported"); -#endif break; case 'h': usage_long(stdout); @@ -1830,6 +1823,17 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; } + /* Show platform support warnings only after confirming we're in client mode */ + if (gsro_flag) { +#if !defined(HAVE_UDP_SEGMENT) && !defined(HAVE_UDP_GRO) + warning("--gsro requested but UDP GSO/GRO not supported on this client; will only be enabled on server if supported"); +#elif !defined(HAVE_UDP_SEGMENT) + warning("--gsro requested but UDP GSO not supported on this client; will be enabled on server if supported"); +#elif !defined(HAVE_UDP_GRO) + warning("--gsro requested but UDP GRO not supported on this client; will be enabled on server if supported"); +#endif + } + #if defined(HAVE_SSL) if (test->role == 's' && (client_username || client_rsa_public_key)){ diff --git a/src/iperf_udp.c b/src/iperf_udp.c index f433016fa..371709a1e 100644 --- a/src/iperf_udp.c +++ b/src/iperf_udp.c @@ -70,7 +70,6 @@ iperf_udp_recv(struct iperf_stream *sp) int sock_opt = 0; int dgram_sz; int buf_sz; - int cnt = 0; char *dgram_buf; char *dgram_buf_end; const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ @@ -84,16 +83,13 @@ iperf_udp_recv(struct iperf_stream *sp) #endif /* HAVE_MSG_TRUNC */ /* Configure loop parameters based on GRO availability */ -#ifdef HAVE_UDP_GRO if (sp->test->settings->gro) { size = sp->test->settings->gro_bf_size; r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz); /* Use negotiated block size for GRO segment stride to ensure correct parsing. */ dgram_sz = sp->settings->blksize; buf_sz = r; - } else -#endif - { + } else { /* GRO disabled or unavailable - use normal UDP receive and single packet size */ r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt); dgram_sz = sp->settings->blksize; @@ -130,7 +126,6 @@ iperf_udp_recv(struct iperf_stream *sp) dgram_buf_end = sp->buffer + buf_sz; while (buf_sz >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) { - cnt++; /* Ensure we have enough bytes for the packet header */ if (buf_sz < min_pkt_size) @@ -217,7 +212,6 @@ iperf_udp_send(struct iperf_stream *sp) const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */ /* Configure loop parameters based on GSO availability */ -#ifdef HAVE_UDP_SEGMENT if (sp->test->settings->gso) { dgram_sz = sp->test->settings->gso_dg_size; buf_sz = sp->test->settings->gso_bf_size; @@ -228,9 +222,7 @@ iperf_udp_send(struct iperf_stream *sp) dgram_sz = buf_sz = size; sp->test->settings->gso = 0; /* Disable GSO for safety */ } - } else -#endif - { + } else { /* GSO disabled or unavailable - single packet */ dgram_sz = buf_sz = size; } @@ -287,13 +279,12 @@ iperf_udp_send(struct iperf_stream *sp) printf("GSO: %d bytes remaining unprocessed\n", buf_sz); } -#ifdef HAVE_UDP_SEGMENT if (sp->test->settings->gso) { size = sp->test->settings->gso_bf_size; r = Nwrite_gso(sp->socket, sp->buffer, size, Pudp, sp->test->settings->gso_dg_size); - } else -#endif - r = Nwrite(sp->socket, sp->buffer, size, Pudp); + } else { + r = Nwrite(sp->socket, sp->buffer, size, Pudp); + } if (r <= 0) { --sp->packet_count; /* Don't count messages that no data was sent from them. @@ -438,6 +429,14 @@ iperf_udp_gso(struct iperf_test *test, int s) return rc; } +#else +int +iperf_udp_gso(struct iperf_test *test, int s) +{ + /* GSO not supported on this platform */ + test->settings->gso = 0; + return -1; +} #endif #ifdef HAVE_UDP_GRO @@ -459,6 +458,14 @@ iperf_udp_gro(struct iperf_test *test, int s) return rc; } +#else +int +iperf_udp_gro(struct iperf_test *test, int s) +{ + /* GRO not supported on this platform */ + test->settings->gro = 0; + return -1; +} #endif /* @@ -520,14 +527,10 @@ iperf_udp_accept(struct iperf_test *test) } } -#ifdef HAVE_UDP_SEGMENT if (test->settings->gso) iperf_udp_gso(test, s); -#endif -#ifdef HAVE_UDP_GRO if (test->settings->gro) iperf_udp_gro(test, s); -#endif #if defined(HAVE_SO_MAX_PACING_RATE) /* If socket pacing is specified, try it. */ @@ -629,14 +632,10 @@ iperf_udp_connect(struct iperf_test *test) /* error */ return rc; -#ifdef HAVE_UDP_SEGMENT if (test->settings->gso) iperf_udp_gso(test, s); -#endif -#ifdef HAVE_UDP_GRO if (test->settings->gro) iperf_udp_gro(test, s); -#endif /* * If the socket buffer was too small, but it was the default diff --git a/src/net.c b/src/net.c index f628710d6..275595f20 100644 --- a/src/net.c +++ b/src/net.c @@ -603,6 +603,13 @@ Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz) return r; } +#else +int +Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz) +{ + /* GRO not supported on this platform */ + return NET_HARDERROR; +} #endif /* HAVE_UDP_GRO */ /* @@ -709,6 +716,13 @@ Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size) } return r; } +#else +int +Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size) +{ + /* GSO not supported on this platform */ + return NET_HARDERROR; +} #endif /* HAVE_UDP_SEGMENT */ int diff --git a/src/net.h b/src/net.h index de2554fc8..9ce20e4c7 100644 --- a/src/net.h +++ b/src/net.h @@ -35,18 +35,14 @@ int Nread(int fd, char *buf, size_t count, int prot); int Nrecv(int fd, char *buf, size_t count, int prot, int sock_opt); int Nread_no_select(int fd, char *buf, size_t count, int prot); int Nrecv_no_select(int fd, char *buf, size_t count, int prot, int sock_opt); +int Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz); int Nwrite(int fd, const char *buf, size_t count, int prot) /* __attribute__((hot)) */; +int Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size); int has_sendfile(void); int Nsendfile(int fromfd, int tofd, const char *buf, size_t count) /* __attribute__((hot)) */; int setnonblocking(int fd, int nonblocking); int getsockdomain(int sock); int parse_qos(const char *tos); -#ifdef HAVE_UDP_GRO -int Nread_gro(int fd, char *buf, size_t count, int prot, int *dgram_sz); -#endif -#ifdef HAVE_UDP_SEGMENT -int Nwrite_gso(int fd, const char *buf, size_t count, int prot, uint16_t gso_size); -#endif #define NET_SOFTERROR -1 #define NET_HARDERROR -2