Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[sfrench/cifs-2.6.git] / net / ipv4 / inet_fragment.c
index 5ce6969896f5a83dae5d1e8b433d2226bf820672..5c25727d491e75bec4c31457784676d90e29e80a 100644 (file)
@@ -106,48 +106,101 @@ int inet_frags_init(struct inet_frags *f)
        if (!f->frags_cachep)
                return -ENOMEM;
 
+       refcount_set(&f->refcnt, 1);
+       init_completion(&f->completion);
        return 0;
 }
 EXPORT_SYMBOL(inet_frags_init);
 
 void inet_frags_fini(struct inet_frags *f)
 {
-       /* We must wait that all inet_frag_destroy_rcu() have completed. */
-       rcu_barrier();
+       if (refcount_dec_and_test(&f->refcnt))
+               complete(&f->completion);
+
+       wait_for_completion(&f->completion);
 
        kmem_cache_destroy(f->frags_cachep);
        f->frags_cachep = NULL;
 }
 EXPORT_SYMBOL(inet_frags_fini);
 
+/* called from rhashtable_free_and_destroy() at netns_frags dismantle */
 static void inet_frags_free_cb(void *ptr, void *arg)
 {
        struct inet_frag_queue *fq = ptr;
+       int count;
 
-       /* If we can not cancel the timer, it means this frag_queue
-        * is already disappearing, we have nothing to do.
-        * Otherwise, we own a refcount until the end of this function.
-        */
-       if (!del_timer(&fq->timer))
-               return;
+       count = del_timer_sync(&fq->timer) ? 1 : 0;
 
        spin_lock_bh(&fq->lock);
        if (!(fq->flags & INET_FRAG_COMPLETE)) {
                fq->flags |= INET_FRAG_COMPLETE;
-               refcount_dec(&fq->refcnt);
+               count++;
+       } else if (fq->flags & INET_FRAG_HASH_DEAD) {
+               count++;
        }
        spin_unlock_bh(&fq->lock);
 
-       inet_frag_put(fq);
+       if (refcount_sub_and_test(count, &fq->refcnt))
+               inet_frag_destroy(fq);
 }
 
-void inet_frags_exit_net(struct netns_frags *nf)
+static void fqdir_rwork_fn(struct work_struct *work)
 {
-       nf->high_thresh = 0; /* prevent creation of new frags */
+       struct fqdir *fqdir = container_of(to_rcu_work(work),
+                                          struct fqdir, destroy_rwork);
+       struct inet_frags *f = fqdir->f;
+
+       rhashtable_free_and_destroy(&fqdir->rhashtable, inet_frags_free_cb, NULL);
+
+       /* We need to make sure all ongoing call_rcu(..., inet_frag_destroy_rcu)
+        * have completed, since they need to dereference fqdir.
+        * Would it not be nice to have kfree_rcu_barrier() ? :)
+        */
+       rcu_barrier();
+
+       if (refcount_dec_and_test(&f->refcnt))
+               complete(&f->completion);
 
-       rhashtable_free_and_destroy(&nf->rhashtable, inet_frags_free_cb, NULL);
+       kfree(fqdir);
 }
