]> git.ipfire.org Git - thirdparty/iptables.git/commitdiff
ebtables: Implement --change-counters command
authorPhil Sutter <phil@nwl.cc>
Tue, 21 Nov 2023 22:14:47 +0000 (23:14 +0100)
committerPhil Sutter <phil@nwl.cc>
Thu, 23 Nov 2023 16:59:42 +0000 (17:59 +0100)
Treat it like --replace against the same rule with changed counters.
The operation is obviously not atomic, so rule counters may change in
kernel while the rule is fetched, modified and replaced.

Signed-off-by: Phil Sutter <phil@nwl.cc>
iptables/nft-cmd.c
iptables/nft-cmd.h
iptables/nft.c
iptables/nft.h
iptables/tests/shell/testcases/ebtables/0010-change-counters_0 [new file with mode: 0755]
iptables/xtables-eb.c

index 8a824586ad8c35f5e14b8a80ccd72be1246c08dd..8372d171b00c4c4ca604302d7bf06faae2910dc0 100644 (file)
@@ -400,3 +400,23 @@ int ebt_cmd_user_chain_policy(struct nft_handle *h, const char *table,
 
        return 1;
 }
+
+int nft_cmd_rule_change_counters(struct nft_handle *h,
+                                const char *chain, const char *table,
+                                struct iptables_command_state *cs,
+                                int rule_nr, uint8_t counter_op, bool verbose)
+{
+       struct nft_cmd *cmd;
+
+       cmd = nft_cmd_new(h, NFT_COMPAT_RULE_CHANGE_COUNTERS, table, chain,
+                         rule_nr == -1 ? cs : NULL, rule_nr, verbose);
+       if (!cmd)
+               return 0;
+
+       cmd->counter_op = counter_op;
+       cmd->counters = cs->counters;
+
+       nft_cache_level_set(h, NFT_CL_RULES, cmd);
+
+       return 1;
+}
index ae5908d8d596bdf2f3b95ef0b84ab6720f71d758..8163b82c3511fc3b3d8fb6cd06ae36fee3be377f 100644 (file)
@@ -7,6 +7,13 @@
 
 struct nftnl_rule;
 
