/*      $KAME: sctp_sys_calls.c,v 1.10 2005/03/06 16:04:16 itojun Exp $ */
/*      $NetBSD: sctp_sys_calls.c,v 1.2 2024/01/20 14:52:48 christos Exp $ */

/*
* Copyright (C) 2002, 2003, 2004 Cisco Systems Inc,
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/sctp_uio.h>
#include <netinet/sctp.h>

#include <net/if_dl.h>

#ifndef IN6_IS_ADDR_V4MAPPED
#define IN6_IS_ADDR_V4MAPPED(a)              \
       ((*(const u_int32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \
        (*(const u_int32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \
        (*(const u_int32_t *)(const void *)(&(a)->s6_addr[8]) == ntohl(0x0000ffff)))
#endif

#define SCTP_CONTROL_VEC_SIZE_RCV       16384

#ifdef SCTP_DEBUG_PRINT_ADDRESS
static void
SCTPPrintAnAddress(struct sockaddr *a)
{
       char stringToPrint[256];
       u_short prt;
       char *srcaddr, *txt;

       if (a == NULL) {
               printf("NULL\n");
               return;
       }
       if (a->sa_family == AF_INET) {
               srcaddr = (char *)&((struct sockaddr_in *)a)->sin_addr;
               txt = "IPv4 Address: ";
               prt = ntohs(((struct sockaddr_in *)a)->sin_port);
       } else if (a->sa_family == AF_INET6) {
               srcaddr = (char *)&((struct sockaddr_in6 *)a)->sin6_addr;
               prt = ntohs(((struct sockaddr_in6 *)a)->sin6_port);
               txt = "IPv6 Address: ";
       } else if (a->sa_family == AF_LINK) {
               int i;
               char tbuf[200];
               u_char adbuf[200];
               struct sockaddr_dl *dl;

               dl = (struct sockaddr_dl *)a;
               strncpy(tbuf, dl->sdl_data, dl->sdl_nlen);
               tbuf[dl->sdl_nlen] = 0;
               printf("Intf:%s (len:%d)Interface index:%d type:%x(%d) ll-len:%d ",
                   tbuf, dl->sdl_nlen, dl->sdl_index, dl->sdl_type,
                   dl->sdl_type, dl->sdl_alen);
               memcpy(adbuf, LLADDR(dl), dl->sdl_alen);
               for (i = 0; i < dl->sdl_alen; i++){
                       printf("%2.2x", adbuf[i]);
                       if (i < (dl->sdl_alen - 1))
                               printf(":");
               }
               printf("\n");
       /*      u_short sdl_route[16];*/        /* source routing information */
               return;
       } else {
               return;
       }
       if (inet_ntop(a->sa_family, srcaddr, stringToPrint,
           sizeof(stringToPrint))) {
               if (a->sa_family == AF_INET6) {
                       printf("%s%s:%d scope:%d\n", txt, stringToPrint, prt,
                           ((struct sockaddr_in6 *)a)->sin6_scope_id);
               } else {
                       printf("%s%s:%d\n", txt, stringToPrint, prt);
               }

       } else {
               printf("%s unprintable?\n", txt);
       }
}
#endif /* SCTP_DEBUG_PRINT_ADDRESS */

void
in6_sin6_2_sin(struct sockaddr_in *sin, struct sockaddr_in6 *sin6)
{
       memset(sin, 0, sizeof(*sin));
       sin->sin_len = sizeof(struct sockaddr_in);
       sin->sin_family = AF_INET;
       sin->sin_port = sin6->sin6_port;
       sin->sin_addr.s_addr = sin6->sin6_addr.__u6_addr.__u6_addr32[3];
}

