Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,34 @@ if test "x$iperf3_cv_header_dontfragment" = "xyes"; then
AC_DEFINE([HAVE_DONT_FRAGMENT], [1], [Have IP_MTU_DISCOVER/IP_DONTFRAG/IP_DONTFRAGMENT sockopt.])
fi

# Check for IP_PKTINFO (Linux only)
AC_CACHE_CHECK([IP_PKTINFO socket option],
[iperf3_cv_header_ip_pktinfo],
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <netinet/in.h>]],
[[int foo = IP_PKTINFO;]])],
iperf3_cv_header_ip_pktinfo=yes,
iperf3_cv_header_ip_pktinfo=no))
if test "x$iperf3_cv_header_ip_pktinfo" = "xyes"; then
AC_DEFINE([HAVE_IP_PKTINFO], [1], [Have IP_PKTINFO sockopt.])
fi

# Check for IP_RECVDSTADDR (BSD?)
AC_CACHE_CHECK([IP_RECVDSTADDR socket option],
[iperf3_cv_header_ip_recvdstaddr],
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([[#include <netinet/in.h>]],
[[int foo = IP_RECVDSTADDR;]])],
iperf3_cv_header_ip_recvdstaddr=yes,
iperf3_cv_header_ip_recvdstaddr=no))
if test "x$iperf3_cv_header_ip_recvdstaddr" = "xyes"; then
AC_DEFINE([HAVE_IP_RECVDSTADDR], [1], [Have IP_RECVDSTADDR sockopt.])
fi

if test "x$iperf3_cv_header_ip_pktinfo$iperf3_cv_header_ip_recvdstaddr" = "xnono"; then
AC_MSG_ERROR([neither IP_PKTINFO nor IP_RECVDSTADDR is available])
fi

