diff mbox series

[nftables] src: add last statement

Message ID 20210705191848.15930-1-pablo@netfilter.org
State Under Review
Delegated to: Pablo Neira
Headers show
Series [nftables] src: add last statement | expand

Commit Message

Pablo Neira Ayuso July 5, 2021, 7:18 p.m. UTC
This new statement allows you to know how long ago there was a matching
packet.

 # nft list ruleset
 table ip x {
        chain y {
		[...]
                ip protocol icmp last used 49m54s884ms counter packets 1 bytes 64
	}
 }

if the statement never sees a packet, then the listing says:

                ip protocol icmp last used never counter packets 0 bytes 0

Add tests/py in this patch too.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 include/parser.h            |  1 +
 include/statement.h         | 10 ++++++++++
 src/evaluate.c              |  1 +
 src/netlink_delinearize.c   | 14 ++++++++++++++
 src/netlink_linearize.c     | 14 ++++++++++++++
 src/parser_bison.y          | 25 +++++++++++++++++++++++--
 src/scanner.l               |  9 ++++++++-
 src/statement.c             | 30 ++++++++++++++++++++++++++++++
 tests/py/any/last.t         | 13 +++++++++++++
 tests/py/any/last.t.payload |  8 ++++++++
 10 files changed, 122 insertions(+), 3 deletions(-)
 create mode 100644 tests/py/any/last.t
 create mode 100644 tests/py/any/last.t.payload
diff mbox series

Patch

