#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>
#include "iperf.h"
#include "iperf_api.h"
#include "iperf_sctp.h"
#include "net.h"
int
iperf_sctp_recv(struct iperf_stream *sp)
{
#if defined(HAVE_SCTP_H)
int r;
r = Nread_no_select(sp->socket, sp->buffer, sp->settings->blksize, Psctp);
if (r < 0)
return r;
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
}
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
}
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
}
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);
test->listener = -1;
snprintf(portstr, 6, "%d", test->server_port);
memset(&hints, 0, sizeof(hints));
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 (bind_to_device(s, res->ai_family, test->bind_dev) < 0) {
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
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;
}
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
}
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 (bind_to_device(s, server_res->ai_family, test->bind_dev) < 0) {
saved_errno = errno;
close(s);
freeaddrinfo(local_res);
freeaddrinfo(server_res);
i_errno = IEBINDDEV;
errno = saved_errno;
return -1;
}
}
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);
}
else if (test->bind_port) {
size_t addrlen;
struct sockaddr_storage lcl;
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);
}
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);
}
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)) {
#ifdef HAVE_STRUCT_SCTP_ASSOC_VALUE
struct sctp_assoc_value av;
#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;
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
}
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;
}
}
if (!TAILQ_EMPTY(&test->xbind_addrs)) {
if (iperf_sctp_bindx(test, s, IPERF_SCTP_CLIENT)) {
freeaddrinfo(server_res);
close(s);
return -1;
}
}
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;
}
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;
}
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
}
int
iperf_sctp_init(struct iperf_test *test)
{
#if defined(HAVE_SCTP_H)
return 0;
#else
i_errno = IENOSCTP;
return -1;
#endif
}
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;
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;
}
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 (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;
}
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;
}
}
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:
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
}
int
iperf_sctp_get_info(struct iperf_stream *sp, struct iperf_sctp_info *sctp_info)
{
#if defined(HAVE_SCTP_H)
struct sctp_status status;
socklen_t len;
sctp_assoc_t assoc_id;
int rc = 0;
if (sp->test->protocol->id != Psctp) {
rc = -1;
} else {
#ifdef SCTP_FUTURE_ASSOC
assoc_id = SCTP_FUTURE_ASSOC;
#else
assoc_id = 0;
#endif
len = sizeof(status);
rc = sctp_opt_info(sp->socket, assoc_id, SCTP_STATUS, &status, &len);
if (rc < 0) {
if (sp->test->debug_level >= DEBUG_LEVEL_ERROR)
iperf_err(sp->test, "sctp_opt_info get SCTP_STATUS for socket %d failed with errno %d - %s", sp->socket, errno, strerror(errno));
} else {
sctp_info->wnd = status.sstat_rwnd;
sctp_info->rtt = status.sstat_primary.spinfo_srtt;
sctp_info->pmtu = status.sstat_primary.spinfo_mtu;
sctp_info->cwnd = status.sstat_primary.spinfo_cwnd;
}
}
return rc;
#else
return -1;
#endif
}