Skip to content

Commit

Permalink
Fix #812 and #835 - add control connection keepalive (after rebase wi…
Browse files Browse the repository at this point in the history
…th master 3.17.1+)
  • Loading branch information
davidBar-On committed Aug 29, 2024
1 parent 5bf93d6 commit 9d73cee
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 7 deletions.
12 changes: 12 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,18 @@ if test "x$iperf3_cv_header_tcp_user_timeout" = "xyes"; then
AC_DEFINE([HAVE_TCP_USER_TIMEOUT], [1], [Have TCP_USER_TIMEOUT sockopt.])
fi

# Check for TCP_KEEPIDLE sockopt (not clear where supported)
AC_CACHE_CHECK([TCP_KEEPIDLE socket option],
[iperf3_cv_header_tcp_keepalive],
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <netinet/tcp.h>]],
[[int foo = TCP_KEEPIDLE;]])],
iperf3_cv_header_tcp_keepalive=yes,
iperf3_cv_header_tcp_keepalive=no))
if test "x$iperf3_cv_header_tcp_keepalive" = "xyes"; then
AC_DEFINE([HAVE_TCP_KEEPALIVE], [1], [Have TCP_KEEPIDLE sockopt.])
fi

# Check for IPv6 flowlabel support (believed to be Linux only)
# We check for IPV6_FLOWLABEL_MGR in <linux/in6.h> even though we
# don't use that file directly (we have our own stripped-down
Expand Down
4 changes: 4 additions & 0 deletions src/iperf.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ struct iperf_settings
int idle_timeout; /* server idle time timeout */
unsigned int snd_timeout; /* Timeout for sending tcp messages in active mode, in us */
struct iperf_time rcv_timeout; /* Timeout for receiving messages in active mode, in us */
int cntl_ka; /* Use Control TCP connection Keepalive */
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) */
};

struct iperf_test;
Expand Down
122 changes: 122 additions & 0 deletions src/iperf_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
{"idle-timeout", required_argument, NULL, OPT_IDLE_TIMEOUT},
{"rcv-timeout", required_argument, NULL, OPT_RCV_TIMEOUT},
{"snd-timeout", required_argument, NULL, OPT_SND_TIMEOUT},
#if defined(HAVE_TCP_KEEPALIVE)
{"cntl-ka", optional_argument, NULL, OPT_CNTL_KA},
#endif /* HAVE_TCP_KEEPALIVE */
{"debug", optional_argument, NULL, 'd'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
Expand All @@ -1162,6 +1165,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
char* comma;
#endif /* HAVE_CPU_AFFINITY */
char* slash;
#if defined(HAVE_TCP_KEEPALIVE)
char* slash2;
#endif /* HAVE_TCP_KEEPALIVE */
char *p, *p1;
struct xbind_entry *xbe;
double farg;
Expand Down Expand Up @@ -1530,6 +1536,39 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
snd_timeout_flag = 1;
break;
#endif /* HAVE_TCP_USER_TIMEOUT */
#if defined (HAVE_TCP_KEEPALIVE)
case OPT_CNTL_KA:
test->settings->cntl_ka = 1;
if (optarg) {
slash = strchr(optarg, '/');
if (slash) {
*slash = '\0';
++slash;
slash2 = strchr(slash, '/');
if (slash2) {
*slash2 = '\0';
++slash2;
if (strlen(slash2) > 0) {
test->settings->cntl_ka_count = atoi(slash2);
}
}
if (strlen(slash) > 0) {
test->settings->cntl_ka_interval = atoi(slash);
}
}
if (strlen(optarg) > 0) {
test->settings->cntl_ka_keepidle = atoi(optarg);
}
}
// Seems that at least in Windows WSL2, TCP keepalive retries full inteval must be
// smaller than the idle interval. Otherwise, the keepalive message is sent only once.
if (test->settings->cntl_ka_keepidle &&
test->settings->cntl_ka_keepidle <= (test->settings->cntl_ka_count * test->settings->cntl_ka_interval)) {
i_errno = IECNTLKA;
return -1;
}
break;
#endif /* HAVE_TCP_KEEPALIVE */
case 'A':
#if defined(HAVE_CPU_AFFINITY)
test->affinity = strtol(optarg, &endptr, 0);
Expand Down Expand Up @@ -2975,6 +3014,10 @@ iperf_defaults(struct iperf_test *testp)
testp->settings->rcv_timeout.secs = DEFAULT_NO_MSG_RCVD_TIMEOUT / SEC_TO_mS;
testp->settings->rcv_timeout.usecs = (DEFAULT_NO_MSG_RCVD_TIMEOUT % SEC_TO_mS) * mS_TO_US;
testp->zerocopy = 0;
testp->settings->cntl_ka = 0;
testp->settings->cntl_ka_keepidle = 0;
testp->settings->cntl_ka_interval = 0;
testp->settings->cntl_ka_count = 0;

memset(testp->cookie, 0, COOKIE_SIZE);

Expand Down Expand Up @@ -5172,3 +5215,82 @@ iflush(struct iperf_test *test)

return rc2;
}

