}
 }
 
+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 ||
        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 ||
        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)
                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));
        }
 }
 
                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;
        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,
 
        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)
+               if (nft_dump_basechain_hook(skb, family, basechain))
                        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 (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)))
 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;
        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);
        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,
 {
        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);
 
        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;
        }
 
 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);
 }
 
        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)
 {
 
        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)
                        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]);
                        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)
        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)
 {
                        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);
        }