summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Sutter <phil@nwl.cc>2023-06-15 15:24:28 +0200
committerPhil Sutter <phil@nwl.cc>2023-07-13 16:57:56 +0200
commit83e0f4402fb731633975b54ee043820d3cc7ed8e (patch)
tree36fa5f53e74d9e9c457305accd6196140709e4f1
parente2431ab955fe453b5fd25a3ab3090fbf4bf3e653 (diff)
Implement 'reset {set,map,element}' commands
All these are used to reset state in set/map elements, i.e. reset the timeout or zero quota and counter values. While 'reset element' expects a (list of) elements to be specified which should be reset, 'reset set/map' will reset all elements in the given set/map. Signed-off-by: Phil Sutter <phil@nwl.cc>
-rw-r--r--doc/libnftables-json.adoc2
-rw-r--r--doc/nft.txt13
-rw-r--r--include/linux/netfilter/nf_tables.h2
-rw-r--r--include/mnl.h6
-rw-r--r--include/netlink.h5
-rw-r--r--src/cache.c9
-rw-r--r--src/evaluate.c5
-rw-r--r--src/mnl.c22
-rw-r--r--src/netlink.c8
-rw-r--r--src/parser_bison.y12
-rw-r--r--src/parser_json.c4
-rw-r--r--src/rule.c15
-rwxr-xr-xtests/shell/testcases/sets/reset_command_082
13 files changed, 162 insertions, 23 deletions
diff --git a/doc/libnftables-json.adoc b/doc/libnftables-json.adoc
index f9288487..3e6e1db3 100644
--- a/doc/libnftables-json.adoc
+++ b/doc/libnftables-json.adoc
@@ -175,7 +175,7 @@ kind, optionally filtered by *family* and for some, also *table*.
____
*{ "reset":* 'RESET_OBJECT' *}*
-'RESET_OBJECT' := 'COUNTER' | 'COUNTERS' | 'QUOTA' | 'QUOTAS' | 'RULE' | 'RULES'
+'RESET_OBJECT' := 'COUNTER' | 'COUNTERS' | 'QUOTA' | 'QUOTAS' | 'RULE' | 'RULES' | 'SET' | 'MAP' | 'ELEMENT'
____
Reset state in suitable objects, i.e. zero their internal counter.
diff --git a/doc/nft.txt b/doc/nft.txt
index 19ba55d9..fe123d04 100644
--- a/doc/nft.txt
+++ b/doc/nft.txt
@@ -570,7 +570,7 @@ section describes nft set syntax in more detail.
[verse]
*add set* ['family'] 'table' 'set' *{ type* 'type' | *typeof* 'expression' *;* [*flags* 'flags' *;*] [*timeout* 'timeout' *;*] [*gc-interval* 'gc-interval' *;*] [*elements = {* 'element'[*,* ...] *} ;*] [*size* 'size' *;*] [*comment* 'comment' *;*'] [*policy* 'policy' *;*] [*auto-merge ;*] *}*
-{*delete* | *destroy* | *list* | *flush*} *set* ['family'] 'table' 'set'
+{*delete* | *destroy* | *list* | *flush* | *reset* } *set* ['family'] 'table' 'set'
*list sets* ['family']
*delete set* ['family'] 'table' *handle* 'handle'
{*add* | *delete* | *destroy* } *element* ['family'] 'table' 'set' *{* 'element'[*,* ...] *}*
@@ -585,6 +585,7 @@ be tuned with the flags that can be specified at set creation time.
*destroy*:: Delete the specified set, it does not fail if it does not exist.
*list*:: Display the elements in the specified set.
*flush*:: Remove all elements from the specified set.
+*reset*:: Reset timeout and other state in all contained elements.
.Set specifications
[options="header"]
@@ -623,7 +624,7 @@ MAPS
-----
[verse]
*add map* ['family'] 'table' 'map' *{ type* 'type' | *typeof* 'expression' [*flags* 'flags' *;*] [*elements = {* 'element'[*,* ...] *} ;*] [*size* 'size' *;*] [*comment* 'comment' *;*'] [*policy* 'policy' *;*] *}*
-{*delete* | *destroy* | *list* | *flush*} *map* ['family'] 'table' 'map'
+{*delete* | *destroy* | *list* | *flush* | *reset* } *map* ['family'] 'table' 'map'
*list maps* ['family']
Maps store data based on some specific key used as input. They are uniquely identified by a user-defined name and attached to tables.
@@ -634,8 +635,7 @@ Maps store data based on some specific key used as input. They are uniquely iden
*destroy*:: Delete the specified map, it does not fail if it does not exist.
*list*:: Display the elements in the specified map.
*flush*:: Remove all elements from the specified map.
-*add element*:: Comma-separated list of elements to add into the specified map.
-*delete element*:: Comma-separated list of element keys to delete from the specified map.
+*reset*:: Reset timeout and other state in all contained elements.
.Map specifications
[options="header"]
@@ -682,7 +682,7 @@ ELEMENTS
--------
[verse]
____
-{*add* | *create* | *delete* | *destroy* | *get* } *element* ['family'] 'table' 'set' *{* 'ELEMENT'[*,* ...] *}*
+{*add* | *create* | *delete* | *destroy* | *get* | *reset* } *element* ['family'] 'table' 'set' *{* 'ELEMENT'[*,* ...] *}*
'ELEMENT' := 'key_expression' 'OPTIONS' [*:* 'value_expression']
'OPTIONS' := [*timeout* 'TIMESPEC'] [*expires* 'TIMESPEC'] [*comment* 'string']
@@ -702,6 +702,9 @@ listed elements may already exist.
be non-trivial in very large and/or interval sets. In the latter case, the
containing interval is returned instead of just the element itself.
+*reset* command resets timeout or other state attached to the given
+element(s).
+
.Element options
[options="header"]
|=================
diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h
index 673e0507..c62e6ac5 100644
--- a/include/linux/netfilter/nf_tables.h
+++ b/include/linux/netfilter/nf_tables.h
@@ -105,6 +105,7 @@ enum nft_verdicts {
* @NFT_MSG_DESTROYSETELEM: destroy a set element (enum nft_set_elem_attributes)
* @NFT_MSG_DESTROYOBJ: destroy a stateful object (enum nft_object_attributes)
* @NFT_MSG_DESTROYFLOWTABLE: destroy flow table (enum nft_flowtable_attributes)
+ * @NFT_MSG_GETSETELEM_RESET: get set elements and reset attached stateful expressio ns (enum nft_set_elem_attributes)
*/
enum nf_tables_msg_types {
NFT_MSG_NEWTABLE,
@@ -140,6 +141,7 @@ enum nf_tables_msg_types {
NFT_MSG_DESTROYSETELEM,
NFT_MSG_DESTROYOBJ,
NFT_MSG_DESTROYFLOWTABLE,
+ NFT_MSG_GETSETELEM_RESET,
NFT_MSG_MAX,
};
diff --git a/include/mnl.h b/include/mnl.h
index c0676691..cd5a2053 100644
--- a/include/mnl.h
+++ b/include/mnl.h
@@ -68,9 +68,11 @@ int mnl_nft_setelem_add(struct netlink_ctx *ctx, struct cmd *cmd,
int mnl_nft_setelem_del(struct netlink_ctx *ctx, struct cmd *cmd,
const struct handle *h, const struct expr *init);
int mnl_nft_setelem_flush(struct netlink_ctx *ctx, const struct cmd *cmd);
-int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls);
+int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls,
+ bool reset);
struct nftnl_set *mnl_nft_setelem_get_one(struct netlink_ctx *ctx,
- struct nftnl_set *nls);
+ struct nftnl_set *nls,
+ bool reset);
struct nftnl_obj_list *mnl_nft_obj_dump(struct netlink_ctx *ctx, int family,
const char *table,
diff --git a/include/netlink.h b/include/netlink.h
index d52434c7..6766d7e8 100644
--- a/include/netlink.h
+++ b/include/netlink.h
@@ -165,10 +165,11 @@ extern struct stmt *netlink_parse_set_expr(const struct set *set,
const struct nftnl_expr *nle);
extern int netlink_list_setelems(struct netlink_ctx *ctx,
- const struct handle *h, struct set *set);
+ const struct handle *h, struct set *set,
+ bool reset);
extern int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h,
const struct location *loc, struct set *cache_set,
- struct set *set, struct expr *init);
+ struct set *set, struct expr *init, bool reset);
extern int netlink_delinearize_setelem(struct nftnl_set_elem *nlse,
struct set *set,
struct nft_cache *cache);
diff --git a/src/cache.c b/src/cache.c
index d908ae0a..5cab2622 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -282,6 +282,11 @@ static unsigned int evaluate_cache_reset(struct cmd *cmd, unsigned int flags,
flags |= NFT_CACHE_SET | NFT_CACHE_FLOWTABLE |
NFT_CACHE_OBJECT | NFT_CACHE_CHAIN;
break;
+ case CMD_OBJ_ELEMENTS:
+ case CMD_OBJ_SET:
+ case CMD_OBJ_MAP:
+ flags |= NFT_CACHE_SET;
+ break;
default:
flags |= NFT_CACHE_TABLE;
break;
@@ -1069,7 +1074,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags,
continue;
ret = netlink_list_setelems(ctx, &set->handle,
- set);
+ set, false);
if (ret < 0)
goto cache_fails;
}
@@ -1082,7 +1087,7 @@ static int cache_init_objects(struct netlink_ctx *ctx, unsigned int flags,
continue;
ret = netlink_list_setelems(ctx, &set->handle,
- set);
+ set, false);
if (ret < 0)
goto cache_fails;
}
diff --git a/src/evaluate.c b/src/evaluate.c
index 3dc2be0d..33e4ac93 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -5470,6 +5470,11 @@ static int cmd_evaluate_reset(struct eval_ctx *ctx, struct cmd *cmd)
return table_not_found(ctx);
return 0;
+ case CMD_OBJ_ELEMENTS:
+ return setelem_evaluate(ctx, cmd);
+ case CMD_OBJ_SET:
+ case CMD_OBJ_MAP:
+ return cmd_evaluate_list(ctx, cmd);
default:
BUG("invalid command object type %u\n", cmd->obj);
}
diff --git a/src/mnl.c b/src/mnl.c
index 91775c41..9406fc48 100644
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -1870,14 +1870,21 @@ int mnl_nft_setelem_del(struct netlink_ctx *ctx, struct cmd *cmd,
}
struct nftnl_set *mnl_nft_setelem_get_one(struct netlink_ctx *ctx,
- struct nftnl_set *nls_in)
+ struct nftnl_set *nls_in,
+ bool reset)
{
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nftnl_set *nls_out;
struct nlmsghdr *nlh;
+ int msg_type;
int err;
- nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM,
+ if (reset)
+ msg_type = NFT_MSG_GETSETELEM_RESET;
+ else
+ msg_type = NFT_MSG_GETSETELEM;
+
+ nlh = nftnl_nlmsg_build_hdr(buf, msg_type,
nftnl_set_get_u32(nls_in, NFTNL_SET_FAMILY),
NLM_F_ACK, ctx->seqnum);
nftnl_set_elems_nlmsg_build_payload(nlh, nls_in);
@@ -1900,12 +1907,19 @@ struct nftnl_set *mnl_nft_setelem_get_one(struct netlink_ctx *ctx,
return nls_out;
}
-int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls)
+int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls,
+ bool reset)
{
char buf[MNL_SOCKET_BUFFER_SIZE];
struct nlmsghdr *nlh;
+ int msg_type;
+
+ if (reset)
+ msg_type = NFT_MSG_GETSETELEM_RESET;
+ else
+ msg_type = NFT_MSG_GETSETELEM;
- nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM,
+ nlh = nftnl_nlmsg_build_hdr(buf, msg_type,
nftnl_set_get_u32(nls, NFTNL_SET_FAMILY),
NLM_F_DUMP, ctx->seqnum);
nftnl_set_elems_nlmsg_build_payload(nlh, nls);
diff --git a/src/netlink.c b/src/netlink.c
index 3352ad0a..ed61cd89 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -1515,7 +1515,7 @@ static int list_setelements(struct nftnl_set *s, struct netlink_ctx *ctx)
}
int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h,
- struct set *set)
+ struct set *set, bool reset)
{
struct nftnl_set *nls;
int err;
@@ -1530,7 +1530,7 @@ int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h,
if (h->handle.id)
nftnl_set_set_u64(nls, NFTNL_SET_HANDLE, h->handle.id);
- err = mnl_nft_setelem_get(ctx, nls);
+ err = mnl_nft_setelem_get(ctx, nls, reset);
if (err < 0) {
nftnl_set_free(nls);
if (errno == EINTR)
@@ -1558,7 +1558,7 @@ int netlink_list_setelems(struct netlink_ctx *ctx, const struct handle *h,
int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h,
const struct location *loc, struct set *cache_set,
- struct set *set, struct expr *init)
+ struct set *set, struct expr *init, bool reset)
{
struct nftnl_set *nls, *nls_out = NULL;
int err = 0;
@@ -1577,7 +1577,7 @@ int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h,
netlink_dump_set(nls, ctx);
- nls_out = mnl_nft_setelem_get_one(ctx, nls);
+ nls_out = mnl_nft_setelem_get_one(ctx, nls, reset);
if (!nls_out) {
nftnl_set_free(nls);
return -1;
diff --git a/src/parser_bison.y b/src/parser_bison.y
index beb277b6..553ddf97 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -1742,6 +1742,18 @@ reset_cmd : COUNTERS ruleset_spec
{
$$ = cmd_alloc(CMD_RESET, CMD_OBJ_RULE, &$2, &@$, NULL);
}
+ | ELEMENT set_spec set_block_expr
+ {
+ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_ELEMENTS, &$2, &@$, $3);
+ }
+ | SET set_or_id_spec
+ {
+ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_SET, &$2, &@$, NULL);
+ }
+ | MAP set_or_id_spec
+ {
+ $$ = cmd_alloc(CMD_RESET, CMD_OBJ_MAP, &$2, &@$, NULL);
+ }
;
flush_cmd : TABLE table_spec
diff --git a/src/parser_json.c b/src/parser_json.c
index 55c6b8c7..92cffee9 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -3169,6 +3169,7 @@ static struct cmd *json_parse_cmd_add_set(struct json_ctx *ctx, json_t *root,
case CMD_DESTROY:
case CMD_LIST:
case CMD_FLUSH:
+ case CMD_RESET:
return cmd_alloc(op, obj, &h, int_loc, NULL);
default:
break;
@@ -3918,6 +3919,9 @@ static struct cmd *json_parse_cmd_reset(struct json_ctx *ctx,
{ "quotas", CMD_OBJ_QUOTAS, json_parse_cmd_list_multiple },
{ "rule", CMD_OBJ_RULE, json_parse_cmd_reset_rule },
{ "rules", CMD_OBJ_RULES, json_parse_cmd_reset_rule },
+ { "element", CMD_OBJ_ELEMENTS, json_parse_cmd_add_element },
+ { "set", CMD_OBJ_SET, json_parse_cmd_add_set },
+ { "map", CMD_OBJ_MAP, json_parse_cmd_add_set },
};
unsigned int i;
json_t *tmp;
diff --git a/src/rule.c b/src/rule.c
index 18a566f9..533161d3 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -2381,7 +2381,7 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd)
return 0;
}
-static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd)
+static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd, bool reset)
{
struct set *set, *new_set;
struct expr *init;
@@ -2399,7 +2399,7 @@ static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd)
/* Fetch from kernel the elements that have been requested .*/
err = netlink_get_setelem(ctx, &cmd->handle, &cmd->location,
- cmd->elem.set, new_set, init);
+ cmd->elem.set, new_set, init, reset);
if (err >= 0)
__do_list_set(ctx, cmd, new_set);
@@ -2415,7 +2415,7 @@ static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd)
{
switch (cmd->obj) {
case CMD_OBJ_ELEMENTS:
- return do_get_setelems(ctx, cmd);
+ return do_get_setelems(ctx, cmd, false);
default:
BUG("invalid command object type %u\n", cmd->obj);
}
@@ -2452,6 +2452,15 @@ static int do_command_reset(struct netlink_ctx *ctx, struct cmd *cmd)
return do_command_list(ctx, cmd);
case CMD_OBJ_RULE:
return netlink_reset_rules(ctx, cmd, false);
+ case CMD_OBJ_ELEMENTS:
+ return do_get_setelems(ctx, cmd, true);
+ case CMD_OBJ_SET:
+ case CMD_OBJ_MAP:
+ ret = netlink_list_setelems(ctx, &cmd->handle, cmd->set, true);
+ if (ret < 0)
+ return ret;
+
+ return do_command_list(ctx, cmd);
default:
BUG("invalid command object type %u\n", cmd->obj);
}
diff --git a/tests/shell/testcases/sets/reset_command_0 b/tests/shell/testcases/sets/reset_command_0
new file mode 100755
index 00000000..7a088aea
--- /dev/null
+++ b/tests/shell/testcases/sets/reset_command_0
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+set -e
+set -x
+
+RULESET="table t {
+ set s {
+ type ipv4_addr . inet_proto . inet_service
+ flags interval, timeout
+ counter
+ timeout 30s
+ elements = {
+ 1.0.0.1 . udp . 53 counter packets 5 bytes 30,
+ 2.0.0.2 . tcp . 22 counter packets 10 bytes 100 timeout 15s
+ }
+ }
+ map m {
+ type ipv4_addr : ipv4_addr
+ quota 50 bytes
+ elements = {
+ 1.2.3.4 quota 50 bytes used 10 bytes : 10.2.3.4,
+ 5.6.7.8 quota 100 bytes used 50 bytes : 50.6.7.8
+ }
+ }
+}"
+
+$NFT -f - <<< "$RULESET"
+
+sleep 2
+
+drop_ms() {
+ sed 's/s[0-9]*ms/s/g'
+}
+expires_seconds() {
+ sed -n 's/.*expires \([0-9]*\)s.*/\1/p'
+}
+
+# 'reset element' output is supposed to match 'get element' one
+# apart from changing expires ms value
+EXP=$($NFT get element t s '{ 1.0.0.1 . udp . 53 }' | drop_ms)
+OUT=$($NFT reset element t s '{ 1.0.0.1 . udp . 53 }' | drop_ms)
+$DIFF -u <(echo "$EXP") <(echo "$OUT")
+
+EXP=$($NFT get element t m '{ 1.2.3.4 }')
+OUT=$($NFT reset element t m '{ 1.2.3.4 }')
+$DIFF -u <(echo "$EXP") <(echo "$OUT")
+
+# assert counter value is zeroed
+$NFT get element t s '{ 1.0.0.1 . udp . 53 }' | grep -q 'counter packets 0 bytes 0'
+
+# assert expiry is reset
+VAL=$($NFT get element t s '{ 1.0.0.1 . udp . 53 }' | expires_seconds)
+[[ $VAL -gt 28 ]]
+
+# assert quota value is reset
+$NFT get element t m '{ 1.2.3.4 }' | grep -q 'quota 50 bytes : 10.2.3.4'
+
+# assert other elements remain unchanged
+$NFT get element t s '{ 2.0.0.2 . tcp . 22 }'
+OUT=$($NFT get element t s '{ 2.0.0.2 . tcp . 22 }')
+grep -q 'counter packets 10 bytes 100 timeout 15s' <<< "$OUT"
+VAL=$(expires_seconds <<< "$OUT")
+[[ $val -lt 14 ]]
+$NFT get element t m '{ 5.6.7.8 }' | grep -q 'quota 100 bytes used 50 bytes'
+
+# 'reset set' output is supposed to match 'list set' one, again strip the ms values
+EXP=$($NFT list set t s | drop_ms)
+OUT=$($NFT reset set t s | drop_ms)
+$DIFF -u <(echo "$EXP") <(echo "$OUT")
+
+EXP=$($NFT list map t m | drop_ms)
+OUT=$($NFT reset map t m | drop_ms)
+$DIFF -u <(echo "$EXP") <(echo "$OUT")
+
+# assert expiry of element with custom timeout is correct
+VAL=$($NFT get element t s '{ 2.0.0.2 . tcp . 22 }' | expires_seconds)
+[[ $VAL -lt 15 ]]
+
+# assert remaining elements are now all reset
+OUT=$($NFT list ruleset)
+grep -q '2.0.0.2 . tcp . 22 counter packets 0 bytes 0' <<< "$OUT"
+grep -q '5.6.7.8 quota 100 bytes : 50.6.7.8' <<< "$OUT"