#if defined (HAVE_TCP_KEEPALIVE)
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
int
iperf_set_control_keepalive(struct iperf_test *test)
{
int opt, kaidle, kainterval, kacount;
socklen_t len;

if (test->settings->cntl_ka) {
// Set keepalive using system defaults
opt = 1;
if (setsockopt(test->ctrl_sck, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt))) {
i_errno = IESETCNTLKA;
return -1;
}

// Get default values when not specified
if ((kaidle = test->settings->cntl_ka_keepidle) == 0) {
len = sizeof(kaidle);
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPIDLE, (char *) &kaidle, &len)) {
i_errno = IESETCNTLKAINTERVAL;
return -1;
}
}
if ((kainterval = test->settings->cntl_ka_interval) == 0) {
len = sizeof(kainterval);
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &kainterval, &len)) {
i_errno = IESETCNTLKAINTERVAL;
return -1;
}
}
if ((kacount = test->settings->cntl_ka_count) == 0) {
len = sizeof(kacount);
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPCNT, (char *) &kacount, &len)) {
i_errno = IESETCNTLKACOUNT;
return -1;
}
}

// Seems that at least in Windows WSL2, TCP keepalive retries full inteval must be
// smaller than the idle interval. Otherwise, the keepalive message is sent only once.
if (test->settings->cntl_ka_keepidle) {
if (test->settings->cntl_ka_keepidle <= (kainterval * kacount)) {
iperf_err(test, "Keepalive Idle time (%d) should be greater than Retries-interval (%d) times Retries-count (%d)", kaidle, kainterval, kacount);
i_errno = IECNTLKA;
return -1;
}
}

// Set keep alive values when specified
if ((opt = test->settings->cntl_ka_keepidle)) {
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPIDLE, (char *) &opt, sizeof(opt))) {
i_errno = IESETCNTLKAKEEPIDLE;
return -1;
}
}
if ((opt = test->settings->cntl_ka_interval)) {
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &opt, sizeof(opt))) {
i_errno = IESETCNTLKAINTERVAL;
return -1;
}
}
if ((opt = test->settings->cntl_ka_count)) {
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPCNT, (char *) &opt, sizeof(opt))) {
i_errno = IESETCNTLKACOUNT;
return -1;
}
}

if (test->verbose) {
printf("Control connection TCP Keepalive TCP_KEEPIDLE/TCP_KEEPINTVL/TCP_KEEPCNT are set to %d/%d/%d\n",
kaidle, kainterval, kacount);
}
}

return 0;
}
#endif //HAVE_TCP_KEEPALIVE
14 changes: 14 additions & 0 deletions src/iperf_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t;
#define OPT_JSON_STREAM 28
#define OPT_SND_TIMEOUT 29
#define OPT_USE_PKCS1_PADDING 30
#define OPT_CNTL_KA 31

/* states */
#define TEST_START 1
Expand Down Expand Up @@ -309,6 +310,14 @@ void iperf_free_stream(struct iperf_stream * sp);
*/
int iperf_common_sockopts(struct iperf_test *, int s);

#if defined (HAVE_TCP_KEEPALIVE)
/**
* iperf_set_control_keepalive -- set control connection TCP keepalive
*
*/
int iperf_set_control_keepalive(struct iperf_test *test);
#endif //HAVE_TCP_KEEPALIVE

