The following example shows how to populate a set from the packet path using the destination IP address, for each entry there is a counter. The entry expires after the 1 hour timeout if no packets matching this entry are seen. table ip x { set xyz { type ipv4_addr size 65535 flags dynamic,timeout timeout 1h } chain y { type filter hook output priority filter; policy accept; update @xyz { ip daddr counter } counter } } Similar example, that creates a mapping better IP address and mark, where the mark is assigned using an incremental sequence generator from 0 to 1 inclusive. table ip x { map xyz { type ipv4_addr : mark size 65535 flags dynamic,timeout timeout 1h } chain y { type filter hook input priority filter; policy accept; update @xyz { ip saddr counter : numgen inc mod 2 } } } Supported stateful statements are: limit, quota, counter and connlimit. Signed-off-by: Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx> --- include/statement.h | 2 ++ src/evaluate.c | 14 ++++++++++++++ src/expression.c | 7 +++---- src/netlink_delinearize.c | 26 +++++++++++++++----------- src/netlink_linearize.c | 8 ++++++++ src/parser_bison.y | 32 ++++++++++++++++++++++++++------ src/rule.c | 7 ++++--- src/statement.c | 12 ++++++++++++ 8 files changed, 84 insertions(+), 24 deletions(-) diff --git a/include/statement.h b/include/statement.h index 6c583a918eb9..48ba6673553b 100644 --- a/include/statement.h +++ b/include/statement.h @@ -184,6 +184,7 @@ uint32_t fwd_stmt_type(const char *type); struct set_stmt { struct expr *set; struct expr *key; + struct stmt *stmt; enum nft_dynset_ops op; }; @@ -195,6 +196,7 @@ struct map_stmt { struct expr *set; struct expr *key; struct expr *data; + struct stmt *stmt; enum nft_dynset_ops op; }; diff --git a/src/evaluate.c b/src/evaluate.c index 9bc67d8f71f1..647e16069ba4 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -2708,6 +2708,13 @@ static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt) if (stmt->set.key->comment != NULL) return expr_error(ctx->msgs, stmt->set.key, "Key expression comments are not supported"); + if (stmt->set.stmt) { + if (stmt_evaluate(ctx, stmt->set.stmt) < 0) + return -1; + if (!(stmt->set.stmt->flags & STMT_F_STATEFUL)) + return stmt_binary_error(ctx, stmt->set.stmt, stmt, + "meter statement must be stateful"); + } return 0; } @@ -2739,6 +2746,13 @@ static int stmt_evaluate_map(struct eval_ctx *ctx, struct stmt *stmt) if (stmt->map.data->comment != NULL) return expr_error(ctx->msgs, stmt->map.data, "Data expression comments are not supported"); + if (stmt->map.stmt) { + if (stmt_evaluate(ctx, stmt->map.stmt) < 0) + return -1; + if (!(stmt->map.stmt->flags & STMT_F_STATEFUL)) + return stmt_binary_error(ctx, stmt->map.stmt, stmt, + "meter statement must be stateful"); + } return 0; } diff --git a/src/expression.c b/src/expression.c index bea0f4c8d9bc..0bd5112287e7 100644 --- a/src/expression.c +++ b/src/expression.c @@ -1045,13 +1045,12 @@ static void set_elem_expr_print(const struct expr *expr, nft_print(octx, " expires "); time_print(expr->expiration, octx); } - if (expr->comment) - nft_print(octx, " comment \"%s\"", expr->comment); - if (expr->stmt) { - nft_print(octx, " : "); + nft_print(octx, " "); stmt_print(expr->stmt, octx); } + if (expr->comment) + nft_print(octx, " comment \"%s\"", expr->comment); } static void set_elem_expr_destroy(struct expr *expr) diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 898c737f9b28..6c5188cd9b5f 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -1312,23 +1312,27 @@ static void netlink_parse_dynset(struct netlink_parse_ctx *ctx, expr_data = netlink_get_register(ctx, loc, sreg_data); } - if (dstmt != NULL) { - stmt = meter_stmt_alloc(loc); - stmt->meter.set = set_ref_expr_alloc(loc, set); - stmt->meter.key = expr; - stmt->meter.stmt = dstmt; - stmt->meter.size = set->desc.size; - } else if (expr_data != NULL) { + if (expr_data != NULL) { stmt = map_stmt_alloc(loc); stmt->map.set = set_ref_expr_alloc(loc, set); stmt->map.key = expr; stmt->map.data = expr_data; + stmt->map.stmt = dstmt; stmt->map.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); } else { - stmt = set_stmt_alloc(loc); - stmt->set.set = set_ref_expr_alloc(loc, set); - stmt->set.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); - stmt->set.key = expr; + if (dstmt != NULL && set->flags & NFT_SET_ANONYMOUS) { + stmt = meter_stmt_alloc(loc); + stmt->meter.set = set_ref_expr_alloc(loc, set); + stmt->meter.key = expr; + stmt->meter.stmt = dstmt; + stmt->meter.size = set->desc.size; + } else { + stmt = set_stmt_alloc(loc); + stmt->set.set = set_ref_expr_alloc(loc, set); + stmt->set.op = nftnl_expr_get_u32(nle, NFTNL_EXPR_DYNSET_OP); + stmt->set.key = expr; + stmt->set.stmt = dstmt; + } } ctx->stmt = stmt; diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index 821fcd0a6377..0bd946a1cf80 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -1269,6 +1269,10 @@ static void netlink_gen_set_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME, set->handle.set.name); nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID, set->handle.set_id); nftnl_rule_add_expr(ctx->nlr, nle); + + if (stmt->set.stmt) + nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, + netlink_gen_stmt_stateful(ctx, stmt->set.stmt), 0); } static void netlink_gen_map_stmt(struct netlink_linearize_ctx *ctx, @@ -1296,6 +1300,10 @@ static void netlink_gen_map_stmt(struct netlink_linearize_ctx *ctx, nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME, set->handle.set.name); nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID, set->handle.set_id); + if (stmt->map.stmt) + nftnl_expr_set(nle, NFTNL_EXPR_DYNSET_EXPR, + netlink_gen_stmt_stateful(ctx, stmt->map.stmt), 0); + nftnl_rule_add_expr(ctx->nlr, nle); } diff --git a/src/parser_bison.y b/src/parser_bison.y index 199ef13d8c1d..cc114717f579 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -561,8 +561,8 @@ int nft_lex(void *, void *, void *); %destructor { stmt_list_free($$); xfree($$); } stmt_list %type <stmt> stmt match_stmt verdict_stmt %destructor { stmt_free($$); } stmt match_stmt verdict_stmt -%type <stmt> counter_stmt counter_stmt_alloc -%destructor { stmt_free($$); } counter_stmt counter_stmt_alloc +%type <stmt> counter_stmt counter_stmt_alloc stateful_stmt +%destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt %type <stmt> payload_stmt %destructor { stmt_free($$); } payload_stmt %type <stmt> ct_stmt @@ -2112,16 +2112,19 @@ stmt_list : stmt } ; +stateful_stmt : counter_stmt + | limit_stmt + | quota_stmt + | connlimit_stmt + ; + stmt : verdict_stmt | match_stmt | meter_stmt - | connlimit_stmt - | counter_stmt | payload_stmt + | stateful_stmt | meta_stmt | log_stmt - | limit_stmt - | quota_stmt | reject_stmt | nat_stmt | tproxy_stmt @@ -2862,6 +2865,14 @@ set_stmt : SET set_stmt_op set_elem_expr_stmt symbol_expr $$->set.key = $4; $$->set.set = $2; } + | set_stmt_op symbol_expr '{' set_elem_expr_stmt stateful_stmt '}' + { + $$ = set_stmt_alloc(&@$); + $$->set.op = $1; + $$->set.key = $4; + $$->set.set = $2; + $$->set.stmt = $5; + } ; set_stmt_op : ADD { $$ = NFT_DYNSET_OP_ADD; } @@ -2876,6 +2887,15 @@ map_stmt : set_stmt_op symbol_expr '{' set_elem_expr_stmt COLON set_elem_expr_s $$->map.data = $6; $$->map.set = $2; } + | set_stmt_op symbol_expr '{' set_elem_expr_stmt stateful_stmt COLON set_elem_expr_stmt '}' + { + $$ = map_stmt_alloc(&@$); + $$->map.op = $1; + $$->map.key = $4; + $$->map.data = $7; + $$->map.stmt = $5; + $$->map.set = $2; + } ; meter_stmt : flow_stmt_legacy_alloc flow_stmt_opts '{' meter_key_expr stmt '}' diff --git a/src/rule.c b/src/rule.c index fcfcf60cbc7c..8288483a6ad0 100644 --- a/src/rule.c +++ b/src/rule.c @@ -335,10 +335,11 @@ static void set_print_declaration(const struct set *set, const char *type; uint32_t flags; - if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) - type = "map"; - else if (set->flags & NFT_SET_EVAL) + if ((set->flags & (NFT_SET_EVAL | NFT_SET_ANONYMOUS)) == + (NFT_SET_EVAL | NFT_SET_ANONYMOUS)) type = "meter"; + else if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + type = "map"; else type = "set"; diff --git a/src/statement.c b/src/statement.c index 039ca943e92c..98e568448486 100644 --- a/src/statement.c +++ b/src/statement.c @@ -630,6 +630,12 @@ static void set_stmt_print(const struct stmt *stmt, struct output_ctx *octx) expr_print(stmt->set.set, octx); nft_print(octx, " { "); expr_print(stmt->set.key, octx); + if (stmt->set.stmt) { + nft_print(octx, " "); + octx->stateless++; + stmt_print(stmt->set.stmt, octx); + octx->stateless--; + } nft_print(octx, " }"); } @@ -658,6 +664,12 @@ static void map_stmt_print(const struct stmt *stmt, struct output_ctx *octx) expr_print(stmt->map.set, octx); nft_print(octx, " { "); expr_print(stmt->map.key, octx); + if (stmt->map.stmt) { + nft_print(octx, " "); + octx->stateless++; + stmt_print(stmt->map.stmt, octx); + octx->stateless--; + } nft_print(octx, " : "); expr_print(stmt->map.data, octx); nft_print(octx, " }"); -- 2.11.0