X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/b0d623f7f2ae71ed96e60569f61f9a9a27016e80..bb59bff194111743b33cc36712410b5656329d3c:/bsd/netinet6/udp6_output.c diff --git a/bsd/netinet6/udp6_output.c b/bsd/netinet6/udp6_output.c index 191896a47..2d64d6b94 100644 --- a/bsd/netinet6/udp6_output.c +++ b/bsd/netinet6/udp6_output.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008 Apple Inc. All rights reserved. + * Copyright (c) 2000-2014 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -107,9 +107,12 @@ #include #include +#include + #include #include #include +#include #include #include @@ -126,15 +129,9 @@ #include #include -#ifdef IPSEC -#include -#ifdef INET6 -#include -#endif -extern int ipsec_bypass; -#endif /*IPSEC*/ - -#include "faith.h" +#if NECP +#include +#endif /* NECP */ #include @@ -143,65 +140,72 @@ extern int ipsec_bypass; * Per RFC 768, August, 1980. */ -#define in6pcb inpcb -#define udp6stat udpstat -#define udp6s_opackets udps_opackets - -static __inline__ u_int16_t -get_socket_id(struct socket * s) -{ - u_int16_t val; - - if (s == NULL) { - return (0); - } - val = (u_int16_t)(((uintptr_t)s) / sizeof(struct socket)); - if (val == 0) { - val = 0xffff; - } - return (val); -} - int -udp6_output(in6p, m, addr6, control, p) - struct in6pcb *in6p; - struct mbuf *m; - struct mbuf *control; - struct sockaddr *addr6; - struct proc *p; +udp6_output(struct in6pcb *in6p, struct mbuf *m, struct sockaddr *addr6, + struct mbuf *control, struct proc *p) { u_int32_t ulen = m->m_pkthdr.len; - u_int32_t plen = sizeof(struct udphdr) + ulen; + u_int32_t plen = sizeof (struct udphdr) + ulen; struct ip6_hdr *ip6; struct udphdr *udp6; struct in6_addr *laddr, *faddr; u_short fport; int error = 0; - struct ip6_pktopts opt, *stickyopt = in6p->in6p_outputopts; - int priv; - int af = AF_INET6, hlen = sizeof(struct ip6_hdr); + struct ip6_pktopts opt, *optp = NULL; + struct ip6_moptions *im6o; + int af = AF_INET6, hlen = sizeof (struct ip6_hdr); int flags; struct sockaddr_in6 tmp; struct in6_addr storage; + mbuf_svc_class_t msc = MBUF_SC_UNSPEC; + struct ip6_out_args ip6oa = + { IFSCOPE_NONE, { 0 }, IP6OAF_SELECT_SRCIF, 0 }; + struct flowadv *adv = &ip6oa.ip6oa_flowadv; + struct socket *so = in6p->in6p_socket; + struct route_in6 ro; + int flowadv = 0; + + /* Enable flow advisory only when connected */ + flowadv = (so->so_state & SS_ISCONNECTED) ? 1 : 0; + + if (flowadv && INP_WAIT_FOR_IF_FEEDBACK(in6p)) { + error = ENOBUFS; + goto release; + } - priv = (proc_suser(p) == 0); + if (in6p->inp_flags & INP_BOUND_IF) { + ip6oa.ip6oa_boundif = in6p->inp_boundifp->if_index; + ip6oa.ip6oa_flags |= IP6OAF_BOUND_IF; + } + if (INP_NO_CELLULAR(in6p)) + ip6oa.ip6oa_flags |= IP6OAF_NO_CELLULAR; + if (INP_NO_EXPENSIVE(in6p)) + ip6oa.ip6oa_flags |= IP6OAF_NO_EXPENSIVE; + if (INP_AWDL_UNRESTRICTED(in6p)) + ip6oa.ip6oa_flags |= IP6OAF_AWDL_UNRESTRICTED; if (control) { - if ((error = ip6_setpktoptions(control, &opt, priv, 0)) != 0) + msc = mbuf_service_class_from_control(control); + if ((error = ip6_setpktopts(control, &opt, + NULL, IPPROTO_UDP)) != 0) goto release; - in6p->in6p_outputopts = &opt; - } + optp = &opt; + } else + optp = in6p->in6p_outputopts; if (addr6) { /* * IPv4 version of udp_output calls in_pcbconnect in this case, - * which needs splnet and affects performance. + * which has its costs. + * * Since we saw no essential reason for calling in_pcbconnect, * we get rid of such kind of logic, and call in6_selectsrc * and in6_pcbsetport in order to fill in the local address * and the local port. */ - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr6; + struct sockaddr_in6 *sin6 = + (struct sockaddr_in6 *)(void *)addr6; + if (sin6->sin6_port == 0) { error = EADDRNOTAVAIL; goto release; @@ -234,21 +238,22 @@ udp6_output(in6p, m, addr6, control, p) */ error = EINVAL; goto release; - } else + } else { af = AF_INET; + } } /* KAME hack: embed scopeid */ - if (in6_embedscope(&sin6->sin6_addr, sin6, in6p, NULL) != 0) { + if (in6_embedscope(&sin6->sin6_addr, sin6, in6p, NULL, + optp) != 0) { error = EINVAL; goto release; } if (!IN6_IS_ADDR_V4MAPPED(faddr)) { - laddr = in6_selectsrc(sin6, in6p->in6p_outputopts, - in6p->in6p_moptions, - &in6p->in6p_route, - &in6p->in6p_laddr, &storage, &error); + laddr = in6_selectsrc(sin6, optp, + in6p, &in6p->in6p_route, NULL, &storage, + ip6oa.ip6oa_boundif, &error); } else laddr = &in6p->in6p_laddr; /* XXX */ if (laddr == NULL) { @@ -285,14 +290,23 @@ udp6_output(in6p, m, addr6, control, p) fport = in6p->in6p_fport; } + if (in6p->inp_flowhash == 0) + in6p->inp_flowhash = inp_calc_flowhash(in6p); + /* update flowinfo - RFC 6437 */ + if (in6p->inp_flow == 0 && in6p->in6p_flags & IN6P_AUTOFLOWLABEL) { + in6p->inp_flow &= ~IPV6_FLOWLABEL_MASK; + in6p->inp_flow |= + (htonl(in6p->inp_flowhash) & IPV6_FLOWLABEL_MASK); + } + if (af == AF_INET) - hlen = sizeof(struct ip); + hlen = sizeof (struct ip); /* * Calculate data length and get a mbuf * for UDP and IP6 headers. */ - M_PREPEND(m, hlen + sizeof(struct udphdr), M_DONTWAIT); + M_PREPEND(m, hlen + sizeof (struct udphdr), M_DONTWAIT); if (m == 0) { error = ENOBUFS; goto release; @@ -301,7 +315,7 @@ udp6_output(in6p, m, addr6, control, p) /* * Stuff checksum and output datagram. */ - udp6 = (struct udphdr *)(mtod(m, caddr_t) + hlen); + udp6 = (struct udphdr *)(void *)(mtod(m, caddr_t) + hlen); udp6->uh_sport = in6p->in6p_lport; /* lport is always set in the PCB */ udp6->uh_dport = fport; if (plen <= 0xffff) @@ -313,36 +327,155 @@ udp6_output(in6p, m, addr6, control, p) switch (af) { case AF_INET6: ip6 = mtod(m, struct ip6_hdr *); - ip6->ip6_flow = in6p->in6p_flowinfo & IPV6_FLOWINFO_MASK; - ip6->ip6_vfc &= ~IPV6_VERSION_MASK; - ip6->ip6_vfc |= IPV6_VERSION; -#if 0 /* ip6_plen will be filled in ip6_output. */ + ip6->ip6_flow = in6p->inp_flow & IPV6_FLOWINFO_MASK; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; +#if 0 /* ip6_plen will be filled in ip6_output. */ ip6->ip6_plen = htons((u_short)plen); #endif ip6->ip6_nxt = IPPROTO_UDP; - ip6->ip6_hlim = in6_selecthlim(in6p, - in6p->in6p_route.ro_rt ? - in6p->in6p_route.ro_rt->rt_ifp : NULL); + ip6->ip6_hlim = in6_selecthlim(in6p, in6p->in6p_route.ro_rt ? + in6p->in6p_route.ro_rt->rt_ifp : NULL); ip6->ip6_src = *laddr; ip6->ip6_dst = *faddr; - if ((udp6->uh_sum = in6_cksum(m, IPPROTO_UDP, - sizeof(struct ip6_hdr), plen)) == 0) { - udp6->uh_sum = 0xffff; - } + udp6->uh_sum = in6_pseudo(laddr, faddr, + htonl(plen + IPPROTO_UDP)); + m->m_pkthdr.csum_flags = CSUM_UDPIPV6; + m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); + + if (!IN6_IS_ADDR_UNSPECIFIED(laddr)) + ip6oa.ip6oa_flags |= IP6OAF_BOUND_SRCADDR; - flags = 0; + flags = IPV6_OUTARGS; udp6stat.udp6s_opackets++; -#ifdef IPSEC - if (ipsec_bypass == 0 && ipsec_setsocket(m, in6p->in6p_socket) != 0) { + +#if NECP + { + necp_kernel_policy_id policy_id; + if (!necp_socket_is_allowed_to_send_recv_v6(in6p, in6p->in6p_lport, fport, laddr, faddr, NULL, &policy_id)) { + error = EHOSTUNREACH; + goto release; + } + + necp_mark_packet_from_socket(m, in6p, policy_id); + } +#endif /* NECP */ + +#if IPSEC + if (in6p->in6p_sp != NULL && ipsec_setsocket(m, so) != 0) { error = ENOBUFS; goto release; } #endif /*IPSEC*/ - m->m_pkthdr.socket_id = get_socket_id(in6p->in6p_socket); - error = ip6_output(m, in6p->in6p_outputopts, &in6p->in6p_route, - flags, in6p->in6p_moptions, NULL, 0); + + /* In case of IPv4-mapped address used in previous send */ + if (ROUTE_UNUSABLE(&in6p->in6p_route) || + rt_key(in6p->in6p_route.ro_rt)->sa_family != AF_INET6) + ROUTE_RELEASE(&in6p->in6p_route); + + /* Copy the cached route and take an extra reference */ + in6p_route_copyout(in6p, &ro); + + set_packet_service_class(m, so, msc, PKT_SCF_IPV6); + + m->m_pkthdr.pkt_flowsrc = FLOWSRC_INPCB; + m->m_pkthdr.pkt_flowid = in6p->inp_flowhash; + m->m_pkthdr.pkt_proto = IPPROTO_UDP; + m->m_pkthdr.pkt_flags |= (PKTF_FLOW_ID | PKTF_FLOW_LOCALSRC); + if (flowadv) + m->m_pkthdr.pkt_flags |= PKTF_FLOW_ADV; + + im6o = in6p->in6p_moptions; + if (im6o != NULL) { + IM6O_LOCK(im6o); + IM6O_ADDREF_LOCKED(im6o); + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) && + im6o->im6o_multicast_ifp != NULL) { + in6p->in6p_last_outifp = + im6o->im6o_multicast_ifp; + } + IM6O_UNLOCK(im6o); + } + + in6p->inp_sndinprog_cnt++; + + socket_unlock(so, 0); + error = ip6_output(m, optp, &ro, flags, im6o, NULL, &ip6oa); + m = NULL; + socket_lock(so, 0); + + if (im6o != NULL) + IM6O_REMREF(im6o); + + if (error == 0 && nstat_collect) { + boolean_t cell, wifi, wired; + + if (in6p->in6p_route.ro_rt != NULL) { + cell = IFNET_IS_CELLULAR(in6p->in6p_route. + ro_rt->rt_ifp); + wifi = (!cell && IFNET_IS_WIFI(in6p->in6p_route. + ro_rt->rt_ifp)); + wired = (!wifi && IFNET_IS_WIRED(in6p->in6p_route. + ro_rt->rt_ifp)); + } else { + cell = wifi = wired = FALSE; + } + INP_ADD_STAT(in6p, cell, wifi, wired, txpackets, 1); + INP_ADD_STAT(in6p, cell, wifi, wired, txbytes, ulen); + } + + if (flowadv && (adv->code == FADV_FLOW_CONTROLLED || + adv->code == FADV_SUSPENDED)) { + /* + * Return an error to indicate + * that the packet has been dropped. + */ + error = ENOBUFS; + inp_set_fc_state(in6p, adv->code); + } + + VERIFY(in6p->inp_sndinprog_cnt > 0); + if ( --in6p->inp_sndinprog_cnt == 0) + in6p->inp_flags &= ~(INP_FC_FEEDBACK); + + /* Synchronize PCB cached route */ + in6p_route_copyin(in6p, &ro); + + if (in6p->in6p_route.ro_rt != NULL) { + struct rtentry *rt = in6p->in6p_route.ro_rt; + struct ifnet *outif; + + if (rt->rt_flags & RTF_MULTICAST) + rt = NULL; /* unusable */ + + /* + * Always discard the cached route for unconnected + * socket or if it is a multicast route. + */ + if (rt == NULL) + ROUTE_RELEASE(&in6p->in6p_route); + + /* + * If the destination route is unicast, update outif + * with that of the route interface used by IP. + */ + if (rt != NULL && + (outif = rt->rt_ifp) != in6p->in6p_last_outifp) + in6p->in6p_last_outifp = outif; + } else { + ROUTE_RELEASE(&in6p->in6p_route); + } + + /* + * If output interface was cellular/expensive, and this + * socket is denied access to it, generate an event. + */ + if (error != 0 && (ip6oa.ip6oa_retflags & IP6OARF_IFDENIED) && + (INP_NO_CELLULAR(in6p) || INP_NO_EXPENSIVE(in6p))) + soevent(in6p->inp_socket, (SO_FILT_HINT_LOCKED| + SO_FILT_HINT_IFDENIED)); break; case AF_INET: error = EAFNOSUPPORT; @@ -351,13 +484,14 @@ udp6_output(in6p, m, addr6, control, p) goto releaseopt; release: - m_freem(m); + if (m != NULL) + m_freem(m); releaseopt: - if (control) { - ip6_clearpktopts(in6p->in6p_outputopts, 0, -1); - in6p->in6p_outputopts = stickyopt; + if (control != NULL) { + if (optp == &opt) + ip6_clearpktopts(optp, -1); m_freem(control); } - return(error); + return (error); }