]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: add flow statement
authorPatrick McHardy <kaber@trash.net>
Wed, 27 Apr 2016 11:29:50 +0000 (12:29 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 13 May 2016 17:30:29 +0000 (19:30 +0200)
The flow statement allows to instantiate per flow statements for user
defined flows. This can so far be used for per flow accounting or limiting,
similar to what the iptables hashlimit provides. Flows can be aged using
the timeout option.

Examples:

 # nft filter input flow ip saddr . tcp dport limit rate 10/second
 # nft filter input flow table acct iif . ip saddr timeout 60s counter

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
12 files changed:
include/expression.h
include/netlink.h
include/rule.h
include/statement.h
src/evaluate.c
src/expression.c
src/netlink.c
src/netlink_delinearize.c
src/netlink_linearize.c
src/parser_bison.y
src/scanner.l
src/statement.c

index fc1841068f12d92cb3e5ceb85bd851dd83c88654..6e5e835e672e66d5d00bd5807d819cf4648a2884 100644 (file)
@@ -237,6 +237,7 @@ struct expr {
                        uint64_t                timeout;
                        uint64_t                expiration;
                        const char              *comment;
+                       struct stmt             *stmt;
                };
                struct {
                        /* EXPR_UNARY */
index 9ec5409b4d9241b8648d0e26b7c39047c56ddb8f..9f4656018f61255ab017f62da090c6a559e86095 100644 (file)
@@ -142,6 +142,9 @@ extern int netlink_list_sets(struct netlink_ctx *ctx, const struct handle *h,
 extern int netlink_get_set(struct netlink_ctx *ctx, const struct handle *h,
                           const struct location *loc);
 
+extern struct stmt *netlink_parse_set_expr(const struct set *set,
+                                          const struct nftnl_expr *nle);
+
 extern int netlink_add_setelems(struct netlink_ctx *ctx, const struct handle *h,
                                const struct expr *expr);
 extern int netlink_delete_setelems(struct netlink_ctx *ctx, const struct handle *h,
index 09b3ff7016e3daaf22878a4da735565da1d6ce7a..bfe398d6cf9b9a170a942b4efaae091feecfc596 100644 (file)
@@ -206,6 +206,7 @@ enum set_flags {
        SET_F_INTERVAL          = 0x4,
        SET_F_MAP               = 0x8,
        SET_F_TIMEOUT           = 0x10,
+       SET_F_EVAL              = 0x20,
 };
 
 /**
index a6a86f949a46f44e2a8140fb025c07ccd76c585a..e9313ca75dbba7eaa8bfa07f734f1ee0952fc74c 100644 (file)
@@ -138,12 +138,22 @@ struct set_stmt {
 
 extern struct stmt *set_stmt_alloc(const struct location *loc);
 
+struct flow_stmt {
+       struct expr             *set;
+       struct expr             *key;
+       struct stmt             *stmt;
+       const char              *table;
+};
+
+extern struct stmt *flow_stmt_alloc(const struct location *loc);
+
 /**
  * enum stmt_types - statement types
  *
  * @STMT_INVALID:      uninitialised
  * @STMT_EXPRESSION:   expression statement (relational)
  * @STMT_VERDICT:      verdict statement
+ * @STMT_FLOW:         flow statement
  * @STMT_COUNTER:      counters
  * @STMT_PAYLOAD:      payload statement
  * @STMT_META:         meta statement
@@ -163,6 +173,7 @@ enum stmt_types {
        STMT_INVALID,
        STMT_EXPRESSION,
        STMT_VERDICT,
+       STMT_FLOW,
        STMT_COUNTER,
        STMT_PAYLOAD,
        STMT_META,
@@ -217,6 +228,7 @@ struct stmt {
 
        union {
                struct expr             *expr;
+               struct flow_stmt        flow;
                struct counter_stmt     counter;
                struct payload_stmt     payload;
                struct meta_stmt        meta;
index 9d89d9058d1e3133751c09757976c7e786d239e8..53f19b29a580c6459ae1b43dc494eeaf8210a63f 100644 (file)
@@ -1638,6 +1638,41 @@ static int stmt_evaluate_payload(struct eval_ctx *ctx, struct stmt *stmt)
                                 &stmt->payload.val);
 }
 
+static int stmt_evaluate_flow(struct eval_ctx *ctx, struct stmt *stmt)
+{
+       struct expr *key, *set, *setref;
+
+       expr_set_context(&ctx->ectx, NULL, 0);
+       if (expr_evaluate(ctx, &stmt->flow.key) < 0)
+               return -1;
+       if (expr_is_constant(stmt->flow.key))
+               return expr_error(ctx->msgs, stmt->flow.key,
+                                 "Flow key expression can not be constant");
+       if (stmt->flow.key->comment)
+               return expr_error(ctx->msgs, stmt->flow.key,
+                                 "Flow key expression can not contain comments");
+
+       /* Declare an empty set */
+       key = stmt->flow.key;
+       set = set_expr_alloc(&key->location);
+       set->set_flags |= SET_F_EVAL;
+       if (key->timeout)
+               set->set_flags |= SET_F_TIMEOUT;
+
+       setref = implicit_set_declaration(ctx, stmt->flow.table ?: "__ft%d",
+                                         key->dtype, key->len, set);
+
+       stmt->flow.set = setref;
+
+       if (stmt_evaluate(ctx, stmt->flow.stmt) < 0)
+               return -1;
+       if (!(stmt->flow.stmt->flags & STMT_F_STATEFUL))
+               return stmt_binary_error(ctx, stmt->flow.stmt, stmt,
+                                        "Per-flow statement must be stateful");
+
+       return 0;
+}
+
 static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt)
 {
        return stmt_evaluate_arg(ctx, stmt,
@@ -2257,6 +2292,8 @@ int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt)
                return stmt_evaluate_verdict(ctx, stmt);
        case STMT_PAYLOAD:
                return stmt_evaluate_payload(ctx, stmt);
+       case STMT_FLOW:
+               return stmt_evaluate_flow(ctx, stmt);
        case STMT_META:
                return stmt_evaluate_meta(ctx, stmt);
        case STMT_CT:
index ab195e5f3eb8efebaa05b3e979ec33a143986124..a10af5d3a60b5747358febe1b13048e4857f0688 100644 (file)
@@ -16,6 +16,7 @@
 #include <limits.h>
 
 #include <expression.h>
+#include <statement.h>
 #include <datatype.h>
 #include <rule.h>
 #include <gmputil.h>
@@ -852,10 +853,14 @@ struct expr *map_expr_alloc(const struct location *loc, struct expr *arg,
 
 static void set_ref_expr_print(const struct expr *expr)
 {
-       if (expr->set->flags & SET_F_ANONYMOUS)
-               expr_print(expr->set->init);
-       else
+       if (expr->set->flags & SET_F_ANONYMOUS) {
+               if (expr->set->flags & SET_F_EVAL)
+                       printf("table %s", expr->set->handle.set);
+               else
+                       expr_print(expr->set->init);
+       } else {
                printf("@%s", expr->set->handle.set);
+       }
 }
 
 static void set_ref_expr_clone(struct expr *new, const struct expr *expr)
@@ -899,6 +904,11 @@ static void set_elem_expr_print(const struct expr *expr)
        }
        if (expr->comment)
                printf(" comment \"%s\"", expr->comment);
+
+       if (expr->stmt) {
+               printf(" : ");
+               stmt_print(expr->stmt);
+       }
 }
 
 static void set_elem_expr_destroy(struct expr *expr)
index d727cd2ddbbfdb0a8036c017b5c36fd363a5f1ed..b0dcb907c7caff7c52d9e2bc33c6a5fd894564ee 100644 (file)
@@ -1460,6 +1460,12 @@ static int netlink_delinearize_setelem(struct nftnl_set_elem *nlse,
                expr->comment = xmalloc(len);
                memcpy((char *)expr->comment, data, len);
        }
+       if (nftnl_set_elem_is_set(nlse, NFT_SET_ELEM_ATTR_EXPR)) {
+               const struct nftnl_expr *nle;
+
+               nle = nftnl_set_elem_get(nlse, NFT_SET_ELEM_ATTR_EXPR, NULL);
+               expr->stmt = netlink_parse_set_expr(set, nle);
+       }
 
        if (flags & NFT_SET_ELEM_INTERVAL_END) {
                expr->flags |= EXPR_F_INTERVAL_END;
index 1767098d70cdb6c8a8e54905b8a669a11db3c233..de5d66c0db8e2bb3fedad82480a2a80ab2e3d62c 100644 (file)
@@ -35,6 +35,9 @@ struct netlink_parse_ctx {
        struct expr             *registers[1 + NFT_REG32_15 - NFT_REG32_00 + 1];
 };
 
+static int netlink_parse_expr(const struct nftnl_expr *nle,
+                             struct netlink_parse_ctx *ctx);
+
 static void __fmtstring(3, 4) netlink_error(struct netlink_parse_ctx *ctx,
                                            const struct location *loc,
                                            const char *fmt, ...)
@@ -910,8 +913,9 @@ static void netlink_parse_dynset(struct netlink_parse_ctx *ctx,
                                 const struct location *loc,
                                 const struct nftnl_expr *nle)
 {
+       const struct nftnl_expr *dnle;
        struct expr *expr;
-       struct stmt *stmt;
+       struct stmt *stmt, *dstmt;
        struct set *set;
        enum nft_registers sreg;
        const char *name;
@@ -938,10 +942,28 @@ static void netlink_parse_dynset(struct netlink_parse_ctx *ctx,
        expr = set_elem_expr_alloc(&expr->location, expr);
        expr->timeout = nftnl_expr_get_u64(nle, NFTNL_EXPR_DYNSET_TIMEOUT);
 
-       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;
+       dstmt = NULL;
+       dnle = nftnl_expr_get(nle, NFTNL_EXPR_DYNSET_EXPR, NULL);
+       if (dnle != NULL) {
+               if (netlink_parse_expr(dnle, ctx) < 0)
+                       return;
+               if (ctx->stmt == NULL)
+                       return netlink_error(ctx, loc,
+                                            "Could not parse dynset stmt");
+               dstmt = ctx->stmt;
+       }
+
+       if (dstmt != NULL) {
+               stmt = flow_stmt_alloc(loc);
+               stmt->flow.set  = set_ref_expr_alloc(loc, set);
+               stmt->flow.key  = expr;
+               stmt->flow.stmt = dstmt;
+       } 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;
+       }
 
        ctx->stmt = stmt;
 }
@@ -1011,6 +1033,20 @@ static int netlink_parse_rule_expr(struct nftnl_expr *nle, void *arg)
        return 0;
 }
 
+struct stmt *netlink_parse_set_expr(const struct set *set,
+                                   const struct nftnl_expr *nle)
+{
+       struct netlink_parse_ctx ctx, *pctx = &ctx;
+
+       pctx->rule = rule_alloc(&netlink_location, &set->handle);
+       pctx->table = table_lookup(&set->handle);
+       assert(pctx->table != NULL);
+
+       if (netlink_parse_expr(nle, pctx) < 0)
+               return NULL;
+       return pctx->stmt;
+}
+
 struct rule_pp_ctx {
        struct proto_ctx        pctx;
        struct payload_dep_ctx  pdctx;
@@ -1717,6 +1753,9 @@ static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *r
                                      stmt->payload.expr->byteorder);
                        expr_postprocess(&rctx, &stmt->payload.val);
                        break;
+               case STMT_FLOW:
+                       expr_postprocess(&rctx, &stmt->flow.key);
+                       break;
                case STMT_META:
                        if (stmt->meta.expr != NULL)
                                expr_postprocess(&rctx, &stmt->meta.expr);
index fbf6e2c33befcd974bd28dcd187b9a529c889fb7..62bb25c4aa226f5761b0f5de7aaf8c85b5ae7676 100644 (file)
@@ -1017,6 +1017,7 @@ static void netlink_gen_ct_stmt(struct netlink_linearize_ctx *ctx,
 static void netlink_gen_set_stmt(struct netlink_linearize_ctx *ctx,
                                 const struct stmt *stmt)
 {
+       struct set *set = stmt->flow.set->set;
        struct nftnl_expr *nle;
        enum nft_registers sreg_key;
 
@@ -1029,10 +1030,39 @@ static void netlink_gen_set_stmt(struct netlink_linearize_ctx *ctx,
        nftnl_expr_set_u64(nle, NFTNL_EXPR_DYNSET_TIMEOUT,
                           stmt->set.key->timeout);
        nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_OP, stmt->set.op);
-       nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME,
-                          stmt->set.set->set->handle.set);
-       nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID,
-                          stmt->set.set->set->handle.set_id);
+       nftnl_expr_set_str(nle, NFTNL_EXPR_DYNSET_SET_NAME, set->handle.set);
+       nftnl_expr_set_u32(nle, NFTNL_EXPR_DYNSET_SET_ID, set->handle.set_id);
+       nftnl_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_flow_stmt(struct netlink_linearize_ctx *ctx,
+                                 const struct stmt *stmt)
+{
+       struct nftnl_expr *nle;
+       enum nft_registers sreg_key;
+       enum nft_dynset_ops op;
+       struct set *set;
+
+       sreg_key = get_register(ctx, stmt->flow.key);
+       netlink_gen_expr(ctx, stmt->flow.key, sreg_key);
+       release_register(ctx, stmt->flow.key);
+
+       set = stmt->flow.set->set;
+       if (stmt->flow.key->timeout)
+               op = NFT_DYNSET_OP_UPDATE;
+       else
+               op = NFT_DYNSET_OP_ADD;
+
+       nle = alloc_nft_expr("dynset");
+       netlink_put_register(nle, NFT_EXPR_DYNSET_SREG_KEY, sreg_key);
+       if (stmt->flow.key->timeout)
+               nftnl_expr_set_u64(nle, NFT_EXPR_DYNSET_TIMEOUT,
+                                  stmt->flow.key->timeout);
+       nftnl_expr_set_u32(nle, NFT_EXPR_DYNSET_OP, op);
+       nftnl_expr_set_str(nle, NFT_EXPR_DYNSET_SET_NAME, set->handle.set);
+       nftnl_expr_set_u32(nle, NFT_EXPR_DYNSET_SET_ID, set->handle.set_id);
+       nftnl_expr_set(nle, NFT_EXPR_DYNSET_EXPR,
+                      netlink_gen_stmt_stateful(ctx, stmt->flow.stmt), 0);
        nftnl_rule_add_expr(ctx->nlr, nle);
 }
 
@@ -1046,6 +1076,8 @@ static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx,
                return netlink_gen_expr(ctx, stmt->expr, NFT_REG_VERDICT);
        case STMT_VERDICT:
                return netlink_gen_verdict_stmt(ctx, stmt);
+       case STMT_FLOW:
+               return netlink_gen_flow_stmt(ctx, stmt);
        case STMT_PAYLOAD:
                return netlink_gen_payload_stmt(ctx, stmt);
        case STMT_META:
index b8d33861aca1c96150eb810211ade52e0fb02bdb..8a7785b33bc27005181b716af96cee5423fa0095 100644 (file)
@@ -216,6 +216,8 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %token PERFORMANCE             "performance"
 %token SIZE                    "size"
 
+%token FLOW                    "flow"
+
 %token <val> NUM               "number"
 %token <string> STRING         "string"
 %token <string> QUOTED_STRING
@@ -484,6 +486,8 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %type <stmt>                   set_stmt
 %destructor { stmt_free($$); } set_stmt
 %type <val>                    set_stmt_op
+%type <stmt>                   flow_stmt flow_stmt_alloc
+%destructor { stmt_free($$); } flow_stmt flow_stmt_alloc
 
 %type <expr>                   symbol_expr verdict_expr integer_expr
 %destructor { expr_free($$); } symbol_expr verdict_expr integer_expr
@@ -519,6 +523,9 @@ static void location_update(struct location *loc, struct location *rhs, int n)
 %type <expr>                   set_elem_expr set_elem_expr_alloc set_lhs_expr set_rhs_expr
 %destructor { expr_free($$); } set_elem_expr set_elem_expr_alloc set_lhs_expr set_rhs_expr
 
+%type <expr>                   flow_key_expr flow_key_expr_alloc
+%destructor { expr_free($$); } flow_key_expr flow_key_expr_alloc
+
 %type <expr>                   expr initializer_expr
 %destructor { expr_free($$); } expr initializer_expr
 
@@ -1306,6 +1313,7 @@ stmt_list         :       stmt
 
 stmt                   :       verdict_stmt
                        |       match_stmt
+                       |       flow_stmt
                        |       counter_stmt
                        |       payload_stmt
                        |       meta_stmt
@@ -1757,6 +1765,41 @@ set_stmt_op              :       ADD     { $$ = NFT_DYNSET_OP_ADD; }
                        |       UPDATE  { $$ = NFT_DYNSET_OP_UPDATE; }
                        ;
 
+flow_stmt              :       flow_stmt_alloc         flow_stmt_opts  flow_key_expr   stmt
+                       {
+                               $1->flow.key  = $3;
+                               $1->flow.stmt = $4;
+                               $$->location  = @$;
+                               $$ = $1;
+                       }
+                       |       flow_stmt_alloc         flow_key_expr   stmt
+                       {
+                               $1->flow.key  = $2;
+                               $1->flow.stmt = $3;
+                               $$->location  = @$;
+                               $$ = $1;
+                       }
+                       ;
+
+flow_stmt_alloc                :       FLOW
+                       {
+                               $$ = flow_stmt_alloc(&@$);
+                       }
+                       ;
+
+flow_stmt_opts         :       flow_stmt_opt
+                       {
+                               $<stmt>$        = $<stmt>0;
+                       }
+                       |       flow_stmt_opts          flow_stmt_opt
+                       ;
+
+flow_stmt_opt          :       TABLE                   identifier
+                       {
+                               $<stmt>0->flow.table = $2;
+                       }
+                       ;
+
 match_stmt             :       relational_expr
                        {
                                $$ = expr_stmt_alloc(&@$, $1);
@@ -1941,6 +1984,20 @@ set_list_member_expr     :       opt_newline     set_expr        opt_newline
                        }
                        ;
 
+flow_key_expr          :       flow_key_expr_alloc
+                       |       flow_key_expr_alloc             set_elem_options
+                       {
+                               $$->location = @$;
+                               $$ = $1;
+                       }
+                       ;
+
+flow_key_expr_alloc    :       concat_expr
+                       {
+                               $$ = set_elem_expr_alloc(&@1, $1);
+                       }
+                       ;
+
 set_elem_expr          :       set_elem_expr_alloc
                        |       set_elem_expr_alloc             set_elem_options
                        ;
index e8b216ef6ae8a6ee0dc0c6d6df07636e59560dff..b0221145b479ffe1b5c9958656eacb8972e3c4bc 100644 (file)
@@ -285,6 +285,8 @@ addrstring  ({macaddr}|{ip4addr}|{ip6addr})
 "performance"          { return PERFORMANCE; }
 "memory"               { return MEMORY; }
 
+"flow"                 { return FLOW; }
+
 "counter"              { return COUNTER; }
 "packets"              { return PACKETS; }
 "bytes"                        { return BYTES; }
index 414984188fee7149d72e8836b826826d4c2d8d32..988cfeb7a0893836c9bdd2a3316baf61d0a4c83c 100644 (file)
@@ -41,6 +41,8 @@ struct stmt *stmt_alloc(const struct location *loc,
 
 void stmt_free(struct stmt *stmt)
 {
+       if (stmt == NULL)
+               return;
        if (stmt->ops->destroy)
                stmt->ops->destroy(stmt);
        xfree(stmt);
@@ -103,6 +105,37 @@ struct stmt *verdict_stmt_alloc(const struct location *loc, struct expr *expr)
        return stmt;
 }
 
+static void flow_stmt_print(const struct stmt *stmt)
+{
+       printf("flow ");
+       if (stmt->flow.set) {
+               expr_print(stmt->flow.set);
+               printf(" ");
+       }
+       expr_print(stmt->flow.key);
+       printf(" ");
+       stmt_print(stmt->flow.stmt);
+}
+
+static void flow_stmt_destroy(struct stmt *stmt)
+{
+       expr_free(stmt->flow.key);
+       expr_free(stmt->flow.set);
+       stmt_free(stmt->flow.stmt);
+}
+
+static const struct stmt_ops flow_stmt_ops = {
+       .type           = STMT_FLOW,
+       .name           = "flow",
+       .print          = flow_stmt_print,
+       .destroy        = flow_stmt_destroy,
+};
+
+struct stmt *flow_stmt_alloc(const struct location *loc)
+{
+       return stmt_alloc(loc, &flow_stmt_ops);
+}
+
 static void counter_stmt_print(const struct stmt *stmt)
 {
        printf("counter packets %" PRIu64 " bytes %" PRIu64,