21581a7216
This lets iperf work better with multi-homed machines and VRF. Fixes #1089. Based on a patch by Ben Greear <greearb@candelatech.com> via PR #817. Co-authored-by: Ben Greear <greearb@candelatech.com>
736 строки
19 KiB
C
736 строки
19 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <sys/time.h>
|
|
#include <sys/select.h>
|
|
#include <limits.h>
|
|
|
|
#ifdef HAVE_NETINET_SCTP_H
|
|
#include <netinet/sctp.h>
|
|
#endif /* HAVE_NETINET_SCTP_H */
|
|
|
|
#include "iperf.h"
|
|
#include "iperf_api.h"
|
|
#include "iperf_sctp.h"
|
|
#include "net.h"
|
|
|
|
|
|
|
|
/* iperf_sctp_recv
|
|
*
|
|
* receives the data for SCTP
|
|
*/
|
|
int
|
|
iperf_sctp_recv(struct iperf_stream *sp)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
int r;
|
|
|
|
r = Nread(sp->socket, sp->buffer, sp->settings->blksize, Psctp);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
/* Only count bytes received while we're in the correct state. */
|
|
if (sp->test->state == TEST_RUNNING) {
|
|
sp->result->bytes_received += r;
|
|
sp->result->bytes_received_this_interval += r;
|
|
}
|
|
else {
|
|
if (sp->test->debug)
|
|
printf("Late receive, state = %d\n", sp->test->state);
|
|
}
|
|
|
|
return r;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|
|
|
|
|
|
/* iperf_sctp_send
|
|
*
|
|
* sends the data for SCTP
|
|
*/
|
|
int
|
|
iperf_sctp_send(struct iperf_stream *sp)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
int r;
|
|
|
|
r = Nwrite(sp->socket, sp->buffer, sp->settings->blksize, Psctp);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
sp->result->bytes_sent += r;
|
|
sp->result->bytes_sent_this_interval += r;
|
|
|
|
return r;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|
|
|
|
|
|
|
|
/* iperf_sctp_accept
|
|
*
|
|
* accept a new SCTP stream connection
|
|
*/
|
|
int
|
|
iperf_sctp_accept(struct iperf_test * test)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
int s;
|
|
signed char rbuf = ACCESS_DENIED;
|
|
char cookie[COOKIE_SIZE];
|
|
socklen_t len;
|
|
struct sockaddr_storage addr;
|
|
|
|
len = sizeof(addr);
|
|
s = accept(test->listener, (struct sockaddr *) &addr, &len);
|
|
if (s < 0) {
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
|
|
if (Nread(s, cookie, COOKIE_SIZE, Psctp) < 0) {
|
|
i_errno = IERECVCOOKIE;
|
|
close(s);
|
|
return -1;
|
|
}
|
|
|
|
if (strncmp(test->cookie, cookie, COOKIE_SIZE) != 0) {
|
|
if (Nwrite(s, (char*) &rbuf, sizeof(rbuf), Psctp) < 0) {
|
|
i_errno = IESENDMESSAGE;
|
|
close(s);
|
|
return -1;
|
|
}
|
|
close(s);
|
|
}
|
|
|
|
return s;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|
|
|
|
|
|
/* iperf_sctp_listen
|
|
*
|
|
* start up a listener for SCTP stream connections
|
|
*/
|
|
int
|
|
iperf_sctp_listen(struct iperf_test *test)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
struct addrinfo hints, *res;
|
|
char portstr[6];
|
|
int s, opt, saved_errno;
|
|
|
|
close(test->listener);
|
|
|
|
snprintf(portstr, 6, "%d", test->server_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.
|
|
* More details in the comments in netanounce().
|
|
*/
|
|
if (test->settings->domain == AF_UNSPEC && !test->bind_address) {
|
|
hints.ai_family = AF_INET6;
|
|
} else {
|
|
hints.ai_family = test->settings->domain;
|
|
}
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
if ((gerror = getaddrinfo(test->bind_address, portstr, &hints, &res)) != 0) {
|
|
i_errno = IESTREAMLISTEN;
|
|
return -1;
|
|
}
|
|
|
|
if ((s = socket(res->ai_family, SOCK_STREAM, IPPROTO_SCTP)) < 0) {
|
|
freeaddrinfo(res);
|
|
i_errno = IESTREAMLISTEN;
|
|
return -1;
|
|
}
|
|
|
|
if ((opt = test->settings->socket_bufsize)) {
|
|
int saved_errno;
|
|
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(res);
|
|
errno = saved_errno;
|
|
i_errno = IESETBUF;
|
|
return -1;
|
|
}
|
|
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(res);
|
|
errno = saved_errno;
|
|
i_errno = IESETBUF;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (test->bind_dev) {
|
|
#if defined(SO_BINDTODEVICE)
|
|
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
|
|
test->bind_dev, IFNAMSIZ) < 0)
|
|
#endif // SO_BINDTODEVICE
|
|
{
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(res);
|
|
i_errno = IEBINDDEV;
|
|
errno = saved_errno;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
|
|
if (res->ai_family == AF_INET6 && (test->settings->domain == AF_UNSPEC ||
|
|
test->settings->domain == AF_INET6)) {
|
|
if (test->settings->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;
|
|
i_errno = IEPROTOCOL;
|
|
return -1;
|
|
}
|
|
}
|
|
#endif /* IPV6_V6ONLY */
|
|
|
|
opt = 1;
|
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(res);
|
|
errno = saved_errno;
|
|
i_errno = IEREUSEADDR;
|
|
return -1;
|
|
}
|
|
|
|
/* servers must call sctp_bindx() _instead_ of bind() */
|
|
if (!TAILQ_EMPTY(&test->xbind_addrs)) {
|
|
if (iperf_sctp_bindx(test, s, IPERF_SCTP_SERVER)) {
|
|
close(s);
|
|
freeaddrinfo(res);
|
|
return -1;
|
|
}
|
|
} else
|
|
if (bind(s, (struct sockaddr *) res->ai_addr, res->ai_addrlen) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(res);
|
|
errno = saved_errno;
|
|
i_errno = IESTREAMLISTEN;
|
|
return -1;
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
|
|
if (listen(s, INT_MAX) < 0) {
|
|
i_errno = IESTREAMLISTEN;
|
|
return -1;
|
|
}
|
|
|
|
test->listener = s;
|
|
|
|
return s;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|
|
|
|
|
|
/* iperf_sctp_connect
|
|
*
|
|
* connect to a SCTP stream listener
|
|
*/
|
|
int
|
|
iperf_sctp_connect(struct iperf_test *test)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
int s, opt, saved_errno;
|
|
char portstr[6];
|
|
struct addrinfo hints, *local_res = NULL, *server_res = NULL;
|
|
|
|
if (test->bind_address) {
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = test->settings->domain;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
if ((gerror = getaddrinfo(test->bind_address, NULL, &hints, &local_res)) != 0) {
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = test->settings->domain;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
snprintf(portstr, sizeof(portstr), "%d", test->server_port);
|
|
if ((gerror = getaddrinfo(test->server_hostname, portstr, &hints, &server_res)) != 0) {
|
|
if (test->bind_address)
|
|
freeaddrinfo(local_res);
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
|
|
s = socket(server_res->ai_family, SOCK_STREAM, IPPROTO_SCTP);
|
|
if (s < 0) {
|
|
freeaddrinfo(local_res);
|
|
freeaddrinfo(server_res);
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
|
|
if ((opt = test->settings->socket_bufsize)) {
|
|
int saved_errno;
|
|
if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETBUF;
|
|
return -1;
|
|
}
|
|
if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETBUF;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (test->bind_dev) {
|
|
#if defined(SO_BINDTODEVICE)
|
|
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
|
|
test->bind_dev, IFNAMSIZ) < 0)
|
|
#endif // SO_BINDTODEVICE
|
|
{
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(local_res);
|
|
freeaddrinfo(server_res);
|
|
i_errno = IEBINDDEV;
|
|
errno = saved_errno;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Various ways to bind the local end of the connection.
|
|
* 1. --bind (with or without --cport).
|
|
*/
|
|
if (test->bind_address) {
|
|
struct sockaddr_in *lcladdr;
|
|
lcladdr = (struct sockaddr_in *)local_res->ai_addr;
|
|
lcladdr->sin_port = htons(test->bind_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;
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
freeaddrinfo(local_res);
|
|
}
|
|
/* --cport, no --bind */
|
|
else if (test->bind_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(test->bind_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(test->bind_port);
|
|
lcladdr->sin6_addr = in6addr_any;
|
|
addrlen = sizeof(struct sockaddr_in6);
|
|
}
|
|
/* Unknown protocol */
|
|
else {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IEPROTOCOL;
|
|
return -1;
|
|
}
|
|
|
|
if (bind(s, (struct sockaddr *) &lcl, addrlen) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (test->no_delay != 0) {
|
|
opt = 1;
|
|
if (setsockopt(s, IPPROTO_SCTP, SCTP_NODELAY, &opt, sizeof(opt)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETNODELAY;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((test->settings->mss >= 512 && test->settings->mss <= 131072)) {
|
|
|
|
/*
|
|
* Some platforms use a struct sctp_assoc_value as the
|
|
* argument to SCTP_MAXSEG. Other (older API implementations)
|
|
* take an int. FreeBSD 10 and CentOS 6 support SCTP_MAXSEG,
|
|
* but OpenSolaris 11 doesn't.
|
|
*/
|
|
#ifdef HAVE_STRUCT_SCTP_ASSOC_VALUE
|
|
struct sctp_assoc_value av;
|
|
|
|
/*
|
|
* Some platforms support SCTP_FUTURE_ASSOC, others need to
|
|
* (equivalently) do 0 here. FreeBSD 10 is an example of the
|
|
* former, CentOS 6 Linux is an example of the latter.
|
|
*/
|
|
#ifdef SCTP_FUTURE_ASSOC
|
|
av.assoc_id = SCTP_FUTURE_ASSOC;
|
|
#else
|
|
av.assoc_id = 0;
|
|
#endif
|
|
av.assoc_value = test->settings->mss;
|
|
|
|
if (setsockopt(s, IPPROTO_SCTP, SCTP_MAXSEG, &av, sizeof(av)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETMSS;
|
|
return -1;
|
|
}
|
|
#else
|
|
opt = test->settings->mss;
|
|
|
|
/*
|
|
* Solaris might not support this option. If it doesn't work,
|
|
* ignore the error (at least for now).
|
|
*/
|
|
if (setsockopt(s, IPPROTO_SCTP, SCTP_MAXSEG, &opt, sizeof(opt)) < 0 &&
|
|
errno != ENOPROTOOPT) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETMSS;
|
|
return -1;
|
|
}
|
|
#endif /* HAVE_STRUCT_SCTP_ASSOC_VALUE */
|
|
}
|
|
|
|
if (test->settings->num_ostreams > 0) {
|
|
struct sctp_initmsg initmsg;
|
|
|
|
memset(&initmsg, 0, sizeof(struct sctp_initmsg));
|
|
initmsg.sinit_num_ostreams = test->settings->num_ostreams;
|
|
|
|
if (setsockopt(s, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(struct sctp_initmsg)) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETSCTPNSTREAM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* clients must call bind() followed by sctp_bindx() before connect() */
|
|
if (!TAILQ_EMPTY(&test->xbind_addrs)) {
|
|
if (iperf_sctp_bindx(test, s, IPERF_SCTP_CLIENT)) {
|
|
freeaddrinfo(server_res);
|
|
close(s);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* TODO support sctp_connectx() to avoid heartbeating. */
|
|
if (connect(s, (struct sockaddr *) server_res->ai_addr, server_res->ai_addrlen) < 0 && errno != EINPROGRESS) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESTREAMCONNECT;
|
|
return -1;
|
|
}
|
|
|
|
/* Send cookie for verification */
|
|
if (Nwrite(s, test->cookie, COOKIE_SIZE, Psctp) < 0) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESENDCOOKIE;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* We want to allow fragmentation. But there's at least one
|
|
* implementation (Solaris) that doesn't support this option,
|
|
* even though it defines SCTP_DISABLE_FRAGMENTS. So we have to
|
|
* try setting the option and ignore the error, if it doesn't
|
|
* work.
|
|
*/
|
|
opt = 0;
|
|
if (setsockopt(s, IPPROTO_SCTP, SCTP_DISABLE_FRAGMENTS, &opt, sizeof(opt)) < 0 &&
|
|
errno != ENOPROTOOPT) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
freeaddrinfo(server_res);
|
|
errno = saved_errno;
|
|
i_errno = IESETSCTPDISABLEFRAG;
|
|
return -1;
|
|
}
|
|
|
|
freeaddrinfo(server_res);
|
|
return s;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|
|
|
|
|
|
|
|
int
|
|
iperf_sctp_init(struct iperf_test *test)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
return 0;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|
|
|
|
|
|
|
|
/* iperf_sctp_bindx
|
|
*
|
|
* handle binding to multiple endpoints (-X parameters)
|
|
*/
|
|
int
|
|
iperf_sctp_bindx(struct iperf_test *test, int s, int is_server)
|
|
{
|
|
#if defined(HAVE_SCTP_H)
|
|
struct addrinfo hints;
|
|
char portstr[6];
|
|
char *servname;
|
|
struct addrinfo *ai, *ai0;
|
|
struct sockaddr *xaddrs;
|
|
struct xbind_entry *xbe, *xbe0;
|
|
char *bp;
|
|
size_t xaddrlen;
|
|
int nxaddrs;
|
|
int retval;
|
|
int domain;
|
|
int saved_errno;
|
|
|
|
domain = test->settings->domain;
|
|
xbe0 = NULL;
|
|
retval = 0;
|
|
|
|
if (TAILQ_EMPTY(&test->xbind_addrs))
|
|
return retval; /* nothing to do */
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = (domain == AF_UNSPEC ? AF_INET6 : domain);
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
servname = NULL;
|
|
if (is_server) {
|
|
hints.ai_flags |= AI_PASSIVE;
|
|
snprintf(portstr, 6, "%d", test->server_port);
|
|
servname = portstr;
|
|
}
|
|
|
|
/* client: must pop first -X address and call bind().
|
|
* sctp_bindx() must see the ephemeral port chosen by bind().
|
|
* we deliberately ignore the -B argument in this case.
|
|
*/
|
|
if (!is_server) {
|
|
struct sockaddr *sa;
|
|
struct sockaddr_in *sin;
|
|
struct sockaddr_in6 *sin6;
|
|
int eport;
|
|
|
|
xbe0 = TAILQ_FIRST(&test->xbind_addrs);
|
|
TAILQ_REMOVE(&test->xbind_addrs, xbe0, link);
|
|
|
|
if ((gerror = getaddrinfo(xbe0->name, servname, &hints, &xbe0->ai)) != 0) {
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
|
|
ai = xbe0->ai;
|
|
if (domain != AF_UNSPEC && domain != ai->ai_family) {
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
if (bind(s, (struct sockaddr *)ai->ai_addr, ai->ai_addrlen) < 0) {
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* if only one -X argument, nothing more to do */
|
|
if (TAILQ_EMPTY(&test->xbind_addrs))
|
|
goto out;
|
|
|
|
sa = (struct sockaddr *)ai->ai_addr;
|
|
if (sa->sa_family == AF_INET) {
|
|
sin = (struct sockaddr_in *)ai->ai_addr;
|
|
eport = sin->sin_port;
|
|
} else if (sa->sa_family == AF_INET6) {
|
|
sin6 = (struct sockaddr_in6 *)ai->ai_addr;
|
|
eport = sin6->sin6_port;
|
|
} else {
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
snprintf(portstr, 6, "%d", ntohs(eport));
|
|
servname = portstr;
|
|
}
|
|
|
|
/* pass 1: resolve and compute lengths. */
|
|
nxaddrs = 0;
|
|
xaddrlen = 0;
|
|
TAILQ_FOREACH(xbe, &test->xbind_addrs, link) {
|
|
if (xbe->ai != NULL)
|
|
freeaddrinfo(xbe->ai);
|
|
if ((gerror = getaddrinfo(xbe->name, servname, &hints, &xbe->ai)) != 0) {
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
ai0 = xbe->ai;
|
|
for (ai = ai0; ai; ai = ai->ai_next) {
|
|
if (domain != AF_UNSPEC && domain != ai->ai_family)
|
|
continue;
|
|
xaddrlen += ai->ai_addrlen;
|
|
++nxaddrs;
|
|
}
|
|
}
|
|
|
|
/* pass 2: copy into flat buffer. */
|
|
xaddrs = (struct sockaddr *)malloc(xaddrlen);
|
|
if (!xaddrs) {
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
bp = (char *)xaddrs;
|
|
TAILQ_FOREACH(xbe, &test->xbind_addrs, link) {
|
|
ai0 = xbe->ai;
|
|
for (ai = ai0; ai; ai = ai->ai_next) {
|
|
if (domain != AF_UNSPEC && domain != ai->ai_family)
|
|
continue;
|
|
memcpy(bp, ai->ai_addr, ai->ai_addrlen);
|
|
bp += ai->ai_addrlen;
|
|
}
|
|
}
|
|
|
|
if (sctp_bindx(s, xaddrs, nxaddrs, SCTP_BINDX_ADD_ADDR) == -1) {
|
|
saved_errno = errno;
|
|
close(s);
|
|
free(xaddrs);
|
|
errno = saved_errno;
|
|
i_errno = IESETSCTPBINDX;
|
|
retval = -1;
|
|
goto out;
|
|
}
|
|
|
|
free(xaddrs);
|
|
retval = 0;
|
|
|
|
out:
|
|
/* client: put head node back. */
|
|
if (!is_server && xbe0)
|
|
TAILQ_INSERT_HEAD(&test->xbind_addrs, xbe0, link);
|
|
|
|
TAILQ_FOREACH(xbe, &test->xbind_addrs, link) {
|
|
if (xbe->ai) {
|
|
freeaddrinfo(xbe->ai);
|
|
xbe->ai = NULL;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
#else
|
|
i_errno = IENOSCTP;
|
|
return -1;
|
|
#endif /* HAVE_SCTP_H */
|
|
}
|