+enum {
+       CTR_OP_INC_PKTS = 1 << 0,
+       CTR_OP_DEC_PKTS = 1 << 1,
+       CTR_OP_INC_BYTES = 1 << 2,
+       CTR_OP_DEC_BYTES = 1 << 3,
+};
+
 struct nft_cmd {
        struct list_head                head;
        int                             command;
@@ -22,6 +29,7 @@ struct nft_cmd {
        } obj;
        const char                      *policy;
        struct xt_counters              counters;
+       uint8_t                         counter_op;
        const char                      *rename;
        int                             counters_save;
        struct {
@@ -77,6 +85,10 @@ int nft_cmd_rule_list_save(struct nft_handle *h, const char *chain,
                           const char *table, int rulenum, int counters);
 int ebt_cmd_user_chain_policy(struct nft_handle *h, const char *table,
                              const char *chain, const char *policy);
+int nft_cmd_rule_change_counters(struct nft_handle *h,
+                                const char *chain, const char *table,
+                                struct iptables_command_state *cs,
+                                int rule_nr, uint8_t counter_op, bool verbose);
 void nft_cmd_table_new(struct nft_handle *h, const char *table);
 
 #endif /* _NFT_CMD_H_ */
index 97fd4f49fdb4cf38cf74d24d9de0b44383e809ce..f536857829cd28516e82c3ec8680cddc429bc0eb 100644 (file)
@@ -337,6 +337,7 @@ static int mnl_append_error(const struct nft_handle *h,
        case NFT_COMPAT_RULE_REPLACE:
        case NFT_COMPAT_RULE_DELETE:
        case NFT_COMPAT_RULE_FLUSH:
+       case NFT_COMPAT_RULE_CHANGE_COUNTERS:
                snprintf(tcr, sizeof(tcr), "rule in chain %s",
                         nftnl_rule_get_str(o->rule, NFTNL_RULE_CHAIN));
 #if 0
@@ -2641,6 +2642,58 @@ int nft_rule_replace(struct nft_handle *h, const char *chain,
        return ret;
 }
 
+static int nft_rule_change_counters(struct nft_handle *h, const char *table,
+                                   const char *chain, struct nftnl_rule *rule,
+                                   int rulenum, struct xt_counters *counters,
+                                   uint8_t counter_op, bool verbose)
+{
+       struct iptables_command_state cs = {};
+       struct nftnl_rule *r, *new_rule;
+       struct nft_rule_ctx ctx = {
+               .command = NFT_COMPAT_RULE_APPEND,
+       };
+       struct nft_chain *c;
+
+       nft_fn = nft_rule_change_counters;
+
+       c = nft_chain_find(h, table, chain);
+       if (!c) {
+               errno = ENOENT;
+               return 0;
+       }
+
+       r = nft_rule_find(h, c, rule, rulenum);
+       if (!r) {
+               errno = E2BIG;
+               return 0;
+       }
+
+       DEBUGP("changing counters of rule with handle=%llu\n",
+               (unsigned long long)
+               nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE));
+
+       h->ops->rule_to_cs(h, r, &cs);
+
+       if (counter_op & CTR_OP_INC_PKTS)
+               cs.counters.pcnt += counters->pcnt;
+       else if (counter_op & CTR_OP_DEC_PKTS)
+               cs.counters.pcnt -= counters->pcnt;
+       else
+               cs.counters.pcnt = counters->pcnt;
+
+       if (counter_op & CTR_OP_INC_BYTES)
+               cs.counters.bcnt += counters->bcnt;
+       else if (counter_op & CTR_OP_DEC_BYTES)
+               cs.counters.bcnt -= counters->bcnt;
+       else
+               cs.counters.bcnt = counters->bcnt;
+
+       new_rule = nft_rule_new(h, &ctx, chain, table, &cs);
+       h->ops->clear_cs(&cs);
+
+       return nft_rule_append(h, chain, table, new_rule, r, verbose);
+}
+
 static int
 __nft_rule_list(struct nft_handle *h, struct nftnl_chain *c,
                int rulenum, unsigned int format,
@@ -3031,6 +3084,7 @@ static void batch_obj_del(struct nft_handle *h, struct obj_update *o)
        case NFT_COMPAT_RULE_APPEND:
        case NFT_COMPAT_RULE_INSERT:
        case NFT_COMPAT_RULE_REPLACE:
+       case NFT_COMPAT_RULE_CHANGE_COUNTERS:
                break;
        case NFT_COMPAT_RULE_DELETE:
        case NFT_COMPAT_RULE_FLUSH:
@@ -3118,6 +3172,7 @@ static void nft_refresh_transaction(struct nft_handle *h)
                case NFT_COMPAT_RULE_APPEND:
                case NFT_COMPAT_RULE_INSERT:
                case NFT_COMPAT_RULE_REPLACE:
+               case NFT_COMPAT_RULE_CHANGE_COUNTERS:
                case NFT_COMPAT_RULE_DELETE:
                case NFT_COMPAT_SET_ADD:
                case NFT_COMPAT_RULE_LIST:
@@ -3208,6 +3263,7 @@ retry:
                                                  n->rule);
                        break;
                case NFT_COMPAT_RULE_REPLACE:
+               case NFT_COMPAT_RULE_CHANGE_COUNTERS:
                        nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
                                                  NLM_F_CREATE | NLM_F_REPLACE,
                                                  n->seq, n->rule);
@@ -3510,6 +3566,15 @@ static int nft_prepare(struct nft_handle *h)
                case NFT_COMPAT_CHAIN_ADD:
                        assert(0);
                        return 0;
+               case NFT_COMPAT_RULE_CHANGE_COUNTERS:
+                       ret = nft_rule_change_counters(h, cmd->table,
+                                                      cmd->chain,
+                                                      cmd->obj.rule,
+                                                      cmd->rulenum,
+                                                      &cmd->counters,
+                                                      cmd->counter_op,
+                                                      cmd->verbose);
+                       break;
                }
 
                nft_cmd_free(cmd);
