@@ -8460,6 +8460,27 @@ nft_flowtable_type_get(struct net *net, u8 family)
return ERR_PTR(-ENOENT);
}
+static int nft_register_flowtable_hook(struct net *net,
+ struct nft_flowtable *flowtable,
+ struct nft_hook *hook)
+{
+ int err;
+
+ err = flowtable->data.type->setup(&flowtable->data,
+ hook->ops.dev, FLOW_BLOCK_BIND);
+ if (err < 0)
+ return err;
+
+ err = nf_register_net_hook(net, &hook->ops);
+ if (err < 0) {
+ flowtable->data.type->setup(&flowtable->data,
+ hook->ops.dev, FLOW_BLOCK_UNBIND);
+ return err;
+ }
+
+ return 0;
+}
+
/* Only called from error and netdev event paths. */
static void nft_unregister_flowtable_hook(struct net *net,
struct nft_flowtable *flowtable,
@@ -8521,20 +8542,10 @@ static int nft_register_flowtable_net_hooks(struct net *net,
}
}
- err = flowtable->data.type->setup(&flowtable->data,
- hook->ops.dev,
- FLOW_BLOCK_BIND);
+ err = nft_register_flowtable_hook(net, flowtable, hook);
if (err < 0)
goto err_unregister_net_hooks;
- err = nf_register_net_hook(net, &hook->ops);
- if (err < 0) {
- flowtable->data.type->setup(&flowtable->data,
- hook->ops.dev,
- FLOW_BLOCK_UNBIND);
- goto err_unregister_net_hooks;
- }
-
i++;
}
@@ -9191,20 +9202,40 @@ static int nf_tables_fill_gen_info(struct sk_buff *skb, struct net *net,
return -EMSGSIZE;
}
-static void nft_flowtable_event(unsigned long event, struct net_device *dev,
- struct nft_flowtable *flowtable)
+static int nft_flowtable_event(unsigned long event, struct net_device *dev,
+ struct nft_flowtable *flowtable)
{
struct nft_hook *hook;
list_for_each_entry(hook, &flowtable->hook_list, list) {
- if (hook->ops.dev != dev)
- continue;
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ if (hook->ops.dev != dev)
+ break;
- /* flow_offload_netdev_event() cleans up entries for us. */
- nft_unregister_flowtable_hook(dev_net(dev), flowtable, hook);
- hook->ops.dev = NULL;
- break;
+ /* flow_offload_netdev_event() cleans up entries for us. */
+ nft_unregister_flowtable_hook(dev_net(dev),
+ flowtable, hook);
+ hook->ops.dev = NULL;
+ return 1;
+ case NETDEV_REGISTER:
+ if (hook->ops.dev ||
+ strncmp(hook->ifname, dev->name, hook->ifnamelen))
+ break;
+
+ hook->ops.dev = dev;
+ if (!nft_register_flowtable_hook(dev_net(dev),
+ flowtable, hook))
+ return 1;
+
+ printk(KERN_ERR
+ "flowtable %s: Can't hook into device %s\n",
+ flowtable->name, dev->name);
+ hook->ops.dev = NULL;
+ break;
+ }
}
+ return 0;
}
static int nf_tables_flowtable_event(struct notifier_block *this,
@@ -9216,7 +9247,8 @@ static int nf_tables_flowtable_event(struct notifier_block *this,
struct nft_table *table;
struct net *net;
- if (event != NETDEV_UNREGISTER)
+ if (event != NETDEV_UNREGISTER &&
+ event != NETDEV_REGISTER)
return 0;
net = dev_net(dev);
@@ -9224,9 +9256,11 @@ static int nf_tables_flowtable_event(struct notifier_block *this,
mutex_lock(&nft_net->commit_mutex);
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(flowtable, &table->flowtables, list) {
- nft_flowtable_event(event, dev, flowtable);
+ if (nft_flowtable_event(event, dev, flowtable))
+ goto out_unlock;
}
}
+out_unlock:
mutex_unlock(&nft_net->commit_mutex);
return NOTIFY_DONE;
@@ -318,19 +318,50 @@ static const struct nft_chain_type nft_chain_filter_netdev = {
},
};
+static int nft_netdev_hook_dev_update(struct nft_hook *hook,
+ struct net_device *dev)
+{
+ int ret = 0;
+
+ if (hook->ops.dev)
+ nf_unregister_net_hook(dev_net(hook->ops.dev), &hook->ops);
+
+ hook->ops.dev = dev;
+
+ if (dev) {
+ ret = nf_register_net_hook(dev_net(dev), &hook->ops);
+ if (ret < 0)
+ hook->ops.dev = NULL;
+ }
+
+ return ret;
+}
+
static void nft_netdev_event(unsigned long event, struct net_device *dev,
struct nft_ctx *ctx)
{
struct nft_base_chain *basechain = nft_base_chain(ctx->chain);
struct nft_hook *hook;
- if (event != NETDEV_UNREGISTER)
+ if (event != NETDEV_UNREGISTER &&
+ event != NETDEV_REGISTER)
return;
list_for_each_entry(hook, &basechain->hook_list, list) {
- if (hook->ops.dev == dev) {
- nf_unregister_net_hook(ctx->net, &hook->ops);
- hook->ops.dev = NULL;
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ if (hook->ops.dev == dev)
+ nft_netdev_hook_dev_update(hook, NULL);
+ break;
+ case NETDEV_REGISTER:
+ if (hook->ops.dev ||
+ strncmp(hook->ifname, dev->name, hook->ifnamelen))
+ break;
+ if (!nft_netdev_hook_dev_update(hook, dev))
+ return;
+
+ printk(KERN_ERR "chain %s: Can't hook into device %s\n",
+ ctx->chain->name, dev->name);
break;
}
}
@@ -349,6 +380,7 @@ static int nf_tables_netdev_event(struct notifier_block *this,
};
if (event != NETDEV_UNREGISTER &&
+ event != NETDEV_REGISTER &&
event != NETDEV_CHANGENAME)
return NOTIFY_DONE;
Upon NETDEV_REGISTER event, search existing flowtables and netdev-family chains for a matching inactive hook and bind the device. Signed-off-by: Phil Sutter <phil@nwl.cc> --- net/netfilter/nf_tables_api.c | 76 +++++++++++++++++++++++--------- net/netfilter/nft_chain_filter.c | 40 +++++++++++++++-- 2 files changed, 91 insertions(+), 25 deletions(-)