Skip to content

Commit 4c4139d

Browse files
committed
Fix #812 and #835 - add control connection keepalive
1 parent 9d78f79 commit 4c4139d

File tree

7 files changed

+167
-7
lines changed

7 files changed

+167
-7
lines changed

configure.ac

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,18 @@ if test "x$iperf3_cv_header_tcp_user_timeout" = "xyes"; then
181181
AC_DEFINE([HAVE_TCP_USER_TIMEOUT], [1], [Have TCP_USER_TIMEOUT sockopt.])
182182
fi
183183

184+
# Check for TCP_KEEPIDLE sockopt (not clear where supported)
185+
AC_CACHE_CHECK([TCP_KEEPIDLE socket option],
186+
[iperf3_cv_header_tcp_keepalive],
187+
AC_COMPILE_IFELSE(
188+
[AC_LANG_PROGRAM([[#include <netinet/tcp.h>]],
189+
[[int foo = TCP_KEEPIDLE;]])],
190+
iperf3_cv_header_tcp_keepalive=yes,
191+
iperf3_cv_header_tcp_keepalive=no))
192+
if test "x$iperf3_cv_header_tcp_keepalive" = "xyes"; then
193+
AC_DEFINE([HAVE_TCP_KEEPALIVE], [1], [Have TCP_KEEPIDLE sockopt.])
194+
fi
195+
184196
# Check for IPv6 flowlabel support (believed to be Linux only)
185197
# We check for IPV6_FLOWLABEL_MGR in <linux/in6.h> even though we
186198
# don't use that file directly (we have our own stripped-down

src/iperf.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ struct iperf_settings
167167
int idle_timeout; /* server idle time timeout */
168168
unsigned int snd_timeout; /* Timeout for sending tcp messages in active mode, in us */
169169
struct iperf_time rcv_timeout; /* Timeout for receiving messages in active mode, in us */
170+
int cntl_ka; /* Use Control TCP connection Keepalive */
171+
int cntl_ka_keepidle; /* Control TCP connection Keepalive idle time (TCP_KEEPIDLE) */
172+
int cntl_ka_interval; /* Control TCP connection Keepalive interval between retries (TCP_KEEPINTV) */
173+
int cntl_ka_count; /* Control TCP connection Keepalive number of retries (TCP_KEEPCNT) */
170174
};
171175

172176
struct iperf_test;

src/iperf_api.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
10781078
{"idle-timeout", required_argument, NULL, OPT_IDLE_TIMEOUT},
10791079
{"rcv-timeout", required_argument, NULL, OPT_RCV_TIMEOUT},
10801080
{"snd-timeout", required_argument, NULL, OPT_SND_TIMEOUT},
1081+
#if defined(HAVE_TCP_KEEPALIVE)
1082+
{"cntl-ka", optional_argument, NULL, OPT_CNTL_KA},
1083+
#endif /* HAVE_TCP_KEEPALIVE */
10811084
{"debug", optional_argument, NULL, 'd'},
10821085
{"help", no_argument, NULL, 'h'},
10831086
{NULL, 0, NULL, 0}
@@ -1091,6 +1094,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
10911094
char* comma;
10921095
#endif /* HAVE_CPU_AFFINITY */
10931096
char* slash;
1097+
#if defined(HAVE_TCP_KEEPALIVE)
1098+
char* slash2;
1099+
#endif /* HAVE_TCP_KEEPALIVE */
10941100
char *p, *p1;
10951101
struct xbind_entry *xbe;
10961102
double farg;
@@ -1454,6 +1460,32 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
14541460
snd_timeout_flag = 1;
14551461
break;
14561462
#endif /* HAVE_TCP_USER_TIMEOUT */
1463+
#if defined (HAVE_TCP_KEEPALIVE)
1464+
case OPT_CNTL_KA:
1465+
test->settings->cntl_ka = 1;
1466+
if (optarg) {
1467+
slash = strchr(optarg, '/');
1468+
if (slash) {
1469+
*slash = '\0';
1470+
++slash;
1471+
slash2 = strchr(slash, '/');
1472+
if (slash2) {
1473+
*slash2 = '\0';
1474+
++slash2;
1475+
if (strlen(slash2) > 0) {
1476+
test->settings->cntl_ka_count = atoi(slash2);
1477+
}
1478+
}
1479+
if (strlen(slash) > 0) {
1480+
test->settings->cntl_ka_interval = atoi(slash);
1481+
}
1482+
}
1483+
if (strlen(optarg) > 0) {
1484+
test->settings->cntl_ka_keepidle = atoi(optarg);
1485+
}
1486+
}
1487+
break;
1488+
#endif /* HAVE_TCP_KEEPALIVE */
14571489
case 'A':
14581490
#if defined(HAVE_CPU_AFFINITY)
14591491
test->affinity = strtol(optarg, &endptr, 0);
@@ -2805,6 +2837,10 @@ iperf_defaults(struct iperf_test *testp)
28052837
testp->settings->rcv_timeout.secs = DEFAULT_NO_MSG_RCVD_TIMEOUT / SEC_TO_mS;
28062838
testp->settings->rcv_timeout.usecs = (DEFAULT_NO_MSG_RCVD_TIMEOUT % SEC_TO_mS) * mS_TO_US;
28072839
testp->zerocopy = 0;
2840+
testp->settings->cntl_ka = 0;
2841+
testp->settings->cntl_ka_keepidle = 0;
2842+
testp->settings->cntl_ka_interval = 0;
2843+
testp->settings->cntl_ka_count = 0;
28082844

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

@@ -3091,6 +3127,10 @@ iperf_reset_test(struct iperf_test *test)
30913127
test->settings->tos = 0;
30923128
test->settings->dont_fragment = 0;
30933129
test->zerocopy = 0;
3130+
test->settings->cntl_ka = 0;
3131+
test->settings->cntl_ka_keepidle = 0;
3132+
test->settings->cntl_ka_interval = 0;
3133+
test->settings->cntl_ka_count = 0;
30943134

30953135
#if defined(HAVE_SSL)
30963136
if (test->settings->authtoken) {

src/iperf_api.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ typedef uint64_t iperf_size_t;
9090
#define OPT_DONT_FRAGMENT 26
9191
#define OPT_RCV_TIMEOUT 27
9292
#define OPT_SND_TIMEOUT 28
93+
#define OPT_CNTL_KA 29
9394

9495
/* states */
9596
#define TEST_START 1
@@ -390,6 +391,7 @@ enum {
390391
IERCVTIMEOUT = 31, // Illegal message receive timeout
391392
IERVRSONLYRCVTIMEOUT = 32, // Client receive timeout is valid only in reverse mode
392393
IESNDTIMEOUT = 33, // Illegal message send timeout
394+
IECNTLKA = 34, // Control connection Keepalive period should be larger than retry period (interval * count)
393395
/* Test errors */
394396
IENEWTEST = 100, // Unable to create a new test (check perror)
395397
IEINITTEST = 101, // Test initialization failed (check perror)
@@ -440,6 +442,10 @@ enum {
440442
IEBINDDEVNOSUPPORT = 146, // `ip%%dev` is not supported as system does not support bind to device
441443
IEHOSTDEV = 147, // host device name (ip%%<dev>) is supported (and required) only for IPv6 link-local address
442444
IESETUSERTIMEOUT = 148, // Unable to set TCP USER_TIMEOUT (check perror)
445+
IESETCNTLKA = 149, // Unable to set socket keepalive (SO_KEEPALIVE) option
446+
IESETCNTLKAKEEPIDLE = 150, // Unable to set socket keepalive TCP period (TCP_KEEPIDLE) option
447+
IESETCNTLKAINTERVAL = 151, // Unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option
448+
IESETCNTLKACOUNT = 152, // Unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option
443449
/* Stream errors */
444450
IECREATESTREAM = 200, // Unable to create a new stream (check herror/perror)
445451
IEINITSTREAM = 201, // Unable to initialize stream (check herror/perror)

src/iperf_client_api.c

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,9 @@ iperf_connect(struct iperf_test *test)
364364
{
365365
int opt;
366366
socklen_t len;
367+
#if defined(HAVE_TCP_KEEPALIVE)
368+
int kainterval, kacount;
369+
#endif /* HAVE_TCP_KEEPALIVE */
367370

368371
if (NULL == test)
369372
{
@@ -391,11 +394,82 @@ iperf_connect(struct iperf_test *test)
391394
return -1;
392395
}
393396

397+
#if defined (HAVE_TCP_KEEPALIVE)
398+
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
399+
if (test->settings->cntl_ka) {
400+
opt = 1;
401+
if (setsockopt(test->ctrl_sck, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt))) {
402+
i_errno = IESETCNTLKA;
403+
return -1;
404+
}
405+
406+
if ((opt = test->settings->cntl_ka_keepidle)) {
407+
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPIDLE, (char *) &opt, sizeof(opt))) {
408+
i_errno = IESETCNTLKAKEEPIDLE;
409+
return -1;
410+
}
411+
}
412+
413+
// Seems that at least in Windows WSL2, TCP keepalive retries inteval must be smaller
414+
// than keepalive interval. Otherwise, the keepalive message is sent only once.
415+
// To make sure issues will not impact the tests in case the keepalive period was set,
416+
// when possible it is made sure that the full keepalive retries interval is less
417+
// than the the keepalive period.
418+
if (test->settings->cntl_ka_keepidle) {
419+
if ((kainterval = test->settings->cntl_ka_interval) == 0) {
420+
len = sizeof(kainterval);
421+
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &kainterval, &len)) {
422+
i_errno = IESETCNTLKAINTERVAL;
423+
return -1;
424+
}
425+
}
426+
if ((kacount = test->settings->cntl_ka_count) == 0) {
427+
len = sizeof(kacount);
428+
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPCNT, (char *) &kacount, &len)) {
429+
i_errno = IESETCNTLKACOUNT;
430+
return -1;
431+
}
432+
}
433+
434+
if (test->settings->cntl_ka_keepidle <= (kainterval * kacount)) {
435+
if (test->settings->cntl_ka_interval > 0 || test->settings->cntl_ka_count > 0) {
436+
i_errno = IECNTLKA;
437+
return -1;
438+
}
439+
if (kacount > 0) {
440+
test->settings->cntl_ka_interval = ((test->settings->cntl_ka_keepidle - 1) / kacount);
441+
if (test->settings->cntl_ka_interval <= 0) {
442+
test->settings->cntl_ka_interval = 1;
443+
}
444+
}
445+
}
446+
}
447+
448+
if ((opt = test->settings->cntl_ka_interval)) {
449+
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &opt, sizeof(opt))) {
450+
i_errno = IESETCNTLKAINTERVAL;
451+
return -1;
452+
}
453+
}
454+
if ((opt = test->settings->cntl_ka_count)) {
455+
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPCNT, (char *) &opt, sizeof(opt))) {
456+
i_errno = IESETCNTLKACOUNT;
457+
return -1;
458+
}
459+
}
460+
461+
if (test->verbose) {
462+
printf("Control connection TCP Keepalive TCP_KEEPIDLE/TCP_KEEPINTVL/TCP_KEEPCNT set to %d/%d/%d (0 is system default)\n",
463+
test->settings->cntl_ka_keepidle, test->settings->cntl_ka_interval, test->settings->cntl_ka_count);
464+
}
465+
}
466+
#endif //HAVE_TCP_KEEPALIVE
467+
394468
#if defined(HAVE_TCP_USER_TIMEOUT)
395469
if ((opt = test->settings->snd_timeout)) {
396470
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
397-
i_errno = IESETUSERTIMEOUT;
398-
return -1;
471+
i_errno = IESETUSERTIMEOUT;
472+
return -1;
399473
}
400474
}
401475
#endif /* HAVE_TCP_USER_TIMEOUT */