-EXPORT_SYMBOL(inet_frags_exit_net);
+
+int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net)
+{
+       struct fqdir *fqdir = kzalloc(sizeof(*fqdir), GFP_KERNEL);
+       int res;
+
+       if (!fqdir)
+               return -ENOMEM;
+       fqdir->f = f;
+       fqdir->net = net;
+       res = rhashtable_init(&fqdir->rhashtable, &fqdir->f->rhash_params);
+       if (res < 0) {
+               kfree(fqdir);
+               return res;
+       }
+       refcount_inc(&f->refcnt);
+       *fqdirp = fqdir;
+       return 0;
+}
+EXPORT_SYMBOL(fqdir_init);
+
+void fqdir_exit(struct fqdir *fqdir)
+{
+       fqdir->high_thresh = 0; /* prevent creation of new frags */
+
+       fqdir->dead = true;
+
+       /* call_rcu is supposed to provide memory barrier semantics,
+        * separating the setting of fqdir->dead with the destruction
+        * work.  This implicit barrier is paired with inet_frag_kill().
+        */
+
+       INIT_RCU_WORK(&fqdir->destroy_rwork, fqdir_rwork_fn);
+       queue_rcu_work(system_wq, &fqdir->destroy_rwork);
+
+}
+EXPORT_SYMBOL(fqdir_exit);
 
 void inet_frag_kill(struct inet_frag_queue *fq)
 {
@@ -155,11 +208,23 @@ void inet_frag_kill(struct inet_frag_queue *fq)
                refcount_dec(&fq->refcnt);
 
        if (!(fq->flags & INET_FRAG_COMPLETE)) {
-               struct netns_frags *nf = fq->net;
+               struct fqdir *fqdir = fq->fqdir;
 
                fq->flags |= INET_FRAG_COMPLETE;
-               rhashtable_remove_fast(&nf->rhashtable, &fq->node, nf->f->rhash_params);
-               refcount_dec(&fq->refcnt);
+               rcu_read_lock();
+               /* The RCU read lock provides a memory barrier
+                * guaranteeing that if fqdir->dead is false then
+                * the hash table destruction will not start until
+                * after we unlock.  Paired with inet_frags_exit_net().
+                */
+               if (!fqdir->dead) {
+                       rhashtable_remove_fast(&fqdir->rhashtable, &fq->node,
+                                              fqdir->f->rhash_params);
+                       refcount_dec(&fq->refcnt);
+               } else {
+                       fq->flags |= INET_FRAG_HASH_DEAD;
+               }
+               rcu_read_unlock();
        }
 }
 EXPORT_SYMBOL(inet_frag_kill);
