+ /* Get socket address now before we obtain socket lock */
+ if (ep.sae_srcaddr != USER_ADDR_NULL) {
+ if (ep.sae_srcaddrlen > sizeof(ss)) {
+ error = getsockaddr(so, &src, ep.sae_srcaddr, ep.sae_srcaddrlen, dgram);
+ } else {
+ error = getsockaddr_s(so, &ss, ep.sae_srcaddr, ep.sae_srcaddrlen, dgram);
+ if (error == 0) {
+ src = (struct sockaddr *)&ss;
+ }
+ }
+
+ 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) {
+ error = EINVAL;
+ goto out;
+ }
+
+ if (uap->len == USER_ADDR_NULL) {
+ error = EINVAL;
+ goto out;
+ }
+
+ /* 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) {
+ 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, PR_F_WILLUNLOCK);
+ } 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;
+
+ 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;
+ }
+
+ if (auio != NULL) {
+ if ((error = mac_socket_check_send(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;
+ }
+
+ error = soconnectxlocked(so, src, dst, p, ifscope,
+ aid, pcid, flags, NULL, 0, auio, bytes_written);
+ if (error != 0) {
+ 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, PR_F_WILLUNLOCK);
+ } 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)
+{
+#pragma unused(p, uap, retval)
+ /*
+ * Due to similiarity with a POSIX interface, define as
+ * an unofficial cancellation point.
+ */
+ __pthread_testcancel(1);
+ return 0;
+}
+
+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;