netfilter: nf_conntrack_ipv6: fix tracking of ICMPv6 error messages containing fragments
authorPatrick McHardy <kaber@trash.net>
Sun, 26 Aug 2012 17:13:59 +0000 (19:13 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 30 Aug 2012 01:00:11 +0000 (03:00 +0200)
ICMPv6 error messages are tracked by extracting the conntrack tuple of
the inner packet and looking up the corresponding conntrack entry. Tuple
extraction uses the ->get_l4proto() callback, which in case of fragments
returns NEXTHDR_FRAGMENT instead of the upper protocol, even for the
first fragment when the entire next header is present, resulting in a
failure to find the correct connection tracking entry.

This patch changes ipv6_get_l4proto() to use ipv6_skip_exthdr() instead
of nf_ct_ipv6_skip_exthdr() in order to skip fragment headers when the
fragment offset is zero.

Signed-off-by: Patrick McHardy <kaber@trash.net>
net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c

index 521ddca876f865276056eeef7d52fe17ca858cec..dcf6010f68d9135234129ac294c75f087accd7de 100644 (file)
@@ -64,82 +64,31 @@ static int ipv6_print_tuple(struct seq_file *s,
                          tuple->src.u3.ip6, tuple->dst.u3.ip6);
 }
 
-/*
- * Based on ipv6_skip_exthdr() in net/ipv6/exthdr.c
- *
- * This function parses (probably truncated) exthdr set "hdr"
- * of length "len". "nexthdrp" initially points to some place,
- * where type of the first header can be found.
- *
- * It skips all well-known exthdrs, and returns pointer to the start
- * of unparsable area i.e. the first header with unknown type.
- * if success, *nexthdr is updated by type/protocol of this header.
- *
- * NOTES: - it may return pointer pointing beyond end of packet,
- *          if the last recognized header is truncated in the middle.
- *        - if packet is truncated, so that all parsed headers are skipped,
- *          it returns -1.
- *        - if packet is fragmented, return pointer of the fragment header.
- *        - ESP is unparsable for now and considered like
- *          normal payload protocol.
- *        - Note also special handling of AUTH header. Thanks to IPsec wizards.
- */
-
-static int nf_ct_ipv6_skip_exthdr(const struct sk_buff *skb, int start,
-                                 u8 *nexthdrp, int len)
-{
-       u8 nexthdr = *nexthdrp;
-
-       while (ipv6_ext_hdr(nexthdr)) {
-               struct ipv6_opt_hdr hdr;
-               int hdrlen;
-
-               if (len < (int)sizeof(struct ipv6_opt_hdr))
-                       return -1;
-               if (nexthdr == NEXTHDR_NONE)
-                       break;
-               if (nexthdr == NEXTHDR_FRAGMENT)
-                       break;
-               if (skb_copy_bits(skb, start, &hdr, sizeof(hdr)))
-                       BUG();
-               if (nexthdr == NEXTHDR_AUTH)
-                       hdrlen = (hdr.hdrlen+2)<<2;
-               else
-                       hdrlen = ipv6_optlen(&hdr);
-
-               nexthdr = hdr.nexthdr;
-               len -= hdrlen;
-               start += hdrlen;
-       }
-
-       *nexthdrp = nexthdr;
-       return start;
-}
-
 static int ipv6_get_l4proto(const struct sk_buff *skb, unsigned int nhoff,
                            unsigned int *dataoff, u_int8_t *protonum)
 {
        unsigned int extoff = nhoff + sizeof(struct ipv6hdr);
-       unsigned char pnum;
+       __be16 frag_off;
        int protoff;
+       u8 nexthdr;
 
        if (skb_copy_bits(skb, nhoff + offsetof(struct ipv6hdr, nexthdr),
-                         &pnum, sizeof(pnum)) != 0) {
+                         &nexthdr, sizeof(nexthdr)) != 0) {
                pr_debug("ip6_conntrack_core: can't get nexthdr\n");
                return -NF_ACCEPT;
        }
-       protoff = nf_ct_ipv6_skip_exthdr(skb, extoff, &pnum, skb->len - extoff);
+       protoff = ipv6_skip_exthdr(skb, extoff, &nexthdr, &frag_off);
        /*
         * (protoff == skb->len) mean that the packet doesn't have no data
         * except of IPv6 & ext headers. but it's tracked anyway. - YK
         */
-       if ((protoff < 0) || (protoff > skb->len)) {
+       if (protoff < 0 || (frag_off & htons(~0x7)) != 0) {
                pr_debug("ip6_conntrack_core: can't find proto in pkt\n");
                return -NF_ACCEPT;
        }
 
        *dataoff = protoff;
-       *protonum = pnum;
+       *protonum = nexthdr;
        return NF_ACCEPT;
 }