if (nla == NULL)
                return ERR_PTR(-EINVAL);
 
-       list_for_each_entry(table, &net->nft.tables, list) {
+       list_for_each_entry_rcu(table, &net->nft.tables, list) {
                if (!nla_strcmp(nla, table->name) &&
                    table->family == family &&
                    nft_active_genmask(table, genmask))
        return skb->len;
 }
 
+static int nft_netlink_dump_start_rcu(struct sock *nlsk, struct sk_buff *skb,
+                                     const struct nlmsghdr *nlh,
+                                     struct netlink_dump_control *c)
+{
+       int err;
+
+       if (!try_module_get(THIS_MODULE))
+               return -EINVAL;
+
+       rcu_read_unlock();
+       err = netlink_dump_start(nlsk, skb, nlh, c);
+       rcu_read_lock();
+       module_put(THIS_MODULE);
+
+       return err;
+}
+
+/* called with rcu_read_lock held */
 static int nf_tables_gettable(struct net *net, struct sock *nlsk,
                              struct sk_buff *skb, const struct nlmsghdr *nlh,
                              const struct nlattr * const nla[],
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_tables,
+                       .module = THIS_MODULE,
                };
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        table = nft_table_lookup(net, nla[NFTA_TABLE_NAME], family, genmask);
                return PTR_ERR(table);
        }
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (!skb2)
                return -ENOMEM;
 
        if (nla == NULL)
                return ERR_PTR(-EINVAL);
 
-       list_for_each_entry(chain, &table->chains, list) {
+       list_for_each_entry_rcu(chain, &table->chains, list) {
                if (!nla_strcmp(nla, chain->name) &&
                    nft_active_genmask(chain, genmask))
                        return chain;
        return skb->len;
 }
 
+/* called with rcu_read_lock held */
 static int nf_tables_getchain(struct net *net, struct sock *nlsk,
                              struct sk_buff *skb, const struct nlmsghdr *nlh,
                              const struct nlattr * const nla[],
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_chains,
+                       .module = THIS_MODULE,
                };
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        table = nft_table_lookup(net, nla[NFTA_CHAIN_TABLE], family, genmask);
                return PTR_ERR(chain);
        }
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (!skb2)
                return -ENOMEM;
 
        struct nft_rule *rule;
 
        // FIXME: this sucks
-       list_for_each_entry(rule, &chain->rules, list) {
+       list_for_each_entry_rcu(rule, &chain->rules, list) {
                if (handle == rule->handle)
                        return rule;
        }
        return 0;
 }
 
+/* called with rcu_read_lock held */
 static int nf_tables_getrule(struct net *net, struct sock *nlsk,
                             struct sk_buff *skb, const struct nlmsghdr *nlh,
                             const struct nlattr * const nla[],
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_rules,
                        .done = nf_tables_dump_rules_done,
+                       .module = THIS_MODULE,
                };
 
                if (nla[NFTA_RULE_TABLE] || nla[NFTA_RULE_CHAIN]) {
                        struct nft_rule_dump_ctx *ctx;
 
-                       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+                       ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC);
                        if (!ctx)
                                return -ENOMEM;
 
                        if (nla[NFTA_RULE_TABLE]) {
                                ctx->table = nla_strdup(nla[NFTA_RULE_TABLE],
-                                                       GFP_KERNEL);
+                                                       GFP_ATOMIC);
                                if (!ctx->table) {
                                        kfree(ctx);
                                        return -ENOMEM;
                        }
                        if (nla[NFTA_RULE_CHAIN]) {
                                ctx->chain = nla_strdup(nla[NFTA_RULE_CHAIN],
-                                                       GFP_KERNEL);
+                                                       GFP_ATOMIC);
                                if (!ctx->chain) {
                                        kfree(ctx->table);
                                        kfree(ctx);
                        c.data = ctx;
                }
 
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        table = nft_table_lookup(net, nla[NFTA_RULE_TABLE], family, genmask);
                return PTR_ERR(rule);
        }
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (!skb2)
                return -ENOMEM;
 
        if (nla == NULL)
                return ERR_PTR(-EINVAL);
 
