]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/netinet6/in6_cksum.c
xnu-7195.50.7.100.1.tar.gz
[apple/xnu.git] / bsd / netinet6 / in6_cksum.c
index bf8fe680d39d84ff6db3acf8b74440453f1715fa..41350d56057eddadc0a24829bd3e09a3b84bbee5 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (c) 2009-2011 Apple Inc. All rights reserved.
+ * Copyright (c) 2009-2012 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
- * 
+ *
  * This file contains Original Code and/or Modifications of Original Code
  * as defined in and that are subject to the Apple Public Source License
  * Version 2.0 (the 'License'). You may not use this file except in
  * unlawful or unlicensed copies of an Apple operating system, or to
  * circumvent, violate, or enable the circumvention or violation of, any
  * terms of an Apple operating system software license agreement.
- * 
+ *
  * Please obtain a copy of the License at
  * http://www.opensource.apple.com/apsl/ and read it before using this file.
- * 
+ *
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
@@ -22,7 +22,7 @@
  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  * Please see the License for the specific language governing rights and
  * limitations under the License.
- * 
+ *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  */
 
  */
 
 #include <sys/param.h>
+#include <machine/endian.h>
 #include <sys/mbuf.h>
 #include <sys/systm.h>
 #include <kern/debug.h>
 #include <netinet/in.h>
 #include <netinet/ip6.h>
-
-#include <net/net_osdep.h>
-
-#include <machine/endian.h>
-
-
+#include <netinet6/ip6_var.h>
 
 /*
  * Checksum routine for Internet Protocol family headers (Portable Version).
  * code and should be modified for each CPU to be as fast as possible.
  */
 
-#define ADDCARRY(x)  do { if (x > 65535) { x -= 65535; } } while (0)
-#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);}
-
 /*
- * m MUST contain a continuous IP6 header.
- * off is a offset where TCP/UDP/ICMP6 header starts.
- * len is a total length of a transport segment.
- * (e.g. TCP header + TCP payload)
+ * Compute IPv6 pseudo-header checksum; assumes 16-bit aligned pointers.
  */
-
-u_int16_t
-inet6_cksum(struct mbuf *m, unsigned int nxt, unsigned int off,
-    unsigned int len)
+uint16_t
+in6_pseudo(const struct in6_addr *src, const struct in6_addr *dst, uint32_t x)
 {
-       u_int16_t *w;
-       int sum = 0;
-       int mlen = 0;
-       int byte_swapped = 0;
-       struct ip6_hdr *ip6;
-       union {
-               u_int16_t phs[4];
-               struct {
-                       u_int32_t       ph_len;
-                       u_int8_t        ph_zero[3];
-                       u_int8_t        ph_nxt;
-               } ph __attribute__((__packed__));
-       } uph;
-       union {
-               u_int8_t        c[2];
-               u_int16_t       s;
-       } s_util;
-       union {
-               u_int16_t s[2];
-               u_int32_t l;
-       } l_util;
-
-       /* sanity check */
-       if ((m->m_flags & M_PKTHDR) && m->m_pkthdr.len < off + len) {
-               panic("inet6_cksum: mbuf len (%d) < off+len (%d+%d)\n",
-                   m->m_pkthdr.len, off, len);
-       }
-
-       if (nxt != 0) {
-               bzero(&uph, sizeof (uph));
+       uint32_t sum = 0;
+       const uint16_t *w;
 
-               /*
-                * First create IP6 pseudo header and calculate a summary.
-                */
-               ip6 = mtod(m, struct ip6_hdr *);
-               w = (u_int16_t *)&ip6->ip6_src;
-               uph.ph.ph_len = htonl(len);
-               uph.ph.ph_nxt = nxt;
-
-               /* IPv6 source address */
-               sum += w[0];
-               if (!IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src))
-                       sum += w[1];
-               sum += w[2]; sum += w[3]; sum += w[4]; sum += w[5];
-               sum += w[6]; sum += w[7];
-               /* IPv6 destination address */
-               sum += w[8];
-               if (!IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst))
-                       sum += w[9];
-               sum += w[10]; sum += w[11]; sum += w[12]; sum += w[13];
-               sum += w[14]; sum += w[15];
-               /* Payload length and upper layer identifier */
-               sum += uph.phs[0];  sum += uph.phs[1];
-               sum += uph.phs[2];  sum += uph.phs[3];
-       }
-
-       /*
-        * Secondly calculate a summary of the first mbuf excluding offset.
-        */
-       while (m != NULL && off > 0) {
-               if (m->m_len <= off)
-                       off -= m->m_len;
-               else
-                       break;
-               m = m->m_next;
-       }
-       w = (u_int16_t *)(void *)(mtod(m, u_char *) + off);
-       mlen = m->m_len - off;
-       if (len < mlen)
-               mlen = len;
-       len -= mlen;
        /*
-        * Force to even boundary.
+        * IPv6 source address
         */
