From 83e0f4402fb731633975b54ee043820d3cc7ed8e Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Thu, 15 Jun 2023 15:24:28 +0200 Subject: 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 --- doc/libnftables-json.adoc | 2 +- doc/nft.txt | 13 +++-- include/linux/netfilter/nf_tables.h | 2 + include/mnl.h | 6 ++- include/netlink.h | 5 +- src/cache.c | 9 +++- src/evaluate.c | 5 ++ src/mnl.c | 22 ++++++-- src/netlink.c | 8 +-- src/parser_bison.y | 12 +++++ src/parser_json.c | 4 ++ src/rule.c | 15 ++++-- tests/shell/testcases/sets/reset_command_0 | 82 ++++++++++++++++++++++++++++++ 13 files changed, 162 insertions(+), 23 deletions(-) create mode 100755 tests/shell/testcases/sets/reset_command_0 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" -- cgit v1.2.3