-       list_for_each_entry(set, &table->sets, list) {
+       list_for_each_entry_rcu(set, &table->sets, list) {
                if (!nla_strcmp(nla, set->name) &&
                    nft_active_genmask(set, genmask))
                        return set;
        return 0;
 }
 
+/* called with rcu_read_lock held */
 static int nf_tables_getset(struct net *net, struct sock *nlsk,
                            struct sk_buff *skb, const struct nlmsghdr *nlh,
                            const struct nlattr * const nla[],
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_sets,
                        .done = nf_tables_dump_sets_done,
+                       .module = THIS_MODULE,
                };
                struct nft_ctx *ctx_dump;
 
-               ctx_dump = kmalloc(sizeof(*ctx_dump), GFP_KERNEL);
+               ctx_dump = kmalloc(sizeof(*ctx_dump), GFP_ATOMIC);
                if (ctx_dump == NULL)
                        return -ENOMEM;
 
                *ctx_dump = ctx;
                c.data = ctx_dump;
 
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        /* Only accept unspec with dump */
        if (IS_ERR(set))
                return PTR_ERR(set);
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (skb2 == NULL)
                return -ENOMEM;
 
        ext = nft_set_elem_ext(set, &elem);
 
        err = -ENOMEM;
-       skb = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb = nlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (skb == NULL)
                goto err1;
 
        return err == -EAGAIN ? -ENOBUFS : err;
 }
 
+/* called with rcu_read_lock held */
 static int nf_tables_getsetelem(struct net *net, struct sock *nlsk,
                                struct sk_buff *skb, const struct nlmsghdr *nlh,
                                const struct nlattr * const nla[],
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_set,
                        .done = nf_tables_dump_set_done,
+                       .module = THIS_MODULE,
                };
                struct nft_set_dump_ctx *dump_ctx;
 