-       if ((1 & (intptr_t) w) && (mlen > 0)) {
-               REDUCE;
-               sum <<= 8;
-               s_util.c[0] = *(u_char *)w;
-               w = (u_int16_t *)(void *)((char *)w + 1);
-               mlen--;
-               byte_swapped = 1;
+       w = (const uint16_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];
+
        /*
-        * Unroll the loop to make overhead from
-        * branches &c small.
+        * IPv6 destination address
         */
-       while ((mlen -= 32) >= 0) {
-               sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
-               sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
-               sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
-               sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
-               w += 16;
-       }
-       mlen += 32;
-       while ((mlen -= 8) >= 0) {
-               sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
-               w += 4;
+       w = (const uint16_t *)dst;
+       sum += w[0];
+       if (!IN6_IS_SCOPE_EMBED(dst)) {
+               sum += w[1];
        }
-       mlen += 8;
-       if (mlen == 0 && byte_swapped == 0)
-               goto next;
-       REDUCE;
-       while ((mlen -= 2) >= 0) {
-               sum += *w++;
-       }
-       if (byte_swapped) {
-               REDUCE;
-               sum <<= 8;
-               byte_swapped = 0;
-               if (mlen == -1) {
-                       s_util.c[1] = *(char *)w;
-                       sum += s_util.s;
-                       mlen = 0;
-               } else
-                       mlen = -1;
-       } else if (mlen == -1)
-               s_util.c[0] = *(char *)w;
- next:
-       m = m->m_next;
+       sum += w[2]; sum += w[3]; sum += w[4]; sum += w[5];
+       sum += w[6]; sum += w[7];
 
        /*
-        * Lastly calculate a summary of the rest of mbufs.
-        */
+        * Caller-supplied value; 'x' could be one of:
+        *
+        *      htonl(proto + length), or
+        *      htonl(proto + length + sum)
+        **/
+       sum += x;
+
+       /* fold in carry bits */
+       ADDCARRY(sum);
+
+       return (uint16_t)sum;
+}
+
+/*
+ * m MUST contain at least an IPv6 header, if nxt is specified;
+ * nxt is the upper layer protocol number;
+ * off is an offset where TCP/UDP/ICMP6 header starts;
+ * len is a total length of a transport segment (e.g. TCP header + TCP payload)
+ */
+u_int16_t
+inet6_cksum(struct mbuf *m, uint32_t nxt, uint32_t off, uint32_t len)
+{
+       uint32_t sum;
+
+       sum = m_sum16(m, off, len);
+
+       if (nxt != 0) {
+               struct ip6_hdr *ip6;
+               unsigned char buf[sizeof(*ip6)] __attribute__((aligned(8)));
+               uint32_t mlen;
 
-       for (;m && len; m = m->m_next) {
-               if (m->m_len == 0)
-                       continue;
-               w = mtod(m, u_int16_t *);
-               if (mlen == -1) {
-                       /*
-                        * The first byte of this mbuf is the continuation
-                        * of a word spanning between this mbuf and the
-                        * last mbuf.
-                        *
-                        * s_util.c[0] is already saved when scanning previous
-                        * mbuf.
-                        */
-                       s_util.c[1] = *(char *)w;
-                       sum += s_util.s;
-                       w = (u_int16_t *)(void *)((char *)w + 1);
-                       mlen = m->m_len - 1;
-                       len--;
-               } else
-                       mlen = m->m_len;
-               if (len < mlen)
-                       mlen = len;
-               len -= mlen;
                /*
-                * Force to even boundary.
+                * Sanity check
+                *
+                * Use m_length2() instead of m_length(), as we cannot rely on
+                * the caller setting m_pkthdr.len correctly, if the mbuf is
+                * a M_PKTHDR one.
                 */
-               if ((1 & (intptr_t) w) && (mlen > 0)) {
-                       REDUCE;
-                       sum <<= 8;
-                       s_util.c[0] = *(u_char *)w;
-                       w = (u_int16_t *)(void *)((char *)w + 1);
-                       mlen--;
-                       byte_swapped = 1;
+               if ((mlen = m_length2(m, NULL)) < sizeof(*ip6)) {
+                       panic("%s: mbuf %p pkt too short (%d) for IPv6 header",
+                           __func__, m, mlen);
+                       /* NOTREACHED */
                }
+
                /*
-                * Unroll the loop to make overhead from
-                * branches &c small.
+                * In case the IPv6 header is not contiguous, or not 32-bit
+                * aligned, copy it to a local buffer.  Note here that we
+                * expect the data pointer to point to the IPv6 header.
                 */
-               while ((mlen -= 32) >= 0) {
-                       sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
-                       sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7];
-                       sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11];
-                       sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15];
-                       w += 16;
+               if ((sizeof(*ip6) > m->m_len) ||
+                   !IP6_HDR_ALIGNED_P(mtod(m, caddr_t))) {
+                       m_copydata(m, 0, sizeof(*ip6), (caddr_t)buf);
+                       ip6 = (struct ip6_hdr *)(void *)buf;
+               } else {
+                       ip6 = (struct ip6_hdr *)(void *)(m->m_data);
                }
-               mlen += 32;
-               while ((mlen -= 8) >= 0) {
-                       sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3];
-                       w += 4;
-               }
-               mlen += 8;
-               if (mlen == 0 && byte_swapped == 0)
-                       continue;
-               REDUCE;
-               while ((mlen -= 2) >= 0) {
-                       sum += *w++;
-               }
-               if (byte_swapped) {
-                       REDUCE;
-                       sum <<= 8;
-                       byte_swapped = 0;
-                       if (mlen == -1) {
-                               s_util.c[1] = *(char *)w;
-                               sum += s_util.s;
-                               mlen = 0;
-                       } else
-                               mlen = -1;
-               } else if (mlen == -1)
-                       s_util.c[0] = *(char *)w;
-       }
-       if (len)
-               printf("inet6_cksum: out of data by %d\n", len);
-       if (mlen == -1) {
-               /* The last mbuf has odd # of bytes. Follow the
-                  standard (the odd byte may be shifted left by 8 bits
-                  or not as determined by endian-ness of the machine) */
-               s_util.c[1] = 0;
-               sum += s_util.s;
+
+               /* add pseudo header checksum */
+               sum += in6_pseudo(&ip6->ip6_src, &ip6->ip6_dst,
+                   htonl(nxt + len));
+
+               /* fold in carry bits */
+               ADDCARRY(sum);
        }
-       REDUCE;
-       return (~sum & 0xffff);
+
+       return ~sum & 0xffff;
 }
 