diff --git a/include/parser.h b/include/parser.h
index e8635b4c0feb..39128ae85d1a 100644
--- a/include/parser.h
+++ b/include/parser.h
@@ -36,6 +36,7 @@  enum startcond_type {
 	PARSER_SC_ETH,
 	PARSER_SC_IP,
 	PARSER_SC_IP6,
+	PARSER_SC_LAST,
 	PARSER_SC_LIMIT,
 	PARSER_SC_QUOTA,
 	PARSER_SC_SCTP,
diff --git a/include/statement.h b/include/statement.h
index 06221040fa0c..ce0aa9a63825 100644
--- a/include/statement.h
+++ b/include/statement.h
@@ -47,6 +47,13 @@  struct counter_stmt {
 
 extern struct stmt *counter_stmt_alloc(const struct location *loc);
 
+struct last_stmt {
+	uint64_t		used;
+	uint32_t		set;
+};
+
+extern struct stmt *last_stmt_alloc(const struct location *loc);
+
 struct exthdr_stmt {
 	struct expr			*expr;
 	struct expr			*val;
@@ -297,6 +304,7 @@  extern struct stmt *xt_stmt_alloc(const struct location *loc);
  * @STMT_MAP:		map statement
  * @STMT_SYNPROXY:	synproxy statement
  * @STMT_CHAIN:		chain statement
+ * @STMT_LAST:		last statement
  */
 enum stmt_types {
 	STMT_INVALID,
@@ -326,6 +334,7 @@  enum stmt_types {
 	STMT_MAP,
 	STMT_SYNPROXY,
 	STMT_CHAIN,
+	STMT_LAST,
 };
 
 /**
@@ -375,6 +384,7 @@  struct stmt {
 		struct counter_stmt	counter;
 		struct payload_stmt	payload;
 		struct meta_stmt	meta;
+		struct last_stmt	last;
 		struct log_stmt		log;
 		struct limit_stmt	limit;
 		struct reject_stmt	reject;
diff --git a/src/evaluate.c b/src/evaluate.c
index dbc773d164ed..585182d3599f 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -3707,6 +3707,7 @@  int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt)
 	switch (stmt->ops->type) {
 	case STMT_CONNLIMIT:
 	case STMT_COUNTER:
+	case STMT_LAST:
 	case STMT_LIMIT:
 	case STMT_QUOTA:
 	case STMT_NOTRACK:
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 71b69f622a76..744af3b064d7 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -923,6 +923,19 @@  static void netlink_parse_counter(struct netlink_parse_ctx *ctx,
 	ctx->stmt = stmt;
 }
 
+static void netlink_parse_last(struct netlink_parse_ctx *ctx,
+			       const struct location *loc,
+			       const struct nftnl_expr *nle)
+{
+	struct stmt *stmt;
+
+	stmt = last_stmt_alloc(loc);
+	stmt->last.used = nftnl_expr_get_u64(nle, NFTNL_EXPR_LAST_MSECS);
+	stmt->last.set = nftnl_expr_get_u32(nle, NFTNL_EXPR_LAST_SET);
+
+	ctx->stmt = stmt;
+}
+
 static void netlink_parse_log(struct netlink_parse_ctx *ctx,
 			      const struct location *loc,
 			      const struct nftnl_expr *nle)
@@ -1707,6 +1720,7 @@  static const struct expr_handler netlink_parsers[] = {
 	{ .name = "ct",		.parse = netlink_parse_ct },
 	{ .name = "connlimit",	.parse = netlink_parse_connlimit },
 	{ .name = "counter",	.parse = netlink_parse_counter },
+	{ .name = "last",	.parse = netlink_parse_last },
 	{ .name = "log",	.parse = netlink_parse_log },
 	{ .name = "limit",	.parse = netlink_parse_limit },
 	{ .name = "range",	.parse = netlink_parse_range },
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index b1f3feeeb4b7..e2122602dd33 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -924,6 +924,17 @@  static struct nftnl_expr *netlink_gen_quota_stmt(const struct stmt *stmt)
 	return nle;
 }
 
+static struct nftnl_expr *netlink_gen_last_stmt(const struct stmt *stmt)
+{
+	struct nftnl_expr *nle;
+
+	nle = alloc_nft_expr("last");
+	nftnl_expr_set_u32(nle, NFTNL_EXPR_LAST_SET, stmt->last.set);
+	nftnl_expr_set_u64(nle, NFTNL_EXPR_LAST_MSECS, stmt->last.used);
+
+	return nle;
+}
+
 struct nftnl_expr *netlink_gen_stmt_stateful(const struct stmt *stmt)
 {
 	switch (stmt->ops->type) {
@@ -935,6 +946,8 @@  struct nftnl_expr *netlink_gen_stmt_stateful(const struct stmt *stmt)
 		return netlink_gen_limit_stmt(stmt);
 	case STMT_QUOTA:
 		return netlink_gen_quota_stmt(stmt);
+	case STMT_LAST:
+		return netlink_gen_last_stmt(stmt);
 	default:
 		BUG("unknown stateful statement type %s\n", stmt->ops->name);
 	}
@@ -1581,6 +1594,7 @@  static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx,
 	case STMT_COUNTER:
 	case STMT_LIMIT:
 	case STMT_QUOTA:
+	case STMT_LAST:
 		nle = netlink_gen_stmt_stateful(stmt);
 		nft_rule_add_expr(ctx, nle, &stmt->location);
 		break;
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 872d7cdb92ad..c1fcedd7ecce 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -525,6 +525,9 @@  int nft_lex(void *, void *, void *);
 %token BYTES			"bytes"
 %token AVGPKT			"avgpkt"
 
+%token LAST			"last"
+%token NEVER			"never"
+
 %token COUNTERS			"counters"
 %token QUOTAS			"quotas"
 %token LIMITS			"limits"
@@ -676,8 +679,8 @@  int nft_lex(void *, void *, void *);
 %destructor { stmt_list_free($$); xfree($$); } stmt_list stateful_stmt_list set_elem_stmt_list
 %type <stmt>			stmt match_stmt verdict_stmt set_elem_stmt
 %destructor { stmt_free($$); }	stmt match_stmt verdict_stmt set_elem_stmt
-%type <stmt>			counter_stmt counter_stmt_alloc stateful_stmt
-%destructor { stmt_free($$); }	counter_stmt counter_stmt_alloc stateful_stmt
+%type <stmt>			counter_stmt counter_stmt_alloc stateful_stmt last_stmt
+%destructor { stmt_free($$); }	counter_stmt counter_stmt_alloc stateful_stmt last_stmt
 %type <stmt>			payload_stmt
 %destructor { stmt_free($$); }	payload_stmt
 %type <stmt>			ct_stmt
@@ -915,6 +918,7 @@  opt_newline		:	NEWLINE
 close_scope_arp		: { scanner_pop_start_cond(nft->scanner, PARSER_SC_ARP); };
 close_scope_ct		: { scanner_pop_start_cond(nft->scanner, PARSER_SC_CT); };
 close_scope_counter	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_COUNTER); };
+close_scope_last	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_LAST); };
 close_scope_eth		: { scanner_pop_start_cond(nft->scanner, PARSER_SC_ETH); };
 close_scope_fib		: { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_FIB); };
 close_scope_hash	: { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); };
@@ -2790,6 +2794,7 @@  stateful_stmt		:	counter_stmt	close_scope_counter
 			|	limit_stmt
 			|	quota_stmt
 			|	connlimit_stmt
+			|	last_stmt	close_scope_last
 			;
 
 stmt			:	verdict_stmt
@@ -2915,6 +2920,22 @@  counter_arg		:	PACKETS			NUM
 			}
 			;
 
+last_stmt		:	LAST
+			{
+				$$ = last_stmt_alloc(&@$);
+			}
+			|	LAST USED	NEVER
+			{
+				$$ = last_stmt_alloc(&@$);
+			}
+			|	LAST USED	time_spec
+			{
+				$$ = last_stmt_alloc(&@$);
+				$$->last.used = $3;
+				$$->last.set = true;
+			}
+			;
+
 log_stmt		:	log_stmt_alloc
 			|	log_stmt_alloc		log_args
 			;
diff --git a/src/scanner.l b/src/scanner.l
index 6cc7778dd85e..1fc8143b4044 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -202,6 +202,7 @@  addrstring	({macaddr}|{ip4addr}|{ip6addr})
 %s SCANSTATE_ETH
 %s SCANSTATE_IP
 %s SCANSTATE_IP6
+%s SCANSTATE_LAST
 %s SCANSTATE_LIMIT
 %s SCANSTATE_QUOTA
 %s SCANSTATE_SCTP
@@ -362,6 +363,11 @@  addrstring	({macaddr}|{ip4addr}|{ip6addr})
 <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets"		{ return PACKETS; }
 <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes"	{ return BYTES; }
 
+"last"			{ scanner_push_start_cond(yyscanner, SCANSTATE_LAST); return LAST; }
+<SCANSTATE_LAST>{
+	"never"		{ return NEVER; }
+}
+
 "counters"		{ return COUNTERS; }
 "quotas"		{ return QUOTAS; }
 
@@ -389,10 +395,11 @@  addrstring	({macaddr}|{ip4addr}|{ip6addr})
 
 "quota"			{ scanner_push_start_cond(yyscanner, SCANSTATE_QUOTA); return QUOTA; }
 <SCANSTATE_QUOTA>{
-	"used"		{ return USED; }
 	"until"		{ return UNTIL; }
 }
 
+<SCANSTATE_QUOTA,SCANSTATE_LAST>"used"		{ return USED; }
+
 "second"		{ return SECOND; }
 "minute"		{ return MINUTE; }
 "hour"			{ return HOUR; }
diff --git a/src/statement.c b/src/statement.c
index dfd275104c59..b3e53451f5c7 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -248,6 +248,36 @@  struct stmt *counter_stmt_alloc(const struct location *loc)
 	return stmt;
 }
 
+static void last_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
+{
+	nft_print(octx, "last");
+
+	if (nft_output_stateless(octx))
+		return;
+
+	nft_print(octx, " used ");
+
+	if (stmt->last.set)
+		time_print(stmt->last.used, octx);
+	else
+		nft_print(octx, "never");
+}
+
+static const struct stmt_ops last_stmt_ops = {
+	.type		= STMT_LAST,
+	.name		= "last",
+	.print		= last_stmt_print,
+};
+
+struct stmt *last_stmt_alloc(const struct location *loc)
+{
+	struct stmt *stmt;
+
+	stmt = stmt_alloc(loc, &last_stmt_ops);
+	stmt->flags |= STMT_F_STATEFUL;
+	return stmt;
+}
+
 static const char *objref_type[NFT_OBJECT_MAX + 1] = {
 	[NFT_OBJECT_COUNTER]	= "counter",
 	[NFT_OBJECT_QUOTA]	= "quota",
diff --git a/tests/py/any/last.t b/tests/py/any/last.t
new file mode 100644
index 000000000000..5c530461479f
--- /dev/null
+++ b/tests/py/any/last.t
@@ -0,0 +1,13 @@ 
+:input;type filter hook input priority 0
+:ingress;type filter hook ingress device lo priority 0
+
+*ip;test-ip4;input
+*ip6;test-ip6;input
+*inet;test-inet;input
+*arp;test-arp;input
+*bridge;test-bridge;input
+*netdev;test-netdev;ingress
+
+last;ok
+last used 300s;ok;last
+last used foo;fail
diff --git a/tests/py/any/last.t.payload b/tests/py/any/last.t.payload
new file mode 100644
index 000000000000..ed47d0f355eb
--- /dev/null
+++ b/tests/py/any/last.t.payload
@@ -0,0 +1,8 @@ 
+# last
+ip
+  [ last never ]
+
+# last used 300s
+ip
+  [ last 300000 ]
+