+
+ if (error)
+ goto out;
+ }
+
+ if (ep.sae_dstaddr == USER_ADDR_NULL) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /* Get socket address now before we obtain socket lock */
+ if (ep.sae_dstaddrlen > sizeof (sd)) {
+ error = getsockaddr(so, &dst, ep.sae_dstaddr, ep.sae_dstaddrlen, dgram);
+ } else {
+ error = getsockaddr_s(so, &sd, ep.sae_dstaddr, ep.sae_dstaddrlen, dgram);
+ if (error == 0)
+ dst = (struct sockaddr *)&sd;
+ }
+
+ if (error)
+ goto out;
+
+ VERIFY(dst != NULL);
+
+ if (uap->iov != USER_ADDR_NULL) {
+ /* Verify range before calling uio_create() */
+ if (uap->iovcnt <= 0 || uap->iovcnt > UIO_MAXIOV)
+ return (EINVAL);
+
+ if (uap->len == USER_ADDR_NULL)
+ return (EINVAL);
+
+ /* allocate a uio to hold the number of iovecs passed */
+ auio = uio_create(uap->iovcnt, 0,
+ (IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32),
+ UIO_WRITE);
+
+ if (auio == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+
+ /*
+ * get location of iovecs within the uio.
+ * then copyin the iovecs from user space.
+ */
+ iovp = uio_iovsaddr(auio);
+ if (iovp == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ error = copyin_user_iovec_array(uap->iov,
+ IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32,
+ uap->iovcnt, iovp);
+ if (error != 0)
+ goto out;
+
+ /* finish setup of uio_t */
+ error = uio_calculateresid(auio);
+ if (error != 0) {
+ goto out;
+ }
+ }
+
+ error = connectitx(so, src, dst, p, ep.sae_srcif, uap->associd,
+ &cid, auio, uap->flags, &bytes_written);
+ if (error == ERESTART)
+ error = EINTR;
+
+ if (uap->len != USER_ADDR_NULL) {
+ error1 = copyout(&bytes_written, uap->len, sizeof (uap->len));
+ /* give precedence to connectitx errors */
+ if ((error1 != 0) && (error == 0))
+ error = error1;
+ }
+
+ if (uap->connid != USER_ADDR_NULL) {
+ error1 = copyout(&cid, uap->connid, sizeof (cid));
+ /* give precedence to connectitx errors */
+ if ((error1 != 0) && (error == 0))
+ error = error1;
+ }
+out:
+ file_drop(fd);
+ if (auio != NULL) {
+ uio_free(auio);
+ }
+ if (src != NULL && src != SA(&ss))
+ FREE(src, M_SONAME);
+ if (dst != NULL && dst != SA(&sd))
+ FREE(dst, M_SONAME);
+ return (error);
+}
+
+int
+connectx(struct proc *p, struct connectx_args *uap, int *retval)
+{
+ /*
+ * Due to similiarity with a POSIX interface, define as
+ * an unofficial cancellation point.
+ */
+ __pthread_testcancel(1);
+ return (connectx_nocancel(p, uap, retval));
+}
+
+static int
+connectit(struct socket *so, struct sockaddr *sa)
+{
+ int error;
+
+ AUDIT_ARG(sockaddr, vfs_context_cwd(vfs_context_current()), sa);
+#if CONFIG_MACF_SOCKET_SUBSET
+ if ((error = mac_socket_check_connect(kauth_cred_get(), so, sa)) != 0)
+ return (error);
+#endif /* MAC_SOCKET_SUBSET */
+
+ socket_lock(so, 1);
+ if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) {
+ error = EALREADY;
+ goto out;
+ }
+ error = soconnectlock(so, sa, 0);
+ if (error != 0) {
+ so->so_state &= ~SS_ISCONNECTING;
+ goto out;
+ }
+ if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) {
+ error = EINPROGRESS;
+ goto out;
+ }
+ while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) {
+ lck_mtx_t *mutex_held;
+
+ if (so->so_proto->pr_getlock != NULL)
+ mutex_held = (*so->so_proto->pr_getlock)(so, 0);
+ else
+ mutex_held = so->so_proto->pr_domain->dom_mtx;
+ error = msleep((caddr_t)&so->so_timeo, mutex_held,
+ PSOCK | PCATCH, __func__, 0);
+ if (so->so_state & SS_DRAINING) {
+ error = ECONNABORTED;
+ }
+ if (error != 0)
+ break;
+ }
+ if (error == 0) {
+ error = so->so_error;
+ so->so_error = 0;
+ }
+out:
+ socket_unlock(so, 1);
+ return (error);
+}
+
+static int
+connectitx(struct socket *so, struct sockaddr *src,
+ struct sockaddr *dst, struct proc *p, uint32_t ifscope,
+ sae_associd_t aid, sae_connid_t *pcid, uio_t auio, unsigned int flags,
+ user_ssize_t *bytes_written)
+{
+ int error;
+#pragma unused (flags)
+
+ VERIFY(dst != NULL);
+
+ AUDIT_ARG(sockaddr, vfs_context_cwd(vfs_context_current()), dst);
+#if CONFIG_MACF_SOCKET_SUBSET
+ if ((error = mac_socket_check_connect(kauth_cred_get(), so, dst)) != 0)
+ return (error);
+#endif /* MAC_SOCKET_SUBSET */
+
+ socket_lock(so, 1);
+ if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) {
+ error = EALREADY;
+ goto out;
+ }
+
+ if ((so->so_proto->pr_flags & PR_DATA_IDEMPOTENT) &&
+ (flags & CONNECT_DATA_IDEMPOTENT)) {
+ so->so_flags1 |= SOF1_DATA_IDEMPOTENT;
+
+ if (flags & CONNECT_DATA_AUTHENTICATED)
+ so->so_flags |= SOF1_DATA_AUTHENTICATED;
+ }
+
+ /*
+ * Case 1: CONNECT_RESUME_ON_READ_WRITE set, no data.
+ * Case 2: CONNECT_RESUME_ON_READ_WRITE set, with data (user error)
+ * Case 3: CONNECT_RESUME_ON_READ_WRITE not set, with data
+ * Case 3 allows user to combine write with connect even if they have
+ * no use for TFO (such as regular TCP, and UDP).
+ * Case 4: CONNECT_RESUME_ON_READ_WRITE not set, no data (regular case)
+ */
+ if ((so->so_proto->pr_flags & PR_PRECONN_WRITE) &&
+ ((flags & CONNECT_RESUME_ON_READ_WRITE) || auio))
+ so->so_flags1 |= SOF1_PRECONNECT_DATA;
+
+ /*
+ * If a user sets data idempotent and does not pass an uio, or
+ * sets CONNECT_RESUME_ON_READ_WRITE, this is an error, reset
+ * SOF1_DATA_IDEMPOTENT.
+ */
+ if (!(so->so_flags1 & SOF1_PRECONNECT_DATA) &&
+ (so->so_flags1 & SOF1_DATA_IDEMPOTENT)) {
+ /* We should return EINVAL instead perhaps. */
+ so->so_flags1 &= ~SOF1_DATA_IDEMPOTENT;
+ }
+
+ error = soconnectxlocked(so, src, dst, p, ifscope,
+ aid, pcid, 0, NULL, 0, auio, bytes_written);
+ if (error != 0) {
+ so->so_state &= ~SS_ISCONNECTING;
+ goto out;
+ }
+ /*
+ * If, after the call to soconnectxlocked the flag is still set (in case
+ * data has been queued and the connect() has actually been triggered,
+ * it will have been unset by the transport), we exit immediately. There
+ * is no reason to wait on any event.
+ */
+ if (so->so_flags1 & SOF1_PRECONNECT_DATA) {
+ error = 0;
+ goto out;
+ }
+ if ((so->so_state & SS_NBIO) && (so->so_state & SS_ISCONNECTING)) {
+ error = EINPROGRESS;
+ goto out;
+ }
+ while ((so->so_state & SS_ISCONNECTING) && so->so_error == 0) {
+ lck_mtx_t *mutex_held;
+
+ if (so->so_proto->pr_getlock != NULL)
+ mutex_held = (*so->so_proto->pr_getlock)(so, 0);
+ else
+ mutex_held = so->so_proto->pr_domain->dom_mtx;
+ error = msleep((caddr_t)&so->so_timeo, mutex_held,
+ PSOCK | PCATCH, __func__, 0);
+ if (so->so_state & SS_DRAINING) {
+ error = ECONNABORTED;
+ }
+ if (error != 0)
+ break;
+ }
+ if (error == 0) {
+ error = so->so_error;
+ so->so_error = 0;
+ }
+out:
+ socket_unlock(so, 1);
+ return (error);
+}
+
+int
+peeloff(struct proc *p, struct peeloff_args *uap, int *retval)
+{
+ /*
+ * Due to similiarity with a POSIX interface, define as
+ * an unofficial cancellation point.
+ */
+ __pthread_testcancel(1);
+ return (peeloff_nocancel(p, uap, retval));
+}
+
+static int
+peeloff_nocancel(struct proc *p, struct peeloff_args *uap, int *retval)
+{
+ struct fileproc *fp;
+ struct socket *mp_so, *so = NULL;
+ int newfd, fd = uap->s;
+ short fflag; /* type must match fp->f_flag */
+ int error;
+
+ *retval = -1;
+
+ error = fp_getfsock(p, fd, &fp, &mp_so);
+ if (error != 0) {
+ if (error == EOPNOTSUPP)
+ error = ENOTSOCK;
+ goto out_nofile;
+ }
+ if (mp_so == NULL) {
+ error = EBADF;
+ goto out;
+ }
+
+ socket_lock(mp_so, 1);
+ error = sopeelofflocked(mp_so, uap->aid, &so);
+ if (error != 0) {
+ socket_unlock(mp_so, 1);
+ goto out;
+ }
+ VERIFY(so != NULL);
+ socket_unlock(mp_so, 0); /* keep ref on mp_so for us */
+
+ fflag = fp->f_flag;
+ error = falloc(p, &fp, &newfd, vfs_context_current());
+ if (error != 0) {
+ /* drop this socket (probably ran out of file descriptors) */
+ soclose(so);
+ sodereference(mp_so); /* our mp_so ref */
+ goto out;
+ }
+
+ fp->f_flag = fflag;
+ fp->f_ops = &socketops;
+ fp->f_data = (caddr_t)so;
+
+ /*
+ * If the socket has been marked as inactive by sosetdefunct(),
+ * disallow further operations on it.
+ */
+ if (so->so_flags & SOF_DEFUNCT) {
+ sodefunct(current_proc(), so,
+ SHUTDOWN_SOCKET_LEVEL_DISCONNECT_INTERNAL);
+ }
+
+ proc_fdlock(p);
+ procfdtbl_releasefd(p, newfd, NULL);
+ fp_drop(p, newfd, fp, 1);
+ proc_fdunlock(p);
+
+ sodereference(mp_so); /* our mp_so ref */
+ *retval = newfd;
+
+out:
+ file_drop(fd);
+
+out_nofile:
+ return (error);
+}
+
+int
+disconnectx(struct proc *p, struct disconnectx_args *uap, int *retval)
+{
+ /*
+ * Due to similiarity with a POSIX interface, define as
+ * an unofficial cancellation point.
+ */
+ __pthread_testcancel(1);
+ return (disconnectx_nocancel(p, uap, retval));
+}
+
+static int
+disconnectx_nocancel(struct proc *p, struct disconnectx_args *uap, int *retval)
+{
+#pragma unused(p, retval)
+ struct socket *so;
+ int fd = uap->s;
+ int error;
+
+ error = file_socket(fd, &so);
+ if (error != 0)
+ return (error);
+ if (so == NULL) {
+ error = EBADF;
+ goto out;
+ }
+
+ error = sodisconnectx(so, uap->aid, uap->cid);
+out:
+ file_drop(fd);
+ return (error);
+}
+
+/*
+ * Returns: 0 Success
+ * socreate:EAFNOSUPPORT
+ * socreate:EPROTOTYPE
+ * socreate:EPROTONOSUPPORT
+ * socreate:ENOBUFS
+ * socreate:ENOMEM
+ * socreate:EISCONN
+ * socreate:??? [other protocol families, IPSEC]
+ * falloc:ENFILE
+ * falloc:EMFILE
+ * falloc:ENOMEM
+ * copyout:EFAULT
+ * soconnect2:EINVAL
+ * soconnect2:EPROTOTYPE
+ * soconnect2:??? [other protocol families[
+ */
+int
+socketpair(struct proc *p, struct socketpair_args *uap,
+ __unused int32_t *retval)
+{
+ struct fileproc *fp1, *fp2;
+ struct socket *so1, *so2;
+ int fd, error, sv[2];
+
+ AUDIT_ARG(socket, uap->domain, uap->type, uap->protocol);
+ error = socreate(uap->domain, &so1, uap->type, uap->protocol);
+ if (error)
+ return (error);
+ error = socreate(uap->domain, &so2, uap->type, uap->protocol);
+ if (error)
+ goto free1;
+
+ error = falloc(p, &fp1, &fd, vfs_context_current());
+ if (error) {
+ goto free2;
+ }
+ fp1->f_flag = FREAD|FWRITE;
+ fp1->f_ops = &socketops;
+ fp1->f_data = (caddr_t)so1;
+ sv[0] = fd;
+
+ error = falloc(p, &fp2, &fd, vfs_context_current());
+ if (error) {
+ goto free3;
+ }
+ fp2->f_flag = FREAD|FWRITE;
+ fp2->f_ops = &socketops;
+ fp2->f_data = (caddr_t)so2;
+ sv[1] = fd;
+
+ error = soconnect2(so1, so2);
+ if (error) {
+ goto free4;
+ }
+ if (uap->type == SOCK_DGRAM) {
+ /*
+ * Datagram socket connection is asymmetric.
+ */
+ error = soconnect2(so2, so1);
+ if (error) {
+ goto free4;
+ }
+ }
+
+ if ((error = copyout(sv, uap->rsv, 2 * sizeof (int))) != 0)
+ goto free4;
+
+ proc_fdlock(p);
+ procfdtbl_releasefd(p, sv[0], NULL);
+ procfdtbl_releasefd(p, sv[1], NULL);
+ fp_drop(p, sv[0], fp1, 1);
+ fp_drop(p, sv[1], fp2, 1);
+ proc_fdunlock(p);
+
+ return (0);
+free4:
+ fp_free(p, sv[1], fp2);
+free3:
+ fp_free(p, sv[0], fp1);
+free2:
+ (void) soclose(so2);
+free1:
+ (void) soclose(so1);
+ return (error);
+}
+
+/*
+ * Returns: 0 Success
+ * EINVAL
+ * ENOBUFS
+ * EBADF
+ * EPIPE
+ * EACCES Mandatory Access Control failure
+ * file_socket:ENOTSOCK
+ * file_socket:EBADF
+ * getsockaddr:ENAMETOOLONG Filename too long
+ * getsockaddr:EINVAL Invalid argument
+ * getsockaddr:ENOMEM Not enough space
+ * getsockaddr:EFAULT Bad address
+ * <pru_sosend>:EACCES[TCP]
+ * <pru_sosend>:EADDRINUSE[TCP]
+ * <pru_sosend>:EADDRNOTAVAIL[TCP]
+ * <pru_sosend>:EAFNOSUPPORT[TCP]
+ * <pru_sosend>:EAGAIN[TCP]
+ * <pru_sosend>:EBADF
+ * <pru_sosend>:ECONNRESET[TCP]
+ * <pru_sosend>:EFAULT
+ * <pru_sosend>:EHOSTUNREACH[TCP]
+ * <pru_sosend>:EINTR
+ * <pru_sosend>:EINVAL
+ * <pru_sosend>:EISCONN[AF_INET]
+ * <pru_sosend>:EMSGSIZE[TCP]
+ * <pru_sosend>:ENETDOWN[TCP]
+ * <pru_sosend>:ENETUNREACH[TCP]
+ * <pru_sosend>:ENOBUFS
+ * <pru_sosend>:ENOMEM[TCP]
+ * <pru_sosend>:ENOTCONN[AF_INET]
+ * <pru_sosend>:EOPNOTSUPP
+ * <pru_sosend>:EPERM[TCP]
+ * <pru_sosend>:EPIPE
+ * <pru_sosend>:EWOULDBLOCK
+ * <pru_sosend>:???[TCP] [ignorable: mostly IPSEC/firewall/DLIL]
+ * <pru_sosend>:???[AF_INET] [whatever a filter author chooses]
+ * <pru_sosend>:??? [value from so_error]
+ * sockargs:???
+ */
+static int
+sendit(struct proc *p, struct socket *so, struct user_msghdr *mp, uio_t uiop,
+ int flags, int32_t *retval)
+{
+ struct mbuf *control = NULL;
+ struct sockaddr_storage ss;
+ struct sockaddr *to = NULL;
+ boolean_t want_free = TRUE;
+ int error;
+ user_ssize_t len;
+
+ KERNEL_DEBUG(DBG_FNC_SENDIT | DBG_FUNC_START, 0, 0, 0, 0, 0);
+
+ if (mp->msg_name != USER_ADDR_NULL) {
+ if (mp->msg_namelen > sizeof (ss)) {
+ error = getsockaddr(so, &to, mp->msg_name,
+ mp->msg_namelen, TRUE);
+ } else {
+ error = getsockaddr_s(so, &ss, mp->msg_name,
+ mp->msg_namelen, TRUE);
+ if (error == 0) {
+ to = (struct sockaddr *)&ss;
+ want_free = FALSE;
+ }
+ }
+ if (error != 0)
+ goto out;
+ AUDIT_ARG(sockaddr, vfs_context_cwd(vfs_context_current()), to);
+ }
+ if (mp->msg_control != USER_ADDR_NULL) {
+ if (mp->msg_controllen < sizeof (struct cmsghdr)) {