/* * Copyright (c) 2003-2021 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #define __KPI__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if SKYWALK #include #endif /* SKYWALK */ #define SOCK_SEND_MBUF_MODE_VERBOSE 0x0001 static errno_t sock_send_internal(socket_t, const struct msghdr *, mbuf_t, int, size_t *); #undef sock_accept #undef sock_socket errno_t sock_accept(socket_t so, struct sockaddr *from, int fromlen, int flags, sock_upcall callback, void *cookie, socket_t *new_so); errno_t sock_socket(int domain, int type, int protocol, sock_upcall callback, void *context, socket_t *new_so); static errno_t sock_accept_common(socket_t sock, struct sockaddr *from, int fromlen, int flags, sock_upcall callback, void *cookie, socket_t *new_sock, bool is_internal); static errno_t sock_socket_common(int domain, int type, int protocol, sock_upcall callback, void *context, socket_t *new_so, bool is_internal); errno_t sock_accept_common(socket_t sock, struct sockaddr *from, int fromlen, int flags, sock_upcall callback, void *cookie, socket_t *new_sock, bool is_internal) { struct sockaddr *sa; struct socket *new_so; lck_mtx_t *mutex_held; int dosocklock; errno_t error = 0; if (sock == NULL || new_sock == NULL) { return EINVAL; } socket_lock(sock, 1); if ((sock->so_options & SO_ACCEPTCONN) == 0) { socket_unlock(sock, 1); return EINVAL; } if ((flags & ~(MSG_DONTWAIT)) != 0) { socket_unlock(sock, 1); return ENOTSUP; } check_again: if (((flags & MSG_DONTWAIT) != 0 || (sock->so_state & SS_NBIO) != 0) && sock->so_comp.tqh_first == NULL) { socket_unlock(sock, 1); return EWOULDBLOCK; } if (sock->so_proto->pr_getlock != NULL) { mutex_held = (*sock->so_proto->pr_getlock)(sock, PR_F_WILLUNLOCK); dosocklock = 1; } else { mutex_held = sock->so_proto->pr_domain->dom_mtx; dosocklock = 0; } while (TAILQ_EMPTY(&sock->so_comp) && sock->so_error == 0) { if (sock->so_state & SS_CANTRCVMORE) { sock->so_error = ECONNABORTED; break; } error = msleep((caddr_t)&sock->so_timeo, mutex_held, PSOCK | PCATCH, "sock_accept", NULL); if (error != 0) { socket_unlock(sock, 1); return error; } } if (sock->so_error != 0) { error = sock->so_error; sock->so_error = 0; socket_unlock(sock, 1); return error; } so_acquire_accept_list(sock, NULL); if (TAILQ_EMPTY(&sock->so_comp)) { so_release_accept_list(sock); goto check_again; } new_so = TAILQ_FIRST(&sock->so_comp); TAILQ_REMOVE(&sock->so_comp, new_so, so_list); new_so->so_state &= ~SS_COMP; new_so->so_head = NULL; sock->so_qlen--; so_release_accept_list(sock); /* * Count the accepted socket as an in-kernel socket */ new_so->so_flags1 |= SOF1_IN_KERNEL_SOCKET; INC_ATOMIC_INT64_LIM(net_api_stats.nas_socket_in_kernel_total); if (is_internal) { INC_ATOMIC_INT64_LIM(net_api_stats.nas_socket_in_kernel_os_total); } /* * Pass the pre-accepted socket to any interested socket filter(s). * Upon failure, the socket would have been closed by the callee. */ if (new_so->so_filt != NULL) { /* * Temporarily drop the listening socket's lock before we * hand off control over to the socket filter(s), but keep * a reference so that it won't go away. We'll grab it * again once we're done with the filter(s). */ socket_unlock(sock, 0); if ((error = soacceptfilter(new_so, sock)) != 0) { /* Drop reference on listening socket */ sodereference(sock); return error; } socket_lock(sock, 0); } if (dosocklock) { LCK_MTX_ASSERT(new_so->so_proto->pr_getlock(new_so, 0), LCK_MTX_ASSERT_NOTOWNED); socket_lock(new_so, 1); } (void) soacceptlock(new_so, &sa, 0); socket_unlock(sock, 1); /* release the head */ /* see comments in sock_setupcall() */ if (callback != NULL) { #if defined(__arm64__) sock_setupcalls_locked(new_so, callback, cookie, callback, cookie, 0); #else /* defined(__arm64__) */ sock_setupcalls_locked(new_so, callback, cookie, NULL, NULL, 0); #endif /* defined(__arm64__) */ } if (sa != NULL && from != NULL) { if (fromlen > sa->sa_len) { fromlen = sa->sa_len; } memcpy(from, sa, fromlen); } free_sockaddr(sa); /* * If the socket has been marked as inactive by sosetdefunct(), * disallow further operations on it. */ if (new_so->so_flags & SOF_DEFUNCT) { (void) sodefunct(current_proc(), new_so, SHUTDOWN_SOCKET_LEVEL_DISCONNECT_INTERNAL); } *new_sock = new_so; if (dosocklock) { socket_unlock(new_so, 1); } return error; } errno_t sock_accept(socket_t sock, struct sockaddr *from, int fromlen, int flags, sock_upcall callback, void *cookie, socket_t *new_sock) { return sock_accept_common(sock, from, fromlen, flags, callback, cookie, new_sock, false); } errno_t sock_accept_internal(socket_t sock, struct sockaddr *from, int fromlen, int flags, sock_upcall callback, void *cookie, socket_t *new_sock) { return sock_accept_common(sock, from, fromlen, flags, callback, cookie, new_sock, true); } errno_t sock_bind(socket_t sock, const struct sockaddr *to) { int error = 0; struct sockaddr *sa = NULL; struct sockaddr_storage ss; if (sock == NULL || to == NULL) { return EINVAL; } if (to->sa_len > sizeof(ss)) { sa = kalloc_data(to->sa_len, Z_WAITOK | Z_ZERO | Z_NOFAIL); } else { sa = (struct sockaddr *)&ss; } memcpy(sa, to, to->sa_len); error = sobindlock(sock, sa, 1); /* will lock socket */ if (sa != (struct sockaddr *)&ss) { kfree_data(sa, sa->sa_len); } return error; } errno_t sock_connect(socket_t sock, const struct sockaddr *to, int flags) { int error = 0; lck_mtx_t *mutex_held; struct sockaddr *sa = NULL; struct sockaddr_storage ss; if (sock == NULL || to == NULL) { return EINVAL; } if (to->sa_len > sizeof(ss)) { sa = kalloc_data(to->sa_len, (flags & MSG_DONTWAIT) ? Z_NOWAIT : Z_WAITOK); if (sa == NULL) { return ENOBUFS; } } else { sa = (struct sockaddr *)&ss; } memcpy(sa, to, to->sa_len); socket_lock(sock, 1); if ((sock->so_state & SS_ISCONNECTING) && ((sock->so_state & SS_NBIO) != 0 || (flags & MSG_DONTWAIT) != 0)) { error = EALREADY; goto out; } #if SKYWALK sk_protect_t protect = sk_async_transmit_protect(); #endif /* SKYWALK */ error = soconnectlock(sock, sa, 0); #if SKYWALK sk_async_transmit_unprotect(protect); #endif /* SKYWALK */ if (!error) { if ((sock->so_state & SS_ISCONNECTING) && ((sock->so_state & SS_NBIO) != 0 || (flags & MSG_DONTWAIT) != 0)) { error = EINPROGRESS; goto out; } if (sock->so_proto->pr_getlock != NULL) { mutex_held = (*sock->so_proto->pr_getlock)(sock, PR_F_WILLUNLOCK); } else { mutex_held = sock->so_proto->pr_domain->dom_mtx; } while ((sock->so_state & SS_ISCONNECTING) && sock->so_error == 0) { error = msleep((caddr_t)&sock->so_timeo, mutex_held, PSOCK | PCATCH, "sock_connect", NULL); if (error != 0) { break; } } if (error == 0) { error = sock->so_error; sock->so_error = 0; } } else { sock->so_state &= ~SS_ISCONNECTING; } out: socket_unlock(sock, 1); if (sa != (struct sockaddr *)&ss) { kfree_data(sa, sa->sa_len); } return error; } errno_t sock_connectwait(socket_t sock, const struct timeval *tv) { lck_mtx_t *mutex_held; errno_t retval = 0; struct timespec ts; socket_lock(sock, 1); /* Check if we're already connected or if we've already errored out */ if ((sock->so_state & SS_ISCONNECTING) == 0 || sock->so_error != 0) { if (sock->so_error != 0) { retval = sock->so_error; sock->so_error = 0; } else { if ((sock->so_state & SS_ISCONNECTED) != 0) { retval = 0; } else { retval = EINVAL; } } goto done; } /* copied translation from timeval to hertz from SO_RCVTIMEO handling */ if (tv->tv_sec < 0 || tv->tv_sec > SHRT_MAX / hz || tv->tv_usec < 0 || tv->tv_usec >= 1000000) { retval = EDOM; goto done; } ts.tv_sec = tv->tv_sec; ts.tv_nsec = (tv->tv_usec * (integer_t)NSEC_PER_USEC); if ((ts.tv_sec + (ts.tv_nsec / (long)NSEC_PER_SEC)) / 100 > SHRT_MAX) { retval = EDOM; goto done; } if (sock->so_proto->pr_getlock != NULL) { mutex_held = (*sock->so_proto->pr_getlock)(sock, PR_F_WILLUNLOCK); } else { mutex_held = sock->so_proto->pr_domain->dom_mtx; } msleep((caddr_t)&sock->so_timeo, mutex_held, PSOCK, "sock_connectwait", &ts); /* Check if we're still waiting to connect */ if ((sock->so_state & SS_ISCONNECTING) && sock->so_error == 0) { retval = EINPROGRESS; goto done; } if (sock->so_error != 0) { retval = sock->so_error; sock->so_error = 0; } done: socket_unlock(sock, 1); return retval; } errno_t sock_nointerrupt(socket_t sock, int on) { socket_lock(sock, 1); if (on) { sock->so_rcv.sb_flags |= SB_NOINTR; /* This isn't safe */ sock->so_snd.sb_flags |= SB_NOINTR; /* This isn't safe */ } else { sock->so_rcv.sb_flags &= ~SB_NOINTR; /* This isn't safe */ sock->so_snd.sb_flags &= ~SB_NOINTR; /* This isn't safe */ } socket_unlock(sock, 1); return 0; } errno_t sock_getpeername(socket_t sock, struct sockaddr *peername, int peernamelen) { int error; struct sockaddr *sa = NULL; if (sock == NULL || peername == NULL || peernamelen < 0) { return EINVAL; } socket_lock(sock, 1); if (!(sock->so_state & (SS_ISCONNECTED | SS_ISCONFIRMING))) { socket_unlock(sock, 1); return ENOTCONN; } error = sogetaddr_locked(sock, &sa, 1); socket_unlock(sock, 1); if (error == 0) { if (peernamelen > sa->sa_len) { peernamelen = sa->sa_len; } memcpy(peername, sa, peernamelen); free_sockaddr(sa); } return error; } errno_t sock_getsockname(socket_t sock, struct sockaddr *sockname, int socknamelen) { int error; struct sockaddr *sa = NULL; if (sock == NULL || sockname == NULL || socknamelen < 0) { return EINVAL; } socket_lock(sock, 1); error = sogetaddr_locked(sock, &sa, 0); socket_unlock(sock, 1); if (error == 0) { if (socknamelen > sa->sa_len) { socknamelen = sa->sa_len; } memcpy(sockname, sa, socknamelen); free_sockaddr(sa); } return error; } __private_extern__ int sogetaddr_locked(struct socket *so, struct sockaddr **psa, int peer) { int error; if (so == NULL || psa == NULL) { return EINVAL; } *psa = NULL; error = peer ? so->so_proto->pr_usrreqs->pru_peeraddr(so, psa) : so->so_proto->pr_usrreqs->pru_sockaddr(so, psa); if (error == 0 && *psa == NULL) { error = ENOMEM; } else if (error != 0) { free_sockaddr(*psa); } return error; } errno_t sock_getaddr(socket_t sock, struct sockaddr **psa, int peer) { int error; if (sock == NULL || psa == NULL) { return EINVAL; } socket_lock(sock, 1); error = sogetaddr_locked(sock, psa, peer); socket_unlock(sock, 1); return error; } void sock_freeaddr(struct sockaddr *sa) { free_sockaddr(sa); } errno_t sock_getsockopt(socket_t sock, int level, int optname, void *optval, int *optlen) { int error = 0; struct sockopt sopt; if (sock == NULL || optval == NULL || optlen == NULL) { return EINVAL; } sopt.sopt_dir = SOPT_GET; sopt.sopt_level = level; sopt.sopt_name = optname; sopt.sopt_val = CAST_USER_ADDR_T(optval); sopt.sopt_valsize = *optlen; sopt.sopt_p = kernproc; error = sogetoptlock(sock, &sopt, 1); /* will lock socket */ if (error == 0) { *optlen = (uint32_t)sopt.sopt_valsize; } return error; } errno_t sock_ioctl(socket_t sock, unsigned long request, void *argp) { return soioctl(sock, request, argp, kernproc); /* will lock socket */ } errno_t sock_setsockopt(socket_t sock, int level, int optname, const void *optval, int optlen) { struct sockopt sopt; if (sock == NULL || optval == NULL) { return EINVAL; } sopt.sopt_dir = SOPT_SET; sopt.sopt_level = level; sopt.sopt_name = optname; sopt.sopt_val = CAST_USER_ADDR_T(optval); sopt.sopt_valsize = optlen; sopt.sopt_p = kernproc; return sosetoptlock(sock, &sopt, 1); /* will lock socket */ } /* * This follows the recommended mappings between DSCP code points * and WMM access classes. */ static uint32_t so_tc_from_dscp(uint8_t dscp) { uint32_t tc; if (dscp >= 0x30 && dscp <= 0x3f) { tc = SO_TC_VO; } else if (dscp >= 0x20 && dscp <= 0x2f) { tc = SO_TC_VI; } else if (dscp >= 0x08 && dscp <= 0x17) { tc = SO_TC_BK_SYS; } else { tc = SO_TC_BE; } return tc; } errno_t sock_settclassopt(socket_t sock, const void *optval, size_t optlen) { errno_t error = 0; struct sockopt sopt; int sotc; if (sock == NULL || optval == NULL || optlen != sizeof(int)) { return EINVAL; } socket_lock(sock, 1); if (!(sock->so_state & SS_ISCONNECTED)) { /* * If the socket is not connected then we don't know * if the destination is on LAN or not. Skip * setting traffic class in this case */ error = ENOTCONN; goto out; } if (sock->so_proto == NULL || sock->so_proto->pr_domain == NULL || sock->so_pcb == NULL) { error = EINVAL; goto out; } /* * Set the socket traffic class based on the passed DSCP code point * regardless of the scope of the destination */ sotc = so_tc_from_dscp((uint8_t)((*(const int *)optval) >> 2)); sopt.sopt_dir = SOPT_SET; sopt.sopt_val = CAST_USER_ADDR_T(&sotc); sopt.sopt_valsize = sizeof(sotc); sopt.sopt_p = kernproc; sopt.sopt_level = SOL_SOCKET; sopt.sopt_name = SO_TRAFFIC_CLASS; error = sosetoptlock(sock, &sopt, 0); /* already locked */ if (error != 0) { printf("%s: sosetopt SO_TRAFFIC_CLASS failed %d\n", __func__, error); goto out; } /* * Check if the destination address is LAN or link local address. * We do not want to set traffic class bits if the destination * is not local. */ if (!so_isdstlocal(sock)) { goto out; } sopt.sopt_dir = SOPT_SET; sopt.sopt_val = CAST_USER_ADDR_T(optval); sopt.sopt_valsize = optlen; sopt.sopt_p = kernproc; switch (SOCK_DOM(sock)) { case PF_INET: sopt.sopt_level = IPPROTO_IP; sopt.sopt_name = IP_TOS; break; case PF_INET6: sopt.sopt_level = IPPROTO_IPV6; sopt.sopt_name = IPV6_TCLASS; break; default: error = EINVAL; goto out; } error = sosetoptlock(sock, &sopt, 0); /* already locked */ socket_unlock(sock, 1); return error; out: socket_unlock(sock, 1); return error; } errno_t sock_gettclassopt(socket_t sock, void *optval, size_t *optlen) { errno_t error = 0; struct sockopt sopt; if (sock == NULL || optval == NULL || optlen == NULL) { return EINVAL; } sopt.sopt_dir = SOPT_GET; sopt.sopt_val = CAST_USER_ADDR_T(optval); sopt.sopt_valsize = *optlen; sopt.sopt_p = kernproc; socket_lock(sock, 1); if (sock->so_proto == NULL || sock->so_proto->pr_domain == NULL) { socket_unlock(sock, 1); return EINVAL; } switch (SOCK_DOM(sock)) { case PF_INET: sopt.sopt_level = IPPROTO_IP; sopt.sopt_name = IP_TOS; break; case PF_INET6: sopt.sopt_level = IPPROTO_IPV6; sopt.sopt_name = IPV6_TCLASS; break; default: socket_unlock(sock, 1); return EINVAL; } error = sogetoptlock(sock, &sopt, 0); /* already locked */ socket_unlock(sock, 1); if (error == 0) { *optlen = sopt.sopt_valsize; } return error; } errno_t sock_listen(socket_t sock, int backlog) { if (sock == NULL) { return EINVAL; } return solisten(sock, backlog); /* will lock socket */ } errno_t sock_receive_internal(socket_t sock, struct msghdr *msg, mbuf_t *data, int flags, size_t *recvdlen) { uio_t auio; struct mbuf *control = NULL; int error = 0; user_ssize_t length = 0; struct sockaddr *fromsa = NULL; UIO_STACKBUF(uio_buf, (msg != NULL) ? msg->msg_iovlen : 0); if (sock == NULL) { return EINVAL; } auio = uio_createwithbuffer(((msg != NULL) ? msg->msg_iovlen : 0), 0, UIO_SYSSPACE, UIO_READ, &uio_buf[0], sizeof(uio_buf)); if (msg != NULL && data == NULL) { int i; struct iovec *tempp = msg->msg_iov; for (i = 0; i < msg->msg_iovlen; i++) { uio_addiov(auio, CAST_USER_ADDR_T((tempp + i)->iov_base), (tempp + i)->iov_len); } if (uio_resid(auio) < 0) { return EINVAL; } } else if (recvdlen != NULL) { uio_setresid(auio, (uio_resid(auio) + *recvdlen)); } length = uio_resid(auio); if (recvdlen != NULL) { *recvdlen = 0; } /* let pru_soreceive handle the socket locking */ error = sock->so_proto->pr_usrreqs->pru_soreceive(sock, &fromsa, auio, data, (msg && msg->msg_control) ? &control : NULL, &flags); if (error != 0) { goto cleanup; } if (recvdlen != NULL) { *recvdlen = length - uio_resid(auio); } if (msg != NULL) { msg->msg_flags = flags; if (msg->msg_name != NULL) { int salen; salen = msg->msg_namelen; if (msg->msg_namelen > 0 && fromsa != NULL) { salen = MIN(salen, fromsa->sa_len); memcpy(msg->msg_name, fromsa, msg->msg_namelen > fromsa->sa_len ? fromsa->sa_len : msg->msg_namelen); } } if (msg->msg_control != NULL) { struct mbuf *m = control; u_char *ctlbuf = msg->msg_control; int clen = msg->msg_controllen; msg->msg_controllen = 0; while (m != NULL && clen > 0) { unsigned int tocopy; if (clen >= m->m_len) { tocopy = m->m_len; } else { msg->msg_flags |= MSG_CTRUNC; tocopy = clen; } memcpy(ctlbuf, mtod(m, caddr_t), tocopy); ctlbuf += tocopy; clen -= tocopy; m = m->m_next; } msg->msg_controllen = (socklen_t)((uintptr_t)ctlbuf - (uintptr_t)msg->msg_control); } } cleanup: if (control != NULL) { m_freem(control); } free_sockaddr(fromsa); return error; } errno_t sock_receive(socket_t sock, struct msghdr *msg, int flags, size_t *recvdlen) { if ((msg == NULL) || (msg->msg_iovlen < 1) || (msg->msg_iov[0].iov_len == 0) || (msg->msg_iov[0].iov_base == NULL)) { return EINVAL; } return sock_receive_internal(sock, msg, NULL, flags, recvdlen); } errno_t sock_receivembuf(socket_t sock, struct msghdr *msg, mbuf_t *data, int flags, size_t *recvlen) { if (data == NULL || recvlen == 0 || *recvlen <= 0 || (msg != NULL && (msg->msg_iov != NULL || msg->msg_iovlen != 0))) { return EINVAL; } return sock_receive_internal(sock, msg, data, flags, recvlen); } errno_t sock_send_internal(socket_t sock, const struct msghdr *msg, mbuf_t data, int flags, size_t *sentlen) { uio_t auio = NULL; struct mbuf *control = NULL; int error = 0; user_ssize_t datalen = 0; if (sock == NULL) { error = EINVAL; goto errorout; } if (data == NULL && msg != NULL) { struct iovec *tempp = msg->msg_iov; auio = uio_create(msg->msg_iovlen, 0, UIO_SYSSPACE, UIO_WRITE); if (auio == NULL) { #if (DEBUG || DEVELOPMENT) printf("sock_send_internal: so %p uio_createwithbuffer(%lu) failed, ENOMEM\n", sock, UIO_SIZEOF(msg->msg_iovlen)); #endif /* (DEBUG || DEVELOPMENT) */ error = ENOMEM; goto errorout; } if (tempp != NULL) { int i; for (i = 0; i < msg->msg_iovlen; i++) { uio_addiov(auio, CAST_USER_ADDR_T((tempp + i)->iov_base), (tempp + i)->iov_len); } if (uio_resid(auio) < 0) { error = EINVAL; goto errorout; } } } if (sentlen != NULL) { *sentlen = 0; } if (auio != NULL) { datalen = uio_resid(auio); } else { datalen = data->m_pkthdr.len; } if (msg != NULL && msg->msg_control) { if ((size_t)msg->msg_controllen < sizeof(struct cmsghdr)) { error = EINVAL; goto errorout; } if ((size_t)msg->msg_controllen > MLEN) { error = EINVAL; goto errorout; } control = m_get(M_NOWAIT, MT_CONTROL); if (control == NULL) { error = ENOMEM; goto errorout; } memcpy(mtod(control, caddr_t), msg->msg_control, msg->msg_controllen); control->m_len = msg->msg_controllen; } #if SKYWALK sk_protect_t protect = sk_async_transmit_protect(); #endif /* SKYWALK */ error = sock->so_proto->pr_usrreqs->pru_sosend(sock, msg != NULL ? (struct sockaddr *)msg->msg_name : NULL, auio, data, control, flags); #if SKYWALK sk_async_transmit_unprotect(protect); #endif /* SKYWALK */ /* * Residual data is possible in the case of IO vectors but not * in the mbuf case since the latter is treated as atomic send. * If pru_sosend() consumed a portion of the iovecs data and * the error returned is transient, treat it as success; this * is consistent with sendit() behavior. */ if (auio != NULL && uio_resid(auio) != datalen && (error == ERESTART || error == EINTR || error == EWOULDBLOCK)) { error = 0; } if (error == 0 && sentlen != NULL) { if (auio != NULL) { *sentlen = datalen - uio_resid(auio); } else { *sentlen = datalen; } } if (auio != NULL) { uio_free(auio); } return error; /* * In cases where we detect an error before returning, we need to * free the mbuf chain if there is one. sosend (and pru_sosend) will * free the mbuf chain if they encounter an error. */ errorout: if (control) { m_freem(control); } if (data) { m_freem(data); } if (sentlen) { *sentlen = 0; } if (auio != NULL) { uio_free(auio); } return error; } errno_t sock_send(socket_t sock, const struct msghdr *msg, int flags, size_t *sentlen) { if (msg == NULL || msg->msg_iov == NULL || msg->msg_iovlen < 1) { return EINVAL; } return sock_send_internal(sock, msg, NULL, flags, sentlen); } errno_t sock_sendmbuf(socket_t sock, const struct msghdr *msg, mbuf_t data, int flags, size_t *sentlen) { int error; if (data == NULL || (msg != NULL && (msg->msg_iov != NULL || msg->msg_iovlen != 0))) { if (data != NULL) { m_freem(data); } error = EINVAL; goto done; } error = sock_send_internal(sock, msg, data, flags, sentlen); done: return error; } errno_t sock_sendmbuf_can_wait(socket_t sock, const struct msghdr *msg, mbuf_t data, int flags, size_t *sentlen) { int error; int count = 0; int i; mbuf_t m; struct msghdr msg_temp = {}; if (data == NULL || (msg != NULL && (msg->msg_iov != NULL || msg->msg_iovlen != 0))) { error = EINVAL; goto done; } /* * Use the name and control */ msg_temp.msg_name = msg->msg_name; msg_temp.msg_namelen = msg->msg_namelen; msg_temp.msg_control = msg->msg_control; msg_temp.msg_controllen = msg->msg_controllen; /* * Count the number of mbufs in the chain */ for (m = data; m != NULL; m = mbuf_next(m)) { count++; } msg_temp.msg_iov = kalloc_type(struct iovec, count, Z_WAITOK | Z_ZERO); if (msg_temp.msg_iov == NULL) { error = ENOMEM; goto done; } msg_temp.msg_iovlen = count; for (i = 0, m = data; m != NULL; i++, m = mbuf_next(m)) { msg_temp.msg_iov[i].iov_base = mbuf_data(m); msg_temp.msg_iov[i].iov_len = mbuf_len(m); } error = sock_send_internal(sock, &msg_temp, NULL, flags, sentlen); done: if (data != NULL) { m_freem(data); } if (msg_temp.msg_iov != NULL) { kfree_type(struct iovec, count, msg_temp.msg_iov); } return error; } errno_t sock_shutdown(socket_t sock, int how) { if (sock == NULL) { return EINVAL; } return soshutdown(sock, how); } errno_t sock_socket_common(int domain, int type, int protocol, sock_upcall callback, void *context, socket_t *new_so, bool is_internal) { int error = 0; if (new_so == NULL) { return EINVAL; } /* socreate will create an initial so_count */ error = socreate(domain, new_so, type, protocol); if (error == 0) { /* * This is an in-kernel socket */ (*new_so)->so_flags1 |= SOF1_IN_KERNEL_SOCKET; INC_ATOMIC_INT64_LIM(net_api_stats.nas_socket_in_kernel_total); if (is_internal) { INC_ATOMIC_INT64_LIM(net_api_stats.nas_socket_in_kernel_os_total); } /* see comments in sock_setupcall() */ if (callback != NULL) { sock_setupcall(*new_so, callback, context); } /* * last_pid and last_upid should be zero for sockets * created using sock_socket */ (*new_so)->last_pid = 0; (*new_so)->last_upid = 0; } return error; } errno_t sock_socket_internal(int domain, int type, int protocol, sock_upcall callback, void *context, socket_t *new_so) { return sock_socket_common(domain, type, protocol, callback, context, new_so, true); } errno_t sock_socket(int domain, int type, int protocol, sock_upcall callback, void *context, socket_t *new_so) { return sock_socket_common(domain, type, protocol, callback, context, new_so, false); } void sock_close(socket_t sock) { if (sock == NULL) { return; } soclose(sock); } /* Do we want this to be APPLE_PRIVATE API?: YES (LD 12/23/04) */ void sock_retain(socket_t sock) { if (sock == NULL) { return; } socket_lock(sock, 1); sock->so_retaincnt++; sock->so_usecount++; /* add extra reference for holding the socket */ socket_unlock(sock, 1); } /* Do we want this to be APPLE_PRIVATE API? */ void sock_release(socket_t sock) { if (sock == NULL) { return; } socket_lock(sock, 1); if (sock->so_upcallusecount > 0) { soclose_wait_locked(sock); } sock->so_retaincnt--; if (sock->so_retaincnt < 0) { panic("%s: negative retain count (%d) for sock=%p", __func__, sock->so_retaincnt, sock); /* NOTREACHED */ } /* * Check SS_NOFDREF in case a close happened as sock_retain() * was grabbing the lock */ if ((sock->so_retaincnt == 0) && (sock->so_usecount == 2) && (!(sock->so_state & SS_NOFDREF) || (sock->so_flags & SOF_MP_SUBFLOW))) { /* close socket only if the FD is not holding it */ soclose_locked(sock); } else { /* remove extra reference holding the socket */ VERIFY(sock->so_usecount > 1); sock->so_usecount--; } socket_unlock(sock, 1); } errno_t sock_setpriv(socket_t sock, int on) { if (sock == NULL) { return EINVAL; } socket_lock(sock, 1); if (on) { sock->so_state |= SS_PRIV; } else { sock->so_state &= ~SS_PRIV; } socket_unlock(sock, 1); return 0; } int sock_isconnected(socket_t sock) { int retval; socket_lock(sock, 1); retval = ((sock->so_state & SS_ISCONNECTED) ? 1 : 0); socket_unlock(sock, 1); return retval; } int sock_isnonblocking(socket_t sock) { int retval; socket_lock(sock, 1); retval = ((sock->so_state & SS_NBIO) ? 1 : 0); socket_unlock(sock, 1); return retval; } errno_t sock_gettype(socket_t sock, int *outDomain, int *outType, int *outProtocol) { socket_lock(sock, 1); if (outDomain != NULL) { *outDomain = SOCK_DOM(sock); } if (outType != NULL) { *outType = sock->so_type; } if (outProtocol != NULL) { *outProtocol = SOCK_PROTO(sock); } socket_unlock(sock, 1); return 0; } /* * Return the listening socket of a pre-accepted socket. It returns the * listener (so_head) value of a given socket. This is intended to be * called by a socket filter during a filter attach (sf_attach) callback. * The value returned by this routine is safe to be used only in the * context of that callback, because we hold the listener's lock across * the sflt_initsock() call. */ socket_t sock_getlistener(socket_t sock) { return sock->so_head; } static inline void sock_set_tcp_stream_priority(socket_t sock) { if ((SOCK_DOM(sock) == PF_INET || SOCK_DOM(sock) == PF_INET6) && SOCK_TYPE(sock) == SOCK_STREAM) { set_tcp_stream_priority(sock); } } /* * Caller must have ensured socket is valid and won't be going away. */ void socket_set_traffic_mgt_flags_locked(socket_t sock, u_int8_t flags) { u_int32_t soflags1 = 0; if ((flags & TRAFFIC_MGT_SO_BACKGROUND)) { soflags1 |= SOF1_TRAFFIC_MGT_SO_BACKGROUND; } if ((flags & TRAFFIC_MGT_TCP_RECVBG)) { soflags1 |= SOF1_TRAFFIC_MGT_TCP_RECVBG; } (void) OSBitOrAtomic(soflags1, &sock->so_flags1); sock_set_tcp_stream_priority(sock); } void socket_set_traffic_mgt_flags(socket_t sock, u_int8_t flags) { socket_lock(sock, 1); socket_set_traffic_mgt_flags_locked(sock, flags); socket_unlock(sock, 1); } /* * Caller must have ensured socket is valid and won't be going away. */ void socket_clear_traffic_mgt_flags_locked(socket_t sock, u_int8_t flags) { u_int32_t soflags1 = 0; if ((flags & TRAFFIC_MGT_SO_BACKGROUND)) { soflags1 |= SOF1_TRAFFIC_MGT_SO_BACKGROUND; } if ((flags & TRAFFIC_MGT_TCP_RECVBG)) { soflags1 |= SOF1_TRAFFIC_MGT_TCP_RECVBG; } (void) OSBitAndAtomic(~soflags1, &sock->so_flags1); sock_set_tcp_stream_priority(sock); } void socket_clear_traffic_mgt_flags(socket_t sock, u_int8_t flags) { socket_lock(sock, 1); socket_clear_traffic_mgt_flags_locked(sock, flags); socket_unlock(sock, 1); } /* * Caller must have ensured socket is valid and won't be going away. */ errno_t socket_defunct(struct proc *p, socket_t so, int level) { errno_t retval; if (level != SHUTDOWN_SOCKET_LEVEL_DISCONNECT_SVC && level != SHUTDOWN_SOCKET_LEVEL_DISCONNECT_ALL) { return EINVAL; } socket_lock(so, 1); /* * SHUTDOWN_SOCKET_LEVEL_DISCONNECT_SVC level is meant to tear down * all of mDNSResponder IPC sockets, currently those of AF_UNIX; note * that this is an implementation artifact of mDNSResponder. We do * a quick test against the socket buffers for SB_UNIX, since that * would have been set by unp_attach() at socket creation time. */ if (level == SHUTDOWN_SOCKET_LEVEL_DISCONNECT_SVC && (so->so_rcv.sb_flags & so->so_snd.sb_flags & SB_UNIX) != SB_UNIX) { socket_unlock(so, 1); return EOPNOTSUPP; } retval = sosetdefunct(p, so, level, TRUE); if (retval == 0) { retval = sodefunct(p, so, level); } socket_unlock(so, 1); return retval; } void sock_setupcalls_locked(socket_t sock, sock_upcall rcallback, void *rcontext, sock_upcall wcallback, void *wcontext, int locked) { if (rcallback != NULL) { sock->so_rcv.sb_flags |= SB_UPCALL; if (locked) { sock->so_rcv.sb_flags |= SB_UPCALL_LOCK; } sock->so_rcv.sb_upcall = rcallback; sock->so_rcv.sb_upcallarg = rcontext; } else { sock->so_rcv.sb_flags &= ~(SB_UPCALL | SB_UPCALL_LOCK); sock->so_rcv.sb_upcall = NULL; sock->so_rcv.sb_upcallarg = NULL; } if (wcallback != NULL) { sock->so_snd.sb_flags |= SB_UPCALL; if (locked) { sock->so_snd.sb_flags |= SB_UPCALL_LOCK; } sock->so_snd.sb_upcall = wcallback; sock->so_snd.sb_upcallarg = wcontext; } else { sock->so_snd.sb_flags &= ~(SB_UPCALL | SB_UPCALL_LOCK); sock->so_snd.sb_upcall = NULL; sock->so_snd.sb_upcallarg = NULL; } } errno_t sock_setupcall(socket_t sock, sock_upcall callback, void *context) { if (sock == NULL) { return EINVAL; } /* * Note that we don't wait for any in progress upcall to complete. * On embedded, sock_setupcall() causes both read and write * callbacks to be set; on desktop, only read callback is set * to maintain legacy KPI behavior. * * The newer sock_setupcalls() KPI should be used instead to set * the read and write callbacks and their respective parameters. */ socket_lock(sock, 1); #if defined(__arm64__) sock_setupcalls_locked(sock, callback, context, callback, context, 0); #else /* defined(__arm64__) */ sock_setupcalls_locked(sock, callback, context, NULL, NULL, 0); #endif /* defined(__arm64__) */ socket_unlock(sock, 1); return 0; } errno_t sock_setupcalls(socket_t sock, sock_upcall rcallback, void *rcontext, sock_upcall wcallback, void *wcontext) { if (sock == NULL) { return EINVAL; } /* * Note that we don't wait for any in progress upcall to complete. */ socket_lock(sock, 1); sock_setupcalls_locked(sock, rcallback, rcontext, wcallback, wcontext, 0); socket_unlock(sock, 1); return 0; } void sock_catchevents_locked(socket_t sock, sock_evupcall ecallback, void *econtext, uint32_t emask) { socket_lock_assert_owned(sock); /* * Note that we don't wait for any in progress upcall to complete. */ if (ecallback != NULL) { sock->so_event = ecallback; sock->so_eventarg = econtext; sock->so_eventmask = emask; } else { sock->so_event = sonullevent; sock->so_eventarg = NULL; sock->so_eventmask = 0; } } errno_t sock_catchevents(socket_t sock, sock_evupcall ecallback, void *econtext, uint32_t emask) { if (sock == NULL) { return EINVAL; } socket_lock(sock, 1); sock_catchevents_locked(sock, ecallback, econtext, emask); socket_unlock(sock, 1); return 0; } /* * Returns true whether or not a socket belongs to the kernel. */ int sock_iskernel(socket_t so) { return so && so->last_pid == 0; }