src/iperf_error.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ iperf_strerror(int int_errno)
161161
case IEINTERVAL:
162162
snprintf(errstr, len, "invalid report interval (min = %g, max = %g seconds)", MIN_INTERVAL, MAX_INTERVAL);
163163
break;
164-
case IEBIND: /* UNUSED */
164+
case IEBIND: /* UNUSED */
165165
snprintf(errstr, len, "--bind must be specified to use --cport");
166166
break;
167167
case IEUDPBLOCKSIZE:
@@ -437,7 +437,7 @@ iperf_strerror(int int_errno)
437437
case IETOTALRATE:
438438
snprintf(errstr, len, "total required bandwidth is larger than server limit");
439439
break;
440-
case IESKEWTHRESHOLD:
440+
case IESKEWTHRESHOLD:
441441
snprintf(errstr, len, "skew threshold must be a positive number");
442442
break;
443443
case IEIDLETIMEOUT:
@@ -446,22 +446,42 @@ iperf_strerror(int int_errno)
446446
case IEBINDDEV:
447447
snprintf(errstr, len, "Unable to bind-to-device (check perror, maybe permissions?)");
448448
break;
449-
case IEBINDDEVNOSUPPORT:
449+
case IEBINDDEVNOSUPPORT:
450450
snprintf(errstr, len, "`<ip>%%<dev>` is not supported as system does not support bind to device");
451451
break;
452-
case IEHOSTDEV:
452+
case IEHOSTDEV:
453453
snprintf(errstr, len, "host device name (ip%%<dev>) is supported (and required) only for IPv6 link-local address");
454454
break;
455455
case IENOMSG:
456456
snprintf(errstr, len, "idle timeout for receiving data");
457457
break;
458-
case IESETDONTFRAGMENT:
458+
case IESETDONTFRAGMENT:
459459
snprintf(errstr, len, "unable to set IP Do-Not-Fragment flag");
460460
break;
461461
case IESETUSERTIMEOUT:
462462
snprintf(errstr, len, "unable to set TCP/SCTP MSS");
463463
perr = 1;
464464
break;
465+
case IECNTLKA:
466+
snprintf(errstr, len, "control connection Keepalive period should be larger than retry period (interval * count)");
467+
perr = 1;
468+
break;
469+
case IESETCNTLKA:
470+
snprintf(errstr, len, "unable to set socket keepalive (SO_KEEPALIVE) option");
471+
perr = 1;
472+
break;
473+
case IESETCNTLKAKEEPIDLE:
474+
snprintf(errstr, len, "unable to set socket keepalive TCP period (TCP_KEEPIDLE) option");
475+
perr = 1;
476+
break;
477+
case IESETCNTLKAINTERVAL:
478+
snprintf(errstr, len, "unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option");
479+
perr = 1;
480+
break;
481+
case IESETCNTLKACOUNT:
482+
snprintf(errstr, len, "unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option");
483+
perr = 1;
484+
break;
465485
default:
466486
snprintf(errstr, len, "int_errno=%d", int_errno);
467487
perr = 1;

src/iperf_locale.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
124124
" --snd-timeout # timeout for unacknowledged TCP data\n"
125125
" (in ms, default is system settings)\n"
126126
#endif /* HAVE_TCP_USER_TIMEOUT */
127+
#if defined(HAVE_TCP_KEEPALIVE)
128+
" --cntl-ka[=#/#/#] use control connection TCP keepalive - KEEPIDLE/KEEPINTV/KEEPCNT\n"
129+
" each value is optional with system settings default\n"
130+
#endif //HAVE_TCP_KEEPALIVE
127131
" -d, --debug[=#] emit debugging output\n"
128132
" (optional optional \"=\" and debug level: 1-4. Default is 4 - all messages)\n"
129133
" -v, --version show version information and quit\n"

0 commit comments

Comments
 (0)