int
sctp_connectx(int sd, struct sockaddr *addrs, int addrcnt,
               sctp_assoc_t *id)
{
       int i, ret, cnt;
       struct sockaddr *at;
       struct sctp_connectx_addrs sca;
#if 0
       char *cpto;
#endif
       size_t len;

       at = addrs;
       cnt = 0;
       len = 0;
       /* validate all the addresses and get the size */
       for (i = 0; i < addrcnt; i++) {
               if (at->sa_family == AF_INET) {
                       len += at->sa_len;
               } else if (at->sa_family == AF_INET6){
                       if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)(void *)at)->sin6_addr)){
                               len += sizeof(struct sockaddr_in);
#if 0
                               in6_sin6_2_sin((struct sockaddr_in *)cpto,
                                   (struct sockaddr_in6 *)at);
                               cpto = ((caddr_t)cpto + sizeof(struct sockaddr_in));
                               len += sizeof(struct sockaddr_in);
#endif
                       } else {
                               len += at->sa_len;
                       }
               } else {
                       errno = EINVAL;
                       return (-1);
               }
               at = (struct sockaddr *)((caddr_t)at + at->sa_len);
               cnt++;
       }
       /* do we have any? */
       if (cnt == 0) {
               errno = EINVAL;
               return(-1);
       }

       sca.cx_num = cnt;
       sca.cx_len = (int)len;
       sca.cx_addrs = addrs;
       ret = ioctl(sd, SIOCCONNECTX, (void *)&sca);
       if ((ret == 0) && (id != NULL)) {
               memcpy(id, &sca.cx_num, sizeof(sctp_assoc_t));
       }
       return (ret);
}

int
sctp_bindx(int sd, struct sockaddr *addrs, int addrcnt, int flags)
{
       struct sctp_getaddresses *gaddrs;
       struct sockaddr *sa;
       int i, sz, fam, argsz;

       if ((flags != SCTP_BINDX_ADD_ADDR) &&
           (flags != SCTP_BINDX_REM_ADDR)) {
               errno = EFAULT;
               return(-1);
       }
       argsz = (sizeof(struct sockaddr_storage) +
           sizeof(struct sctp_getaddresses));
       gaddrs = (struct sctp_getaddresses *)calloc(1, argsz);
       if (gaddrs == NULL) {
               errno = ENOMEM;
               return(-1);
       }
       gaddrs->sget_assoc_id = 0;
       sa = addrs;
       for (i = 0; i < addrcnt; i++) {
               sz = sa->sa_len;
               fam = sa->sa_family;
               ((struct sockaddr_in *)(void *)&addrs[i])->sin_port = ((struct sockaddr_in *)(void *)sa)->sin_port;
               if ((fam != AF_INET) && (fam != AF_INET6)) {
                       errno = EINVAL;
                       return(-1);
               }
               memcpy(gaddrs->addr, sa, sz);
               if (setsockopt(sd, IPPROTO_SCTP, flags, gaddrs,
                   (unsigned int)argsz) != 0) {
                       free(gaddrs);
                       return(-1);
               }
               memset(gaddrs, 0, argsz);
               sa = (struct sockaddr *)((caddr_t)sa + sz);
       }
       free(gaddrs);
       return(0);
}


int
sctp_opt_info(int sd, sctp_assoc_t id, int opt, void *arg, socklen_t *size)
{
       if ((opt == SCTP_RTOINFO) ||
           (opt == SCTP_ASSOCINFO) ||
           (opt == SCTP_PRIMARY_ADDR) ||
           (opt == SCTP_SET_PEER_PRIMARY_ADDR) ||
           (opt == SCTP_PEER_ADDR_PARAMS) ||
           (opt == SCTP_STATUS) ||
           (opt == SCTP_GET_PEER_ADDR_INFO)) {
               *(sctp_assoc_t *)arg = id;
               return(getsockopt2(sd, IPPROTO_SCTP, opt, arg, size));
       } else {
               errno = EOPNOTSUPP;
               return(-1);
       }
}

int
sctp_getpaddrs(int sd, sctp_assoc_t id, struct sockaddr **raddrs)
{
       struct sctp_getaddresses *addrs;
       struct sockaddr *sa;
       struct sockaddr *re;
       sctp_assoc_t asoc;
       caddr_t lim;
       unsigned int siz;
       int cnt;

       if (raddrs == NULL) {
               errno = EFAULT;
               return(-1);
       }
       asoc = id;
       siz = sizeof(sctp_assoc_t);
       if (getsockopt2(sd, IPPROTO_SCTP, SCTP_GET_REMOTE_ADDR_SIZE,
           &asoc, &siz) != 0) {
               return(-1);
       }
       siz = (unsigned int)asoc;
       siz += sizeof(struct sctp_getaddresses);
       addrs = calloc((unsigned long)1, (unsigned long)siz);
       if (addrs == NULL) {
               errno = ENOMEM;
               return(-1);
       }
       memset(addrs, 0, (size_t)siz);
       addrs->sget_assoc_id = id;
       /* Now lets get the array of addresses */
       if (getsockopt2(sd, IPPROTO_SCTP, SCTP_GET_PEER_ADDRESSES,
           addrs, &siz) != 0) {
               free(addrs);
               return(-1);
       }
       re = (struct sockaddr *)&addrs->addr[0];
       *raddrs = re;
       cnt = 0;
       sa = (struct sockaddr *)&addrs->addr[0];
       lim = (caddr_t)addrs + siz;
       while ((caddr_t)sa < lim) {
               cnt++;
               sa = (struct sockaddr *)((caddr_t)sa + sa->sa_len);
               if (sa->sa_len == 0)
                       break;
       }
       return(cnt);
}

