Skip to content

Commit 36b0a72

Browse files
marcosfschgegles
authored andcommitted
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 <linux/udp.h> 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
1 parent d348cd7 commit 36b0a72

File tree

11 files changed

+650
-25
lines changed

11 files changed

+650
-25
lines changed

configure

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17399,6 +17399,78 @@ printf "%s\n" "#define HAVE_IPPROTO_MPTCP 1" >>confdefs.h
1739917399

1740017400
fi
1740117401

17402+
# Check for UDP_SEGMENT sockopt
17403+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking UDP_SEGMENT socket option" >&5
17404+
printf %s "checking UDP_SEGMENT socket option... " >&6; }
17405+
if test ${iperf3_cv_header_udp_segment+y}
17406+
then :
17407+
printf %s "(cached) " >&6
17408+
else case e in #(
17409+
e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
17410+
/* end confdefs.h. */
17411+
#include <linux/udp.h>
17412+
int
17413+
main (void)
17414+
{
17415+
int foo = UDP_SEGMENT;
17416+
;
17417+
return 0;
17418+
}
17419+
_ACEOF
17420+
if ac_fn_c_try_compile "$LINENO"
17421+
then :
17422+
iperf3_cv_header_udp_segment=yes
17423+
else case e in #(
17424+
e) iperf3_cv_header_udp_segment=no ;;
17425+
esac
17426+
fi
17427+
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
17428+
esac
17429+
fi
17430+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $iperf3_cv_header_udp_segment" >&5
17431+
printf "%s\n" "$iperf3_cv_header_udp_segment" >&6; }
17432+
if test "x$iperf3_cv_header_udp_segment" = "xyes"; then
17433+
17434+
printf "%s\n" "#define HAVE_UDP_SEGMENT 1" >>confdefs.h
17435+
17436+
fi
17437+
17438+
# Check for UDP_GRO sockopt
17439+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking UDP_GRO socket option" >&5
17440+
printf %s "checking UDP_GRO socket option... " >&6; }
17441+
if test ${iperf3_cv_header_udp_gro+y}
17442+
then :
17443+
printf %s "(cached) " >&6
17444+
else case e in #(
17445+
e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext
17446+
/* end confdefs.h. */
17447+
#include <linux/udp.h>
17448+
int
17449+
main (void)
17450+
{
17451+
int foo = UDP_GRO;
17452+
;
17453+
return 0;
17454+
}
17455+
_ACEOF
17456+
if ac_fn_c_try_compile "$LINENO"
17457+
then :
17458+
iperf3_cv_header_udp_gro=yes
17459+
else case e in #(
17460+
e) iperf3_cv_header_udp_gro=no ;;
17461+
esac
17462+
fi
17463+
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;;
17464+
esac
17465+
fi
17466+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $iperf3_cv_header_udp_gro" >&5
17467+
printf "%s\n" "$iperf3_cv_header_udp_gro" >&6; }
17468+
if test "x$iperf3_cv_header_udp_gro" = "xyes"; then
17469+
17470+
printf "%s\n" "#define HAVE_UDP_GRO 1" >>confdefs.h
17471+
17472+
fi
17473+
1740217474
# Check if we need -lrt for clock_gettime
1740317475
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
1740417476
printf %s "checking for library containing clock_gettime... " >&6; }

configure.ac

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,30 @@ if test "x$iperf3_cv_header_ipproto_mptcp" = "xyes"; then
375375
AC_DEFINE([HAVE_IPPROTO_MPTCP], [1], [Have MPTCP protocol.])
376376
fi
377377

