Merge tag 'pm+acpi-3.17-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafae...
[cascardo/linux.git] / net / netfilter / nf_conntrack_ecache.c
index 1df1761..4e78c57 100644 (file)
 
 static DEFINE_MUTEX(nf_ct_ecache_mutex);
 
+#define ECACHE_RETRY_WAIT (HZ/10)
+
+enum retry_state {
+       STATE_CONGESTED,
+       STATE_RESTART,
+       STATE_DONE,
+};
+
+static enum retry_state ecache_work_evict_list(struct ct_pcpu *pcpu)
+{
+       struct nf_conn *refs[16];
+       struct nf_conntrack_tuple_hash *h;
+       struct hlist_nulls_node *n;
+       unsigned int evicted = 0;
+       enum retry_state ret = STATE_DONE;
+
+       spin_lock(&pcpu->lock);
+
+       hlist_nulls_for_each_entry(h, n, &pcpu->dying, hnnode) {
+               struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h);
+
+               if (nf_ct_is_dying(ct))
+                       continue;
+
+               if (nf_conntrack_event(IPCT_DESTROY, ct)) {
+                       ret = STATE_CONGESTED;
+                       break;
+               }
+
+               /* we've got the event delivered, now it's dying */
+               set_bit(IPS_DYING_BIT, &ct->status);
+               refs[evicted] = ct;
+
+               if (++evicted >= ARRAY_SIZE(refs)) {
+                       ret = STATE_RESTART;
+                       break;
+               }
+       }
+
+       spin_unlock(&pcpu->lock);
+
+       /* can't _put while holding lock */
+       while (evicted)
+               nf_ct_put(refs[--evicted]);
+
+       return ret;
+}
+
+static void ecache_work(struct work_struct *work)
+{
+       struct netns_ct *ctnet =
+               container_of(work, struct netns_ct, ecache_dwork.work);
+       int cpu, delay = -1;
+       struct ct_pcpu *pcpu;
+
+       local_bh_disable();
+
+       for_each_possible_cpu(cpu) {
+               enum retry_state ret;
+
+               pcpu = per_cpu_ptr(ctnet->pcpu_lists, cpu);
+
+               ret = ecache_work_evict_list(pcpu);
+
+               switch (ret) {
+               case STATE_CONGESTED:
+                       delay = ECACHE_RETRY_WAIT;
+                       goto out;
+               case STATE_RESTART:
+                       delay = 0;
+                       break;
+               case STATE_DONE:
+                       break;
+               }
+       }
+
+ out:
+       local_bh_enable();
+
+       ctnet->ecache_dwork_pending = delay > 0;
+       if (delay >= 0)
+               schedule_delayed_work(&ctnet->ecache_dwork, delay);
+}
+
 /* deliver cached events and clear cache entry - must be called with locally
  * disabled softirqs */
 void nf_ct_deliver_cached_events(struct nf_conn *ct)
@@ -157,7 +241,6 @@ EXPORT_SYMBOL_GPL(nf_ct_expect_unregister_notifier);
 
 #define NF_CT_EVENTS_DEFAULT 1
 static int nf_ct_events __read_mostly = NF_CT_EVENTS_DEFAULT;
-static int nf_ct_events_retry_timeout __read_mostly = 15*HZ;
 
 #ifdef CONFIG_SYSCTL
 static struct ctl_table event_sysctl_table[] = {
@@ -168,13 +251,6 @@ static struct ctl_table event_sysctl_table[] = {
                .mode           = 0644,
                .proc_handler   = proc_dointvec,
        },
-       {
-               .procname       = "nf_conntrack_events_retry_timeout",
-               .data           = &init_net.ct.sysctl_events_retry_timeout,
-               .maxlen         = sizeof(unsigned int),
-               .mode           = 0644,
-               .proc_handler   = proc_dointvec_jiffies,
-       },
        {}
 };
 #endif /* CONFIG_SYSCTL */
@@ -196,7 +272,6 @@ static int nf_conntrack_event_init_sysctl(struct net *net)
                goto out;
 
        table[0].data = &net->ct.sysctl_events;
-       table[1].data = &net->ct.sysctl_events_retry_timeout;
 
        /* Don't export sysctls to unprivileged users */
        if (net->user_ns != &init_user_ns)
@@ -238,12 +313,13 @@ static void nf_conntrack_event_fini_sysctl(struct net *net)
 int nf_conntrack_ecache_pernet_init(struct net *net)
 {
        net->ct.sysctl_events = nf_ct_events;
-       net->ct.sysctl_events_retry_timeout = nf_ct_events_retry_timeout;
+       INIT_DELAYED_WORK(&net->ct.ecache_dwork, ecache_work);
        return nf_conntrack_event_init_sysctl(net);
 }
 
 void nf_conntrack_ecache_pernet_fini(struct net *net)
 {
+       cancel_delayed_work_sync(&net->ct.ecache_dwork);
        nf_conntrack_event_fini_sysctl(net);
 }