]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: support for implicit chain bindings
authorPablo Neira Ayuso <pablo@netfilter.org>
Sat, 4 Jul 2020 00:43:44 +0000 (02:43 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Wed, 15 Jul 2020 19:56:29 +0000 (21:56 +0200)
This patch allows you to group rules in a subchain, e.g.

 table inet x {
        chain y {
                type filter hook input priority 0;
                tcp dport 22 jump {
                        ip saddr { 127.0.0.0/8, 172.23.0.0/16, 192.168.13.0/24 } accept
                        ip6 saddr ::1/128 accept;
                }
        }
 }

This also supports for the `goto' chain verdict.

This patch adds a new chain binding list to avoid a chain list lookup from the
delinearize path for the usual chains. This can be simplified later on with a
single hashtable per table for all chains.

From the shell, you have to use the explicit separator ';', in bash you
have to escape this:

 # nft add rule inet x y tcp dport 80 jump { ip saddr 127.0.0.1 accept\; ip6 saddr ::1 accept \; }

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
16 files changed:
include/expression.h
include/linux/netfilter/nf_tables.h
include/netlink.h
include/parser.h
include/rule.h
include/statement.h
src/evaluate.c
src/mnl.c
src/netlink.c
src/netlink_delinearize.c
src/netlink_linearize.c
src/parser_bison.y
src/rule.c
src/statement.c
tests/shell/testcases/chains/0041chain_binding_0 [new file with mode: 0755]
tests/shell/testcases/chains/dumps/0041chain_binding_0.nft [new file with mode: 0644]

index 87937a5040b3aab5ec21959db0d04a9e3597dc20..0210a3cb5314105d3428d2c4504fab8cca91b690 100644 (file)
@@ -249,6 +249,7 @@ struct expr {
                        /* EXPR_VERDICT */
                        int                     verdict;
                        struct expr             *chain;
+                       uint32_t                chain_id;
                };
                struct {
                        /* EXPR_VALUE */
index 4565456c0ef447e44e59791b23be2e702b828054..1341b52f069423db112ec1ecc9b91856bc6ec086 100644 (file)
@@ -209,6 +209,7 @@ enum nft_chain_attributes {
        NFTA_CHAIN_COUNTERS,
        NFTA_CHAIN_PAD,
        NFTA_CHAIN_FLAGS,
+       NFTA_CHAIN_ID,
        __NFTA_CHAIN_MAX
 };
 #define NFTA_CHAIN_MAX         (__NFTA_CHAIN_MAX - 1)
@@ -238,6 +239,7 @@ enum nft_rule_attributes {
        NFTA_RULE_PAD,
        NFTA_RULE_ID,
        NFTA_RULE_POSITION_ID,
+       NFTA_RULE_CHAIN_ID,
        __NFTA_RULE_MAX
 };
 #define NFTA_RULE_MAX          (__NFTA_RULE_MAX - 1)
index 0a5fde3cf08cc3c7b191cdaa134e3dc4f532f094..14fcec160e20f8b4fe2a0033dc8459571af23293 100644 (file)
@@ -64,6 +64,7 @@ struct netlink_ctx {
        struct nft_ctx          *nft;
        struct list_head        *msgs;
        struct list_head        list;
+       struct list_head        list_bindings;
        struct set              *set;
        const void              *data;
        uint32_t                seqnum;
@@ -83,6 +84,7 @@ struct nft_data_linearize {
        uint32_t        len;
        uint32_t        value[4];
        char            chain[NFT_CHAIN_MAXNAMELEN];
+       uint32_t        chain_id;
        int             verdict;
 };
 
index 636d1c8810e48b323c1f05f81fff00c36f24ed4a..9baa3a4db789f6a4060594eed770f416eda511c1 100644 (file)
@@ -11,7 +11,7 @@
 #define YYLTYPE_IS_TRIVIAL             0
 #define YYENABLE_NLS                   0
 
-#define SCOPE_NEST_MAX                 3
+#define SCOPE_NEST_MAX                 4
 
 struct parser_state {
        struct input_descriptor         *indesc;
index cfb76b8a0c10337b7cdb74bad444438d80f97906..4de7a0d950ec5844abfaba44a9ee22f39680b68c 100644 (file)
@@ -79,6 +79,7 @@ struct handle {
        struct position_spec    position;
        struct position_spec    index;
        uint32_t                set_id;
+       uint32_t                chain_id;
        uint32_t                rule_id;
        uint32_t                position_id;
 };
@@ -155,6 +156,7 @@ struct table {
        struct list_head        sets;
        struct list_head        objs;
        struct list_head        flowtables;
+       struct list_head        chain_bindings;
        enum table_flags        flags;
        unsigned int            refcnt;
 };
@@ -176,6 +178,7 @@ extern struct table *table_lookup_fuzzy(const struct handle *h,
 enum chain_flags {
        CHAIN_F_BASECHAIN       = 0x1,
        CHAIN_F_HW_OFFLOAD      = 0x2,
+       CHAIN_F_BINDING         = 0x4,
 };
 
 /**
@@ -244,12 +247,16 @@ extern struct chain *chain_lookup(const struct table *table,
 extern struct chain *chain_lookup_fuzzy(const struct handle *h,
                                        const struct nft_cache *cache,
                                        const struct table **table);
+extern struct chain *chain_binding_lookup(const struct table *table,
+                                         const char *chain_name);
 
 extern const char *family2str(unsigned int family);
 extern const char *hooknum2str(unsigned int family, unsigned int hooknum);
 extern const char *chain_policy2str(uint32_t policy);
 extern void chain_print_plain(const struct chain *chain,
                              struct output_ctx *octx);
+extern void chain_rules_print(const struct chain *chain,
+                             struct output_ctx *octx, const char *indent);
 
 /**
  * struct rule - nftables rule
index 061bc61949157c6f7076a9c23841e8d9c4fc16e5..f2fc6ade7734cd0dfbb32178459fefbe15c5852a 100644 (file)
@@ -11,6 +11,14 @@ extern struct stmt *expr_stmt_alloc(const struct location *loc,
 extern struct stmt *verdict_stmt_alloc(const struct location *loc,
                                       struct expr *expr);
 
+struct chain_stmt {
+       struct chain            *chain;
+       struct expr             *expr;
+};
+
+struct stmt *chain_stmt_alloc(const struct location *loc, struct chain *chain,
+                             enum nft_verdicts verdict);
+
 struct flow_stmt {
        const char              *table_name;
 };
@@ -287,6 +295,7 @@ extern struct stmt *xt_stmt_alloc(const struct location *loc);
  * @STMT_CONNLIMIT:    connection limit statement
  * @STMT_MAP:          map statement
  * @STMT_SYNPROXY:     synproxy statement
+ * @STMT_CHAIN:                chain statement
  */
 enum stmt_types {
        STMT_INVALID,
@@ -315,6 +324,7 @@ enum stmt_types {
        STMT_CONNLIMIT,
        STMT_MAP,
        STMT_SYNPROXY,
+       STMT_CHAIN,
 };
 
 /**
@@ -380,6 +390,7 @@ struct stmt {
                struct flow_stmt        flow;
                struct map_stmt         map;
                struct synproxy_stmt    synproxy;
+               struct chain_stmt       chain;
        };
 };
 
index d3368bacc6afe70400826f92d415acab016f63e9..f12c88a07fb5712e93cafc4319b0a8352d05ab3b 100644 (file)
@@ -3098,6 +3098,63 @@ static int stmt_evaluate_synproxy(struct eval_ctx *ctx, struct stmt *stmt)
        return 0;
 }
 
+static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule,
+                        enum cmd_ops op);
+
+static int stmt_evaluate_chain(struct eval_ctx *ctx, struct stmt *stmt)
+{
+       struct chain *chain = stmt->chain.chain;
+       struct cmd *cmd;
+
+       chain->flags |= CHAIN_F_BINDING;
+
+       if (ctx->table != NULL) {
+               list_add_tail(&chain->list, &ctx->table->chains);
+       } else {
+               struct rule *rule, *next;
+               struct handle h;
+
+               memset(&h, 0, sizeof(h));
+               handle_merge(&h, &chain->handle);
+               h.family = ctx->rule->handle.family;
+               xfree(h.table.name);
+               h.table.name = xstrdup(ctx->rule->handle.table.name);
+               h.chain.location = stmt->location;
+               h.chain_id = chain->handle.chain_id;
+
+               cmd = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h, &stmt->location,
+                               chain);
+               cmd->location = stmt->location;
+               list_add_tail(&cmd->list, &ctx->cmd->list);
+               h.chain_id = chain->handle.chain_id;
+
+               list_for_each_entry_safe(rule, next, &chain->rules, list) {
+                       struct eval_ctx rule_ctx = {
+                               .nft    = ctx->nft,
+                               .msgs   = ctx->msgs,
+                       };
+                       struct handle h2 = {};
+
+                       handle_merge(&rule->handle, &ctx->rule->handle);
+                       xfree(rule->handle.table.name);
+                       rule->handle.table.name = xstrdup(ctx->rule->handle.table.name);
+                       xfree(rule->handle.chain.name);
+                       rule->handle.chain.name = NULL;
+                       rule->handle.chain_id = chain->handle.chain_id;
+                       if (rule_evaluate(&rule_ctx, rule, CMD_INVALID) < 0)
+                               return -1;
+
+                       handle_merge(&h2, &rule->handle);
+                       cmd = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h2,
+                                       &rule->location, rule);
+                       list_add_tail(&cmd->list, &ctx->cmd->list);
+                       list_del(&rule->list);
+               }
+       }
+
+       return 0;
+}
+
 static int stmt_evaluate_dup(struct eval_ctx *ctx, struct stmt *stmt)
 {
        int err;
@@ -3490,6 +3547,8 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt)
                return stmt_evaluate_map(ctx, stmt);
        case STMT_SYNPROXY:
                return stmt_evaluate_synproxy(ctx, stmt);
+       case STMT_CHAIN:
+               return stmt_evaluate_chain(ctx, stmt);
        default:
                BUG("unknown statement type %s\n", stmt->ops->name);
        }
@@ -3879,7 +3938,7 @@ static int chain_evaluate(struct eval_ctx *ctx, struct chain *chain)
                        chain_add_hash(chain, table);
                }
                return 0;
-       } else {
+       } else if (!(chain->flags & CHAIN_F_BINDING)) {
                if (chain_lookup(table, &chain->handle) == NULL)
                        chain_add_hash(chain_get(chain), table);
        }
index 19f6664169096f44fbafa0c4df6cd464d8546966..e5e88f3bd990a3a5ec06c0bee23a5fb7eff2e811 100644 (file)
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -466,7 +466,11 @@ int mnl_nft_rule_add(struct netlink_ctx *ctx, struct cmd *cmd,
        cmd_add_loc(cmd, nlh->nlmsg_len, &h->table.location);
        mnl_attr_put_strz(nlh, NFTA_RULE_TABLE, h->table.name);
        cmd_add_loc(cmd, nlh->nlmsg_len, &h->chain.location);
-       mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name);
+
+       if (h->chain_id)
+               mnl_attr_put_u32(nlh, NFTA_RULE_CHAIN_ID, htonl(h->chain_id));
+       else
+               mnl_attr_put_strz(nlh, NFTA_RULE_CHAIN, h->chain.name);
 
        nftnl_rule_nlmsg_build_payload(nlh, nlr);
        nftnl_rule_free(nlr);
@@ -679,7 +683,18 @@ int mnl_nft_chain_add(struct netlink_ctx *ctx, struct cmd *cmd,
        cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.table.location);
        mnl_attr_put_strz(nlh, NFTA_CHAIN_TABLE, cmd->handle.table.name);
        cmd_add_loc(cmd, nlh->nlmsg_len, &cmd->handle.chain.location);
-       mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, cmd->handle.chain.name);
+
+       if (!cmd->chain || !(cmd->chain->flags & CHAIN_F_BINDING)) {
+               mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME, cmd->handle.chain.name);
+       } else {
+               if (cmd->handle.chain.name)
+                       mnl_attr_put_strz(nlh, NFTA_CHAIN_NAME,
+                                         cmd->handle.chain.name);
+
+               mnl_attr_put_u32(nlh, NFTA_CHAIN_ID, htonl(cmd->handle.chain_id));
+               if (cmd->chain->flags)
+                       nftnl_chain_set_u32(nlc, NFTNL_CHAIN_FLAGS, cmd->chain->flags);
+       }
 
        if (cmd->chain && cmd->chain->policy) {
                mpz_export_data(&policy, cmd->chain->policy->value,
index fb0a17bac0d725f005b7b1cb0945d0ffe274dbec..f752c3c932aabb8f721cd7a5000772047a94f368 100644 (file)
@@ -269,31 +269,41 @@ static void netlink_gen_constant_data(const struct expr *expr,
                             div_round_up(expr->len, BITS_PER_BYTE), data);
 }
 
-static void netlink_gen_verdict(const struct expr *expr,
-                               struct nft_data_linearize *data)
+static void netlink_gen_chain(const struct expr *expr,
+                             struct nft_data_linearize *data)
 {
        char chain[NFT_CHAIN_MAXNAMELEN];
        unsigned int len;
 
-       data->verdict = expr->verdict;
+       len = expr->chain->len / BITS_PER_BYTE;
 
-       switch (expr->verdict) {
-       case NFT_JUMP:
-       case NFT_GOTO:
-               len = expr->chain->len / BITS_PER_BYTE;
+       if (!len)
+               BUG("chain length is 0");
 
-               if (!len)
-                       BUG("chain length is 0");
+       if (len > sizeof(chain))
+               BUG("chain is too large (%u, %u max)",
+                   len, (unsigned int)sizeof(chain));
 
-               if (len > sizeof(chain))
-                       BUG("chain is too large (%u, %u max)",
-                           len, (unsigned int)sizeof(chain));
+       memset(chain, 0, sizeof(chain));
 
-               memset(chain, 0, sizeof(chain));
+       mpz_export_data(chain, expr->chain->value,
+                       BYTEORDER_HOST_ENDIAN, len);
+       snprintf(data->chain, NFT_CHAIN_MAXNAMELEN, "%s", chain);
+}
 
-               mpz_export_data(chain, expr->chain->value,
-                               BYTEORDER_HOST_ENDIAN, len);
-               snprintf(data->chain, NFT_CHAIN_MAXNAMELEN, "%s", chain);
+static void netlink_gen_verdict(const struct expr *expr,
+                               struct nft_data_linearize *data)
+{
+
+       data->verdict = expr->verdict;
+
+       switch (expr->verdict) {
+       case NFT_JUMP:
+       case NFT_GOTO:
+               if (expr->chain)
+                       netlink_gen_chain(expr, data);
+               else
+                       data->chain_id = expr->chain_id;
                break;
        }
 }
@@ -546,7 +556,10 @@ static int list_chain_cb(struct nftnl_chain *nlc, void *arg)
                return 0;
 
        chain = netlink_delinearize_chain(ctx, nlc);
-       list_add_tail(&chain->list, &ctx->list);
+       if (chain->flags & CHAIN_F_BINDING)
+               list_add_tail(&chain->list, &ctx->list_bindings);
+       else
+               list_add_tail(&chain->list, &ctx->list);
 
        return 0;
 }
index 7d7e07cf89ced7790fdf71cdb1aed6e6c846b343..d0438f44058defad4c4da6421f021e0942f16fb3 100644 (file)
@@ -163,6 +163,24 @@ err:
        return NULL;
 }
 
+static void netlink_parse_chain_verdict(struct netlink_parse_ctx *ctx,
+                                       const struct location *loc,
+                                       struct expr *expr,
+                                       enum nft_verdicts verdict)
+{
+       char chain_name[NFT_CHAIN_MAXNAMELEN] = {};
+       struct chain *chain;
+
+       expr_chain_export(expr->chain, chain_name);
+       chain = chain_binding_lookup(ctx->table, chain_name);
+       if (chain) {
+               ctx->stmt = chain_stmt_alloc(loc, chain, verdict);
+               expr_free(expr);
+       } else {
+               ctx->stmt = verdict_stmt_alloc(loc, expr);
+       }
+}
+
 static void netlink_parse_immediate(struct netlink_parse_ctx *ctx,
                                    const struct location *loc,
                                    const struct nftnl_expr *nle)
@@ -182,12 +200,23 @@ static void netlink_parse_immediate(struct netlink_parse_ctx *ctx,
        }
 
        dreg = netlink_parse_register(nle, NFTNL_EXPR_IMM_DREG);
-
        expr = netlink_alloc_data(loc, &nld, dreg);
-       if (dreg == NFT_REG_VERDICT)
-               ctx->stmt = verdict_stmt_alloc(loc, expr);
-       else
+
+       if (dreg == NFT_REG_VERDICT) {
+               switch (expr->verdict) {
+               case NFT_JUMP:
+                       netlink_parse_chain_verdict(ctx, loc, expr, NFT_JUMP);
+                       break;
+               case NFT_GOTO:
+                       netlink_parse_chain_verdict(ctx, loc, expr, NFT_GOTO);
+                       break;
+               default:
+                       ctx->stmt = verdict_stmt_alloc(loc, expr);
+                       break;
+               }
+       } else {
                netlink_set_register(ctx, dreg, expr);
+       }
 }
 
 static void netlink_parse_xfrm(struct netlink_parse_ctx *ctx,
index 528f1e5cd0fefc37ec6078c61fda122543943442..846df46b75fd31184ac727fd21804bbbbd9014bd 100644 (file)
@@ -713,10 +713,12 @@ static void netlink_gen_immediate(struct netlink_linearize_ctx *ctx,
                nftnl_expr_set(nle, NFTNL_EXPR_IMM_DATA, nld.value, nld.len);
                break;
        case EXPR_VERDICT:
-               if ((expr->chain != NULL) &&
-                   !nftnl_expr_is_set(nle, NFTNL_EXPR_IMM_CHAIN)) {
+               if (expr->chain) {
                        nftnl_expr_set_str(nle, NFTNL_EXPR_IMM_CHAIN,
                                           nld.chain);
+               } else if (expr->chain_id) {
+                       nftnl_expr_set_u32(nle, NFTNL_EXPR_IMM_CHAIN_ID,
+                                          nld.chain_id);
                }
                nftnl_expr_set_u32(nle, NFTNL_EXPR_IMM_VERDICT, nld.verdict);
                break;
@@ -1445,6 +1447,12 @@ static void netlink_gen_meter_stmt(struct netlink_linearize_ctx *ctx,
        nftnl_rule_add_expr(ctx->nlr, nle);
 }
 
+static void netlink_gen_chain_stmt(struct netlink_linearize_ctx *ctx,
+                                  const struct stmt *stmt)
+{
+       return netlink_gen_expr(ctx, stmt->chain.expr, NFT_REG_VERDICT);
+}
+
 static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx,
                             const struct stmt *stmt)
 {
@@ -1498,6 +1506,8 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx,
                return netlink_gen_objref_stmt(ctx, stmt);
        case STMT_MAP:
                return netlink_gen_map_stmt(ctx, stmt);
+       case STMT_CHAIN:
+               return netlink_gen_chain_stmt(ctx, stmt);
        default:
                BUG("unknown statement type %s\n", stmt->ops->name);
        }
index face99507b828e5d1634076241097dfbe34e8674..756806d9efdb391e995cab766f67fff0a1e2c951 100644 (file)
@@ -594,8 +594,8 @@ int nft_lex(void *, void *, void *);
 
 %type <table>                  table_block_alloc table_block
 %destructor { close_scope(state); table_free($$); }    table_block_alloc
-%type <chain>                  chain_block_alloc chain_block
-%destructor { close_scope(state); chain_free($$); }    chain_block_alloc
+%type <chain>                  chain_block_alloc chain_block subchain_block
+%destructor { close_scope(state); chain_free($$); }    chain_block_alloc subchain_block
 %type <rule>                   rule rule_alloc
 %destructor { rule_free($$); } rule
 
@@ -642,7 +642,9 @@ int nft_lex(void *, void *, void *);
 %destructor { stmt_free($$); } tproxy_stmt
 %type <stmt>                   synproxy_stmt synproxy_stmt_alloc
 %destructor { stmt_free($$); } synproxy_stmt synproxy_stmt_alloc
-
+%type <stmt>                   chain_stmt
+%destructor { stmt_free($$); } chain_stmt
+%type <val>                    chain_stmt_type
 
 %type <stmt>                   queue_stmt queue_stmt_alloc
 %destructor { stmt_free($$); } queue_stmt queue_stmt_alloc
@@ -1682,6 +1684,15 @@ chain_block              :       /* empty */     { $$ = $<chain>-1; }
                        }
                        ;
 
+subchain_block         :       /* empty */     { $$ = $<chain>-1; }
+                       |       subchain_block  stmt_separator
+                       |       subchain_block  rule stmt_separator
+                       {
+                               list_add_tail(&$2->list, &$1->rules);
+                               $$ = $1;
+                       }
+                       ;
+
 typeof_expr            :       primary_expr
                        {
                                if (expr_ops($1)->build_udata == NULL) {
@@ -2527,6 +2538,20 @@ stmt                     :       verdict_stmt
                        |       set_stmt
                        |       map_stmt
                        |       synproxy_stmt
+                       |       chain_stmt
+                       ;
+
+chain_stmt_type                :       JUMP    { $$ = NFT_JUMP; }
+                       |       GOTO    { $$ = NFT_GOTO; }
+                       ;
+
+chain_stmt             :       chain_stmt_type chain_block_alloc '{'   subchain_block  '}'
+                       {
+                               $2->location = @2;
+                               close_scope(state);
+                               $4->location = @4;
+                               $$ = chain_stmt_alloc(&@$, $4, $1);
+                       }
                        ;
 
 verdict_stmt           :       verdict_expr
index 21a52157391dc546dae3b358170e92221b790cee..fa1861403ba155a5912e16ddcf0c6e10562652c1 100644 (file)
@@ -177,7 +177,10 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags)
                        ret = netlink_list_chains(ctx, &table->handle);
                        if (ret < 0)
                                return -1;
+
                        list_splice_tail_init(&ctx->list, &table->chains);
+                       list_splice_tail_init(&ctx->list_bindings,
+                                             &table->chain_bindings);
                }
                if (flags & NFT_CACHE_FLOWTABLE_BIT) {
                        ret = netlink_list_flowtables(ctx, &table->handle);
@@ -196,6 +199,9 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags)
                        ret = netlink_list_rules(ctx, &table->handle);
                        list_for_each_entry_safe(rule, nrule, &ctx->list, list) {
                                chain = chain_lookup(table, &rule->handle);
+                               if (!chain)
+                                       chain = chain_binding_lookup(table,
+                                                       rule->handle.chain.name);
                                list_move_tail(&rule->list, &chain->rules);
                        }
                        if (ret < 0)
@@ -245,6 +251,7 @@ int cache_update(struct nft_ctx *nft, unsigned int flags, struct list_head *msgs
 {
        struct netlink_ctx ctx = {
                .list           = LIST_HEAD_INIT(ctx.list),
+               .list_bindings  = LIST_HEAD_INIT(ctx.list_bindings),
                .nft            = nft,
                .msgs           = msgs,
        };
@@ -858,12 +865,16 @@ const char *chain_hookname_lookup(const char *name)
        return NULL;
 }
 
+/* internal ID to uniquely identify a set in the batch */
+static uint32_t chain_id;
+
 struct chain *chain_alloc(const char *name)
 {
        struct chain *chain;
 
        chain = xzalloc(sizeof(*chain));
        chain->refcnt = 1;
+       chain->handle.chain_id = ++chain_id;
        init_list_head(&chain->rules);
        init_list_head(&chain->scope.symbols);
        if (name != NULL)
@@ -916,6 +927,18 @@ struct chain *chain_lookup(const struct table *table, const struct handle *h)
        return NULL;
 }
 
+struct chain *chain_binding_lookup(const struct table *table,
+                                  const char *chain_name)
+{
+       struct chain *chain;
+
+       list_for_each_entry(chain, &table->chain_bindings, list) {
+               if (!strcmp(chain->handle.chain.name, chain_name))
+                       return chain;
+       }
+       return NULL;
+}
+
 struct chain *chain_lookup_fuzzy(const struct handle *h,
                                 const struct nft_cache *cache,
                                 const struct table **t)
@@ -1175,6 +1198,9 @@ static void chain_print_declaration(const struct chain *chain,
        char priobuf[STD_PRIO_BUFSIZE];
        int policy, i;
 
+       if (chain->flags & CHAIN_F_BINDING)
+               return;
+
        nft_print(octx, "\tchain %s {", chain->handle.chain.name);
        if (nft_output_handle(octx))
                nft_print(octx, " # handle %" PRIu64, chain->handle.handle.id);
@@ -1210,17 +1236,22 @@ static void chain_print_declaration(const struct chain *chain,
        }
 }
 
-static void chain_print(const struct chain *chain, struct output_ctx *octx)
+void chain_rules_print(const struct chain *chain, struct output_ctx *octx,
+                      const char *indent)
 {
        struct rule *rule;
 
-       chain_print_declaration(chain, octx);
-
        list_for_each_entry(rule, &chain->rules, list) {
-               nft_print(octx, "\t\t");
+               nft_print(octx, "\t\t%s", indent ? : "");
                rule_print(rule, octx);
                nft_print(octx, "\n");
        }
+}
+
+static void chain_print(const struct chain *chain, struct output_ctx *octx)
+{
+       chain_print_declaration(chain, octx);
+       chain_rules_print(chain, octx, NULL);
        nft_print(octx, "\t}\n");
 }
 
@@ -1255,6 +1286,7 @@ struct table *table_alloc(void)
        init_list_head(&table->sets);
        init_list_head(&table->objs);
        init_list_head(&table->flowtables);
+       init_list_head(&table->chain_bindings);
        init_list_head(&table->scope.symbols);
        table->refcnt = 1;
 
@@ -1272,6 +1304,8 @@ void table_free(struct table *table)
                return;
        list_for_each_entry_safe(chain, next, &table->chains, list)
                chain_free(chain);
+       list_for_each_entry_safe(chain, next, &table->chain_bindings, list)
+               chain_free(chain);
        list_for_each_entry_safe(set, nset, &table->sets, list)
                set_free(set);
        list_for_each_entry_safe(ft, nft, &table->flowtables, list)
@@ -1437,6 +1471,7 @@ void nft_cmd_expand(struct cmd *cmd)
                list_for_each_entry(chain, &table->chains, list) {
                        memset(&h, 0, sizeof(h));
                        handle_merge(&h, &chain->handle);
+                       h.chain_id = chain->handle.chain_id;
                        new = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &h,
                                        &chain->location, chain_get(chain));
                        list_add_tail(&new->list, &new_cmds);
@@ -1469,6 +1504,12 @@ void nft_cmd_expand(struct cmd *cmd)
                        list_for_each_entry(rule, &chain->rules, list) {
                                memset(&h, 0, sizeof(h));
                                handle_merge(&h, &rule->handle);
+                               if (chain->flags & CHAIN_F_BINDING) {
+                                       rule->handle.chain_id =
+                                               chain->handle.chain_id;
+                                       rule->handle.chain.location =
+                                               chain->location;
+                               }
                                new = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &h,
                                                &rule->location,
                                                rule_get(rule));
index afedbba21b750937bec6109ff88c32318e85137c..6fe8e9d9beb4665b964a62e2ddf6231b9a87b0f5 100644 (file)
@@ -15,6 +15,7 @@
 #include <inttypes.h>
 #include <string.h>
 #include <syslog.h>
+#include <rule.h>
 
 #include <arpa/inet.h>
 #include <linux/netfilter.h>
@@ -112,6 +113,50 @@ struct stmt *verdict_stmt_alloc(const struct location *loc, struct expr *expr)
        return stmt;
 }
 
+static const char *chain_verdict(const struct expr *expr)
+{
+       switch (expr->verdict) {
+       case NFT_JUMP:
+               return "jump";
+       case NFT_GOTO:
+               return "goto";
+       default:
+               BUG("unknown chain verdict");
+       }
+}
+
+static void chain_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
+{
+       nft_print(octx, "%s {\n", chain_verdict(stmt->chain.expr));
+       chain_rules_print(stmt->chain.chain, octx, "\t");
+       nft_print(octx, "\t\t}");
+}
+
+static void chain_stmt_destroy(struct stmt *stmt)
+{
+       expr_free(stmt->chain.expr);
+}
+
+static const struct stmt_ops chain_stmt_ops = {
+       .type           = STMT_CHAIN,
+       .name           = "chain",
+       .print          = chain_stmt_print,
+       .destroy        = chain_stmt_destroy,
+};
+
+struct stmt *chain_stmt_alloc(const struct location *loc, struct chain *chain,
+                             enum nft_verdicts verdict)
+{
+       struct stmt *stmt;
+
+       stmt = stmt_alloc(loc, &chain_stmt_ops);
+       stmt->chain.chain = chain;
+       stmt->chain.expr = verdict_expr_alloc(loc, verdict, NULL);
+       stmt->chain.expr->chain_id = chain->handle.chain_id;
+
+       return stmt;
+}
+
 static void meter_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 {
        unsigned int flags = octx->flags;
diff --git a/tests/shell/testcases/chains/0041chain_binding_0 b/tests/shell/testcases/chains/0041chain_binding_0
new file mode 100755 (executable)
index 0000000..59bdbe9
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+
+EXPECTED="table inet x {
+        chain y {
+                type filter hook input priority 0;
+                meta l4proto { tcp, udp } th dport 53 jump {
+                        ip saddr { 127.0.0.0/8, 172.23.0.0/16, 192.168.13.0/24 } counter accept
+                        ip6 saddr ::1/128 counter accept
+                }
+        }
+}"
+
+$NFT -f - <<< $EXPECTED
+$NFT add rule inet x y meta l4proto icmpv6 jump { counter accept\; }
+$NFT add rule inet x y meta l4proto sctp jump { drop\; }
+$NFT delete rule inet x y handle 13
diff --git a/tests/shell/testcases/chains/dumps/0041chain_binding_0.nft b/tests/shell/testcases/chains/dumps/0041chain_binding_0.nft
new file mode 100644 (file)
index 0000000..520203d
--- /dev/null
@@ -0,0 +1,12 @@
+table inet x {
+       chain y {
+               type filter hook input priority filter; policy accept;
+               meta l4proto { tcp, udp } th dport 53 jump {
+                       ip saddr { 127.0.0.0/8, 172.23.0.0/16, 192.168.13.0/24 } counter packets 0 bytes 0 accept
+                       ip6 saddr ::1 counter packets 0 bytes 0 accept
+               }
+               meta l4proto ipv6-icmp jump {
+                       counter packets 0 bytes 0 accept
+               }
+       }
+}