void sctp_freepaddrs(struct sockaddr *addrs)
{
       /* Take away the hidden association id */
       void *fr_addr;
       fr_addr = (void *)((caddr_t)addrs - sizeof(sctp_assoc_t));
       /* Now free it */
       free(fr_addr);
}

int
sctp_getladdrs (int sd, sctp_assoc_t id, struct sockaddr **raddrs)
{
       struct sctp_getaddresses *addrs;
       struct sockaddr *re;
       caddr_t lim;
       struct sockaddr *sa;
       int size_of_addresses;
       socklen_t siz;
       int cnt;

       if (raddrs == NULL) {
               errno = EFAULT;
               return(-1);
       }
       size_of_addresses = 0;
       siz = sizeof(int);
       if (getsockopt2(sd, IPPROTO_SCTP, SCTP_GET_LOCAL_ADDR_SIZE,
           &size_of_addresses, &siz) != 0) {
               return(-1);
       }
       if (size_of_addresses == 0) {
               errno = ENOTCONN;
               return(-1);
       }
       siz = (socklen_t)(size_of_addresses + sizeof(struct sockaddr_storage));
       siz += sizeof(struct sctp_getaddresses);
       addrs = calloc((unsigned long)1, (unsigned long)siz);
       if (addrs == NULL) {
               errno = ENOMEM;
               return(-1);
       }
       memset(addrs, 0, (size_t)siz);
       addrs->sget_assoc_id = id;
       /* Now lets get the array of addresses */
       if (getsockopt2(sd, IPPROTO_SCTP, SCTP_GET_LOCAL_ADDRESSES, addrs,
           &siz) != 0) {
               free(addrs);
               return(-1);
       }
       re = (struct sockaddr *)&addrs->addr[0];
       *raddrs = re;
       cnt = 0;
       sa = (struct sockaddr *)&addrs->addr[0];
       lim = (caddr_t)addrs + siz;
       while ((caddr_t)sa < lim) {
               cnt++;
               sa = (struct sockaddr *)((caddr_t)sa + sa->sa_len);
               if (sa->sa_len == 0)
                       break;
       }
       return(cnt);
}

void sctp_freeladdrs(struct sockaddr *addrs)
{
       /* Take away the hidden association id */
       void *fr_addr;
       fr_addr = (void *)((caddr_t)addrs - sizeof(sctp_assoc_t));
       /* Now free it */
       free(fr_addr);
}