index 5acbbf82e2c29f1751b70dc9dd3e7bc852b2c703..79f1e037cd6d334402556f6955e64090d7351b35 100644 (file)
@@ -72,6 +72,7 @@ enum obj_update_type {
        NFT_COMPAT_RULE_SAVE,
        NFT_COMPAT_RULE_ZERO,
        NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE,
+       NFT_COMPAT_RULE_CHANGE_COUNTERS,
 };
 
 struct cache_chain {
diff --git a/iptables/tests/shell/testcases/ebtables/0010-change-counters_0 b/iptables/tests/shell/testcases/ebtables/0010-change-counters_0
new file mode 100755 (executable)
index 0000000..4f78381
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+case "$XT_MULTI" in
+*xtables-nft-multi)
+       ;;
+*)
+       echo "skip $XT_MULTI"
+       exit 0
+       ;;
+esac
+
+set -e
+set -x
+
+check_rule() { # (pcnt, bcnt)
+       $XT_MULTI ebtables -L FORWARD --Lc --Ln | \
+               grep -q "^1. -o eth0 -j CONTINUE , pcnt = $1 -- bcnt = $2$"
+}
+
+$XT_MULTI ebtables -A FORWARD -o eth0 -c 10 20
+check_rule 10 20
+
+$XT_MULTI ebtables -C FORWARD 1 100 200
+check_rule 100 200
+
+$XT_MULTI ebtables -C FORWARD 101 201 -o eth0
+check_rule 101 201
+
+$XT_MULTI ebtables -C FORWARD 1 +10 -20
+check_rule 111 181
+
+$XT_MULTI ebtables -C FORWARD -10 +20 -o eth0
+check_rule 101 201
+
+$XT_MULTI ebtables -A FORWARD -o eth1 -c 111 211
+$XT_MULTI ebtables -A FORWARD -o eth2 -c 121 221
+
+$XT_MULTI ebtables -C FORWARD 2:3 +100 -200
+
+EXPECT='1. -o eth0 -j CONTINUE , pcnt = 101 -- bcnt = 201
+2. -o eth1 -j CONTINUE , pcnt = 211 -- bcnt = 11
+3. -o eth2 -j CONTINUE , pcnt = 221 -- bcnt = 21'
+diff -u <(echo "$EXPECT") \
+       <($XT_MULTI ebtables -L FORWARD --Lc --Ln | grep -- '-o eth')
+
index cd45e0495ebcb79654b10c2ec1a8922400857add..ddbe1b5a3adc04f8df550146a4d5d8ac306a7f3c 100644 (file)
@@ -136,6 +136,29 @@ delete_entry(struct nft_handle *h,
        return ret;
 }
 
