inet: limit length of fragment queue hash table bucket lists
authorHannes Frederic Sowa <hannes@stressinduktion.org>
Fri, 15 Mar 2013 11:32:30 +0000 (11:32 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 28 Mar 2013 19:17:23 +0000 (12:17 -0700)
[ Upstream commit 5a3da1fe9561828d0ca7eca664b16ec2b9bf0055 ]

This patch introduces a constant limit of the fragment queue hash
table bucket list lengths. Currently the limit 128 is choosen somewhat
arbitrary and just ensures that we can fill up the fragment cache with
empty packets up to the default ip_frag_high_thresh limits. It should
just protect from list iteration eating considerable amounts of cpu.

If we reach the maximum length in one hash bucket a warning is printed.
This is implemented on the caller side of inet_frag_find to distinguish
between the different users of inet_fragment.c.

I dropped the out of memory warning in the ipv4 fragment lookup path,
because we already get a warning by the slab allocator.

Cc: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Jesper Dangaard Brouer <jbrouer@redhat.com>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
include/net/inet_frag.h
net/ipv4/inet_fragment.c
net/ipv4/ip_fragment.c
net/ipv6/netfilter/nf_conntrack_reasm.c
net/ipv6/reassembly.c

index 32786a0447187f44e1264f4dc4c0b4721ebc3e08..2ab2e43cd51f0f640ae37deac6ae66c33f43827f 100644 (file)
@@ -35,6 +35,13 @@ struct inet_frag_queue {
 
 #define INETFRAGS_HASHSZ               64
 
+/* averaged:
+ * max_depth = default ipfrag_high_thresh / INETFRAGS_HASHSZ /
+ *            rounded up (SKB_TRUELEN(0) + sizeof(struct ipq or
+ *            struct frag_queue))
+ */
+#define INETFRAGS_MAXDEPTH             128
+
 struct inet_frags {
        struct hlist_head       hash[INETFRAGS_HASHSZ];
        rwlock_t                lock;
@@ -65,6 +72,8 @@ int inet_frag_evictor(struct netns_frags *nf, struct inet_frags *f, bool force);
 struct inet_frag_queue *inet_frag_find(struct netns_frags *nf,
                struct inet_frags *f, void *key, unsigned int hash)
        __releases(&f->lock);
+void inet_frag_maybe_warn_overflow(struct inet_frag_queue *q,
+                                  const char *prefix);
 
 static inline void inet_frag_put(struct inet_frag_queue *q, struct inet_frags *f)
 {
index 4750d2b74d79324cdc3176b7a9cbbe0d13c4e9c7..03f5af772a8e8100db9e0a21f862c9660a8c7dd1 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/rtnetlink.h>
 #include <linux/slab.h>
 
+#include <net/sock.h>
 #include <net/inet_frag.h>
 
 static void inet_frag_secret_rebuild(unsigned long dummy)
@@ -276,6 +277,7 @@ struct inet_frag_queue *inet_frag_find(struct netns_frags *nf,
 {
        struct inet_frag_queue *q;
        struct hlist_node *n;
+       int depth = 0;
 
        hlist_for_each_entry(q, n, &f->hash[hash], list) {
                if (q->net == nf && f->match(q, key)) {
@@ -283,9 +285,25 @@ struct inet_frag_queue *inet_frag_find(struct netns_frags *nf,
                        read_unlock(&f->lock);
                        return q;
                }
+               depth++;
        }
        read_unlock(&f->lock);
 
-       return inet_frag_create(nf, f, key);
+       if (depth <= INETFRAGS_MAXDEPTH)
+               return inet_frag_create(nf, f, key);
+       else
+               return ERR_PTR(-ENOBUFS);
 }
 EXPORT_SYMBOL(inet_frag_find);
+
+void inet_frag_maybe_warn_overflow(struct inet_frag_queue *q,
+                                  const char *prefix)
+{
+       static const char msg[] = "inet_frag_find: Fragment hash bucket"
+               " list length grew over limit " __stringify(INETFRAGS_MAXDEPTH)
+               ". Dropping fragment.\n";
+
+       if (PTR_ERR(q) == -ENOBUFS)
+               LIMIT_NETDEBUG(KERN_WARNING "%s%s", prefix, msg);
+}
+EXPORT_SYMBOL(inet_frag_maybe_warn_overflow);
index eb9d63a570cd1ce595076096d9b2aea3723a4c38..a8fc332d07f7e33255728ef7d112d1b3610d164c 100644 (file)
@@ -299,14 +299,11 @@ static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
        hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
 
        q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
-       if (q == NULL)
-               goto out_nomem;
-
+       if (IS_ERR_OR_NULL(q)) {
+               inet_frag_maybe_warn_overflow(q, pr_fmt());
+               return NULL;
+       }
        return container_of(q, struct ipq, q);
-
-out_nomem:
-       LIMIT_NETDEBUG(KERN_ERR pr_fmt("ip_frag_create: no memory left !\n"));
-       return NULL;
 }
 
 /* Is the fragment too far ahead to be part of ipq? */
index 3dacecc9906597e3f44fa9475dda0afb8f4981c2..2f3a018f88956d37af936bb20b877c0718b78e0a 100644 (file)
@@ -14,6 +14,8 @@
  * 2 of the License, or (at your option) any later version.
  */
 
+#define pr_fmt(fmt) "IPv6-nf: " fmt
+
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/string.h>
@@ -180,13 +182,11 @@ static inline struct frag_queue *fq_find(struct net *net, __be32 id,
 
        q = inet_frag_find(&net->nf_frag.frags, &nf_frags, &arg, hash);
        local_bh_enable();
-       if (q == NULL)
-               goto oom;
-
+       if (IS_ERR_OR_NULL(q)) {
+               inet_frag_maybe_warn_overflow(q, pr_fmt());
+               return NULL;
+       }
        return container_of(q, struct frag_queue, q);
-
-oom:
-       return NULL;
 }
 
 
index e5253ec9e0fcd8b80e506956e68b29407b0be3c6..d9ba8a27fde329a4dc6176ae8b6536a598c12e43 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>
@@ -197,9 +200,10 @@ fq_find(struct net *net, __be32 id, const struct in6_addr *src, const struct in6
        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);
 }