summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Sutter <phil@nwl.cc>2023-11-21 23:14:47 +0100
committerPhil Sutter <phil@nwl.cc>2023-11-23 17:59:42 +0100
commitf340b7b6816beaeeebf6cefa819939cf70ae18f4 (patch)
tree39e955718b979a3038e710c3e1c4693f1261643c
parentc0bd98c9339b4975c9d52739d638a3c2bdf9a7ff (diff)
ebtables: Implement --change-counters command
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>
-rw-r--r--iptables/nft-cmd.c20
-rw-r--r--iptables/nft-cmd.h12
-rw-r--r--iptables/nft.c65
-rw-r--r--iptables/nft.h1
-rwxr-xr-xiptables/tests/shell/testcases/ebtables/0010-change-counters_045
-rw-r--r--iptables/xtables-eb.c74
6 files changed, 197 insertions, 20 deletions
diff --git a/iptables/nft-cmd.c b/iptables/nft-cmd.c
index 8a824586..8372d171 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 ae5908d8..8163b82c 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 97fd4f49..f5368578 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 @@ 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);
diff --git a/iptables/nft.h b/iptables/nft.h
index 5acbbf82..79f1e037 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 00000000..4f783819
--- /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 cd45e049..ddbe1b5a 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 @@ 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;