netfilter: nf_tables: add NFT_CHAIN_BINDING
authorPablo Neira Ayuso <pablo@netfilter.org>
Tue, 30 Jun 2020 17:21:36 +0000 (19:21 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 3 Jul 2020 23:22:14 +0000 (01:22 +0200)
This new chain flag specifies that:

* the kernel dynamically allocates the chain name, if no chain name
  is specified.

* If the immediate expression that refers to this chain is removed,
  then this bound chain (and its content) is destroyed.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_tables.h
include/uapi/linux/netfilter/nf_tables.h
net/netfilter/nf_tables_api.c
net/netfilter/nft_immediate.c

index 6d1e7da..822c267 100644 (file)
@@ -899,6 +899,8 @@ static inline struct nft_userdata *nft_userdata(const struct nft_rule *rule)
        return (void *)&rule->data[rule->dlen];
 }
 
+void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *rule);
+
 static inline void nft_set_elem_update_expr(const struct nft_set_ext *ext,
                                            struct nft_regs *regs,
                                            const struct nft_pktinfo *pkt)
@@ -944,7 +946,8 @@ struct nft_chain {
        struct nft_table                *table;
        u64                             handle;
        u32                             use;
-       u8                              flags:6,
+       u8                              flags:5,
+                                       bound:1,
                                        genmask:2;
        char                            *name;
 
@@ -989,6 +992,14 @@ int nft_chain_validate_dependency(const struct nft_chain *chain,
 int nft_chain_validate_hooks(const struct nft_chain *chain,
                              unsigned int hook_flags);
 
+static inline bool nft_chain_is_bound(struct nft_chain *chain)
+{
+       return (chain->flags & NFT_CHAIN_BINDING) && chain->bound;
+}
+
+void nft_chain_del(struct nft_chain *chain);
+void nf_tables_chain_destroy(struct nft_ctx *ctx);
+
 struct nft_stats {
        u64                     bytes;
        u64                     pkts;
index 2cf7cc3..e00b4ae 100644 (file)
@@ -187,6 +187,7 @@ enum nft_table_attributes {
 enum nft_chain_flags {
        NFT_CHAIN_BASE          = (1 << 0),
        NFT_CHAIN_HW_OFFLOAD    = (1 << 1),
+       NFT_CHAIN_BINDING       = (1 << 2),
 };
 
 /**
index a7cb9c0..b8a970d 100644 (file)
@@ -1056,6 +1056,9 @@ static int nft_flush_table(struct nft_ctx *ctx)
                if (!nft_is_active_next(ctx->net, chain))
                        continue;
 
+               if (nft_chain_is_bound(chain))
+                       continue;
+
                ctx->chain = chain;
 
                err = nft_delrule_by_chain(ctx);
@@ -1098,6 +1101,9 @@ static int nft_flush_table(struct nft_ctx *ctx)
                if (!nft_is_active_next(ctx->net, chain))
                        continue;
 
+               if (nft_chain_is_bound(chain))
+                       continue;
+
                ctx->chain = chain;
 
                err = nft_delchain(ctx);
@@ -1413,13 +1419,12 @@ static int nf_tables_fill_chain_info(struct sk_buff *skb, struct net *net,
                                              lockdep_commit_lock_is_held(net));
                if (nft_dump_stats(skb, stats))
                        goto nla_put_failure;
-
-               if ((chain->flags & NFT_CHAIN_HW_OFFLOAD) &&
-                   nla_put_be32(skb, NFTA_CHAIN_FLAGS,
-                                htonl(NFT_CHAIN_HW_OFFLOAD)))
-                       goto nla_put_failure;
        }
 
+       if (chain->flags &&
+           nla_put_be32(skb, NFTA_CHAIN_FLAGS, htonl(chain->flags)))
+               goto nla_put_failure;
+
        if (nla_put_be32(skb, NFTA_CHAIN_USE, htonl(chain->use)))
                goto nla_put_failure;
 
@@ -1621,7 +1626,7 @@ static void nf_tables_chain_free_chain_rules(struct nft_chain *chain)
        kvfree(chain->rules_next);
 }
 
-static void nf_tables_chain_destroy(struct nft_ctx *ctx)
+void nf_tables_chain_destroy(struct nft_ctx *ctx)
 {
        struct nft_chain *chain = ctx->chain;
        struct nft_hook *hook, *next;
@@ -1928,6 +1933,8 @@ static int nft_chain_add(struct nft_table *table, struct nft_chain *chain)
        return 0;
 }
 
+static u64 chain_id;
+
 static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                              u8 policy, u32 flags)
 {
@@ -1936,6 +1943,7 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
        struct nft_base_chain *basechain;
        struct nft_stats __percpu *stats;
        struct net *net = ctx->net;
+       char name[NFT_NAME_MAXLEN];
        struct nft_trans *trans;
        struct nft_chain *chain;
        struct nft_rule **rules;
@@ -1947,6 +1955,9 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
        if (nla[NFTA_CHAIN_HOOK]) {
                struct nft_chain_hook hook;
 
+               if (flags & NFT_CHAIN_BINDING)
+                       return -EOPNOTSUPP;
+
                err = nft_chain_parse_hook(net, nla, &hook, family, true);
                if (err < 0)
                        return err;
@@ -1976,16 +1987,33 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
                        return err;
                }
        } else {
+               if (flags & NFT_CHAIN_BASE)
+                       return -EINVAL;
+               if (flags & NFT_CHAIN_HW_OFFLOAD)
+                       return -EOPNOTSUPP;
+
                chain = kzalloc(sizeof(*chain), GFP_KERNEL);
                if (chain == NULL)
                        return -ENOMEM;
+
+               chain->flags = flags;
        }
        ctx->chain = chain;
 
        INIT_LIST_HEAD(&chain->rules);
        chain->handle = nf_tables_alloc_handle(table);
        chain->table = table;
-       chain->name = nla_strdup(nla[NFTA_CHAIN_NAME], GFP_KERNEL);
+
+       if (nla[NFTA_CHAIN_NAME]) {
+               chain->name = nla_strdup(nla[NFTA_CHAIN_NAME], GFP_KERNEL);
+       } else {
+               if (!(flags & NFT_CHAIN_BINDING))
+                       return -EINVAL;
+
+               snprintf(name, sizeof(name), "__chain%llu", ++chain_id);
+               chain->name = kstrdup(name, GFP_KERNEL);
+       }
+
        if (!chain->name) {
                err = -ENOMEM;
                goto err1;
@@ -2976,8 +3004,7 @@ static void nf_tables_rule_destroy(const struct nft_ctx *ctx,
        kfree(rule);
 }
 
-static void nf_tables_rule_release(const struct nft_ctx *ctx,
-                                  struct nft_rule *rule)
+void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *rule)
 {
        nft_rule_expr_deactivate(ctx, rule, NFT_TRANS_RELEASE);
        nf_tables_rule_destroy(ctx, rule);
@@ -3075,6 +3102,9 @@ static int nf_tables_newrule(struct net *net, struct sock *nlsk,
                        NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
                        return PTR_ERR(chain);
                }
+               if (nft_chain_is_bound(chain))
+                       return -EOPNOTSUPP;
+
        } else if (nla[NFTA_RULE_CHAIN_ID]) {
                chain = nft_chain_lookup_byid(net, nla[NFTA_RULE_CHAIN_ID]);
                if (IS_ERR(chain)) {
@@ -3294,6 +3324,8 @@ static int nf_tables_delrule(struct net *net, struct sock *nlsk,
                        NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
                        return PTR_ERR(chain);
                }
+               if (nft_chain_is_bound(chain))
+                       return -EOPNOTSUPP;
        }
 
        nft_ctx_init(&ctx, net, skb, nlh, family, table, chain, nla);
@@ -5330,11 +5362,24 @@ static int nf_tables_newsetelem(struct net *net, struct sock *nlsk,
  */
 void nft_data_hold(const struct nft_data *data, enum nft_data_types type)
 {
+       struct nft_chain *chain;
+       struct nft_rule *rule;
+
        if (type == NFT_DATA_VERDICT) {
                switch (data->verdict.code) {
                case NFT_JUMP:
                case NFT_GOTO:
-                       data->verdict.chain->use++;
+                       chain = data->verdict.chain;
+                       chain->use++;
+
+                       if (!nft_chain_is_bound(chain))
+                               break;
+
+                       chain->table->use++;
+                       list_for_each_entry(rule, &chain->rules, list)
+                               chain->use++;
+
+                       nft_chain_add(chain->table, chain);
                        break;
                }
        }
@@ -7474,7 +7519,7 @@ static void nft_obj_del(struct nft_object *obj)
        list_del_rcu(&obj->list);
 }
 
-static void nft_chain_del(struct nft_chain *chain)
+void nft_chain_del(struct nft_chain *chain)
 {
        struct nft_table *table = chain->table;
 
@@ -7825,6 +7870,10 @@ static int __nf_tables_abort(struct net *net, bool autoload)
                                kfree(nft_trans_chain_name(trans));
                                nft_trans_destroy(trans);
                        } else {
+                               if (nft_chain_is_bound(trans->ctx.chain)) {
+                                       nft_trans_destroy(trans);
+                                       break;
+                               }
                                trans->ctx.table->use--;
                                nft_chain_del(trans->ctx.chain);
                                nf_tables_unregister_hook(trans->ctx.net,
@@ -8321,10 +8370,23 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
 
 static void nft_verdict_uninit(const struct nft_data *data)
 {
+       struct nft_chain *chain;
+       struct nft_rule *rule;
+
        switch (data->verdict.code) {
        case NFT_JUMP:
        case NFT_GOTO:
-               data->verdict.chain->use--;
+               chain = data->verdict.chain;
+               chain->use--;
+
+               if (!nft_chain_is_bound(chain))
+                       break;
+
+               chain->table->use--;
+               list_for_each_entry(rule, &chain->rules, list)
+                       chain->use--;
+
+               nft_chain_del(chain);
                break;
        }
 }
index c7f0ef7..9e55663 100644 (file)
@@ -54,6 +54,23 @@ static int nft_immediate_init(const struct nft_ctx *ctx,
        if (err < 0)
                goto err1;
 
+       if (priv->dreg == NFT_REG_VERDICT) {
+               struct nft_chain *chain = priv->data.verdict.chain;
+
+               switch (priv->data.verdict.code) {
+               case NFT_JUMP:
+               case NFT_GOTO:
+                       if (nft_chain_is_bound(chain)) {
+                               err = -EBUSY;
+                               goto err1;
+                       }
+                       chain->bound = true;
+                       break;
+               default:
+                       break;
+               }
+       }
+
        return 0;
 
 err1:
@@ -81,6 +98,39 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx,
        return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg));
 }
 
+static void nft_immediate_destroy(const struct nft_ctx *ctx,
+                                 const struct nft_expr *expr)
+{
+       const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+       const struct nft_data *data = &priv->data;
+       struct nft_ctx chain_ctx;
+       struct nft_chain *chain;
+       struct nft_rule *rule;
+
+       if (priv->dreg != NFT_REG_VERDICT)
+               return;
+
+       switch (data->verdict.code) {
+       case NFT_JUMP:
+       case NFT_GOTO:
+               chain = data->verdict.chain;
+
+               if (!nft_chain_is_bound(chain))
+                       break;
+
+               chain_ctx = *ctx;
+               chain_ctx.chain = chain;
+
+               list_for_each_entry(rule, &chain->rules, list)
+                       nf_tables_rule_release(&chain_ctx, rule);
+
+               nf_tables_chain_destroy(&chain_ctx);
+               break;
+       default:
+               break;
+       }
+}
+
 static int nft_immediate_dump(struct sk_buff *skb, const struct nft_expr *expr)
 {
        const struct nft_immediate_expr *priv = nft_expr_priv(expr);
@@ -170,6 +220,7 @@ static const struct nft_expr_ops nft_imm_ops = {
        .init           = nft_immediate_init,
        .activate       = nft_immediate_activate,
        .deactivate     = nft_immediate_deactivate,
+       .destroy        = nft_immediate_destroy,
        .dump           = nft_immediate_dump,
        .validate       = nft_immediate_validate,
        .offload        = nft_immediate_offload,