Skip to content

Commit d1409d0

Browse files
authored
Merge pull request #1423 from davidBar-On/issue-812-835-control-connection-keepalive
Add control connection keepalive arguments
2 parents 3c725cc + e161c6d commit d1409d0

8 files changed

+194
-8
lines changed

configure.ac

+12
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,18 @@ if test "x$iperf3_cv_header_tcp_user_timeout" = "xyes"; then
204204
AC_DEFINE([HAVE_TCP_USER_TIMEOUT], [1], [Have TCP_USER_TIMEOUT sockopt.])
205205
fi
206206

207+
# Check for TCP_KEEPIDLE sockopt (not clear where supported)
208+
AC_CACHE_CHECK([TCP_KEEPIDLE socket option],
209+
[iperf3_cv_header_tcp_keepalive],
210+
AC_COMPILE_IFELSE(
211+
[AC_LANG_PROGRAM([[#include <netinet/tcp.h>]],
212+
[[int foo = TCP_KEEPIDLE;]])],
213+
iperf3_cv_header_tcp_keepalive=yes,
214+
iperf3_cv_header_tcp_keepalive=no))
215+
if test "x$iperf3_cv_header_tcp_keepalive" = "xyes"; then
216+
AC_DEFINE([HAVE_TCP_KEEPALIVE], [1], [Have TCP_KEEPIDLE sockopt.])
217+
fi
218+
207219
# Check for IPv6 flowlabel support (believed to be Linux only)
208220
# We check for IPV6_FLOWLABEL_MGR in <linux/in6.h> even though we
209221
# don't use that file directly (we have our own stripped-down

src/iperf.h

+4
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ struct iperf_settings
184184
int idle_timeout; /* server idle time timeout */
185185
unsigned int snd_timeout; /* Timeout for sending tcp messages in active mode, in us */
186186
struct iperf_time rcv_timeout; /* Timeout for receiving messages in active mode, in us */
187+
int cntl_ka; /* Use Control TCP connection Keepalive */
188+
int cntl_ka_keepidle; /* Control TCP connection Keepalive idle time (TCP_KEEPIDLE) */
189+
int cntl_ka_interval; /* Control TCP connection Keepalive interval between retries (TCP_KEEPINTV) */
190+
int cntl_ka_count; /* Control TCP connection Keepalive number of retries (TCP_KEEPCNT) */
187191
};
188192

189193
struct iperf_test;

src/iperf_api.c

+122
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
11551155
{"idle-timeout", required_argument, NULL, OPT_IDLE_TIMEOUT},
11561156
{"rcv-timeout", required_argument, NULL, OPT_RCV_TIMEOUT},
11571157
{"snd-timeout", required_argument, NULL, OPT_SND_TIMEOUT},
1158+
#if defined(HAVE_TCP_KEEPALIVE)
1159+
{"cntl-ka", optional_argument, NULL, OPT_CNTL_KA},
1160+
#endif /* HAVE_TCP_KEEPALIVE */
11581161
#if defined(HAVE_IPPROTO_MPTCP)
11591162
{"mptcp", no_argument, NULL, 'm'},
11601163
#endif
@@ -1171,6 +1174,9 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
11711174
char* comma;
11721175
#endif /* HAVE_CPU_AFFINITY */
11731176
char* slash;
1177+
#if defined(HAVE_TCP_KEEPALIVE)
1178+
char* slash2;
1179+
#endif /* HAVE_TCP_KEEPALIVE */
11741180
char *p, *p1;
11751181
struct xbind_entry *xbe;
11761182
double farg;
@@ -1539,6 +1545,39 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
15391545
snd_timeout_flag = 1;
15401546
break;
15411547
#endif /* HAVE_TCP_USER_TIMEOUT */
1548+
#if defined (HAVE_TCP_KEEPALIVE)
1549+
case OPT_CNTL_KA:
1550+
test->settings->cntl_ka = 1;
1551+
if (optarg) {
1552+
slash = strchr(optarg, '/');
1553+
if (slash) {
1554+
*slash = '\0';
1555+
++slash;
1556+
slash2 = strchr(slash, '/');
1557+
if (slash2) {
1558+
*slash2 = '\0';
1559+
++slash2;
1560+
if (strlen(slash2) > 0) {
1561+
test->settings->cntl_ka_count = atoi(slash2);
1562+
}
1563+
}
1564+
if (strlen(slash) > 0) {
1565+
test->settings->cntl_ka_interval = atoi(slash);
1566+
}
1567+
}
1568+
if (strlen(optarg) > 0) {
1569+
test->settings->cntl_ka_keepidle = atoi(optarg);
1570+
}
1571+
}
1572+
// Seems that at least in Windows WSL2, TCP keepalive retries full inteval must be
1573+
// smaller than the idle interval. Otherwise, the keepalive message is sent only once.
1574+
if (test->settings->cntl_ka_keepidle &&
1575+
test->settings->cntl_ka_keepidle <= (test->settings->cntl_ka_count * test->settings->cntl_ka_interval)) {
1576+
i_errno = IECNTLKA;
1577+
return -1;
1578+
}
1579+
break;
1580+
#endif /* HAVE_TCP_KEEPALIVE */
15421581
case 'A':
15431582
#if defined(HAVE_CPU_AFFINITY)
15441583
test->affinity = strtol(optarg, &endptr, 0);
@@ -3069,6 +3108,10 @@ iperf_defaults(struct iperf_test *testp)
30693108
testp->settings->rcv_timeout.secs = DEFAULT_NO_MSG_RCVD_TIMEOUT / SEC_TO_mS;
30703109
testp->settings->rcv_timeout.usecs = (DEFAULT_NO_MSG_RCVD_TIMEOUT % SEC_TO_mS) * mS_TO_US;
30713110
testp->zerocopy = 0;
3111+
testp->settings->cntl_ka = 0;
3112+
testp->settings->cntl_ka_keepidle = 0;
3113+
testp->settings->cntl_ka_interval = 0;
3114+
testp->settings->cntl_ka_count = 0;
30723115

30733116
testp->json_callback = NULL;
30743117

@@ -5296,3 +5339,82 @@ iflush(struct iperf_test *test)
52965339

52975340
return rc2;
52985341
}
5342+
5343+
#if defined (HAVE_TCP_KEEPALIVE)
5344+
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
5345+
int
5346+
iperf_set_control_keepalive(struct iperf_test *test)
5347+
{
5348+
int opt, kaidle, kainterval, kacount;
5349+
socklen_t len;
5350+
5351+
if (test->settings->cntl_ka) {
5352+
// Set keepalive using system defaults
5353+
opt = 1;
5354+
if (setsockopt(test->ctrl_sck, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, sizeof(opt))) {
5355+
i_errno = IESETCNTLKA;
5356+
return -1;
5357+
}
5358+
5359+
// Get default values when not specified
5360+
if ((kaidle = test->settings->cntl_ka_keepidle) == 0) {
5361+
len = sizeof(kaidle);
5362+
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPIDLE, (char *) &kaidle, &len)) {
5363+
i_errno = IESETCNTLKAINTERVAL;
5364+
return -1;
5365+
}
5366+
}
5367+
if ((kainterval = test->settings->cntl_ka_interval) == 0) {
5368+
len = sizeof(kainterval);
5369+
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &kainterval, &len)) {
5370+
i_errno = IESETCNTLKAINTERVAL;
5371+
return -1;
5372+
}
5373+
}
5374+
if ((kacount = test->settings->cntl_ka_count) == 0) {
5375+
len = sizeof(kacount);
5376+
if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPCNT, (char *) &kacount, &len)) {
5377+
i_errno = IESETCNTLKACOUNT;
5378+
return -1;
5379+
}
5380+
}
5381+
5382+
// Seems that at least in Windows WSL2, TCP keepalive retries full inteval must be
5383+
// smaller than the idle interval. Otherwise, the keepalive message is sent only once.
5384+
if (test->settings->cntl_ka_keepidle) {
5385+
if (test->settings->cntl_ka_keepidle <= (kainterval * kacount)) {
5386+
iperf_err(test, "Keepalive Idle time (%d) should be greater than Retries-interval (%d) times Retries-count (%d)", kaidle, kainterval, kacount);
5387+
i_errno = IECNTLKA;
5388+
return -1;
5389+
}
5390+
}
5391+
5392+
// Set keep alive values when specified
5393+
if ((opt = test->settings->cntl_ka_keepidle)) {
5394+
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPIDLE, (char *) &opt, sizeof(opt))) {
5395+
i_errno = IESETCNTLKAKEEPIDLE;
5396+
return -1;
5397+
}
5398+
}
5399+
if ((opt = test->settings->cntl_ka_interval)) {
5400+
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPINTVL, (char *) &opt, sizeof(opt))) {
5401+
i_errno = IESETCNTLKAINTERVAL;
5402+
return -1;
5403+
}
5404+
}
5405+
if ((opt = test->settings->cntl_ka_count)) {
5406+
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_KEEPCNT, (char *) &opt, sizeof(opt))) {
5407+
i_errno = IESETCNTLKACOUNT;
5408+
return -1;
5409+
}
5410+
}
5411+
5412+
if (test->verbose) {
5413+
printf("Control connection TCP Keepalive TCP_KEEPIDLE/TCP_KEEPINTVL/TCP_KEEPCNT are set to %d/%d/%d\n",
5414+
kaidle, kainterval, kacount);
5415+
}
5416+
}
5417+
5418+
return 0;
5419+
}
5420+
#endif //HAVE_TCP_KEEPALIVE

