/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #define _XPG4_2 #define __EXTENSIONS__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* This will hold either a v4 or a v6 sockaddr */ union sockaddr_storage_v6 { struct sockaddr_in in; struct sockaddr_in6 in6; }; /* * This file implements all the libsctp calls. */ /* * To bind a list of addresses to a socket. If the socket is * v4, the type of the list of addresses is (struct in_addr). * If the socket is v6, the type is (struct in6_addr). */ int sctp_bindx(int sock, void *addrs, int addrcnt, int flags) { socklen_t sz; if (addrs == NULL || addrcnt == 0) { errno = EINVAL; return (-1); } /* Assume the caller uses the correct family type. */ switch (((struct sockaddr *)addrs)->sa_family) { case AF_INET: sz = sizeof (struct sockaddr_in); break; case AF_INET6: sz = sizeof (struct sockaddr_in6); break; default: errno = EAFNOSUPPORT; return (-1); } switch (flags) { case SCTP_BINDX_ADD_ADDR: return (setsockopt(sock, IPPROTO_SCTP, SCTP_ADD_ADDR, addrs, sz * addrcnt)); case SCTP_BINDX_REM_ADDR: return (setsockopt(sock, IPPROTO_SCTP, SCTP_REM_ADDR, addrs, sz * addrcnt)); default: errno = EINVAL; return (-1); } } /* * XXX currently not atomic -- need a better way to do this. */ int sctp_getpaddrs(int sock, sctp_assoc_t id, void **addrs) { uint32_t naddrs; socklen_t bufsz; struct sctpopt opt; if (addrs == NULL) { errno = EINVAL; return (-1); } /* First, find out how many peer addresses there are. */ *addrs = NULL; opt.sopt_aid = id; opt.sopt_name = SCTP_GET_NPADDRS; opt.sopt_val = (caddr_t)&naddrs; opt.sopt_len = sizeof (naddrs); if (ioctl(sock, SIOCSCTPGOPT, &opt) == -1) { return (-1); } if (naddrs == 0) return (0); /* * Now we can get all the peer addresses. This will over allocate * space for v4 socket. But it should be OK and save us * the job to find out if it is a v4 or v6 socket. */ bufsz = sizeof (union sockaddr_storage_v6) * naddrs; if ((*addrs = malloc(bufsz)) == NULL) { return (-1); } opt.sopt_name = SCTP_GET_PADDRS; opt.sopt_val = *addrs; opt.sopt_len = bufsz; if (ioctl(sock, SIOCSCTPGOPT, &opt) == -1) { free(*addrs); *addrs = NULL; return (-1); } /* Calculate the number of addresses returned. */ switch (((struct sockaddr *)*addrs)->sa_family) { case AF_INET: naddrs = opt.sopt_len / sizeof (struct sockaddr_in); break; case AF_INET6: naddrs = opt.sopt_len / sizeof (struct sockaddr_in6); break; } return (naddrs); } void sctp_freepaddrs(void *addrs) { free(addrs); } int sctp_getladdrs(int sock, sctp_assoc_t id, void **addrs) { uint32_t naddrs; socklen_t bufsz; struct sctpopt opt; if (addrs == NULL) { errno = EINVAL; return (-1); } /* First, try to find out how many bound addresses there are. */ *addrs = NULL; opt.sopt_aid = id; opt.sopt_name = SCTP_GET_NLADDRS; opt.sopt_val = (caddr_t)&naddrs; opt.sopt_len = sizeof (naddrs); if (ioctl(sock, SIOCSCTPGOPT, &opt) == -1) { return (-1); } if (naddrs == 0) return (0); /* * Now we can get all the bound addresses. This will over allocate * space for v4 socket. But it should be OK and save us * the job to find out if it is a v4 or v6 socket. */ bufsz = sizeof (union sockaddr_storage_v6) * naddrs; if ((*addrs = malloc(bufsz)) == NULL) { return (-1); } opt.sopt_name = SCTP_GET_LADDRS; opt.sopt_val = *addrs; opt.sopt_len = bufsz; if (ioctl(sock, SIOCSCTPGOPT, &opt) == -1) { free(*addrs); *addrs = NULL; return (-1); } /* Calculate the number of addresses returned. */ switch (((struct sockaddr *)*addrs)->sa_family) { case AF_INET: naddrs = opt.sopt_len / sizeof (struct sockaddr_in); break; case AF_INET6: naddrs = opt.sopt_len / sizeof (struct sockaddr_in6); break; } return (naddrs); } void sctp_freeladdrs(void *addrs) { free(addrs); } int sctp_opt_info(int sock, sctp_assoc_t id, int opt, void *arg, socklen_t *len) { struct sctpopt sopt; sopt.sopt_aid = id; sopt.sopt_name = opt; sopt.sopt_val = arg; sopt.sopt_len = *len; if (ioctl(sock, SIOCSCTPGOPT, &sopt) == -1) { return (-1); } *len = sopt.sopt_len; return (0); } /* * Branch off an association to its own socket. ioctl() allocates and * returns new fd. */ int sctp_peeloff(int sock, sctp_assoc_t id) { int fd; fd = id; if (ioctl(sock, SIOCSCTPPEELOFF, &fd) == -1) { return (-1); } return (fd); } ssize_t sctp_recvmsg(int s, void *msg, size_t len, struct sockaddr *from, socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo, int *msg_flags) { struct msghdr hdr; struct iovec iov; struct cmsghdr *cmsg; char cinmsg[sizeof (*sinfo) + sizeof (*cmsg) + _CMSG_HDR_ALIGNMENT]; int err; hdr.msg_name = from; hdr.msg_namelen = (fromlen != NULL) ? *fromlen : 0; hdr.msg_iov = &iov; hdr.msg_iovlen = 1; if (sinfo != NULL) { hdr.msg_control = (void *)_CMSG_HDR_ALIGN(cinmsg); hdr.msg_controllen = sizeof (cinmsg) - (_CMSG_HDR_ALIGN(cinmsg) - (uintptr_t)cinmsg); } else { hdr.msg_control = NULL; hdr.msg_controllen = 0; } iov.iov_base = msg; iov.iov_len = len; err = recvmsg(s, &hdr, msg_flags == NULL ? 0 : *msg_flags); if (err == -1) { return (-1); } if (fromlen != NULL) { *fromlen = hdr.msg_namelen; } if (msg_flags != NULL) { *msg_flags = hdr.msg_flags; } if (sinfo != NULL) { for (cmsg = CMSG_FIRSTHDR(&hdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&hdr, cmsg)) { if (cmsg->cmsg_level == IPPROTO_SCTP && cmsg->cmsg_type == SCTP_SNDRCV) { bcopy(CMSG_DATA(cmsg), sinfo, sizeof (*sinfo)); break; } } } return (err); } static ssize_t sctp_send_common(int s, const void *msg, size_t len, const struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t sinfo_flags, uint16_t stream_no, uint32_t timetolive, uint32_t context, sctp_assoc_t aid, int flags) { struct msghdr hdr; struct iovec iov; struct sctp_sndrcvinfo *sinfo; struct cmsghdr *cmsg; char coutmsg[sizeof (*sinfo) + sizeof (*cmsg) + _CMSG_HDR_ALIGNMENT]; hdr.msg_name = (caddr_t)to; hdr.msg_namelen = tolen; hdr.msg_iov = &iov; hdr.msg_iovlen = 1; hdr.msg_control = (void *)_CMSG_HDR_ALIGN(coutmsg); hdr.msg_controllen = sizeof (*cmsg) + sizeof (*sinfo); iov.iov_len = len; iov.iov_base = (caddr_t)msg; cmsg = CMSG_FIRSTHDR(&hdr); cmsg->cmsg_level = IPPROTO_SCTP; cmsg->cmsg_type = SCTP_SNDRCV; cmsg->cmsg_len = sizeof (*cmsg) + sizeof (*sinfo); sinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg); sinfo->sinfo_stream = stream_no; sinfo->sinfo_ssn = 0; sinfo->sinfo_flags = sinfo_flags; sinfo->sinfo_ppid = ppid; sinfo->sinfo_context = context; sinfo->sinfo_timetolive = timetolive; sinfo->sinfo_tsn = 0; sinfo->sinfo_cumtsn = 0; sinfo->sinfo_assoc_id = aid; return (sendmsg(s, &hdr, flags)); } ssize_t sctp_send(int s, const void *msg, size_t len, const struct sctp_sndrcvinfo *sinfo, int flags) { /* Note that msg can be NULL for pure control message. */ if (sinfo == NULL) { errno = EINVAL; return (-1); } return (sctp_send_common(s, msg, len, NULL, 0, sinfo->sinfo_ppid, sinfo->sinfo_flags, sinfo->sinfo_stream, sinfo->sinfo_timetolive, sinfo->sinfo_context, sinfo->sinfo_assoc_id, flags)); } ssize_t sctp_sendmsg(int s, const void *msg, size_t len, const struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t flags, uint16_t stream_no, uint32_t timetolive, uint32_t context) { return (sctp_send_common(s, msg, len, to, tolen, ppid, flags, stream_no, timetolive, context, 0, 0)); }