ssize_t
sctp_sendmsg(int s,
            const void *data,
            size_t len,
            const struct sockaddr *to,
            socklen_t tolen __attribute__((unused)),
            u_int32_t ppid,
            u_int32_t flags,
            u_int16_t stream_no,
            u_int32_t timetolive,
            u_int32_t context)
{
       ssize_t sz;
       struct msghdr msg;
       struct iovec iov[2];
       char controlVector[256];
       struct sctp_sndrcvinfo *s_info;
       struct cmsghdr *cmsg;
       struct sockaddr *who=NULL;
       union {
               struct sockaddr_in in;
               struct sockaddr_in6 in6;
       } addr;

#if 0
       fprintf(io, "sctp_sendmsg(sd:%d, data:%x, len:%d, to:%x, tolen:%d, ppid:%x, flags:%x str:%d ttl:%d ctx:%x\n",
           s, (u_int)data, (int)len, (u_int)to, (int)tolen, ppid, flags,
           (int)stream_no, (int)timetolive, (u_int)context);
       fflush(io);
#endif
       if (to) {
               if (to->sa_len == 0) {
                       /*
                        * For the lazy app, that did not
                        * set sa_len, we attempt to set for them.
                        */
                       switch (to->sa_family) {
                       case AF_INET:
                               memcpy(&addr, to, sizeof(struct sockaddr_in));
                               addr.in.sin_len = sizeof(struct sockaddr_in);
                               break;
                       case AF_INET6:
                               memcpy(&addr, to, sizeof(struct sockaddr_in6));
                               addr.in6.sin6_len = sizeof(struct sockaddr_in6);
                               break;
                       default:
                               errno = EAFNOSUPPORT;
                               return -1;
                       }
               } else {
                       memcpy (&addr, to, to->sa_len);
               }
               who = (struct sockaddr *)(void *)&addr;
       }
       iov[0].iov_base = (void *)(unsigned long)data;
       iov[0].iov_len = len;
       iov[1].iov_base = NULL;
       iov[1].iov_len = 0;

       if (to) {
               msg.msg_name = (caddr_t)who;
               msg.msg_namelen = who->sa_len;
       } else {
               msg.msg_name = (caddr_t)NULL;
               msg.msg_namelen = 0;
       }
       msg.msg_iov = iov;
       msg.msg_iovlen = 1;
       msg.msg_control = (caddr_t)controlVector;

       cmsg = (struct cmsghdr *)controlVector;

       cmsg->cmsg_level = IPPROTO_SCTP;
       cmsg->cmsg_type = SCTP_SNDRCV;
       cmsg->cmsg_len = CMSG_LEN (sizeof(struct sctp_sndrcvinfo) );
       s_info = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);

       s_info->sinfo_stream = stream_no;
       s_info->sinfo_ssn = 0;
       s_info->sinfo_flags = flags;
       s_info->sinfo_ppid = ppid;
       s_info->sinfo_context = context;
       s_info->sinfo_assoc_id = 0;
       s_info->sinfo_timetolive = timetolive;
       errno = 0;
       msg.msg_controllen = cmsg->cmsg_len;
       sz = sendmsg(s, &msg, 0);
       return(sz);
}

sctp_assoc_t
sctp_getassocid(int sd, struct sockaddr *sa)
{
       struct sctp_paddrparams sp;
       socklen_t siz;

       /* First get the assoc id */
       siz = sizeof(struct sctp_paddrparams);
       memset(&sp, 0, sizeof(sp));
       memcpy((caddr_t)&sp.spp_address, sa, sa->sa_len);
       errno = 0;
       if (getsockopt2(sd, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &sp, &siz) != 0)
               return((sctp_assoc_t)0);
       /* We depend on the fact that 0 can never be returned */
       return(sp.spp_assoc_id);
}



ssize_t
sctp_send(int sd, const void *data, size_t len,
         const struct sctp_sndrcvinfo *sinfo,
         int flags)
{
       ssize_t sz;
       struct msghdr msg;
       struct iovec iov[2];
       struct sctp_sndrcvinfo *s_info;
       char controlVector[256];
       struct cmsghdr *cmsg;

       iov[0].iov_base = (void *)(unsigned long)data;
       iov[0].iov_len = len;
       iov[1].iov_base = NULL;
       iov[1].iov_len = 0;

       msg.msg_name = 0;
       msg.msg_namelen = 0;
       msg.msg_iov = iov;
       msg.msg_iovlen = 1;
       msg.msg_control = (caddr_t)controlVector;

       cmsg = (struct cmsghdr *)controlVector;

       cmsg->cmsg_level = IPPROTO_SCTP;
       cmsg->cmsg_type = SCTP_SNDRCV;
       cmsg->cmsg_len = CMSG_LEN (sizeof(struct sctp_sndrcvinfo) );
       s_info = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
       /* copy in the data */
       *s_info = *sinfo;
       errno = 0;
       msg.msg_controllen = cmsg->cmsg_len;
       sz = sendmsg(sd, &msg, flags);
       return(sz);
}


