netfilter: nf_queue: fix nf_queue_nf_hook_drop()
[cascardo/linux.git] / net / netfilter / core.c
index a0e5497..12504fb 100644 (file)
@@ -34,6 +34,9 @@ EXPORT_SYMBOL(nf_afinfo);
 const struct nf_ipv6_ops __rcu *nf_ipv6_ops __read_mostly;
 EXPORT_SYMBOL_GPL(nf_ipv6_ops);
 
+DEFINE_PER_CPU(bool, nf_skb_duplicated);
+EXPORT_SYMBOL_GPL(nf_skb_duplicated);
+
 int nf_register_afinfo(const struct nf_afinfo *afinfo)
 {
        mutex_lock(&afinfo_mutex);
@@ -52,9 +55,6 @@ void nf_unregister_afinfo(const struct nf_afinfo *afinfo)
 }
 EXPORT_SYMBOL_GPL(nf_unregister_afinfo);
 
-struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS] __read_mostly;
-EXPORT_SYMBOL(nf_hooks);
-
 #ifdef HAVE_JUMP_LABEL
 struct static_key nf_hooks_needed[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
 EXPORT_SYMBOL(nf_hooks_needed);
@@ -62,63 +62,169 @@ EXPORT_SYMBOL(nf_hooks_needed);
 
 static DEFINE_MUTEX(nf_hook_mutex);
 
-int nf_register_hook(struct nf_hook_ops *reg)
+static struct list_head *find_nf_hook_list(struct net *net,
+                                          const struct nf_hook_ops *reg)
 {
-       struct list_head *nf_hook_list;
-       struct nf_hook_ops *elem;
+       struct list_head *nf_hook_list = NULL;
 
-       mutex_lock(&nf_hook_mutex);
-       switch (reg->pf) {
-       case NFPROTO_NETDEV:
+       if (reg->pf != NFPROTO_NETDEV)
+               nf_hook_list = &net->nf.hooks[reg->pf][reg->hooknum];
+       else if (reg->hooknum == NF_NETDEV_INGRESS) {
 #ifdef CONFIG_NETFILTER_INGRESS
-               if (reg->hooknum == NF_NETDEV_INGRESS) {
-                       BUG_ON(reg->dev == NULL);
+               if (reg->dev && dev_net(reg->dev) == net)
                        nf_hook_list = &reg->dev->nf_hooks_ingress;
-                       net_inc_ingress_queue();
-                       break;
-               }
 #endif
-               /* Fall through. */
-       default:
-               nf_hook_list = &nf_hooks[reg->pf][reg->hooknum];
-               break;
+       }
+       return nf_hook_list;
+}
+
+int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
+{
+       struct list_head *nf_hook_list;
+       struct nf_hook_ops *elem, *new;
+
+       new = kzalloc(sizeof(*new), GFP_KERNEL);
+       if (!new)
+               return -ENOMEM;
+
+       new->hook     = reg->hook;
+       new->dev      = reg->dev;
+       new->owner    = reg->owner;
+       new->priv     = reg->priv;
+       new->pf       = reg->pf;
+       new->hooknum  = reg->hooknum;
+       new->priority = reg->priority;
+
+       nf_hook_list = find_nf_hook_list(net, reg);
+       if (!nf_hook_list) {
+               kfree(new);
+               return -ENOENT;
        }
 
+       mutex_lock(&nf_hook_mutex);
        list_for_each_entry(elem, nf_hook_list, list) {
                if (reg->priority < elem->priority)
                        break;
        }
-       list_add_rcu(&reg->list, elem->list.prev);
+       list_add_rcu(&new->list, elem->list.prev);
        mutex_unlock(&nf_hook_mutex);
+#ifdef CONFIG_NETFILTER_INGRESS
+       if (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
+               net_inc_ingress_queue();
+#endif
 #ifdef HAVE_JUMP_LABEL
        static_key_slow_inc(&nf_hooks_needed[reg->pf][reg->hooknum]);
 #endif
        return 0;
 }
-EXPORT_SYMBOL(nf_register_hook);
+EXPORT_SYMBOL(nf_register_net_hook);
 
-void nf_unregister_hook(struct nf_hook_ops *reg)
+void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)
 {
+       struct list_head *nf_hook_list;
+       struct nf_hook_ops *elem;
+
+       nf_hook_list = find_nf_hook_list(net, reg);
+       if (!nf_hook_list)
+               return;
+
        mutex_lock(&nf_hook_mutex);
-       list_del_rcu(&reg->list);
-       mutex_unlock(&nf_hook_mutex);
-       switch (reg->pf) {
-       case NFPROTO_NETDEV:
-#ifdef CONFIG_NETFILTER_INGRESS
-               if (reg->hooknum == NF_NETDEV_INGRESS) {
-                       net_dec_ingress_queue();
+       list_for_each_entry(elem, nf_hook_list, list) {
+               if ((reg->hook     == elem->hook) &&
+                   (reg->dev      == elem->dev) &&
+                   (reg->owner    == elem->owner) &&
+                   (reg->priv     == elem->priv) &&
+                   (reg->pf       == elem->pf) &&
+                   (reg->hooknum  == elem->hooknum) &&
+                   (reg->priority == elem->priority)) {
+                       list_del_rcu(&elem->list);
                        break;
                }
-               break;
-#endif
-       default:
-               break;
        }
+       mutex_unlock(&nf_hook_mutex);
+       if (&elem->list == nf_hook_list) {
+               WARN(1, "nf_unregister_net_hook: hook not found!\n");
+               return;
+       }
+#ifdef CONFIG_NETFILTER_INGRESS
+       if (reg->pf == NFPROTO_NETDEV && reg->hooknum == NF_NETDEV_INGRESS)
+               net_dec_ingress_queue();
+#endif
 #ifdef HAVE_JUMP_LABEL
        static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]);
 #endif
        synchronize_net();
-       nf_queue_nf_hook_drop(reg);
+       nf_queue_nf_hook_drop(net, elem);
+       kfree(elem);
+}
+EXPORT_SYMBOL(nf_unregister_net_hook);
+
+int nf_register_net_hooks(struct net *net, const struct nf_hook_ops *reg,
+                         unsigned int n)
+{
+       unsigned int i;
+       int err = 0;
+
+       for (i = 0; i < n; i++) {
+               err = nf_register_net_hook(net, &reg[i]);
+               if (err)
+                       goto err;
+       }
+       return err;
+
+err:
+       if (i > 0)
+               nf_unregister_net_hooks(net, reg, i);
+       return err;
+}
+EXPORT_SYMBOL(nf_register_net_hooks);
+
+void nf_unregister_net_hooks(struct net *net, const struct nf_hook_ops *reg,
+                            unsigned int n)
+{
+       while (n-- > 0)
+               nf_unregister_net_hook(net, &reg[n]);
+}
+EXPORT_SYMBOL(nf_unregister_net_hooks);
+
+static LIST_HEAD(nf_hook_list);
+
+int nf_register_hook(struct nf_hook_ops *reg)
+{
+       struct net *net, *last;
+       int ret;
+
+       rtnl_lock();
+       for_each_net(net) {
+               ret = nf_register_net_hook(net, reg);
+               if (ret && ret != -ENOENT)
+                       goto rollback;
+       }
+       list_add_tail(&reg->list, &nf_hook_list);
+       rtnl_unlock();
+
+       return 0;
+rollback:
+       last = net;
+       for_each_net(net) {
+               if (net == last)
+                       break;
+               nf_unregister_net_hook(net, reg);
+       }
+       rtnl_unlock();
+       return ret;
+}
+EXPORT_SYMBOL(nf_register_hook);
+
+void nf_unregister_hook(struct nf_hook_ops *reg)
+{
+       struct net *net;
+
+       rtnl_lock();
+       list_del(&reg->list);
+       for_each_net(net)
+               nf_unregister_net_hook(net, reg);
+       rtnl_unlock();
 }
 EXPORT_SYMBOL(nf_unregister_hook);
 
@@ -295,8 +401,46 @@ void (*nf_nat_decode_session_hook)(struct sk_buff *, struct flowi *);
 EXPORT_SYMBOL(nf_nat_decode_session_hook);
 #endif
 
+static int nf_register_hook_list(struct net *net)
+{
+       struct nf_hook_ops *elem;
+       int ret;
+
+       rtnl_lock();
+       list_for_each_entry(elem, &nf_hook_list, list) {
+               ret = nf_register_net_hook(net, elem);
+               if (ret && ret != -ENOENT)
+                       goto out_undo;
+       }
+       rtnl_unlock();
+       return 0;
+
+out_undo:
+       list_for_each_entry_continue_reverse(elem, &nf_hook_list, list)
+               nf_unregister_net_hook(net, elem);
+       rtnl_unlock();
+       return ret;
+}
+
+static void nf_unregister_hook_list(struct net *net)
+{
+       struct nf_hook_ops *elem;
+
+       rtnl_lock();
+       list_for_each_entry(elem, &nf_hook_list, list)
+               nf_unregister_net_hook(net, elem);
+       rtnl_unlock();
+}
+
 static int __net_init netfilter_net_init(struct net *net)
 {
+       int i, h, ret;
+
+       for (i = 0; i < ARRAY_SIZE(net->nf.hooks); i++) {
+               for (h = 0; h < NF_MAX_HOOKS; h++)
+                       INIT_LIST_HEAD(&net->nf.hooks[i][h]);
+       }
+
 #ifdef CONFIG_PROC_FS
        net->nf.proc_netfilter = proc_net_mkdir(net, "netfilter",
                                                net->proc_net);
@@ -307,11 +451,16 @@ static int __net_init netfilter_net_init(struct net *net)
                return -ENOMEM;
        }
 #endif
-       return 0;
+       ret = nf_register_hook_list(net);
+       if (ret)
+               remove_proc_entry("netfilter", net->proc_net);
+
+       return ret;
 }
 
 static void __net_exit netfilter_net_exit(struct net *net)
 {
+       nf_unregister_hook_list(net);
        remove_proc_entry("netfilter", net->proc_net);
 }
 
@@ -322,12 +471,7 @@ static struct pernet_operations netfilter_net_ops = {
 
 int __init netfilter_init(void)
 {
-       int i, h, ret;
-
-       for (i = 0; i < ARRAY_SIZE(nf_hooks); i++) {
-               for (h = 0; h < NF_MAX_HOOKS; h++)
-                       INIT_LIST_HEAD(&nf_hooks[i][h]);
-       }
+       int ret;
 
        ret = register_pernet_subsys(&netfilter_net_ops);
        if (ret < 0)