diff mbox series

[iptables,v2,5/8] nft-ruleparse: Fallback to compat expressions in userdata

Message ID 20241009114819.15379-6-phil@nwl.cc
State New
Headers show
Series nft: Implement forward compat for future binaries | expand

Commit Message

Phil Sutter Oct. 9, 2024, 11:48 a.m. UTC
If parsing of a rule fails (e.g. due to an unknown native expression),
check if userdata contains a UDATA_TYPE_COMPAT_EXT attribute and retry
parsing the rule preferring the contained extensions instead of native
expressions.

Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 configure.ac             |   9 +++
 iptables/Makefile.am     |   1 +
 iptables/nft-compat.c    | 148 +++++++++++++++++++++++++++++++++++++++
 iptables/nft-compat.h    |  29 ++++++++
 iptables/nft-ruleparse.c |  17 +++++
 5 files changed, 204 insertions(+)
 create mode 100644 iptables/nft-compat.c
 create mode 100644 iptables/nft-compat.h
diff mbox series

Patch

diff --git a/configure.ac b/configure.ac
index a19a60c03c5b2..4cc4960598822 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,6 +77,14 @@  AC_ARG_WITH([xt-lock-name], AS_HELP_STRING([--with-xt-lock-name=PATH],
 AC_ARG_ENABLE([profiling],
 	AS_HELP_STRING([--enable-profiling], [build for use of gcov/gprof]),
 	[enable_profiling="$enableval"], [enable_profiling="no"])
+AC_ARG_WITH([zlib], [AS_HELP_STRING([--without-zlib],
+	    [Disable payload compression of rule compat expressions])],
+           [], [with_zlib=yes])
+AS_IF([test "x$with_zlib" != xno], [
+       AC_CHECK_LIB([z], [compress], ,
+		    AC_MSG_ERROR([No suitable version of zlib found]))
+       AC_DEFINE([HAVE_ZLIB], [1], [Define if you have zlib])
+])
 
 AC_MSG_CHECKING([whether $LD knows -Wl,--no-undefined])
 saved_LDFLAGS="$LDFLAGS";
@@ -289,6 +297,7 @@  echo "
   nftables support:			${enable_nftables}
   connlabel support:			${enable_connlabel}
   profiling support:			${enable_profiling}
+  compress rule compat expressions:	${with_zlib}
 
 Build parameters:
   Put plugins into executable (static):	${enable_static}
diff --git a/iptables/Makefile.am b/iptables/Makefile.am
index 2007cd10260bd..4855c9a7c2911 100644
--- a/iptables/Makefile.am
+++ b/iptables/Makefile.am
@@ -57,6 +57,7 @@  xtables_nft_multi_SOURCES += nft.c nft.h \
 			     nft-ruleparse-arp.c nft-ruleparse-bridge.c \
 			     nft-ruleparse-ipv4.c nft-ruleparse-ipv6.c \
 			     nft-shared.c nft-shared.h \
+			     nft-compat.c nft-compat.h \
 			     xtables-monitor.c \
 			     xtables.c xtables-arp.c xtables-eb.c \
 			     xtables-standalone.c xtables-eb-standalone.c \
diff --git a/iptables/nft-compat.c b/iptables/nft-compat.c
new file mode 100644
index 0000000000000..1edf08851c579
--- /dev/null
+++ b/iptables/nft-compat.c
@@ -0,0 +1,148 @@ 
+/*
+ * (C) 2024 Red Hat GmbH
+ * Author: Phil Sutter <phil@nwl.cc>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include "config.h"
+#include "nft-compat.h"
+#include "nft-ruleparse.h"
+#include "nft.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <xtables.h>
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+#endif
+
+#include <libnftnl/udata.h>
+
+static struct rule_udata_ext *
+rule_get_udata_ext(const struct nftnl_rule *r, uint32_t *outlen)
+{
+	const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
+	struct nftnl_udata_buf *udata;
+	uint32_t udatalen;
+
+	udata = (void *)nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &udatalen);
+	if (!udata)
+		return NULL;
+
+	if (nftnl_udata_parse(udata, udatalen, parse_udata_cb, tb) < 0)
+		return NULL;
+
+	if (!tb[UDATA_TYPE_COMPAT_EXT])
+		return NULL;
+
+	if (outlen)
+		*outlen = nftnl_udata_len(tb[UDATA_TYPE_COMPAT_EXT]);
+	return nftnl_udata_get(tb[UDATA_TYPE_COMPAT_EXT]);
+}
+
+static struct nftnl_expr *
+__nftnl_expr_from_udata_ext(struct rule_udata_ext *rue, const void *data)
+{
+	struct nftnl_expr *expr = NULL;
+
+	switch (rue->flags & RUE_FLAG_TYPE_BITS) {
+	case RUE_FLAG_MATCH_TYPE:
+		expr = nftnl_expr_alloc("match");
+		__add_match(expr, data);
+		break;
+	case RUE_FLAG_TARGET_TYPE:
+		expr = nftnl_expr_alloc("target");
+		__add_target(expr, data);
+		break;
+	default:
+		fprintf(stderr,
+			"Warning: Unexpected udata extension type %d\n",
+			rue->flags & RUE_FLAG_TYPE_BITS);
+	}
+
+	return expr;
+}
+
+static struct nftnl_expr *
+nftnl_expr_from_zipped_udata_ext(struct rule_udata_ext *rue)
+{
+#ifdef HAVE_ZLIB
+	uLongf datalen = rue->orig_size;
+	struct nftnl_expr *expr = NULL;
+	void *data;
+
+	data = xtables_malloc(datalen);
+	if (uncompress(data, &datalen, rue->data, rue->size) != Z_OK) {
+		fprintf(stderr, "Warning: Failed to uncompress rule udata extension\n");
+		goto out;
+	}
+
+	expr = __nftnl_expr_from_udata_ext(rue, data);
+out:
+	free(data);
+	return expr;
+#else
+	fprintf(stderr, "Warning: Zipped udata extensions are not supported.\n");
+	return NULL;
+#endif
+}
+
+static struct nftnl_expr *nftnl_expr_from_udata_ext(struct rule_udata_ext *rue)
+{
+	if (rue->flags & RUE_FLAG_ZIP)
+		return nftnl_expr_from_zipped_udata_ext(rue);
+	else
+		return __nftnl_expr_from_udata_ext(rue, rue->data);
+}
+
+bool rule_has_udata_ext(const struct nftnl_rule *r)
+{
+	return rule_get_udata_ext(r, NULL) != NULL;
+}
+
+#define rule_udata_ext_foreach(rue, ext, extlen)			\
+	for (rue = (void *)(ext);					\
+	     (char *)rue < (char *)(ext) + extlen;			\
+	     rue = (void *)((char *)rue + sizeof(*rue) + rue->size))
+
+bool rule_parse_udata_ext(struct nft_xt_ctx *ctx, const struct nftnl_rule *r)
+{
+	struct rule_udata_ext *rue;
+	struct nftnl_expr *expr;
+	uint32_t extlen;
+	bool ret = true;
+	int eidx = 0;
+	void *ext;
+
+	ext = rule_get_udata_ext(r, &extlen);
+	if (!ext)
+		return false;
+
+	rule_udata_ext_foreach(rue, ext, extlen) {
+		for (; eidx < rue->start_idx; eidx++) {
+			expr = nftnl_expr_iter_next(ctx->iter);
+			if (!nft_parse_rule_expr(ctx->h, expr, ctx))
+				ret = false;
+		}
+
+		expr = nftnl_expr_from_udata_ext(rue);
+		if (!nft_parse_rule_expr(ctx->h, expr, ctx))
+			ret = false;
+		nftnl_expr_free(expr);
+
+		for (; eidx < rue->end_idx; eidx++)
+			nftnl_expr_iter_next(ctx->iter);
+	}
+	expr = nftnl_expr_iter_next(ctx->iter);
+	while (expr != NULL) {
+		if (!nft_parse_rule_expr(ctx->h, expr, ctx))
+			ret = false;
+		expr = nftnl_expr_iter_next(ctx->iter);
+	}
+	return ret;
+}
+
diff --git a/iptables/nft-compat.h b/iptables/nft-compat.h
new file mode 100644
index 0000000000000..1147f08a0b6d5
--- /dev/null
+++ b/iptables/nft-compat.h
@@ -0,0 +1,29 @@ 
+#ifndef _NFT_COMPAT_H_
+#define _NFT_COMPAT_H_
+
+#include <libnftnl/rule.h>
+
+#include <linux/netfilter/x_tables.h>
+
+enum rule_udata_ext_flags {
+	RUE_FLAG_MATCH_TYPE	= (1 << 0),
+	RUE_FLAG_TARGET_TYPE	= (1 << 1),
+	RUE_FLAG_ZIP		= (1 << 7),
+};
+#define RUE_FLAG_TYPE_BITS	(RUE_FLAG_MATCH_TYPE | RUE_FLAG_TARGET_TYPE)
+
+struct rule_udata_ext {
+	uint8_t start_idx;
+	uint8_t end_idx;
+	uint8_t flags;
+	uint16_t orig_size;
+	uint16_t size;
+	unsigned char data[];
+};
+
+struct nft_xt_ctx;
+
+bool rule_has_udata_ext(const struct nftnl_rule *r);
+bool rule_parse_udata_ext(struct nft_xt_ctx *ctx, const struct nftnl_rule *r);
+
+#endif /* _NFT_COMPAT_H_ */
diff --git a/iptables/nft-ruleparse.c b/iptables/nft-ruleparse.c
index 757d3c29fc816..34270e46ae888 100644
--- a/iptables/nft-ruleparse.c
+++ b/iptables/nft-ruleparse.c
@@ -10,6 +10,7 @@ 
  * This code has been sponsored by Sophos Astaro <http://www.sophos.com>
  */
 
+#include "config.h"
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
@@ -27,6 +28,7 @@ 
 
 #include <xtables.h>
 
+#include "nft-compat.h"
 #include "nft-ruleparse.h"
 #include "nft.h"
 
@@ -948,6 +950,21 @@  bool nft_rule_to_iptables_command_state(struct nft_handle *h,
 			ret = false;
 		expr = nftnl_expr_iter_next(ctx.iter);
 	}
+	if (!ret && rule_has_udata_ext(r)) {
+		fprintf(stderr,
+			"Warning: Rule parser failed, trying compat fallback\n");
+
+		h->ops->clear_cs(cs);
+		if (h->ops->init_cs)
+			h->ops->init_cs(cs);
+
+		nftnl_expr_iter_destroy(ctx.iter);
+		ctx.iter = nftnl_expr_iter_create(r);
+		if (!ctx.iter)
+			return false;
+
+		ret = rule_parse_udata_ext(&ctx, r);
+	}
 
 	nftnl_expr_iter_destroy(ctx.iter);