+/*
+ * Checksum extended TCP header and data.
+ */
+int
+tcp_input_checksum(int af, struct mbuf *m, struct tcphdr *th, int off, int tlen)
+{
+ struct ifnet *ifp = m->m_pkthdr.rcvif;
+
+ switch (af) {
+ case AF_INET: {
+ struct ip *ip = mtod(m, struct ip *);
+ struct ipovly *ipov = (struct ipovly *)ip;
+
+ if (m->m_pkthdr.pkt_flags & PKTF_SW_LRO_DID_CSUM)
+ return (0);
+
+ if ((hwcksum_rx || (ifp->if_flags & IFF_LOOPBACK) ||
+ (m->m_pkthdr.pkt_flags & PKTF_LOOP)) &&
+ (m->m_pkthdr.csum_flags & CSUM_DATA_VALID)) {
+ if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) {
+ th->th_sum = m->m_pkthdr.csum_rx_val;
+ } else {
+ uint16_t sum = m->m_pkthdr.csum_rx_val;
+ uint16_t start = m->m_pkthdr.csum_rx_start;
+
+ /*
+ * Perform 1's complement adjustment of octets
+ * that got included/excluded in the hardware-
+ * calculated checksum value. Ignore cases
+ * where the value includes or excludes the IP
+ * header span, as the sum for those octets
+ * would already be 0xffff and thus no-op.
+ */
+ if ((m->m_pkthdr.csum_flags & CSUM_PARTIAL) &&
+ start != 0 && (off - start) != off) {
+#if BYTE_ORDER != BIG_ENDIAN
+ if (start < off) {
+ HTONS(ip->ip_len);
+ HTONS(ip->ip_off);
+ }
+#endif
+ /* callee folds in sum */
+ sum = m_adj_sum16(m, start, off, sum);
+#if BYTE_ORDER != BIG_ENDIAN
+ if (start < off) {
+ NTOHS(ip->ip_off);
+ NTOHS(ip->ip_len);
+ }
+#endif
+ }
+
+ /* callee folds in sum */
+ th->th_sum = in_pseudo(ip->ip_src.s_addr,
+ ip->ip_dst.s_addr,
+ sum + htonl(tlen + IPPROTO_TCP));
+ }
+ th->th_sum ^= 0xffff;
+ } else {
+ uint16_t ip_sum;
+ int len;
+ char b[9];
+
+ bcopy(ipov->ih_x1, b, sizeof (ipov->ih_x1));
+ bzero(ipov->ih_x1, sizeof (ipov->ih_x1));
+ ip_sum = ipov->ih_len;
+ ipov->ih_len = (u_short)tlen;
+#if BYTE_ORDER != BIG_ENDIAN
+ HTONS(ipov->ih_len);
+#endif
+ len = sizeof (struct ip) + tlen;
+ th->th_sum = in_cksum(m, len);
+ bcopy(b, ipov->ih_x1, sizeof (ipov->ih_x1));
+ ipov->ih_len = ip_sum;
+
+ tcp_in_cksum_stats(len);
+ }
+ break;
+ }
+#if INET6
+ case AF_INET6: {
+ struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
+
+ if (m->m_pkthdr.pkt_flags & PKTF_SW_LRO_DID_CSUM)
+ return (0);
+
+ if ((hwcksum_rx || (ifp->if_flags & IFF_LOOPBACK) ||
+ (m->m_pkthdr.pkt_flags & PKTF_LOOP)) &&
+ (m->m_pkthdr.csum_flags & CSUM_DATA_VALID)) {
+ if (m->m_pkthdr.csum_flags & CSUM_PSEUDO_HDR) {
+ th->th_sum = m->m_pkthdr.csum_rx_val;
+ } else {
+ uint16_t sum = m->m_pkthdr.csum_rx_val;
+ uint16_t start = m->m_pkthdr.csum_rx_start;
+
+ /*
+ * Perform 1's complement adjustment of octets
+ * that got included/excluded in the hardware-
+ * calculated checksum value.
+ */
+ if ((m->m_pkthdr.csum_flags & CSUM_PARTIAL) &&
+ start != off) {
+ uint16_t s, d;
+
+ if (IN6_IS_SCOPE_EMBED(&ip6->ip6_src)) {
+ s = ip6->ip6_src.s6_addr16[1];
+ ip6->ip6_src.s6_addr16[1] = 0 ;
+ }
+ if (IN6_IS_SCOPE_EMBED(&ip6->ip6_dst)) {
+ d = ip6->ip6_dst.s6_addr16[1];
+ ip6->ip6_dst.s6_addr16[1] = 0;
+ }
+
+ /* callee folds in sum */
+ sum = m_adj_sum16(m, start, off, sum);
+
+ if (IN6_IS_SCOPE_EMBED(&ip6->ip6_src))
+ ip6->ip6_src.s6_addr16[1] = s;
+ if (IN6_IS_SCOPE_EMBED(&ip6->ip6_dst))
+ ip6->ip6_dst.s6_addr16[1] = d;
+ }
+
+ th->th_sum = in6_pseudo(
+ &ip6->ip6_src, &ip6->ip6_dst,
+ sum + htonl(tlen + IPPROTO_TCP));
+ }
+ th->th_sum ^= 0xffff;
+ } else {
+ tcp_in6_cksum_stats(tlen);
+ th->th_sum = in6_cksum(m, IPPROTO_TCP, off, tlen);
+ }
+ break;
+ }
+#endif /* INET6 */
+ default:
+ VERIFY(0);
+ /* NOTREACHED */
+ }
+
+ if (th->th_sum != 0) {
+ tcpstat.tcps_rcvbadsum++;
+ IF_TCP_STATINC(ifp, badformat);
+ return (-1);
+ }
+
+ return (0);
+}
+