in_proto_cksum_out() currently calculates ICMP checksums like this: hlen = ip->ip_hl << 2; icp = (struct icmp *)(mtod(m, caddr_t) + hlen); icp->icmp_cksum = 0; icp->icmp_cksum = in4_cksum(m, 0, hlen, ntohs(ip->ip_len) - hlen);
However this won't work if the ICMP header or ICMP checksum field is not in the first mbuf of an mbuf chain. The diff below fixes this as follows: - Instead of fixing this in in_proto_cksum_out() itself, call in_delayed_cksum() which also uses in4_cksum() and includes a check to see if the checksum field is in the first mbuf. Modify in_delayed_cksum() to recognize ICMP accordingly. - In in_delayed_cksum(), set the ICMP checksum to 0 to prepare for calculation (this makes it match what in6_delayed_cksum() does). - While here, move the UDP zero checksum check to the switch statement to remove the duplicate IPPROTO_UDP check (also to match in6_delayed_cksum()). OK? [On a related note, in6_delayed_cksum() also makes an incorrect assumption about the ICMPv6 header/checksum field being in the first mbuf; I'll send the fix in a separate mail.] Index: ip_output.c =================================================================== RCS file: /cvs/src/sys/netinet/ip_output.c,v retrieving revision 1.244 diff -U5 -p -r1.244 ip_output.c --- ip_output.c 31 Jul 2013 15:41:52 -0000 1.244 +++ ip_output.c 5 Aug 2013 02:44:20 -0000 @@ -2058,25 +2058,35 @@ ip_mloopback(struct ifnet *ifp, struct m */ void in_delayed_cksum(struct mbuf *m) { struct ip *ip; - u_int16_t csum, offset; + u_int16_t csum = 0, offset; ip = mtod(m, struct ip *); offset = ip->ip_hl << 2; + + if (ip->ip_p == IPPROTO_ICMP) + if (m_copyback(m, offset + offsetof(struct icmp, icmp_cksum), + sizeof(csum), &csum, M_NOWAIT)) + return; + csum = in4_cksum(m, 0, offset, m->m_pkthdr.len - offset); - if (csum == 0 && ip->ip_p == IPPROTO_UDP) - csum = 0xffff; switch (ip->ip_p) { 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_ICMP: + offset += offsetof(struct icmp, icmp_cksum); break; default: return; } @@ -2101,17 +2111,9 @@ in_proto_cksum_out(struct mbuf *m, struc ifp->if_bridgeport != NULL) { in_delayed_cksum(m); m->m_pkthdr.csum_flags &= ~M_UDP_CSUM_OUT; /* Clear */ } } else if (m->m_pkthdr.csum_flags & M_ICMP_CSUM_OUT) { - struct ip *ip = mtod(m, struct ip *); - int hlen; - struct icmp *icp; - - hlen = ip->ip_hl << 2; - icp = (struct icmp *)(mtod(m, caddr_t) + hlen); - icp->icmp_cksum = 0; - icp->icmp_cksum = in4_cksum(m, 0, hlen, - ntohs(ip->ip_len) - hlen); + in_delayed_cksum(m); m->m_pkthdr.csum_flags &= ~M_ICMP_CSUM_OUT; /* Clear */ } }