static LIST_HEAD(nf_tables_flowtables);
 static u64 table_handle;
 
+enum {
+       NFT_VALIDATE_SKIP       = 0,
+       NFT_VALIDATE_NEED,
+       NFT_VALIDATE_DO,
+};
+
+static void nft_validate_state_update(struct net *net, u8 new_validate_state)
+{
+       switch (net->nft.validate_state) {
+       case NFT_VALIDATE_SKIP:
+               WARN_ON_ONCE(new_validate_state == NFT_VALIDATE_DO);
+               break;
+       case NFT_VALIDATE_NEED:
+               break;
+       case NFT_VALIDATE_DO:
+               if (new_validate_state == NFT_VALIDATE_NEED)
+                       return;
+       }
+
+       net->nft.validate_state = new_validate_state;
+}
+
 static void nft_ctx_init(struct nft_ctx *ctx,
                         struct net *net,
                         const struct sk_buff *skb,
                        goto err1;
        }
 
-       if (ops->validate) {
-               const struct nft_data *data = NULL;
-
-               err = ops->validate(ctx, expr, &data);
-               if (err < 0)
-                       goto err2;
-       }
-
        return 0;
-
-err2:
-       if (ops->destroy)
-               ops->destroy(ctx, expr);
 err1:
        expr->ops = NULL;
        return err;
        nf_tables_rule_destroy(ctx, rule);
 }
 
+int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain)
+{
+       struct nft_expr *expr, *last;
+       const struct nft_data *data;
+       struct nft_rule *rule;
+       int err;
+
+       list_for_each_entry(rule, &chain->rules, list) {
+               if (!nft_is_active_next(ctx->net, rule))
+                       continue;
+
+               nft_rule_for_each_expr(expr, last, rule) {
+                       if (!expr->ops->validate)
+                               continue;
+
+                       err = expr->ops->validate(ctx, expr, &data);
+                       if (err < 0)
+                               return err;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(nft_chain_validate);
+
+static int nft_table_validate(struct net *net, const struct nft_table *table)
+{
+       struct nft_chain *chain;
+       struct nft_ctx ctx = {
+               .net    = net,
+               .family = table->family,
+       };
+       int err;
+
+       list_for_each_entry(chain, &table->chains, list) {
+               if (!nft_is_base_chain(chain))
+                       continue;
+
+               ctx.chain = chain;
+               err = nft_chain_validate(&ctx, chain);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
 #define NFT_RULE_MAXEXPRS      128
 
 static struct nft_expr_info *info;
                err = nf_tables_newexpr(&ctx, &info[i], expr);
                if (err < 0)
                        goto err2;
+
+               if (info[i].ops->validate)
+                       nft_validate_state_update(net, NFT_VALIDATE_NEED);
+
                info[i].ops = NULL;
                expr = nft_expr_next(expr);
        }
                }
        }
        chain->use++;
-       return 0;
 
+       if (net->nft.validate_state == NFT_VALIDATE_DO)
+               return nft_table_validate(net, table);
+
+       return 0;
 err2:
        nf_tables_rule_release(&ctx, rule);
 err1:
                                                          d2.type, d2.len);
                        if (err < 0)
                                goto err3;
+
+                       if (d2.type == NFT_DATA_VERDICT &&
+                           (data.verdict.code == NFT_GOTO ||
+                            data.verdict.code == NFT_JUMP))
+                               nft_validate_state_update(ctx->net,
+                                                         NFT_VALIDATE_NEED);
                }
 
                nft_set_ext_add_length(&tmpl, NFT_SET_EXT_DATA, d2.len);
        const struct nlattr *attr;
        struct nft_set *set;
        struct nft_ctx ctx;
-       int rem, err = 0;
+       int rem, err;
 
        if (nla[NFTA_SET_ELEM_LIST_ELEMENTS] == NULL)
                return -EINVAL;
        nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
                err = nft_add_set_elem(&ctx, set, attr, nlh->nlmsg_flags);
                if (err < 0)
-                       break;
+                       return err;
        }
