+ return (0);
+}
+
+static void
+ip6_out_cksum_stats(int proto, u_int32_t len)
+{
+ switch (proto) {
+ case IPPROTO_TCP:
+ tcp_out6_cksum_stats(len);
+ break;
+ case IPPROTO_UDP:
+ udp_out6_cksum_stats(len);
+ break;
+ default:
+ /* keep only TCP or UDP stats for now */
+ break;
+ }
+}
+
+/*
+ * Process a delayed payload checksum calculation (outbound path.)
+ *
+ * hoff is the number of bytes beyond the mbuf data pointer which
+ * points to the IPv6 header. optlen is the number of bytes, if any,
+ * between the end of IPv6 header and the beginning of the ULP payload
+ * header, which represents the extension headers. If optlen is less
+ * than zero, this routine will bail when it detects extension headers.
+ *
+ * Returns a bitmask representing all the work done in software.
+ */
+uint32_t
+in6_finalize_cksum(struct mbuf *m, uint32_t hoff, int32_t optlen,
+ int32_t nxt0, uint32_t csum_flags)
+{
+ unsigned char buf[sizeof (struct ip6_hdr)] __attribute__((aligned(8)));
+ struct ip6_hdr *ip6;
+ uint32_t offset, mlen, hlen, olen, sw_csum;
+ uint16_t csum, ulpoff, plen;
+ uint8_t nxt;
+
+ _CASSERT(sizeof (csum) == sizeof (uint16_t));
+ VERIFY(m->m_flags & M_PKTHDR);
+
+ sw_csum = (csum_flags & m->m_pkthdr.csum_flags);
+
+ if ((sw_csum &= CSUM_DELAY_IPV6_DATA) == 0)
+ goto done;
+
+ mlen = m->m_pkthdr.len; /* total mbuf len */
+ hlen = sizeof (*ip6); /* IPv6 header len */
+
+ /* sanity check (need at least IPv6 header) */
+ if (mlen < (hoff + hlen)) {
+ panic("%s: mbuf %p pkt len (%u) < hoff+ip6_hdr "
+ "(%u+%u)\n", __func__, m, mlen, hoff, hlen);
+ /* NOTREACHED */
+ }
+
+ /*
+ * In case the IPv6 header is not contiguous, or not 32-bit
+ * aligned, copy it to a local buffer.
+ */
+ if ((hoff + hlen) > m->m_len ||
+ !IP6_HDR_ALIGNED_P(mtod(m, caddr_t) + hoff)) {
+ m_copydata(m, hoff, hlen, (caddr_t)buf);
+ ip6 = (struct ip6_hdr *)(void *)buf;
+ } else {
+ ip6 = (struct ip6_hdr *)(void *)(m->m_data + hoff);
+ }
+
+ nxt = ip6->ip6_nxt;
+ plen = ntohs(ip6->ip6_plen);
+ if (plen != (mlen - (hoff + hlen))) {
+ plen = OSSwapInt16(plen);
+ if (plen != (mlen - (hoff + hlen))) {
+ /* Don't complain for jumbograms */
+ if (plen != 0 || nxt != IPPROTO_HOPOPTS) {
+ printf("%s: mbuf 0x%llx proto %d IPv6 "
+ "plen %d (%x) [swapped %d (%x)] doesn't "
+ "match actual packet length; %d is used "
+ "instead\n", __func__,
+ (uint64_t)VM_KERNEL_ADDRPERM(m), nxt,
+ ip6->ip6_plen, ip6->ip6_plen, plen, plen,
+ (mlen - (hoff + hlen)));
+ }
+ plen = mlen - (hoff + hlen);
+ }
+ }
+
+ if (optlen < 0) {
+ /* next header isn't TCP/UDP and we don't know optlen, bail */
+ if (nxt != IPPROTO_TCP && nxt != IPPROTO_UDP) {
+ sw_csum = 0;
+ goto done;
+ }
+ olen = 0;
+ } else {
+ /* caller supplied the original transport number; use it */
+ if (nxt0 >= 0)
+ nxt = nxt0;
+ olen = optlen;
+ }
+
+ offset = hoff + hlen + olen; /* ULP header */
+
+ /* sanity check */
+ if (mlen < offset) {
+ panic("%s: mbuf %p pkt len (%u) < hoff+ip6_hdr+ext_hdr "
+ "(%u+%u+%u)\n", __func__, m, mlen, hoff, hlen, olen);
+ /* NOTREACHED */
+ }
+
+ /*
+ * offset is added to the lower 16-bit value of csum_data,
+ * which is expected to contain the ULP offset; therefore
+ * CSUM_PARTIAL offset adjustment must be undone.
+ */
+ if ((m->m_pkthdr.csum_flags & (CSUM_PARTIAL|CSUM_DATA_VALID)) ==
+ (CSUM_PARTIAL|CSUM_DATA_VALID)) {
+ /*
+ * Get back the original ULP offset (this will
+ * undo the CSUM_PARTIAL logic in ip6_output.)
+ */
+ m->m_pkthdr.csum_data = (m->m_pkthdr.csum_tx_stuff -
+ m->m_pkthdr.csum_tx_start);
+ }
+
+ ulpoff = (m->m_pkthdr.csum_data & 0xffff); /* ULP csum offset */
+
+ if (mlen < (ulpoff + sizeof (csum))) {
+ panic("%s: mbuf %p pkt len (%u) proto %d invalid ULP "
+ "cksum offset (%u) cksum flags 0x%x\n", __func__,
+ m, mlen, nxt, ulpoff, m->m_pkthdr.csum_flags);
+ /* NOTREACHED */
+ }
+
+ csum = inet6_cksum(m, 0, offset, plen - olen);
+
+ /* Update stats */
+ ip6_out_cksum_stats(nxt, plen - olen);
+
+ /* RFC1122 4.1.3.4 */
+ if (csum == 0 && (m->m_pkthdr.csum_flags & CSUM_UDPIPV6))
+ csum = 0xffff;
+
+ /* Insert the checksum in the ULP csum field */
+ offset += ulpoff;
+ if ((offset + sizeof (csum)) > m->m_len) {
+ m_copyback(m, offset, sizeof (csum), &csum);
+ } else if (IP6_HDR_ALIGNED_P(mtod(m, char *) + hoff)) {
+ *(uint16_t *)(void *)(mtod(m, char *) + offset) = csum;
+ } else {
+ bcopy(&csum, (mtod(m, char *) + offset), sizeof (csum));
+ }
+ m->m_pkthdr.csum_flags &=
+ ~(CSUM_DELAY_IPV6_DATA | CSUM_DATA_VALID | CSUM_PARTIAL);
+
+done:
+ return (sw_csum);