diff mbox series

[nft,1/4] xt: Delay libxtables access until translation

Message ID 20221124165641.26921-2-phil@nwl.cc
State Accepted
Delegated to: Pablo Neira
Headers show
Series [nft,1/4] xt: Delay libxtables access until translation | expand

Commit Message

Phil Sutter Nov. 24, 2022, 4:56 p.m. UTC
There is no point in spending efforts setting up the xt match/target
when it is not printed afterwards. So just store the statement data from
libnftnl in struct xt_stmt and perform the extension lookup from
xt_stmt_xlate() instead.

This means some data structures are only temporarily allocated for the
sake of passing to libxtables callbacks, no need to drag them around.
Also no need to clone the looked up extension, it is needed only to call
the functions it provides.

While being at it, select numeric output in xt_xlate_*_params -
otherwise there will be reverse DNS lookups which should not happen by
default.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 include/statement.h |   9 +--
 src/xt.c            | 192 ++++++++++++++++++--------------------------
 2 files changed, 80 insertions(+), 121 deletions(-)

Comments

Pablo Neira Ayuso Dec. 8, 2022, 9:21 p.m. UTC | #1
Hi Phil,

On Thu, Nov 24, 2022 at 05:56:38PM +0100, Phil Sutter wrote:
> There is no point in spending efforts setting up the xt match/target
> when it is not printed afterwards. So just store the statement data from
> libnftnl in struct xt_stmt and perform the extension lookup from
> xt_stmt_xlate() instead.

There is nft -i and nft monitor which keep a ruleset cache. Both are
sort of incomplete: nft -i resorts to cleaning up the cache based on
the generation number and nft monitor still needs to be updated to
keep track of incremental ruleset updates via netlink events. Sooner
or later these two will get better support for incremental ruleset
updates.

I mean, in those two cases, every call to print the translation will
trigger the allocation of the xt structures, fill them and then call
.xlate. I agree it is a bit more work, I guess this won't case any
noticeable penalty, but it might be work that needs to be done over
and over again when ruleset uses xt match / target.
Phil Sutter Dec. 9, 2022, 12:14 a.m. UTC | #2
Hi Pablo,


On Thu, Dec 08, 2022 at 10:21:02PM +0100, Pablo Neira Ayuso wrote:
> On Thu, Nov 24, 2022 at 05:56:38PM +0100, Phil Sutter wrote:
> > There is no point in spending efforts setting up the xt match/target
> > when it is not printed afterwards. So just store the statement data from
> > libnftnl in struct xt_stmt and perform the extension lookup from
> > xt_stmt_xlate() instead.
> 
> There is nft -i and nft monitor which keep a ruleset cache. Both are
> sort of incomplete: nft -i resorts to cleaning up the cache based on
> the generation number and nft monitor still needs to be updated to
> keep track of incremental ruleset updates via netlink events. Sooner
> or later these two will get better support for incremental ruleset
> updates.
> 
> I mean, in those two cases, every call to print the translation will
> trigger the allocation of the xt structures, fill them and then call
> .xlate. I agree it is a bit more work, I guess this won't case any
> noticeable penalty, but it might be work that needs to be done over
> and over again when ruleset uses xt match / target.

So you're saying the overhead when printing a rule might be more
significant than when fetching it. I doubt this simply because the same
rule is usually printed at most once and there are multiple other
commands requiring a rule cache.

IMO we may also just leave the code as-is and wait for someone to
complain about bad performance with rulesets containing many compat
expressions. Depending on the actual report, we may also follow a hybrid
approach and do the match/target lookup only when needed and cache it
for later use.

My patch made most sense with an nft in mind which does not need xtables
support for saving/restoring compat expressions. Users depending on this
for whatever reason will execute the xlate code path in any case now.

Cheers, Phil
Pablo Neira Ayuso Dec. 9, 2022, 4:06 p.m. UTC | #3
On Fri, Dec 09, 2022 at 01:14:50AM +0100, Phil Sutter wrote:
> Hi Pablo,
> 
> 
> On Thu, Dec 08, 2022 at 10:21:02PM +0100, Pablo Neira Ayuso wrote:
> > On Thu, Nov 24, 2022 at 05:56:38PM +0100, Phil Sutter wrote:
> > > There is no point in spending efforts setting up the xt match/target
> > > when it is not printed afterwards. So just store the statement data from
> > > libnftnl in struct xt_stmt and perform the extension lookup from
> > > xt_stmt_xlate() instead.
> > 
> > There is nft -i and nft monitor which keep a ruleset cache. Both are
> > sort of incomplete: nft -i resorts to cleaning up the cache based on
> > the generation number and nft monitor still needs to be updated to
> > keep track of incremental ruleset updates via netlink events. Sooner
> > or later these two will get better support for incremental ruleset
> > updates.
> > 
> > I mean, in those two cases, every call to print the translation will
> > trigger the allocation of the xt structures, fill them and then call
> > .xlate. I agree it is a bit more work, I guess this won't case any
> > noticeable penalty, but it might be work that needs to be done over
> > and over again when ruleset uses xt match / target.
> 
> So you're saying the overhead when printing a rule might be more
> significant than when fetching it.