+/*
+ * buffer MUST contain at least an IPv6 header, if nxt is specified;
+ * nxt is the upper layer protocol number;
+ * off is an offset where TCP/UDP/ICMP6 header starts;
+ * len is a total length of a transport segment (e.g. TCP header + TCP payload)
+ */
+u_int16_t
+inet6_cksum_buffer(const uint8_t *buffer, uint32_t nxt, uint32_t off,
+    uint32_t len)
+{
+       uint32_t sum;
+
+       if (off >= len) {
+               panic("%s: off (%d) >= len (%d)", __func__, off, len);
+       }
+
+       sum = b_sum16(&((const uint8_t *)buffer)[off], len);
+
+       if (nxt != 0) {
+               const struct ip6_hdr *ip6;
+               unsigned char buf[sizeof(*ip6)] __attribute__((aligned(8)));
+
+               /*
+                * In case the IPv6 header is not contiguous, or not 32-bit
+                * aligned, copy it to a local buffer.  Note here that we
+                * expect the data pointer to point to the IPv6 header.
+                */
+               if (!IP6_HDR_ALIGNED_P(buffer)) {
+                       memcpy(buf, buffer, sizeof(*ip6));
+                       ip6 = (const struct ip6_hdr *)(const void *)buf;
+               } else {
+                       ip6 = (const struct ip6_hdr *)buffer;
+               }
+
+               /* add pseudo header checksum */
+               sum += in6_pseudo(&ip6->ip6_src, &ip6->ip6_dst,
+                   htonl(nxt + len));
+
+               /* fold in carry bits */
+               ADDCARRY(sum);
+       }
+
+       return ~sum & 0xffff;
+}