@@ -168,7 +233,7 @@ static void inet_frag_destroy_rcu(struct rcu_head *head)
 {
        struct inet_frag_queue *q = container_of(head, struct inet_frag_queue,
                                                 rcu);
-       struct inet_frags *f = q->net->f;
+       struct inet_frags *f = q->fqdir->f;
 
        if (f->destructor)
                f->destructor(q);
@@ -199,7 +264,7 @@ EXPORT_SYMBOL(inet_frag_rbtree_purge);
 
 void inet_frag_destroy(struct inet_frag_queue *q)
 {
-       struct netns_frags *nf;
+       struct fqdir *fqdir;
        unsigned int sum, sum_truesize = 0;
        struct inet_frags *f;
 
@@ -207,18 +272,18 @@ void inet_frag_destroy(struct inet_frag_queue *q)
        WARN_ON(del_timer(&q->timer) != 0);
 
        /* Release all fragment data. */
-       nf = q->net;
-       f = nf->f;
+       fqdir = q->fqdir;
+       f = fqdir->f;
        sum_truesize = inet_frag_rbtree_purge(&q->rb_fragments);
        sum = sum_truesize + f->qsize;
 
        call_rcu(&q->rcu, inet_frag_destroy_rcu);
 
-       sub_frag_mem_limit(nf, sum);
+       sub_frag_mem_limit(fqdir, sum);
 }
 EXPORT_SYMBOL(inet_frag_destroy);
 
-static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf,
+static struct inet_frag_queue *inet_frag_alloc(struct fqdir *fqdir,
                                               struct inet_frags *f,
                                               void *arg)
 {
@@ -228,9 +293,9 @@ static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf,
        if (!q)
                return NULL;
 
-       q->net = nf;
+       q->fqdir = fqdir;
        f->constructor(q, arg);
-       add_frag_mem_limit(nf, f->qsize);
+       add_frag_mem_limit(fqdir, f->qsize);
 
        timer_setup(&q->timer, f->frag_expire, 0);
        spin_lock_init(&q->lock);
@@ -239,21 +304,21 @@ static struct inet_frag_queue *inet_frag_alloc(struct netns_frags *nf,
        return q;
 }
 
-static struct inet_frag_queue *inet_frag_create(struct netns_frags *nf,
+static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir,
                                                void *arg,
                                                struct inet_frag_queue **prev)
 {
-       struct inet_frags *f = nf->f;
+       struct inet_frags *f = fqdir->f;
        struct inet_frag_queue *q;
 
-       q = inet_frag_alloc(nf, f, arg);
+       q = inet_frag_alloc(fqdir, f, arg);
        if (!q) {
                *prev = ERR_PTR(-ENOMEM);
                return NULL;
        }
-       mod_timer(&q->timer, jiffies + nf->timeout);
+       mod_timer(&q->timer, jiffies + fqdir->timeout);
 
-       *prev = rhashtable_lookup_get_insert_key(&nf->rhashtable, &q->key,
+       *prev = rhashtable_lookup_get_insert_key(&fqdir->rhashtable, &q->key,
                                                 &q->node, f->rhash_params);
        if (*prev) {
                q->flags |= INET_FRAG_COMPLETE;
@@ -265,18 +330,18 @@ static struct inet_frag_queue *inet_frag_create(struct netns_frags *nf,
 }
 
 /* TODO : call from rcu_read_lock() and no longer use refcount_inc_not_zero() */
-struct inet_frag_queue *inet_frag_find(struct netns_frags *nf, void *key)
+struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key)
 {
        struct inet_frag_queue *fq = NULL, *prev;
 
-       if (!nf->high_thresh || frag_mem_limit(nf) > nf->high_thresh)
+       if (!fqdir->high_thresh || frag_mem_limit(fqdir) > fqdir->high_thresh)
                return NULL;
 
        rcu_read_lock();
 
-       prev = rhashtable_lookup(&nf->rhashtable, key, nf->f->rhash_params);
+       prev = rhashtable_lookup(&fqdir->rhashtable, key, fqdir->f->rhash_params);
        if (!prev)
-               fq = inet_frag_create(nf, key, &prev);
+               fq = inet_frag_create(fqdir, key, &prev);
        if (prev && !IS_ERR(prev)) {
                fq = prev;
                if (!refcount_inc_not_zero(&fq->refcnt))
@@ -387,7 +452,7 @@ void *inet_frag_reasm_prepare(struct inet_frag_queue *q, struct sk_buff *skb,
 
        delta += head->truesize;
        if (delta)
-               add_frag_mem_limit(q->net, delta);
+               add_frag_mem_limit(q->fqdir, delta);
 
        /* If the first fragment is fragmented itself, we split
         * it to two chunks: the first with data and paged part
@@ -409,7 +474,7 @@ void *inet_frag_reasm_prepare(struct inet_frag_queue *q, struct sk_buff *skb,
                head->truesize += clone->truesize;
                clone->csum = 0;
                clone->ip_summed = head->ip_summed;
-               add_frag_mem_limit(q->net, clone->truesize);
+               add_frag_mem_limit(q->fqdir, clone->truesize);
                skb_shinfo(head)->frag_list = clone;
                nextp = &clone->next;
        } else {
@@ -462,7 +527,7 @@ void inet_frag_reasm_finish(struct inet_frag_queue *q, struct sk_buff *head,
                        rbn = rbnext;
                }
        }
-       sub_frag_mem_limit(q->net, head->truesize);
+       sub_frag_mem_limit(q->fqdir, head->truesize);
 
        *nextp = NULL;
        skb_mark_not_on_list(head);
@@ -490,7 +555,7 @@ struct sk_buff *inet_frag_pull_head(struct inet_frag_queue *q)
        if (head == q->fragments_tail)
                q->fragments_tail = NULL;
 
-       sub_frag_mem_limit(q->net, head->truesize);
+       sub_frag_mem_limit(q->fqdir, head->truesize);
 
        return head;
 }