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@xxxxxx> --- iptables/nft-cmd.c | 20 +++++ iptables/nft-cmd.h | 12 +++ iptables/nft.c | 65 ++++++++++++++++ iptables/nft.h | 1 + .../testcases/ebtables/0010-change-counters_0 | 45 +++++++++++ iptables/xtables-eb.c | 74 ++++++++++++++----- 6 files changed, 197 insertions(+), 20 deletions(-) create mode 100755 iptables/tests/shell/testcases/ebtables/0010-change-counters_0 diff --git a/iptables/nft-cmd.c b/iptables/nft-cmd.c index 8a824586ad8c3..8372d171b00c4 100644 --- a/iptables/nft-cmd.c +++ b/iptables/nft-cmd.c @@ -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; +} diff --git a/iptables/nft-cmd.h b/iptables/nft-cmd.h index ae5908d8d596b..8163b82c3511f 100644 --- a/iptables/nft-cmd.h +++ b/iptables/nft-cmd.h @@ -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_ */ diff --git a/iptables/nft.c b/iptables/nft.c index 97fd4f49fdb4c..f536857829cd2 100644 --- a/iptables/nft.c +++ b/iptables/nft.c @@ -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 @@ static int nft_action(struct nft_handle *h, int action) 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); diff --git a/iptables/nft.h b/iptables/nft.h index 5acbbf82e2c29..79f1e037cd6d3 100644 --- a/iptables/nft.h +++ b/iptables/nft.h @@ -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 index 0000000000000..4f783819d10eb --- /dev/null +++ b/iptables/tests/shell/testcases/ebtables/0010-change-counters_0 @@ -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') + diff --git a/iptables/xtables-eb.c b/iptables/xtables-eb.c index cd45e0495ebcb..ddbe1b5a3adc0 100644 --- a/iptables/xtables-eb.c +++ b/iptables/xtables-eb.c @@ -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 @@ int do_commandeb(struct nft_handle *h, int argc, char *argv[], char **table, } 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; -- 2.41.0