diff --git a/configure.ac b/configure.ac index 073d07dcc..cc36d7bdf 100644 --- a/configure.ac +++ b/configure.ac @@ -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 ]], + [[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 even though we # don't use that file directly (we have our own stripped-down diff --git a/src/iperf.h b/src/iperf.h index 7d14a3453..2d2023cba 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -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; diff --git a/src/iperf_api.c b/src/iperf_api.c index 34f08bc81..4c0ea1b07 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -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} @@ -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; @@ -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); @@ -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); @@ -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 diff --git a/src/iperf_api.h b/src/iperf_api.h index 2b71613e9..15d7df906 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -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 @@ -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); @@ -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) @@ -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) diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index 67b05cc69..6911033b8 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -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 */ diff --git a/src/iperf_error.c b/src/iperf_error.c index 3388d376e..e2a5fc140 100644 --- a/src/iperf_error.c +++ b/src/iperf_error.c @@ -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: @@ -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: @@ -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, "`%%` is not supported as system does not support bind to device"); break; - case IEHOSTDEV: + case IEHOSTDEV: snprintf(errstr, len, "host device name (ip%%) 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: @@ -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: diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 32883da84..c77c487e9 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -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" diff --git a/src/iperf_server_api.c b/src/iperf_server_api.c index 9727cdddb..873c50c16 100644 --- a/src/iperf_server_api.c +++ b/src/iperf_server_api.c @@ -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