/* * 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_SCTP_H #include #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); test->listener = -1; 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 */ }