From e8da23159bb22f3081c5c6dd2d2bcdda4169e8ff Mon Sep 17 00:00:00 2001
From: DavidBar-On <david.cdb004@gmail.com>
Date: Wed, 23 Oct 2024 09:54:39 +0300
Subject: [PATCH] Support for UDP Data-port that is different than the
 Control-port

---
 src/iperf.h        |  1 +
 src/iperf_api.c    | 55 ++++++++++++++++++++++++++++++++++++++--------
 src/iperf_api.h    |  1 +
 src/iperf_error.c  |  5 ++++-
 src/iperf_locale.c |  5 +++--
 src/iperf_udp.c    |  6 ++---
 6 files changed, 58 insertions(+), 15 deletions(-)

diff --git a/src/iperf.h b/src/iperf.h
index 7d14a3453..42e62aa03 100644
--- a/src/iperf.h
+++ b/src/iperf.h
@@ -302,6 +302,7 @@ struct iperf_test
     TAILQ_HEAD(xbind_addrhead, xbind_entry) xbind_addrs; /* all -X opts */
     int       bind_port;                        /* --cport option */
     int       server_port;
+    int       data_port;                        /* from --port option */
     int       omit;                             /* duration of omit period (-O flag) */
     int       duration;                         /* total duration of test (-t flag) */
     char     *diskfile_name;			/* -F option */
diff --git a/src/iperf_api.c b/src/iperf_api.c
index 262adbc2e..4ce84ec79 100644
--- a/src/iperf_api.c
+++ b/src/iperf_api.c
@@ -295,6 +295,12 @@ iperf_get_test_server_port(struct iperf_test *ipt)
     return ipt->server_port;
 }
 
+int
+iperf_get_test_data_port(struct iperf_test *ipt)
+{
+    return ipt->data_port;
+}
+
 char*
 iperf_get_test_server_hostname(struct iperf_test *ipt)
 {
@@ -566,6 +572,12 @@ iperf_set_test_server_port(struct iperf_test *ipt, int srv_port)
     ipt->server_port = srv_port;
 }
 
