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

Reply via email to