+static int
+change_entry_counters(struct nft_handle *h,
+                     const char *chain, const char *table,
+                     struct iptables_command_state *cs,
+                     int rule_nr, int rule_nr_end, uint8_t counter_op,
+                     bool verbose)
+{
+       int ret = 1;
+
+       if (rule_nr == -1)
+               return nft_cmd_rule_change_counters(h, chain, table, cs,
+                                                   rule_nr, counter_op,
+                                                   verbose);
+       do {
+               ret = nft_cmd_rule_change_counters(h, chain, table, cs,
+                                                  rule_nr, counter_op,
+                                                  verbose);
+               rule_nr++;
+       } while (rule_nr < rule_nr_end);
+
+       return ret;
+}
+
 int ebt_get_current_chain(const char *chain)
 {
        if (!chain)
@@ -391,51 +414,62 @@ static int parse_rule_range(const char *argv, int *rule_nr, int *rule_nr_end)
 /* Incrementing or decrementing rules in daemon mode is not supported as the
  * involved code overload is not worth it (too annoying to take the increased
  * counters in the kernel into account). */
-static int parse_change_counters_rule(int argc, char **argv, int *rule_nr, int *rule_nr_end, struct iptables_command_state *cs)
+static uint8_t parse_change_counters_rule(int argc, char **argv,
+                                         int *rule_nr, int *rule_nr_end,
+                                         struct iptables_command_state *cs)
 {
+       uint8_t ret = 0;
        char *buffer;
-       int ret = 0;
 
-       if (optind + 1 >= argc || argv[optind][0] == '-' || argv[optind + 1][0] == '-')
+       if (optind + 1 >= argc ||
+           (argv[optind][0] == '-' && !isdigit(argv[optind][1])) ||
+           (argv[optind + 1][0] == '-' && !isdigit(argv[optind + 1][1])))
                xtables_error(PARAMETER_PROBLEM,
                              "The command -C needs at least 2 arguments");
-       if (optind + 2 < argc && (argv[optind + 2][0] != '-' || (argv[optind + 2][1] >= '0' && argv[optind + 2][1] <= '9'))) {
+       if (optind + 2 < argc &&
+           (argv[optind + 2][0] != '-' || isdigit(argv[optind + 2][1]))) {
                if (optind + 3 != argc)
                        xtables_error(PARAMETER_PROBLEM,
                                      "No extra options allowed with -C start_nr[:end_nr] pcnt bcnt");
                if (parse_rule_range(argv[optind], rule_nr, rule_nr_end))
                        xtables_error(PARAMETER_PROBLEM,
-                                     "Something is wrong with the rule number specification '%s'", argv[optind]);
+                                     "Something is wrong with the rule number specification '%s'",
+                                     argv[optind]);
                optind++;
        }
 
        if (argv[optind][0] == '+') {
-               ret += 1;
+               ret |= CTR_OP_INC_PKTS;
                cs->counters.pcnt = strtoull(argv[optind] + 1, &buffer, 10);
        } else if (argv[optind][0] == '-') {
-               ret += 2;
+               ret |= CTR_OP_DEC_PKTS;
                cs->counters.pcnt = strtoull(argv[optind] + 1, &buffer, 10);
-       } else
+       } else {
                cs->counters.pcnt = strtoull(argv[optind], &buffer, 10);
-
+       }
        if (*buffer != '\0')
                goto invalid;
+
        optind++;
+
        if (argv[optind][0] == '+') {
-               ret += 3;
+               ret |= CTR_OP_INC_BYTES;
                cs->counters.bcnt = strtoull(argv[optind] + 1, &buffer, 10);
        } else if (argv[optind][0] == '-') {
-               ret += 6;
+               ret |= CTR_OP_DEC_BYTES;
                cs->counters.bcnt = strtoull(argv[optind] + 1, &buffer, 10);
-       } else
+       } else {
                cs->counters.bcnt = strtoull(argv[optind], &buffer, 10);
-
+       }
        if (*buffer != '\0')
                goto invalid;
+
        optind++;
+
        return ret;
 invalid:
-       xtables_error(PARAMETER_PROBLEM,"Packet counter '%s' invalid", argv[optind]);
+       xtables_error(PARAMETER_PROBLEM,
+                     "Packet counter '%s' invalid", argv[optind]);
 }
 
 static void ebtables_parse_interface(const char *arg, char *vianame)
@@ -695,7 +729,7 @@ int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table,
 {
        char *buffer;
        int c, i;
-       int chcounter = 0; /* Needed for -C */
+       uint8_t chcounter = 0; /* Needed for -C */
        int rule_nr = 0;
        int rule_nr_end = 0;
        int ret = 0;
@@ -1171,11 +1205,11 @@ print_zero:
        } else if (command == 14) {
                ret = nft_cmd_rule_check(h, chain, *table,
                                         &cs, flags & OPT_VERBOSE);
-       } /*else if (replace->command == 'C') {
-               ebt_change_counters(replace, new_entry, rule_nr, rule_nr_end, &(new_entry->cnt_surplus), chcounter);
-               if (ebt_errormsg[0] != '\0')
-                       return -1;
-       }*/
+       } else if (command == 'C') {
+               ret = change_entry_counters(h, chain, *table, &cs,
+                                           rule_nr - 1, rule_nr_end, chcounter,
+                                           flags & OPT_VERBOSE);
+       }
 
        ebt_cs_clean(&cs);
        return ret;