#
# Check for tcpi_snd_wnd in struct tcp_info
#
Expand Down
96 changes: 76 additions & 20 deletions src/iperf_udp.c
Original file line number Diff line number Diff line change
Expand Up @@ -522,25 +522,91 @@ iperf_udp_gro(struct iperf_test *test, int s)
int
iperf_udp_accept(struct iperf_test *test)
{
struct sockaddr_storage sa_peer;
struct sockaddr_storage sa_peer, sa_local;
unsigned int buf;
socklen_t len;
socklen_t len, len_local;
int sz, s;
int rc;

/*
* Get the current outstanding socket. This socket will be used to handle
* data transfers and a new "listening" socket will be created.
*/
s = test->prot_listener;
char cbuffer[
#ifdef HAVE_IP_PKTINFO
CMSG_SPACE(sizeof (struct in_pktinfo)
> sizeof (struct in6_pktinfo)?
sizeof (struct in_pktinfo):
sizeof (struct in6_pktinfo))
#elif defined(HAVE_IP_RECVDSTADDR)
CMSG_SPACE(sizeof (struct in_addr)
> sizeof (struct in6_pktinfo)?
sizeof (struct in_addr):
sizeof (struct in6_pktinfo))
#endif
];
struct cmsghdr *cm;
struct msghdr msg;
struct iovec iov;

/*
* Grab the UDP packet sent by the client. From that we can extract the
* client's address, and then use that information to bind the remote side
* of the socket to the client.
*/
len = sizeof(sa_peer);
if ((sz = recvfrom(test->prot_listener, &buf, sizeof(buf), 0, (struct sockaddr *) &sa_peer, &len)) < 0) {
len = len_local = sizeof(sa_peer);

memset(cbuffer, 0, sizeof cbuffer);

msg.msg_name = &sa_peer;
msg.msg_namelen = len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cbuffer;
msg.msg_controllen = sizeof cbuffer;
msg.msg_flags = 0;

iov.iov_base = &buf;
iov.iov_len = sizeof buf;

/* Get the address of the local socket to determine the domain and port. */
if (getsockname(test->prot_listener, (struct sockaddr *) &sa_local,
&len_local) < 0) {
i_errno = IESTREAMACCEPT;
return -1;
}

if ((sz = recvmsg(test->prot_listener, &msg, 0)) < 0) {
i_errno = IESTREAMACCEPT;
return -1;
}

/* Parse the retrieved ancillary data and retrieve the incoming
packet's destination address. */
for (cm = CMSG_FIRSTHDR(&msg); cm != NULL; cm = CMSG_NXTHDR(&msg, cm))
switch (sa_local.ss_family) {
case AF_INET:
#ifdef HAVE_IP_PKTINFO
if (cm->cmsg_level == IPPROTO_IP && cm->cmsg_type == IP_PKTINFO
&& cm->cmsg_len >= CMSG_LEN(sizeof (struct in_pktinfo)))
memcpy(&((struct sockaddr_in *) &sa_local)->sin_addr,
CMSG_DATA(cm) + offsetof(struct in_pktinfo, ipi_addr),
sizeof (struct in_addr));
#elif defined(HAVE_IP_RECVDSTADDR)
if (cm->cmsg_level == IPPROTO_IP && cm->cmsg_type == IP_RECVDSTADDR
&& cm->cmsg_len >= CMSG_LEN(sizeof (struct in_addr)))
memcpy(&((struct sockaddr_in *) &sa_local)->sin_addr,
CMSG_DATA(cm), sizeof (struct in_addr));
#endif
break;
case AF_INET6:
if (cm->cmsg_level == IPPROTO_IPV6 && cm->cmsg_type == IPV6_PKTINFO
&& cm->cmsg_len >= CMSG_LEN(sizeof (struct in6_pktinfo)))
memcpy(&((struct sockaddr_in6 *) &sa_local)->sin6_addr,
CMSG_DATA(cm) + offsetof(struct in6_pktinfo, ipi6_addr),
sizeof (struct in6_addr));
}

/* Open and bind the session socket. */
s = netannounce_sockaddr(test->settings->domain, Pudp,
(struct sockaddr *) &sa_local, len_local,
test->bind_dev, 0);
if (s < 0) {
i_errno = IESTREAMACCEPT;
return -1;
}
Expand Down Expand Up @@ -602,16 +668,6 @@ 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);
if (test->prot_listener < 0) {
i_errno = IESTREAMLISTEN;
return -1;
}

FD_SET(test->prot_listener, &test->read_set);
test->max_fd = (test->max_fd < test->prot_listener) ? test->prot_listener : test->max_fd;

Expand Down
112 changes: 71 additions & 41 deletions src/net.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,52 +290,22 @@ netdial(int domain, int proto, const char *local, const char *bind_dev, int loca
/***************************************************************/

int
netannounce(int domain, int proto, const char *local, const char *bind_dev, int port)
netannounce_sockaddr(int domain, int proto, const struct sockaddr *local,
socklen_t local_len, const char *bind_dev, int is_listen)
{
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)
s = socket(local->sa_family, proto, 0);
if (s < 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)
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;
}
Expand All @@ -346,7 +316,6 @@ netannounce(int domain, int proto, const char *local, const char *bind_dev, int
(char *) &opt, sizeof(opt)) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
Expand All @@ -359,7 +328,7 @@ netannounce(int domain, int proto, const char *local, const char *bind_dev, int
* 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 (local->sa_family == AF_INET6 && (domain == AF_UNSPEC || domain == AF_INET6)) {
if (domain == AF_UNSPEC)
opt = 0;
else
Expand All @@ -368,22 +337,38 @@ netannounce(int domain, int proto, const char *local, const char *bind_dev, int
(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) {
/* Request the passing of in_pktinfo/in6_pktinfo as ancillary data. */
if (is_listen && proto == SOCK_DGRAM) {
opt = 1;
if (setsockopt(s, local->sa_family == AF_INET6?
IPPROTO_IPV6: IPPROTO_IP,
local->sa_family == AF_INET6?
IPV6_RECVPKTINFO:
#ifdef HAVE_IP_PKTINFO
IP_PKTINFO,
#elif defined(HAVE_IP_RECVDSTADDR)
IP_RECVDSTADDR,
#endif
&opt, sizeof opt) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(res);
errno = saved_errno;
return -1;
}
}

freeaddrinfo(res);
if (bind(s, local, local_len) < 0) {
saved_errno = errno;
close(s);
errno = saved_errno;
return -1;
}

if (proto == SOCK_STREAM) {
if (listen(s, INT_MAX) < 0) {
Expand All @@ -397,6 +382,51 @@ netannounce(int domain, int proto, const char *local, const char *bind_dev, int
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;

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 = netannounce_sockaddr(domain, proto,
(struct sockaddr *) res->ai_addr,
res->ai_addrlen, bind_dev, 1);
if (s < 0) {
freeaddrinfo(res);
return -1;
}

freeaddrinfo(res);

return s;
}

/*******************************************************************/
/* Nread - reads 'count' bytes from a socket */
/********************************************************************/
Expand Down
1 change: 1 addition & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
int timeout_connect(int s, const struct sockaddr *name, socklen_t namelen, int timeout);
int create_socket(int domain, int type, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, struct addrinfo **server_res_out);
int netdial(int domain, int proto, const char *local, const char *bind_dev, int local_port, const char *server, int port, int timeout);
int netannounce_sockaddr(int domain, int proto, const struct sockaddr *local, socklen_t local_len, const char *bind_dev, int is_listen);
int netannounce(int domain, int proto, const char *local, const char *bind_dev, int port);
int Nread(int fd, char *buf, size_t count, int prot);
int Nrecv(int fd, char *buf, size_t count, int prot, int sock_opt);
Expand Down
Loading