This new statement is stateful, so it can be used from flow tables, eg.
# nft add rule filter input \
flow table http { ip saddr timeout 60s quota over 50 mbytes } drop
This basically sets a quota per source IP address of 50 mbytes after
which packets are dropped. Note that the timeout releases the entry if
no traffic is seen from this IP after 60 seconds.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
extern struct stmt *queue_stmt_alloc(const struct location *loc);
+struct quota_stmt {
+ uint64_t bytes;
+ uint32_t flags;
+};
+
+struct stmt *quota_stmt_alloc(const struct location *loc);
+
#include <ct.h>
struct ct_stmt {
enum nft_ct_keys key;
* @STMT_DUP: dup statement
* @STMT_FWD: forward statement
* @STMT_XT: XT statement
+ * @STMT_QUOTA: quota statement
*/
enum stmt_types {
STMT_INVALID,
STMT_DUP,
STMT_FWD,
STMT_XT,
+ STMT_QUOTA,
};
/**
struct masq_stmt masq;
struct redir_stmt redir;
struct queue_stmt queue;
+ struct quota_stmt quota;
struct ct_stmt ct;
struct set_stmt set;
struct dup_stmt dup;
switch (stmt->ops->type) {
case STMT_COUNTER:
case STMT_LIMIT:
+ case STMT_QUOTA:
return 0;
case STMT_EXPRESSION:
return stmt_evaluate_expr(ctx, stmt);
ctx->stmt = stmt;
}
+static void netlink_parse_quota(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = quota_stmt_alloc(loc);
+ stmt->quota.bytes = nftnl_expr_get_u64(nle, NFTNL_EXPR_QUOTA_BYTES);
+ stmt->quota.flags = nftnl_expr_get_u32(nle, NFTNL_EXPR_QUOTA_FLAGS);
+
+ ctx->stmt = stmt;
+}
+
static void netlink_parse_reject(struct netlink_parse_ctx *ctx,
const struct location *loc,
const struct nftnl_expr *expr)
{ .name = "fwd", .parse = netlink_parse_fwd },
{ .name = "target", .parse = netlink_parse_target },
{ .name = "match", .parse = netlink_parse_match },
+ { .name = "quota", .parse = netlink_parse_quota },
};
static int netlink_parse_expr(const struct nftnl_expr *nle,
return nle;
}
+static struct nftnl_expr *
+netlink_gen_quota_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nftnl_expr *nle;
+
+ nle = alloc_nft_expr("quota");
+ nftnl_expr_set_u64(nle, NFTNL_EXPR_QUOTA_BYTES, stmt->quota.bytes);
+ nftnl_expr_set_u32(nle, NFTNL_EXPR_QUOTA_FLAGS, stmt->quota.flags);
+
+ return nle;
+}
+
static struct nftnl_expr *
netlink_gen_stmt_stateful(struct netlink_linearize_ctx *ctx,
const struct stmt *stmt)
return netlink_gen_counter_stmt(ctx, stmt);
case STMT_LIMIT:
return netlink_gen_limit_stmt(ctx, stmt);
+ case STMT_QUOTA:
+ return netlink_gen_quota_stmt(ctx, stmt);
default:
BUG("unknown stateful statement type %s\n", stmt->ops->name);
}
return netlink_gen_fwd_stmt(ctx, stmt);
case STMT_COUNTER:
case STMT_LIMIT:
+ case STMT_QUOTA:
nle = netlink_gen_stmt_stateful(ctx, stmt);
nftnl_rule_add_expr(ctx->nlr, nle);
break;
%token OVER "over"
%token UNTIL "until"
+%token QUOTA "quota"
+
%token NANOSECOND "nanosecond"
%token MICROSECOND "microsecond"
%token MILLISECOND "millisecond"
%destructor { handle_free(&$$); } set_spec set_identifier
%type <val> family_spec family_spec_explicit chain_policy prio_spec
-%type <string> dev_spec
-%destructor { xfree($$); } dev_spec
+%type <string> dev_spec quota_unit
+%destructor { xfree($$); } dev_spec quota_unit
%type <table> table_block_alloc table_block
%destructor { close_scope(state); table_free($$); } table_block_alloc
%type <stmt> log_stmt log_stmt_alloc
%destructor { stmt_free($$); } log_stmt log_stmt_alloc
%type <val> level_type
-%type <stmt> limit_stmt
-%destructor { stmt_free($$); } limit_stmt
-%type <val> limit_burst limit_mode time_unit
+%type <stmt> limit_stmt quota_stmt
+%destructor { stmt_free($$); } limit_stmt quota_stmt
+%type <val> limit_burst limit_mode time_unit quota_mode
%type <stmt> reject_stmt reject_stmt_alloc
%destructor { stmt_free($$); } reject_stmt reject_stmt_alloc
%type <stmt> nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc
| meta_stmt
| log_stmt
| limit_stmt
+ | quota_stmt
| reject_stmt
| nat_stmt
| queue_stmt
}
;
+quota_mode : OVER { $$ = NFT_QUOTA_F_INV; }
+ | UNTIL { $$ = 0; }
+ | /* empty */ { $$ = 0; }
+ ;
+
+quota_unit : BYTES { $$ = xstrdup("bytes"); }
+ | STRING { $$ = $1; }
+ ;
+
+quota_stmt : QUOTA quota_mode NUM quota_unit
+ {
+ struct error_record *erec;
+ uint64_t rate;
+
+ erec = data_unit_parse(&@$, $4, &rate);
+ if (erec != NULL) {
+ erec_queue(erec, state->msgs);
+ YYERROR;
+ }
+ $$ = quota_stmt_alloc(&@$);
+ $$->quota.bytes = $3 * rate;
+ $$->quota.flags = $2;
+ }
+ ;
+
limit_mode : OVER { $$ = NFT_LIMIT_F_INV; }
| UNTIL { $$ = 0; }
| /* empty */ { $$ = 0; }
"until" { return UNTIL; }
"over" { return OVER; }
+"quota" { return QUOTA; }
+
"nanosecond" { return NANOSECOND; }
"microsecond" { return MICROSECOND; }
"millisecond" { return MILLISECOND; }
return stmt_alloc(loc, &queue_stmt_ops);
}
+static void quota_stmt_print(const struct stmt *stmt)
+{
+ bool inv = stmt->quota.flags & NFT_QUOTA_F_INV;
+ const char *data_unit;
+ uint64_t bytes;
+
+ data_unit = get_rate(stmt->quota.bytes, &bytes);
+ printf("quota %s%"PRIu64" %s",
+ inv ? "over " : "", bytes, data_unit);
+}
+
+static const struct stmt_ops quota_stmt_ops = {
+ .type = STMT_QUOTA,
+ .name = "quota",
+ .print = quota_stmt_print,
+};
+
+struct stmt *quota_stmt_alloc(const struct location *loc)
+{
+ struct stmt *stmt;
+
+ stmt = stmt_alloc(loc, "a_stmt_ops);
+ stmt->flags |= STMT_F_STATEFUL;
+ return stmt;
+}
+
static void reject_stmt_print(const struct stmt *stmt)
{
printf("reject");
--- /dev/null
+:output;type filter hook output priority 0
+:ingress;type filter hook ingress device lo priority 0
+
+*ip;test-ip4;output
+*ip6;test-ip6;output
+*inet;test-inet;output
+*arp;test-arp;output
+*bridge;test-bridge;output
+*netdev;test-netdev;ingress
+
+quota 1025 bytes;ok
+quota 1 kbytes;ok
+quota 2 kbytes;ok
+quota 1025 kbytes;ok
+quota 1023 mbytes;ok
+quota 10230 mbytes;ok
+quota 1023000 mbytes;ok
+
+quota over 1 kbytes;ok
+quota over 2 kbytes;ok
+quota over 1025 kbytes;ok
+quota over 1023 mbytes;ok
+quota over 10230 mbytes;ok
+quota over 1023000 mbytes;ok
--- /dev/null
+# quota 1025 bytes
+ip test-ip4 output
+ [ quota bytes 1025 flags 0 ]
+
+# quota 1 kbytes
+ip test-ip4 output
+ [ quota bytes 1024 flags 0 ]
+
+# quota 2 kbytes
+ip test-ip4 output
+ [ quota bytes 2048 flags 0 ]
+
+# quota 1025 kbytes
+ip test-ip4 output
+ [ quota bytes 1049600 flags 0 ]
+
+# quota 1023 mbytes
+ip test-ip4 output
+ [ quota bytes 1072693248 flags 0 ]
+
+# quota 10230 mbytes
+ip test-ip4 output
+ [ quota bytes 10726932480 flags 0 ]
+
+# quota 1023000 mbytes
+ip test-ip4 output
+ [ quota bytes 1072693248000 flags 0 ]
+
+# quota over 1 kbytes
+ip test-ip4 output
+ [ quota bytes 1024 flags 1 ]
+
+# quota over 2 kbytes
+ip test-ip4 output
+ [ quota bytes 2048 flags 1 ]
+
+# quota over 1025 kbytes
+ip test-ip4 output
+ [ quota bytes 1049600 flags 1 ]
+
+# quota over 1023 mbytes
+ip test-ip4 output
+ [ quota bytes 1072693248 flags 1 ]
+
+# quota over 10230 mbytes
+ip test-ip4 output
+ [ quota bytes 10726932480 flags 1 ]
+
+# quota over 1023000 mbytes
+ip test-ip4 output
+ [ quota bytes 1072693248000 flags 1 ]
+