Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -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 <linux/udp.h>
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 <linux/udp.h>
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; }
Expand Down
24 changes: 24 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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 <linux/udp.h>]],
[[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 <linux/udp.h>]],
[[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
Expand Down
12 changes: 12 additions & 0 deletions src/iperf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
105 changes: 103 additions & 2 deletions src/iperf_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
Expand Down Expand Up @@ -1781,6 +1784,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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have experience with using gso/gro, but since there are two separate options for setsockopt(), it may be better to also allow setting these options separately. This can be done by adding an optional_argument:
--no-gsro [<GSO>][/<GRO>], where GSO/GRO are boolean (0/1, T/F, E/N (enable/disable), etc.). The default will be that gso/gro will not be supported as it is implemented now (with the name of the option, this probably means they are set to true by default). For example of how to define and parse option with optional arguments see --cntl-ka (OPT_CNTL_KA).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidBar-On I hear what you’re saying, and I’m open to changing the option’s behavior. For example, I originally had two distinct options to explicitly enable or disable each feature (--no-gso and --no-gro). But then I started thinking: if the kernel supports them and they’re implemented correctly, why wouldn’t we want both enabled by default? The packets on the wire are identical, and the benefits—whether higher throughput or lower CPU usage—are significant.

That said, if the consensus is to provide more knobs and finer-grained control, I’m fine with that too.

/* 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);
Expand All @@ -1802,6 +1816,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)){
Expand Down Expand Up @@ -1906,6 +1922,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 = DEFAULT_UDP_BLKSIZE;
}
}
#endif

test->settings->blksize = blksize;

if (!rate_flag)
Expand Down Expand Up @@ -2449,6 +2479,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)
Expand Down Expand Up @@ -2572,6 +2618,33 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since get_parameters() is run by the Server and --no-gsro is a Client only parameter, then there should be no related logic here. Instead, all the related values should be sent in send_parameters() (by the Client), and get_parameters() will just get them and set the related variables.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out. Addressed in e63ff17.
I moved the GSO/GRO policy decisions out of get_parameters() and into the client side:

  • The client now includes GSO/GRO intent and sizes in send_parameters(). The server’s get_parameters() just reads those values and doesn’t recompute from blksize.
  • Each endpoint independently attempts to enable the feature on its own sockets. If the local kernel rejects it (e.g., setsockopt(UDP_SEGMENT/UDP_GRO) fails), we log and disable that feature locally. This correctly handles the asymmetric case (sender uses GSO, receiver uses GRO) and
    kernel capability differences.
  • For compatibility with older clients that don’t send GSO fields, the server derives gso_dg_size from blksize as a fallback to preserve prior behavior. If the client sends explicit values (including --no-gsro sending gso=0/gro=0), the server honors them as-is.

This keeps --no-gsro a client-only knob, avoids server-side policy decisions, and still respects local kernel capabilities at runtime. Build and unit tests pass locally.

/* 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;
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 {
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)
Expand Down Expand Up @@ -3231,6 +3304,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;
Expand Down Expand Up @@ -3544,6 +3626,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;
Expand Down Expand Up @@ -4708,6 +4797,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) {
Expand Down Expand Up @@ -4766,13 +4856,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);
Expand Down
1 change: 1 addition & 0 deletions src/iperf_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 34 additions & 22 deletions src/iperf_client_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 = DEFAULT_UDP_BLKSIZE;
}
}
#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;
Expand Down
6 changes: 6 additions & 0 deletions src/iperf_config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -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 <unistd.h> header file. */
#undef HAVE_UNISTD_H

Expand Down
Loading