summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPablo Neira Ayuso <pablo@netfilter.org>2020-07-04 02:43:44 +0200
committerPablo Neira Ayuso <pablo@netfilter.org>2020-07-15 21:56:29 +0200
commitc330152b7f7779f15dba3e0862bf5616e7cb3eab (patch)
tree49c9ab5d837ab99a23e15399acb7ea610606ecfc /src
parent1cba7a5e5e96dd920271823125b45e182f22ec82 (diff)
src: support for implicit chain bindings
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>
Diffstat (limited to 'src')
-rw-r--r--src/evaluate.c61
-rw-r--r--src/mnl.c19
-rw-r--r--src/netlink.c47
-rw-r--r--src/netlink_delinearize.c37
-rw-r--r--src/netlink_linearize.c14
-rw-r--r--src/parser_bison.y31
-rw-r--r--src/rule.c49
-rw-r--r--src/statement.c45
8 files changed, 270 insertions, 33 deletions
diff --git a/src/evaluate.c b/src/evaluate.c
index d3368bac..f12c88a0 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -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);
}
diff --git a/src/mnl.c b/src/mnl.c
index 19f66641..e5e88f3b 100644
--- 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,
diff --git a/src/netlink.c b/src/netlink.c
index fb0a17ba..f752c3c9 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -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;
}
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 7d7e07cf..d0438f44 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -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,
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index 528f1e5c..846df46b 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -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);
}
diff --git a/src/parser_bison.y b/src/parser_bison.y
index face9950..756806d9 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -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
diff --git a/src/rule.c b/src/rule.c
index 21a52157..fa186140 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -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));
diff --git a/src/statement.c b/src/statement.c
index afedbba2..6fe8e9d9 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -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;