The original issue was that for a rule with limit match added by ebtables-nft, the kernel might attempt to use xt_limit instead of ebt_limit (and fail due to that). This happens if xt_limit.ko is loaded but ebt_limit.ko is not, because the kernel prefers the family-independent variants. There are multiple ways to avoid above issue, but using neither xt_limit nor ebt_limit with nft-variants should be the most effective one. Therefore translate a created limit match in userspace into native nftables code before sending it to kernel and do the reverse translation when listing rules. Apart from the translation routines, this requires slight adjustment of nft_is_expr_compatible() since neither xt_limit nor ebt_limit support byte-based limits or inverted limit match. Signed-off-by: Phil Sutter <phil@xxxxxx> --- iptables/nft-shared.c | 46 +++++++++++++++++++++++++++++++++++++ iptables/nft.c | 53 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c index 5b55c7c0a7052..871037d21a7a6 100644 --- a/iptables/nft-shared.c +++ b/iptables/nft-shared.c @@ -22,6 +22,7 @@ #include <linux/netfilter/nf_tables.h> #include <linux/netfilter/xt_comment.h> +#include <linux/netfilter/xt_limit.h> #include <libmnl/libmnl.h> #include <libnftnl/rule.h> @@ -549,6 +550,49 @@ void nft_parse_immediate(struct nft_xt_ctx *ctx, struct nftnl_expr *e) ops->parse_immediate(jumpto, nft_goto, data); } +static void nft_parse_limit(struct nft_xt_ctx *ctx, struct nftnl_expr *e) +{ + __u32 burst = nftnl_expr_get_u32(e, NFTNL_EXPR_LIMIT_BURST); + __u64 unit = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_UNIT); + __u64 rate = nftnl_expr_get_u64(e, NFTNL_EXPR_LIMIT_RATE); + struct xtables_rule_match **matches; + struct xtables_match *match; + struct nft_family_ops *ops; + struct xt_rateinfo *rinfo; + size_t size; + + switch (ctx->family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_BRIDGE: + matches = &ctx->cs->matches; + break; + default: + fprintf(stderr, "BUG: nft_parse_match() unknown family %d\n", + ctx->family); + exit(EXIT_FAILURE); + } + + match = xtables_find_match("limit", XTF_TRY_LOAD, matches); + if (match == NULL) + return; + + size = XT_ALIGN(sizeof(struct xt_entry_match)) + match->size; + match->m = xtables_calloc(1, size); + match->m->u.match_size = size; + strcpy(match->m->u.user.name, match->name); + match->m->u.user.revision = match->revision; + xs_init_match(match); + + rinfo = (void *)match->m->data; + rinfo->avg = XT_LIMIT_SCALE * unit / rate; + rinfo->burst = burst; + + ops = nft_family_ops_lookup(ctx->family); + if (ops->parse_match != NULL) + ops->parse_match(match, ctx->cs); +} + void nft_rule_to_iptables_command_state(const struct nftnl_rule *r, struct iptables_command_state *cs) { @@ -586,6 +630,8 @@ void nft_rule_to_iptables_command_state(const struct nftnl_rule *r, nft_parse_match(&ctx, expr); else if (strcmp(name, "target") == 0) nft_parse_target(&ctx, expr); + else if (strcmp(name, "limit") == 0) + nft_parse_limit(&ctx, expr); expr = nftnl_expr_iter_next(iter); } diff --git a/iptables/nft.c b/iptables/nft.c index 120039ed6aac8..8c0746dd94b87 100644 --- a/iptables/nft.c +++ b/iptables/nft.c @@ -38,6 +38,8 @@ #include <linux/netfilter/nf_tables.h> #include <linux/netfilter/nf_tables_compat.h> +#include <linux/netfilter/xt_limit.h> + #include <libmnl/libmnl.h> #include <libnftnl/table.h> #include <libnftnl/chain.h> @@ -896,11 +898,50 @@ static int __add_match(struct nftnl_expr *e, struct xt_entry_match *m) return 0; } +static int add_nft_limit(struct nftnl_rule *r, struct xt_entry_match *m) +{ + struct xt_rateinfo *rinfo = (void *)m->data; + static const uint32_t mult[] = { + XT_LIMIT_SCALE*24*60*60, /* day */ + XT_LIMIT_SCALE*60*60, /* hour */ + XT_LIMIT_SCALE*60, /* min */ + XT_LIMIT_SCALE, /* sec */ + }; + struct nftnl_expr *expr; + int i; + + expr = nftnl_expr_alloc("limit"); + if (!expr) + return -ENOMEM; + + for (i = 1; i < ARRAY_SIZE(mult); i++) { + if (rinfo->avg > mult[i] || + mult[i] / rinfo->avg < mult[i] % rinfo->avg) + break; + } + + nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_TYPE, NFT_LIMIT_PKTS); + nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_FLAGS, 0); + + nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_RATE, + mult[i - 1] / rinfo->avg); + nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_UNIT, + mult[i - 1] / XT_LIMIT_SCALE); + + nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_BURST, rinfo->burst); + + nftnl_rule_add_expr(r, expr); + return 0; +} + int add_match(struct nftnl_rule *r, struct xt_entry_match *m) { struct nftnl_expr *expr; int ret; + if (!strcmp(m->u.user.name, "limit")) + return add_nft_limit(r, m); + expr = nftnl_expr_alloc("match"); if (expr == NULL) return -ENOMEM; @@ -2996,8 +3037,9 @@ static const char *supported_exprs[NFT_COMPAT_EXPR_MAX] = { }; -static int nft_is_expr_compatible(const char *name) +static int nft_is_expr_compatible(const struct nftnl_expr *expr) { + const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); int i; for (i = 0; i < NFT_COMPAT_EXPR_MAX; i++) { @@ -3005,6 +3047,11 @@ static int nft_is_expr_compatible(const char *name) return 0; } + if (!strcmp(name, "limit") && + nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_TYPE) == NFT_LIMIT_PKTS && + nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_FLAGS) == 0) + return 0; + return 1; } @@ -3020,9 +3067,7 @@ static bool nft_is_rule_compatible(struct nftnl_rule *rule) expr = nftnl_expr_iter_next(iter); while (expr != NULL) { - const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); - - if (nft_is_expr_compatible(name) == 0) { + if (nft_is_expr_compatible(expr) == 0) { expr = nftnl_expr_iter_next(iter); continue; } -- 2.18.0