diff options
author | kevlo <kevlo@FreeBSD.org> | 2014-05-13 06:05:53 +0000 |
---|---|---|
committer | kevlo <kevlo@FreeBSD.org> | 2014-05-13 06:05:53 +0000 |
commit | 69da76e9e5a9758fee9f8286827143e4608bb87a (patch) | |
tree | 9eae681ae0fb7e2b696040fefa6df9b3b66c7360 /sys/netinet6/udp6_usrreq.c | |
parent | ab793415d02166fdd7cfea2301a03a903362e3f9 (diff) | |
download | FreeBSD-src-69da76e9e5a9758fee9f8286827143e4608bb87a.zip FreeBSD-src-69da76e9e5a9758fee9f8286827143e4608bb87a.tar.gz |
MFC r264212,r264213,r264248,r265776,r265811,r265909:
- Add support for UDP-Lite protocol (RFC 3828) to IPv4 and IPv6 stacks.
Tested with vlc and a test suite [1].
[1] http://www.erg.abdn.ac.uk/~gerrit/udp-lite/files/udplite_linux.tar.gz
Reviewed by: jhb, glebius, adrian
- Fix a logic bug which prevented the sending of UDP packet with 0 checksum.
- Disable TX checksum offload for UDP-Lite completely. It wasn't used for
partial checksum coverage, but even for full checksum coverage it doesn't
work.
Diffstat (limited to 'sys/netinet6/udp6_usrreq.c')
-rw-r--r-- | sys/netinet6/udp6_usrreq.c | 168 |
1 files changed, 124 insertions, 44 deletions
diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c index 7df0db7..faa6427 100644 --- a/sys/netinet6/udp6_usrreq.c +++ b/sys/netinet6/udp6_usrreq.c @@ -1,6 +1,7 @@ /*- * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * Copyright (c) 2010-2011 Juniper Networks, Inc. + * Copyright (c) 2014 Kevin Lo * All rights reserved. * * Portions of this software were developed by Robert N. M. Watson under @@ -109,6 +110,7 @@ __FBSDID("$FreeBSD$"); #include <netinet/ip_var.h> #include <netinet/udp.h> #include <netinet/udp_var.h> +#include <netinet/udplite.h> #include <netinet6/ip6protosw.h> #include <netinet6/ip6_var.h> @@ -181,12 +183,15 @@ udp6_input(struct mbuf **mp, int *offp, int proto) struct ip6_hdr *ip6; struct udphdr *uh; struct inpcb *inp; + struct inpcbinfo *pcbinfo; struct udpcb *up; int off = *offp; + int cscov_partial; int plen, ulen; struct sockaddr_in6 fromsa; struct m_tag *fwd_tag; uint16_t uh_sum; + uint8_t nxt; ifp = m->m_pkthdr.rcvif; ip6 = mtod(m, struct ip6_hdr *); @@ -218,6 +223,13 @@ udp6_input(struct mbuf **mp, int *offp, int proto) plen = ntohs(ip6->ip6_plen) - off + sizeof(*ip6); ulen = ntohs((u_short)uh->uh_ulen); + nxt = ip6->ip6_nxt; + cscov_partial = (nxt == IPPROTO_UDPLITE) ? 1 : 0; + if (nxt == IPPROTO_UDPLITE && ulen == 0) { + /* Zero means checksum over the complete packet. */ + ulen = plen; + cscov_partial = 0; + } if (plen != ulen) { UDPSTAT_INC(udps_badlen); goto badunlocked; @@ -227,19 +239,22 @@ udp6_input(struct mbuf **mp, int *offp, int proto) * Checksum extended UDP header and data. */ if (uh->uh_sum == 0) { - UDPSTAT_INC(udps_nosum); - goto badunlocked; + if (ulen > plen || ulen < sizeof(struct udphdr)) { + UDPSTAT_INC(udps_nosum); + goto badunlocked; + } } - if (m->m_pkthdr.csum_flags & CSUM_DATA_VALID_IPV6) { + if ((m->m_pkthdr.csum_flags & CSUM_DATA_VALID_IPV6) && + !cscov_partial) { if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) uh_sum = m->m_pkthdr.csum_data; else - uh_sum = in6_cksum_pseudo(ip6, ulen, - IPPROTO_UDP, m->m_pkthdr.csum_data); + uh_sum = in6_cksum_pseudo(ip6, ulen, nxt, + m->m_pkthdr.csum_data); uh_sum ^= 0xffff; } else - uh_sum = in6_cksum(m, IPPROTO_UDP, off, ulen); + uh_sum = in6_cksum(m, nxt, off, ulen); if (uh_sum != 0) { UDPSTAT_INC(udps_badsum); @@ -252,11 +267,13 @@ udp6_input(struct mbuf **mp, int *offp, int proto) init_sin6(&fromsa, m); fromsa.sin6_port = uh->uh_sport; + pcbinfo = get_inpcbinfo(nxt); if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { struct inpcb *last; + struct inpcbhead *pcblist; struct ip6_moptions *imo; - INP_INFO_RLOCK(&V_udbinfo); + INP_INFO_RLOCK(pcbinfo); /* * In the event that laddr should be set to the link-local * address (this happens in RIPng), the multicast address @@ -272,8 +289,9 @@ udp6_input(struct mbuf **mp, int *offp, int proto) * here. We need udphdr for IPsec processing so we do that * later. */ + pcblist = get_pcblist(nxt); last = NULL; - LIST_FOREACH(inp, &V_udb, inp_list) { + LIST_FOREACH(inp, pcblist, inp_list) { if ((inp->inp_vflag & INP_IPV6) == 0) continue; if (inp->inp_lport != uh->uh_dport) @@ -378,7 +396,7 @@ udp6_input(struct mbuf **mp, int *offp, int proto) goto badheadlocked; } INP_RLOCK(last); - INP_INFO_RUNLOCK(&V_udbinfo); + INP_INFO_RUNLOCK(pcbinfo); up = intoudpcb(last); UDP_PROBE(receive, NULL, last, ip6, last, uh); if (up->u_tun_func == NULL) { @@ -409,8 +427,8 @@ udp6_input(struct mbuf **mp, int *offp, int proto) * Transparently forwarded. Pretend to be the destination. * Already got one like this? */ - inp = in6_pcblookup_mbuf(&V_udbinfo, - &ip6->ip6_src, uh->uh_sport, &ip6->ip6_dst, uh->uh_dport, + inp = in6_pcblookup_mbuf(pcbinfo, &ip6->ip6_src, + uh->uh_sport, &ip6->ip6_dst, uh->uh_dport, INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif, m); if (!inp) { /* @@ -418,7 +436,7 @@ udp6_input(struct mbuf **mp, int *offp, int proto) * Because we've rewritten the destination address, * any hardware-generated hash is ignored. */ - inp = in6_pcblookup(&V_udbinfo, &ip6->ip6_src, + inp = in6_pcblookup(pcbinfo, &ip6->ip6_src, uh->uh_sport, &next_hop6->sin6_addr, next_hop6->sin6_port ? htons(next_hop6->sin6_port) : uh->uh_dport, INPLOOKUP_WILDCARD | @@ -428,7 +446,7 @@ udp6_input(struct mbuf **mp, int *offp, int proto) m_tag_delete(m, fwd_tag); m->m_flags &= ~M_IP6_NEXTHOP; } else - inp = in6_pcblookup_mbuf(&V_udbinfo, &ip6->ip6_src, + inp = in6_pcblookup_mbuf(pcbinfo, &ip6->ip6_src, uh->uh_sport, &ip6->ip6_dst, uh->uh_dport, INPLOOKUP_WILDCARD | INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif, m); @@ -459,6 +477,13 @@ udp6_input(struct mbuf **mp, int *offp, int proto) } INP_RLOCK_ASSERT(inp); up = intoudpcb(inp); + if (cscov_partial) { + if (up->u_rxcslen > ulen) { + INP_RUNLOCK(inp); + m_freem(m); + return (IPPROTO_DONE); + } + } UDP_PROBE(receive, NULL, inp, ip6, inp, uh); if (up->u_tun_func == NULL) { udp6_append(inp, m, off, &fromsa); @@ -473,15 +498,16 @@ udp6_input(struct mbuf **mp, int *offp, int proto) return (IPPROTO_DONE); badheadlocked: - INP_INFO_RUNLOCK(&V_udbinfo); + INP_INFO_RUNLOCK(pcbinfo); badunlocked: if (m) m_freem(m); return (IPPROTO_DONE); } -void -udp6_ctlinput(int cmd, struct sockaddr *sa, void *d) +static void +udp6_common_ctlinput(int cmd, struct sockaddr *sa, void *d, + struct inpcbinfo *pcbinfo) { struct udphdr uh; struct ip6_hdr *ip6; @@ -537,14 +563,28 @@ udp6_ctlinput(int cmd, struct sockaddr *sa, void *d) bzero(&uh, sizeof(uh)); m_copydata(m, off, sizeof(*uhp), (caddr_t)&uh); - (void) in6_pcbnotify(&V_udbinfo, sa, uh.uh_dport, + (void)in6_pcbnotify(pcbinfo, sa, uh.uh_dport, (struct sockaddr *)ip6cp->ip6c_src, uh.uh_sport, cmd, cmdarg, notify); } else - (void) in6_pcbnotify(&V_udbinfo, sa, 0, + (void)in6_pcbnotify(pcbinfo, sa, 0, (const struct sockaddr *)sa6_src, 0, cmd, cmdarg, notify); } +void +udp6_ctlinput(int cmd, struct sockaddr *sa, void *d) +{ + + return (udp6_common_ctlinput(cmd, sa, d, &V_udbinfo)); +} + +void +udplite6_ctlinput(int cmd, struct sockaddr *sa, void *d) +{ + + return (udp6_common_ctlinput(cmd, sa, d, &V_ulitecbinfo)); +} + static int udp6_getcred(SYSCTL_HANDLER_ARGS) { @@ -602,9 +642,12 @@ udp6_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr6, struct in6_addr *laddr, *faddr, in6a; struct sockaddr_in6 *sin6 = NULL; struct ifnet *oifp = NULL; + int cscov_partial = 0; int scope_ambiguous = 0; u_short fport; int error = 0; + uint8_t nxt; + uint16_t cscov = 0; struct ip6_pktopts *optp, opt; int af = AF_INET6, hlen = sizeof(struct ip6_hdr); int flags; @@ -761,10 +804,25 @@ udp6_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr6, /* * Stuff checksum and output datagram. */ + nxt = (inp->inp_socket->so_proto->pr_protocol == IPPROTO_UDP) ? + IPPROTO_UDP : IPPROTO_UDPLITE; 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) + if (nxt == IPPROTO_UDPLITE) { + struct udpcb *up; + + up = intoudpcb(inp); + cscov = up->u_txcslen; + if (cscov >= plen) + cscov = 0; + udp6->uh_ulen = htons(cscov); + /* + * For UDP-Lite, checksum coverage length of zero means + * the entire UDPLite packet is covered by the checksum. + */ + cscov_partial = (cscov == 0) ? 0 : 1; + } else if (plen <= 0xffff) udp6->uh_ulen = htons((u_short)plen); else udp6->uh_ulen = 0; @@ -777,14 +835,20 @@ udp6_output(struct inpcb *inp, struct mbuf *m, struct sockaddr *addr6, ip6->ip6_vfc &= ~IPV6_VERSION_MASK; ip6->ip6_vfc |= IPV6_VERSION; ip6->ip6_plen = htons((u_short)plen); - ip6->ip6_nxt = IPPROTO_UDP; + ip6->ip6_nxt = nxt; ip6->ip6_hlim = in6_selecthlim(inp, NULL); ip6->ip6_src = *laddr; ip6->ip6_dst = *faddr; - udp6->uh_sum = in6_cksum_pseudo(ip6, plen, IPPROTO_UDP, 0); - m->m_pkthdr.csum_flags = CSUM_UDP_IPV6; - m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); + if (cscov_partial) { + if ((udp6->uh_sum = in6_cksum(m, 0, + sizeof(struct ip6_hdr), cscov)) == 0) + udp6->uh_sum = 0xffff; + } else { + udp6->uh_sum = in6_cksum_pseudo(ip6, plen, nxt, 0); + m->m_pkthdr.csum_flags = CSUM_UDP_IPV6; + m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); + } flags = 0; @@ -814,7 +878,9 @@ static void udp6_abort(struct socket *so) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp6_abort: inp == NULL")); @@ -830,10 +896,10 @@ udp6_abort(struct socket *so) INP_WLOCK(inp); if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); in6_pcbdisconnect(inp); inp->in6p_laddr = in6addr_any; - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); soisdisconnected(so); } INP_WUNLOCK(inp); @@ -843,8 +909,10 @@ static int udp6_attach(struct socket *so, int proto, struct thread *td) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; int error; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp == NULL, ("udp6_attach: inp != NULL")); @@ -853,10 +921,10 @@ udp6_attach(struct socket *so, int proto, struct thread *td) if (error) return (error); } - INP_INFO_WLOCK(&V_udbinfo); - error = in_pcballoc(so, &V_udbinfo); + INP_INFO_WLOCK(pcbinfo); + error = in_pcballoc(so, pcbinfo); if (error) { - INP_INFO_WUNLOCK(&V_udbinfo); + INP_INFO_WUNLOCK(pcbinfo); return (error); } inp = (struct inpcb *)so->so_pcb; @@ -877,11 +945,11 @@ udp6_attach(struct socket *so, int proto, struct thread *td) if (error) { in_pcbdetach(inp); in_pcbfree(inp); - INP_INFO_WUNLOCK(&V_udbinfo); + INP_INFO_WUNLOCK(pcbinfo); return (error); } INP_WUNLOCK(inp); - INP_INFO_WUNLOCK(&V_udbinfo); + INP_INFO_WUNLOCK(pcbinfo); return (0); } @@ -889,13 +957,15 @@ static int udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; int error; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp6_bind: inp == NULL")); INP_WLOCK(inp); - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); inp->inp_vflag &= ~INP_IPV4; inp->inp_vflag |= INP_IPV6; if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { @@ -923,7 +993,7 @@ udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) #ifdef INET out: #endif - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); INP_WUNLOCK(inp); return (error); } @@ -932,7 +1002,9 @@ static void udp6_close(struct socket *so) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp6_close: inp == NULL")); @@ -947,10 +1019,10 @@ udp6_close(struct socket *so) #endif INP_WLOCK(inp); if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) { - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); in6_pcbdisconnect(inp); inp->in6p_laddr = in6addr_any; - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); soisdisconnected(so); } INP_WUNLOCK(inp); @@ -960,9 +1032,11 @@ static int udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; struct sockaddr_in6 *sin6; int error; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); sin6 = (struct sockaddr_in6 *)nam; KASSERT(inp != NULL, ("udp6_connect: inp == NULL")); @@ -989,10 +1063,10 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) error = prison_remote_ip4(td->td_ucred, &sin.sin_addr); if (error != 0) goto out; - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); error = in_pcbconnect(inp, (struct sockaddr *)&sin, td->td_ucred); - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); if (error == 0) soisconnected(so); goto out; @@ -1007,9 +1081,9 @@ udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) error = prison_remote_ip6(td->td_ucred, &sin6->sin6_addr); if (error != 0) goto out; - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); error = in6_pcbconnect(inp, nam, td->td_ucred); - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); if (error == 0) soisconnected(so); out: @@ -1021,18 +1095,20 @@ static void udp6_detach(struct socket *so) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; struct udpcb *up; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp6_detach: inp == NULL")); - INP_INFO_WLOCK(&V_udbinfo); + INP_INFO_WLOCK(pcbinfo); INP_WLOCK(inp); up = intoudpcb(inp); KASSERT(up != NULL, ("%s: up == NULL", __func__)); in_pcbdetach(inp); in_pcbfree(inp); - INP_INFO_WUNLOCK(&V_udbinfo); + INP_INFO_WUNLOCK(pcbinfo); udp_discardcb(up); } @@ -1040,8 +1116,10 @@ static int udp6_disconnect(struct socket *so) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; int error; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp6_disconnect: inp == NULL")); @@ -1062,10 +1140,10 @@ udp6_disconnect(struct socket *so) goto out; } - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); in6_pcbdisconnect(inp); inp->in6p_laddr = in6addr_any; - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); SOCK_LOCK(so); so->so_state &= ~SS_ISCONNECTED; /* XXX */ SOCK_UNLOCK(so); @@ -1079,8 +1157,10 @@ udp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, struct mbuf *control, struct thread *td) { struct inpcb *inp; + struct inpcbinfo *pcbinfo; int error = 0; + pcbinfo = get_inpcbinfo(so->so_proto->pr_protocol); inp = sotoinpcb(so); KASSERT(inp != NULL, ("udp6_send: inp == NULL")); @@ -1132,9 +1212,9 @@ udp6_send(struct socket *so, int flags, struct mbuf *m, #ifdef MAC mac_inpcb_create_mbuf(inp, m); #endif - INP_HASH_WLOCK(&V_udbinfo); + INP_HASH_WLOCK(pcbinfo); error = udp6_output(inp, m, addr, control, td); - INP_HASH_WUNLOCK(&V_udbinfo); + INP_HASH_WUNLOCK(pcbinfo); #ifdef INET #endif INP_WUNLOCK(inp); |