src/iperf_api.h

+15-1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t;
101101
#define OPT_JSON_STREAM 28
102102
#define OPT_SND_TIMEOUT 29
103103
#define OPT_USE_PKCS1_PADDING 30
104+
#define OPT_CNTL_KA 31
104105

105106
/* states */
106107
#define TEST_START 1
@@ -310,6 +311,14 @@ void iperf_free_stream(struct iperf_stream * sp);
310311
*/
311312
int iperf_common_sockopts(struct iperf_test *, int s);
312313

314+
#if defined (HAVE_TCP_KEEPALIVE)
315+
/**
316+
* iperf_set_control_keepalive -- set control connection TCP keepalive
317+
*
318+
*/
319+
int iperf_set_control_keepalive(struct iperf_test *test);
320+
#endif //HAVE_TCP_KEEPALIVE
321+
313322
int has_tcpinfo(void);
314323
int has_tcpinfo_retransmits(void);
315324
void save_tcpinfo(struct iperf_stream *sp, struct iperf_interval_results *irp);
@@ -423,6 +432,7 @@ enum {
423432
IESNDTIMEOUT = 33, // Illegal message send timeout
424433
IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP
425434
IESERVERAUTHUSERS = 35, // Cannot access authorized users file
435+
IECNTLKA = 36, // Control connection Keepalive period should be larger than the full retry period (interval * count)
426436
/* Test errors */
427437
IENEWTEST = 100, // Unable to create a new test (check perror)
428438
IEINITTEST = 101, // Test initialization failed (check perror)
@@ -478,7 +488,11 @@ enum {
478488
IEPTHREADJOIN=152, // Unable to join thread (check perror)
479489
IEPTHREADATTRINIT=153, // Unable to initialize thread attribute (check perror)
480490
IEPTHREADATTRDESTROY=154, // Unable to destroy thread attribute (check perror)
481-
IEPTHREADSIGMASK=155, // Unable to initialize sub thread signal mask (check perror)
491+
IESETCNTLKA = 155, // Unable to set socket keepalive (SO_KEEPALIVE) option
492+
IESETCNTLKAKEEPIDLE = 156, // Unable to set socket keepalive TCP period (TCP_KEEPIDLE) option
493+
IESETCNTLKAINTERVAL = 157, // Unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option
494+
IESETCNTLKACOUNT = 158, // Unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option
495+
IEPTHREADSIGMASK=159, // Unable to initialize sub thread signal mask (check perror)
482496
/* Stream errors */
483497
IECREATESTREAM = 200, // Unable to create a new stream (check herror/perror)
484498
IEINITSTREAM = 201, // Unable to initialize stream (check herror/perror)

src/iperf_client_api.c

+8-2
Original file line numberDiff line numberDiff line change
@@ -444,11 +444,17 @@ iperf_connect(struct iperf_test *test)
444444
return -1;
445445
}
446446

447+
#if defined (HAVE_TCP_KEEPALIVE)
448+
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
449+
if (iperf_set_control_keepalive(test) < 0)
450+
return -1;
451+
#endif //HAVE_TCP_KEEPALIVE
452+
447453
#if defined(HAVE_TCP_USER_TIMEOUT)
448454
if ((opt = test->settings->snd_timeout)) {
449455
if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
450-
i_errno = IESETUSERTIMEOUT;
451-
return -1;
456+
i_errno = IESETUSERTIMEOUT;
457+
return -1;
452458
}
453459
}
454460
#endif /* HAVE_TCP_USER_TIMEOUT */

