Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add control connection keepalive arguments #1423

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
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 @@ -3016,6 +3055,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 @@ -5214,3 +5257,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
16 changes: 15 additions & 1 deletion 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,7 +485,11 @@ 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)
IEPTHREADSIGMASK=155, // Unable to initialize sub thread signal mask (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
IEPTHREADSIGMASK=159, // Unable to initialize sub thread signal mask (check perror)
/* 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 @@ -444,11 +444,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 Down Expand Up @@ -510,6 +510,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 @@ -184,6 +184,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