+ /*
+ * The process of preparing the TCB list is too time-consuming and
+ * resource-intensive to repeat twice on every request.
+ */
+ lck_rw_lock_shared(udbinfo.ipi_lock);
+ if (req->oldptr == USER_ADDR_NULL) {
+ n = udbinfo.ipi_count;
+ req->oldidx =
+ 2 * (sizeof(xig)) + (n + n / 8) * sizeof(struct xinpcb64);
+ lck_rw_done(udbinfo.ipi_lock);
+ return 0;
+ }
+
+ if (req->newptr != USER_ADDR_NULL) {
+ lck_rw_done(udbinfo.ipi_lock);
+ return EPERM;
+ }
+
+ /*
+ * OK, now we're committed to doing something.
+ */
+ gencnt = udbinfo.ipi_gencnt;
+ n = udbinfo.ipi_count;
+
+ bzero(&xig, sizeof(xig));
+ xig.xig_len = sizeof(xig);
+ xig.xig_count = n;
+ xig.xig_gen = gencnt;
+ xig.xig_sogen = so_gencnt;
+ error = SYSCTL_OUT(req, &xig, sizeof(xig));
+ if (error) {
+ lck_rw_done(udbinfo.ipi_lock);
+ return error;
+ }
+ /*
+ * We are done if there is no pcb
+ */
+ if (n == 0) {
+ lck_rw_done(udbinfo.ipi_lock);
+ return 0;
+ }
+
+ inp_list = _MALLOC(n * sizeof(*inp_list), M_TEMP, M_WAITOK);
+ if (inp_list == 0) {
+ lck_rw_done(udbinfo.ipi_lock);
+ return ENOMEM;
+ }
+
+ for (inp = LIST_FIRST(udbinfo.ipi_listhead), i = 0; inp && i < n;
+ inp = LIST_NEXT(inp, inp_list)) {
+ if (inp->inp_gencnt <= gencnt &&
+ inp->inp_state != INPCB_STATE_DEAD) {
+ inp_list[i++] = inp;
+ }
+ }
+ n = i;
+
+ error = 0;
+ for (i = 0; i < n; i++) {
+ struct xinpcb64 xi;
+
+ inp = inp_list[i];
+
+ if (in_pcb_checkstate(inp, WNT_ACQUIRE, 0) == WNT_STOPUSING) {
+ continue;
+ }
+ udp_lock(inp->inp_socket, 1, 0);
+ if (in_pcb_checkstate(inp, WNT_RELEASE, 1) == WNT_STOPUSING) {
+ udp_unlock(inp->inp_socket, 1, 0);
+ continue;
+ }
+ if (inp->inp_gencnt > gencnt) {
+ udp_unlock(inp->inp_socket, 1, 0);
+ continue;
+ }
+
+ bzero(&xi, sizeof(xi));
+ xi.xi_len = sizeof(xi);
+ inpcb_to_xinpcb64(inp, &xi);
+ if (inp->inp_socket) {
+ sotoxsocket64(inp->inp_socket, &xi.xi_socket);
+ }
+
+ udp_unlock(inp->inp_socket, 1, 0);
+
+ error = SYSCTL_OUT(req, &xi, sizeof(xi));
+ }
+ if (!error) {
+ /*
+ * Give the user an updated idea of our state.
+ * If the generation differs from what we told
+ * her before, she knows that something happened
+ * while we were processing this request, and it
+ * might be necessary to retry.
+ */
+ bzero(&xig, sizeof(xig));
+ xig.xig_len = sizeof(xig);
+ xig.xig_gen = udbinfo.ipi_gencnt;
+ xig.xig_sogen = so_gencnt;
+ xig.xig_count = udbinfo.ipi_count;
+ error = SYSCTL_OUT(req, &xig, sizeof(xig));
+ }
+ FREE(inp_list, M_TEMP);
+ lck_rw_done(udbinfo.ipi_lock);
+ return error;
+}
+
+SYSCTL_PROC(_net_inet_udp, OID_AUTO, pcblist64,
+ CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, udp_pcblist64,
+ "S,xinpcb64", "List of active UDP sockets");
+
+#endif /* !CONFIG_EMBEDDED */
+
+static int
+udp_pcblist_n SYSCTL_HANDLER_ARGS
+{
+#pragma unused(oidp, arg1, arg2)
+ return get_pcblist_n(IPPROTO_UDP, req, &udbinfo);
+}
+
+SYSCTL_PROC(_net_inet_udp, OID_AUTO, pcblist_n,
+ CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED, 0, 0, udp_pcblist_n,
+ "S,xinpcb_n", "List of active UDP sockets");
+
+__private_extern__ void
+udp_get_ports_used(uint32_t ifindex, int protocol, uint32_t flags,
+ bitstr_t *bitfield)
+{
+ inpcb_get_ports_used(ifindex, protocol, flags, bitfield,
+ &udbinfo);
+}
+
+__private_extern__ uint32_t
+udp_count_opportunistic(unsigned int ifindex, u_int32_t flags)
+{
+ return inpcb_count_opportunistic(ifindex, &udbinfo, flags);
+}
+
+__private_extern__ uint32_t
+udp_find_anypcb_byaddr(struct ifaddr *ifa)
+{
+ return inpcb_find_anypcb_byaddr(ifa, &udbinfo);
+}
+
+static int
+udp_check_pktinfo(struct mbuf *control, struct ifnet **outif,
+ struct in_addr *laddr)
+{
+ struct cmsghdr *cm = 0;
+ struct in_pktinfo *pktinfo;
+ struct ifnet *ifp;
+
+ if (outif != NULL) {
+ *outif = NULL;
+ }
+
+ /*
+ * XXX: Currently, we assume all the optional information is stored
+ * in a single mbuf.
+ */
+ if (control->m_next) {
+ return EINVAL;
+ }
+
+ if (control->m_len < CMSG_LEN(0)) {
+ return EINVAL;
+ }
+
+ for (cm = M_FIRST_CMSGHDR(control);
+ is_cmsg_valid(control, cm);
+ cm = M_NXT_CMSGHDR(control, cm)) {
+ if (cm->cmsg_level != IPPROTO_IP ||
+ cm->cmsg_type != IP_PKTINFO) {
+ continue;
+ }
+
+ if (cm->cmsg_len != CMSG_LEN(sizeof(struct in_pktinfo))) {
+ return EINVAL;
+ }
+
+ pktinfo = (struct in_pktinfo *)(void *)CMSG_DATA(cm);
+
+ /* Check for a valid ifindex in pktinfo */
+ ifnet_head_lock_shared();
+
+ if (pktinfo->ipi_ifindex > if_index) {
+ ifnet_head_done();
+ return ENXIO;
+ }
+
+ /*
+ * If ipi_ifindex is specified it takes precedence
+ * over ipi_spec_dst.
+ */
+ if (pktinfo->ipi_ifindex) {
+ ifp = ifindex2ifnet[pktinfo->ipi_ifindex];
+ if (ifp == NULL) {
+ ifnet_head_done();
+ return ENXIO;
+ }
+ if (outif != NULL) {
+ ifnet_reference(ifp);
+ *outif = ifp;
+ }
+ ifnet_head_done();
+ laddr->s_addr = INADDR_ANY;
+ break;
+ }
+
+ ifnet_head_done();
+
+ /*
+ * Use the provided ipi_spec_dst address for temp
+ * source address.
+ */
+ *laddr = pktinfo->ipi_spec_dst;
+ break;
+ }
+ return 0;
+}
+
+int
+udp_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr,
+ struct mbuf *control, struct proc *p)
+{
+ struct udpiphdr *ui;
+ int len = m->m_pkthdr.len;
+ struct sockaddr_in *sin;
+ struct in_addr origladdr, laddr, faddr, pi_laddr;
+ u_short lport, fport;
+ int error = 0, udp_dodisconnect = 0, pktinfo = 0;
+ struct socket *so = inp->inp_socket;
+ int soopts = 0;
+ struct mbuf *inpopts;
+ struct ip_moptions *mopts;
+ struct route ro;
+ struct ip_out_args ipoa;
+#if CONTENT_FILTER
+ struct m_tag *cfil_tag = NULL;
+ bool cfil_faddr_use = false;
+ uint32_t cfil_so_state_change_cnt = 0;
+ short cfil_so_options = 0;
+ struct sockaddr *cfil_faddr = NULL;
+#endif
+
+ bzero(&ipoa, sizeof(ipoa));
+ ipoa.ipoa_boundif = IFSCOPE_NONE;
+ ipoa.ipoa_flags = IPOAF_SELECT_SRCIF;
+
+ struct ifnet *outif = NULL;
+ struct flowadv *adv = &ipoa.ipoa_flowadv;
+ int sotc = SO_TC_UNSPEC;
+ int netsvctype = _NET_SERVICE_TYPE_UNSPEC;
+ struct ifnet *origoutifp = NULL;
+ int flowadv = 0;
+ int tos = IPTOS_UNSPEC;
+
+ /* Enable flow advisory only when connected */
+ flowadv = (so->so_state & SS_ISCONNECTED) ? 1 : 0;
+ pi_laddr.s_addr = INADDR_ANY;
+
+ KERNEL_DEBUG(DBG_FNC_UDP_OUTPUT | DBG_FUNC_START, 0, 0, 0, 0, 0);
+
+ socket_lock_assert_owned(so);
+
+#if CONTENT_FILTER
+ /*
+ * If socket is subject to UDP Content Filter and no addr is passed in,
+ * retrieve CFIL saved state from mbuf and use it if necessary.
+ */
+ if (so->so_cfil_db && !addr) {
+ cfil_tag = cfil_udp_get_socket_state(m, &cfil_so_state_change_cnt, &cfil_so_options, &cfil_faddr);
+ if (cfil_tag) {
+ sin = (struct sockaddr_in *)(void *)cfil_faddr;
+ if (inp && inp->inp_faddr.s_addr == INADDR_ANY) {
+ /*
+ * Socket is unconnected, simply use the saved faddr as 'addr' to go through
+ * the connect/disconnect logic.
+ */
+ addr = (struct sockaddr *)cfil_faddr;
+ } else if ((so->so_state_change_cnt != cfil_so_state_change_cnt) &&
+ (inp->inp_fport != sin->sin_port ||
+ inp->inp_faddr.s_addr != sin->sin_addr.s_addr)) {
+ /*
+ * Socket is connected but socket state and dest addr/port changed.
+ * We need to use the saved faddr info.
+ */
+ cfil_faddr_use = true;
+ }
+ }
+ }
+#endif
+
+ if (control != NULL) {
+ tos = so_tos_from_control(control);
+ sotc = so_tc_from_control(control, &netsvctype);
+ VERIFY(outif == NULL);
+ error = udp_check_pktinfo(control, &outif, &pi_laddr);
+ m_freem(control);
+ control = NULL;
+ if (error) {
+ goto release;
+ }
+ pktinfo++;
+ if (outif != NULL) {
+ ipoa.ipoa_boundif = outif->if_index;
+ }
+ }
+ if (sotc == SO_TC_UNSPEC) {
+ sotc = so->so_traffic_class;
+ netsvctype = so->so_netsvctype;
+ }