X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/9bccf70c0258c7cac2dcb80011b2a964d884c552..94ff46dc2849db4d43eaaf144872decc522aafb4:/bsd/netinet6/udp6_usrreq.c diff --git a/bsd/netinet6/udp6_usrreq.c b/bsd/netinet6/udp6_usrreq.c index a6a5951dc..2917f5c7e 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 @@ -78,10 +105,14 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include #include @@ -101,97 +132,171 @@ #if IPSEC #include #include +#include +#include extern int ipsec_bypass; -#endif /*IPSEC*/ +extern int esp_udp_encap_port; +#endif /* IPSEC */ -#include "faith.h" -#if defined(NFAITH) && NFAITH > 0 -#include -#endif +#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 __P((struct inpcb *, struct in6_addr *, struct ifnet *)); -static int udp6_detach __P((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, struct ifnet *); +static int udp6_input_checksum(struct mbuf *, struct udphdr *, int, int); -static int -in6_mcmatch(in6p, ia6, ifp) - 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; +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, +}; - 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)) - return 1; +/* + * 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 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 /* 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) { + udpstat.udps_fullsock++; + } else { + sorwakeup(last->in6p_socket); } - return 0; } int -udp6_input(mp, offp, proto) - struct mbuf **mp; - int *offp, proto; +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), IPPROTO_DONE); + 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)) { - struct inpcb *last; + int reuse_sock = 0, mcast_delivered = 0; + struct ip6_moptions *imo; /* * Deliver a multicast datagram to all sockets @@ -233,65 +338,105 @@ udp6_input(mp, offp, proto) * Locate pcb(s) for datagram. * (Algorithm copied from raw_intr().) */ - last = NULL; + lck_rw_lock_shared(pcbinfo->ipi_lock); + LIST_FOREACH(in6p, &udb, inp_list) { - if ((in6p->inp_vflag & INP_IPV6) == 0) +#if IPSEC + int skipit; +#endif /* IPSEC */ + + if ((in6p->inp_vflag & INP_IPV6) == 0) { continue; - if (in6p->in6p_lport != uh->uh_dport) + } + + if (inp_restricted_recv(in6p, ifp)) { + continue; + } + + if (in_pcb_checkstate(in6p, WNT_ACQUIRE, 0) == + WNT_STOPUSING) { 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)) - 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_lock(in6p->in6p_socket, 1, 0); + + if (in_pcb_checkstate(in6p, WNT_RELEASE, 1) == + WNT_STOPUSING) { + udp_unlock(in6p->in6p_socket, 1, 0); + continue; + } + if (in6p->in6p_lport != uh->uh_dport) { + udp_unlock(in6p->in6p_socket, 1, 0); + continue; + } + + /* + * 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) && + (!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 (last != NULL) { - struct mbuf *n; + reuse_sock = in6p->inp_socket->so_options & + (SO_REUSEPORT | SO_REUSEADDR); -#if IPSEC +#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 */ + { + struct mbuf *n = NULL; /* - * Check AH/ESP integrity. + * 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 (ipsec_bypass == 0 && ipsec6_in_reject_so(m, last->inp_socket)) - ipsec6stat.in_polvio++; - /* do not inject data into pcb */ - else -#endif /*IPSEC*/ - if ((n = m_copy(m, 0, M_COPYALL)) != NULL) { - /* - * KAME NOTE: do not - * m_copy(m, offset, ...) above. - * sbappendaddr() expects M_PKTHDR, - * and m_copy() will copy M_PKTHDR - * only if offset is 0. - */ - if (last->in6p_flags & IN6P_CONTROLOPTS - || last->in6p_socket->so_options & SO_TIMESTAMP) - ip6_savecontrol(last, &opts, - ip6, n); - - m_adj(n, off + sizeof(struct udphdr)); - if (sbappendaddr(&last->in6p_socket->so_rcv, - (struct sockaddr *)&udp_in6, - n, opts) == 0) { - m_freem(n); - if (opts) - m_freem(opts); - udpstat.udps_fullsock++; - } else - sorwakeup(last->in6p_socket); - opts = NULL; + if (reuse_sock) { + n = m_copy(m, 0, M_COPYALL); } + udp6_append(in6p, ip6, &udp_in6, m, + off + sizeof(struct udphdr), ifp); + mcast_delivered++; + m = n; } - last = in6p; + 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 @@ -300,143 +445,236 @@ udp6_input(mp, offp, proto) * port. It assumes that an application will never * clear these options after setting them. */ - if ((last->in6p_socket->so_options & - (SO_REUSEPORT|SO_REUSEADDR)) == 0) + 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 *)(void *)((caddr_t)ip6 + off); } + lck_rw_done(pcbinfo->ipi_lock); - if (last == NULL) { + if (mcast_delivered == 0) { /* * No matching pcb found; discard datagram. * (No need to send an ICMP Port Unreachable * for a broadcast or multicast datgram.) */ udpstat.udps_noport++; -#ifndef __APPLE__ udpstat.udps_noportmcast++; -#endif + IF_UDP_STATINC(ifp, port_unreach); goto bad; } + + /* 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))) { /* - * Check AH/ESP integrity. + * Check if ESP or keepalive: + * 1. If the destination port of the incoming packet is 4500. + * 2. If the source port of the incoming packet is 4500, + * then check the SADB to match IP address and port. */ - if (ipsec_bypass == 0 && ipsec6_in_reject_so(m, last->inp_socket)) { - ipsec6stat.in_polvio++; - goto bad; + bool check_esp = true; + if (uh->uh_dport != ntohs((u_short)esp_udp_encap_port)) { + check_esp = key_checksa_present(AF_INET6, (caddr_t)&ip6->ip6_dst, + (caddr_t)&ip6->ip6_src, uh->uh_dport, + uh->uh_sport); } -#endif /*IPSEC*/ - if (last->in6p_flags & IN6P_CONTROLOPTS - || last->in6p_socket->so_options & SO_TIMESTAMP) - ip6_savecontrol(last, &opts, ip6, m); - - m_adj(m, off + sizeof(struct udphdr)); - if (sbappendaddr(&last->in6p_socket->so_rcv, - (struct sockaddr *)&udp_in6, - m, opts) == 0) { - udpstat.udps_fullsock++; - goto bad; + + if (check_esp) { + 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); + } } - sorwakeup(last->in6p_socket); - return IPPROTO_DONE; } +#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]; - strcpy(buf, ip6_sprintf(&ip6->ip6_dst)); - 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)); + strlcpy(buf, ip6_sprintf(&ip6->ip6_dst), sizeof(buf)); + 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 && ipsec6_in_reject_so(m, in6p->in6p_socket)) { - ipsec6stat.in_polvio++; +#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. * Stuff source address and datagram in user buffer. */ + udp_lock(in6p->in6p_socket, 1, 0); + + 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) == 0) { + (struct sockaddr *)&udp_in6, m, opts, NULL) == 0) { + m = NULL; + opts = NULL; udpstat.udps_fullsock++; + udp_unlock(in6p->in6p_socket, 1, 0); goto bad; } sorwakeup(in6p->in6p_socket); + 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(cmd, sa, d) - 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) __P((struct inpcb *, int)) = udp_notify; + void (*notify)(struct inpcb *, int) = udp_notify; struct udp_portonly { u_int16_t uh_sport; u_int16_t uh_dport; } *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; @@ -447,108 +685,76 @@ udp6_ctlinput(cmd, sa, d) 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(&udb, sa, uh.uh_dport, - (struct sockaddr*)ip6cp->ip6c_src, - uh.uh_sport, cmd, notify); - } else - (void) in6_pcbnotify(&udb, 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 = suser(req->p->p_ucred, &req->p->p_acflag); - if (error) - return (error); - - if (req->newlen != sizeof(addrs)) - return (EINVAL); - if (req->oldlen != sizeof(struct ucred)) - 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(struct ucred)); - -out: - splx(s); - return (error); + (void) in6_pcbnotify(&udbinfo, sa, uh.uh_dport, + (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; - int s; 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); - s = splnet(); in6_pcbdetach(inp); - splx(s); return 0; } static int udp6_attach(struct socket *so, int proto, struct proc *p) { +#pragma unused(proto) struct inpcb *inp; - int s, error; + int error; inp = sotoinpcb(so); - if (inp != 0) + if (inp != NULL) { return EINVAL; + } + + error = in_pcballoc(so, &udbinfo, p); + 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; + } } - s = splnet(); - error = in_pcballoc(so, &udbinfo, p); - splx(s); - if (error) - return error; inp = (struct inpcb *)so->so_pcb; inp->inp_vflag |= INP_IPV6; - inp->in6p_hops = -1; /* use kernel default */ - inp->in6p_cksum = -1; /* just to be sure */ + 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 */ /* * XXX: ugly!! * IPv4 TTL initialization is necessary for an IPv6 socket as well, @@ -556,6 +762,9 @@ udp6_attach(struct socket *so, 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; } @@ -563,64 +772,88 @@ static int udp6_bind(struct socket *so, struct sockaddr *nam, struct proc *p) { struct inpcb *inp; - int s, error; + 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); inp->inp_vflag |= INP_IPV4; inp->inp_vflag &= ~INP_IPV6; - s = splnet(); error = in_pcbbind(inp, (struct sockaddr *)&sin, p); - splx(s); return error; } } - s = splnet(); error = in6_pcbbind(inp, nam, p); - splx(s); return error; } -static int +int udp6_connect(struct socket *so, struct sockaddr *nam, struct proc *p) { struct inpcb *inp; - int s, error; + 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); - s = splnet(); - error = in_pcbconnect(inp, (struct sockaddr *)&sin, p); - splx(s); +#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); @@ -629,33 +862,82 @@ 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; - s = splnet(); + } + + 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); - splx(s); if (error == 0) { - if (ip6_mapped_addr_on) { /* 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; - int s; inp = sotoinpcb(so); - if (inp == 0) + if (inp == NULL) { return EINVAL; - s = splnet(); + } in6_pcbdetach(inp); - splx(s); return 0; } @@ -663,45 +945,86 @@ static int udp6_disconnect(struct socket *so) { struct inpcb *inp; - int s; 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; + } - s = splnet(); in6_pcbdisconnect(inp); + + /* reset flow-controlled state, just in case */ + inp_reset_fc_state(inp); + inp->in6p_laddr = in6addr_any; - splx(s); - 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; } @@ -711,41 +1034,173 @@ udp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, } } - if (ip6_mapped_addr_on) { + 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, sopoll -}; +/* + * 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; +}