netfilter: nf_tables_netdev: unregister hooks on net_device removal
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 15 Jun 2015 10:12:01 +0000 (12:12 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 15 Jun 2015 21:02:35 +0000 (23:02 +0200)
In case the net_device is gone, we have to unregister the hooks and put back
the reference on the net_device object. Once it comes back, register them
again. This also covers the device rename case.

This patch also adds a new flag to indicate that the basechain is disabled, so
their hooks are not registered. This flag is used by the netdev family to
handle the case where the net_device object is gone. Currently this flag is not
exposed to userspace.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_tables.h
net/netfilter/nf_tables_api.c
net/netfilter/nf_tables_netdev.c

index 09d6f8df60f0323e02bef33f6497875fbed27219..2a246680a6c38934485fbd7a540df04ad47a0253 100644 (file)
@@ -781,6 +781,7 @@ struct nft_stats {
 };
 
 #define NFT_HOOK_OPS_MAX               2
+#define NFT_BASECHAIN_DISABLED         (1 << 0)
 
 /**
  *     struct nft_base_chain - nf_tables base chain
@@ -798,6 +799,7 @@ struct nft_base_chain {
        possible_net_t                  pnet;
        const struct nf_chain_type      *type;
        u8                              policy;
+       u8                              flags;
        struct nft_stats __percpu       *stats;
        struct nft_chain                chain;
        char                            dev_name[IFNAMSIZ];
@@ -808,6 +810,11 @@ static inline struct nft_base_chain *nft_base_chain(const struct nft_chain *chai
        return container_of(chain, struct nft_base_chain, chain);
 }
 
+int nft_register_basechain(struct nft_base_chain *basechain,
+                          unsigned int hook_nops);
+void nft_unregister_basechain(struct nft_base_chain *basechain,
+                             unsigned int hook_nops);
+
 unsigned int nft_do_chain(struct nft_pktinfo *pkt,
                          const struct nf_hook_ops *ops);
 
index c66dc62afb468c3a8d9cc05b55546240ee61a4cb..cfe636808541a07f2c4b69c29737d88a167efe2f 100644 (file)
@@ -127,17 +127,25 @@ static void nft_trans_destroy(struct nft_trans *trans)
        kfree(trans);
 }
 
-static int nft_register_basechain(struct nft_base_chain *basechain,
-                                 unsigned int hook_nops)
+int nft_register_basechain(struct nft_base_chain *basechain,
+                          unsigned int hook_nops)
 {
+       if (basechain->flags & NFT_BASECHAIN_DISABLED)
+               return 0;
+
        return nf_register_hooks(basechain->ops, hook_nops);
 }
+EXPORT_SYMBOL_GPL(nft_register_basechain);
 
-static void nft_unregister_basechain(struct nft_base_chain *basechain,
-                                    unsigned int hook_nops)
+void nft_unregister_basechain(struct nft_base_chain *basechain,
+                             unsigned int hook_nops)
 {
+       if (basechain->flags & NFT_BASECHAIN_DISABLED)
+               return;
+
        nf_unregister_hooks(basechain->ops, hook_nops);
 }
+EXPORT_SYMBOL_GPL(nft_unregister_basechain);
 
 static int nf_tables_register_hooks(const struct nft_table *table,
                                    struct nft_chain *chain,
index 04cb17057f46a6ee4113f550a0c50862988da0b9..2cae4d4a03b77c0a08b1d0f29900b5f06082572c 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <linux/init.h>
 #include <linux/module.h>
+#include <linux/netdevice.h>
 #include <net/netfilter/nf_tables.h>
 #include <linux/ip.h>
 #include <linux/ipv6.h>
@@ -157,6 +158,77 @@ static const struct nf_chain_type nft_filter_chain_netdev = {
        .hook_mask      = (1 << NF_NETDEV_INGRESS),
 };
 
+static void nft_netdev_event(unsigned long event, struct nft_af_info *afi,
+                            struct net_device *dev, struct nft_table *table,
+                            struct nft_base_chain *basechain)
+{
+       switch (event) {
+       case NETDEV_REGISTER:
+               if (strcmp(basechain->dev_name, dev->name) != 0)
+                       return;
+
+               BUG_ON(!(basechain->flags & NFT_BASECHAIN_DISABLED));
+
+               dev_hold(dev);
+               basechain->ops[0].dev = dev;
+               basechain->flags &= ~NFT_BASECHAIN_DISABLED;
+               if (!(table->flags & NFT_TABLE_F_DORMANT))
+                       nft_register_basechain(basechain, afi->nops);
+               break;
+       case NETDEV_UNREGISTER:
+               if (strcmp(basechain->dev_name, dev->name) != 0)
+                       return;
+
+               BUG_ON(basechain->flags & NFT_BASECHAIN_DISABLED);
+
+               if (!(table->flags & NFT_TABLE_F_DORMANT))
+                       nft_unregister_basechain(basechain, afi->nops);
+
+               dev_put(basechain->ops[0].dev);
+               basechain->ops[0].dev = NULL;
+               basechain->flags |= NFT_BASECHAIN_DISABLED;
+               break;
+       case NETDEV_CHANGENAME:
+               if (dev->ifindex != basechain->ops[0].dev->ifindex)
+                       return;
+
+               strncpy(basechain->dev_name, dev->name, IFNAMSIZ);
+               break;
+       }
+}
+
+static int nf_tables_netdev_event(struct notifier_block *this,
+                                 unsigned long event, void *ptr)
+{
+       struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+       struct nft_af_info *afi;
+       struct nft_table *table;
+       struct nft_chain *chain;
+
+       nfnl_lock(NFNL_SUBSYS_NFTABLES);
+       list_for_each_entry(afi, &dev_net(dev)->nft.af_info, list) {
+               if (afi->family != NFPROTO_NETDEV)
+                       continue;
+
+               list_for_each_entry(table, &afi->tables, list) {
+                       list_for_each_entry(chain, &table->chains, list) {
+                               if (!(chain->flags & NFT_BASE_CHAIN))
+                                       continue;
+
+                               nft_netdev_event(event, afi, dev, table,
+                                                nft_base_chain(chain));
+                       }
+               }
+       }
+       nfnl_unlock(NFNL_SUBSYS_NFTABLES);
+
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block nf_tables_netdev_notifier = {
+       .notifier_call  = nf_tables_netdev_event,
+};
+
 static int __init nf_tables_netdev_init(void)
 {
        int ret;
@@ -166,11 +238,14 @@ static int __init nf_tables_netdev_init(void)
        if (ret < 0)
                nft_unregister_chain_type(&nft_filter_chain_netdev);
 
+       register_netdevice_notifier(&nf_tables_netdev_notifier);
+
        return ret;
 }
 
 static void __exit nf_tables_netdev_exit(void)
 {
+       unregister_netdevice_notifier(&nf_tables_netdev_notifier);
        unregister_pernet_subsys(&nf_tables_netdev_net_ops);
        nft_unregister_chain_type(&nft_filter_chain_netdev);
 }