I'm saying that there might be scenarios where, once the xt
match/target is set up, we might call .xlate to print it not just once
(considering nft -i use case), but see below.

> I doubt this simply because the same rule is usually printed at most
> once and there are multiple other commands requiring a rule cache.
>
> IMO we may also just leave the code as-is and wait for someone to
> complain about bad performance with rulesets containing many compat
> expressions.
>
> Depending on the actual report, we may also follow a hybrid approach
> and do the match/target lookup only when needed and cache it for
> later use.
>
> My patch made most sense with an nft in mind which does not need xtables
> support for saving/restoring compat expressions. Users depending on this
> for whatever reason will execute the xlate code path in any case now.

OK, you are refering to commands that do not need ruleset listing, in
that case setting up the xt match/target structure makes no sense.

Your original patch description says:

> Also no need to clone the looked up extension, it is needed only to call
> the functions it provides.

I agree, removing this extra clone is good.

I think this patch is fine, the caching of the xt match/target setup
should be easy to do, struct stmt is const on that path, but it could
be overrided in this case.
diff mbox series

Patch

diff --git a/include/statement.h b/include/statement.h
index 2a2d300106181..8651fc78892c9 100644
--- a/include/statement.h
+++ b/include/statement.h
@@ -264,12 +264,11 @@  struct xtables_target;
 struct xt_stmt {
 	const char			*name;
 	enum nft_xt_type		type;
+	uint32_t			rev;
+	uint32_t			family;
+	size_t				infolen;
+	void				*info;
 	uint32_t			proto;
-	union {
-		struct xtables_match	*match;
-		struct xtables_target	*target;
-	};
-	void				*entry;
 };
 
 extern struct stmt *xt_stmt_alloc(const struct location *loc);
diff --git a/src/xt.c b/src/xt.c
index a54173522c229..7880fa1bc6966 100644
--- a/src/xt.c
+++ b/src/xt.c
@@ -28,51 +28,94 @@ 
 
 #ifdef HAVE_LIBXTABLES
 #include <xtables.h>
