diff options
author | Lawrence Teo <lteo@cvs.openbsd.org> | 2013-04-08 15:32:24 +0000 |
---|---|---|
committer | Lawrence Teo <lteo@cvs.openbsd.org> | 2013-04-08 15:32:24 +0000 |
commit | 5e23820c8135672151d123ff0724c631da91c23f (patch) | |
tree | 7a4bdbb57301ee42fe4a98c50f7c20c23d168989 /sys/netinet6 | |
parent | 8abbbcd7a9b2b5e924dbd97b20f541868d4d300d (diff) |
Recalculate the IP and protocol checksums of packets (re)injected via
divert(4) sockets.
Recalculation of these checksums is necessary because (1) PF no longer
updates IP checksums as of pf.c rev 1.731, so translated packets that
are diverted to userspace (e.g. divert-packet with nat-to/rdr-to) will
have bad IP checksums and will be reinjected with bad IP checksums if
the userspace program doesn't correct the checksums; (2) the userspace
program may modify the packets, which would invalidate the checksums;
and (3) the divert(4) man page states that checksums are supposed to be
recalculated on reinjection.
This diff has been tested on a public webserver serving both IPv4/IPv6
for more than four weeks. It has also been tested on a firewall with
divert-packet and nat-to/rdr-to where it transferred over 60GB of
FTP/HTTP/HTTPS/SSH/DNS/ICMP/ICMPv6 data correctly, using IPv4/IPv6
userspace programs that intentionally break the IP and protocol
checksums to confirm that recalculation is done correctly on
reinjection. IPv6 extension headers were tested with Scapy.
Thanks to florian@ for testing the original version of the diff with
dnsfilter and Justin Mayes for testing the original version with Snort
inline. Thanks also to todd@ for helping me in my search for the cause
of this bug.
I would especially like to thank blambert@ for reviewing many versions
of this diff, and providing guidance and tons of helpful feedback.
no objections from florian@
help/ok blambert@, ok henning@
Diffstat (limited to 'sys/netinet6')
-rw-r--r-- | sys/netinet6/ip6_divert.c | 72 |
1 files changed, 66 insertions, 6 deletions
diff --git a/sys/netinet6/ip6_divert.c b/sys/netinet6/ip6_divert.c index 0beac45ea8c..c96205516bf 100644 --- a/sys/netinet6/ip6_divert.c +++ b/sys/netinet6/ip6_divert.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ip6_divert.c,v 1.10 2013/04/02 18:27:47 bluhm Exp $ */ +/* $OpenBSD: ip6_divert.c,v 1.11 2013/04/08 15:32:23 lteo Exp $ */ /* * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org> @@ -37,8 +37,10 @@ #include <netinet/in_pcb.h> #include <netinet/ip6.h> #include <netinet6/in6_var.h> -#include <netinet6/in6_var.h> #include <netinet6/ip6_divert.h> +#include <netinet/tcp.h> +#include <netinet/udp.h> +#include <netinet/icmp6.h> struct inpcbtable divb6table; struct div6stat div6stat; @@ -87,8 +89,11 @@ divert6_output(struct mbuf *m, ...) struct sockaddr_in6 *sin6; struct socket *so; struct ifaddr *ifa; - int s, error = 0; + int s, error = 0, p_hdrlen = 0, nxt = 0, off; va_list ap; + struct ip6_hdr *ip6; + u_int16_t csum = 0; + size_t p_off = 0; va_start(ap, m); inp = va_arg(ap, struct inpcb *); @@ -106,15 +111,65 @@ divert6_output(struct mbuf *m, ...) sin6 = mtod(nam, struct sockaddr_in6 *); so = inp->inp_socket; + /* Do basic sanity checks. */ + if (m->m_pkthdr.len < sizeof(struct ip6_hdr)) + goto fail; + if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == NULL) { + /* m_pullup() has freed the mbuf, so just return. */ + div6stat.divs_errors++; + return (ENOBUFS); + } + ip6 = mtod(m, struct ip6_hdr *); + if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) + goto fail; + if (m->m_pkthdr.len < sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen)) + goto fail; + + /* + * Recalculate the protocol checksum since the userspace application + * may have modified the packet prior to reinjection. + */ + off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); + if (off < sizeof(struct ip6_hdr)) + goto fail; + switch (nxt) { + case IPPROTO_TCP: + p_hdrlen = sizeof(struct tcphdr); + p_off = offsetof(struct tcphdr, th_sum); + break; + case IPPROTO_UDP: + p_hdrlen = sizeof(struct udphdr); + p_off = offsetof(struct udphdr, uh_sum); + break; + case IPPROTO_ICMPV6: + p_hdrlen = sizeof(struct icmp6_hdr); + p_off = offsetof(struct icmp6_hdr, icmp6_cksum); + break; + default: + /* nothing */ + break; + } + if (p_hdrlen) { + if (m->m_pkthdr.len < off + p_hdrlen) + goto fail; + + if ((error = m_copyback(m, off + p_off, sizeof(csum), &csum, M_NOWAIT))) + goto fail; + csum = in6_cksum(m, nxt, off, m->m_pkthdr.len - off); + if (nxt == IPPROTO_UDP && csum == 0) + csum = 0xffff; + if ((error = m_copyback(m, off + p_off, sizeof(csum), &csum, M_NOWAIT))) + goto fail; + } + m->m_pkthdr.pf.flags |= PF_TAG_DIVERTED_PACKET; if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { ip6addr.sin6_addr = sin6->sin6_addr; ifa = ifa_ifwithaddr(sin6tosa(&ip6addr), m->m_pkthdr.rdomain); if (ifa == NULL) { - div6stat.divs_errors++; - m_freem(m); - return (EADDRNOTAVAIL); + error = EADDRNOTAVAIL; + goto fail; } m->m_pkthdr.rcvif = ifa->ifa_ifp; @@ -132,6 +187,11 @@ divert6_output(struct mbuf *m, ...) div6stat.divs_opackets++; return (error); + +fail: + div6stat.divs_errors++; + m_freem(m); + return (error ? error : EINVAL); } int |