+void
+iperf_set_test_data_port(struct iperf_test *ipt, int srv_data_port)
+{
+    ipt->data_port = srv_data_port;
+}
+
 void
 iperf_set_test_socket_bufsize(struct iperf_test *ipt, int socket_bufsize)
 {
@@ -956,10 +968,10 @@ iperf_on_connect(struct iperf_test *test)
 	iperf_printf(test, report_time, now_str);
 
     if (test->role == 'c') {
-	if (test->json_output)
-	    cJSON_AddItemToObject(test->json_start, "connecting_to", iperf_json_printf("host: %s  port: %d", test->server_hostname, (int64_t) test->server_port));
-	else {
-	    iperf_printf(test, report_connecting, test->server_hostname, test->server_port);
+	if (test->json_output) {
+	    cJSON_AddItemToObject(test->json_start, "connecting_to", iperf_json_printf("host: %s  port: %d  data-port: %d", test->server_hostname, (int64_t) test->server_port, (int64_t) test->data_port));
+	} else {
+	    iperf_printf(test, report_connecting, test->server_hostname, test->server_port, test->data_port);
 	    if (test->reverse)
 		iperf_printf(test, report_reverse, test->server_hostname);
 	}
@@ -1177,12 +1189,27 @@ 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 = strchr(optarg, '/');
+		if (slash) { // Get Data port
+		    *slash = '\0';
+		    ++slash;
+                    portno = atoi(slash);
+		    if (portno < 1 || portno > 65535) {
+		        i_errno = IEBADPORT;
+		        return -1;
+		    }
+                    test->data_port = portno;
+                    client_flag = 1;
 		}
-		test->server_port = portno;
+                if (strlen(optarg) > 0) { // Get control (and data) port
+                    portno = atoi(optarg);
+                    if (portno < 1 || portno > 65535) {
+                        i_errno = IEBADPORT;
+                        return -1;
+                    }
+                    test->server_port = portno;
+                }
                 break;
             case 'f':
 		if (!optarg) {
@@ -1772,6 +1799,11 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
     if (!rate_flag)
 	test->settings->rate = test->protocol->id == Pudp ? UDP_RATE : 0;
 
+    if (test->data_port != test->server_port && test->protocol->id != Pudp) {
+	i_errno = IEDATAPORT;
+	return -1;
+    }
+
     /* if no bytes or blocks specified, nor a duration_flag, and we have -F,
     ** get the file-size as the bytes count to be transferred
     */
@@ -2288,6 +2320,8 @@ send_parameters(struct iperf_test *test)
 	cJSON_AddNumberToObject(j, "time", test->duration);
         cJSON_AddNumberToObject(j, "num", test->settings->bytes);
         cJSON_AddNumberToObject(j, "blockcount", test->settings->blocks);
+        if (test->protocol->id == Pudp)
+            cJSON_AddNumberToObject(j, "data_port", test->data_port);
 	if (test->settings->mss)
 	    cJSON_AddNumberToObject(j, "MSS", test->settings->mss);
 	if (test->no_delay)
@@ -2403,6 +2437,8 @@ get_parameters(struct iperf_test *test)
         test->settings->blocks = 0;
 	if ((j_p = cJSON_GetObjectItem(j, "blockcount")) != NULL)
 	    test->settings->blocks = j_p->valueint;
+	if ((j_p = cJSON_GetObjectItem(j, "data_port")) != NULL)
+	    test->data_port = j_p->valueint;
 	if ((j_p = cJSON_GetObjectItem(j, "MSS")) != NULL)
 	    test->settings->mss = j_p->valueint;
 	if ((j_p = cJSON_GetObjectItem(j, "nodelay")) != NULL)
@@ -3025,6 +3061,7 @@ iperf_defaults(struct iperf_test *testp)
     testp->congestion_used = NULL;
     testp->remote_congestion_used = NULL;
     testp->server_port = PORT;
+    testp->data_port = testp->server_port;
     testp->ctrl_sck = -1;
     testp->listener = -1;
     testp->prot_listener = -1;
diff --git a/src/iperf_api.h b/src/iperf_api.h
index 2b71613e9..ed3ecdc2f 100644
--- a/src/iperf_api.h
+++ b/src/iperf_api.h
@@ -420,6 +420,7 @@ enum {
     IESNDTIMEOUT = 33,      // Illegal message send timeout
     IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP
     IESERVERAUTHUSERS = 35,   // Cannot access authorized users file
+    IEDATAPORT = 36,        // Data port is supported only for UDP
     /* 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 3388d376e..83f406150 100644
--- a/src/iperf_error.c
+++ b/src/iperf_error.c
@@ -204,7 +204,10 @@ iperf_strerror(int int_errno)
 	    snprintf(errstr, len, "bad format specifier (valid formats are in the set [kmgtKMGT])");
 	    break;
 	case IEBADPORT:
-	    snprintf(errstr, len, "port number must be between 1 and 65535 inclusive");
+	    snprintf(errstr, len, "illegal port value, number must be between 1 and 65535 inclusive");
+	    break;
+	case IEDATAPORT:
+	    snprintf(errstr, len, "data port is supported only for UDP");
 	    break;
         case IEMSS:
             snprintf(errstr, len, "TCP MSS too large (maximum = %d bytes)", MAX_MSS);
diff --git a/src/iperf_locale.c b/src/iperf_locale.c
index 5c6e66dfd..ee2fe807d 100644
--- a/src/iperf_locale.c
+++ b/src/iperf_locale.c
@@ -99,7 +99,8 @@ 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"
-                           "  -p, --port      #         server port to listen on/connect to\n"
+                           "  -p, --port      #[/#]     server port to listen on/connect to\n"
+                           "                            (optional Client only - port for UDP data send/receive)\n"
                            "  -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"
@@ -311,7 +312,7 @@ const char report_time[] =
 "Time: %s\n";
 
 const char report_connecting[] =
-"Connecting to host %s, port %d\n";
+"Connecting to host %s, port %d, data-port %d\n";
 
 const char report_authentication_succeeded[] =
 "Authentication succeeded for user '%s' ts %" PRIu64 "\n";
diff --git a/src/iperf_udp.c b/src/iperf_udp.c
index 760116b4b..36e661429 100644
--- a/src/iperf_udp.c
+++ b/src/iperf_udp.c
@@ -447,7 +447,7 @@ iperf_udp_accept(struct iperf_test *test)
      * 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, test->data_port);
     if (test->prot_listener < 0) {
         i_errno = IESTREAMLISTEN;
         return -1;
@@ -479,7 +479,7 @@ iperf_udp_listen(struct iperf_test *test)
 {
     int s;
 
-    if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
+    if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->data_port)) < 0) {
         i_errno = IESTREAMLISTEN;
         return -1;
     }
@@ -508,7 +508,7 @@ iperf_udp_connect(struct iperf_test *test)
     int i, max_len_wait_for_reply;
 
     /* Create and bind our local socket. */
-    if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, -1)) < 0) {
+    if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->data_port, -1)) < 0) {
         i_errno = IESTREAMCONNECT;
         return -1;
     }