--- embedaddon/iperf/src/iperf_udp.c 2016/10/18 13:28:18 1.1.1.1 +++ embedaddon/iperf/src/iperf_udp.c 2023/09/27 11:14:54 1.1.1.3 @@ -1,5 +1,5 @@ /* - * iperf, Copyright (c) 2014, 2016, The Regents of the University of + * iperf, Copyright (c) 2014-2022, The Regents of the University of * California, through Lawrence Berkeley National Laboratory (subject * to receipt of any required approvals from the U.S. Dept. of * Energy). All rights reserved. @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -45,8 +46,21 @@ #include "iperf_udp.h" #include "timer.h" #include "net.h" +#include "cjson.h" #include "portable_endian.h" +#if defined(HAVE_INTTYPES_H) +# include +#else +# ifndef PRIu64 +# if sizeof(long) == 8 +# define PRIu64 "lu" +# else +# define PRIu64 "llu" +# endif +# endif +#endif + /* iperf_udp_recv * * receives the data for UDP @@ -58,8 +72,9 @@ iperf_udp_recv(struct iperf_stream *sp) uint64_t pcount; int r; int size = sp->settings->blksize; + int first_packet = 0; double transit = 0, d = 0; - struct timeval sent_time, arrival_time; + struct iperf_time sent_time, arrival_time, temp_time; r = Nread(sp->socket, sp->buffer, size, Pudp); @@ -71,57 +86,120 @@ iperf_udp_recv(struct iperf_stream *sp) if (r <= 0) return r; - sp->result->bytes_received += r; - sp->result->bytes_received_this_interval += r; + /* Only count bytes received while we're in the correct state. */ + if (sp->test->state == TEST_RUNNING) { - if (sp->test->udp_counters_64bit) { - memcpy(&sec, sp->buffer, sizeof(sec)); - memcpy(&usec, sp->buffer+4, sizeof(usec)); - memcpy(&pcount, sp->buffer+8, sizeof(pcount)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = be64toh(pcount); - sent_time.tv_sec = sec; - sent_time.tv_usec = usec; - } - else { - uint32_t pc; - memcpy(&sec, sp->buffer, sizeof(sec)); - memcpy(&usec, sp->buffer+4, sizeof(usec)); - memcpy(&pc, sp->buffer+8, sizeof(pc)); - sec = ntohl(sec); - usec = ntohl(usec); - pcount = ntohl(pc); - sent_time.tv_sec = sec; - sent_time.tv_usec = usec; - } + /* + * For jitter computation below, it's important to know if this + * packet is the first packet received. + */ + if (sp->result->bytes_received == 0) { + first_packet = 1; + } - /* Out of order packets */ - if (pcount >= sp->packet_count + 1) { - if (pcount > sp->packet_count + 1) { - sp->cnt_error += (pcount - 1) - sp->packet_count; - } - sp->packet_count = pcount; - } else { - sp->outoforder_packets++; - iperf_err(sp->test, "OUT OF ORDER - incoming packet = %zu and received packet = %d AND SP = %d", pcount, sp->packet_count, sp->socket); - } + sp->result->bytes_received += r; + sp->result->bytes_received_this_interval += r; - /* jitter measurement */ - gettimeofday(&arrival_time, NULL); + /* Dig the various counters out of the incoming UDP packet */ + if (sp->test->udp_counters_64bit) { + memcpy(&sec, sp->buffer, sizeof(sec)); + memcpy(&usec, sp->buffer+4, sizeof(usec)); + memcpy(&pcount, sp->buffer+8, sizeof(pcount)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = be64toh(pcount); + sent_time.secs = sec; + sent_time.usecs = usec; + } + else { + uint32_t pc; + memcpy(&sec, sp->buffer, sizeof(sec)); + memcpy(&usec, sp->buffer+4, sizeof(usec)); + memcpy(&pc, sp->buffer+8, sizeof(pc)); + sec = ntohl(sec); + usec = ntohl(usec); + pcount = ntohl(pc); + sent_time.secs = sec; + sent_time.usecs = usec; + } - transit = timeval_diff(&sent_time, &arrival_time); - d = transit - sp->prev_transit; - if (d < 0) - d = -d; - sp->prev_transit = transit; - // XXX: This is NOT the way to calculate jitter - // J = |(R1 - S1) - (R0 - S0)| [/ number of packets, for average] - sp->jitter += (d - sp->jitter) / 16.0; + if (sp->test->debug_level >= DEBUG_LEVEL_DEBUG) + fprintf(stderr, "pcount %" PRIu64 " packet_count %" PRIu64 "\n", pcount, sp->packet_count); - if (sp->test->debug) { - fprintf(stderr, "packet_count %d\n", sp->packet_count); + /* + * Try to handle out of order packets. The way we do this + * uses a constant amount of storage but might not be + * correct in all cases. In particular we seem to have the + * assumption that packets can't be duplicated in the network, + * because duplicate packets will possibly cause some problems here. + * + * First figure out if the sequence numbers are going forward. + * Note that pcount is the sequence number read from the packet, + * and sp->packet_count is the highest sequence number seen so + * far (so we're expecting to see the packet with sequence number + * sp->packet_count + 1 arrive next). + */ + if (pcount >= sp->packet_count + 1) { + + /* Forward, but is there a gap in sequence numbers? */ + if (pcount > sp->packet_count + 1) { + /* There's a gap so count that as a loss. */ + sp->cnt_error += (pcount - 1) - sp->packet_count; + } + /* Update the highest sequence number seen so far. */ + sp->packet_count = pcount; + } else { + + /* + * Sequence number went backward (or was stationary?!?). + * This counts as an out-of-order packet. + */ + sp->outoforder_packets++; + + /* + * If we have lost packets, then the fact that we are now + * seeing an out-of-order packet offsets a prior sequence + * number gap that was counted as a loss. So we can take + * away a loss. + */ + if (sp->cnt_error > 0) + sp->cnt_error--; + + /* Log the out-of-order packet */ + if (sp->test->debug) + fprintf(stderr, "OUT OF ORDER - incoming packet sequence %" PRIu64 " but expected sequence %" PRIu64 " on stream %d", pcount, sp->packet_count + 1, sp->socket); + } + + /* + * jitter measurement + * + * This computation is based on RFC 1889 (specifically + * sections 6.3.1 and A.8). + * + * Note that synchronized clocks are not required since + * the source packet delta times are known. Also this + * computation does not require knowing the round-trip + * time. + */ + iperf_time_now(&arrival_time); + + iperf_time_diff(&arrival_time, &sent_time, &temp_time); + transit = iperf_time_in_secs(&temp_time); + + /* Hack to handle the first packet by initializing prev_transit. */ + if (first_packet) + sp->prev_transit = transit; + + d = transit - sp->prev_transit; + if (d < 0) + d = -d; + sp->prev_transit = transit; + sp->jitter += (d - sp->jitter) / 16.0; } + else { + if (sp->test->debug) + printf("Late receive, state = %d\n", sp->test->state); + } return r; } @@ -136,9 +214,9 @@ iperf_udp_send(struct iperf_stream *sp) { int r; int size = sp->settings->blksize; - struct timeval before; + struct iperf_time before; - gettimeofday(&before, 0); + iperf_time_now(&before); ++sp->packet_count; @@ -147,37 +225,47 @@ iperf_udp_send(struct iperf_stream *sp) uint32_t sec, usec; uint64_t pcount; - sec = htonl(before.tv_sec); - usec = htonl(before.tv_usec); + sec = htonl(before.secs); + usec = htonl(before.usecs); pcount = htobe64(sp->packet_count); - + memcpy(sp->buffer, &sec, sizeof(sec)); memcpy(sp->buffer+4, &usec, sizeof(usec)); memcpy(sp->buffer+8, &pcount, sizeof(pcount)); - + } else { uint32_t sec, usec, pcount; - sec = htonl(before.tv_sec); - usec = htonl(before.tv_usec); + sec = htonl(before.secs); + usec = htonl(before.usecs); pcount = htonl(sp->packet_count); - + memcpy(sp->buffer, &sec, sizeof(sec)); memcpy(sp->buffer+4, &usec, sizeof(usec)); memcpy(sp->buffer+8, &pcount, sizeof(pcount)); - + } r = Nwrite(sp->socket, sp->buffer, size, Pudp); - if (r < 0) - return r; + if (r <= 0) { + --sp->packet_count; /* Don't count messages that no data was sent from them. + * Allows "resending" a massage with the same numbering */ + if (r < 0) { + if (r == NET_SOFTERROR && sp->test->debug_level >= DEBUG_LEVEL_INFO) + printf("UDP send failed on NET_SOFTERROR. errno=%s\n", strerror(errno)); + return r; + } + } sp->result->bytes_sent += r; sp->result->bytes_sent_this_interval += r; + if (sp->test->debug_level >= DEBUG_LEVEL_DEBUG) + printf("sent %d bytes of %d, total %" PRIu64 "\n", r, sp->settings->blksize, sp->result->bytes_sent); + return r; } @@ -194,6 +282,99 @@ iperf_udp_send(struct iperf_stream *sp) */ /* + * Set and verify socket buffer sizes. + * Return 0 if no error, -1 if an error, +1 if socket buffers are + * potentially too small to hold a message. + */ +int +iperf_udp_buffercheck(struct iperf_test *test, int s) +{ + int rc = 0; + int sndbuf_actual, rcvbuf_actual; + + /* + * Set socket buffer size if requested. Do this for both sending and + * receiving so that we can cover both normal and --reverse operation. + */ + int opt; + socklen_t optlen; + + if ((opt = test->settings->socket_bufsize)) { + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) { + i_errno = IESETBUF; + return -1; + } + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) { + i_errno = IESETBUF; + return -1; + } + } + + /* Read back and verify the sender socket buffer size */ + optlen = sizeof(sndbuf_actual); + if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) { + i_errno = IESETBUF; + return -1; + } + if (test->debug) { + printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize); + } + if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) { + i_errno = IESETBUF2; + return -1; + } + if (test->settings->blksize > sndbuf_actual) { + char str[WARN_STR_LEN]; + snprintf(str, sizeof(str), + "Block size %d > sending socket buffer size %d", + test->settings->blksize, sndbuf_actual); + warning(str); + rc = 1; + } + + /* Read back and verify the receiver socket buffer size */ + optlen = sizeof(rcvbuf_actual); + if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) { + i_errno = IESETBUF; + return -1; + } + if (test->debug) { + printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize); + } + if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) { + i_errno = IESETBUF2; + return -1; + } + if (test->settings->blksize > rcvbuf_actual) { + char str[WARN_STR_LEN]; + snprintf(str, sizeof(str), + "Block size %d > receiving socket buffer size %d", + test->settings->blksize, rcvbuf_actual); + warning(str); + rc = 1; + } + + if (test->json_output) { + cJSON *sock_bufsize_item = cJSON_GetObjectItem(test->json_start, "sock_bufsize"); + if (sock_bufsize_item == NULL) { + cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize); + } + + cJSON *sndbuf_actual_item = cJSON_GetObjectItem(test->json_start, "sndbuf_actual"); + if (sndbuf_actual_item == NULL) { + cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual); + } + + cJSON *rcvbuf_actual_item = cJSON_GetObjectItem(test->json_start, "rcvbuf_actual"); + if (rcvbuf_actual_item == NULL) { + cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual); + } + } + + return rc; +} + +/* * iperf_udp_accept * * Accepts a new UDP "connection" @@ -202,9 +383,10 @@ int iperf_udp_accept(struct iperf_test *test) { struct sockaddr_storage sa_peer; - int buf; + unsigned int buf; socklen_t len; int sz, s; + int rc; /* * Get the current outstanding socket. This socket will be used to handle @@ -228,43 +410,57 @@ iperf_udp_accept(struct iperf_test *test) return -1; } + /* Check and set socket buffer sizes */ + rc = iperf_udp_buffercheck(test, s); + if (rc < 0) + /* error */ + return rc; /* - * Set socket buffer size if requested. Do this for both sending and - * receiving so that we can cover both normal and --reverse operation. + * If the socket buffer was too small, but it was the default + * size, then try explicitly setting it to something larger. */ - int opt; - if ((opt = test->settings->socket_bufsize)) { - if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) { - i_errno = IESETBUF; - return -1; - } - if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) { - i_errno = IESETBUF; - return -1; - } + if (rc > 0) { + if (test->settings->socket_bufsize == 0) { + char str[WARN_STR_LEN]; + int bufsize = test->settings->blksize + UDP_BUFFER_EXTRA; + snprintf(str, sizeof(str), "Increasing socket buffer size to %d", + bufsize); + warning(str); + test->settings->socket_bufsize = bufsize; + rc = iperf_udp_buffercheck(test, s); + if (rc < 0) + return rc; + } } #if defined(HAVE_SO_MAX_PACING_RATE) - /* If socket pacing is available and not disabled, try it. */ - if (! test->no_fq_socket_pacing) { + /* If socket pacing is specified, try it. */ + if (test->settings->fqrate) { /* Convert bits per second to bytes per second */ - unsigned int rate = test->settings->rate / 8; - if (rate > 0) { + unsigned int fqrate = test->settings->fqrate / 8; + if (fqrate > 0) { if (test->debug) { - printf("Setting fair-queue socket pacing to %u\n", rate); + printf("Setting fair-queue socket pacing to %u\n", fqrate); } - if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &rate, sizeof(rate)) < 0) { - warning("Unable to set socket pacing, using application pacing instead"); - test->no_fq_socket_pacing = 1; + if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) { + warning("Unable to set socket pacing"); } } } #endif /* HAVE_SO_MAX_PACING_RATE */ + { + unsigned int rate = test->settings->rate / 8; + if (rate > 0) { + if (test->debug) { + printf("Setting application pacing to %u\n", rate); + } + } + } /* * Create a new "listening" socket to replace the one we were using before. */ - test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->server_port); + test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port); if (test->prot_listener < 0) { i_errno = IESTREAMLISTEN; return -1; @@ -274,7 +470,7 @@ iperf_udp_accept(struct iperf_test *test) 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 = 987654321; /* any content will work here */ + buf = UDP_CONNECT_REPLY; if (write(s, &buf, sizeof(buf)) < 0) { i_errno = IESTREAMWRITE; return -1; @@ -296,7 +492,7 @@ iperf_udp_listen(struct iperf_test *test) { int s; - if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->server_port)) < 0) { + if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port)) < 0) { i_errno = IESTREAMLISTEN; return -1; } @@ -316,50 +512,70 @@ iperf_udp_listen(struct iperf_test *test) int iperf_udp_connect(struct iperf_test *test) { - int s, buf, sz; + int s, sz; + unsigned int buf; #ifdef SO_RCVTIMEO struct timeval tv; #endif + int rc; + 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_port, test->server_hostname, test->server_port)) < 0) { + if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, -1)) < 0) { i_errno = IESTREAMCONNECT; return -1; } + /* Check and set socket buffer sizes */ + rc = iperf_udp_buffercheck(test, s); + if (rc < 0) + /* error */ + return rc; /* - * Set socket buffer size if requested. Do this for both sending and - * receiving so that we can cover both normal and --reverse operation. + * If the socket buffer was too small, but it was the default + * size, then try explicitly setting it to something larger. */ - int opt; - if ((opt = test->settings->socket_bufsize)) { - if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) { - i_errno = IESETBUF; - return -1; - } - if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) { - i_errno = IESETBUF; - return -1; - } + if (rc > 0) { + if (test->settings->socket_bufsize == 0) { + char str[WARN_STR_LEN]; + int bufsize = test->settings->blksize + UDP_BUFFER_EXTRA; + snprintf(str, sizeof(str), "Increasing socket buffer size to %d", + bufsize); + warning(str); + test->settings->socket_bufsize = bufsize; + rc = iperf_udp_buffercheck(test, s); + if (rc < 0) + return rc; + } } #if defined(HAVE_SO_MAX_PACING_RATE) /* If socket pacing is available and not disabled, try it. */ - if (! test->no_fq_socket_pacing) { + if (test->settings->fqrate) { /* Convert bits per second to bytes per second */ - unsigned int rate = test->settings->rate / 8; - if (rate > 0) { + unsigned int fqrate = test->settings->fqrate / 8; + if (fqrate > 0) { if (test->debug) { - printf("Setting fair-queue socket pacing to %u\n", rate); + printf("Setting fair-queue socket pacing to %u\n", fqrate); } - if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &rate, sizeof(rate)) < 0) { - warning("Unable to set socket pacing, using application pacing instead"); - test->no_fq_socket_pacing = 1; + if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) { + warning("Unable to set socket pacing"); } } } #endif /* HAVE_SO_MAX_PACING_RATE */ + { + unsigned int rate = test->settings->rate / 8; + if (rate > 0) { + if (test->debug) { + printf("Setting application pacing to %u\n", rate); + } + } + } + /* Set common socket options */ + iperf_common_sockopts(test, s); + #ifdef SO_RCVTIMEO /* 30 sec timeout for a case when there is a network problem. */ tv.tv_sec = 30; @@ -371,17 +587,35 @@ iperf_udp_connect(struct iperf_test *test) * Write a datagram to the UDP stream to let the server know we're here. * The server learns our address by obtaining its peer's address. */ - buf = 123456789; /* this can be pretty much anything */ + buf = UDP_CONNECT_MSG; + if (test->debug) { + printf("Sending Connect message to Socket %d\n", s); + } if (write(s, &buf, sizeof(buf)) < 0) { - // XXX: Should this be changed to IESTREAMCONNECT? + // XXX: Should this be changed to IESTREAMCONNECT? i_errno = IESTREAMWRITE; return -1; } /* - * Wait until the server replies back to us. + * Wait until the server replies back to us with the "accept" response. */ - if ((sz = recv(s, &buf, sizeof(buf), 0)) < 0) { + i = 0; + max_len_wait_for_reply = sizeof(buf); + if (test->reverse) /* In reverse mode allow few packets to have the "accept" response - to handle out of order packets */ + max_len_wait_for_reply += MAX_REVERSE_OUT_OF_ORDER_PACKETS * test->settings->blksize; + do { + if ((sz = recv(s, &buf, sizeof(buf), 0)) < 0) { + i_errno = IESTREAMREAD; + 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); + + if (buf != UDP_CONNECT_REPLY && buf != LEGACY_UDP_CONNECT_REPLY) { i_errno = IESTREAMREAD; return -1; }