-       return err;
+
+       if (net->nft.validate_state == NFT_VALIDATE_DO)
+               return nft_table_validate(net, ctx.table);
+
+       return 0;
 }
 
 /**
        },
 };
 
+static int nf_tables_validate(struct net *net)
+{
+       struct nft_table *table;
+
+       switch (net->nft.validate_state) {
+       case NFT_VALIDATE_SKIP:
+               break;
+       case NFT_VALIDATE_NEED:
+               nft_validate_state_update(net, NFT_VALIDATE_DO);
+               /* fall through */
+       case NFT_VALIDATE_DO:
+               list_for_each_entry(table, &net->nft.tables, list) {
+                       if (nft_table_validate(net, table) < 0)
+                               return -EAGAIN;
+               }
+               break;
+       }
+
+       return 0;
+}
+
 static void nft_chain_commit_update(struct nft_trans *trans)
 {
        struct nft_base_chain *basechain;
        struct nft_chain *chain;
        struct nft_table *table;
 
+       /* 0. Validate ruleset, otherwise roll back for error reporting. */
+       if (nf_tables_validate(net) < 0)
+               return -EAGAIN;
+
        /* 1.  Allocate space for next generation rules_gen_X[] */
        list_for_each_entry_safe(trans, next, &net->nft.commit_list, list) {
                int ret;
        return 0;
 }
 
+static void nf_tables_cleanup(struct net *net)
+{
+       nft_validate_state_update(net, NFT_VALIDATE_SKIP);
+}
+
 static bool nf_tables_valid_genid(struct net *net, u32 genid)
 {
        return net->nft.base_seq == genid;
        .cb             = nf_tables_cb,
        .commit         = nf_tables_commit,
        .abort          = nf_tables_abort,
+       .cleanup        = nf_tables_cleanup,
        .valid_genid    = nf_tables_valid_genid,
 };
 
 
        list_for_each_entry(rule, &chain->rules, list) {
                nft_rule_for_each_expr(expr, last, rule) {
-                       const struct nft_data *data = NULL;
+                       struct nft_immediate_expr *priv;
+                       const struct nft_data *data;
                        int err;
 
-                       if (!expr->ops->validate)
+                       if (strcmp(expr->ops->type->name, "immediate"))
                                continue;
 
-                       err = expr->ops->validate(ctx, expr, &data);
-                       if (err < 0)
-                               return err;
-
-                       if (data == NULL)
+                       priv = nft_expr_priv(expr);
+                       if (priv->dreg != NFT_REG_VERDICT)
                                continue;
 
+                       data = &priv->data;
                        switch (data->verdict.code) {
                        case NFT_JUMP:
                        case NFT_GOTO:
        INIT_LIST_HEAD(&net->nft.tables);
        INIT_LIST_HEAD(&net->nft.commit_list);
        net->nft.base_seq = 1;
+       net->nft.validate_state = NFT_VALIDATE_SKIP;
+
        return 0;
 }
 
 
        return -1;
 }
 
+static int nft_lookup_validate_setelem(const struct nft_ctx *ctx,
+                                      struct nft_set *set,
+                                      const struct nft_set_iter *iter,
+                                      struct nft_set_elem *elem)
+{
+       const struct nft_set_ext *ext = nft_set_elem_ext(set, elem->priv);
+       const struct nft_data *data;
+
+       if (nft_set_ext_exists(ext, NFT_SET_EXT_FLAGS) &&
+           *nft_set_ext_flags(ext) & NFT_SET_ELEM_INTERVAL_END)
+               return 0;
+
+       data = nft_set_ext_data(ext);
+       switch (data->verdict.code) {
+       case NFT_JUMP:
+       case NFT_GOTO:
+               return nft_chain_validate(ctx, data->verdict.chain);
+       default:
+               return 0;
+       }
+}
+
+static int nft_lookup_validate(const struct nft_ctx *ctx,
+                              const struct nft_expr *expr,
+                              const struct nft_data **d)
+{
+       const struct nft_lookup *priv = nft_expr_priv(expr);
+       struct nft_set_iter iter;
+
+       if (!(priv->set->flags & NFT_SET_MAP) ||
+           priv->set->dtype != NFT_DATA_VERDICT)
+               return 0;
+
+       iter.genmask    = nft_genmask_next(ctx->net);
+       iter.skip       = 0;
+       iter.count      = 0;
+       iter.err        = 0;
+       iter.fn         = nft_lookup_validate_setelem;
+
+       priv->set->ops->walk(ctx, priv->set, &iter);
+       if (iter.err < 0)
+               return iter.err;
+
+       return 0;
+}
+
 static const struct nft_expr_ops nft_lookup_ops = {
        .type           = &nft_lookup_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
        .init           = nft_lookup_init,
        .destroy        = nft_lookup_destroy,
        .dump           = nft_lookup_dump,
+       .validate       = nft_lookup_validate,
 };
 
 struct nft_expr_type nft_lookup_type __read_mostly = {