-               dump_ctx = kmalloc(sizeof(*dump_ctx), GFP_KERNEL);
+               dump_ctx = kmalloc(sizeof(*dump_ctx), GFP_ATOMIC);
                if (!dump_ctx)
                        return -ENOMEM;
 
                dump_ctx->ctx = ctx;
 
                c.data = dump_ctx;
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        if (!nla[NFTA_SET_ELEM_LIST_ELEMENTS])
 {
        struct nft_object *obj;
 
-       list_for_each_entry(obj, &table->objects, list) {
+       list_for_each_entry_rcu(obj, &table->objects, list) {
                if (!nla_strcmp(nla, obj->name) &&
                    objtype == obj->ops->type->type &&
                    nft_active_genmask(obj, genmask))
 {
        struct nft_obj_filter *filter;
 
-       filter = kzalloc(sizeof(*filter), GFP_KERNEL);
+       filter = kzalloc(sizeof(*filter), GFP_ATOMIC);
        if (!filter)
                return ERR_PTR(-ENOMEM);
 
        if (nla[NFTA_OBJ_TABLE]) {
-               filter->table = nla_strdup(nla[NFTA_OBJ_TABLE], GFP_KERNEL);
+               filter->table = nla_strdup(nla[NFTA_OBJ_TABLE], GFP_ATOMIC);
                if (!filter->table) {
                        kfree(filter);
                        return ERR_PTR(-ENOMEM);
        return filter;
 }
 
+/* called with rcu_read_lock held */
 static int nf_tables_getobj(struct net *net, struct sock *nlsk,
                            struct sk_buff *skb, const struct nlmsghdr *nlh,
                            const struct nlattr * const nla[],
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_obj,
                        .done = nf_tables_dump_obj_done,
+                       .module = THIS_MODULE,
                };
 
                if (nla[NFTA_OBJ_TABLE] ||
 
                        c.data = filter;
                }
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        if (!nla[NFTA_OBJ_NAME] ||
                return PTR_ERR(obj);
        }
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (!skb2)
                return -ENOMEM;
 
 {
        struct nft_flowtable *flowtable;
 
-       list_for_each_entry(flowtable, &table->flowtables, list) {
+       list_for_each_entry_rcu(flowtable, &table->flowtables, list) {
                if (!nla_strcmp(nla, flowtable->name) &&
                    nft_active_genmask(flowtable, genmask))
                        return flowtable;
 {
        struct nft_flowtable_filter *filter;
 
-       filter = kzalloc(sizeof(*filter), GFP_KERNEL);
+       filter = kzalloc(sizeof(*filter), GFP_ATOMIC);
        if (!filter)
                return ERR_PTR(-ENOMEM);
 
        if (nla[NFTA_FLOWTABLE_TABLE]) {
                filter->table = nla_strdup(nla[NFTA_FLOWTABLE_TABLE],
-                                          GFP_KERNEL);
+                                          GFP_ATOMIC);
                if (!filter->table) {
                        kfree(filter);
                        return ERR_PTR(-ENOMEM);
        return filter;
 }
 
+/* called with rcu_read_lock held */
 static int nf_tables_getflowtable(struct net *net, struct sock *nlsk,
                                  struct sk_buff *skb,
                                  const struct nlmsghdr *nlh,
                struct netlink_dump_control c = {
                        .dump = nf_tables_dump_flowtable,
                        .done = nf_tables_dump_flowtable_done,
+                       .module = THIS_MODULE,
                };
 
                if (nla[NFTA_FLOWTABLE_TABLE]) {
 
                        c.data = filter;
                }
-               return netlink_dump_start(nlsk, skb, nlh, &c);
+               return nft_netlink_dump_start_rcu(nlsk, skb, nlh, &c);
        }
 
        if (!nla[NFTA_FLOWTABLE_NAME])
        if (IS_ERR(flowtable))
                return PTR_ERR(flowtable);
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (!skb2)
                return -ENOMEM;
 
        struct sk_buff *skb2;
        int err;
 
-       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+       skb2 = alloc_skb(NLMSG_GOODSIZE, GFP_ATOMIC);
        if (skb2 == NULL)
                return -ENOMEM;
 
                .policy         = nft_table_policy,
        },
        [NFT_MSG_GETTABLE] = {
-               .call           = nf_tables_gettable,
+               .call_rcu       = nf_tables_gettable,
                .attr_count     = NFTA_TABLE_MAX,
                .policy         = nft_table_policy,
        },
                .policy         = nft_chain_policy,
        },
        [NFT_MSG_GETCHAIN] = {
-               .call           = nf_tables_getchain,
+               .call_rcu       = nf_tables_getchain,
                .attr_count     = NFTA_CHAIN_MAX,
                .policy         = nft_chain_policy,
        },
                .policy         = nft_rule_policy,
        },
        [NFT_MSG_GETRULE] = {
-               .call           = nf_tables_getrule,
+               .call_rcu       = nf_tables_getrule,
                .attr_count     = NFTA_RULE_MAX,
                .policy         = nft_rule_policy,
        },
                .policy         = nft_set_policy,
        },
        [NFT_MSG_GETSET] = {
-               .call           = nf_tables_getset,
+               .call_rcu       = nf_tables_getset,
                .attr_count     = NFTA_SET_MAX,
                .policy         = nft_set_policy,
        },
                .policy         = nft_set_elem_list_policy,
        },
        [NFT_MSG_GETSETELEM] = {
-               .call           = nf_tables_getsetelem,
+               .call_rcu       = nf_tables_getsetelem,
                .attr_count     = NFTA_SET_ELEM_LIST_MAX,
                .policy         = nft_set_elem_list_policy,
        },
                .policy         = nft_set_elem_list_policy,
        },
        [NFT_MSG_GETGEN] = {
-               .call           = nf_tables_getgen,
+               .call_rcu       = nf_tables_getgen,
        },
        [NFT_MSG_NEWOBJ] = {
                .call_batch     = nf_tables_newobj,
                .policy         = nft_obj_policy,
        },
        [NFT_MSG_GETOBJ] = {
-               .call           = nf_tables_getobj,
+               .call_rcu       = nf_tables_getobj,
                .attr_count     = NFTA_OBJ_MAX,
                .policy         = nft_obj_policy,
        },
                .policy         = nft_obj_policy,
        },
        [NFT_MSG_GETOBJ_RESET] = {
-               .call           = nf_tables_getobj,
+               .call_rcu       = nf_tables_getobj,
                .attr_count     = NFTA_OBJ_MAX,
                .policy         = nft_obj_policy,
        },
                .policy         = nft_flowtable_policy,
        },
        [NFT_MSG_GETFLOWTABLE] = {
-               .call           = nf_tables_getflowtable,
+               .call_rcu       = nf_tables_getflowtable,
                .attr_count     = NFTA_FLOWTABLE_MAX,
                .policy         = nft_flowtable_policy,
        },