378+
# Check for UDP_SEGMENT sockopt
379+
AC_CACHE_CHECK([UDP_SEGMENT socket option],
380+
[iperf3_cv_header_udp_segment],
381+
AC_COMPILE_IFELSE(
382+
[AC_LANG_PROGRAM([[#include <linux/udp.h>]],
383+
[[int foo = UDP_SEGMENT;]])],
384+
iperf3_cv_header_udp_segment=yes,
385+
iperf3_cv_header_udp_segment=no))
386+
if test "x$iperf3_cv_header_udp_segment" = "xyes"; then
387+
AC_DEFINE([HAVE_UDP_SEGMENT], [1], [Have UDP_SEGMENT sockopt.])
388+
fi
389+
390+
# Check for UDP_GRO sockopt
391+
AC_CACHE_CHECK([UDP_GRO socket option],
392+
[iperf3_cv_header_udp_gro],
393+
AC_COMPILE_IFELSE(
394+
[AC_LANG_PROGRAM([[#include <linux/udp.h>]],
395+
[[int foo = UDP_GRO;]])],
396+
iperf3_cv_header_udp_gro=yes,
397+
iperf3_cv_header_udp_gro=no))
398+
if test "x$iperf3_cv_header_udp_gro" = "xyes"; then
399+
AC_DEFINE([HAVE_UDP_GRO], [1], [Have UDP_GRO sockopt.])
400+
fi
401+
378402
# Check if we need -lrt for clock_gettime
379403
AC_SEARCH_LIBS(clock_gettime, [rt posix4])
380404
# Check for clock_gettime support

src/iperf.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ struct iperf_settings
191191
int cntl_ka_keepidle; /* Control TCP connection Keepalive idle time (TCP_KEEPIDLE) */
192192
int cntl_ka_interval; /* Control TCP connection Keepalive interval between retries (TCP_KEEPINTV) */
193193
int cntl_ka_count; /* Control TCP connection Keepalive number of retries (TCP_KEEPCNT) */
194+
#ifdef HAVE_UDP_SEGMENT
195+
int gso;
196+
int gso_dg_size;
197+
int gso_bf_size;
198+
#endif
199+
#ifdef HAVE_UDP_GRO
200+
int gro;
201+
int gro_bf_size;
202+
#endif
194203
};
195204

196205
struct iperf_test;
@@ -486,4 +495,7 @@ extern int gerror; /* error value from getaddrinfo(3), for use in internal error
486495
/* In Reverse mode, maximum number of packets to wait for "accept" response - to handle out of order packets */
487496
#define MAX_REVERSE_OUT_OF_ORDER_PACKETS 2
488497

498+
#define GSO_BF_MAX_SIZE MAX_UDP_BLOCKSIZE
499+
#define GRO_BF_MAX_SIZE MAX_UDP_BLOCKSIZE
500+
489501
#endif /* !__IPERF_H */

src/iperf_api.c

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
11881188
#endif /* HAVE_TCP_KEEPALIVE */
11891189
#if defined(HAVE_IPPROTO_MPTCP)
11901190
{"mptcp", no_argument, NULL, 'm'},
1191+
#endif
1192+
#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO)
1193+
{"no-gsro", no_argument, NULL, OPT_NO_GSRO},
11911194
#endif
11921195
{"debug", optional_argument, NULL, 'd'},
11931196
{"help", no_argument, NULL, 'h'},
@@ -1781,6 +1784,17 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
17811784
set_protocol(test, Ptcp);
17821785
test->mptcp = 1;
17831786
break;
1787+
#endif
1788+
#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO)
1789+
case OPT_NO_GSRO:
1790+
/* Disable GSO/GRO which would otherwise be enabled by default */
1791+
#ifdef HAVE_UDP_SEGMENT
1792+
test->settings->gso = 0;
1793+
#endif
1794+
#ifdef HAVE_UDP_GRO
1795+
test->settings->gro = 0;
1796+
#endif
1797+
break;
17841798
#endif
17851799
case 'h':
17861800
usage_long(stdout);
@@ -1802,6 +1816,8 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
18021816
return -1;
18031817
}
18041818

1819+
/* GSO/GRO are enabled by default when available, disabled only via --no-gsro */
1820+
18051821
#if defined(HAVE_SSL)
18061822

18071823
if (test->role == 's' && (client_username || client_rsa_public_key)){
@@ -1906,6 +1922,20 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
19061922
i_errno = IEUDPBLOCKSIZE;
19071923
return -1;
19081924
}
1925+
1926+
#ifdef HAVE_UDP_SEGMENT
1927+
if (test->protocol->id == Pudp && test->settings->gso) {
1928+
test->settings->gso_dg_size = blksize;
1929+
/* use the multiple of datagram size for the best efficiency. */
1930+
if (test->settings->gso_dg_size > 0) {
1931+
test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size;
1932+
} else {
1933+
/* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */
1934+
test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */
1935+
}
1936+
}
1937+
#endif
1938+
19091939
test->settings->blksize = blksize;
19101940

19111941
if (!rate_flag)
@@ -2572,6 +2602,18 @@ get_parameters(struct iperf_test *test)
25722602
test->settings->socket_bufsize = j_p->valueint;
25732603
if ((j_p = iperf_cJSON_GetObjectItemType(j, "len", cJSON_Number)) != NULL)
25742604
test->settings->blksize = j_p->valueint;
2605+
#ifdef HAVE_UDP_SEGMENT
2606+
if (test->protocol->id == Pudp && test->settings->gso == 1) {
2607+
test->settings->gso_dg_size = test->settings->blksize;
2608+
/* use the multiple of datagram size for the best efficiency. */
2609+
if (test->settings->gso_dg_size > 0) {
2610+
test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size;
2611+
} else {
2612+
/* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */
2613+
test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */
2614+
}
2615+
}
2616+
#endif
25752617
if ((j_p = iperf_cJSON_GetObjectItemType(j, "bandwidth", cJSON_Number)) != NULL)
25762618
test->settings->rate = j_p->valueint;
25772619
if ((j_p = iperf_cJSON_GetObjectItemType(j, "fqrate", cJSON_Number)) != NULL)
@@ -3232,6 +3274,15 @@ iperf_defaults(struct iperf_test *testp)
32323274
testp->settings->fqrate = 0;
32333275
testp->settings->pacing_timer = DEFAULT_PACING_TIMER;
32343276
testp->settings->burst = 0;
3277+
#ifdef HAVE_UDP_SEGMENT
3278+
testp->settings->gso = 1; /* Enable GSO by default */
3279+
testp->settings->gso_dg_size = 0;
3280+
testp->settings->gso_bf_size = GSO_BF_MAX_SIZE;
3281+
#endif
3282+
#ifdef HAVE_UDP_GRO
3283+
testp->settings->gro = 1; /* Enable GRO by default */
3284+
testp->settings->gro_bf_size = GRO_BF_MAX_SIZE;
3285+
#endif
32353286
testp->settings->mss = 0;
32363287
testp->settings->bytes = 0;
32373288
testp->settings->blocks = 0;
@@ -3545,6 +3596,13 @@ iperf_reset_test(struct iperf_test *test)
35453596
test->settings->burst = 0;
35463597
test->settings->mss = 0;
35473598
test->settings->tos = 0;
3599+
#ifdef HAVE_UDP_SEGMENT
3600+
test->settings->gso_dg_size = 0;
3601+
test->settings->gso_bf_size = GSO_BF_MAX_SIZE;
3602+
#endif
3603+
#ifdef HAVE_UDP_GRO
3604+
test->settings->gro_bf_size = GRO_BF_MAX_SIZE;
3605+
#endif
35483606
test->settings->dont_fragment = 0;
35493607
test->zerocopy = 0;
35503608
test->settings->skip_rx_copy = 0;
@@ -4709,6 +4767,7 @@ iperf_new_stream(struct iperf_test *test, int s, int sender)
47094767
{
47104768
struct iperf_stream *sp;
47114769
int ret = 0;
4770+
int size;
47124771

47134772
char template[1024];
47144773
if (test->tmp_template) {
@@ -4767,13 +4826,24 @@ iperf_new_stream(struct iperf_test *test, int s, int sender)
47674826
free(sp);
47684827
return NULL;
47694828
}
4770-
if (ftruncate(sp->buffer_fd, test->settings->blksize) < 0) {
4829+
size = test->settings->blksize;
4830+
#ifdef HAVE_UDP_SEGMENT
4831+
if (test->protocol->id == Pudp && test->settings->gso && (size < test->settings->gso_bf_size))
4832+
size = test->settings->gso_bf_size;
4833+
#endif
4834+
#ifdef HAVE_UDP_GRO
4835+
if (test->protocol->id == Pudp && test->settings->gro && (size < test->settings->gro_bf_size))
4836+
size = test->settings->gro_bf_size;
4837+
#endif
4838+
if (sp->test->debug)
4839+
printf("Buffer %d bytes\n", size);
4840+
if (ftruncate(sp->buffer_fd, size) < 0) {
47714841
i_errno = IECREATESTREAM;
47724842
free(sp->result);
47734843
free(sp);
47744844
return NULL;
47754845
}
4776-
sp->buffer = (char *) mmap(NULL, test->settings->blksize, PROT_READ|PROT_WRITE, MAP_SHARED, sp->buffer_fd, 0);
4846+
sp->buffer = (char *) mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE, sp->buffer_fd, 0);
47774847
if (sp->buffer == MAP_FAILED) {
47784848
i_errno = IECREATESTREAM;
47794849
free(sp->result);

src/iperf_api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t;
106106
#define OPT_SKIP_RX_COPY 32
107107
#define OPT_JSON_STREAM_FULL_OUTPUT 33
108108
#define OPT_SERVER_MAX_DURATION 34
109+
#define OPT_NO_GSRO 35
109110

110111
/* states */
111112
#define TEST_START 1

src/iperf_client_api.c

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -505,29 +505,41 @@ iperf_connect(struct iperf_test *test)
505505
* the user always has the option to override.
506506
*/
507507
if (test->protocol->id == Pudp) {
508-
if (test->settings->blksize == 0) {
509-
if (test->ctrl_sck_mss) {
510-
test->settings->blksize = test->ctrl_sck_mss;
511-
}
512-
else {
513-
test->settings->blksize = DEFAULT_UDP_BLKSIZE;
514-
}
515-
if (test->verbose) {
516-
printf("Setting UDP block size to %d\n", test->settings->blksize);
517-
}
518-
}
508+
if (test->settings->blksize == 0) {
509+
if (test->ctrl_sck_mss) {
510+
test->settings->blksize = test->ctrl_sck_mss;
511+
}
512+
else {
513+
test->settings->blksize = DEFAULT_UDP_BLKSIZE;
514+
}
515+
if (test->verbose) {
516+
printf("Setting UDP block size to %d\n", test->settings->blksize);
517+
}
518+
}
519+
#ifdef HAVE_UDP_SEGMENT
520+
if (test->settings->gso) {
521+
test->settings->gso_dg_size = test->settings->blksize;
522+
/* use the multiple of datagram size for the best efficiency. */
523+
if (test->settings->gso_dg_size > 0) {
524+
test->settings->gso_bf_size = (test->settings->gso_bf_size / test->settings->gso_dg_size) * test->settings->gso_dg_size;
525+
} else {
526+
/* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */
527+
test->settings->gso_dg_size = 1472; /* Standard UDP payload size for Ethernet MTU */
528+
}
529+
}
530+
#endif
519531

520-
/*
521-
* Regardless of whether explicitly or implicitly set, if the
522-
* block size is larger than the MSS, print a warning.
523-
*/
524-
if (test->ctrl_sck_mss > 0 &&
525-
test->settings->blksize > test->ctrl_sck_mss) {
526-
char str[WARN_STR_LEN];
527-
snprintf(str, sizeof(str),
528-
"UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss);
529-
warning(str);
530-
}
532+
/*
533+
* Regardless of whether explicitly or implicitly set, if the
534+
* block size is larger than the MSS, print a warning.
535+
*/
536+
if (test->ctrl_sck_mss > 0 &&
537+
test->settings->blksize > test->ctrl_sck_mss) {
538+
char str[WARN_STR_LEN];
539+
snprintf(str, sizeof(str),
540+
"UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss);
541+
warning(str);
542+
}
531543
}
532544

533545
return 0;

src/iperf_config.h.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@
132132
/* Have TCP_USER_TIMEOUT sockopt. */
133133
#undef HAVE_TCP_USER_TIMEOUT
134134

135+
/* Have UDP_GRO sockopt. */
136+
#undef HAVE_UDP_GRO
137+
138+
/* Have UDP_SEGMENT sockopt. */
139+
#undef HAVE_UDP_SEGMENT
140+
135141
/* Define to 1 if you have the <unistd.h> header file. */
136142
#undef HAVE_UNISTD_H
137143

src/iperf_locale.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
219219
" --extra-data str data string to include in client and server JSON\n"
220220
" --get-server-output get results from server\n"
221221
" --udp-counters-64bit use 64-bit counters in UDP test packets\n"
222+
#if defined(HAVE_UDP_SEGMENT) || defined(HAVE_UDP_GRO)
223+
" --no-gsro disable UDP GSO/GRO (Generic Segmentation/Receive Offload)\n"
224+
#endif
222225
" --repeating-payload use repeating pattern in payload, instead of\n"
223226
" randomized payload (like in iperf2)\n"
224227
#if defined(HAVE_DONT_FRAGMENT)

0 commit comments

Comments
 (0)