]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
Implement 'reset {set,map,element}' commands
authorPhil Sutter <phil@nwl.cc>
Thu, 15 Jun 2023 13:24:28 +0000 (15:24 +0200)
committerPhil Sutter <phil@nwl.cc>
Thu, 13 Jul 2023 14:57:56 +0000 (16:57 +0200)
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>
13 files changed:
doc/libnftables-json.adoc
doc/nft.txt
include/linux/netfilter/nf_tables.h
include/mnl.h
include/netlink.h
src/cache.c
src/evaluate.c
src/mnl.c
src/netlink.c
src/parser_bison.y
src/parser_json.c
src/rule.c
tests/shell/testcases/sets/reset_command_0 [new file with mode: 0755]

index f9288487e4b25ee3c1b9ab54394678af37de0788..3e6e1db381d8ff63b9a50b8fcdc3ffb7bb16a419 100644 (file)
@@ -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.
index 19ba55d965055b7242c52936f113a821da258e76..fe123d04f9b95537599f2ee04528ecc48b5a68ad 100644 (file)
@@ -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"]
 |=================
index 673e05073de8252e3aa4a7172c3e434d9aaf1801..c62e6ac563988ec1ca03cd7074e875cb015e1695 100644 (file)
@@ -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,
 };
 
index c06766912539507c31290a439f64b8d31a738696..cd5a2053b1661f571d41a0f44cbbbcb4fe4bde06 100644 (file)
@@ -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,
index d52434c72bc2c9d765e3d20112e0b63f227f767d..6766d7e8563fe82d4bea68772f32df3e6cab0a7d 100644 (file)
@@ -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);
index d908ae0ad1920aa0121c2bfd58312933f5223a35..5cab2622db95a46e969621ccacd564847ec30741 100644 (file)
@@ -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;
                        }
index 3dc2be0d0df5451249663a254eaadd5348be194a..33e4ac93e89af5d4a4bcb237da3de26aa464b00c 100644 (file)
@@ -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);
        }
index 91775c41b24616b60ef5d3b376cee1e81505c7ee..9406fc4821236be9e907ce4009da8033a179b830 100644 (file)
--- 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);
index 3352ad0abb61064e4bd1b30b8d2d349b688900b8..ed61cd896511a0c193d42d62face596bfd544290 100644 (file)
@@ -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;
index beb277b68288e27f2e1284514fd067156de3ac83..553ddf9757f49cb07fa9ef7b8212fa41c6ef55bf 100644 (file)
@@ -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
index 55c6b8c7df9055fd424797c372b3fa2bb9dddc7a..92cffee905e304e827ef85894f0578c072e57a7c 100644 (file)
@@ -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;
index 18a566f9fc62e0994589ad96395547d7fc0203e1..533161d3c23e031ea3d4e7c4aea4b5695669b255 100644 (file)
@@ -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 (executable)
index 0000000..7a088ae
--- /dev/null
@@ -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"