src/iperf_error.c

+23-5
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ iperf_strerror(int int_errno)
200200
case IEINTERVAL:
201201
snprintf(errstr, len, "invalid report interval (min = %g, max = %g seconds)", MIN_INTERVAL, MAX_INTERVAL);
202202
break;
203-
case IEBIND: /* UNUSED */
203+
case IEBIND: /* UNUSED */
204204
snprintf(errstr, len, "--bind must be specified to use --cport");
205205
break;
206206
case IEUDPBLOCKSIZE:
@@ -482,7 +482,7 @@ iperf_strerror(int int_errno)
482482
case IETOTALRATE:
483483
snprintf(errstr, len, "total required bandwidth is larger than server limit");
484484
break;
485-
case IESKEWTHRESHOLD:
485+
case IESKEWTHRESHOLD:
486486
snprintf(errstr, len, "skew threshold must be a positive number");
487487
break;
488488
case IEIDLETIMEOUT:
@@ -491,16 +491,16 @@ iperf_strerror(int int_errno)
491491
case IEBINDDEV:
492492
snprintf(errstr, len, "Unable to bind-to-device (check perror, maybe permissions?)");
493493
break;
494-
case IEBINDDEVNOSUPPORT:
494+
case IEBINDDEVNOSUPPORT:
495495
snprintf(errstr, len, "`<ip>%%<dev>` is not supported as system does not support bind to device");
496496
break;
497-
case IEHOSTDEV:
497+
case IEHOSTDEV:
498498
snprintf(errstr, len, "host device name (ip%%<dev>) is supported (and required) only for IPv6 link-local address");
499499
break;
500500
case IENOMSG:
501501
snprintf(errstr, len, "idle timeout for receiving data");
502502
break;
503-
case IESETDONTFRAGMENT:
503+
case IESETDONTFRAGMENT:
504504
snprintf(errstr, len, "unable to set IP Do-Not-Fragment flag");
505505
break;
506506
case IESETUSERTIMEOUT:
@@ -528,6 +528,24 @@ iperf_strerror(int int_errno)
528528
break;
529529
case IEPTHREADATTRDESTROY:
530530
snprintf(errstr, len, "unable to destroy thread attributes");
531+
case IECNTLKA:
532+
snprintf(errstr, len, "control connection Keepalive period should be larger than the full retry period (interval * count)");
533+
perr = 1;
534+
break;
535+
case IESETCNTLKA:
536+
snprintf(errstr, len, "unable to set socket keepalive (SO_KEEPALIVE) option");
537+
perr = 1;
538+
break;
539+
case IESETCNTLKAKEEPIDLE:
540+
snprintf(errstr, len, "unable to set socket keepalive TCP period (TCP_KEEPIDLE) option");
541+
perr = 1;
542+
break;
543+
case IESETCNTLKAINTERVAL:
544+
snprintf(errstr, len, "unable to set/get socket keepalive TCP retry interval (TCP_KEEPINTVL) option");
545+
perr = 1;
546+
break;
547+
case IESETCNTLKACOUNT:
548+
snprintf(errstr, len, "unable to set/get socket keepalive TCP number of retries (TCP_KEEPCNT) option");
531549
perr = 1;
532550
break;
533551
default:

