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

Support parallel UDP streams under Windows and Cygwin #1163

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
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
Next Next commit
Rebase with master of 3.17.1+
davidBar-On committed Sep 29, 2024
commit 962c4c18a66b37228d00f11e62da5a154b6e2130
8 changes: 8 additions & 0 deletions src/iperf.h
Original file line number Diff line number Diff line change
@@ -302,6 +302,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) */
char *diskfile_name; /* -F option */
@@ -466,10 +468,16 @@ extern int gerror; /* error value from getaddrinfo(3), for use in internal error
#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

/* In Reverse mode, maximum number of packets to wait for "accept" response - to handle out of order packets */
#define MAX_REVERSE_OUT_OF_ORDER_PACKETS 2

/* 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 */
50 changes: 41 additions & 9 deletions src/iperf_api.c
Original file line number Diff line number Diff line change
@@ -61,9 +61,9 @@
#include <sys/cpuset.h>
#endif /* HAVE_CPUSET_SETAFFINITY */

#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 <Windows.h>
@@ -1177,12 +1177,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:hX:", 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) {
@@ -1339,7 +1357,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;
}
@@ -2197,6 +2215,17 @@ iperf_exchange_parameters(struct iperf_test *test)
if (get_parameters(test) < 0)
return -1;

// Check spcific conditions required for UDP under Windows as parallel streams
// using the same port numebr 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){
if (iperf_set_send_state(test, SERVER_ERROR) != 0)
@@ -2212,7 +2241,7 @@ iperf_exchange_parameters(struct iperf_test *test)
#endif //HAVE_SSL

if ((s = test->protocol->listen(test)) < 0) {
if (iperf_set_send_state(test, SERVER_ERROR) != 0)
if (iperf_set_send_state(test, SERVER_ERROR) != 0)
return -1;
err = htonl(i_errno);
if (Nwrite(test->ctrl_sck, (char*) &err, sizeof(err), Ptcp) < 0) {
@@ -3025,6 +3054,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;
@@ -3307,6 +3338,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;
2 changes: 2 additions & 0 deletions src/iperf_api.h
Original file line number Diff line number Diff line change
@@ -420,6 +420,8 @@ enum {
IESNDTIMEOUT = 33, // Illegal message send timeout
IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP
IESERVERAUTHUSERS = 35, // Cannot access authorized users file
IENUMPORTS = 36, // number of ports is less than 1 or larger than server limit
IEPORTNUM = 37, // 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)
9 changes: 7 additions & 2 deletions src/iperf_error.c
Original file line number Diff line number Diff line change
@@ -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:
@@ -482,12 +482,17 @@ iperf_strerror(int int_errno)
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:
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");
6 changes: 6 additions & 0 deletions src/iperf_locale.c
Original file line number Diff line number Diff line change
@@ -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(WINDOWS_ANY)
" -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"
8 changes: 8 additions & 0 deletions src/iperf_server_api.c
Original file line number Diff line number Diff line change
@@ -442,6 +442,7 @@ static void
cleanup_server(struct iperf_test *test)
{
struct iperf_stream *sp;
int i;

/* Cancel outstanding threads */
int i_errno_save = i_errno;
@@ -494,6 +495,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);
49 changes: 43 additions & 6 deletions src/iperf_udp.c
Original file line number Diff line number Diff line change
@@ -373,6 +373,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
@@ -443,11 +444,30 @@ iperf_udp_accept(struct iperf_test *test)
}
}

port = test->server_port;
buf = UDP_CONNECT_REPLY;

/*
* Since Windows does not suppport 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;
@@ -456,8 +476,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;
@@ -594,19 +616,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 suppport 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;
}