ssize_t
sctp_sendx(int sd, const void *msg, size_t len,
          struct sockaddr *addrs, int addrcnt,
          struct sctp_sndrcvinfo *sinfo,
          int flags)
{
       int i, cnt, saved_errno;
       ssize_t ret;
       int add_len;
       struct sockaddr *at;
       struct sctp_connectx_addrs sca;

       len = 0;
       at = addrs;
       cnt = 0;
       /* validate all the addresses and get the size */
       for (i = 0; i < addrcnt; i++) {
               if (at->sa_family == AF_INET) {
                       add_len = sizeof(struct sockaddr_in);
               } else if (at->sa_family == AF_INET6) {
                       add_len = sizeof(struct sockaddr_in6);
               } else {
                       errno = EINVAL;
                       return (-1);
               }
               len += add_len;
               at = (struct sockaddr *)((caddr_t)at + add_len);
               cnt++;
       }
       /* do we have any? */
       if (cnt == 0) {
               errno = EINVAL;
               return(-1);
       }

       sca.cx_num = cnt;
       sca.cx_len = (int)len;
       sca.cx_addrs = addrs;
       ret = ioctl(sd, SIOCCONNECTXDEL, (void *)&sca);
       if (ret != 0) {
               return(ret);
       }
       sinfo->sinfo_assoc_id = sctp_getassocid(sd, addrs);
       if (sinfo->sinfo_assoc_id == 0) {
               printf("Huh, can't get associd? TSNH!\n");
               (void)setsockopt(sd, IPPROTO_SCTP, SCTP_CONNECT_X_COMPLETE, (void *)addrs,
                                (unsigned int)addrs->sa_len);
               errno = ENOENT;
               return (-1);
       }
       ret = sctp_send(sd, msg, len, sinfo, flags);
       saved_errno = errno;
       (void)setsockopt(sd, IPPROTO_SCTP, SCTP_CONNECT_X_COMPLETE, (void *)addrs,
                        (unsigned int)addrs->sa_len);

       errno = saved_errno;
       return (ret);
}

ssize_t
sctp_sendmsgx(int sd,
             const void *msg,
             size_t len,
             struct sockaddr *addrs,
             int addrcnt,
             u_int32_t ppid,
             u_int32_t flags,
             u_int16_t stream_no,
             u_int32_t timetolive,
             u_int32_t context)
{
       struct sctp_sndrcvinfo sinfo;

       memset((void *) &sinfo, 0, sizeof(struct sctp_sndrcvinfo));
       sinfo.sinfo_ppid       = ppid;
       sinfo.sinfo_flags      = flags;
       sinfo.sinfo_ssn        = stream_no;
       sinfo.sinfo_timetolive = timetolive;
       sinfo.sinfo_context    = context;
       return sctp_sendx(sd, msg, len, addrs, addrcnt, &sinfo, 0);
}

ssize_t
sctp_recvmsg (int s,
             void *dbuf,
             size_t len,
             struct sockaddr *from,
             socklen_t *fromlen,
             struct sctp_sndrcvinfo *sinfo,
             int *msg_flags)
{
       struct sctp_sndrcvinfo *s_info;
       ssize_t sz;
       struct msghdr msg;
       struct iovec iov[2];
       char controlVector[2048];
       struct cmsghdr *cmsg;
       iov[0].iov_base = dbuf;
       iov[0].iov_len = len;
       iov[1].iov_base = NULL;
       iov[1].iov_len = 0;
       msg.msg_name = (caddr_t)from;
       msg.msg_namelen = *fromlen;
       msg.msg_iov = iov;
       msg.msg_iovlen = 1;
       msg.msg_control = (caddr_t)controlVector;
       msg.msg_controllen = sizeof(controlVector);
       errno = 0;
       sz = recvmsg(s, &msg, 0);

       s_info = NULL;
       len = sz;
       *msg_flags = msg.msg_flags;
       *fromlen = msg.msg_namelen;
       if ((msg.msg_controllen) && sinfo) {
               /* parse through and see if we find
                * the sctp_sndrcvinfo (if the user wants it).
                */
               cmsg = (struct cmsghdr *)controlVector;
               while (cmsg) {
                       if (cmsg->cmsg_level == IPPROTO_SCTP) {
                               if (cmsg->cmsg_type == SCTP_SNDRCV) {
                                       /* Got it */
                                       s_info = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
                                       /* Copy it to the user */
                                       *sinfo = *s_info;
                                       break;
                               }
                       }
                       cmsg = CMSG_NXTHDR(&msg, cmsg);
               }
       }
       return(sz);
}