int has_tcpinfo(void);
int has_tcpinfo_retransmits(void);
void save_tcpinfo(struct iperf_stream *sp, struct iperf_interval_results *irp);
Expand Down Expand Up @@ -420,6 +429,7 @@ enum {
IESNDTIMEOUT = 33, // Illegal message send timeout
IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP
IESERVERAUTHUSERS = 35, // Cannot access authorized users file
IECNTLKA = 36, // Control connection Keepalive period should be larger than the full retry period (interval * count)
/* Test errors */
IENEWTEST = 100, // Unable to create a new test (check perror)
IEINITTEST = 101, // Test initialization failed (check perror)
Expand Down Expand Up @@ -475,6 +485,10 @@ enum {
IEPTHREADJOIN=152, // Unable to join thread (check perror)
IEPTHREADATTRINIT=153, // Unable to initialize thread attribute (check perror)
IEPTHREADATTRDESTROY=154, // Unable to destroy thread attribute (check perror)
IESETCNTLKA = 155, // Unable to set socket keepalive (SO_KEEPALIVE) option
IESETCNTLKAKEEPIDLE = 156, // Unable to set socket keepalive TCP period (TCP_KEEPIDLE) option
IESETCNTLKAINTERVAL = 157, // Unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option
IESETCNTLKACOUNT = 158, // Unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option
/* Stream errors */
IECREATESTREAM = 200, // Unable to create a new stream (check herror/perror)
IEINITSTREAM = 201, // Unable to initialize stream (check herror/perror)
Expand Down
10 changes: 8 additions & 2 deletions src/iperf_client_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -421,11 +421,17 @@ iperf_connect(struct iperf_test *test)
return -1;
}

#if defined (HAVE_TCP_KEEPALIVE)
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
if (iperf_set_control_keepalive(test) < 0)
return -1;
#endif //HAVE_TCP_KEEPALIVE

#if defined(HAVE_TCP_USER_TIMEOUT)
if ((opt = test->settings->snd_timeout)) {
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
i_errno = IESETUSERTIMEOUT;
return -1;
i_errno = IESETUSERTIMEOUT;
return -1;
}
}
#endif /* HAVE_TCP_USER_TIMEOUT */
Expand Down
28 changes: 23 additions & 5 deletions src/iperf_error.c
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ iperf_strerror(int int_errno)
case IEINTERVAL:
snprintf(errstr, len, "invalid report interval (min = %g, max = %g seconds)", MIN_INTERVAL, MAX_INTERVAL);
break;
case IEBIND: /* UNUSED */
case IEBIND: /* UNUSED */
snprintf(errstr, len, "--bind must be specified to use --cport");
break;
case IEUDPBLOCKSIZE:
Expand Down Expand Up @@ -464,7 +464,7 @@ iperf_strerror(int int_errno)
case IETOTALRATE:
snprintf(errstr, len, "total required bandwidth is larger than server limit");
break;
case IESKEWTHRESHOLD:
case IESKEWTHRESHOLD:
snprintf(errstr, len, "skew threshold must be a positive number");
break;
case IEIDLETIMEOUT:
Expand All @@ -473,16 +473,16 @@ iperf_strerror(int int_errno)
case IEBINDDEV:
snprintf(errstr, len, "Unable to bind-to-device (check perror, maybe permissions?)");
break;
case IEBINDDEVNOSUPPORT:
case IEBINDDEVNOSUPPORT:
snprintf(errstr, len, "`<ip>%%<dev>` is not supported as system does not support bind to device");
break;
case IEHOSTDEV:
case IEHOSTDEV:
snprintf(errstr, len, "host device name (ip%%<dev>) is supported (and required) only for IPv6 link-local address");
break;
case IENOMSG:
snprintf(errstr, len, "idle timeout for receiving data");
break;
case IESETDONTFRAGMENT:
case IESETDONTFRAGMENT:
snprintf(errstr, len, "unable to set IP Do-Not-Fragment flag");
break;
case IESETUSERTIMEOUT:
Expand All @@ -507,6 +507,24 @@ iperf_strerror(int int_errno)
break;
case IEPTHREADATTRDESTROY:
snprintf(errstr, len, "unable to destroy thread attributes");
case IECNTLKA:
snprintf(errstr, len, "control connection Keepalive period should be larger than the full retry period (interval * count)");
perr = 1;
break;
case IESETCNTLKA:
snprintf(errstr, len, "unable to set socket keepalive (SO_KEEPALIVE) option");
perr = 1;
break;
case IESETCNTLKAKEEPIDLE:
snprintf(errstr, len, "unable to set socket keepalive TCP period (TCP_KEEPIDLE) option");
perr = 1;
break;
case IESETCNTLKAINTERVAL:
snprintf(errstr, len, "unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option");
perr = 1;
break;
case IESETCNTLKACOUNT:
snprintf(errstr, len, "unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option");
perr = 1;
break;
default:
Expand Down
4 changes: 4 additions & 0 deletions src/iperf_locale.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
" --snd-timeout # timeout for unacknowledged TCP data\n"
" (in ms, default is system settings)\n"
#endif /* HAVE_TCP_USER_TIMEOUT */
#if defined(HAVE_TCP_KEEPALIVE)
" --cntl-ka[=#/#/#] use control connection TCP keepalive - KEEPIDLE/KEEPINTV/KEEPCNT\n"
" each value is optional with system settings default\n"
#endif //HAVE_TCP_KEEPALIVE
" -d, --debug[=#] emit debugging output\n"
" (optional optional \"=\" and debug level: 1-4. Default is 4 - all messages)\n"
" -v, --version show version information and quit\n"
Expand Down
6 changes: 6 additions & 0 deletions src/iperf_server_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ iperf_accept(struct iperf_test *test)
}
#endif /* HAVE_TCP_USER_TIMEOUT */

#if defined (HAVE_TCP_KEEPALIVE)
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
if (iperf_set_control_keepalive(test) < 0)
return -1;
#endif //HAVE_TCP_KEEPALIVE

if (Nread(test->ctrl_sck, test->cookie, COOKIE_SIZE, Ptcp) != COOKIE_SIZE) {
/*
* Note this error covers both the case of a system error
Expand Down

0 comments on commit 9d73cee

Please sign in to comment.