+
+static void *xt_entry_alloc(const struct xt_stmt *xt, uint32_t af);
 #endif
 
 void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx)
 {
 #ifdef HAVE_LIBXTABLES
 	struct xt_xlate *xl = xt_xlate_alloc(10240);
+	struct xtables_target *tg;
+	struct xt_entry_target *t;
+	struct xtables_match *mt;
+	struct xt_entry_match *m;
+	size_t size;
+	void *entry;
+
+	xtables_set_nfproto(stmt->xt.family);
+	entry = xt_entry_alloc(&stmt->xt, stmt->xt.family);
 
 	switch (stmt->xt.type) {
 	case NFT_XT_MATCH:
-		if (stmt->xt.match->xlate) {
+		mt = xtables_find_match(stmt->xt.name, XTF_TRY_LOAD, NULL);
+		if (!mt) {
+			fprintf(stderr, "XT match %s not found\n",
+				stmt->xt.name);
+			return;
+		}
+		size = XT_ALIGN(sizeof(*m)) + stmt->xt.infolen;
+
+		m = xzalloc(size);
+		memcpy(&m->data, stmt->xt.info, stmt->xt.infolen);
+
+		m->u.match_size = size;
+		m->u.user.revision = stmt->xt.rev;
+
+		if (mt->xlate) {
 			struct xt_xlate_mt_params params = {
-				.ip		= stmt->xt.entry,
-				.match		= stmt->xt.match->m,
-				.numeric        = 0,
+				.ip		= entry,
+				.match		= m,
+				.numeric        = 1,
 			};
 
-			stmt->xt.match->xlate(xl, &params);
+			mt->xlate(xl, &params);
 			nft_print(octx, "%s", xt_xlate_get(xl));
-		} else if (stmt->xt.match->print) {
+		} else if (mt->print) {
 			printf("#");
-			stmt->xt.match->print(&stmt->xt.entry,
-					      stmt->xt.match->m, 0);
+			mt->print(&entry, m, 0);
 		}
+		xfree(m);
 		break;
 	case NFT_XT_WATCHER:
 	case NFT_XT_TARGET:
-		if (stmt->xt.target->xlate) {
+		tg = xtables_find_target(stmt->xt.name, XTF_TRY_LOAD);
+		if (!tg) {
+			fprintf(stderr, "XT target %s not found\n",
+				stmt->xt.name);
+			return;
+		}
+		size = XT_ALIGN(sizeof(*t)) + stmt->xt.infolen;
+
+		t = xzalloc(size);
+		memcpy(&t->data, stmt->xt.info, stmt->xt.infolen);
+
+		t->u.target_size = size;
+		t->u.user.revision = stmt->xt.rev;
+
+		strcpy(t->u.user.name, tg->name);
+
+		if (tg->xlate) {
 			struct xt_xlate_tg_params params = {
-				.ip		= stmt->xt.entry,
-				.target		= stmt->xt.target->t,
-				.numeric        = 0,
+				.ip		= entry,
+				.target		= t,
+				.numeric        = 1,
 			};
 
-			stmt->xt.target->xlate(xl, &params);
+			tg->xlate(xl, &params);
 			nft_print(octx, "%s", xt_xlate_get(xl));
-		} else if (stmt->xt.target->print) {
+		} else if (tg->print) {
 			printf("#");
-			stmt->xt.target->print(NULL, stmt->xt.target->t, 0);
+			tg->print(NULL, t, 0);
 		}
+		xfree(t);
 		break;
 	default:
 		break;
 	}
 
 	xt_xlate_free(xl);
+	xfree(entry);
 #else
 	nft_print(octx, "# xt_%s", stmt->xt.name);
 #endif
@@ -80,33 +123,12 @@  void xt_stmt_xlate(const struct stmt *stmt, struct output_ctx *octx)
 
 void xt_stmt_destroy(struct stmt *stmt)
 {
-#ifdef HAVE_LIBXTABLES
-	switch (stmt->xt.type) {
-	case NFT_XT_MATCH:
-		if (!stmt->xt.match)
-			break;
-		if (stmt->xt.match->m)
-			xfree(stmt->xt.match->m);
-		xfree(stmt->xt.match);
-		break;
-	case NFT_XT_WATCHER:
-	case NFT_XT_TARGET:
-		if (!stmt->xt.target)
-			break;
-		if (stmt->xt.target->t)
-			xfree(stmt->xt.target->t);
-		xfree(stmt->xt.target);
-		break;
-	default:
-		break;
-	}
-#endif
-	xfree(stmt->xt.entry);
 	xfree(stmt->xt.name);
+	xfree(stmt->xt.info);
 }
 
 #ifdef HAVE_LIBXTABLES
-static void *xt_entry_alloc(struct xt_stmt *xt, uint32_t af)
+static void *xt_entry_alloc(const struct xt_stmt *xt, uint32_t af)
 {
 	union nft_entry {
 		struct ipt_entry ipt;
@@ -173,24 +195,6 @@  static uint32_t xt_proto(const struct proto_ctx *pctx)
 
 	return 0;
 }
-
-static struct xtables_target *xt_target_clone(struct xtables_target *t)
-{
-	struct xtables_target *clone;
-
-	clone = xzalloc(sizeof(struct xtables_target));
-	memcpy(clone, t, sizeof(struct xtables_target));
-	return clone;
-}
-
-static struct xtables_match *xt_match_clone(struct xtables_match *m)
-{
-	struct xtables_match *clone;
-
-	clone = xzalloc(sizeof(struct xtables_match));
-	memcpy(clone, m, sizeof(struct xtables_match));
-	return clone;
-}
 #endif
 
 /*
@@ -201,43 +205,22 @@  void netlink_parse_match(struct netlink_parse_ctx *ctx,
 			 const struct location *loc,
 			 const struct nftnl_expr *nle)
 {
-	struct stmt *stmt;
-	const char *name;
-#ifdef HAVE_LIBXTABLES
-	struct xtables_match *mt;
 	const char *mtinfo;
-	struct xt_entry_match *m;
+	struct stmt *stmt;
 	uint32_t mt_len;
 
-	xtables_set_nfproto(ctx->table->handle.family);
-
-	name = nftnl_expr_get_str(nle, NFTNL_EXPR_MT_NAME);
-
-	mt = xtables_find_match(name, XTF_TRY_LOAD, NULL);
-	if (!mt) {
-		fprintf(stderr, "XT match %s not found\n", name);
-		return;
-	}
 	mtinfo = nftnl_expr_get(nle, NFTNL_EXPR_MT_INFO, &mt_len);
 
-	m = xzalloc(sizeof(struct xt_entry_match) + mt_len);
-	memcpy(&m->data, mtinfo, mt_len);
-
-	m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match));
-	m->u.user.revision = nftnl_expr_get_u32(nle, NFTNL_EXPR_MT_REV);
-
 	stmt = xt_stmt_alloc(loc);
-	stmt->xt.name = strdup(name);
+	stmt->xt.name = strdup(nftnl_expr_get_str(nle, NFTNL_EXPR_MT_NAME));
 	stmt->xt.type = NFT_XT_MATCH;
-	stmt->xt.match = xt_match_clone(mt);
-	stmt->xt.match->m = m;
-#else
-	name = nftnl_expr_get_str(nle, NFTNL_EXPR_MT_NAME);
+	stmt->xt.rev = nftnl_expr_get_u32(nle, NFTNL_EXPR_MT_REV);
+	stmt->xt.family = ctx->table->handle.family;
+
+	stmt->xt.infolen = mt_len;
+	stmt->xt.info = xmalloc(mt_len);
+	memcpy(stmt->xt.info, mtinfo, mt_len);
 
-	stmt = xt_stmt_alloc(loc);
-	stmt->xt.name = strdup(name);
-	stmt->xt.type = NFT_XT_MATCH;
-#endif
 	ctx->table->has_xt_stmts = true;
 	rule_stmt_append(ctx->rule, stmt);
 }
@@ -246,44 +229,22 @@  void netlink_parse_target(struct netlink_parse_ctx *ctx,
 			  const struct location *loc,
 			  const struct nftnl_expr *nle)
 {
-	struct stmt *stmt;
-	const char *name;
-#ifdef HAVE_LIBXTABLES
-	struct xtables_target *tg;
 	const void *tginfo;
-	struct xt_entry_target *t;
-	size_t size;
+	struct stmt *stmt;
 	uint32_t tg_len;
 
-	xtables_set_nfproto(ctx->table->handle.family);
-
-	name = nftnl_expr_get_str(nle, NFTNL_EXPR_TG_NAME);
-	tg = xtables_find_target(name, XTF_TRY_LOAD);
-	if (!tg) {
-		fprintf(stderr, "XT target %s not found\n", name);
-		return;
-	}
 	tginfo = nftnl_expr_get(nle, NFTNL_EXPR_TG_INFO, &tg_len);
 
-	size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len;
-	t = xzalloc(size);
-	memcpy(&t->data, tginfo, tg_len);
-	t->u.target_size = size;
-	t->u.user.revision = nftnl_expr_get_u32(nle, NFTNL_EXPR_TG_REV);
-	strcpy(t->u.user.name, tg->name);
-
 	stmt = xt_stmt_alloc(loc);
-	stmt->xt.name = strdup(name);
+	stmt->xt.name = strdup(nftnl_expr_get_str(nle, NFTNL_EXPR_TG_NAME));
 	stmt->xt.type = NFT_XT_TARGET;
-	stmt->xt.target = xt_target_clone(tg);
-	stmt->xt.target->t = t;
-#else
-	name = nftnl_expr_get_str(nle, NFTNL_EXPR_TG_NAME);
+	stmt->xt.rev = nftnl_expr_get_u32(nle, NFTNL_EXPR_TG_REV);
+	stmt->xt.family = ctx->table->handle.family;
+
+	stmt->xt.infolen = tg_len;
+	stmt->xt.info = xmalloc(tg_len);
+	memcpy(stmt->xt.info, tginfo, tg_len);
 
-	stmt = xt_stmt_alloc(loc);
-	stmt->xt.name = strdup(name);
-	stmt->xt.type = NFT_XT_TARGET;
-#endif
 	ctx->table->has_xt_stmts = true;
 	rule_stmt_append(ctx->rule, stmt);
 }
@@ -311,7 +272,6 @@  void stmt_xt_postprocess(struct rule_pp_ctx *rctx, struct stmt *stmt,
 		stmt->xt.type = NFT_XT_WATCHER;
 
 	stmt->xt.proto = xt_proto(&rctx->pctx);
-	stmt->xt.entry = xt_entry_alloc(&stmt->xt, rctx->pctx.family);
 }
 
 static int nft_xt_compatible_revision(const char *name, uint8_t rev, int opt)