Delayed checksumming means that the TCP/UDP code merely calculates
the pseudo-header checksum and that we only complete the checksum
in the output path if the interface doesn't support TX checksum
offload.

Currently this is only implemented for IPv4.  The diff below adds
the missing pieces for IPv6, so that we will later be able to profit
from checksum offload over IPv6.

This is an update of the work I originally did at n2k10 (mostly
cribbed from NetBSD), with some overlapping parts taken from Henning's
csum diff.

Works for me for basic TCP/UDP use over IPv6.
bridge(4) and pf(4) are completely untested.


Index: sys/mbuf.h
===================================================================
RCS file: /cvs/src/sys/sys/mbuf.h,v
retrieving revision 1.159
diff -u -p -r1.159 mbuf.h
--- sys/mbuf.h  5 Oct 2012 12:27:02 -0000       1.159
+++ sys/mbuf.h  12 Nov 2012 18:28:30 -0000
@@ -182,7 +182,7 @@ struct mbuf {
 
 /* Checksumming flags */
 #define        M_IPV4_CSUM_OUT         0x0001  /* IPv4 checksum needed */
-#define M_TCP_CSUM_OUT         0x0002  /* TCP checksum needed */
+#define        M_TCP_CSUM_OUT          0x0002  /* TCP checksum needed */
 #define        M_UDP_CSUM_OUT          0x0004  /* UDP checksum needed */
 #define        M_IPV4_CSUM_IN_OK       0x0008  /* IPv4 checksum verified */
 #define        M_IPV4_CSUM_IN_BAD      0x0010  /* IPv4 checksum bad */
Index: net/pf.c
===================================================================
RCS file: /cvs/src/sys/net/pf.c,v
retrieving revision 1.816
diff -u -p -r1.816 pf.c
--- net/pf.c    6 Nov 2012 12:32:41 -0000       1.816
+++ net/pf.c    12 Nov 2012 20:37:03 -0000
@@ -6108,6 +6108,7 @@ pf_route6(struct mbuf **m, struct pf_rul
        if (IN6_IS_SCOPE_EMBED(&dst->sin6_addr))
                dst->sin6_addr.s6_addr16[1] = htons(ifp->if_index);
        if ((u_long)m0->m_pkthdr.len <= ifp->if_mtu) {
+               in6_proto_cksum_out(m0, ifp);
                nd6_output(ifp, ifp, m0, dst, NULL);
        } else {
                in6_ifstat_inc(ifp, ifs6_in_toobig);
Index: netinet/tcp_output.c
===================================================================
RCS file: /cvs/src/sys/netinet/tcp_output.c,v
retrieving revision 1.97
diff -u -p -r1.97 tcp_output.c
--- netinet/tcp_output.c        20 Sep 2012 10:25:03 -0000      1.97
+++ netinet/tcp_output.c        12 Nov 2012 18:47:48 -0000
@@ -961,8 +961,11 @@ send:
 #endif /* INET */
 #ifdef INET6
        case AF_INET6:
-               th->th_sum = in6_cksum(m, IPPROTO_TCP, sizeof(struct ip6_hdr),
-                       hdrlen - sizeof(struct ip6_hdr) + len);
+               /* Defer checksumming until later (ip6_output() or hardware) */
+               m->m_pkthdr.csum_flags |= M_TCP_CSUM_OUT;
+               if (len + optlen)
+                       th->th_sum = in_cksum_addword(th->th_sum,
+                           htons((u_int16_t)(len + optlen)));
                break;
 #endif /* INET6 */
        }
Index: netinet/tcp_subr.c
===================================================================
RCS file: /cvs/src/sys/netinet/tcp_subr.c,v
retrieving revision 1.113
diff -u -p -r1.113 tcp_subr.c
--- netinet/tcp_subr.c  10 Mar 2012 12:03:29 -0000      1.113
+++ netinet/tcp_subr.c  12 Nov 2012 18:49:19 -0000
@@ -283,7 +283,9 @@ tcp_template(tp)
 
                        th = (struct tcphdr *)(mtod(m, caddr_t) +
                                sizeof(struct ip6_hdr));
-                       th->th_sum = 0;
+                       th->th_sum = in6_cksum_phdr(&inp->inp_laddr6,
+                           &inp->inp_faddr6, htonl(sizeof(struct tcphdr)),
+                           htonl(IPPROTO_TCP));
                }
                break;
 #endif /* INET6 */
Index: netinet6/in6.h
===================================================================
RCS file: /cvs/src/sys/netinet6/in6.h,v
retrieving revision 1.61
diff -u -p -r1.61 in6.h
--- netinet6/in6.h      6 Nov 2012 12:32:42 -0000       1.61
+++ netinet6/in6.h      12 Nov 2012 18:41:19 -0000
@@ -757,7 +757,57 @@ struct ip6_mtuinfo {
 #ifdef _KERNEL
 struct cmsghdr;
 
+/*
+ * in6_cksum_phdr:
+ *
+ *     Compute significant parts of the IPv6 checksum pseudo-header
+ *     for use in a delayed TCP/UDP checksum calculation.
+ *
+ *     Args:
+ *
+ *             src             Source IPv6 address
+ *             dst             Destination IPv6 address
+ *             len             htonl(proto-hdr-len)
+ *             nxt             htonl(next-proto-number)
+ *
+ *     NOTE: We expect the src and dst addresses to be 16-bit
+ *     aligned!
+ */
+static __inline u_int16_t __attribute__((__unused__))
+in6_cksum_phdr(const struct in6_addr *src, const struct in6_addr *dst,
+    u_int32_t len, u_int32_t nxt)
+{
+       u_int32_t sum = 0;
+       const u_int16_t *w;
+
+       w = (const u_int16_t *) src;
+       sum += w[0];
+       if (!IN6_IS_SCOPE_EMBED(src))
+               sum += w[1];
+       sum += w[2]; sum += w[3]; sum += w[4]; sum += w[5];
+       sum += w[6]; sum += w[7];
+
+       w = (const u_int16_t *) dst;
+       sum += w[0];
+       if (!IN6_IS_SCOPE_EMBED(dst))
+               sum += w[1];
+       sum += w[2]; sum += w[3]; sum += w[4]; sum += w[5];
+       sum += w[6]; sum += w[7];
+
+       sum += (u_int16_t)(len >> 16) + (u_int16_t)(len /*& 0xffff*/);
+
+       sum += (u_int16_t)(nxt >> 16) + (u_int16_t)(nxt /*& 0xffff*/);
+
+       sum = (u_int16_t)(sum >> 16) + (u_int16_t)(sum /*& 0xffff*/);
+
+       if (sum > 0xffff)
+               sum -= 0xffff;
+
+       return (sum);
+}
+
 int    in6_cksum(struct mbuf *, u_int8_t, u_int32_t, u_int32_t);
+extern void in6_proto_cksum_out(struct mbuf *, struct ifnet *);
 int    in6_localaddr(struct in6_addr *);
 int    in6_addrscope(struct in6_addr *);
 struct in6_ifaddr *in6_ifawithscope(struct ifnet *, struct in6_addr *, u_int);
Index: netinet6/in6_cksum.c
===================================================================
RCS file: /cvs/src/sys/netinet6/in6_cksum.c,v
retrieving revision 1.15
diff -u -p -r1.15 in6_cksum.c
--- netinet6/in6_cksum.c        11 Jun 2008 19:00:50 -0000      1.15
+++ netinet6/in6_cksum.c        12 Nov 2012 20:22:30 -0000
@@ -115,6 +115,10 @@ in6_cksum(struct mbuf *m, u_int8_t nxt, 
                        m->m_pkthdr.len, off, len);
        }
 
+       /* Skip pseudo-header if nxt == 0. */
+       if (nxt == 0)
+                goto skip_phdr;
+
        bzero(&uph, sizeof(uph));
 
        /*
@@ -141,6 +145,7 @@ in6_cksum(struct mbuf *m, u_int8_t nxt, 
        sum += uph.phs[0];  sum += uph.phs[1];
        sum += uph.phs[2];  sum += uph.phs[3];
 
+skip_phdr:
        /*
         * Secondly calculate a summary of the first mbuf excluding offset.
         */
Index: netinet6/ip6_output.c
===================================================================
RCS file: /cvs/src/sys/netinet6/ip6_output.c,v
retrieving revision 1.132
diff -u -p -r1.132 ip6_output.c
--- netinet6/ip6_output.c       6 Nov 2012 12:32:42 -0000       1.132
+++ netinet6/ip6_output.c       12 Nov 2012 20:23:14 -0000
@@ -134,6 +134,8 @@ int ip6_splithdr(struct mbuf *, struct i
 int ip6_getpmtu(struct route_in6 *, struct route_in6 *,
        struct ifnet *, struct in6_addr *, u_long *, int *);
 int copypktopts(struct ip6_pktopts *, struct ip6_pktopts *, int);
+void in6_delayed_cksum(struct mbuf *, u_int8_t);
+void in6_proto_cksum_out(struct mbuf *, struct ifnet *);
 
 /* Context for non-repeating IDs */
 struct idgen32_ctx ip6_id_ctx;
@@ -297,6 +299,12 @@ ip6_output(struct mbuf *m0, struct ip6_p
                sspi = tdb->tdb_spi;
                sproto = tdb->tdb_sproto;
                splx(s);
+
+               /*
+                * If it needs TCP/UDP hardware-checksumming, do the
+                * computation now.
+                */
+               in6_proto_cksum_out(m, NULL);
        }
 
        /* Fall through to the routing/multicast handling code */
@@ -875,6 +883,7 @@ reroute:
         * transmit packet without fragmentation
         */
        if (dontfrag || (!alwaysfrag && tlen <= mtu)) { /* case 1-a and 2-a */
+               in6_proto_cksum_out(m, ifp);
                error = nd6_output(ifp, origifp, m, dst, ro->ro_rt);
                goto done;
        }
@@ -935,6 +944,8 @@ reroute:
                        ip6->ip6_nxt = IPPROTO_FRAGMENT;
                }
 
+               in6_proto_cksum_out(m, NULL);
+
                m0 = m;
                error = ip6_fragment(m0, hlen, nextproto, mtu);
 
@@ -3213,4 +3224,63 @@ void
 ip6_randomid_init(void)
 {
        idgen32_init(&ip6_id_ctx);
+}
+
+/*
+ * Process a delayed payload checksum calculation.
+ */
+void
+in6_delayed_cksum(struct mbuf *m, u_int8_t nxt)
+{
+       int nxtp, offset;
+       u_int16_t csum;
+
+       offset = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxtp); 
+       if (offset <= 0 || nxtp != nxt)
+               /* If the desired next protocol isn't found, punt. */
+               return;
+
+       csum = (u_int16_t)(in6_cksum(m, 0, offset, m->m_pkthdr.len - offset));
+
+       switch (nxt) {
+       case IPPROTO_TCP:
+               offset += offsetof(struct tcphdr, th_sum);
+               break;
+
+       case IPPROTO_UDP:
+               offset += offsetof(struct udphdr, uh_sum);
+               if (csum == 0)
+                       csum = 0xffff;
+               break;
+
+       case IPPROTO_ICMPV6:
+               offset += offsetof(struct icmp6_hdr, icmp6_cksum);
+               break;
+       }
+
+       if ((offset + sizeof(u_int16_t)) > m->m_len)
+               m_copyback(m, offset, sizeof(csum), &csum, M_NOWAIT);
+       else
+               *(u_int16_t *)(mtod(m, caddr_t) + offset) = csum;
+}
+
+void
+in6_proto_cksum_out(struct mbuf *m, struct ifnet *ifp)
+{
+       if (m->m_pkthdr.csum_flags & M_TCP_CSUM_OUT) {
+               if (!ifp || !(ifp->if_capabilities & IFCAP_CSUM_TCPv6) ||
+                   ifp->if_bridgeport != NULL) {
+                       in6_delayed_cksum(m, IPPROTO_TCP);
+                       m->m_pkthdr.csum_flags &= ~M_TCP_CSUM_OUT; /* Clear */
+               }
+       } else if (m->m_pkthdr.csum_flags & M_UDP_CSUM_OUT) {
+               if (!ifp || !(ifp->if_capabilities & IFCAP_CSUM_UDPv6) ||
+                   ifp->if_bridgeport != NULL) {
+                       in6_delayed_cksum(m, IPPROTO_UDP);
+                       m->m_pkthdr.csum_flags &= ~M_UDP_CSUM_OUT; /* Clear */
+               }
+       } else if (m->m_pkthdr.csum_flags & M_ICMP_CSUM_OUT) {
+               in6_delayed_cksum(m, IPPROTO_ICMPV6);
+               m->m_pkthdr.csum_flags &= ~M_ICMP_CSUM_OUT; /* Clear */
+       }
 }
Index: netinet6/udp6_output.c
===================================================================
RCS file: /cvs/src/sys/netinet6/udp6_output.c,v
retrieving revision 1.17
diff -u -p -r1.17 udp6_output.c
--- netinet6/udp6_output.c      24 Nov 2011 17:39:55 -0000      1.17
+++ netinet6/udp6_output.c      12 Nov 2012 20:21:23 -0000
@@ -259,10 +259,9 @@ udp6_output(struct in6pcb *in6p, struct 
                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_cksum_phdr(laddr, faddr,
+                   htonl(plen), htonl(IPPROTO_UDP));
+               m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
 
                flags = 0;
                if (in6p->in6p_flags & IN6P_MINMTU)
-- 
Christian "naddy" Weisgerber                          [email protected]

Reply via email to