521 lines
13 KiB
C
521 lines
13 KiB
C
/*
|
|
* iperf, Copyright (c) 2014-2019, 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"
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
|
|
/* 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 hints, *local_res = NULL, *server_res = NULL;
|
|
int s, saved_errno;
|
|
|
|
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;
|
|
if ((gerror = getaddrinfo(server, NULL, &hints, &server_res)) != 0)
|
|
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 {
|
|
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;
|
|
}
|
|
}
|
|
|
|
((struct sockaddr_in *) server_res->ai_addr)->sin_port = htons(port);
|
|
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;
|
|
|
|
while (nleft > 0) {
|
|
r = read(fd, buf, nleft);
|
|
if (r < 0) {
|
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
|
|
break;
|
|
else
|
|
return NET_HARDERROR;
|
|
} else if (r == 0)
|
|
break;
|
|
|
|
nleft -= r;
|
|
buf += r;
|
|
}
|
|
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)
|
|
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)
|
|
{
|
|
off_t offset;
|
|
#if defined(HAVE_SENDFILE)
|
|
#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)
|
|
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;
|
|
}
|