diff --git a/src/iperf.h b/src/iperf.h index a6052737a..82a83ce1f 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -318,6 +318,8 @@ struct iperf_test TAILQ_HEAD(xbind_addrhead, xbind_entry) xbind_addrs; /* all -X opts */ int bind_port; /* --cport option */ int server_port; + int num_server_ports; /* second value of --port option */ + int server_udp_streams_accepted; /* offset of last server port used - 0 means none used */ int omit; /* duration of omit period (-O flag) */ int duration; /* total duration of test (-t flag) */ int max_server_duration; /* maximum possible duration of test as enforced by the server (--max-server-duration flag) */ @@ -484,10 +486,12 @@ extern int gerror; /* error value from getaddrinfo(3), for use in internal error #if BYTE_ORDER == BIG_ENDIAN #define UDP_CONNECT_MSG 0x39383736 #define UDP_CONNECT_REPLY 0x36373839 +#define UDP_CONNECT_REPLY_NEXT_PORT 0x35373839 #define LEGACY_UDP_CONNECT_REPLY 0xb168de3a #else #define UDP_CONNECT_MSG 0x36373839 // "6789" - legacy value was 123456789 #define UDP_CONNECT_REPLY 0x39383736 // "9876" - legacy value was 987654321 +#define UDP_CONNECT_REPLY_NEXT_PORT 0x39383735 // "9875": for Windows - indicates use next port #define LEGACY_UDP_CONNECT_REPLY 987654321 // Old servers may still reply with the legacy value #endif @@ -497,4 +501,9 @@ extern int gerror; /* error value from getaddrinfo(3), for use in internal error #define GSO_BF_MAX_SIZE MAX_UDP_BLOCKSIZE #define GRO_BF_MAX_SIZE MAX_UDP_BLOCKSIZE +/* Any type of WIndows OS or Cygwin */ +#if (defined(__CYGWIN__) || defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)) +#define WINDOWS_ANY 1 +#endif /* Any Windows type */ + #endif /* !__IPERF_H */ diff --git a/src/iperf_api.c b/src/iperf_api.c index fb4273f1e..cedb1f515 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -64,9 +64,9 @@ #include #endif -#if defined(__CYGWIN__) || defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__) +#if defined(WINDOWS_ANY) #define CPU_SETSIZE __CPU_SETSIZE -#endif /* __CYGWIN__, _WIN32, _WIN64, __WINDOWS__ */ +#endif /* WINDOWS_ANY */ #if defined(HAVE_SETPROCESSAFFINITYMASK) #include @@ -1226,12 +1226,30 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) while ((flag = getopt_long(argc, argv, "p:f:i:D1VJvsc:ub:t:n:k:l:P:Rw:B:M:N46S:L:ZO:F:A:T:C:dI:mhX:", longopts, NULL)) != -1) { switch (flag) { case 'p': - portno = atoi(optarg); - if (portno < 1 || portno > 65535) { - i_errno = IEBADPORT; - return -1; + slash = optarg; +#if defined(WINDOWS_ANY) + slash = strchr(optarg, '/'); + if (slash) { + *slash = '\0'; + ++slash; + if (*slash != '\0') { + test->num_server_ports = atoi(slash); + if (test->num_server_ports < 1 || test->num_server_ports > MAX_STREAMS) { + i_errno = IENUMPORTS; + return -1; + } + server_flag = 1; + } } - test->server_port = portno; +#endif /* WINDOWS_ANY */ + if (!slash || strlen(optarg) > 0) { + portno = atoi(optarg); + if (portno < 1 || portno > 65535) { + i_errno = IEBADPORT; + return -1; + } + test->server_port = portno; + } break; case 'f': if (!optarg) { @@ -1413,7 +1431,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) break; case 'P': test->num_streams = atoi(optarg); - if (test->num_streams > MAX_STREAMS) { + if (test->num_streams > MAX_STREAMS || test->num_streams < 1) { i_errno = IENUMSTREAMS; return -1; } @@ -2364,6 +2382,17 @@ iperf_exchange_parameters(struct iperf_test *test) return -1; } + // Check specific conditions required for UDP under Windows as parallel streams + // using the same port number is not supported. +#if defined(WINDOWS_ANY) + if (test->protocol->id == Pudp) { + if (test->num_server_ports < test->num_streams * (test->bidirectional ? 2 : 1)) { + i_errno = IEPORTNUM; + return -1; + } + } +#endif /* WINDOWS_ANY */ + #if defined(HAVE_SSL) if (test_is_authorized(test) < 0){ return -1; @@ -3271,6 +3300,8 @@ iperf_defaults(struct iperf_test *testp) testp->congestion_used = NULL; testp->remote_congestion_used = NULL; testp->server_port = PORT; + testp->num_server_ports = 1; + testp->server_udp_streams_accepted = 0; testp->ctrl_sck = -1; testp->listener = -1; testp->prot_listener = -1; @@ -3570,6 +3601,7 @@ iperf_reset_test(struct iperf_test *test) test->mode = RECEIVER; test->sender_has_retransmits = 0; set_protocol(test, Ptcp); + test->server_udp_streams_accepted = 0; test->omit = OMIT; test->duration = DURATION; test->server_affinity = -1; diff --git a/src/iperf_api.h b/src/iperf_api.h index 25ac951e0..f1c204abd 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -445,6 +445,8 @@ enum { IECNTLKA = 36, // Control connection Keepalive period should be larger than the full retry period (interval * count) IEMAXSERVERTESTDURATIONEXCEEDED = 37, // Client's duration exceeds server's maximum duration IEUNITVAL = 38, // Invalid unit value or suffix + IENUMPORTS = 39, // number of ports is less than 1 or larger than server limit + IEPORTNUM = 40, // requested number of parallel streams is larger than the number of ports available for the server /* Test errors */ IENEWTEST = 100, // Unable to create a new test (check perror) IEINITTEST = 101, // Test initialization failed (check perror) diff --git a/src/iperf_error.c b/src/iperf_error.c index 40ca492ea..d7b6b73d1 100644 --- a/src/iperf_error.c +++ b/src/iperf_error.c @@ -513,6 +513,11 @@ iperf_strerror(int int_errno) case IESETUSERTIMEOUT: snprintf(errstr, len, "unable to set TCP USER_TIMEOUT"); perr = 1; + case IENUMPORTS: + snprintf(errstr, len, "number of ports is less than 1 or larger than server limit"); + break; + case IEPORTNUM: + snprintf(errstr, len, "requested number of parallel streams is larger than the number of ports available for the server"); break; case IEPTHREADCREATE: snprintf(errstr, len, "unable to create thread"); diff --git a/src/iperf_locale.c b/src/iperf_locale.c index eb07c9651..05d992bcc 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -99,7 +99,13 @@ const char usage_shortstr[] = "Usage: iperf3 [-s|-c host] [options]\n" const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" " iperf3 [-h|--help] [-v|--version]\n\n" "Server or Client:\n" +#if (defined(__CYGWIN__) || defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)) + " -p, --port #[/#] server port to listen on / connect to\n" + " (optional for server UDP: pool size of ports starting with\n" + " port # - required for parallel UDP sterams under Windows)\n" +#else " -p, --port # server port to listen on/connect to\n" +#endif /* WINDOWS_ANY */ " -f, --format [kmgtKMGT] format to report: Kbits, Mbits, Gbits, Tbits\n" " -i, --interval # seconds between periodic throughput reports\n" " -I, --pidfile file write PID file\n" diff --git a/src/iperf_server_api.c b/src/iperf_server_api.c index 66401fa6b..84c89668b 100644 --- a/src/iperf_server_api.c +++ b/src/iperf_server_api.c @@ -461,6 +461,7 @@ cleanup_server(struct iperf_test *test) { struct iperf_stream *sp; int32_t err; + int i; /* Try to send the error code to the client*/ if (i_errno != IENONE && test->ctrl_sck != -1) { @@ -528,6 +529,13 @@ cleanup_server(struct iperf_test *test) test->prot_listener = -1; } + /* Close all listening ports in case pool of listening ports is used */ + for (i = 0; i <= test->server_udp_streams_accepted; i++) { + if (test->debug) + printf("Closing UDP port %d;\n", test->server_port + i); + close(test->server_port + i); + } + /* Cancel any remaining timers. */ if (test->stats_timer != NULL) { tmr_cancel(test->stats_timer); diff --git a/src/iperf_udp.c b/src/iperf_udp.c index 393836c43..bf345112d 100644 --- a/src/iperf_udp.c +++ b/src/iperf_udp.c @@ -527,6 +527,7 @@ iperf_udp_accept(struct iperf_test *test) socklen_t len; int sz, s; int rc; + int port; /* * Get the current outstanding socket. This socket will be used to handle @@ -602,11 +603,30 @@ iperf_udp_accept(struct iperf_test *test) } } + port = test->server_port; + buf = UDP_CONNECT_REPLY; + + /* + * Since Windows does not support parallel UDP streams using the same local and remote ports, + * different server port is used for each steam - indicated by replying with UDP_CONNECT_REPLY_NEXT_PORT. + * Each stream, the listening port number is increased by 1. + */ +#if (defined(WINDOWS_ANY)) + if (test->num_server_ports > 1) { + test->server_udp_streams_accepted++; + + /* Change port number for next stream (but not for the last for backward compatibility) */ + if (test->server_udp_streams_accepted < test->num_streams * ((test->bidirectional ? 2 : 1))) { + port += test->server_udp_streams_accepted; + buf = UDP_CONNECT_REPLY_NEXT_PORT; + } + } +#endif /* WINDOWS_ANY */ + /* * Create a new "listening" socket to replace the one we were using before. */ - FD_CLR(test->prot_listener, &test->read_set); // No control messages from old listener - test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port); + test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, port); if (test->prot_listener < 0) { i_errno = IESTREAMLISTEN; return -1; @@ -615,8 +635,10 @@ iperf_udp_accept(struct iperf_test *test) FD_SET(test->prot_listener, &test->read_set); test->max_fd = (test->max_fd < test->prot_listener) ? test->prot_listener : test->max_fd; - /* Let the client know we're ready "accept" another UDP "stream" */ - buf = UDP_CONNECT_REPLY; + /* + * Let the client know we're ready "accept" another UDP "stream", + * and send the listening port when applicable. + */ if (write(s, &buf, sizeof(buf)) < 0) { i_errno = IESTREAMWRITE; return -1; @@ -759,19 +781,34 @@ iperf_udp_connect(struct iperf_test *test) do { if ((sz = recv(s, &buf, sizeof(buf), 0)) < 0) { i_errno = IESTREAMREAD; + if (test->num_server_ports > 1 && test->num_streams > 1) { + iperf_err(test, "accept response receive failed - may be caused by an old Windows client that does not support parallel UDP streams"); + } return -1; } if (test->debug) { printf("Connect received for Socket %d, sz=%d, buf=%x, i=%d, max_len_wait_for_reply=%d\n", s, sz, buf, i, max_len_wait_for_reply); } i += sz; - } while (buf != UDP_CONNECT_REPLY && buf != LEGACY_UDP_CONNECT_REPLY && i < max_len_wait_for_reply); + } while (buf != UDP_CONNECT_REPLY && buf != UDP_CONNECT_REPLY_NEXT_PORT && buf != LEGACY_UDP_CONNECT_REPLY && i < max_len_wait_for_reply); - if (buf != UDP_CONNECT_REPLY && buf != LEGACY_UDP_CONNECT_REPLY) { + /* + * Since Windows does not support parallel UDP streams using the same local and remote ports, + * different server port is used for each steam - indicated by UDP_CONNECT_REPLY_NEXT_PORT. + */ + if (buf != UDP_CONNECT_REPLY && buf != UDP_CONNECT_REPLY_NEXT_PORT && buf != LEGACY_UDP_CONNECT_REPLY) { i_errno = IESTREAMREAD; return -1; } + /* + * On WIndows, to overcome the limit of not supporting parallel UDP streams using the same port, + * `buf` will be UDP_CONNECT_REPLY_NEXT_PORT - indicating different server port for the next connection. + */ + if (buf == UDP_CONNECT_REPLY_NEXT_PORT) { + test->server_port += 1; + } + return s; }