X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/b0d623f7f2ae71ed96e60569f61f9a9a27016e80..cb3231590a3c94ab4375e2228bd5e86b0cf1ad7e:/bsd/netinet6/udp6_usrreq.c diff --git a/bsd/netinet6/udp6_usrreq.c b/bsd/netinet6/udp6_usrreq.c index fed294b90..9b4c3a16e 100644 --- a/bsd/netinet6/udp6_usrreq.c +++ b/bsd/netinet6/udp6_usrreq.c @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2000-2019 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@ + */ + /* $FreeBSD: src/sys/netinet6/udp6_usrreq.c,v 1.6.2.6 2001/07/29 19:32:40 ume Exp $ */ /* $KAME: udp6_usrreq.c,v 1.27 2001/05/21 05:45:10 jinmei Exp $ */ @@ -64,11 +92,10 @@ * * @(#)udp_var.h 8.1 (Berkeley) 6/10/93 */ - -#include #include #include #include +#include #include #include #include @@ -83,6 +110,9 @@ #include #include #include +#include +#include +#include #include #include @@ -102,142 +132,170 @@ #if IPSEC #include #include +#include extern int ipsec_bypass; -#endif /*IPSEC*/ -extern lck_mtx_t *nd6_mutex; +extern int esp_udp_encap_port; +#endif /* IPSEC */ + +#if NECP +#include +#endif /* NECP */ + +#if FLOW_DIVERT +#include +#endif /* FLOW_DIVERT */ + +#if CONTENT_FILTER +#include +#endif /* CONTENT_FILTER */ /* * UDP protocol inplementation. * Per RFC 768, August, 1980. */ -extern struct protosw inetsw[]; -static int in6_mcmatch(struct inpcb *, struct in6_addr *, struct ifnet *); -static int udp6_detach(struct socket *so); +static int udp6_abort(struct socket *); +static int udp6_attach(struct socket *, int, struct proc *); +static int udp6_bind(struct socket *, struct sockaddr *, struct proc *); +static int udp6_connectx(struct socket *, struct sockaddr *, + struct sockaddr *, struct proc *, uint32_t, sae_associd_t, + sae_connid_t *, uint32_t, void *, uint32_t, struct uio *, user_ssize_t *); +static int udp6_detach(struct socket *); +static int udp6_disconnect(struct socket *); +static int udp6_disconnectx(struct socket *, sae_associd_t, sae_connid_t); +static int udp6_send(struct socket *, int, struct mbuf *, struct sockaddr *, + struct mbuf *, struct proc *); static void udp6_append(struct inpcb *, struct ip6_hdr *, - struct sockaddr_in6 *, struct mbuf *, int); - -extern void ipfwsyslog( int level, const char *format,...); -extern int fw_verbose; + struct sockaddr_in6 *, struct mbuf *, int, struct ifnet *); +static int udp6_input_checksum(struct mbuf *, struct udphdr *, int, int); -#if IPFIREWALL -#define log_in_vain_log( a ) { \ - if ( (log_in_vain == 3 ) && (fw_verbose == 2)) { /* Apple logging, log to ipfw.log */ \ - ipfwsyslog a ; \ - } \ - else log a ; \ -} -#else -#define log_in_vain_log( a ) { log a; } -#endif - -static int -in6_mcmatch( - struct inpcb *in6p, - register struct in6_addr *ia6, - struct ifnet *ifp) -{ - struct ip6_moptions *im6o = in6p->in6p_moptions; - struct in6_multi_mship *imm; - - if (im6o == NULL) - return 0; - - lck_mtx_lock(nd6_mutex); - for (imm = im6o->im6o_memberships.lh_first; imm != NULL; - imm = imm->i6mm_chain.le_next) { - if ((ifp == NULL || - imm->i6mm_maddr->in6m_ifp == ifp) && - IN6_ARE_ADDR_EQUAL(&imm->i6mm_maddr->in6m_addr, - ia6)) { - lck_mtx_unlock(nd6_mutex); - return 1; - } - } - lck_mtx_unlock(nd6_mutex); - return 0; -} +struct pr_usrreqs udp6_usrreqs = { + .pru_abort = udp6_abort, + .pru_attach = udp6_attach, + .pru_bind = udp6_bind, + .pru_connect = udp6_connect, + .pru_connectx = udp6_connectx, + .pru_control = in6_control, + .pru_detach = udp6_detach, + .pru_disconnect = udp6_disconnect, + .pru_disconnectx = udp6_disconnectx, + .pru_peeraddr = in6_mapped_peeraddr, + .pru_send = udp6_send, + .pru_shutdown = udp_shutdown, + .pru_sockaddr = in6_mapped_sockaddr, + .pru_sosend = sosend, + .pru_soreceive = soreceive, + .pru_soreceive_list = soreceive_list, +}; /* * subroutine of udp6_input(), mainly for source code readability. */ static void udp6_append(struct inpcb *last, struct ip6_hdr *ip6, - struct sockaddr_in6 *udp_in6, struct mbuf *n, int off) + struct sockaddr_in6 *udp_in6, struct mbuf *n, int off, struct ifnet *ifp) { +#pragma unused(ip6) struct mbuf *opts = NULL; + int ret = 0; + boolean_t cell = IFNET_IS_CELLULAR(ifp); + boolean_t wifi = (!cell && IFNET_IS_WIFI(ifp)); + boolean_t wired = (!wifi && IFNET_IS_WIRED(ifp)); #if CONFIG_MACF_NET if (mac_inpcb_check_deliver(last, n, AF_INET6, SOCK_DGRAM) != 0) { m_freem(n); return; } -#endif - if (last->in6p_flags & IN6P_CONTROLOPTS || - last->in6p_socket->so_options & SO_TIMESTAMP) - ip6_savecontrol(last, &opts, ip6, n); - +#endif /* CONFIG_MACF_NET */ + if ((last->in6p_flags & INP_CONTROLOPTS) != 0 || + (last->in6p_socket->so_options & SO_TIMESTAMP) != 0 || + (last->in6p_socket->so_options & SO_TIMESTAMP_MONOTONIC) != 0 || + (last->in6p_socket->so_options & SO_TIMESTAMP_CONTINUOUS) != 0) { + ret = ip6_savecontrol(last, n, &opts); + if (ret != 0) { + m_freem(n); + m_freem(opts); + return; + } + } m_adj(n, off); + if (nstat_collect) { + INP_ADD_STAT(last, cell, wifi, wired, rxpackets, 1); + INP_ADD_STAT(last, cell, wifi, wired, rxbytes, n->m_pkthdr.len); + inp_set_activity_bitmap(last); + } + so_recv_data_stat(last->in6p_socket, n, 0); if (sbappendaddr(&last->in6p_socket->so_rcv, - (struct sockaddr *)udp_in6, n, opts, NULL) == 0) + (struct sockaddr *)udp_in6, n, opts, NULL) == 0) { udpstat.udps_fullsock++; - else + } else { sorwakeup(last->in6p_socket); + } } int -udp6_input( - struct mbuf **mp, - int *offp) +udp6_input(struct mbuf **mp, int *offp, int proto) { +#pragma unused(proto) struct mbuf *m = *mp; - register struct ip6_hdr *ip6; - register struct udphdr *uh; - register struct inpcb *in6p; + struct ifnet *ifp; + struct ip6_hdr *ip6; + struct udphdr *uh; + struct inpcb *in6p; struct mbuf *opts = NULL; int off = *offp; - int plen, ulen; + int plen, ulen, ret = 0; + boolean_t cell, wifi, wired; struct sockaddr_in6 udp_in6; struct inpcbinfo *pcbinfo = &udbinfo; + struct sockaddr_in6 fromsa; IP6_EXTHDR_CHECK(m, off, sizeof(struct udphdr), return IPPROTO_DONE); - ip6 = mtod(m, struct ip6_hdr *); + /* Expect 32-bit aligned data pointer on strict-align platforms */ + MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(m); -#if defined(NFAITH) && 0 < NFAITH - if (faithprefix(&ip6->ip6_dst)) { - /* XXX send icmp6 host/port unreach? */ - m_freem(m); - return IPPROTO_DONE; - } -#endif + ifp = m->m_pkthdr.rcvif; + ip6 = mtod(m, struct ip6_hdr *); + cell = IFNET_IS_CELLULAR(ifp); + wifi = (!cell && IFNET_IS_WIFI(ifp)); + wired = (!wifi && IFNET_IS_WIRED(ifp)); udpstat.udps_ipackets++; plen = ntohs(ip6->ip6_plen) - off + sizeof(*ip6); - uh = (struct udphdr *)((caddr_t)ip6 + off); + uh = (struct udphdr *)(void *)((caddr_t)ip6 + off); ulen = ntohs((u_short)uh->uh_ulen); if (plen != ulen) { udpstat.udps_badlen++; + IF_UDP_STATINC(ifp, badlength); + goto bad; + } + + /* destination port of 0 is illegal, based on RFC768. */ + if (uh->uh_dport == 0) { + IF_UDP_STATINC(ifp, port0); goto bad; } /* * Checksum extended UDP header and data. */ -#ifndef __APPLE__ - if (uh->uh_sum == 0) - udpstat.udps_nosum++; -#endif - else if (in6_cksum(m, IPPROTO_UDP, off, ulen) != 0) { - udpstat.udps_badsum++; + if (udp6_input_checksum(m, uh, off, ulen)) { goto bad; } + /* + * Construct sockaddr format source address. + */ + init_sin6(&fromsa, m); + fromsa.sin6_port = uh->uh_sport; + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { int reuse_sock = 0, mcast_delivered = 0; - struct mbuf *n = NULL; + struct ip6_moptions *imo; /* * Deliver a multicast datagram to all sockets @@ -279,19 +337,30 @@ udp6_input( * Locate pcb(s) for datagram. * (Algorithm copied from raw_intr().) */ - lck_rw_lock_shared(pcbinfo->mtx); + lck_rw_lock_shared(pcbinfo->ipi_lock); LIST_FOREACH(in6p, &udb, inp_list) { +#if IPSEC + int skipit; +#endif /* IPSEC */ - if ((in6p->inp_vflag & INP_IPV6) == 0) + if ((in6p->inp_vflag & INP_IPV6) == 0) { continue; + } - if (in_pcb_checkstate(in6p, WNT_ACQUIRE, 0) == WNT_STOPUSING) + if (inp_restricted_recv(in6p, ifp)) { continue; + } - udp_lock(in6p->in6p_socket, 1, 0); + if (in_pcb_checkstate(in6p, WNT_ACQUIRE, 0) == + WNT_STOPUSING) { + continue; + } - if (in_pcb_checkstate(in6p, WNT_RELEASE, 1) == WNT_STOPUSING) { + udp_lock(in6p->in6p_socket, 1, 0); + + if (in_pcb_checkstate(in6p, WNT_RELEASE, 1) == + WNT_STOPUSING) { udp_unlock(in6p->in6p_socket, 1, 0); continue; } @@ -299,56 +368,74 @@ udp6_input( udp_unlock(in6p->in6p_socket, 1, 0); continue; } - if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr)) { - if (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, - &ip6->ip6_dst) && - !in6_mcmatch(in6p, &ip6->ip6_dst, - m->m_pkthdr.rcvif)) { + + /* + * Handle socket delivery policy for any-source + * and source-specific multicast. [RFC3678] + */ + imo = in6p->in6p_moptions; + if (imo && IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + struct sockaddr_in6 mcaddr; + int blocked; + + IM6O_LOCK(imo); + bzero(&mcaddr, sizeof(struct sockaddr_in6)); + mcaddr.sin6_len = sizeof(struct sockaddr_in6); + mcaddr.sin6_family = AF_INET6; + mcaddr.sin6_addr = ip6->ip6_dst; + + blocked = im6o_mc_filter(imo, ifp, + &mcaddr, &fromsa); + IM6O_UNLOCK(imo); + if (blocked != MCAST_PASS) { udp_unlock(in6p->in6p_socket, 1, 0); + if (blocked == MCAST_NOTSMEMBER || + blocked == MCAST_MUTED) { + udpstat.udps_filtermcast++; + } continue; } } - if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr)) { - if (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, - &ip6->ip6_src) || - in6p->in6p_fport != uh->uh_sport) { - udp_unlock(in6p->in6p_socket, 1, 0); - continue; - } + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr) && + (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, + &ip6->ip6_src) || + in6p->in6p_fport != uh->uh_sport)) { + udp_unlock(in6p->in6p_socket, 1, 0); + continue; } reuse_sock = in6p->inp_socket->so_options & (SO_REUSEPORT | SO_REUSEADDR); +#if NECP + skipit = 0; + if (!necp_socket_is_allowed_to_send_recv_v6(in6p, + uh->uh_dport, uh->uh_sport, &ip6->ip6_dst, + &ip6->ip6_src, ifp, NULL, NULL, NULL)) { + /* do not inject data to pcb */ + skipit = 1; + } + if (skipit == 0) +#endif /* NECP */ { -#if IPSEC - int skipit = 0; - /* Check AH/ESP integrity. */ - if (ipsec_bypass == 0) { - if (ipsec6_in_reject_so(m, in6p->inp_socket)) { - IPSEC_STAT_INCREMENT(ipsec6stat.in_polvio); - /* do not inject data to pcb */ - skipit = 1; - } + struct mbuf *n = NULL; + /* + * KAME NOTE: do not + * m_copy(m, offset, ...) below. + * sbappendaddr() expects M_PKTHDR, + * and m_copy() will copy M_PKTHDR + * only if offset is 0. + */ + if (reuse_sock) { + n = m_copy(m, 0, M_COPYALL); } - if (skipit == 0) -#endif /*IPSEC*/ - { - /* - * KAME NOTE: do not - * m_copy(m, offset, ...) below. - * sbappendaddr() expects M_PKTHDR, - * and m_copy() will copy M_PKTHDR - * only if offset is 0. - */ - if (reuse_sock) - n = m_copy(m, 0, M_COPYALL); - udp6_append(in6p, ip6, &udp_in6, m, - off + sizeof (struct udphdr)); - mcast_delivered++; - } - udp_unlock(in6p->in6p_socket, 1, 0); + udp6_append(in6p, ip6, &udp_in6, m, + off + sizeof(struct udphdr), ifp); + mcast_delivered++; + m = n; } + udp_unlock(in6p->in6p_socket, 1, 0); + /* * Don't look for additional matches if this one does * not have either the SO_REUSEPORT or SO_REUSEADDR @@ -357,15 +444,23 @@ udp6_input( * port. It assumes that an application will never * clear these options after setting them. */ - if (reuse_sock == 0 || ((m = n) == NULL)) + if (reuse_sock == 0 || m == NULL) { break; + } + + /* + * Expect 32-bit aligned data pointer on strict-align + * platforms. + */ + MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(m); + /* * Recompute IP and UDP header pointers for new mbuf */ ip6 = mtod(m, struct ip6_hdr *); - uh = (struct udphdr *)((caddr_t)ip6 + off); + uh = (struct udphdr *)(void *)((caddr_t)ip6 + off); } - lck_rw_done(pcbinfo->mtx); + lck_rw_done(pcbinfo->ipi_lock); if (mcast_delivered == 0) { /* @@ -374,62 +469,106 @@ udp6_input( * for a broadcast or multicast datgram.) */ udpstat.udps_noport++; -#ifndef __APPLE__ udpstat.udps_noportmcast++; -#endif + IF_UDP_STATINC(ifp, port_unreach); goto bad; } - if (reuse_sock != 0) /* free the extra copy of mbuf */ + /* free the extra copy of mbuf or skipped by NECP */ + if (m != NULL) { m_freem(m); + } return IPPROTO_DONE; } + +#if IPSEC + /* + * UDP to port 4500 with a payload where the first four bytes are + * not zero is a UDP encapsulated IPsec packet. Packets where + * the payload is one byte and that byte is 0xFF are NAT keepalive + * packets. Decapsulate the ESP packet and carry on with IPsec input + * or discard the NAT keep-alive. + */ + if (ipsec_bypass == 0 && (esp_udp_encap_port & 0xFFFF) != 0 && + (uh->uh_dport == ntohs((u_short)esp_udp_encap_port) || + uh->uh_sport == ntohs((u_short)esp_udp_encap_port))) { + int payload_len = ulen - sizeof(struct udphdr) > 4 ? 4 : + ulen - sizeof(struct udphdr); + + if (m->m_len < off + sizeof(struct udphdr) + payload_len) { + if ((m = m_pullup(m, off + sizeof(struct udphdr) + + payload_len)) == NULL) { + udpstat.udps_hdrops++; + goto bad; + } + /* + * Expect 32-bit aligned data pointer on strict-align + * platforms. + */ + MBUF_STRICT_DATA_ALIGNMENT_CHECK_32(m); + + ip6 = mtod(m, struct ip6_hdr *); + uh = (struct udphdr *)(void *)((caddr_t)ip6 + off); + } + /* Check for NAT keepalive packet */ + if (payload_len == 1 && *(u_int8_t*) + ((caddr_t)uh + sizeof(struct udphdr)) == 0xFF) { + goto bad; + } else if (payload_len == 4 && *(u_int32_t*)(void *) + ((caddr_t)uh + sizeof(struct udphdr)) != 0) { + /* UDP encapsulated IPsec packet to pass through NAT */ + /* preserve the udp header */ + *offp = off + sizeof(struct udphdr); + return esp6_input(mp, offp, IPPROTO_UDP); + } + } +#endif /* IPSEC */ + /* * Locate pcb for datagram. */ in6p = in6_pcblookup_hash(&udbinfo, &ip6->ip6_src, uh->uh_sport, - &ip6->ip6_dst, uh->uh_dport, 1, - m->m_pkthdr.rcvif); - if (in6p == 0) { - if (log_in_vain) { + &ip6->ip6_dst, uh->uh_dport, 1, m->m_pkthdr.rcvif); + if (in6p == NULL) { + IF_UDP_STATINC(ifp, port_unreach); + + if (udp_log_in_vain) { char buf[INET6_ADDRSTRLEN]; strlcpy(buf, ip6_sprintf(&ip6->ip6_dst), sizeof(buf)); - if (log_in_vain != 3) - log(LOG_INFO, - "Connection attempt to UDP %s:%d from %s:%d\n", - buf, ntohs(uh->uh_dport), - ip6_sprintf(&ip6->ip6_src), ntohs(uh->uh_sport)); - else if (!(m->m_flags & (M_BCAST | M_MCAST)) && - !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &ip6->ip6_src)) - log_in_vain_log((LOG_INFO, - "Connection attempt to UDP %s:%d from %s:%d\n", - buf, ntohs(uh->uh_dport), - ip6_sprintf(&ip6->ip6_src), ntohs(uh->uh_sport))); + if (udp_log_in_vain < 3) { + log(LOG_INFO, "Connection attempt to UDP " + "%s:%d from %s:%d\n", buf, + ntohs(uh->uh_dport), + ip6_sprintf(&ip6->ip6_src), + ntohs(uh->uh_sport)); + } else if (!(m->m_flags & (M_BCAST | M_MCAST)) && + !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &ip6->ip6_src)) { + log(LOG_INFO, "Connection attempt " + "to UDP %s:%d from %s:%d\n", buf, + ntohs(uh->uh_dport), + ip6_sprintf(&ip6->ip6_src), + ntohs(uh->uh_sport)); + } } udpstat.udps_noport++; if (m->m_flags & M_MCAST) { printf("UDP6: M_MCAST is set in a unicast packet.\n"); -#ifndef __APPLE__ udpstat.udps_noportmcast++; -#endif + IF_UDP_STATINC(ifp, badmcast); goto bad; } icmp6_error(m, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT, 0); return IPPROTO_DONE; } -#if IPSEC - /* - * Check AH/ESP integrity. - */ - if (ipsec_bypass == 0) { - if (ipsec6_in_reject_so(m, in6p->in6p_socket)) { - IPSEC_STAT_INCREMENT(ipsec6stat.in_polvio); - in_pcb_checkstate(in6p, WNT_RELEASE, 0); - goto bad; - } +#if NECP + if (!necp_socket_is_allowed_to_send_recv_v6(in6p, uh->uh_dport, + uh->uh_sport, &ip6->ip6_dst, &ip6->ip6_src, ifp, NULL, NULL, NULL)) { + in_pcb_checkstate(in6p, WNT_RELEASE, 0); + IF_UDP_STATINC(ifp, badipsec); + goto bad; } -#endif /*IPSEC*/ +#endif /* NECP */ /* * Construct sockaddr format source address. @@ -439,18 +578,31 @@ udp6_input( if (in_pcb_checkstate(in6p, WNT_RELEASE, 1) == WNT_STOPUSING) { udp_unlock(in6p->in6p_socket, 1, 0); + IF_UDP_STATINC(ifp, cleanup); goto bad; } - + init_sin6(&udp_in6, m); /* general init */ udp_in6.sin6_port = uh->uh_sport; - if (in6p->in6p_flags & IN6P_CONTROLOPTS - || in6p->in6p_socket->so_options & SO_TIMESTAMP) - ip6_savecontrol(in6p, &opts, ip6, m); + if ((in6p->in6p_flags & INP_CONTROLOPTS) != 0 || + (in6p->in6p_socket->so_options & SO_TIMESTAMP) != 0 || + (in6p->in6p_socket->so_options & SO_TIMESTAMP_MONOTONIC) != 0 || + (in6p->in6p_socket->so_options & SO_TIMESTAMP_CONTINUOUS) != 0) { + ret = ip6_savecontrol(in6p, m, &opts); + if (ret != 0) { + udp_unlock(in6p->in6p_socket, 1, 0); + goto bad; + } + } m_adj(m, off + sizeof(struct udphdr)); + if (nstat_collect) { + INP_ADD_STAT(in6p, cell, wifi, wired, rxpackets, 1); + INP_ADD_STAT(in6p, cell, wifi, wired, rxbytes, m->m_pkthdr.len); + inp_set_activity_bitmap(in6p); + } + so_recv_data_stat(in6p->in6p_socket, m, 0); if (sbappendaddr(&in6p->in6p_socket->so_rcv, - (struct sockaddr *)&udp_in6, - m, opts, NULL) == 0) { + (struct sockaddr *)&udp_in6, m, opts, NULL) == 0) { m = NULL; opts = NULL; udpstat.udps_fullsock++; @@ -461,24 +613,24 @@ udp6_input( udp_unlock(in6p->in6p_socket, 1, 0); return IPPROTO_DONE; bad: - if (m) + if (m != NULL) { m_freem(m); - if (opts) + } + if (opts != NULL) { m_freem(opts); + } return IPPROTO_DONE; } void -udp6_ctlinput( - int cmd, - struct sockaddr *sa, - void *d) +udp6_ctlinput(int cmd, struct sockaddr *sa, void *d, __unused struct ifnet *ifp) { struct udphdr uh; struct ip6_hdr *ip6; struct mbuf *m; int off = 0; struct ip6ctlparam *ip6cp = NULL; + struct icmp6_hdr *icmp6 = NULL; const struct sockaddr_in6 *sa6_src = NULL; void (*notify)(struct inpcb *, int) = udp_notify; struct udp_portonly { @@ -487,21 +639,26 @@ udp6_ctlinput( } *uhp; if (sa->sa_family != AF_INET6 || - sa->sa_len != sizeof(struct sockaddr_in6)) + sa->sa_len != sizeof(struct sockaddr_in6)) { return; + } - if ((unsigned)cmd >= PRC_NCMDS) + if ((unsigned)cmd >= PRC_NCMDS) { return; - if (PRC_IS_REDIRECT(cmd)) - notify = in6_rtchange, d = NULL; - else if (cmd == PRC_HOSTDEAD) + } + if (PRC_IS_REDIRECT(cmd)) { + notify = in6_rtchange; d = NULL; - else if (inet6ctlerrmap[cmd] == 0) + } else if (cmd == PRC_HOSTDEAD) { + d = NULL; + } else if (inet6ctlerrmap[cmd] == 0) { return; + } /* if the parameter is from icmp6, decode it. */ if (d != NULL) { ip6cp = (struct ip6ctlparam *)d; + icmp6 = ip6cp->ip6c_icmp6; m = ip6cp->ip6c_m; ip6 = ip6cp->ip6c_ip6; off = ip6cp->ip6c_off; @@ -512,106 +669,76 @@ udp6_ctlinput( sa6_src = &sa6_any; } - if (ip6) { + if (ip6 != NULL) { /* * XXX: We assume that when IPV6 is non NULL, * M and OFF are valid. */ - /* check if we can safely examine src and dst ports */ - if (m->m_pkthdr.len < off + sizeof(*uhp)) + if (m->m_pkthdr.len < off + sizeof(*uhp)) { return; + } bzero(&uh, sizeof(uh)); m_copydata(m, off, sizeof(*uhp), (caddr_t)&uh); (void) in6_pcbnotify(&udbinfo, sa, uh.uh_dport, - (struct sockaddr*)ip6cp->ip6c_src, - uh.uh_sport, cmd, notify); - } else - (void) in6_pcbnotify(&udbinfo, sa, 0, (struct sockaddr *)&sa6_src, - 0, cmd, notify); -} - -#ifndef __APPLE__ -static int -udp6_getcred SYSCTL_HANDLER_ARGS -{ - struct sockaddr_in6 addrs[2]; - struct inpcb *inp; - int error, s; - - error = proc_suser(req->p); - if (error) - return (error); - - if (req->newlen != sizeof(addrs)) - return (EINVAL); - if (req->oldlen != sizeof(*(kauth_cred_t)0)) - return (EINVAL); - error = SYSCTL_IN(req, addrs, sizeof(addrs)); - if (error) - return (error); - s = splnet(); - inp = in6_pcblookup_hash(&udbinfo, &addrs[1].sin6_addr, - addrs[1].sin6_port, - &addrs[0].sin6_addr, addrs[0].sin6_port, - 1, NULL); - if (!inp || !inp->inp_socket || !inp->inp_socket->so_cred) { - error = ENOENT; - goto out; - } - error = SYSCTL_OUT(req, inp->inp_socket->so_cred->pc_ucred, - sizeof(*(kauth_cred_t)0)); - -out: - splx(s); - return (error); + (struct sockaddr*)ip6cp->ip6c_src, uh.uh_sport, + cmd, NULL, notify); + } + /* + * XXX The else condition here was broken for a long time. + * Fixing it made us deliver notification correctly but broke + * some frameworks that didn't handle it well. + * For now we have removed it and will revisit it later. + */ } -SYSCTL_PROC(_net_inet6_udp6, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW, - 0, 0, - udp6_getcred, "S,ucred", "Get the ucred of a UDP6 connection"); -#endif - static int udp6_abort(struct socket *so) { struct inpcb *inp; inp = sotoinpcb(so); - if (inp == 0) - return EINVAL; /* ??? possible? panic instead? */ + if (inp == NULL) { + panic("%s: so=%p null inp\n", __func__, so); + /* NOTREACHED */ + } soisdisconnected(so); in6_pcbdetach(inp); return 0; } static int -udp6_attach(struct socket *so, __unused int proto, struct proc *p) +udp6_attach(struct socket *so, int proto, struct proc *p) { +#pragma unused(proto) struct inpcb *inp; int error; inp = sotoinpcb(so); - if (inp != 0) + if (inp != NULL) { return EINVAL; + } error = in_pcballoc(so, &udbinfo, p); - if (error) + if (error) { return error; + } if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { error = soreserve(so, udp_sendspace, udp_recvspace); - if (error) + if (error) { return error; + } } inp = (struct inpcb *)so->so_pcb; inp->inp_vflag |= INP_IPV6; - if (ip6_mapped_addr_on) + if (ip6_mapped_addr_on) { inp->inp_vflag |= INP_IPV4; - inp->in6p_hops = -1; /* use kernel default */ - inp->in6p_cksum = -1; /* just to be sure */ + } + inp->in6p_hops = -1; /* use kernel default */ + inp->in6p_cksum = -1; /* just to be sure */ /* * XXX: ugly!! * IPv4 TTL initialization is necessary for an IPv6 socket as well, @@ -619,6 +746,9 @@ udp6_attach(struct socket *so, __unused int proto, struct proc *p) * which may match an IPv4-mapped IPv6 address. */ inp->inp_ip_ttl = ip_defttl; + if (nstat_collect) { + nstat_udp_new_pcb(inp); + } return 0; } @@ -629,19 +759,20 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct proc *p) int error; inp = sotoinpcb(so); - if (inp == 0) + if (inp == NULL) { return EINVAL; + } inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { struct sockaddr_in6 *sin6_p; - sin6_p = (struct sockaddr_in6 *)nam; + sin6_p = (struct sockaddr_in6 *)(void *)nam; - if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) + if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) { inp->inp_vflag |= INP_IPV4; - else if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { + } else if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { struct sockaddr_in sin; in6_sin6_2_sin(&sin, sin6_p); @@ -656,28 +787,57 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct proc *p) return error; } -static int +int udp6_connect(struct socket *so, struct sockaddr *nam, struct proc *p) { struct inpcb *inp; int error; +#if defined(NECP) && defined(FLOW_DIVERT) + int should_use_flow_divert = 0; +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ inp = sotoinpcb(so); - if (inp == 0) + if (inp == NULL) { return EINVAL; + } + +#if defined(NECP) && defined(FLOW_DIVERT) + should_use_flow_divert = necp_socket_should_use_flow_divert(inp); +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { struct sockaddr_in6 *sin6_p; - sin6_p = (struct sockaddr_in6 *)nam; + sin6_p = (struct sockaddr_in6 *)(void *)nam; if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { struct sockaddr_in sin; - if (inp->inp_faddr.s_addr != INADDR_ANY) + if (inp->inp_faddr.s_addr != INADDR_ANY) { return EISCONN; + } + + if (!(so->so_flags1 & SOF1_CONNECT_COUNTED)) { + so->so_flags1 |= SOF1_CONNECT_COUNTED; + INC_ATOMIC_INT64_LIM(net_api_stats.nas_socket_inet_dgram_connected); + } + in6_sin6_2_sin(&sin, sin6_p); - error = in_pcbconnect(inp, (struct sockaddr *)&sin, p); +#if defined(NECP) && defined(FLOW_DIVERT) + if (should_use_flow_divert) { + goto do_flow_divert; + } +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ + error = in_pcbconnect(inp, (struct sockaddr *)&sin, + p, IFSCOPE_NONE, NULL); if (error == 0) { +#if NECP + /* Update NECP client with connected five-tuple */ + if (!uuid_is_null(inp->necp_client_uuid)) { + socket_unlock(so, 0); + necp_client_assign_from_socket(so->last_pid, inp->necp_client_uuid, inp); + socket_lock(so, 0); + } +#endif /* NECP */ inp->inp_vflag |= INP_IPV4; inp->inp_vflag &= ~INP_IPV6; soisconnected(so); @@ -686,27 +846,81 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct proc *p) } } - if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { return EISCONN; + } + + if (!(so->so_flags1 & SOF1_CONNECT_COUNTED)) { + so->so_flags1 |= SOF1_CONNECT_COUNTED; + INC_ATOMIC_INT64_LIM(net_api_stats.nas_socket_inet6_dgram_connected); + } + +#if defined(NECP) && defined(FLOW_DIVERT) +do_flow_divert: + if (should_use_flow_divert) { + uint32_t fd_ctl_unit = necp_socket_get_flow_divert_control_unit(inp); + if (fd_ctl_unit > 0) { + error = flow_divert_pcb_init(so, fd_ctl_unit); + if (error == 0) { + error = flow_divert_connect_out(so, nam, p); + } + } else { + error = ENETDOWN; + } + return error; + } +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ + error = in6_pcbconnect(inp, nam, p); if (error == 0) { - if (ip6_mapped_addr_on || (inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { /* should be non mapped addr */ + /* should be non mapped addr */ + if (ip6_mapped_addr_on || + (inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; } +#if NECP + /* Update NECP client with connected five-tuple */ + if (!uuid_is_null(inp->necp_client_uuid)) { + socket_unlock(so, 0); + necp_client_assign_from_socket(so->last_pid, inp->necp_client_uuid, inp); + socket_lock(so, 0); + } +#endif /* NECP */ soisconnected(so); + if (inp->inp_flowhash == 0) { + inp->inp_flowhash = inp_calc_flowhash(inp); + } + /* update flowinfo - RFC 6437 */ + if (inp->inp_flow == 0 && + inp->in6p_flags & IN6P_AUTOFLOWLABEL) { + inp->inp_flow &= ~IPV6_FLOWLABEL_MASK; + inp->inp_flow |= + (htonl(inp->inp_flowhash) & IPV6_FLOWLABEL_MASK); + } } return error; } +static int +udp6_connectx(struct socket *so, struct sockaddr *src, + struct sockaddr *dst, struct proc *p, uint32_t ifscope, + sae_associd_t aid, sae_connid_t *pcid, uint32_t flags, void *arg, + uint32_t arglen, struct uio *uio, user_ssize_t *bytes_written) +{ + return udp_connectx_common(so, AF_INET6, src, dst, + p, ifscope, aid, pcid, flags, arg, arglen, uio, bytes_written); +} + static int udp6_detach(struct socket *so) { struct inpcb *inp; inp = sotoinpcb(so); - if (inp == 0) + if (inp == NULL) { return EINVAL; + } in6_pcbdetach(inp); return 0; } @@ -717,40 +931,84 @@ udp6_disconnect(struct socket *so) struct inpcb *inp; inp = sotoinpcb(so); - if (inp == 0) - return EINVAL; + if (inp == NULL +#if NECP + || (necp_socket_should_use_flow_divert(inp)) +#endif /* NECP */ + ) { + return inp == NULL ? EINVAL : EPROTOTYPE; + } if (inp->inp_vflag & INP_IPV4) { struct pr_usrreqs *pru; pru = ip_protox[IPPROTO_UDP]->pr_usrreqs; - return ((*pru->pru_disconnect)(so)); + return (*pru->pru_disconnect)(so); } - if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { return ENOTCONN; + } in6_pcbdisconnect(inp); + + /* reset flow-controlled state, just in case */ + inp_reset_fc_state(inp); + inp->in6p_laddr = in6addr_any; - so->so_state &= ~SS_ISCONNECTED; /* XXX */ + inp->in6p_last_outifp = NULL; + + so->so_state &= ~SS_ISCONNECTED; /* XXX */ return 0; } +static int +udp6_disconnectx(struct socket *so, sae_associd_t aid, sae_connid_t cid) +{ +#pragma unused(cid) + if (aid != SAE_ASSOCID_ANY && aid != SAE_ASSOCID_ALL) { + return EINVAL; + } + + return udp6_disconnect(so); +} + static int udp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, - struct mbuf *control, struct proc *p) + struct mbuf *control, struct proc *p) { struct inpcb *inp; int error = 0; +#if defined(NECP) && defined(FLOW_DIVERT) + int should_use_flow_divert = 0; +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ +#if CONTENT_FILTER + struct m_tag *cfil_tag = NULL; + struct sockaddr *cfil_faddr = NULL; +#endif inp = sotoinpcb(so); - if (inp == 0) { + if (inp == NULL) { error = EINVAL; goto bad; } - if (addr) { - if (addr->sa_len != sizeof(struct sockaddr_in6)) { +#if CONTENT_FILTER + //If socket is subject to UDP Content Filter and unconnected, get addr from tag. + if (so->so_cfil_db && !addr && IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + cfil_tag = cfil_udp_get_socket_state(m, NULL, NULL, &cfil_faddr); + if (cfil_tag) { + addr = (struct sockaddr *)cfil_faddr; + } + } +#endif + +#if defined(NECP) && defined(FLOW_DIVERT) + should_use_flow_divert = necp_socket_should_use_flow_divert(inp); +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ + + if (addr != NULL) { + if (addr->sa_len != sizeof(struct sockaddr_in6)) { error = EINVAL; goto bad; } @@ -762,39 +1020,171 @@ udp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, if (ip6_mapped_addr_on || (inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { int hasv4addr; - struct sockaddr_in6 *sin6 = 0; + struct sockaddr_in6 *sin6 = NULL; - if (addr == 0) + if (addr == NULL) { hasv4addr = (inp->inp_vflag & INP_IPV4); - else { - sin6 = (struct sockaddr_in6 *)addr; - hasv4addr = IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) - ? 1 : 0; + } else { + sin6 = (struct sockaddr_in6 *)(void *)addr; + hasv4addr = + IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) ? 1 : 0; } if (hasv4addr) { struct pr_usrreqs *pru; - if (sin6) + if (sin6 != NULL) { in6_sin6_2_sin_in_sock(addr); + } +#if defined(NECP) && defined(FLOW_DIVERT) + if (should_use_flow_divert) { + goto do_flow_divert; + } +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ pru = ip_protox[IPPROTO_UDP]->pr_usrreqs; - error = ((*pru->pru_send)(so, flags, m, addr, control, - p)); + error = ((*pru->pru_send)(so, flags, m, addr, + control, p)); +#if CONTENT_FILTER + if (cfil_tag) { + m_tag_free(cfil_tag); + } +#endif /* addr will just be freed in sendit(). */ return error; } } - return udp6_output(inp, m, addr, control, p); +#if defined(NECP) && defined(FLOW_DIVERT) +do_flow_divert: + if (should_use_flow_divert) { + /* Implicit connect */ + error = flow_divert_implicit_data_out(so, flags, m, addr, control, p); +#if CONTENT_FILTER + if (cfil_tag) { + m_tag_free(cfil_tag); + } +#endif + return error; + } +#endif /* defined(NECP) && defined(FLOW_DIVERT) */ + + error = udp6_output(inp, m, addr, control, p); +#if CONTENT_FILTER + if (cfil_tag) { + m_tag_free(cfil_tag); + } +#endif + return error; + +bad: + VERIFY(error != 0); - bad: - m_freem(m); - return(error); + if (m != NULL) { + m_freem(m); + } + if (control != NULL) { + m_freem(control); + } +#if CONTENT_FILTER + if (cfil_tag) { + m_tag_free(cfil_tag); + } +#endif + return error; } -struct pr_usrreqs udp6_usrreqs = { - udp6_abort, pru_accept_notsupp, udp6_attach, udp6_bind, udp6_connect, - pru_connect2_notsupp, in6_control, udp6_detach, udp6_disconnect, - pru_listen_notsupp, in6_mapped_peeraddr, pru_rcvd_notsupp, - pru_rcvoob_notsupp, udp6_send, pru_sense_null, udp_shutdown, - in6_mapped_sockaddr, sosend, soreceive, pru_sopoll_notsupp -}; +/* + * Checksum extended UDP header and data. + */ +static int +udp6_input_checksum(struct mbuf *m, struct udphdr *uh, int off, int ulen) +{ + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + + if (!(m->m_pkthdr.csum_flags & CSUM_DATA_VALID) && + uh->uh_sum == 0) { + /* UDP/IPv6 checksum is mandatory (RFC2460) */ + + /* + * If checksum was already validated, ignore this check. + * This is necessary for transport-mode ESP, which may be + * getting UDP payloads without checksums when the network + * has a NAT64. + */ + udpstat.udps_nosum++; + goto badsum; + } + + if ((hwcksum_rx || (ifp->if_flags & IFF_LOOPBACK) || + (m->m_pkthdr.pkt_flags & PKTF_LOOP)) && + (m->m_pkthdr.csum_flags & CSUM_DATA_VALID)) { + if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) { + uh->uh_sum = m->m_pkthdr.csum_rx_val; + } else { + uint32_t sum = m->m_pkthdr.csum_rx_val; + uint32_t start = m->m_pkthdr.csum_rx_start; + int32_t trailer = (m_pktlen(m) - (off + ulen)); + + /* + * Perform 1's complement adjustment of octets + * that got included/excluded in the hardware- + * calculated checksum value. Also take care + * of any trailing bytes and subtract out + * their partial sum. + */ + ASSERT(trailer >= 0); + if ((m->m_pkthdr.csum_flags & CSUM_PARTIAL) && + (start != off || trailer != 0)) { + uint32_t swbytes = (uint32_t)trailer; + uint16_t s = 0, d = 0; + + if (IN6_IS_SCOPE_EMBED(&ip6->ip6_src)) { + s = ip6->ip6_src.s6_addr16[1]; + ip6->ip6_src.s6_addr16[1] = 0; + } + if (IN6_IS_SCOPE_EMBED(&ip6->ip6_dst)) { + d = ip6->ip6_dst.s6_addr16[1]; + ip6->ip6_dst.s6_addr16[1] = 0; + } + + /* callee folds in sum */ + sum = m_adj_sum16(m, start, off, ulen, sum); + if (off > start) { + swbytes += (off - start); + } else { + swbytes += (start - off); + } + + if (IN6_IS_SCOPE_EMBED(&ip6->ip6_src)) { + ip6->ip6_src.s6_addr16[1] = s; + } + if (IN6_IS_SCOPE_EMBED(&ip6->ip6_dst)) { + ip6->ip6_dst.s6_addr16[1] = d; + } + + if (swbytes != 0) { + udp_in_cksum_stats(swbytes); + } + if (trailer != 0) { + m_adj(m, -trailer); + } + } + + uh->uh_sum = in6_pseudo(&ip6->ip6_src, &ip6->ip6_dst, + sum + htonl(ulen + IPPROTO_UDP)); + } + uh->uh_sum ^= 0xffff; + } else { + udp_in6_cksum_stats(ulen); + uh->uh_sum = in6_cksum(m, IPPROTO_UDP, off, ulen); + } + + if (uh->uh_sum != 0) { +badsum: + udpstat.udps_badsum++; + IF_UDP_STATINC(ifp, badchksum); + return -1; + } + + return 0; +}