Linux 3.2.88
[pandora-kernel.git] / net / ipv6 / reassembly.c
index dfb164e..5378faa 100644 (file)
@@ -26,6 +26,9 @@
  *     YOSHIFUJI,H. @USAGI     Always remove fragment header to
  *                             calculate ICV correctly.
  */
+
+#define pr_fmt(fmt) "IPv6: " fmt
+
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/string.h>
@@ -141,8 +144,11 @@ int ip6_frag_match(struct inet_frag_queue *q, void *a)
 
        fq = container_of(q, struct frag_queue, q);
        return (fq->id == arg->id && fq->user == arg->user &&
-                       ipv6_addr_equal(&fq->saddr, arg->src) &&
-                       ipv6_addr_equal(&fq->daddr, arg->dst));
+               ipv6_addr_equal(&fq->saddr, arg->src) &&
+               ipv6_addr_equal(&fq->daddr, arg->dst) &&
+               (arg->iif == fq->iif ||
+                !(ipv6_addr_type(arg->dst) & (IPV6_ADDR_MULTICAST |
+                                              IPV6_ADDR_LINKLOCAL))));
 }
 EXPORT_SYMBOL(ip6_frag_match);
 
@@ -225,7 +231,8 @@ out:
 }
 
 static __inline__ struct frag_queue *
-fq_find(struct net *net, __be32 id, const struct in6_addr *src, const struct in6_addr *dst)
+fq_find(struct net *net, __be32 id, const struct in6_addr *src,
+       const struct in6_addr *dst, int iif)
 {
        struct inet_frag_queue *q;
        struct ip6_create_arg arg;
@@ -235,14 +242,16 @@ fq_find(struct net *net, __be32 id, const struct in6_addr *src, const struct in6
        arg.user = IP6_DEFRAG_LOCAL_DELIVER;
        arg.src = src;
        arg.dst = dst;
+       arg.iif = iif;
 
        read_lock(&ip6_frags.lock);
        hash = inet6_hash_frag(id, src, dst, ip6_frags.rnd);
 
        q = inet_frag_find(&net->ipv6.frags, &ip6_frags, &arg, hash);
-       if (q == NULL)
+       if (IS_ERR_OR_NULL(q)) {
+               inet_frag_maybe_warn_overflow(q, pr_fmt());
                return NULL;
-
+       }
        return container_of(q, struct frag_queue, q);
 }
 
@@ -382,8 +391,17 @@ found:
        }
 
        if (fq->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
-           fq->q.meat == fq->q.len)
-               return ip6_frag_reasm(fq, prev, dev);
+           fq->q.meat == fq->q.len) {
+               int res;
+               unsigned long orefdst = skb->_skb_refdst;
+
+               skb->_skb_refdst = 0UL;
+               res = ip6_frag_reasm(fq, prev, dev);
+               skb->_skb_refdst = orefdst;
+               return res;
+       }
+
+       skb_dst_drop(skb);
 
        write_lock(&ip6_frags.lock);
        list_move_tail(&fq->q.lru_list, &fq->q.net->lru_list);
@@ -504,6 +522,7 @@ static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *prev,
        head->tstamp = fq->q.stamp;
        ipv6_hdr(head)->payload_len = htons(payload_len);
        IP6CB(head)->nhoff = nhoff;
+       IP6CB(head)->flags |= IP6SKB_FRAGMENTED;
 
        /* Yes, and fold redundant checksum back. 8) */
        if (head->ip_summed == CHECKSUM_COMPLETE)
@@ -539,6 +558,9 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
        const struct ipv6hdr *hdr = ipv6_hdr(skb);
        struct net *net = dev_net(skb_dst(skb)->dev);
 
+       if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED)
+               goto fail_hdr;
+
        IP6_INC_STATS_BH(net, ip6_dst_idev(skb_dst(skb)), IPSTATS_MIB_REASMREQDS);
 
        /* Jumbo payload inhibits frag. header */
@@ -559,13 +581,15 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
                                 ip6_dst_idev(skb_dst(skb)), IPSTATS_MIB_REASMOKS);
 
                IP6CB(skb)->nhoff = (u8 *)fhdr - skb_network_header(skb);
+               IP6CB(skb)->flags |= IP6SKB_FRAGMENTED;
                return 1;
        }
 
        if (atomic_read(&net->ipv6.frags.mem) > net->ipv6.frags.high_thresh)
                ip6_evictor(net, ip6_dst_idev(skb_dst(skb)));
 
-       fq = fq_find(net, fhdr->identification, &hdr->saddr, &hdr->daddr);
+       fq = fq_find(net, fhdr->identification, &hdr->saddr, &hdr->daddr,
+                    skb->dev ? skb->dev->ifindex : 0);
        if (fq != NULL) {
                int ret;