ssize_t
sctp_recvv(int sd,
   const struct iovec *iov,
   int iovlen,
   struct sockaddr *from,
   socklen_t * fromlen,
   void *info,
   socklen_t * infolen,
   unsigned int *infotype,
   int *flags)
{
       char cmsgbuf[SCTP_CONTROL_VEC_SIZE_RCV];
       struct msghdr msg;
       struct cmsghdr *cmsg;
       ssize_t ret;
       struct sctp_rcvinfo *rcvinfo;
       struct sctp_nxtinfo *nxtinfo;

       if (((info != NULL) && (infolen == NULL)) ||
           ((info == NULL) && (infolen != NULL) && (*infolen != 0)) ||
           ((info != NULL) && (infotype == NULL))) {
               errno = EINVAL;
               return (-1);
       }
       if (infotype) {
               *infotype = SCTP_RECVV_NOINFO;
       }
       msg.msg_name = from;
       if (fromlen == NULL) {
               msg.msg_namelen = 0;
       } else {
               msg.msg_namelen = *fromlen;
       }
       msg.msg_iov = __UNCONST(iov);
       msg.msg_iovlen = iovlen;
       msg.msg_control = cmsgbuf;
       msg.msg_controllen = sizeof(cmsgbuf);
       msg.msg_flags = 0;
       ret = recvmsg(sd, &msg, *flags);
       *flags = msg.msg_flags;
       if ((ret > 0) &&
           (msg.msg_controllen > 0) &&
           (infotype != NULL) &&
           (infolen != NULL) &&
           (*infolen > 0)) {
               rcvinfo = NULL;
               nxtinfo = NULL;
               for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                       if (cmsg->cmsg_level != IPPROTO_SCTP) {
                               continue;
                       }
                       if (cmsg->cmsg_type == SCTP_RCVINFO) {
                               rcvinfo = (struct sctp_rcvinfo *)CMSG_DATA(cmsg);
                               if (nxtinfo != NULL) {
                                       break;
                               } else {
                                       continue;
                               }
                       }
                       if (cmsg->cmsg_type == SCTP_NXTINFO) {
                               nxtinfo = (struct sctp_nxtinfo *)CMSG_DATA(cmsg);
                               if (rcvinfo != NULL) {
                                       break;
                               } else {
                                       continue;
                               }
                       }
               }
               if (rcvinfo != NULL) {
                       if ((nxtinfo != NULL) && (*infolen >= sizeof(struct sctp_recvv_rn))) {
                               struct sctp_recvv_rn *rn_info;

                               rn_info = (struct sctp_recvv_rn *)info;
                               rn_info->recvv_rcvinfo = *rcvinfo;
                               rn_info->recvv_nxtinfo = *nxtinfo;
                               *infolen = (socklen_t) sizeof(struct sctp_recvv_rn);
                               *infotype = SCTP_RECVV_RN;
                       } else if (*infolen >= sizeof(struct sctp_rcvinfo)) {
                               memcpy(info, rcvinfo, sizeof(struct sctp_rcvinfo));
                               *infolen = (socklen_t) sizeof(struct sctp_rcvinfo);
                               *infotype = SCTP_RECVV_RCVINFO;
                       }
               } else if (nxtinfo != NULL) {
                       if (*infolen >= sizeof(struct sctp_nxtinfo)) {
                               memcpy(info, nxtinfo, sizeof(struct sctp_nxtinfo));
                               *infolen = (socklen_t) sizeof(struct sctp_nxtinfo);
                               *infotype = SCTP_RECVV_NXTINFO;
                       }
               }
       }
       return (ret);
}

