summaryrefslogtreecommitdiffstats
path: root/src/net.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/net.c')
-rw-r--r--src/net.c607
1 files changed, 607 insertions, 0 deletions
diff --git a/src/net.c b/src/net.c
new file mode 100644
index 0000000..c82caff
--- /dev/null
+++ b/src/net.c
@@ -0,0 +1,607 @@
+/*
+ * iperf, Copyright (c) 2014-2023, 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.
+ *
+ * If you have questions about your rights to use or distribute this
+ * software, please contact Berkeley Lab's Technology Transfer
+ * Department at TTD@lbl.gov.
+ *
+ * NOTICE. This software is owned by the U.S. Department of Energy.
+ * As such, the U.S. Government has been granted for itself and others
+ * acting on its behalf a paid-up, nonexclusive, irrevocable,
+ * worldwide license in the Software to reproduce, prepare derivative
+ * works, and perform publicly and display publicly. Beginning five
+ * (5) years after the date permission to assert copyright is obtained
+ * from the U.S. Department of Energy, and subject to any subsequent
+ * five (5) year renewals, the U.S. Government is granted for itself
+ * and others acting on its behalf a paid-up, nonexclusive,
+ * irrevocable, worldwide license in the Software to reproduce,
+ * prepare derivative works, distribute copies to the public, perform
+ * publicly and display publicly, and to permit others to do so.
+ *
+ * This code is distributed under a BSD style license, see the LICENSE
+ * file for complete information.
+ */
+#include "iperf_config.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <assert.h>
+#include <netdb.h>
+#include <string.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#ifdef HAVE_SENDFILE
+#ifdef linux
+#include <sys/sendfile.h>
+#else
+#ifdef __FreeBSD__
+#include <sys/uio.h>
+#else
+#if defined(__APPLE__) && defined(__MACH__) /* OS X */
+#include <AvailabilityMacros.h>
+#if defined(MAC_OS_X_VERSION_10_6)
+#include <sys/uio.h>
+#endif
+#endif
+#endif
+#endif
+#endif /* HAVE_SENDFILE */
+
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif /* HAVE_POLL_H */
+
+#include "iperf.h"
+#include "iperf_util.h"
+#include "net.h"
+#include "timer.h"
+
+static int nread_read_timeout = 10;
+static int nread_overall_timeout = 30;
+
+/*
+ * Declaration of gerror in iperf_error.c. Most other files in iperf3 can get this
+ * by including "iperf.h", but net.c lives "below" this layer. Clearly the
+ * presence of this declaration is a sign we need to revisit this layering.
+ */
+extern int gerror;
+
+/*
+ * timeout_connect adapted from netcat, via OpenBSD and FreeBSD
+ * Copyright (c) 2001 Eric Jackson <ericj@monkey.org>
+ */
+int
+timeout_connect(int s, const struct sockaddr *name, socklen_t namelen,
+ int timeout)
+{
+ struct pollfd pfd;
+ socklen_t optlen;
+ int flags, optval;
+ int ret;
+
+ flags = 0;
+ if (timeout != -1) {
+ flags = fcntl(s, F_GETFL, 0);
+ if (fcntl(s, F_SETFL, flags | O_NONBLOCK) == -1)
+ return -1;
+ }
+
+ if ((ret = connect(s, name, namelen)) != 0 && errno == EINPROGRESS) {
+ pfd.fd = s;
+ pfd.events = POLLOUT;
+ if ((ret = poll(&pfd, 1, timeout)) == 1) {
+ optlen = sizeof(optval);
+ if ((ret = getsockopt(s, SOL_SOCKET, SO_ERROR,
+ &optval, &optlen)) == 0) {
+ errno = optval;
+ ret = optval == 0 ? 0 : -1;
+ }
+ } else if (ret == 0) {
+ errno = ETIMEDOUT;
+ ret = -1;
+ } else
+ ret = -1;
+ }
+
+ if (timeout != -1 && fcntl(s, F_SETFL, flags) == -1)
+ ret = -1;
+
+ return (ret);
+}
+
+/* netdial and netannouce code comes from libtask: http://swtch.com/libtask/
+ * Copyright: http://swtch.com/libtask/COPYRIGHT
+*/
+
+/* create a socket */
+int
+create_socket(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, struct addrinfo **server_res_out)
+{
+ struct addrinfo hints, *local_res = NULL, *server_res = NULL;
+ int s, saved_errno;
+ char portstr[6];
+
+ if (local) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = domain;
+ hints.ai_socktype = proto;
+ if ((gerror = getaddrinfo(local, NULL, &hints, &local_res)) != 0)
+ return -1;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = domain;
+ hints.ai_socktype = proto;
+ snprintf(portstr, sizeof(portstr), "%d", port);
+ if ((gerror = getaddrinfo(server, portstr, &hints, &server_res)) != 0) {
+ if (local)
+ freeaddrinfo(local_res);
+ return -1;
+ }
+
+ s = socket(server_res->ai_family, proto, 0);
+ if (s < 0) {
+ if (local)
+ freeaddrinfo(local_res);
+ freeaddrinfo(server_res);
+ return -1;
+ }
+
+ if (bind_dev) {
+#if defined(HAVE_SO_BINDTODEVICE)
+ if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
+ bind_dev, IFNAMSIZ) < 0)
+#endif // HAVE_SO_BINDTODEVICE
+ {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(local_res);
+ freeaddrinfo(server_res);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ /* Bind the local address if given a name (with or without --cport) */
+ if (local) {
+ if (local_port) {
+ struct sockaddr_in *lcladdr;
+ lcladdr = (struct sockaddr_in *)local_res->ai_addr;
+ lcladdr->sin_port = htons(local_port);
+ }
+
+ if (bind(s, (struct sockaddr *) local_res->ai_addr, local_res->ai_addrlen) < 0) {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(local_res);
+ freeaddrinfo(server_res);
+ errno = saved_errno;
+ return -1;
+ }
+ freeaddrinfo(local_res);
+ }
+ /* No local name, but --cport given */
+ else if (local_port) {
+ size_t addrlen;
+ struct sockaddr_storage lcl;
+
+ /* IPv4 */
+ if (server_res->ai_family == AF_INET) {
+ struct sockaddr_in *lcladdr = (struct sockaddr_in *) &lcl;
+ lcladdr->sin_family = AF_INET;
+ lcladdr->sin_port = htons(local_port);
+ lcladdr->sin_addr.s_addr = INADDR_ANY;
+ addrlen = sizeof(struct sockaddr_in);
+ }
+ /* IPv6 */
+ else if (server_res->ai_family == AF_INET6) {
+ struct sockaddr_in6 *lcladdr = (struct sockaddr_in6 *) &lcl;
+ lcladdr->sin6_family = AF_INET6;
+ lcladdr->sin6_port = htons(local_port);
+ lcladdr->sin6_addr = in6addr_any;
+ addrlen = sizeof(struct sockaddr_in6);
+ }
+ /* Unknown protocol */
+ else {
+ close(s);
+ freeaddrinfo(server_res);
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ if (bind(s, (struct sockaddr *) &lcl, addrlen) < 0) {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(server_res);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ *server_res_out = server_res;
+ return s;
+}
+
+/* make connection to server */
+int
+netdial(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, int timeout)
+{
+ struct addrinfo *server_res = NULL;
+ int s, saved_errno;
+
+ s = create_socket(domain, proto, local, bind_dev, local_port, server, port, &server_res);
+ if (s < 0) {
+ return -1;
+ }
+
+ if (timeout_connect(s, (struct sockaddr *) server_res->ai_addr, server_res->ai_addrlen, timeout) < 0 && errno != EINPROGRESS) {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(server_res);
+ errno = saved_errno;
+ return -1;
+ }
+
+ freeaddrinfo(server_res);
+ return s;
+}
+
+/***************************************************************/
+
+int
+netannounce(int domain, int proto, const char *local, const char *bind_dev, int port)
+{
+ struct addrinfo hints, *res;
+ char portstr[6];
+ int s, opt, saved_errno;
+
+ snprintf(portstr, 6, "%d", port);
+ memset(&hints, 0, sizeof(hints));
+ /*
+ * If binding to the wildcard address with no explicit address
+ * family specified, then force us to get an AF_INET6 socket. On
+ * CentOS 6 and MacOS, getaddrinfo(3) with AF_UNSPEC in ai_family,
+ * and ai_flags containing AI_PASSIVE returns a result structure
+ * with ai_family set to AF_INET, with the result that we create
+ * and bind an IPv4 address wildcard address and by default, we
+ * can't accept IPv6 connections.
+ *
+ * On FreeBSD, under the above circumstances, ai_family in the
+ * result structure is set to AF_INET6.
+ */
+ if (domain == AF_UNSPEC && !local) {
+ hints.ai_family = AF_INET6;
+ }
+ else {
+ hints.ai_family = domain;
+ }
+ hints.ai_socktype = proto;
+ hints.ai_flags = AI_PASSIVE;
+ if ((gerror = getaddrinfo(local, portstr, &hints, &res)) != 0)
+ return -1;
+
+ s = socket(res->ai_family, proto, 0);
+ if (s < 0) {
+ freeaddrinfo(res);
+ return -1;
+ }
+
+ if (bind_dev) {
+#if defined(HAVE_SO_BINDTODEVICE)
+ if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
+ bind_dev, IFNAMSIZ) < 0)
+#endif // HAVE_SO_BINDTODEVICE
+ {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(res);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ opt = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &opt, sizeof(opt)) < 0) {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(res);
+ errno = saved_errno;
+ return -1;
+ }
+ /*
+ * If we got an IPv6 socket, figure out if it should accept IPv4
+ * connections as well. We do that if and only if no address
+ * family was specified explicitly. Note that we can only
+ * do this if the IPV6_V6ONLY socket option is supported. Also,
+ * OpenBSD explicitly omits support for IPv4-mapped addresses,
+ * even though it implements IPV6_V6ONLY.
+ */
+#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
+ if (res->ai_family == AF_INET6 && (domain == AF_UNSPEC || domain == AF_INET6)) {
+ if (domain == AF_UNSPEC)
+ opt = 0;
+ else
+ opt = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *) &opt, sizeof(opt)) < 0) {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(res);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+#endif /* IPV6_V6ONLY */
+
+ if (bind(s, (struct sockaddr *) res->ai_addr, res->ai_addrlen) < 0) {
+ saved_errno = errno;
+ close(s);
+ freeaddrinfo(res);
+ errno = saved_errno;
+ return -1;
+ }
+
+ freeaddrinfo(res);
+
+ if (proto == SOCK_STREAM) {
+ if (listen(s, INT_MAX) < 0) {
+ saved_errno = errno;
+ close(s);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ return s;
+}
+
+
+/*******************************************************************/
+/* reads 'count' bytes from a socket */
+/********************************************************************/
+
+int
+Nread(int fd, char *buf, size_t count, int prot)
+{
+ register ssize_t r;
+ register size_t nleft = count;
+ struct iperf_time ftimeout = { 0, 0 };
+
+ fd_set rfdset;
+ struct timeval timeout = { nread_read_timeout, 0 };
+
+ /*
+ * fd might not be ready for reading on entry. Check for this
+ * (with timeout) first.
+ *
+ * This check could go inside the while() loop below, except we're
+ * currently considering whether it might make sense to support a
+ * codepath that bypassese this check, for situations where we
+ * already know that fd has data on it (for example if we'd gotten
+ * to here as the result of a select() call.
+ */
+ {
+ FD_ZERO(&rfdset);
+ FD_SET(fd, &rfdset);
+ r = select(fd + 1, &rfdset, NULL, NULL, &timeout);
+ if (r < 0) {
+ return NET_HARDERROR;
+ }
+ if (r == 0) {
+ return 0;
+ }
+ }
+
+ while (nleft > 0) {
+ r = read(fd, buf, nleft);
+ if (r < 0) {
+ /* XXX EWOULDBLOCK can't happen without non-blocking sockets */
+ if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+ else
+ return NET_HARDERROR;
+ } else if (r == 0)
+ break;
+
+ nleft -= r;
+ buf += r;
+
+ /*
+ * We need some more bytes but don't want to wait around
+ * forever for them. In the case of partial results, we need
+ * to be able to read some bytes every nread_timeout seconds.
+ */
+ if (nleft > 0) {
+ struct iperf_time now;
+
+ /*
+ * Also, we have an approximate upper limit for the total time
+ * that a Nread call is supposed to take. We trade off accuracy
+ * of this timeout for a hopefully lower performance impact.
+ */
+ iperf_time_now(&now);
+ if (ftimeout.secs == 0) {
+ ftimeout = now;
+ iperf_time_add_usecs(&ftimeout, nread_overall_timeout * 1000000L);
+ }
+ if (iperf_time_compare(&ftimeout, &now) < 0) {
+ break;
+ }
+
+ FD_ZERO(&rfdset);
+ FD_SET(fd, &rfdset);
+ r = select(fd + 1, &rfdset, NULL, NULL, &timeout);
+ if (r < 0) {
+ return NET_HARDERROR;
+ }
+ if (r == 0) {
+ break;
+ }
+ }
+ }
+ return count - nleft;
+}
+
+
+/*
+ * N W R I T E
+ */
+
+int
+Nwrite(int fd, const char *buf, size_t count, int prot)
+{
+ register ssize_t r;
+ register size_t nleft = count;
+
+ while (nleft > 0) {
+ r = write(fd, buf, nleft);
+ if (r < 0) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if (EAGAIN != EWOULDBLOCK)
+ /* XXX EWOULDBLOCK can't happen without non-blocking sockets */
+ case EWOULDBLOCK:
+#endif
+ return count - nleft;
+
+ case ENOBUFS:
+ return NET_SOFTERROR;
+
+ default:
+ return NET_HARDERROR;
+ }
+ } else if (r == 0)
+ return NET_SOFTERROR;
+ nleft -= r;
+ buf += r;
+ }
+ return count;
+}
+
+
+int
+has_sendfile(void)
+{
+#if defined(HAVE_SENDFILE)
+ return 1;
+#else /* HAVE_SENDFILE */
+ return 0;
+#endif /* HAVE_SENDFILE */
+
+}
+
+
+/*
+ * N S E N D F I L E
+ */
+
+int
+Nsendfile(int fromfd, int tofd, const char *buf, size_t count)
+{
+#if defined(HAVE_SENDFILE)
+ off_t offset;
+#if defined(__FreeBSD__) || (defined(__APPLE__) && defined(__MACH__) && defined(MAC_OS_X_VERSION_10_6))
+ off_t sent;
+#endif
+ register size_t nleft;
+ register ssize_t r;
+
+ nleft = count;
+ while (nleft > 0) {
+ offset = count - nleft;
+#ifdef linux
+ r = sendfile(tofd, fromfd, &offset, nleft);
+ if (r > 0)
+ nleft -= r;
+#elif defined(__FreeBSD__)
+ r = sendfile(fromfd, tofd, offset, nleft, NULL, &sent, 0);
+ nleft -= sent;
+#elif defined(__APPLE__) && defined(__MACH__) && defined(MAC_OS_X_VERSION_10_6) /* OS X */
+ sent = nleft;
+ r = sendfile(fromfd, tofd, offset, &sent, NULL, 0);
+ nleft -= sent;
+#else
+ /* Shouldn't happen. */
+ r = -1;
+ errno = ENOSYS;
+#endif
+ if (r < 0) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if (EAGAIN != EWOULDBLOCK)
+ /* XXX EWOULDBLOCK can't happen without non-blocking sockets */
+ case EWOULDBLOCK:
+#endif
+ if (count == nleft)
+ return NET_SOFTERROR;
+ return count - nleft;
+
+ case ENOBUFS:
+ case ENOMEM:
+ return NET_SOFTERROR;
+
+ default:
+ return NET_HARDERROR;
+ }
+ }
+#ifdef linux
+ else if (r == 0)
+ return NET_SOFTERROR;
+#endif
+ }
+ return count;
+#else /* HAVE_SENDFILE */
+ errno = ENOSYS; /* error if somehow get called without HAVE_SENDFILE */
+ return NET_HARDERROR;
+#endif /* HAVE_SENDFILE */
+}
+
+/*************************************************************************/
+
+int
+setnonblocking(int fd, int nonblocking)
+{
+ int flags, newflags;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ perror("fcntl(F_GETFL)");
+ return -1;
+ }
+ if (nonblocking)
+ newflags = flags | (int) O_NONBLOCK;
+ else
+ newflags = flags & ~((int) O_NONBLOCK);
+ if (newflags != flags)
+ if (fcntl(fd, F_SETFL, newflags) < 0) {
+ perror("fcntl(F_SETFL)");
+ return -1;
+ }
+ return 0;
+}
+
+/****************************************************************************/
+
+int
+getsockdomain(int sock)
+{
+ struct sockaddr_storage sa;
+ socklen_t len = sizeof(sa);
+
+ if (getsockname(sock, (struct sockaddr *)&sa, &len) < 0) {
+ return -1;
+ }
+ return ((struct sockaddr *) &sa)->sa_family;
+}