diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-09 22:42:09 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-10 09:06:58 +0200 |
commit | bceabc95c1c85d793200446fa85f1ddc6313ea29 (patch) | |
tree | 973c8bd8deca9fd69913f2895cc91e0e6114d46c /freebsd/sys/netinet6/udp6_usrreq.c | |
parent | Add FreeBSD sources as a submodule (diff) | |
download | rtems-libbsd-bceabc95c1c85d793200446fa85f1ddc6313ea29.tar.bz2 |
Move files to match FreeBSD layout
Diffstat (limited to 'freebsd/sys/netinet6/udp6_usrreq.c')
-rw-r--r-- | freebsd/sys/netinet6/udp6_usrreq.c | 1112 |
1 files changed, 1112 insertions, 0 deletions
diff --git a/freebsd/sys/netinet6/udp6_usrreq.c b/freebsd/sys/netinet6/udp6_usrreq.c new file mode 100644 index 00000000..735c795b --- /dev/null +++ b/freebsd/sys/netinet6/udp6_usrreq.c @@ -0,0 +1,1112 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $KAME: udp6_usrreq.c,v 1.27 2001/05/21 05:45:10 jinmei Exp $ + * $KAME: udp6_output.c,v 1.31 2001/05/21 16:39:15 jinmei Exp $ + */ + +/*- + * Copyright (c) 1982, 1986, 1988, 1990, 1993, 1995 + * The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)udp_usrreq.c 8.6 (Berkeley) 5/23/95 + */ + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <freebsd/local/opt_inet.h> +#include <freebsd/local/opt_inet6.h> +#include <freebsd/local/opt_ipsec.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/jail.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mbuf.h> +#include <freebsd/sys/priv.h> +#include <freebsd/sys/proc.h> +#include <freebsd/sys/protosw.h> +#include <freebsd/sys/signalvar.h> +#include <freebsd/sys/socket.h> +#include <freebsd/sys/socketvar.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/syslog.h> +#include <freebsd/sys/systm.h> + +#include <freebsd/net/if.h> +#include <freebsd/net/if_types.h> +#include <freebsd/net/route.h> + +#include <freebsd/netinet/in.h> +#include <freebsd/netinet/in_pcb.h> +#include <freebsd/netinet/in_systm.h> +#include <freebsd/netinet/in_var.h> +#include <freebsd/netinet/ip.h> +#include <freebsd/netinet/ip_icmp.h> +#include <freebsd/netinet/ip6.h> +#include <freebsd/netinet/icmp_var.h> +#include <freebsd/netinet/icmp6.h> +#include <freebsd/netinet/ip_var.h> +#include <freebsd/netinet/udp.h> +#include <freebsd/netinet/udp_var.h> + +#include <freebsd/netinet6/ip6protosw.h> +#include <freebsd/netinet6/ip6_var.h> +#include <freebsd/netinet6/in6_pcb.h> +#include <freebsd/netinet6/udp6_var.h> +#include <freebsd/netinet6/scope6_var.h> + +#ifdef IPSEC +#include <freebsd/netipsec/ipsec.h> +#include <freebsd/netipsec/ipsec6.h> +#endif /* IPSEC */ + +#include <freebsd/security/mac/mac_framework.h> + +/* + * UDP protocol implementation. + * Per RFC 768, August, 1980. + */ + +extern struct protosw inetsw[]; +static void udp6_detach(struct socket *so); + +static void +udp6_append(struct inpcb *inp, struct mbuf *n, int off, + struct sockaddr_in6 *fromsa) +{ + struct socket *so; + struct mbuf *opts; + + INP_LOCK_ASSERT(inp); + +#ifdef IPSEC + /* Check AH/ESP integrity. */ + if (ipsec6_in_reject(n, inp)) { + m_freem(n); + V_ipsec6stat.in_polvio++; + return; + } +#endif /* IPSEC */ +#ifdef MAC + if (mac_inpcb_check_deliver(inp, n) != 0) { + m_freem(n); + return; + } +#endif + opts = NULL; + if (inp->inp_flags & INP_CONTROLOPTS || + inp->inp_socket->so_options & SO_TIMESTAMP) + ip6_savecontrol(inp, n, &opts); + m_adj(n, off + sizeof(struct udphdr)); + + so = inp->inp_socket; + SOCKBUF_LOCK(&so->so_rcv); + if (sbappendaddr_locked(&so->so_rcv, (struct sockaddr *)fromsa, n, + opts) == 0) { + SOCKBUF_UNLOCK(&so->so_rcv); + m_freem(n); + if (opts) + m_freem(opts); + UDPSTAT_INC(udps_fullsock); + } else + sorwakeup_locked(so); +} + +int +udp6_input(struct mbuf **mp, int *offp, int proto) +{ + struct mbuf *m = *mp; + struct ifnet *ifp; + struct ip6_hdr *ip6; + struct udphdr *uh; + struct inpcb *inp; + struct udpcb *up; + int off = *offp; + int plen, ulen; + struct sockaddr_in6 fromsa; + + ifp = m->m_pkthdr.rcvif; + ip6 = mtod(m, struct ip6_hdr *); + + if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { + /* XXX send icmp6 host/port unreach? */ + m_freem(m); + return (IPPROTO_DONE); + } + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(struct udphdr), IPPROTO_DONE); + ip6 = mtod(m, struct ip6_hdr *); + uh = (struct udphdr *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(uh, struct udphdr *, m, off, sizeof(*uh)); + if (!uh) + return (IPPROTO_DONE); +#endif + + UDPSTAT_INC(udps_ipackets); + + /* + * Destination port of 0 is illegal, based on RFC768. + */ + if (uh->uh_dport == 0) + goto badunlocked; + + plen = ntohs(ip6->ip6_plen) - off + sizeof(*ip6); + ulen = ntohs((u_short)uh->uh_ulen); + + if (plen != ulen) { + UDPSTAT_INC(udps_badlen); + goto badunlocked; + } + + /* + * Checksum extended UDP header and data. + */ + if (uh->uh_sum == 0) { + UDPSTAT_INC(udps_nosum); + goto badunlocked; + } + if (in6_cksum(m, IPPROTO_UDP, off, ulen) != 0) { + UDPSTAT_INC(udps_badsum); + goto badunlocked; + } + + /* + * Construct sockaddr format source address. + */ + init_sin6(&fromsa, m); + fromsa.sin6_port = uh->uh_sport; + + INP_INFO_RLOCK(&V_udbinfo); + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + struct inpcb *last; + struct ip6_moptions *imo; + + /* + * In the event that laddr should be set to the link-local + * address (this happens in RIPng), the multicast address + * specified in the received packet will not match laddr. To + * handle this situation, matching is relaxed if the + * receiving interface is the same as one specified in the + * socket and if the destination multicast address matches + * one of the multicast groups specified in the socket. + */ + + /* + * KAME note: traditionally we dropped udpiphdr from mbuf + * here. We need udphdr for IPsec processing so we do that + * later. + */ + last = NULL; + LIST_FOREACH(inp, &V_udb, inp_list) { + if ((inp->inp_vflag & INP_IPV6) == 0) + continue; + if (inp->inp_lport != uh->uh_dport) + continue; + if (inp->inp_fport != 0 && + inp->inp_fport != uh->uh_sport) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) { + if (!IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, + &ip6->ip6_dst)) + continue; + } + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + if (!IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, + &ip6->ip6_src) || + inp->inp_fport != uh->uh_sport) + continue; + } + + /* + * Handle socket delivery policy for any-source + * and source-specific multicast. [RFC3678] + */ + imo = inp->in6p_moptions; + if (imo && IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + struct sockaddr_in6 mcaddr; + int blocked; + + INP_RLOCK(inp); + + 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, + (struct sockaddr *)&mcaddr, + (struct sockaddr *)&fromsa); + if (blocked != MCAST_PASS) { + if (blocked == MCAST_NOTGMEMBER) + IP6STAT_INC(ip6s_notmember); + if (blocked == MCAST_NOTSMEMBER || + blocked == MCAST_MUTED) + UDPSTAT_INC(udps_filtermcast); + INP_RUNLOCK(inp); /* XXX */ + continue; + } + + INP_RUNLOCK(inp); + } + if (last != NULL) { + struct mbuf *n; + + if ((n = m_copy(m, 0, M_COPYALL)) != NULL) { + INP_RLOCK(last); + up = intoudpcb(last); + if (up->u_tun_func == NULL) { + udp6_append(last, n, off, &fromsa); + } else { + /* + * Engage the tunneling + * protocol we will have to + * leave the info_lock up, + * since we are hunting + * through multiple UDP's. + * + */ + (*up->u_tun_func)(n, off, last); + } + INP_RUNLOCK(last); + } + } + last = inp; + /* + * Don't look for additional matches if this one does + * not have either the SO_REUSEPORT or SO_REUSEADDR + * socket options set. This heuristic avoids + * searching through all pcbs in the common case of a + * non-shared port. It assumes that an application + * will never clear these options after setting them. + */ + if ((last->inp_socket->so_options & + (SO_REUSEPORT|SO_REUSEADDR)) == 0) + break; + } + + if (last == NULL) { + /* + * No matching pcb found; discard datagram. (No need + * to send an ICMP Port Unreachable for a broadcast + * or multicast datgram.) + */ + UDPSTAT_INC(udps_noport); + UDPSTAT_INC(udps_noportmcast); + goto badheadlocked; + } + INP_RLOCK(last); + INP_INFO_RUNLOCK(&V_udbinfo); + up = intoudpcb(last); + if (up->u_tun_func == NULL) { + udp6_append(last, m, off, &fromsa); + } else { + /* + * Engage the tunneling protocol. + */ + (*up->u_tun_func)(m, off, last); + } + INP_RUNLOCK(last); + return (IPPROTO_DONE); + } + /* + * Locate pcb for datagram. + */ + inp = in6_pcblookup_hash(&V_udbinfo, &ip6->ip6_src, uh->uh_sport, + &ip6->ip6_dst, uh->uh_dport, 1, m->m_pkthdr.rcvif); + if (inp == NULL) { + if (udp_log_in_vain) { + char ip6bufs[INET6_ADDRSTRLEN]; + char ip6bufd[INET6_ADDRSTRLEN]; + + log(LOG_INFO, + "Connection attempt to UDP [%s]:%d from [%s]:%d\n", + ip6_sprintf(ip6bufd, &ip6->ip6_dst), + ntohs(uh->uh_dport), + ip6_sprintf(ip6bufs, &ip6->ip6_src), + ntohs(uh->uh_sport)); + } + UDPSTAT_INC(udps_noport); + if (m->m_flags & M_MCAST) { + printf("UDP6: M_MCAST is set in a unicast packet.\n"); + UDPSTAT_INC(udps_noportmcast); + goto badheadlocked; + } + INP_INFO_RUNLOCK(&V_udbinfo); + if (V_udp_blackhole) + goto badunlocked; + if (badport_bandlim(BANDLIM_ICMP6_UNREACH) < 0) + goto badunlocked; + icmp6_error(m, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT, 0); + return (IPPROTO_DONE); + } + INP_RLOCK(inp); + INP_INFO_RUNLOCK(&V_udbinfo); + up = intoudpcb(inp); + if (up->u_tun_func == NULL) { + udp6_append(inp, m, off, &fromsa); + } else { + /* + * Engage the tunneling protocol. + */ + + (*up->u_tun_func)(m, off, inp); + } + INP_RUNLOCK(inp); + return (IPPROTO_DONE); + +badheadlocked: + INP_INFO_RUNLOCK(&V_udbinfo); +badunlocked: + if (m) + m_freem(m); + return (IPPROTO_DONE); +} + +void +udp6_ctlinput(int cmd, struct sockaddr *sa, void *d) +{ + struct udphdr uh; + struct ip6_hdr *ip6; + struct mbuf *m; + int off = 0; + struct ip6ctlparam *ip6cp = NULL; + const struct sockaddr_in6 *sa6_src = NULL; + void *cmdarg; + struct inpcb *(*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)) + return; + + if ((unsigned)cmd >= PRC_NCMDS) + return; + if (PRC_IS_REDIRECT(cmd)) + notify = in6_rtchange, d = NULL; + 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; + m = ip6cp->ip6c_m; + ip6 = ip6cp->ip6c_ip6; + off = ip6cp->ip6c_off; + cmdarg = ip6cp->ip6c_cmdarg; + sa6_src = ip6cp->ip6c_src; + } else { + m = NULL; + ip6 = NULL; + cmdarg = NULL; + sa6_src = &sa6_any; + } + + if (ip6) { + /* + * 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)) + return; + + bzero(&uh, sizeof(uh)); + m_copydata(m, off, sizeof(*uhp), (caddr_t)&uh); + + (void) in6_pcbnotify(&V_udbinfo, sa, uh.uh_dport, + (struct sockaddr *)ip6cp->ip6c_src, uh.uh_sport, cmd, + cmdarg, notify); + } else + (void) in6_pcbnotify(&V_udbinfo, sa, 0, + (const struct sockaddr *)sa6_src, 0, cmd, cmdarg, notify); +} + +static int +udp6_getcred(SYSCTL_HANDLER_ARGS) +{ + struct xucred xuc; + struct sockaddr_in6 addrs[2]; + struct inpcb *inp; + int error; + + error = priv_check(req->td, PRIV_NETINET_GETCRED); + if (error) + return (error); + + if (req->newlen != sizeof(addrs)) + return (EINVAL); + if (req->oldlen != sizeof(struct xucred)) + return (EINVAL); + error = SYSCTL_IN(req, addrs, sizeof(addrs)); + if (error) + return (error); + if ((error = sa6_embedscope(&addrs[0], V_ip6_use_defzone)) != 0 || + (error = sa6_embedscope(&addrs[1], V_ip6_use_defzone)) != 0) { + return (error); + } + INP_INFO_RLOCK(&V_udbinfo); + inp = in6_pcblookup_hash(&V_udbinfo, &addrs[1].sin6_addr, + addrs[1].sin6_port, &addrs[0].sin6_addr, addrs[0].sin6_port, 1, + NULL); + if (inp != NULL) { + INP_RLOCK(inp); + INP_INFO_RUNLOCK(&V_udbinfo); + if (inp->inp_socket == NULL) + error = ENOENT; + if (error == 0) + error = cr_canseesocket(req->td->td_ucred, + inp->inp_socket); + if (error == 0) + cru2x(inp->inp_cred, &xuc); + INP_RUNLOCK(inp); + } else { + INP_INFO_RUNLOCK(&V_udbinfo); + error = ENOENT; + } + if (error == 0) + error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred)); + return (error); +} + +SYSCTL_PROC(_net_inet6_udp6, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW, 0, + 0, udp6_getcred, "S,xucred", "Get the xucred of a UDP6 connection"); + +static int +udp6_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr6, + struct mbuf *control, struct thread *td) +{ + u_int32_t ulen = m->m_pkthdr.len; + u_int32_t plen = sizeof(struct udphdr) + ulen; + struct ip6_hdr *ip6; + struct udphdr *udp6; + struct in6_addr *laddr, *faddr, in6a; + struct sockaddr_in6 *sin6 = NULL; + struct ifnet *oifp = NULL; + int scope_ambiguous = 0; + u_short fport; + int error = 0; + struct ip6_pktopts *optp, opt; + int af = AF_INET6, hlen = sizeof(struct ip6_hdr); + int flags; + struct sockaddr_in6 tmp; + + INP_WLOCK_ASSERT(inp); + + if (addr6) { + /* addr6 has been validated in udp6_send(). */ + sin6 = (struct sockaddr_in6 *)addr6; + + /* protect *sin6 from overwrites */ + tmp = *sin6; + sin6 = &tmp; + + /* + * Application should provide a proper zone ID or the use of + * default zone IDs should be enabled. Unfortunately, some + * applications do not behave as it should, so we need a + * workaround. Even if an appropriate ID is not determined, + * we'll see if we can determine the outgoing interface. If we + * can, determine the zone ID based on the interface below. + */ + if (sin6->sin6_scope_id == 0 && !V_ip6_use_defzone) + scope_ambiguous = 1; + if ((error = sa6_embedscope(sin6, V_ip6_use_defzone)) != 0) + return (error); + } + + if (control) { + if ((error = ip6_setpktopts(control, &opt, + inp->in6p_outputopts, td->td_ucred, IPPROTO_UDP)) != 0) + goto release; + optp = &opt; + } else + optp = inp->in6p_outputopts; + + if (sin6) { + faddr = &sin6->sin6_addr; + + /* + * IPv4 version of udp_output calls in_pcbconnect in this case, + * which needs splnet and affects performance. + * 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. + */ + if (sin6->sin6_port == 0) { + error = EADDRNOTAVAIL; + goto release; + } + + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + /* how about ::ffff:0.0.0.0 case? */ + error = EISCONN; + goto release; + } + + fport = sin6->sin6_port; /* allow 0 port */ + + if (IN6_IS_ADDR_V4MAPPED(faddr)) { + if ((inp->inp_flags & IN6P_IPV6_V6ONLY)) { + /* + * I believe we should explicitly discard the + * packet when mapped addresses are disabled, + * rather than send the packet as an IPv6 one. + * If we chose the latter approach, the packet + * might be sent out on the wire based on the + * default route, the situation which we'd + * probably want to avoid. + * (20010421 jinmei@kame.net) + */ + error = EINVAL; + goto release; + } + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) && + !IN6_IS_ADDR_V4MAPPED(&inp->in6p_laddr)) { + /* + * when remote addr is an IPv4-mapped address, + * local addr should not be an IPv6 address, + * since you cannot determine how to map IPv6 + * source address to IPv4. + */ + error = EINVAL; + goto release; + } + + af = AF_INET; + } + + if (!IN6_IS_ADDR_V4MAPPED(faddr)) { + error = in6_selectsrc(sin6, optp, inp, NULL, + td->td_ucred, &oifp, &in6a); + if (error) + goto release; + if (oifp && scope_ambiguous && + (error = in6_setscope(&sin6->sin6_addr, + oifp, NULL))) { + goto release; + } + laddr = &in6a; + } else + laddr = &inp->in6p_laddr; /* XXX */ + if (laddr == NULL) { + if (error == 0) + error = EADDRNOTAVAIL; + goto release; + } + if (inp->inp_lport == 0 && + (error = in6_pcbsetport(laddr, inp, td->td_ucred)) != 0) + goto release; + } else { + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + error = ENOTCONN; + goto release; + } + if (IN6_IS_ADDR_V4MAPPED(&inp->in6p_faddr)) { + if ((inp->inp_flags & IN6P_IPV6_V6ONLY)) { + /* + * XXX: this case would happen when the + * application sets the V6ONLY flag after + * connecting the foreign address. + * Such applications should be fixed, + * so we bark here. + */ + log(LOG_INFO, "udp6_output: IPV6_V6ONLY " + "option was set for a connected socket\n"); + error = EINVAL; + goto release; + } else + af = AF_INET; + } + laddr = &inp->in6p_laddr; + faddr = &inp->in6p_faddr; + fport = inp->inp_fport; + } + + if (af == AF_INET) + 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); + if (m == 0) { + error = ENOBUFS; + goto release; + } + + /* + * Stuff checksum and output datagram. + */ + udp6 = (struct udphdr *)(mtod(m, caddr_t) + hlen); + udp6->uh_sport = inp->inp_lport; /* lport is always set in the PCB */ + udp6->uh_dport = fport; + if (plen <= 0xffff) + udp6->uh_ulen = htons((u_short)plen); + else + udp6->uh_ulen = 0; + udp6->uh_sum = 0; + + switch (af) { + case AF_INET6: + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_flow = inp->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(inp, 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; + } + + flags = 0; + + UDPSTAT_INC(udps_opackets); + error = ip6_output(m, optp, NULL, flags, inp->in6p_moptions, + NULL, inp); + break; + case AF_INET: + error = EAFNOSUPPORT; + goto release; + } + goto releaseopt; + +release: + m_freem(m); + +releaseopt: + if (control) { + ip6_clearpktopts(&opt, -1); + m_freem(control); + } + return (error); +} + +static void +udp6_abort(struct socket *so) +{ + struct inpcb *inp; + + inp = sotoinpcb(so); + KASSERT(inp != NULL, ("udp6_abort: inp == NULL")); + +#ifdef INET + if (inp->inp_vflag & INP_IPV4) { + struct pr_usrreqs *pru; + + pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; + (*pru->pru_abort)(so); + return; + } +#endif + + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + in6_pcbdisconnect(inp); + inp->in6p_laddr = in6addr_any; + soisdisconnected(so); + } + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); +} + +static int +udp6_attach(struct socket *so, int proto, struct thread *td) +{ + struct inpcb *inp; + int error; + + inp = sotoinpcb(so); + KASSERT(inp == NULL, ("udp6_attach: inp != NULL")); + + if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { + error = soreserve(so, udp_sendspace, udp_recvspace); + if (error) + return (error); + } + INP_INFO_WLOCK(&V_udbinfo); + error = in_pcballoc(so, &V_udbinfo); + if (error) { + INP_INFO_WUNLOCK(&V_udbinfo); + return (error); + } + inp = (struct inpcb *)so->so_pcb; + inp->inp_vflag |= INP_IPV6; + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) + 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, + * because the socket may be bound to an IPv6 wildcard address, + * which may match an IPv4-mapped IPv6 address. + */ + inp->inp_ip_ttl = V_ip_defttl; + + error = udp_newudpcb(inp); + if (error) { + in_pcbdetach(inp); + in_pcbfree(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + return (error); + } + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + return (0); +} + +static int +udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct inpcb *inp; + int error; + + inp = sotoinpcb(so); + KASSERT(inp != NULL, ("udp6_bind: inp == NULL")); + + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + 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; + + if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) + inp->inp_vflag |= INP_IPV4; + 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; + error = in_pcbbind(inp, (struct sockaddr *)&sin, + td->td_ucred); + goto out; + } + } + + error = in6_pcbbind(inp, nam, td->td_ucred); +out: + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + return (error); +} + +static void +udp6_close(struct socket *so) +{ + struct inpcb *inp; + + inp = sotoinpcb(so); + KASSERT(inp != NULL, ("udp6_close: inp == NULL")); + +#ifdef INET + if (inp->inp_vflag & INP_IPV4) { + struct pr_usrreqs *pru; + + pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; + (*pru->pru_disconnect)(so); + return; + } +#endif + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + in6_pcbdisconnect(inp); + inp->in6p_laddr = in6addr_any; + soisdisconnected(so); + } + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); +} + +static int +udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct inpcb *inp; + struct sockaddr_in6 *sin6; + int error; + + inp = sotoinpcb(so); + sin6 = (struct sockaddr_in6 *)nam; + KASSERT(inp != NULL, ("udp6_connect: inp == NULL")); + + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 && + IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + struct sockaddr_in sin; + + if (inp->inp_faddr.s_addr != INADDR_ANY) { + error = EISCONN; + goto out; + } + in6_sin6_2_sin(&sin, sin6); + error = prison_remote_ip4(td->td_ucred, &sin.sin_addr); + if (error != 0) + goto out; + error = in_pcbconnect(inp, (struct sockaddr *)&sin, + td->td_ucred); + if (error == 0) { + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; + soisconnected(so); + } + goto out; + } + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + error = EISCONN; + goto out; + } + error = prison_remote_ip6(td->td_ucred, &sin6->sin6_addr); + if (error != 0) + goto out; + error = in6_pcbconnect(inp, nam, td->td_ucred); + if (error == 0) { + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { + /* should be non mapped addr */ + inp->inp_vflag &= ~INP_IPV4; + inp->inp_vflag |= INP_IPV6; + } + soisconnected(so); + } +out: + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + return (error); +} + +static void +udp6_detach(struct socket *so) +{ + struct inpcb *inp; + struct udpcb *up; + + inp = sotoinpcb(so); + KASSERT(inp != NULL, ("udp6_detach: inp == NULL")); + + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + up = intoudpcb(inp); + KASSERT(up != NULL, ("%s: up == NULL", __func__)); + in_pcbdetach(inp); + in_pcbfree(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + udp_discardcb(up); +} + +static int +udp6_disconnect(struct socket *so) +{ + struct inpcb *inp; + int error; + + inp = sotoinpcb(so); + KASSERT(inp != NULL, ("udp6_disconnect: inp == NULL")); + + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + +#ifdef INET + if (inp->inp_vflag & INP_IPV4) { + struct pr_usrreqs *pru; + + pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; + error = (*pru->pru_disconnect)(so); + goto out; + } +#endif + + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { + error = ENOTCONN; + goto out; + } + + in6_pcbdisconnect(inp); + inp->in6p_laddr = in6addr_any; + SOCK_LOCK(so); + so->so_state &= ~SS_ISCONNECTED; /* XXX */ + SOCK_UNLOCK(so); +out: + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + return (0); +} + +static int +udp6_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *addr, struct mbuf *control, struct thread *td) +{ + struct inpcb *inp; + int error = 0; + + inp = sotoinpcb(so); + KASSERT(inp != NULL, ("udp6_send: inp == NULL")); + + INP_INFO_WLOCK(&V_udbinfo); + INP_WLOCK(inp); + if (addr) { + if (addr->sa_len != sizeof(struct sockaddr_in6)) { + error = EINVAL; + goto bad; + } + if (addr->sa_family != AF_INET6) { + error = EAFNOSUPPORT; + goto bad; + } + } + +#ifdef INET + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { + int hasv4addr; + struct sockaddr_in6 *sin6 = 0; + + if (addr == 0) + hasv4addr = (inp->inp_vflag & INP_IPV4); + else { + sin6 = (struct sockaddr_in6 *)addr; + hasv4addr = IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) + ? 1 : 0; + } + if (hasv4addr) { + struct pr_usrreqs *pru; + + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) && + !IN6_IS_ADDR_V4MAPPED(&inp->in6p_laddr)) { + /* + * When remote addr is IPv4-mapped address, + * local addr should not be an IPv6 address; + * since you cannot determine how to map IPv6 + * source address to IPv4. + */ + error = EINVAL; + goto out; + } + + /* + * XXXRW: We release UDP-layer locks before calling + * udp_send() in order to avoid recursion. However, + * this does mean there is a short window where inp's + * fields are unstable. Could this lead to a + * potential race in which the factors causing us to + * select the UDPv4 output routine are invalidated? + */ + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + if (sin6) + in6_sin6_2_sin_in_sock(addr); + pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; + /* addr will just be freed in sendit(). */ + return ((*pru->pru_send)(so, flags, m, addr, control, + td)); + } + } +#endif +#ifdef MAC + mac_inpcb_create_mbuf(inp, m); +#endif + error = udp6_output(inp, m, addr, control, td); +out: + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + return (error); + +bad: + INP_WUNLOCK(inp); + INP_INFO_WUNLOCK(&V_udbinfo); + m_freem(m); + return (error); +} + +struct pr_usrreqs udp6_usrreqs = { + .pru_abort = udp6_abort, + .pru_attach = udp6_attach, + .pru_bind = udp6_bind, + .pru_connect = udp6_connect, + .pru_control = in6_control, + .pru_detach = udp6_detach, + .pru_disconnect = udp6_disconnect, + .pru_peeraddr = in6_mapped_peeraddr, + .pru_send = udp6_send, + .pru_shutdown = udp_shutdown, + .pru_sockaddr = in6_mapped_sockaddr, + .pru_soreceive = soreceive_dgram, + .pru_sosend = sosend_dgram, + .pru_sosetlabel = in_pcbsosetlabel, + .pru_close = udp6_close +}; |