ssize_t
sctp_sendv(int sd,
   const struct iovec *iov, int iovcnt,
   struct sockaddr *addrs, int addrcnt,
   void *info, socklen_t infolen, unsigned int infotype,
   int flags)
{
       ssize_t ret;
       int i;
       socklen_t addr_len;
       struct msghdr msg;
       in_port_t port;
       struct sctp_sendv_spa *spa_info;
       struct cmsghdr *cmsg;
       char *cmsgbuf;
       struct sockaddr *addr;
       struct sockaddr_in *addr_in;
       struct sockaddr_in6 *addr_in6;
       void *assoc_id_ptr;
       sctp_assoc_t assoc_id;

       if ((addrcnt < 0) ||
           (iovcnt < 0) ||
           ((addrs == NULL) && (addrcnt > 0)) ||
           ((addrs != NULL) && (addrcnt == 0)) ||
           ((iov == NULL) && (iovcnt > 0)) ||
           ((iov != NULL) && (iovcnt == 0))) {
               errno = EINVAL;
               return (-1);
       }
       cmsgbuf = malloc(CMSG_SPACE(sizeof(struct sctp_sndinfo)) +
           CMSG_SPACE(sizeof(struct sctp_prinfo)) +
           CMSG_SPACE(sizeof(struct sctp_authinfo)) +
           (size_t)addrcnt * CMSG_SPACE(sizeof(struct in6_addr)));
       if (cmsgbuf == NULL) {
               errno = ENOMEM;
               return (-1);
       }
       assoc_id_ptr = NULL;
       msg.msg_control = cmsgbuf;
       msg.msg_controllen = 0;
       cmsg = (struct cmsghdr *)cmsgbuf;
       switch (infotype) {
       case SCTP_SENDV_NOINFO:
               if ((infolen != 0) || (info != NULL)) {
                       free(cmsgbuf);
                       errno = EINVAL;
                       return (-1);
               }
               break;
       case SCTP_SENDV_SNDINFO:
               if ((info == NULL) || (infolen < sizeof(struct sctp_sndinfo))) {
                       free(cmsgbuf);
                       errno = EINVAL;
                       return (-1);
               }
               cmsg->cmsg_level = IPPROTO_SCTP;
               cmsg->cmsg_type = SCTP_SNDINFO;
               cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndinfo));
               memcpy(CMSG_DATA(cmsg), info, sizeof(struct sctp_sndinfo));
               msg.msg_controllen += CMSG_SPACE(sizeof(struct sctp_sndinfo));
               cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct sctp_sndinfo)));
               assoc_id_ptr = &(((struct sctp_sndinfo *)info)->snd_assoc_id);
               break;
       case SCTP_SENDV_PRINFO:
               if ((info == NULL) || (infolen < sizeof(struct sctp_prinfo))) {
                       free(cmsgbuf);
                       errno = EINVAL;
                       return (-1);
               }
               cmsg->cmsg_level = IPPROTO_SCTP;
               cmsg->cmsg_type = SCTP_PRINFO;
               cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_prinfo));
               memcpy(CMSG_DATA(cmsg), info, sizeof(struct sctp_prinfo));
               msg.msg_controllen += CMSG_SPACE(sizeof(struct sctp_prinfo));
               cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct sctp_prinfo)));
               break;
       case SCTP_SENDV_AUTHINFO:
               if ((info == NULL) || (infolen < sizeof(struct sctp_authinfo))) {
                       free(cmsgbuf);
                       errno = EINVAL;
                       return (-1);
               }
               cmsg->cmsg_level = IPPROTO_SCTP;
               cmsg->cmsg_type = SCTP_AUTHINFO;
               cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_authinfo));
               memcpy(CMSG_DATA(cmsg), info, sizeof(struct sctp_authinfo));
               msg.msg_controllen += CMSG_SPACE(sizeof(struct sctp_authinfo));
               cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct sctp_authinfo)));
               break;
       case SCTP_SENDV_SPA:
               if ((info == NULL) || (infolen < sizeof(struct sctp_sendv_spa))) {
                       free(cmsgbuf);
                       errno = EINVAL;
                       return (-1);
               }
               spa_info = (struct sctp_sendv_spa *)info;
               if (spa_info->sendv_flags & SCTP_SEND_SNDINFO_VALID) {
                       cmsg->cmsg_level = IPPROTO_SCTP;
                       cmsg->cmsg_type = SCTP_SNDINFO;
                       cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndinfo));
                       memcpy(CMSG_DATA(cmsg), &spa_info->sendv_sndinfo, sizeof(struct sctp_sndinfo));
                       msg.msg_controllen += CMSG_SPACE(sizeof(struct sctp_sndinfo));
                       cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct sctp_sndinfo)));
                       assoc_id_ptr = &(spa_info->sendv_sndinfo.snd_assoc_id);
               }
               if (spa_info->sendv_flags & SCTP_SEND_PRINFO_VALID) {
                       cmsg->cmsg_level = IPPROTO_SCTP;
                       cmsg->cmsg_type = SCTP_PRINFO;
                       cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_prinfo));
                       memcpy(CMSG_DATA(cmsg), &spa_info->sendv_prinfo, sizeof(struct sctp_prinfo));
                       msg.msg_controllen += CMSG_SPACE(sizeof(struct sctp_prinfo));
                       cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct sctp_prinfo)));
               }
               if (spa_info->sendv_flags & SCTP_SEND_AUTHINFO_VALID) {
                       cmsg->cmsg_level = IPPROTO_SCTP;
                       cmsg->cmsg_type = SCTP_AUTHINFO;
                       cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_authinfo));
                       memcpy(CMSG_DATA(cmsg), &spa_info->sendv_authinfo, sizeof(struct sctp_authinfo));
                       msg.msg_controllen += CMSG_SPACE(sizeof(struct sctp_authinfo));
                       cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct sctp_authinfo)));
               }
               break;
       default:
               free(cmsgbuf);
               errno = EINVAL;
               return (-1);
       }
       addr = addrs;
       msg.msg_name = NULL;
       msg.msg_namelen = 0;

       for (i = 0; i < addrcnt; i++) {
               switch (addr->sa_family) {
               case AF_INET:
                       addr_len = (socklen_t) sizeof(struct sockaddr_in);
                       addr_in = (struct sockaddr_in *)(void *)addr;
                       if (addr_in->sin_len != addr_len) {
                               free(cmsgbuf);
                               errno = EINVAL;
                               return (-1);
                       }
                       if (i == 0) {
                               port = addr_in->sin_port;
                       } else {
                               if (port == addr_in->sin_port) {
                                       cmsg->cmsg_level = IPPROTO_SCTP;
                                       cmsg->cmsg_type = SCTP_DSTADDRV4;
                                       cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
                                       memcpy(CMSG_DATA(cmsg), &addr_in->sin_addr, sizeof(struct in_addr));
                                       msg.msg_controllen += CMSG_SPACE(sizeof(struct in_addr));
                                       cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct in_addr)));
                               } else {
                                       free(cmsgbuf);
                                       errno = EINVAL;
                                       return (-1);
                               }
                       }
                       break;
               case AF_INET6:
                       addr_len = (socklen_t) sizeof(struct sockaddr_in6);
                       addr_in6 = (struct sockaddr_in6 *)(void *)addr;
                       if (addr_in6->sin6_len != addr_len) {
                               free(cmsgbuf);
                               errno = EINVAL;
                               return (-1);
                       }
                       if (i == 0) {
                               port = addr_in6->sin6_port;
                       } else {
                               if (port == addr_in6->sin6_port) {
                                       cmsg->cmsg_level = IPPROTO_SCTP;
                                       cmsg->cmsg_type = SCTP_DSTADDRV6;
                                       cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_addr));
                                       memcpy(CMSG_DATA(cmsg), &addr_in6->sin6_addr, sizeof(struct in6_addr));
                                       msg.msg_controllen += CMSG_SPACE(sizeof(struct in6_addr));
                                       cmsg = (struct cmsghdr *)((caddr_t)cmsg + CMSG_SPACE(sizeof(struct in6_addr)));
                               } else {
                                       free(cmsgbuf);
                                       errno = EINVAL;
                                       return (-1);
                               }
                       }
                       break;
               default:
                       free(cmsgbuf);
                       errno = EINVAL;
                       return (-1);
               }
               if (i == 0) {
                       msg.msg_name = addr;
                       msg.msg_namelen = addr_len;
               }
               addr = (struct sockaddr *)((caddr_t)addr + addr_len);
       }
       if (msg.msg_controllen == 0) {
               msg.msg_control = NULL;
       }
       msg.msg_iov = __UNCONST(iov);
       msg.msg_iovlen = iovcnt;
       msg.msg_flags = 0;
       ret = sendmsg(sd, &msg, flags);
       free(cmsgbuf);
       if ((ret >= 0) && (addrs != NULL) && (assoc_id_ptr != NULL)) {
               assoc_id = sctp_getassocid(sd, addrs);
               memcpy(assoc_id_ptr, &assoc_id, sizeof(assoc_id));
       }
       return (ret);
}

int
sctp_peeloff(int sd, sctp_assoc_t assoc_id)
{
       int ret;
       uint32_t val;

       val = assoc_id;
       ret = ioctl(sd, SIOCPEELOFF, &val);
       if (ret == -1)
               return ret;
       else
               return (int) val;
}