src/iperf_locale.c

+4
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n"
128128
" --snd-timeout # timeout for unacknowledged TCP data\n"
129129
" (in ms, default is system settings)\n"
130130
#endif /* HAVE_TCP_USER_TIMEOUT */
131+
#if defined(HAVE_TCP_KEEPALIVE)
132+
" --cntl-ka[=#/#/#] use control connection TCP keepalive - KEEPIDLE/KEEPINTV/KEEPCNT\n"
133+
" each value is optional with system settings default\n"
134+
#endif //HAVE_TCP_KEEPALIVE
131135
#if defined(HAVE_IPPROTO_MPTCP)
132136
" -m, --mptcp use MPTCP rather than plain TCP\n"
133137
#endif

src/iperf_server_api.c

+6
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ iperf_accept(struct iperf_test *test)
184184
}
185185
#endif /* HAVE_TCP_USER_TIMEOUT */
186186

187+
#if defined (HAVE_TCP_KEEPALIVE)
188+
// Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
189+
if (iperf_set_control_keepalive(test) < 0)
190+
return -1;
191+
#endif //HAVE_TCP_KEEPALIVE
192+
187193
if (Nread(test->ctrl_sck, test->cookie, COOKIE_SIZE, Ptcp) != COOKIE_SIZE) {
188194
/*
189195
* Note this error covers both the case of a system error

0 commit comments

Comments
 (0)