Merge branch 'core-rcu-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-2.6-block.git] / net / netfilter / nf_tables_api.c
index 712a428509add2c28261b9daf537abe8f9d71311..062b73a83af0efd2537669ec515312f50815d041 100644 (file)
@@ -151,11 +151,64 @@ static void nft_set_trans_bind(const struct nft_ctx *ctx, struct nft_set *set)
        }
 }
 
+static int nft_netdev_register_hooks(struct net *net,
+                                    struct list_head *hook_list)
+{
+       struct nft_hook *hook;
+       int err, j;
+
+       j = 0;
+       list_for_each_entry(hook, hook_list, list) {
+               err = nf_register_net_hook(net, &hook->ops);
+               if (err < 0)
+                       goto err_register;
+
+               j++;
+       }
+       return 0;
+
+err_register:
+       list_for_each_entry(hook, hook_list, list) {
+               if (j-- <= 0)
+                       break;
+
+               nf_unregister_net_hook(net, &hook->ops);
+       }
+       return err;
+}
+
+static void nft_netdev_unregister_hooks(struct net *net,
+                                       struct list_head *hook_list)
+{
+       struct nft_hook *hook;
+
+       list_for_each_entry(hook, hook_list, list)
+               nf_unregister_net_hook(net, &hook->ops);
+}
+
+static int nft_register_basechain_hooks(struct net *net, int family,
+                                       struct nft_base_chain *basechain)
+{
+       if (family == NFPROTO_NETDEV)
+               return nft_netdev_register_hooks(net, &basechain->hook_list);
+
+       return nf_register_net_hook(net, &basechain->ops);
+}
+
+static void nft_unregister_basechain_hooks(struct net *net, int family,
+                                          struct nft_base_chain *basechain)
+{
+       if (family == NFPROTO_NETDEV)
+               nft_netdev_unregister_hooks(net, &basechain->hook_list);
+       else
+               nf_unregister_net_hook(net, &basechain->ops);
+}
+
 static int nf_tables_register_hook(struct net *net,
                                   const struct nft_table *table,
                                   struct nft_chain *chain)
 {
-       const struct nft_base_chain *basechain;
+       struct nft_base_chain *basechain;
        const struct nf_hook_ops *ops;
 
        if (table->flags & NFT_TABLE_F_DORMANT ||
@@ -168,14 +221,14 @@ static int nf_tables_register_hook(struct net *net,
        if (basechain->type->ops_register)
                return basechain->type->ops_register(net, ops);
 
-       return nf_register_net_hook(net, ops);
+       return nft_register_basechain_hooks(net, table->family, basechain);
 }
 
 static void nf_tables_unregister_hook(struct net *net,
                                      const struct nft_table *table,
                                      struct nft_chain *chain)
 {
-       const struct nft_base_chain *basechain;
+       struct nft_base_chain *basechain;
        const struct nf_hook_ops *ops;
 
        if (table->flags & NFT_TABLE_F_DORMANT ||
@@ -187,7 +240,7 @@ static void nf_tables_unregister_hook(struct net *net,
        if (basechain->type->ops_unregister)
                return basechain->type->ops_unregister(net, ops);
 
-       nf_unregister_net_hook(net, ops);
+       nft_unregister_basechain_hooks(net, table->family, basechain);
 }
 
 static int nft_trans_table_add(struct nft_ctx *ctx, int msg_type)
@@ -308,6 +361,7 @@ static struct nft_trans *nft_trans_rule_add(struct nft_ctx *ctx, int msg_type,
 
 static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
 {
+       struct nft_flow_rule *flow;
        struct nft_trans *trans;
        int err;
 
@@ -315,6 +369,16 @@ static int nft_delrule(struct nft_ctx *ctx, struct nft_rule *rule)
        if (trans == NULL)
                return -ENOMEM;
 
+       if (ctx->chain->flags & NFT_CHAIN_HW_OFFLOAD) {
+               flow = nft_flow_rule_create(ctx->net, rule);
+               if (IS_ERR(flow)) {
+                       nft_trans_destroy(trans);
+                       return PTR_ERR(flow);
+               }
+
+               nft_trans_flow_rule(trans) = flow;
+       }
+
        err = nf_tables_delrule_deactivate(ctx, rule);
        if (err < 0) {
                nft_trans_destroy(trans);
@@ -742,7 +806,8 @@ static void nft_table_disable(struct net *net, struct nft_table *table, u32 cnt)
                if (cnt && i++ == cnt)
                        break;
 
-               nf_unregister_net_hook(net, &nft_base_chain(chain)->ops);
+               nft_unregister_basechain_hooks(net, table->family,
+                                              nft_base_chain(chain));
        }
 }
 
@@ -757,14 +822,16 @@ static int nf_tables_table_enable(struct net *net, struct nft_table *table)
                if (!nft_is_base_chain(chain))
                        continue;
 
-               err = nf_register_net_hook(net, &nft_base_chain(chain)->ops);
+               err = nft_register_basechain_hooks(net, table->family,
+                                                  nft_base_chain(chain));
                if (err < 0)
-                       goto err;
+                       goto err_register_hooks;
 
                i++;
        }
        return 0;
-err:
+
+err_register_hooks:
        if (i)
                nft_table_disable(net, table, i);
        return err;
@@ -1225,6 +1292,46 @@ nla_put_failure:
        return -ENOSPC;
 }
 
+static int nft_dump_basechain_hook(struct sk_buff *skb, int family,
+                                  const struct nft_base_chain *basechain)
+{
+       const struct nf_hook_ops *ops = &basechain->ops;
+       struct nft_hook *hook, *first = NULL;
+       struct nlattr *nest, *nest_devs;
+       int n = 0;
+
+       nest = nla_nest_start_noflag(skb, NFTA_CHAIN_HOOK);
+       if (nest == NULL)
+               goto nla_put_failure;
+       if (nla_put_be32(skb, NFTA_HOOK_HOOKNUM, htonl(ops->hooknum)))
+               goto nla_put_failure;
+       if (nla_put_be32(skb, NFTA_HOOK_PRIORITY, htonl(ops->priority)))
+               goto nla_put_failure;
+
+       if (family == NFPROTO_NETDEV) {
+               nest_devs = nla_nest_start_noflag(skb, NFTA_HOOK_DEVS);
+               list_for_each_entry(hook, &basechain->hook_list, list) {
+                       if (!first)
+                               first = hook;
+
+                       if (nla_put_string(skb, NFTA_DEVICE_NAME,
+                                          hook->ops.dev->name))
+                               goto nla_put_failure;
+                       n++;
+               }
+               nla_nest_end(skb, nest_devs);
+
+               if (n == 1 &&
+                   nla_put_string(skb, NFTA_HOOK_DEV, first->ops.dev->name))
+                       goto nla_put_failure;
+       }
+       nla_nest_end(skb, nest);
+
+       return 0;
+nla_put_failure:
+       return -1;
+}
+
 static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net,
                                     u32 portid, u32 seq, int event, u32 flags,
                                     int family, const struct nft_table *table,
@@ -1253,21 +1360,10 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net,
 
        if (nft_is_base_chain(chain)) {
                const struct nft_base_chain *basechain = nft_base_chain(chain);
-               const struct nf_hook_ops *ops = &basechain->ops;
                struct nft_stats __percpu *stats;
-               struct nlattr *nest;
 
-               nest = nla_nest_start_noflag(skb, NFTA_CHAIN_HOOK);
-               if (nest == NULL)
-                       goto nla_put_failure;
-               if (nla_put_be32(skb, NFTA_HOOK_HOOKNUM, htonl(ops->hooknum)))
+               if (nft_dump_basechain_hook(skb, family, basechain))
                        goto nla_put_failure;
-               if (nla_put_be32(skb, NFTA_HOOK_PRIORITY, htonl(ops->priority)))
-                       goto nla_put_failure;
-               if (basechain->dev_name[0] &&
-                   nla_put_string(skb, NFTA_HOOK_DEV, basechain->dev_name))
-                       goto nla_put_failure;
-               nla_nest_end(skb, nest);
 
                if (nla_put_be32(skb, NFTA_CHAIN_POLICY,
                                 htonl(basechain->policy)))
@@ -1461,8 +1557,9 @@ static void nft_chain_stats_replace(struct nft_trans *trans)
        if (!nft_trans_chain_stats(trans))
                return;
 
-       rcu_swap_protected(chain->stats, nft_trans_chain_stats(trans),
-                          lockdep_commit_lock_is_held(trans->ctx.net));
+       nft_trans_chain_stats(trans) =
+               rcu_replace_pointer(chain->stats, nft_trans_chain_stats(trans),
+                                   lockdep_commit_lock_is_held(trans->ctx.net));
 
        if (!nft_trans_chain_stats(trans))
                static_branch_inc(&nft_counters_enabled);
@@ -1485,6 +1582,7 @@ static void nf_tables_chain_free_chain_rules(struct nft_chain *chain)
 static void nf_tables_chain_destroy(struct nft_ctx *ctx)
 {
        struct nft_chain *chain = ctx->chain;
+       struct nft_hook *hook, *next;
 
        if (WARN_ON(chain->use > 0))
                return;
@@ -1495,6 +1593,13 @@ static void nf_tables_chain_destroy(struct nft_ctx *ctx)
        if (nft_is_base_chain(chain)) {
                struct nft_base_chain *basechain = nft_base_chain(chain);
 
+               if (ctx->family == NFPROTO_NETDEV) {
+                       list_for_each_entry_safe(hook, next,
+                                                &basechain->hook_list, list) {
+                               list_del_rcu(&hook->list);
+                               kfree_rcu(hook, rcu);
+                       }
+               }
                module_put(basechain->type->owner);
                if (rcu_access_pointer(basechain->stats)) {
                        static_branch_dec(&nft_counters_enabled);
@@ -1508,13 +1613,125 @@ static void nf_tables_chain_destroy(struct nft_ctx *ctx)
        }
 }
 
+static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
+                                             const struct nlattr *attr)
+{
+       struct net_device *dev;
+       char ifname[IFNAMSIZ];
+       struct nft_hook *hook;
+       int err;
+
+       hook = kmalloc(sizeof(struct nft_hook), GFP_KERNEL);
+       if (!hook) {
+               err = -ENOMEM;
+               goto err_hook_alloc;
+       }
+
+       nla_strlcpy(ifname, attr, IFNAMSIZ);
+       dev = __dev_get_by_name(net, ifname);
+       if (!dev) {
+               err = -ENOENT;
+               goto err_hook_dev;
+       }
+       hook->ops.dev = dev;
+
+       return hook;
+
+err_hook_dev:
+       kfree(hook);
+err_hook_alloc:
+       return ERR_PTR(err);
+}
+
+static bool nft_hook_list_find(struct list_head *hook_list,
+                              const struct nft_hook *this)
+{
+       struct nft_hook *hook;
+
+       list_for_each_entry(hook, hook_list, list) {
+               if (this->ops.dev == hook->ops.dev)
+                       return true;
+       }
+
+       return false;
+}
+
+static int nf_tables_parse_netdev_hooks(struct net *net,
+                                       const struct nlattr *attr,
+                                       struct list_head *hook_list)
+{
+       struct nft_hook *hook, *next;
+       const struct nlattr *tmp;
+       int rem, n = 0, err;
+
+       nla_for_each_nested(tmp, attr, rem) {
+               if (nla_type(tmp) != NFTA_DEVICE_NAME) {
+                       err = -EINVAL;
+                       goto err_hook;
+               }
+
+               hook = nft_netdev_hook_alloc(net, tmp);
+               if (IS_ERR(hook)) {
+                       err = PTR_ERR(hook);
+                       goto err_hook;
+               }
+               if (nft_hook_list_find(hook_list, hook)) {
+                       err = -EEXIST;
+                       goto err_hook;
+               }
+               list_add_tail(&hook->list, hook_list);
+               n++;
+
+               if (n == NFT_NETDEVICE_MAX) {
+                       err = -EFBIG;
+                       goto err_hook;
+               }
+       }
+       if (!n)
+               return -EINVAL;
+
+       return 0;
+
+err_hook:
+       list_for_each_entry_safe(hook, next, hook_list, list) {
+               list_del(&hook->list);
+               kfree(hook);
+       }
+       return err;
+}
+
 struct nft_chain_hook {
        u32                             num;
        s32                             priority;
        const struct nft_chain_type     *type;
-       struct net_device               *dev;
+       struct list_head                list;
 };
 
+static int nft_chain_parse_netdev(struct net *net,
+                                 struct nlattr *tb[],
+                                 struct list_head *hook_list)
+{
+       struct nft_hook *hook;
+       int err;
+
+       if (tb[NFTA_HOOK_DEV]) {
+               hook = nft_netdev_hook_alloc(net, tb[NFTA_HOOK_DEV]);
+               if (IS_ERR(hook))
+                       return PTR_ERR(hook);
+
+               list_add_tail(&hook->list, hook_list);
+       } else if (tb[NFTA_HOOK_DEVS]) {
+               err = nf_tables_parse_netdev_hooks(net, tb[NFTA_HOOK_DEVS],
+                                                  hook_list);
+               if (err < 0)
+                       return err;
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static int nft_chain_parse_hook(struct net *net,
                                const struct nlattr * const nla[],
                                struct nft_chain_hook *hook, u8 family,
@@ -1522,7 +1739,6 @@ static int nft_chain_parse_hook(struct net *net,
 {
        struct nlattr *ha[NFTA_HOOK_MAX + 1];
        const struct nft_chain_type *type;
-       struct net_device *dev;
        int err;
 
        lockdep_assert_held(&net->nft.commit_mutex);
@@ -1560,23 +1776,14 @@ static int nft_chain_parse_hook(struct net *net,
 
        hook->type = type;
 
-       hook->dev = NULL;
+       INIT_LIST_HEAD(&hook->list);
        if (family == NFPROTO_NETDEV) {
-               char ifname[IFNAMSIZ];
-
-               if (!ha[NFTA_HOOK_DEV]) {
-                       module_put(type->owner);
-                       return -EOPNOTSUPP;
-               }
-
-               nla_strlcpy(ifname, ha[NFTA_HOOK_DEV], IFNAMSIZ);
-               dev = __dev_get_by_name(net, ifname);
-               if (!dev) {
+               err = nft_chain_parse_netdev(net, ha, &hook->list);
+               if (err < 0) {
                        module_put(type->owner);
-                       return -ENOENT;
+                       return err;
                }
-               hook->dev = dev;
-       } else if (ha[NFTA_HOOK_DEV]) {
+       } else if (ha[NFTA_HOOK_DEV] || ha[NFTA_HOOK_DEVS]) {
                module_put(type->owner);
                return -EOPNOTSUPP;
        }
@@ -1586,6 +1793,12 @@ static int nft_chain_parse_hook(struct net *net,
 
 static void nft_chain_release_hook(struct nft_chain_hook *hook)
 {
+       struct nft_hook *h, *next;
+
+       list_for_each_entry_safe(h, next, &hook->list, list) {
+               list_del(&h->list);
+               kfree(h);
+       }
        module_put(hook->type->owner);
 }
 
@@ -1610,6 +1823,49 @@ static struct nft_rule **nf_tables_chain_alloc_rules(const struct nft_chain *cha
        return kvmalloc(alloc, GFP_KERNEL);
 }
 
+static void nft_basechain_hook_init(struct nf_hook_ops *ops, u8 family,
+                                   const struct nft_chain_hook *hook,
+                                   struct nft_chain *chain)
+{
+       ops->pf         = family;
+       ops->hooknum    = hook->num;
+       ops->priority   = hook->priority;
+       ops->priv       = chain;
+       ops->hook       = hook->type->hooks[ops->hooknum];
+}
+
+static int nft_basechain_init(struct nft_base_chain *basechain, u8 family,
+                             struct nft_chain_hook *hook, u32 flags)
+{
+       struct nft_chain *chain;
+       struct nft_hook *h;
+
+       basechain->type = hook->type;
+       INIT_LIST_HEAD(&basechain->hook_list);
+       chain = &basechain->chain;
+
+       if (family == NFPROTO_NETDEV) {
+               list_splice_init(&hook->list, &basechain->hook_list);
+               list_for_each_entry(h, &basechain->hook_list, list)
+                       nft_basechain_hook_init(&h->ops, family, hook, chain);
+
+               basechain->ops.hooknum  = hook->num;
+               basechain->ops.priority = hook->priority;
+       } else {
+               nft_basechain_hook_init(&basechain->ops, family, hook, chain);
+       }
+
+       chain->flags |= NFT_BASE_CHAIN | flags;
+       basechain->policy = NF_ACCEPT;
+       if (chain->flags & NFT_CHAIN_HW_OFFLOAD &&
+           nft_chain_offload_priority(basechain) < 0)
+               return -EOPNOTSUPP;
+
+       flow_block_init(&basechain->flow_block);
+
+       return 0;
+}
+
 static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                              u8 policy, u32 flags)
 {
@@ -1628,7 +1884,6 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
 
        if (nla[NFTA_CHAIN_HOOK]) {
                struct nft_chain_hook hook;
-               struct nf_hook_ops *ops;
 
                err = nft_chain_parse_hook(net, nla, &hook, family, true);
                if (err < 0)
@@ -1639,9 +1894,7 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                        nft_chain_release_hook(&hook);
                        return -ENOMEM;
                }
-
-               if (hook.dev != NULL)
-                       strncpy(basechain->dev_name, hook.dev->name, IFNAMSIZ);
+               chain = &basechain->chain;
 
                if (nla[NFTA_CHAIN_COUNTERS]) {
                        stats = nft_stats_alloc(nla[NFTA_CHAIN_COUNTERS]);
@@ -1654,24 +1907,12 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                        static_branch_inc(&nft_counters_enabled);
                }
 
-               basechain->type = hook.type;
-               chain = &basechain->chain;
-
-               ops             = &basechain->ops;
-               ops->pf         = family;
-               ops->hooknum    = hook.num;
-               ops->priority   = hook.priority;
-               ops->priv       = chain;
-               ops->hook       = hook.type->hooks[ops->hooknum];
-               ops->dev        = hook.dev;
-
-               chain->flags |= NFT_BASE_CHAIN | flags;
-               basechain->policy = NF_ACCEPT;
-               if (chain->flags & NFT_CHAIN_HW_OFFLOAD &&
-                   nft_chain_offload_priority(basechain) < 0)
-                       return -EOPNOTSUPP;
-
-               flow_block_init(&basechain->flow_block);
+               err = nft_basechain_init(basechain, family, &hook, flags);
+               if (err < 0) {
+                       nft_chain_release_hook(&hook);
+                       kfree(basechain);
+                       return err;
+               }
        } else {
                chain = kzalloc(sizeof(*chain), GFP_KERNEL);
                if (chain == NULL)
@@ -1731,6 +1972,25 @@ err1:
        return err;
 }
 
+static bool nft_hook_list_equal(struct list_head *hook_list1,
+                               struct list_head *hook_list2)
+{
+       struct nft_hook *hook;
+       int n = 0, m = 0;
+
+       n = 0;
+       list_for_each_entry(hook, hook_list2, list) {
+               if (!nft_hook_list_find(hook_list1, hook))
+                       return false;
+
+               n++;
+       }
+       list_for_each_entry(hook, hook_list1, list)
+               m++;
+
+       return n == m;
+}
+
 static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
                              u32 flags)
 {
@@ -1762,12 +2022,19 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
                        return -EBUSY;
                }
 
-               ops = &basechain->ops;
-               if (ops->hooknum != hook.num ||
-                   ops->priority != hook.priority ||
-                   ops->dev != hook.dev) {
-                       nft_chain_release_hook(&hook);
-                       return -EBUSY;
+               if (ctx->family == NFPROTO_NETDEV) {
+                       if (!nft_hook_list_equal(&basechain->hook_list,
+                                                &hook.list)) {
+                               nft_chain_release_hook(&hook);
+                               return -EBUSY;
+                       }
+               } else {
+                       ops = &basechain->ops;
+                       if (ops->hooknum != hook.num ||
+                           ops->priority != hook.priority) {
+                               nft_chain_release_hook(&hook);
+                               return -EBUSY;
+                       }
                }
                nft_chain_release_hook(&hook);
        }
@@ -5580,6 +5847,7 @@ static const struct nla_policy nft_flowtable_policy[NFTA_FLOWTABLE_MAX + 1] = {
                                            .len = NFT_NAME_MAXLEN - 1 },
        [NFTA_FLOWTABLE_HOOK]           = { .type = NLA_NESTED },
        [NFTA_FLOWTABLE_HANDLE]         = { .type = NLA_U64 },
+       [NFTA_FLOWTABLE_FLAGS]          = { .type = NLA_U32 },
 };
 
 struct nft_flowtable *nft_flowtable_lookup(const struct nft_table *table,
@@ -5626,43 +5894,6 @@ nft_flowtable_lookup_byhandle(const struct nft_table *table,
        return ERR_PTR(-ENOENT);
 }
 
-static int nf_tables_parse_devices(const struct nft_ctx *ctx,
-                                  const struct nlattr *attr,
-                                  struct net_device *dev_array[], int *len)
-{
-       const struct nlattr *tmp;
-       struct net_device *dev;
-       char ifname[IFNAMSIZ];
-       int rem, n = 0, err;
-
-       nla_for_each_nested(tmp, attr, rem) {
-               if (nla_type(tmp) != NFTA_DEVICE_NAME) {
-                       err = -EINVAL;
-                       goto err1;
-               }
-
-               nla_strlcpy(ifname, tmp, IFNAMSIZ);
-               dev = __dev_get_by_name(ctx->net, ifname);
-               if (!dev) {
-                       err = -ENOENT;
-                       goto err1;
-               }
-
-               dev_array[n++] = dev;
-               if (n == NFT_FLOWTABLE_DEVICE_MAX) {
-                       err = -EFBIG;
-                       goto err1;
-               }
-       }
-       if (!len)
-               return -EINVAL;
-
-       err = 0;
-err1:
-       *len = n;
-       return err;
-}
-
 static const struct nla_policy nft_flowtable_hook_policy[NFTA_FLOWTABLE_HOOK_MAX + 1] = {
        [NFTA_FLOWTABLE_HOOK_NUM]       = { .type = NLA_U32 },
        [NFTA_FLOWTABLE_HOOK_PRIORITY]  = { .type = NLA_U32 },
@@ -5673,11 +5904,10 @@ static int nf_tables_flowtable_parse_hook(const struct nft_ctx *ctx,
                                          const struct nlattr *attr,
                                          struct nft_flowtable *flowtable)
 {
-       struct net_device *dev_array[NFT_FLOWTABLE_DEVICE_MAX];
        struct nlattr *tb[NFTA_FLOWTABLE_HOOK_MAX + 1];
-       struct nf_hook_ops *ops;
+       struct nft_hook *hook;
        int hooknum, priority;
-       int err, n = 0, i;
+       int err;
 
        err = nla_parse_nested_deprecated(tb, NFTA_FLOWTABLE_HOOK_MAX, attr,
                                          nft_flowtable_hook_policy, NULL);
@@ -5695,27 +5925,21 @@ static int nf_tables_flowtable_parse_hook(const struct nft_ctx *ctx,
 
        priority = ntohl(nla_get_be32(tb[NFTA_FLOWTABLE_HOOK_PRIORITY]));
 
-       err = nf_tables_parse_devices(ctx, tb[NFTA_FLOWTABLE_HOOK_DEVS],
-                                     dev_array, &n);
+       err = nf_tables_parse_netdev_hooks(ctx->net,
+                                          tb[NFTA_FLOWTABLE_HOOK_DEVS],
+                                          &flowtable->hook_list);
        if (err < 0)
                return err;
 
-       ops = kcalloc(n, sizeof(struct nf_hook_ops), GFP_KERNEL);
-       if (!ops)
-               return -ENOMEM;
-
-       flowtable->hooknum      = hooknum;
-       flowtable->priority     = priority;
-       flowtable->ops          = ops;
-       flowtable->ops_len      = n;
+       flowtable->hooknum              = hooknum;
+       flowtable->data.priority        = priority;
 
-       for (i = 0; i < n; i++) {
-               flowtable->ops[i].pf            = NFPROTO_NETDEV;
-               flowtable->ops[i].hooknum       = hooknum;
-               flowtable->ops[i].priority      = priority;
-               flowtable->ops[i].priv          = &flowtable->data;
-               flowtable->ops[i].hook          = flowtable->data.type->hook;
-               flowtable->ops[i].dev           = dev_array[i];
+       list_for_each_entry(hook, &flowtable->hook_list, list) {
+               hook->ops.pf            = NFPROTO_NETDEV;
+               hook->ops.hooknum       = hooknum;
+               hook->ops.priority      = priority;
+               hook->ops.priv          = &flowtable->data;
+               hook->ops.hook          = flowtable->data.type->hook;
        }
 
        return err;
@@ -5752,17 +5976,73 @@ nft_flowtable_type_get(struct net *net, u8 family)
        return ERR_PTR(-ENOENT);
 }
 
+static void nft_unregister_flowtable_hook(struct net *net,
+                                         struct nft_flowtable *flowtable,
+                                         struct nft_hook *hook)
+{
+       nf_unregister_net_hook(net, &hook->ops);
+       flowtable->data.type->setup(&flowtable->data, hook->ops.dev,
+                                   FLOW_BLOCK_UNBIND);
+}
+
 static void nft_unregister_flowtable_net_hooks(struct net *net,
                                               struct nft_flowtable *flowtable)
 {
-       int i;
+       struct nft_hook *hook;
 
-       for (i = 0; i < flowtable->ops_len; i++) {
-               if (!flowtable->ops[i].dev)
-                       continue;
+       list_for_each_entry(hook, &flowtable->hook_list, list)
+               nft_unregister_flowtable_hook(net, flowtable, hook);
+}
+
+static int nft_register_flowtable_net_hooks(struct net *net,
+                                           struct nft_table *table,
+                                           struct nft_flowtable *flowtable)
+{
+       struct nft_hook *hook, *hook2, *next;
+       struct nft_flowtable *ft;
+       int err, i = 0;
+
+       list_for_each_entry(hook, &flowtable->hook_list, list) {
+               list_for_each_entry(ft, &table->flowtables, list) {
+                       list_for_each_entry(hook2, &ft->hook_list, list) {
+                               if (hook->ops.dev == hook2->ops.dev &&
+                                   hook->ops.pf == hook2->ops.pf) {
+                                       err = -EBUSY;
+                                       goto err_unregister_net_hooks;
+                               }
+                       }
+               }
+
+               err = flowtable->data.type->setup(&flowtable->data,
+                                                 hook->ops.dev,
+                                                 FLOW_BLOCK_BIND);
+               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++;
+       }
+
+       return 0;
 
-               nf_unregister_net_hook(net, &flowtable->ops[i]);
+err_unregister_net_hooks:
+       list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
+               if (i-- <= 0)
+                       break;
+
+               nft_unregister_flowtable_hook(net, flowtable, hook);
+               list_del_rcu(&hook->list);
+               kfree_rcu(hook, rcu);
        }
+
+       return err;
 }
 
 static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
@@ -5773,12 +6053,13 @@ static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
 {
        const struct nfgenmsg *nfmsg = nlmsg_data(nlh);
        const struct nf_flowtable_type *type;
-       struct nft_flowtable *flowtable, *ft;
        u8 genmask = nft_genmask_next(net);
        int family = nfmsg->nfgen_family;
+       struct nft_flowtable *flowtable;
+       struct nft_hook *hook, *next;
        struct nft_table *table;
        struct nft_ctx ctx;
-       int err, i, k;
+       int err;
 
        if (!nla[NFTA_FLOWTABLE_TABLE] ||
            !nla[NFTA_FLOWTABLE_NAME] ||
@@ -5817,6 +6098,7 @@ static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
 
        flowtable->table = table;
        flowtable->handle = nf_tables_alloc_handle(table);
+       INIT_LIST_HEAD(&flowtable->hook_list);
 
        flowtable->name = nla_strdup(nla[NFTA_FLOWTABLE_NAME], GFP_KERNEL);
        if (!flowtable->name) {
@@ -5830,6 +6112,14 @@ static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
                goto err2;
        }
 
+       if (nla[NFTA_FLOWTABLE_FLAGS]) {
+               flowtable->data.flags =
+                       ntohl(nla_get_be32(nla[NFTA_FLOWTABLE_FLAGS]));
+               if (flowtable->data.flags & ~NF_FLOWTABLE_HW_OFFLOAD)
+                       goto err3;
+       }
+
+       write_pnet(&flowtable->data.net, net);
        flowtable->data.type = type;
        err = type->init(&flowtable->data);
        if (err < 0)
@@ -5840,43 +6130,24 @@ static int nf_tables_newflowtable(struct net *net, struct sock *nlsk,
        if (err < 0)
                goto err4;
 
-       for (i = 0; i < flowtable->ops_len; i++) {
-               if (!flowtable->ops[i].dev)
-                       continue;
-
-               list_for_each_entry(ft, &table->flowtables, list) {
-                       for (k = 0; k < ft->ops_len; k++) {
-                               if (!ft->ops[k].dev)
-                                       continue;
-
-                               if (flowtable->ops[i].dev == ft->ops[k].dev &&
-                                   flowtable->ops[i].pf == ft->ops[k].pf) {
-                                       err = -EBUSY;
-                                       goto err5;
-                               }
-                       }
-               }
-
-               err = nf_register_net_hook(net, &flowtable->ops[i]);
-               if (err < 0)
-                       goto err5;
-       }
+       err = nft_register_flowtable_net_hooks(ctx.net, table, flowtable);
+       if (err < 0)
+               goto err4;
 
        err = nft_trans_flowtable_add(&ctx, NFT_MSG_NEWFLOWTABLE, flowtable);
        if (err < 0)
-               goto err6;
+               goto err5;
 
        list_add_tail_rcu(&flowtable->list, &table->flowtables);
        table->use++;
 
        return 0;
-err6:
-       i = flowtable->ops_len;
 err5:
-       for (k = i - 1; k >= 0; k--)
-               nf_unregister_net_hook(net, &flowtable->ops[k]);
-
-       kfree(flowtable->ops);
+       list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
+               nft_unregister_flowtable_hook(net, flowtable, hook);
+               list_del_rcu(&hook->list);
+               kfree_rcu(hook, rcu);
+       }
 err4:
        flowtable->data.type->free(&flowtable->data);
 err3:
@@ -5943,8 +6214,8 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net,
 {
        struct nlattr *nest, *nest_devs;
        struct nfgenmsg *nfmsg;
+       struct nft_hook *hook;
        struct nlmsghdr *nlh;
-       int i;
 
        event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
        nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct nfgenmsg), flags);
@@ -5960,25 +6231,23 @@ static int nf_tables_fill_flowtable_info(struct sk_buff *skb, struct net *net,
            nla_put_string(skb, NFTA_FLOWTABLE_NAME, flowtable->name) ||
            nla_put_be32(skb, NFTA_FLOWTABLE_USE, htonl(flowtable->use)) ||
            nla_put_be64(skb, NFTA_FLOWTABLE_HANDLE, cpu_to_be64(flowtable->handle),
-                        NFTA_FLOWTABLE_PAD))
+                        NFTA_FLOWTABLE_PAD) ||
+           nla_put_be32(skb, NFTA_FLOWTABLE_FLAGS, htonl(flowtable->data.flags)))
                goto nla_put_failure;
 
        nest = nla_nest_start_noflag(skb, NFTA_FLOWTABLE_HOOK);
        if (!nest)
                goto nla_put_failure;
        if (nla_put_be32(skb, NFTA_FLOWTABLE_HOOK_NUM, htonl(flowtable->hooknum)) ||
-           nla_put_be32(skb, NFTA_FLOWTABLE_HOOK_PRIORITY, htonl(flowtable->priority)))
+           nla_put_be32(skb, NFTA_FLOWTABLE_HOOK_PRIORITY, htonl(flowtable->data.priority)))
                goto nla_put_failure;
 
        nest_devs = nla_nest_start_noflag(skb, NFTA_FLOWTABLE_HOOK_DEVS);
        if (!nest_devs)
                goto nla_put_failure;
 
-       for (i = 0; i < flowtable->ops_len; i++) {
-               const struct net_device *dev = READ_ONCE(flowtable->ops[i].dev);
-
-               if (dev &&
-                   nla_put_string(skb, NFTA_DEVICE_NAME, dev->name))
+       list_for_each_entry_rcu(hook, &flowtable->hook_list, list) {
+               if (nla_put_string(skb, NFTA_DEVICE_NAME, hook->ops.dev->name))
                        goto nla_put_failure;
        }
        nla_nest_end(skb, nest_devs);
@@ -6169,7 +6438,12 @@ err:
 
 static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
 {
-       kfree(flowtable->ops);
+       struct nft_hook *hook, *next;
+
+       list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
+               list_del_rcu(&hook->list);
+               kfree(hook);
+       }
        kfree(flowtable->name);
        flowtable->data.type->free(&flowtable->data);
        module_put(flowtable->data.type->owner);
@@ -6209,14 +6483,15 @@ nla_put_failure:
 static void nft_flowtable_event(unsigned long event, struct net_device *dev,
                                struct nft_flowtable *flowtable)
 {
-       int i;
+       struct nft_hook *hook;
 
-       for (i = 0; i < flowtable->ops_len; i++) {
-               if (flowtable->ops[i].dev != dev)
+       list_for_each_entry(hook, &flowtable->hook_list, list) {
+               if (hook->ops.dev != dev)
                        continue;
 
-               nf_unregister_net_hook(dev_net(dev), &flowtable->ops[i]);
-               flowtable->ops[i].dev = NULL;
+               nft_unregister_flowtable_hook(dev_net(dev), flowtable, hook);
+               list_del_rcu(&hook->list);
+               kfree_rcu(hook, rcu);
                break;
        }
 }