I've started seeing the following problem after updating my
carp setup to -current yesterday. But it is probably a bit older.

My carp setup uses IPv6.

The carp master complains as follows:
Oct  1 22:10:19 dougal /bsd: carp: checksum failed, on carp0

The carp slave is also seeing checksum errors, and they cause it to
flip between master and backup states:
Oct  1 22:00:59 cyril /bsd: carp0: state transition: BACKUP -> MASTER
Oct  1 22:01:08 cyril /bsd: carp0: state transition: MASTER -> BACKUP
Oct  1 22:01:08 cyril /bsd: carp: checksum failed, on carp0
Oct  1 22:01:18 cyril /bsd: carp: checksum failed, on carp0
Oct  1 22:01:38 cyril last message repeated 2 times
Oct  1 22:01:39 cyril /bsd: carp0: state transition: BACKUP -> MASTER
Oct  1 22:01:48 cyril /bsd: carp0: state transition: MASTER -> BACKUP
Oct  1 22:01:48 cyril /bsd: carp: checksum failed, on carp0
Oct  1 22:01:58 cyril /bsd: carp: checksum failed, on carp0

carp stats on either side look like this:
$ netstat -s -p carp 
carp:
        48 packets received (IPv4)
        48 packets received (IPv6)
                0 packets discarded for bad interface
                0 packets discarded for wrong TTL
                0 packets shorter than header
               48 discarded for bad checksums
                0 discarded packets with a bad version
                0 discarded because packet too short
                0 discarded for bad authentication
                0 discarded for unknown vhid
                0 discarded because of a bad address list
        0 packets sent (IPv4)
        0 packets sent (IPv6)
                0 send failed due to mbuf memory error
        0 transitions to master

The problem is that the IPv6 input path uses IP6_EXTHDR_GET() to
obtain a pointer to the carp header when verifying the carp header's
checksum. IP6_EXTHDR_GET() internally uses m_pulldown(), which might
return a pointer to a different mbuf in the chain. However, there is
no way for the caller of IP6_EXTHDR_GET() to get at the different mbuf
pointer returned by m_pulldown().

The diff below expands the IP6_EXTHDR_GET() code inline and uses
the different mbuf if it is returned.
Maybe there is a simpler way to write this diff, but it has
the desired effect:

$ netstat -s -p carp 
carp:
        94 packets received (IPv4)
        94 packets received (IPv6)
                0 packets discarded for bad interface
                0 packets discarded for wrong TTL
                0 packets shorter than header
                0 discarded for bad checksums
                0 discarded packets with a bad version
                0 discarded because packet too short
                0 discarded for bad authentication
                0 discarded for unknown vhid
                0 discarded because of a bad address list
        14 packets sent (IPv4)
        14 packets sent (IPv6)
                0 send failed due to mbuf memory error
        1 transition to master

Note that this diff conflicts with the carp error message diff
I sent earlier. However, this diff is about a real bug so I'll wait for
this to get fixed and then adjust my error message diff as necessary.

ok?

Index: ip_carp.c
===================================================================
RCS file: /cvs/src/sys/netinet/ip_carp.c,v
retrieving revision 1.190
diff -u -p -r1.190 ip_carp.c
--- ip_carp.c   6 Sep 2011 16:00:22 -0000       1.190
+++ ip_carp.c   2 Oct 2011 14:40:34 -0000
@@ -622,6 +622,7 @@ carp6_proto_input(struct mbuf **mp, int 
        struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
        struct carp_header *ch;
        u_int len;
+       struct mbuf *m2, *mch;
 
        carpstats.carps_ipackets6++;
 
@@ -648,9 +649,22 @@ carp6_proto_input(struct mbuf **mp, int 
                return (IPPROTO_DONE);
        }
 
-       /* verify that we have a complete carp packet */
-       len = m->m_len;
-       IP6_EXTHDR_GET(ch, struct carp_header *, m, *offp, sizeof(*ch));
+       /* verify that we have a complete carp packet and ensure that
+        * the carp header is in a continuous region of memory */
+       len = m->m_pkthdr.len;
+       m2 = NULL;
+       if (m->m_len >= (*offp + sizeof(*ch)))
+               ch = (struct carp_header *)(mtod(m, caddr_t) + *offp);
+       else {
+               m2 = m_pulldown(m, *offp, sizeof(*ch), offp);
+               if (m2) {
+                       if (m2->m_len < *offp + sizeof(*ch))
+                               panic("m_pulldown malfunction");
+                       ch = (struct carp_header *)(mtod(m2, caddr_t) + *offp);
+               } else {
+                       ch = NULL;
+               }
+       }
        if (ch == NULL) {
                carpstats.carps_badlen++;
                CARP_LOG(LOG_INFO, sc, ("packet size %u too small", len));
@@ -659,15 +673,16 @@ carp6_proto_input(struct mbuf **mp, int 
 
 
        /* verify the CARP checksum */
-       m->m_data += *offp;
-       if (carp_cksum(m, sizeof(*ch))) {
+       mch = m2 ? m2 : m;
+       mch->m_data += *offp;
+       if (carp_cksum(mch, sizeof(*ch))) {
                carpstats.carps_badsum++;
                CARP_LOG(LOG_INFO, sc, ("checksum failed, on %s",
                    m->m_pkthdr.rcvif->if_xname));
                m_freem(m);
                return (IPPROTO_DONE);
        }
-       m->m_data -= *offp;
+       mch->m_data -= *offp;
 
        carp_proto_input_c(m, ch, 1, AF_INET6);
        return (IPPROTO_DONE);

Reply via email to