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 */
}
}