From 4a2dd52be4800f6938fa81f6c38a4f76b5300b9e Mon Sep 17 00:00:00 2001 From: DavidBar-On Date: Thu, 10 Oct 2024 10:46:16 +0300 Subject: [PATCH] Add Bounceback test option --- src/iperf.h | 52 ++++++++ src/iperf_api.c | 264 +++++++++++++++++++++++++++++++++++++++-- src/iperf_api.h | 14 +++ src/iperf_client_api.c | 247 +++++++++++++++++++++++++++++++++++++- src/iperf_error.c | 3 + src/iperf_locale.c | 13 ++ src/iperf_locale.h | 2 + src/iperf_server_api.c | 110 ++++++++++++++++- 8 files changed, 684 insertions(+), 21 deletions(-) diff --git a/src/iperf.h b/src/iperf.h index 7d14a3453..7b3826eee 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -62,6 +62,7 @@ #endif // HAVE_SSL #include "iperf_pthread.h" +#include /* * Atomic types highly desired, but if not, we approximate what we need @@ -126,6 +127,13 @@ struct iperf_interval_results long rtt; long rttvar; long pmtu; + + /* for Bounceback */ + atomic_iperf_size_t bounceback_roundtrip_sum_this_interval; + atomic_iperf_size_t bounceback_roundtrip_sqrt_sum_this_interval; + long bounceback_count_this_interval; + long bounceback_min_this_interval; + long bounceback_max_this_interval; }; struct iperf_stream_result @@ -135,6 +143,18 @@ struct iperf_stream_result atomic_iperf_size_t bytes_received_this_interval; atomic_iperf_size_t bytes_sent_this_interval; atomic_iperf_size_t bytes_sent_omit; + + double bounceback_roundtrip_sum; + double bounceback_roundtrip_sum_this_interval; + double bounceback_roundtrip_sqrt_sum; + double bounceback_roundtrip_sqrt_sum_this_interval; + long bounceback_count; + long bounceback_count_this_interval; + long bounceback_min; + long bounceback_min_this_interval; + long bounceback_max; + long bounceback_max_this_interval; + long stream_prev_total_retrans; long stream_retrans; long stream_max_rtt; @@ -184,6 +204,12 @@ 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 bounceback; /* whether to run bounceback test */ + int bounceback_burst; /* number of messages in bounceback burst */ + int bounceback_inum; /* number of bounceback bursts in report interval */ + double bounceback_period; /* number of seconds between baounceback bursts (calculated from inum) */ + int bounceback_size; /* baounceback message size */ + int bounceback_response_size; /* baounceback response message size */ }; struct iperf_test; @@ -376,6 +402,8 @@ struct iperf_test int num_streams; /* total streams in the test (-P) */ + sem_t bounceback_sem; + atomic_iperf_size_t bytes_sent; atomic_iperf_size_t blocks_sent; @@ -421,10 +449,34 @@ struct iperf_test }; +/* Bounceback structures */ +struct bounceback_report // bounceback report about one message and its bounceback +{ + uint64_t bb_burst_id; + uint32_t bb_index_in_burst; + struct iperf_time bb_client_tx_ts; + struct iperf_time bb_server_rx_ts; + struct iperf_time bb_server_tx_ts; + struct iperf_time bb_client_rx_ts; + uint64_t time_uplink; // us + uint64_t time_downlink; // us + uint64_t time_roundtrip; // us +}; + +struct bounceback_header // Header of bounceback message (both directions) +{ + uint32_t bb_burst_id; + uint32_t bb_index_in_burst; + struct iperf_time bb_client_tx_ts; + struct iperf_time bb_server_rx_ts; + struct iperf_time bb_server_tx_ts; +}; + /* default settings */ #define PORT 5201 /* default port to listen on (don't use the same port as iperf2) */ #define uS_TO_NS 1000 #define mS_TO_US 1000 +#define mS_TO_NS 1000000 #define SEC_TO_mS 1000 #define SEC_TO_US 1000000LL #define UDP_RATE (1024 * 1024) /* 1 Mbps */ diff --git a/src/iperf_api.c b/src/iperf_api.c index 262adbc2e..307b155e3 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -55,6 +55,7 @@ #include #include #include +#include #if defined(HAVE_CPUSET_SETAFFINITY) #include @@ -115,7 +116,7 @@ usage() void usage_long(FILE *f) { - fprintf(f, usage_longstr, DEFAULT_NO_MSG_RCVD_TIMEOUT, UDP_RATE / (1024*1024), DEFAULT_PACING_TIMER, DURATION, DEFAULT_TCP_BLKSIZE / 1024, DEFAULT_UDP_BLKSIZE); + fprintf(f, usage_longstr, DEFAULT_NO_MSG_RCVD_TIMEOUT, UDP_RATE / (1024*1024), DEFAULT_PACING_TIMER, DURATION, DEFAULT_TCP_BLKSIZE / 1024, DEFAULT_UDP_BLKSIZE, DEFAULT_BOUNCEBACK_INUM, DEFAULT_BOUNCEBACK_BURST, DEFAULT_BOUNCEBACK_MSG_SIZE); } @@ -440,6 +441,36 @@ iperf_get_mapped_v4(struct iperf_test* ipt) return ipt->mapped_v4; } +int +iperf_get_test_bounceback(struct iperf_test *ipt) +{ + return ipt->settings->bounceback; +} + +int +iperf_get_test_bounceback_inum(struct iperf_test *ipt) +{ + return ipt->settings->bounceback_inum; +} + +int +iperf_get_test_bounceback_burst(struct iperf_test *ipt) +{ + return ipt->settings->bounceback_burst; +} + +int +iperf_get_test_bounceback_size(struct iperf_test *ipt) +{ + return ipt->settings->bounceback_size; +} + +int +iperf_get_test_bounceback_response_size(struct iperf_test *ipt) +{ + return ipt->settings->bounceback_response_size; +} + /************** Setter routines for some fields inside iperf_test *************/ void @@ -721,6 +752,36 @@ iperf_set_test_unit_format(struct iperf_test *ipt, char unit_format) ipt->settings->unit_format = unit_format; } +void +iperf_set_test_bounceback(struct iperf_test *ipt, int onoff) +{ + ipt->settings->bounceback = onoff; +} + +void +iperf_set_test_bounceback_inum(struct iperf_test *ipt, double inum) +{ + ipt->settings->bounceback_period = inum; +} + +void +iperf_set_test_bounceback_burst(struct iperf_test *ipt, int burst) +{ + ipt->settings->bounceback_burst = burst; +} + +void +iperf_set_test_bounceback_size(struct iperf_test *ipt, int size) +{ + ipt->settings->bounceback_size = size; +} + +void +iperf_set_test_bounceback_response_size(struct iperf_test *ipt, int size) +{ + ipt->settings->bounceback_response_size = size; +} + #if defined(HAVE_SSL) void iperf_set_test_client_username(struct iperf_test *ipt, const char *client_username) @@ -896,7 +957,7 @@ void iperf_on_test_start(struct iperf_test *test) { if (test->json_output) { - cJSON_AddItemToObject(test->json_start, "test_start", iperf_json_printf("protocol: %s num_streams: %d blksize: %d omit: %d duration: %d bytes: %d blocks: %d reverse: %d tos: %d target_bitrate: %d bidir: %d fqrate: %d interval: %f", test->protocol->name, (int64_t) test->num_streams, (int64_t) test->settings->blksize, (int64_t) test->omit, (int64_t) test->duration, (int64_t) test->settings->bytes, (int64_t) test->settings->blocks, test->reverse?(int64_t)1:(int64_t)0, (int64_t) test->settings->tos, (int64_t) test->settings->rate, (int64_t) test->bidirectional, (uint64_t) test->settings->fqrate, test->stats_interval)); + cJSON_AddItemToObject(test->json_start, "test_start", iperf_json_printf("protocol: %s num_streams: %d blksize: %d omit: %d duration: %d bytes: %d blocks: %d reverse: %d tos: %d target_bitrate: %d bidir: %d fqrate: %d interval: %f bounceback: %d", test->protocol->name, (int64_t) test->num_streams, (int64_t) test->settings->blksize, (int64_t) test->omit, (int64_t) test->duration, (int64_t) test->settings->bytes, (int64_t) test->settings->blocks, test->reverse?(int64_t)1:(int64_t)0, (int64_t) test->settings->tos, (int64_t) test->settings->rate, (int64_t) test->bidirectional, (uint64_t) test->settings->fqrate, test->stats_interval, test->settings->bounceback)); } else { if (test->verbose) { if (test->settings->bytes) @@ -905,6 +966,8 @@ iperf_on_test_start(struct iperf_test *test) iperf_printf(test, test_start_blocks, test->protocol->name, test->num_streams, test->settings->blksize, test->omit, test->settings->blocks, test->settings->tos); else iperf_printf(test, test_start_time, test->protocol->name, test->num_streams, test->settings->blksize, test->omit, test->duration, test->settings->tos); + if (test->settings->bounceback) + iperf_printf(test, test_start_bounceback, test->settings->bounceback_burst, test->settings->bounceback_inum, test->settings->bounceback_size, test->settings->bounceback_response_size); } } if (test->json_stream) { @@ -1149,6 +1212,7 @@ 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}, + {"bounceback", optional_argument, NULL, OPT_BOUNCEBACK}, {"debug", optional_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} @@ -1161,7 +1225,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) #if defined(HAVE_CPU_AFFINITY) char* comma; #endif /* HAVE_CPU_AFFINITY */ - char* slash; + char *slash, *slash1, *slash2; char *p, *p1; struct xbind_entry *xbe; double farg; @@ -1565,6 +1629,68 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; #endif /* HAVE_TCP_CONGESTION */ break; + case OPT_BOUNCEBACK: + if (optarg) { + slash = strchr(optarg, '/'); + if (slash) { + *slash = '\0'; + ++slash; + slash1 = strchr(slash, '/'); + if (slash1) { + *slash1 = '\0'; + ++slash1; + slash2 = strchr(slash1, '/'); + if (slash2) { + *slash2 = '\0'; + ++slash2; + if (strlen(slash2) > 0) { + test->settings->bounceback_response_size = atoi(slash2); + if (test->settings->bounceback_response_size < sizeof(struct bounceback_header) || + test->settings->bounceback_response_size > MAX_BLOCKSIZE) { + i_errno = IEBOUNCEBACK; + return -1; + } + } + } + if (strlen(slash1) > 0) { + test->settings->bounceback_size = atoi(slash1); + if (test->settings->bounceback_size < sizeof(struct bounceback_header) || + test->settings->bounceback_size > MAX_BLOCKSIZE) { + i_errno = IEBOUNCEBACK; + return -1; + } + } + } + if (strlen(slash) > 0) { + test->settings->bounceback_inum = atoi(slash); + if (test->settings->bounceback_inum < 1) { + i_errno = IEBOUNCEBACK; + return -1; + } +#if !defined(HAVE_CLOCK_NANOSLEEP) && !defined(HAVE_NANOSLEEP) + /* If nanosleep is not supported, support only one bounceback burst per interval */ + if (test->settings->bounceback_inum > 1) { + test->settings->bounceback_inum = 1; + warning("Number of bursts per interval (inum in --bounceback) set to 1 as nanosleep is not supported"); + } +#endif /* !defined(HAVE_CLOCK_NANOSLEEP) && !defined(HAVE_CLOCK_NANOSLEEP) */ + } + } + if (strlen(optarg) > 0) { + test->settings->bounceback_burst = atoi(optarg); + if (test->settings->bounceback_burst < 1 || + test->settings->bounceback_burst > MAX_BURST) { + i_errno = IEBOUNCEBACK; + return -1; + } + } + } /* if optarg */ + if (test->settings->bounceback_response_size == 0) { + test->settings->bounceback_response_size = test->settings->bounceback_size; + } + test->settings->bounceback = 1; /* indicates that bounceback test is requested */ + client_flag = 1; + break; case 'd': test->debug = 1; test->debug_level = DEBUG_LEVEL_MAX; @@ -1806,6 +1932,28 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) return -1; } + if (test->settings->bounceback) { + /* No meaning for bounceback test with unlimited test time and no reporting interval */ + if (test->duration == 0 && test->stats_interval == 0) { + i_errno = IEBOUNCEBACK; + return -1; + } + /* Set the period between bounceback bursts */ + test->settings->bounceback_period = + (double)((test->stats_interval > 0) ? test->stats_interval : test->duration) / + test->settings->bounceback_inum; + /* Do not allow less then 10us between bounceback tests */ + if (test->settings->bounceback_period < 1e-5) { + i_errno = IEBOUNCEBACK; + return -1; + } + + /* Initializa a semaphre to allow the main thread to initiate bounceback at each interval */ + if (sem_init(&test->bounceback_sem, 0 ,1) != 0) { + perror("iperf_parse_arguments: sem_init"); + } + } + /* For subsequent calls to getopt */ #ifdef __APPLE__ optreset = 1; @@ -2333,6 +2481,11 @@ send_parameters(struct iperf_test *test) if (test->settings->dont_fragment) cJSON_AddNumberToObject(j, "dont_fragment", test->settings->dont_fragment); #endif /* HAVE_DONT_FRAGMENT */ + if (test->settings->bounceback) { + cJSON_AddNumberToObject(j, "bounceback", test->settings->bounceback); + cJSON_AddNumberToObject(j, "bounceback_size", test->settings->bounceback_size); + cJSON_AddNumberToObject(j, "bounceback_response_size", test->settings->bounceback_response_size); + } #if defined(HAVE_SSL) /* Send authentication parameters */ if (test->settings->client_username && test->settings->client_password && test->settings->client_rsa_pubkey){ @@ -2449,6 +2602,12 @@ get_parameters(struct iperf_test *test) if ((j_p = cJSON_GetObjectItem(j, "dont_fragment")) != NULL) test->settings->dont_fragment = j_p->valueint; #endif /* HAVE_DONT_FRAGMENT */ + if ((j_p = cJSON_GetObjectItem(j, "bounceback")) != NULL) + test->settings->bounceback = j_p->valueint; + if ((j_p = cJSON_GetObjectItem(j, "bounceback_size")) != NULL) + test->settings->bounceback_size = j_p->valueint; + if ((j_p = cJSON_GetObjectItem(j, "bounceback_response_size")) != NULL) + test->settings->bounceback_response_size = j_p->valueint; #if defined(HAVE_SSL) if ((j_p = cJSON_GetObjectItem(j, "authtoken")) != NULL) test->settings->authtoken = strdup(j_p->valuestring); @@ -3053,6 +3212,12 @@ iperf_defaults(struct iperf_test *testp) testp->settings->connect_timeout = -1; 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->settings->bounceback = 0; + testp->settings->bounceback_burst = DEFAULT_BOUNCEBACK_BURST; + testp->settings->bounceback_inum = DEFAULT_BOUNCEBACK_INUM; + testp->settings->bounceback_period = 1 / DEFAULT_BOUNCEBACK_INUM; + testp->settings->bounceback_size = DEFAULT_BOUNCEBACK_MSG_SIZE; + testp->settings->bounceback_response_size = 0; testp->zerocopy = 0; memset(testp->cookie, 0, COOKIE_SIZE); @@ -3209,12 +3374,9 @@ iperf_free_test(struct iperf_test *test) free(prot); } - /* Destroy print mutex. iperf_printf() doesn't work after this point */ - int rc; - rc = pthread_mutex_destroy(&(test->print_mutex)); - if (rc != 0) { - errno = rc; - perror("iperf_free_test: pthread_mutex_destroy"); + /* Destroy bounceback semaphore */ + if (test->settings->bounceback) { + sem_destroy(&test->bounceback_sem); } if (test->logfile) { @@ -3350,6 +3512,12 @@ iperf_reset_test(struct iperf_test *test) test->settings->mss = 0; test->settings->tos = 0; test->settings->dont_fragment = 0; + test->settings->bounceback = 0; + test->settings->bounceback_burst = DEFAULT_BOUNCEBACK_BURST; + test->settings->bounceback_inum = DEFAULT_BOUNCEBACK_INUM; + test->settings->bounceback_period = 1 / DEFAULT_BOUNCEBACK_INUM; + test->settings->bounceback_size = DEFAULT_BOUNCEBACK_MSG_SIZE; + test->settings->bounceback_response_size = 0; test->zerocopy = 0; #if defined(HAVE_SSL) @@ -3416,6 +3584,10 @@ iperf_reset_stats(struct iperf_test *test) rp->bytes_sent_omit = rp->bytes_sent; rp->bytes_received = 0; rp->bytes_sent_this_interval = rp->bytes_received_this_interval = 0; + rp->bounceback_roundtrip_sum = rp->bounceback_roundtrip_sqrt_sum = 0; + rp->bounceback_count = rp->bounceback_min = rp->bounceback_max = 0; + rp->bounceback_roundtrip_sum_this_interval = rp->bounceback_roundtrip_sqrt_sum_this_interval = 0; + rp->bounceback_count_this_interval = rp->bounceback_min_this_interval = rp->bounceback_max_this_interval = 0; if (test->sender_has_retransmits == 1) { struct iperf_interval_results ir; /* temporary results structure */ save_tcpinfo(sp, &ir); @@ -3546,8 +3718,17 @@ iperf_stats_callback(struct iperf_test *test) } #endif /* HAVE_SCTP_H */ + /* Add Bounceback Statistics */ + temp.bounceback_roundtrip_sum_this_interval = rp->bounceback_roundtrip_sum_this_interval; + temp.bounceback_roundtrip_sqrt_sum_this_interval = rp->bounceback_roundtrip_sqrt_sum_this_interval; + temp.bounceback_count_this_interval = rp->bounceback_count_this_interval; + temp.bounceback_min_this_interval = rp->bounceback_min_this_interval; + temp.bounceback_max_this_interval = rp->bounceback_max_this_interval; + add_to_interval_list(rp, &temp); rp->bytes_sent_this_interval = rp->bytes_received_this_interval = 0; + rp->bounceback_roundtrip_sum_this_interval = rp->bounceback_roundtrip_sqrt_sum_this_interval = 0; + rp->bounceback_count_this_interval = rp->bounceback_min_this_interval = rp->bounceback_max_this_interval = 0; } /* Verify that total server's throughput is not above specified limit */ @@ -3566,6 +3747,7 @@ static void iperf_print_intermediate(struct iperf_test *test) { struct iperf_stream *sp = NULL; + struct iperf_stream *bb_sp = NULL; struct iperf_interval_results *irp; struct iperf_time temp_time; cJSON *json_interval; @@ -3680,6 +3862,9 @@ iperf_print_intermediate(struct iperf_test *test) double avg_jitter = 0.0, lost_percent; int stream_must_be_sender = current_mode * current_mode; + double bb_average, bb_stdev, bb_min, bb_max; + struct iperf_interval_results *bb_irp = NULL; + char *sum_name; /* Print stream role just for bidirectional mode. */ @@ -3692,7 +3877,17 @@ iperf_print_intermediate(struct iperf_test *test) } SLIST_FOREACH(sp, &test->streams, streams) { - if (sp->sender == stream_must_be_sender) { + if (sp->sender == 2) { + if (test->role == 'c' && current_mode == upper_mode) { + bb_sp = sp; + bb_irp = TAILQ_LAST(&sp->result->interval_results, irlisthead); + if (bb_irp == NULL) { + iperf_err(test, + "iperf_print_intermediate error: bounceback interval_results is NULL"); + return; + } + } + } else if (sp->sender == stream_must_be_sender) { print_interval_results(test, sp, json_interval_streams); /* sum up all streams */ irp = TAILQ_LAST(&sp->result->interval_results, irlisthead); @@ -3782,6 +3977,26 @@ iperf_print_intermediate(struct iperf_test *test) } } } + + /* Print Bounceback statistics for the interval */ + if (bb_irp) { + iperf_time_diff(&bb_sp->result->start_time, &bb_irp->interval_start_time, &temp_time); + start_time = iperf_time_in_secs(&temp_time); + iperf_time_diff(&bb_sp->result->start_time, &bb_irp->interval_end_time, &temp_time); + end_time = iperf_time_in_secs(&temp_time); + + /* Reporting values are in ms while stored values are in us */ + bb_average = (double)bb_irp->bounceback_roundtrip_sum_this_interval / bb_irp->bounceback_count_this_interval / 1000; + bb_stdev = sqrt(((double)bb_irp->bounceback_roundtrip_sqrt_sum_this_interval / bb_irp->bounceback_count_this_interval) - + (bb_average * bb_average)) / 1000; + bb_min = (double)bb_irp->bounceback_min_this_interval / 1000; + bb_max = (double)bb_irp->bounceback_max_this_interval / 1000; + if (test->json_output) { + cJSON_AddItemToObject(json_interval, "Bounceback", iperf_json_printf("start: %f end: %f count: %d avarage: %f min: %f max: %f stdev: %f", start_time, end_time, bb_irp->bounceback_count_this_interval, bb_average, bb_min, bb_max, bb_stdev)); + } else { + iperf_printf(test, report_bounceback_format, (test->mode == BIDIRECTIONAL) ? " " : "", start_time, end_time, bb_irp->bounceback_count_this_interval, bb_average, bb_min, bb_max, bb_stdev); + } + } } if (test->json_stream) @@ -3880,6 +4095,9 @@ iperf_print_results(struct iperf_test *test) struct iperf_time temp_time; double bandwidth; + double bb_average, bb_stdev, bb_min, bb_max; + struct iperf_stream *bb_sp = NULL; + char mbuf[UNIT_LEN]; int stream_must_be_sender = current_mode * current_mode; @@ -3932,7 +4150,12 @@ iperf_print_results(struct iperf_test *test) sender_time = sp->result->sender_time; receiver_time = sp->result->receiver_time; SLIST_FOREACH(sp, &test->streams, streams) { - if (sp->sender == stream_must_be_sender) { + + if (sp->sender == 2) { // Save Bounceback stream + if (test->role == 'c' && current_mode == upper_mode) { + bb_sp = sp; + } + } else if (sp->sender == stream_must_be_sender) { if (test->json_output) { json_summary_stream = cJSON_CreateObject(); if (json_summary_stream == NULL) @@ -4260,6 +4483,21 @@ iperf_print_results(struct iperf_test *test) } } + /* Print Bounceback statistics for the interval */ + if (bb_sp) { + /* Report values are in ms while stored values are in us */ + bb_average = (double)bb_sp->result->bounceback_roundtrip_sum / bb_sp->result->bounceback_count / 1000; + bb_stdev = sqrt(((double)bb_sp->result->bounceback_roundtrip_sqrt_sum / bb_sp->result->bounceback_count) - + (bb_average * bb_average)) / 1000; + bb_min = (double)bb_sp->result->bounceback_min / 1000; + bb_max = (double)bb_sp->result->bounceback_max / 1000; + if (test->json_output) { + cJSON_AddItemToObject(test->json_end, "Bounceback", iperf_json_printf("start: %f end: %f count: %d avarage: %f min: %f max: %f stdev: %f", start_time, sender_time, bb_sp->result->bounceback_count, bb_average, bb_min, bb_max, bb_stdev)); + } else { + iperf_printf(test, report_bounceback_format, (test->mode == BIDIRECTIONAL) ? " " : "", start_time, sender_time, bb_sp->result->bounceback_count, bb_average, bb_min, bb_max, bb_stdev); + } + } + if (test->json_output && current_mode == upper_mode) { cJSON_AddItemToObject(test->json_end, "cpu_utilization_percent", iperf_json_printf("host_total: %f host_user: %f host_system: %f remote_total: %f remote_user: %f remote_system: %f", (double) test->cpu_util[0], (double) test->cpu_util[1], (double) test->cpu_util[2], (double) test->remote_cpu_util[0], (double) test->remote_cpu_util[1], (double) test->remote_cpu_util[2])); if (test->protocol->id == Ptcp) { @@ -4614,6 +4852,10 @@ iperf_new_stream(struct iperf_test *test, int s, int sender) } iperf_add_stream(test, sp); + if (test->debug_level >= DEBUG_LEVEL_INFO) { + iperf_printf(test, "New socket %d created; Stream id %d with sender type %d\n", sp->socket, sp->id, sp->sender); + } + return sp; } diff --git a/src/iperf_api.h b/src/iperf_api.h index 2b71613e9..aaf77d2f6 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -71,6 +71,10 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t; #define WARN_STR_LEN 128 +#define DEFAULT_BOUNCEBACK_MSG_SIZE 100 /* default Bounceback message size */ +#define DEFAULT_BOUNCEBACK_BURST 10 /* default burst size of baounceback messages */ +#define DEFAULT_BOUNCEBACK_INUM 1 /* default number of baounceback burst per report interval */ + /* short option equivalents, used to support options that only have long form */ #define OPT_SCTP 1 #define OPT_LOGFILE 2 @@ -101,6 +105,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_BOUNCEBACK 31 /* states */ #define TEST_START 1 @@ -168,6 +173,10 @@ int iperf_get_dont_fragment( struct iperf_test* ipt ); char* iperf_get_test_congestion_control(struct iperf_test* ipt); int iperf_get_test_mss(struct iperf_test* ipt); int iperf_get_mapped_v4(struct iperf_test* ipt); +int iperf_get_test_bounceback_burst(struct iperf_test *ipt); +double iperf_get_test_bounceback_period(struct iperf_test *ipt); +int iperf_get_test_bounceback_size(struct iperf_test *ipt); +int iperf_get_test_bounceback_response_size(struct iperf_test *ipt); /* Setter routines for some fields inside iperf_test. */ void iperf_set_verbose( struct iperf_test* ipt, int verbose ); @@ -217,6 +226,10 @@ void iperf_set_on_new_stream_callback(struct iperf_test* ipt, void (*callback void iperf_set_on_test_start_callback(struct iperf_test* ipt, void (*callback)()); void iperf_set_on_test_connect_callback(struct iperf_test* ipt, void (*callback)()); void iperf_set_on_test_finish_callback(struct iperf_test* ipt, void (*callback)()); +void iperf_set_test_bounceback_burst(struct iperf_test *ipt, int burst); +void iperf_set_test_bounceback_period(struct iperf_test *ipt, double period); +void iperf_set_test_bounceback_size(struct iperf_test *ipt, int size); +void iperf_set_test_bounceback_response_size(struct iperf_test *ipt, int size); #if defined(HAVE_SSL) void iperf_set_test_client_username(struct iperf_test *ipt, const char *client_username); @@ -420,6 +433,7 @@ enum { IESNDTIMEOUT = 33, // Illegal message send timeout IEUDPFILETRANSFER = 34, // Cannot transfer file using UDP IESERVERAUTHUSERS = 35, // Cannot access authorized users file + IEBOUNCEBACK = 36, // Invalid value specified in bounceback /* Test errors */ IENEWTEST = 100, // Unable to create a new test (check perror) IEINITTEST = 101, // Test initialization failed (check perror) diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c index d2542f717..e636fdf27 100644 --- a/src/iperf_client_api.c +++ b/src/iperf_client_api.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "iperf.h" #include "iperf_api.h" @@ -96,6 +97,206 @@ iperf_client_worker_run(void *s) { return NULL; } +void * +iperf_client_bounceback_worker_run(void *s) +{ + static uint64_t burst_id = 0; + + struct iperf_stream *sp = (struct iperf_stream *) s; + struct iperf_test *test = sp->test; + struct iperf_stream_result *rp; + int r, in_burst_cnt, inum_cnt; + struct iperf_time now; + struct bounceback_header *phdr; + struct bounceback_report bb_report; +#if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) + uint64_t ns_period = test->settings->bounceback_period * SEC_TO_NS; + int64_t ns; + struct timespec nanosleep_time; +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP) */ +#if defined(HAVE_CLOCK_NANOSLEEP) + int ret; +#elif defined(HAVE_NANOSLEEP) + struct iperf_time burst_time_start, burst_time_end; +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP*/ + + /* Blocking signal to make sure that signal will be handled by main thread */ + sigset_t set; + sigemptyset(&set); +#ifdef SIGTERM + sigaddset(&set, SIGTERM); +#endif +#ifdef SIGHUP + sigaddset(&set, SIGHUP); +#endif +#ifdef SIGINT + sigaddset(&set, SIGINT); +#endif + if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) { + i_errno = IEPTHREADSIGMASK; + goto cleanup_and_fail; + } + + /* Allow this thread to be cancelled even if it's in a syscall */ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + rp = sp->result; + phdr = (struct bounceback_header *)sp->buffer; + + /* loop of wrining bounceback message and reading its reply */ + while (! (test->done) && ! (sp->done)) { + /* wait until the next report interval starts */ + if (sem_wait(&test->bounceback_sem) < 0) { + perror("iperf_client_bounceback_worker_run: sem_wait"); + continue; + } + /* No need for queued semaphores */ + while (sem_trywait(&test->bounceback_sem) == 0); + + /* Loop of bounceback bursts during an interval */ + for (inum_cnt = test->settings->bounceback_inum; inum_cnt > 0; --inum_cnt) { + /* Set time for next burst */ + #if defined(HAVE_CLOCK_NANOSLEEP) + if (clock_gettime(CLOCK_MONOTONIC, &nanosleep_time) != 0) + perror("iperf_client_bounceback_worker_run: clock_gettime"); + #elif defined(HAVE_NANOSLEEP) + iperf_time_now(&burst_time_start); + #endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP */ + + burst_id++; + + /* burst loop */ + for (in_burst_cnt = 1; in_burst_cnt <= test->settings->bounceback_burst; in_burst_cnt++) { + /* writing bounceback message */ + phdr->bb_burst_id = htonl(burst_id); + phdr->bb_index_in_burst = htonl(in_burst_cnt); + iperf_time_now(&now); + phdr->bb_client_tx_ts.secs = htonl(now.secs); + phdr->bb_client_tx_ts.usecs = htonl(now.usecs); + phdr->bb_server_rx_ts.secs = -1; + phdr->bb_server_rx_ts.usecs = -1; + phdr->bb_server_tx_ts.secs = -1; + phdr->bb_server_tx_ts.usecs = -1; + + r = Nwrite(sp->socket, sp->buffer, test->settings->bounceback_size, test->protocol->id); + if (r < 0) { + if (r == NET_SOFTERROR) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback send failed on NET_SOFTERROR. errno=%s\n", strerror(errno)); + continue; + } else { + iperf_err(test, "Bounceback send failed. errno=%s", strerror(errno)); + goto cleanup_and_fail; + } + } + + if (r != test->settings->bounceback_size) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback send wrote only %d bytes instead of %d bytes\n", r, test->settings->bounceback_size); + continue; + } + + r = Nread(sp->socket, sp->buffer, test->settings->bounceback_response_size, test->protocol->id); + if (r < 0) { + if (r == NET_SOFTERROR) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback receive failed on NET_SOFTERROR. errno=%s\n", strerror(errno)); + continue; + } else { + iperf_err(test, "Bounceback receive failed. errno=%s", strerror(errno)); + goto cleanup_and_fail; + } + } + if (r != test->settings->bounceback_response_size) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback receive read only %d bytes instead of %d bytes\n", r, test->settings->bounceback_response_size); + continue; + } + + /* Get bounceback input data */ + iperf_time_now(&bb_report.bb_client_rx_ts); + bb_report.bb_burst_id = ntohl(phdr->bb_burst_id); + bb_report.bb_index_in_burst = ntohl(phdr->bb_index_in_burst); + bb_report.bb_client_tx_ts.secs = ntohl(phdr->bb_client_tx_ts.secs); + bb_report.bb_client_tx_ts.usecs = ntohl(phdr->bb_client_tx_ts.usecs); + bb_report.bb_server_rx_ts.secs = ntohl(phdr->bb_server_rx_ts.secs); + bb_report.bb_server_rx_ts.usecs = ntohl(phdr->bb_server_rx_ts.usecs); + bb_report.bb_server_tx_ts.secs = ntohl(phdr->bb_server_tx_ts.secs); + bb_report.bb_server_tx_ts.usecs = ntohl(phdr->bb_server_tx_ts.usecs); + + /* Calculate statistics */ + bb_report.time_uplink = iperf_time_in_usecs(&bb_report.bb_server_rx_ts) - + iperf_time_in_usecs(&bb_report.bb_client_tx_ts); + bb_report.time_downlink = iperf_time_in_usecs(&bb_report.bb_client_rx_ts) - + iperf_time_in_usecs(&bb_report.bb_server_tx_ts); + bb_report.time_roundtrip = iperf_time_in_usecs(&bb_report.bb_client_rx_ts) - + iperf_time_in_usecs(&bb_report.bb_client_tx_ts) - + iperf_time_in_usecs(&bb_report.bb_server_tx_ts) + + iperf_time_in_usecs(&bb_report.bb_server_rx_ts); + if (test->debug_level >= DEBUG_LEVEL_INFO) { + iperf_printf(test, "iperf_client_bounceback_worker_run: busrt=%ld, index=%d - BB message statistics in us: Uplink=%ld, Downlink=%ld, Rounttrip=%ld\n", burst_id, in_burst_cnt, bb_report.time_uplink, bb_report.time_downlink, bb_report.time_roundtrip); + } + + /* Add burst data to the statistics data */ + rp->bounceback_count++; + rp->bounceback_count_this_interval++; + rp->bounceback_roundtrip_sum += bb_report.time_roundtrip; + rp->bounceback_roundtrip_sum_this_interval += bb_report.time_roundtrip; + rp->bounceback_roundtrip_sqrt_sum += bb_report.time_roundtrip * bb_report.time_roundtrip; + rp->bounceback_roundtrip_sqrt_sum_this_interval += bb_report.time_roundtrip * bb_report.time_roundtrip; + if (rp->bounceback_min == 0 || rp->bounceback_min > bb_report.time_roundtrip) + rp->bounceback_min = bb_report.time_roundtrip; + if (rp->bounceback_max < bb_report.time_roundtrip) + rp->bounceback_max = bb_report.time_roundtrip; + if (rp->bounceback_min_this_interval == 0 || rp->bounceback_min_this_interval > bb_report.time_roundtrip) + rp->bounceback_min_this_interval = bb_report.time_roundtrip; + if (rp->bounceback_max_this_interval < bb_report.time_roundtrip) + rp->bounceback_max_this_interval = bb_report.time_roundtrip; + + } /* burst loop*/ + + /* sleep between bursts (but not after the last burst in the interval) */ + if (inum_cnt > 1) { +#if defined(HAVE_CLOCK_NANOSLEEP) + // Calculate absolute end of sleep time + ns = nanosleep_time.tv_nsec + ns_period; + if (ns < SEC_TO_NS) { + nanosleep_time.tv_nsec = ns; + } else { + nanosleep_time.tv_sec += ns / SEC_TO_NS; + nanosleep_time.tv_nsec = ns % SEC_TO_NS; + } + // Sleep + while((ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &nanosleep_time, NULL)) == EINTR); +#elif defined(HAVE_NANOSLEEP) + iperf_time_now(&burst_time_end); + ns = ns_period - ((iperf_time_in_usecs(&burst_time_end) - iperf_time_in_usecs(&burst_time_start)) * uS_TO_NS); + if (ns > 0) { + nanosleep_time.tv_sec = 0; + do { + // nansleep() time should be less than 1 sec + nanosleep_time.tv_nsec = (ns >= SEC_TO_NS) ? SEC_TO_NS - 1 : ns; + ns -= nanosleep_time.tv_nsec; + nanosleep(&nanosleep_time, NULL); + } while (ns > 0); + } +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP */ + } /* if to sleep */ + + } /* all bursts loop*/ + + } /* main while */ + + return NULL; + + cleanup_and_fail: + return NULL; +} + +/* Create Client's Stream: + sender: 0 - Rx, 1 - Tx, 2 - for Bounceback. +*/ int iperf_create_streams(struct iperf_test *test, int sender) { @@ -104,27 +305,43 @@ iperf_create_streams(struct iperf_test *test, int sender) iperf_err(NULL, "No test\n"); return -1; } + int i, s; #if defined(HAVE_TCP_CONGESTION) int saved_errno; #endif /* HAVE_TCP_CONGESTION */ struct iperf_stream *sp; + int num_streams; + int flag; + + num_streams = (sender == 2) ? 1 : test->num_streams; int orig_bind_port = test->bind_port; - for (i = 0; i < test->num_streams; ++i) { + for (i = 0; i < num_streams; ++i) { test->bind_port = orig_bind_port; if (orig_bind_port) { test->bind_port += i; // If Bidir make sure send and receive ports are different - if (!sender && test->mode == BIDIRECTIONAL) + if ((!sender && test->mode == BIDIRECTIONAL)) test->bind_port += test->num_streams; + else if (sender == 2) { + test->bind_port += test->num_streams + (test->mode == BIDIRECTIONAL)? test->num_streams : 0; + } } s = test->protocol->connect(test); test->bind_port = orig_bind_port; if (s < 0) return -1; + /* Set TCP_NODELAY to the Bouncenack stream */ + if (i + 1 == num_streams && test->protocol->id == Ptcp && test->settings->bounceback) { + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(flag))) { + i_errno = IESETNODELAY; + return -1; + } + } + #if defined(HAVE_TCP_CONGESTION) if (test->protocol->id == Ptcp) { if (test->congestion) { @@ -201,6 +418,13 @@ client_reporter_timer_proc(TimerClientData client_data, struct iperf_time *nowP) return; if (test->reporter_callback) test->reporter_callback(test); + + /* Send semaphore to start new interval bounceback tests */ + if (test->settings->bounceback) { + if (sem_post(&test->bounceback_sem) != 0) { + perror("client_reporter_timer_proc: sem_post"); + } + } } static int @@ -341,8 +565,13 @@ iperf_handle_message_client(struct iperf_test *test) if (iperf_create_streams(test, 0) < 0) return -1; } - else if (iperf_create_streams(test, test->mode) < 0) + else if (iperf_create_streams(test, test->mode) < 0) { return -1; + } + if (test->settings->bounceback) { + if (iperf_create_streams(test, 2) < 0) + return -1; + } break; case TEST_START: if (iperf_init_test(test) < 0) @@ -572,6 +801,7 @@ iperf_run_client(struct iperf_test * test) int64_t timeout_us; int64_t rcv_timeout_us; int i_errno_save; + void *worker; if (NULL == test) { @@ -706,12 +936,19 @@ iperf_run_client(struct iperf_test * test) } SLIST_FOREACH(sp, &test->streams, streams) { - if (pthread_create(&(sp->thr), &attr, &iperf_client_worker_run, sp) != 0) { + if (sp->sender == 2) { // Bounceback + worker = &iperf_client_bounceback_worker_run; + } else { // Sender (1) or Receiver (0) + worker = &iperf_client_worker_run; + } + + if (pthread_create(&(sp->thr), &attr, worker, sp) != 0) { i_errno = IEPTHREADCREATE; goto cleanup_and_fail; } if (test->debug_level >= DEBUG_LEVEL_INFO) { - iperf_printf(test, "Thread FD %d created\n", sp->socket); + iperf_printf(test, "Thread FD %d created; Stream id %d with sender type %d\n", + sp->socket, sp->id, sp->sender); } } if (test->debug_level >= DEBUG_LEVEL_INFO) { diff --git a/src/iperf_error.c b/src/iperf_error.c index 3388d376e..1dac22332 100644 --- a/src/iperf_error.c +++ b/src/iperf_error.c @@ -200,6 +200,9 @@ iperf_strerror(int int_errno) case IESERVERAUTHUSERS: snprintf(errstr, len, "cannot access authorized users file"); break; + case IEBOUNCEBACK: + snprintf(errstr, len, "invalid value specified in bounceback"); + break; case IEBADFORMAT: snprintf(errstr, len, "bad format specifier (valid formats are in the set [kmgtKMGT])"); break; diff --git a/src/iperf_locale.c b/src/iperf_locale.c index 5c6e66dfd..cb287456c 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -214,6 +214,13 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" #if defined(HAVE_DONT_FRAGMENT) " --dont-fragment set IPv4 Don't Fragment flag\n" #endif /* HAVE_DONT_FRAGMENT */ + " --bounceback[=[burst][/[inum][/[len][/replen]]]] Bounceback - send `inum` (default %d)\n" + " bursts of `burst` (default %d) messages of `len` (default %d)\n" + " bytes per report interval. Server reply is `replen`\n" + " (default `len`) bytes (TCP uses TCP_NODELAY).\n" +#if !defined(HAVE_CLOCK_NANOSLEEP) && !defined(HAVE_NANOSLEEP) + " (NOTE: `period` is rounded to nearest second, with minimum 1 sec.\n" +#endif /* !HAVE_CLOCK_NANOSLEEP and !HAVE_NANOSLEEP */ #if defined(HAVE_SSL) " --username username for authentication\n" " --rsa-public-key-path path to the RSA public key used to encrypt\n" @@ -302,6 +309,9 @@ const char test_start_bytes[] = const char test_start_blocks[] = "Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %"PRIuFAST64" bytes to send, tos %d\n"; +const char test_start_bounceback[] = +"Bounceback Test: burst-size: %d, %d bursts-in-interval, %d byte send, %d byte response\n"; + /* ------------------------------------------------------------------- * reports @@ -400,6 +410,9 @@ const char report_bw_udp_format_no_omitted_error[] = const char report_bw_udp_sender_format[] = "[%3d]%s %6.2f-%-6.2f sec %ss %ss/sec %s %" PRId64 " %s\n"; +const char report_bounceback_format[] = +"[BBK]%s %6.2f-%-6.2f sec count: %ld, avarage: %.3fms min: %.3fms max: %.3fms stdev: %.3fms\n"; + const char report_summary[] = "Test Complete. Summary Results:\n"; diff --git a/src/iperf_locale.h b/src/iperf_locale.h index bc9c96cb4..a6e357655 100644 --- a/src/iperf_locale.h +++ b/src/iperf_locale.h @@ -48,6 +48,7 @@ extern const char wait_server_threads[] ; extern const char test_start_time[]; extern const char test_start_bytes[]; extern const char test_start_blocks[]; +extern const char test_start_bounceback[]; extern const char report_time[] ; extern const char report_connecting[] ; @@ -80,6 +81,7 @@ extern const char report_bw_retrans_cwnd_format[] ; extern const char report_bw_udp_format[] ; extern const char report_bw_udp_format_no_omitted_error[] ; extern const char report_bw_udp_sender_format[] ; +extern const char report_bounceback_format[] ; extern const char report_summary[] ; extern const char report_sum_bw_format[] ; extern const char report_sum_bw_retrans_format[] ; diff --git a/src/iperf_server_api.c b/src/iperf_server_api.c index 9727cdddb..61cbf61d4 100644 --- a/src/iperf_server_api.c +++ b/src/iperf_server_api.c @@ -109,6 +109,91 @@ iperf_server_worker_run(void *s) { return NULL; } +void * +iperf_server_bounceback_worker_run(void *s) +{ + struct iperf_stream *sp = (struct iperf_stream *) s; + struct iperf_test *test = sp->test; + int r; + struct iperf_time now; + struct bounceback_header *phdr; + + /* Blocking signal to make sure that signal will be handled by main thread */ + sigset_t set; + sigemptyset(&set); +#ifdef SIGTERM + sigaddset(&set, SIGTERM); +#endif +#ifdef SIGHUP + sigaddset(&set, SIGHUP); +#endif +#ifdef SIGINT + sigaddset(&set, SIGINT); +#endif + if (pthread_sigmask(SIG_BLOCK, &set, NULL) != 0) { + i_errno = IEPTHREADSIGMASK; + goto cleanup_and_fail; + } + + /* Allow this thread to be cancelled even if it's in a syscall */ + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + + phdr = (struct bounceback_header *)sp->buffer; + + /* loop of reading bounceback message and writing its reply */ + while (! (test->done) && ! (sp->done)) { + + /* reading bounceback reply */ + r = Nread(sp->socket, sp->buffer, test->settings->bounceback_size, test->protocol->id); + if (r < 0) { + if (r == NET_SOFTERROR) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback receive failed on NET_SOFTERROR. errno=%s\n", strerror(errno)); + continue; + } + } + if (r != test->settings->bounceback_size) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback receive read only %d bytes\n", r); + continue; + } + if (test->debug_level >= DEBUG_LEVEL_INFO) { + iperf_printf(test, "iperf_server_bounceback_worker_run: received message burst=%d, index=%d\n", + ntohl(phdr->bb_burst_id), ntohl(phdr->bb_index_in_burst)); + } + + /* Add server's data */ + iperf_time_now(&now); // Receive time + phdr->bb_server_rx_ts.secs = htonl(now.secs); + phdr->bb_server_rx_ts.usecs = htonl(now.usecs); + iperf_time_now(&now); // Sent time + phdr->bb_server_tx_ts.secs = htonl(now.secs); + phdr->bb_server_tx_ts.usecs = htonl(now.usecs); + + /* writing bounceback response message */ + r = Nwrite(sp->socket, sp->buffer, test->settings->bounceback_response_size, test->protocol->id); + if (r < 0) { + if (r == NET_SOFTERROR) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback send failed on NET_SOFTERROR. errno=%s\n", strerror(errno)); + continue; + } + } + if (r != test->settings->bounceback_response_size) { + if (sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("Bounceback send wrote only %d bytes\n", r); + continue; + } + + } /* main while */ + + return NULL; + + cleanup_and_fail: + return NULL; +} + int iperf_server_listen(struct iperf_test *test) { @@ -522,8 +607,8 @@ int iperf_run_server(struct iperf_test *test) { int result, s; - int send_streams_accepted, rec_streams_accepted; - int streams_to_send = 0, streams_to_rec = 0; + int send_streams_accepted, rec_streams_accepted, bounceback_streams_accepted; + int streams_to_send = 0, streams_to_rec = 0, streams_bounceback = 0; #if defined(HAVE_TCP_CONGESTION) int saved_errno; #endif /* HAVE_TCP_CONGESTION */ @@ -539,6 +624,7 @@ iperf_run_server(struct iperf_test *test) int64_t t_usecs; int64_t timeout_us; int64_t rcv_timeout_us; + void *worker; if (test->logfile) { if (iperf_open_logfile(test) < 0) @@ -581,6 +667,7 @@ iperf_run_server(struct iperf_test *test) iperf_set_test_state(test, IPERF_START); send_streams_accepted = 0; rec_streams_accepted = 0; + bounceback_streams_accepted = 0; rcv_timeout_us = (test->settings->rcv_timeout.secs * SEC_TO_US) + test->settings->rcv_timeout.usecs; while (test->state != IPERF_DONE) { @@ -704,6 +791,9 @@ iperf_run_server(struct iperf_test *test) streams_to_send = test->num_streams; streams_to_rec = 0; } + if (test->settings->bounceback) { + streams_bounceback = 1; + } } } if (FD_ISSET(test->ctrl_sck, &read_set)) { @@ -813,6 +903,9 @@ iperf_run_server(struct iperf_test *test) } else if (send_streams_accepted != streams_to_send) { flag = 1; ++send_streams_accepted; + } else if (streams_bounceback > 0) { // Bounceback stream (last stream) + flag = 2; + ++bounceback_streams_accepted; } if (flag != -1) { @@ -834,7 +927,9 @@ iperf_run_server(struct iperf_test *test) } - if (rec_streams_accepted == streams_to_rec && send_streams_accepted == streams_to_send) { + if (rec_streams_accepted == streams_to_rec && send_streams_accepted == streams_to_send && + bounceback_streams_accepted == streams_bounceback) + { if (test->protocol->id != Ptcp) { FD_CLR(test->prot_listener, &test->read_set); close(test->prot_listener); @@ -904,13 +999,18 @@ iperf_run_server(struct iperf_test *test) }; SLIST_FOREACH(sp, &test->streams, streams) { - if (pthread_create(&(sp->thr), &attr, &iperf_server_worker_run, sp) != 0) { + if (sp->sender == 2) { // Bounceback + worker = &iperf_server_bounceback_worker_run; + } else { // Sender (1) or Receiver (0) + worker = &iperf_server_worker_run; + } + if (pthread_create(&(sp->thr), &attr, worker, sp) != 0) { i_errno = IEPTHREADCREATE; cleanup_server(test); return -1; } if (test->debug_level >= DEBUG_LEVEL_INFO) { - iperf_printf(test, "Thread FD %d created\n", sp->socket); + iperf_printf(test, "Thread FD %d created; Stream id %d with sender type %d\n", sp->socket, sp->id, sp->sender); } } if (test->debug_level >= DEBUG_LEVEL_INFO) {