Blame SOURCES/0004-add-ipset-to-nftables-translation-infrastructure.patch

e0f94f
From 469387c1fce52280daf0ed71a1e8cf1953551e8d Mon Sep 17 00:00:00 2001
e0f94f
From: Pablo Neira Ayuso <pablo@netfilter.org>
e0f94f
Date: Fri, 25 Jun 2021 22:30:42 +0200
e0f94f
Subject: [PATCH] add ipset to nftables translation infrastructure
e0f94f
e0f94f
This patch provides the ipset-translate utility which allows you to
e0f94f
translate your existing ipset file to nftables.
e0f94f
e0f94f
The ipset-translate utility is actually a symlink to ipset, which checks
e0f94f
for 'argv[0] == ipset-translate' to exercise the translation path.
e0f94f
e0f94f
You can translate your ipset file through:
e0f94f
e0f94f
	ipset-translate restore < sets.ipt
e0f94f
e0f94f
This patch reuses the existing parser and API to represent the sets and
e0f94f
the elements.
e0f94f
e0f94f
There is a new ipset_xlate_set dummy object that allows to store a
e0f94f
created set to fetch the type without interactions with the kernel.
e0f94f
e0f94f
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
e0f94f
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
e0f94f
(cherry picked from commit 325af556cd3a6d1636c0cd355b494c87f58397e0)
e0f94f
---
e0f94f
 configure.ac                 |   1 +
e0f94f
 include/libipset/Makefile.am |   3 +-
e0f94f
 include/libipset/xlate.h     |   6 +
e0f94f
 lib/ipset.c                  | 541 ++++++++++++++++++++++++++++++++++-
e0f94f
 src/Makefile.am              |   8 +-
e0f94f
 src/ipset-translate.8        |  91 ++++++
e0f94f
 src/ipset.c                  |   8 +-
e0f94f
 7 files changed, 654 insertions(+), 4 deletions(-)
e0f94f
 create mode 100644 include/libipset/xlate.h
e0f94f
 create mode 100644 src/ipset-translate.8
e0f94f
e0f94f
diff --git a/configure.ac b/configure.ac
e0f94f
index bd6116ca7f0a3..3ba3e17137d34 100644
e0f94f
--- a/configure.ac
e0f94f
+++ b/configure.ac
e0f94f
@@ -7,6 +7,7 @@ AC_CONFIG_HEADER([config.h])
e0f94f
 AM_INIT_AUTOMAKE([foreign subdir-objects tar-pax])
e0f94f
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
e0f94f
 
e0f94f
+AC_PROG_LN_S
e0f94f
 AC_ENABLE_STATIC
e0f94f
 LT_INIT([dlopen])
e0f94f
 LT_CONFIG_LTDL_DIR([libltdl])
e0f94f
diff --git a/include/libipset/Makefile.am b/include/libipset/Makefile.am
e0f94f
index c7f7b2bfce487..2c040291abc06 100644
e0f94f
--- a/include/libipset/Makefile.am
e0f94f
+++ b/include/libipset/Makefile.am
e0f94f
@@ -17,6 +17,7 @@ pkginclude_HEADERS = \
e0f94f
 	transport.h \
e0f94f
 	types.h \
e0f94f
 	ipset.h \
e0f94f
-	utils.h
e0f94f
+	utils.h \
e0f94f
+	xlate.h
e0f94f
 
e0f94f
 EXTRA_DIST = debug.h icmp.h icmpv6.h
e0f94f
diff --git a/include/libipset/xlate.h b/include/libipset/xlate.h
e0f94f
new file mode 100644
e0f94f
index 0000000000000..65697682f722d
e0f94f
--- /dev/null
e0f94f
+++ b/include/libipset/xlate.h
e0f94f
@@ -0,0 +1,6 @@
e0f94f
+#ifndef LIBIPSET_XLATE_H
e0f94f
+#define LIBIPSET_XLATE_H
e0f94f
+
e0f94f
+int ipset_xlate_argv(struct ipset *ipset, int argc, char *argv[]);
e0f94f
+
e0f94f
+#endif
e0f94f
diff --git a/lib/ipset.c b/lib/ipset.c
e0f94f
index 5232d8b76c46f..73e67db88e0d1 100644
e0f94f
--- a/lib/ipset.c
e0f94f
+++ b/lib/ipset.c
e0f94f
@@ -13,6 +13,7 @@
e0f94f
 #include <stdio.h>				/* printf */
e0f94f
 #include <stdlib.h>				/* exit */
e0f94f
 #include <string.h>				/* str* */
e0f94f
+#include <inttypes.h>				/* PRIu64 */
e0f94f
 
e0f94f
 #include <config.h>
e0f94f
 
e0f94f
@@ -28,6 +29,7 @@
e0f94f
 #include <libipset/utils.h>			/* STREQ */
e0f94f
 #include <libipset/ipset.h>			/* prototypes */
e0f94f
 #include <libipset/ip_set_compiler.h>		/* compiler attributes */
e0f94f
+#include <libipset/list_sort.h>			/* lists */
e0f94f
 
e0f94f
 static char program_name[] = PACKAGE;
e0f94f
 static char program_version[] = PACKAGE_VERSION;
e0f94f
@@ -50,6 +52,17 @@ struct ipset {
e0f94f
 	char *newargv[MAX_ARGS];
e0f94f
 	int newargc;
e0f94f
 	const char *filename;			/* Input/output filename */
e0f94f
+	bool xlate;
e0f94f
+	struct list_head xlate_sets;
e0f94f
+};
e0f94f
+
e0f94f
+struct ipset_xlate_set {
e0f94f
+	struct list_head list;
e0f94f
+	char name[IPSET_MAXNAMELEN];
e0f94f
+	uint8_t netmask;
e0f94f
+	uint8_t family;
e0f94f
+	bool interval;
e0f94f
+	const struct ipset_type *type;
e0f94f
 };
e0f94f
 
e0f94f
 /* Commands and environment options */
e0f94f
@@ -923,6 +936,31 @@ static const char *cmd_prefix[] = {
e0f94f
 	[IPSET_TEST]   = "test   SETNAME",
e0f94f
 };
e0f94f
 
e0f94f
+static const struct ipset_xlate_set *
e0f94f
+ipset_xlate_set_get(struct ipset *ipset, const char *name)
e0f94f
+{
e0f94f
+	const struct ipset_xlate_set *set;
e0f94f
+
e0f94f
+	list_for_each_entry(set, &ipset->xlate_sets, list) {
e0f94f
+		if (!strcmp(set->name, name))
e0f94f
+			return set;
e0f94f
+	}
e0f94f
+
e0f94f
+	return NULL;
e0f94f
+}
e0f94f
+
e0f94f
+static const struct ipset_type *ipset_xlate_type_get(struct ipset *ipset,
e0f94f
+						     const char *name)
e0f94f
+{
e0f94f
+	const struct ipset_xlate_set *set;
e0f94f
+
e0f94f
+	set = ipset_xlate_set_get(ipset, name);
e0f94f
+	if (!set)
e0f94f
+		return NULL;
e0f94f
+
e0f94f
+	return set->type;
e0f94f
+}
e0f94f
+
e0f94f
 static int
e0f94f
 ipset_parser(struct ipset *ipset, int oargc, char *oargv[])
e0f94f
 {
e0f94f
@@ -1241,7 +1279,12 @@ ipset_parser(struct ipset *ipset, int oargc, char *oargv[])
e0f94f
 		if (ret < 0)
e0f94f
 			return ipset->standard_error(ipset, p);
e0f94f
 
e0f94f
-		type = ipset_type_get(session, cmd);
e0f94f
+		if (!ipset->xlate) {
e0f94f
+			type = ipset_type_get(session, cmd);
e0f94f
+		} else {
e0f94f
+			type = ipset_xlate_type_get(ipset, arg0);
e0f94f
+			ipset_session_data_set(session, IPSET_OPT_TYPE, type);
e0f94f
+		}
e0f94f
 		if (type == NULL)
e0f94f
 			return ipset->standard_error(ipset, p);
e0f94f
 
e0f94f
@@ -1474,6 +1517,9 @@ ipset_init(void)
e0f94f
 		return NULL;
e0f94f
 	}
e0f94f
 	ipset_custom_printf(ipset, NULL, NULL, NULL, NULL);
e0f94f
+
e0f94f
+	INIT_LIST_HEAD(&ipset->xlate_sets);
e0f94f
+
e0f94f
 	return ipset;
e0f94f
 }
e0f94f
 
e0f94f
@@ -1488,6 +1534,8 @@ ipset_init(void)
e0f94f
 int
e0f94f
 ipset_fini(struct ipset *ipset)
e0f94f
 {
e0f94f
+	struct ipset_xlate_set *xlate_set, *next;
e0f94f
+
e0f94f
 	assert(ipset);
e0f94f
 
e0f94f
 	if (ipset->session)
e0f94f
@@ -1496,6 +1544,497 @@ ipset_fini(struct ipset *ipset)
e0f94f
 	if (ipset->newargv[0])
e0f94f
 		free(ipset->newargv[0]);
e0f94f
 
e0f94f
+	list_for_each_entry_safe(xlate_set, next, &ipset->xlate_sets, list)
e0f94f
+		free(xlate_set);
e0f94f
+
e0f94f
 	free(ipset);
e0f94f
 	return 0;
e0f94f
 }
e0f94f
+
e0f94f
+/* Ignore the set family, use inet. */
e0f94f
+static const char *ipset_xlate_family(uint8_t family)
e0f94f
+{
e0f94f
+	return "inet";
e0f94f
+}
e0f94f
+
e0f94f
+enum ipset_xlate_set_type {
e0f94f
+	IPSET_XLATE_TYPE_UNKNOWN	= 0,
e0f94f
+	IPSET_XLATE_TYPE_HASH_MAC,
e0f94f
+	IPSET_XLATE_TYPE_HASH_IP,
e0f94f
+	IPSET_XLATE_TYPE_HASH_IP_MAC,
e0f94f
+	IPSET_XLATE_TYPE_HASH_NET_IFACE,
e0f94f
+	IPSET_XLATE_TYPE_HASH_NET_PORT,
e0f94f
+	IPSET_XLATE_TYPE_HASH_NET_PORT_NET,
e0f94f
+	IPSET_XLATE_TYPE_HASH_NET_NET,
e0f94f
+	IPSET_XLATE_TYPE_HASH_NET,
e0f94f
+	IPSET_XLATE_TYPE_HASH_IP_PORT_NET,
e0f94f
+	IPSET_XLATE_TYPE_HASH_IP_PORT_IP,
e0f94f
+	IPSET_XLATE_TYPE_HASH_IP_MARK,
e0f94f
+	IPSET_XLATE_TYPE_HASH_IP_PORT,
e0f94f
+	IPSET_XLATE_TYPE_BITMAP_PORT,
e0f94f
+	IPSET_XLATE_TYPE_BITMAP_IP_MAC,
e0f94f
+	IPSET_XLATE_TYPE_BITMAP_IP,
e0f94f
+};
e0f94f
+
e0f94f
+static enum ipset_xlate_set_type ipset_xlate_set_type(const char *typename)
e0f94f
+{
e0f94f
+	if (!strcmp(typename, "hash:mac"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_MAC;
e0f94f
+	else if (!strcmp(typename, "hash:ip"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP;
e0f94f
+	else if (!strcmp(typename, "hash:ip,mac"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP_MAC;
e0f94f
+	else if (!strcmp(typename, "hash:net,iface"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_NET_IFACE;
e0f94f
+	else if (!strcmp(typename, "hash:net,port"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_NET_PORT;
e0f94f
+	else if (!strcmp(typename, "hash:net,port,net"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_NET_PORT_NET;
e0f94f
+	else if (!strcmp(typename, "hash:net,net"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_NET_NET;
e0f94f
+	else if (!strcmp(typename, "hash:net"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_NET;
e0f94f
+	else if (!strcmp(typename, "hash:ip,port,net"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP_PORT_NET;
e0f94f
+	else if (!strcmp(typename, "hash:ip,port,ip"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP_PORT_IP;
e0f94f
+	else if (!strcmp(typename, "hash:ip,mark"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP_MARK;
e0f94f
+	else if (!strcmp(typename, "hash:ip,port"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP_PORT;
e0f94f
+	else if (!strcmp(typename, "hash:ip"))
e0f94f
+		return IPSET_XLATE_TYPE_HASH_IP;
e0f94f
+	else if (!strcmp(typename, "bitmap:port"))
e0f94f
+		return IPSET_XLATE_TYPE_BITMAP_PORT;
e0f94f
+	else if (!strcmp(typename, "bitmap:ip,mac"))
e0f94f
+		return IPSET_XLATE_TYPE_BITMAP_IP_MAC;
e0f94f
+	else if (!strcmp(typename, "bitmap:ip"))
e0f94f
+		return IPSET_XLATE_TYPE_BITMAP_IP;
e0f94f
+
e0f94f
+	return IPSET_XLATE_TYPE_UNKNOWN;
e0f94f
+}
e0f94f
+
e0f94f
+#define NFT_SET_INTERVAL	(1 << 0)
e0f94f
+
e0f94f
+static const char *
e0f94f
+ipset_xlate_type_to_nftables(int family, enum ipset_xlate_set_type type,
e0f94f
+			     uint32_t *flags)
e0f94f
+{
e0f94f
+	switch (type) {
e0f94f
+	case IPSET_XLATE_TYPE_HASH_MAC:
e0f94f
+		return "ether_addr";
e0f94f
+	case IPSET_XLATE_TYPE_HASH_IP:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_IP_MAC:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . ether_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . ether_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_NET_IFACE:
e0f94f
+		*flags |= NFT_SET_INTERVAL;
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . ifname";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . ifname";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_NET_PORT:
e0f94f
+		*flags |= NFT_SET_INTERVAL;
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . inet_proto . inet_service";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . inet_proto . inet_service";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_NET_PORT_NET:
e0f94f
+		*flags |= NFT_SET_INTERVAL;
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . inet_proto . inet_service . ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . inet_proto . inet_service . ipv6_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_NET_NET:
e0f94f
+		*flags |= NFT_SET_INTERVAL;
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . ipv6_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_NET:
e0f94f
+		*flags |= NFT_SET_INTERVAL;
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_IP_PORT_NET:
e0f94f
+		*flags |= NFT_SET_INTERVAL;
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . inet_proto . inet_service . ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . inet_proto . inet_service . ipv6_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_IP_PORT_IP:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . inet_proto . inet_service . ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . inet_proto . inet_service . ipv6_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_IP_MARK:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . mark";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . mark";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_HASH_IP_PORT:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . inet_proto . inet_service";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . inet_proto . inet_service";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_BITMAP_PORT:
e0f94f
+		return "inet_service";
e0f94f
+	case IPSET_XLATE_TYPE_BITMAP_IP_MAC:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr . ether_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr . ether_addr";
e0f94f
+		break;
e0f94f
+	case IPSET_XLATE_TYPE_BITMAP_IP:
e0f94f
+		if (family == AF_INET)
e0f94f
+			return "ipv4_addr";
e0f94f
+		else if (family == AF_INET6)
e0f94f
+			return "ipv6_addr";
e0f94f
+		break;
e0f94f
+	}
e0f94f
+	/* This should not ever happen. */
e0f94f
+	return "unknown";
e0f94f
+}
e0f94f
+
e0f94f
+static int ipset_xlate(struct ipset *ipset, enum ipset_cmd cmd,
e0f94f
+		       const char *table)
e0f94f
+{
e0f94f
+	const char *set, *typename, *nft_type;
e0f94f
+	const struct ipset_type *ipset_type;
e0f94f
+	struct ipset_xlate_set *xlate_set;
e0f94f
+	enum ipset_xlate_set_type type;
e0f94f
+	struct ipset_session *session;
e0f94f
+	const uint32_t *cadt_flags;
e0f94f
+	const uint32_t *timeout;
e0f94f
+	const uint32_t *maxelem;
e0f94f
+	struct ipset_data *data;
e0f94f
+	const uint8_t *netmask;
e0f94f
+	const char *comment;
e0f94f
+	uint32_t flags = 0;
e0f94f
+	uint8_t family;
e0f94f
+	char buf[64];
e0f94f
+	bool concat;
e0f94f
+	char *term;
e0f94f
+	int i;
e0f94f
+
e0f94f
+	session = ipset_session(ipset);
e0f94f
+	data = ipset_session_data(session);
e0f94f
+
e0f94f
+	set = ipset_data_get(data, IPSET_SETNAME);
e0f94f
+	family = ipset_data_family(data);
e0f94f
+
e0f94f
+	switch (cmd) {
e0f94f
+	case IPSET_CMD_CREATE:
e0f94f
+		/* Not supported. */
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_MARKMASK)) {
e0f94f
+			printf("# %s", ipset->cmdline);
e0f94f
+			break;
e0f94f
+		}
e0f94f
+		cadt_flags = ipset_data_get(data, IPSET_OPT_CADT_FLAGS);
e0f94f
+
e0f94f
+		/* Ignore:
e0f94f
+		 * - IPSET_FLAG_WITH_COMMENT
e0f94f
+		 * - IPSET_FLAG_WITH_FORCEADD
e0f94f
+		 */
e0f94f
+		if (cadt_flags &&
e0f94f
+		    (*cadt_flags & (IPSET_FLAG_BEFORE |
e0f94f
+				   IPSET_FLAG_PHYSDEV |
e0f94f
+				   IPSET_FLAG_NOMATCH |
e0f94f
+				   IPSET_FLAG_WITH_SKBINFO |
e0f94f
+				   IPSET_FLAG_IFACE_WILDCARD))) {
e0f94f
+			printf("# %s", ipset->cmdline);
e0f94f
+			break;
e0f94f
+		}
e0f94f
+
e0f94f
+		typename = ipset_data_get(data, IPSET_OPT_TYPENAME);
e0f94f
+		type = ipset_xlate_set_type(typename);
e0f94f
+		nft_type = ipset_xlate_type_to_nftables(family, type, &flags);
e0f94f
+
e0f94f
+		printf("add set %s %s %s { type %s; ",
e0f94f
+		       ipset_xlate_family(family), table, set, nft_type);
e0f94f
+		if (cadt_flags) {
e0f94f
+			if (*cadt_flags & IPSET_FLAG_WITH_COUNTERS)
e0f94f
+				printf("counter; ");
e0f94f
+		}
e0f94f
+		timeout = ipset_data_get(data, IPSET_OPT_TIMEOUT);
e0f94f
+		if (timeout)
e0f94f
+			printf("timeout %us; ", *timeout);
e0f94f
+		maxelem = ipset_data_get(data, IPSET_OPT_MAXELEM);
e0f94f
+		if (maxelem)
e0f94f
+			printf("size %u; ", *maxelem);
e0f94f
+
e0f94f
+		netmask = ipset_data_get(data, IPSET_OPT_NETMASK);
e0f94f
+		if (netmask &&
e0f94f
+		    ((family == AF_INET && *netmask < 32) ||
e0f94f
+		     (family == AF_INET6 && *netmask < 128)))
e0f94f
+			flags |= NFT_SET_INTERVAL;
e0f94f
+
e0f94f
+		if (flags & NFT_SET_INTERVAL)
e0f94f
+			printf("flags interval; ");
e0f94f
+
e0f94f
+		/* These create-specific options are safe to be ignored:
e0f94f
+		 * - IPSET_OPT_GC
e0f94f
+		 * - IPSET_OPT_HASHSIZE
e0f94f
+		 * - IPSET_OPT_PROBES
e0f94f
+		 * - IPSET_OPT_RESIZE
e0f94f
+		 * - IPSET_OPT_SIZE
e0f94f
+		 * - IPSET_OPT_FORCEADD
e0f94f
+		 *
e0f94f
+		 * Ranges and CIDR are safe to be ignored too:
e0f94f
+		 * - IPSET_OPT_IP_FROM
e0f94f
+		 * - IPSET_OPT_IP_TO
e0f94f
+		 * - IPSET_OPT_PORT_FROM
e0f94f
+		 * - IPSET_OPT_PORT_TO
e0f94f
+		 */
e0f94f
+
e0f94f
+		printf("}\n");
e0f94f
+
e0f94f
+		xlate_set = calloc(1, sizeof(*xlate_set));
e0f94f
+		if (!xlate_set)
e0f94f
+			return -1;
e0f94f
+
e0f94f
+		snprintf(xlate_set->name, sizeof(xlate_set->name), "%s", set);
e0f94f
+		ipset_type = ipset_types();
e0f94f
+		while (ipset_type) {
e0f94f
+			if (!strcmp(ipset_type->name, typename))
e0f94f
+				break;
e0f94f
+			ipset_type = ipset_type->next;
e0f94f
+		}
e0f94f
+
e0f94f
+		xlate_set->family = family;
e0f94f
+		xlate_set->type = ipset_type;
e0f94f
+		if (netmask) {
e0f94f
+			xlate_set->netmask = *netmask;
e0f94f
+			xlate_set->interval = true;
e0f94f
+		}
e0f94f
+		list_add_tail(&xlate_set->list, &ipset->xlate_sets);
e0f94f
+		break;
e0f94f
+	case IPSET_CMD_DESTROY:
e0f94f
+		printf("del set %s %s %s\n",
e0f94f
+		       ipset_xlate_family(family), table, set);
e0f94f
+		break;
e0f94f
+	case IPSET_CMD_FLUSH:
e0f94f
+		if (!set) {
e0f94f
+			printf("# %s", ipset->cmdline);
e0f94f
+		} else {
e0f94f
+			printf("flush set %s %s %s\n",
e0f94f
+			       ipset_xlate_family(family), table, set);
e0f94f
+		}
e0f94f
+		break;
e0f94f
+	case IPSET_CMD_RENAME:
e0f94f
+		printf("# %s", ipset->cmdline);
e0f94f
+		return -1;
e0f94f
+	case IPSET_CMD_SWAP:
e0f94f
+		printf("# %s", ipset->cmdline);
e0f94f
+		return -1;
e0f94f
+	case IPSET_CMD_LIST:
e0f94f
+		if (!set) {
e0f94f
+			printf("list sets %s\n",
e0f94f
+			       ipset_xlate_family(family), table);
e0f94f
+		} else {
e0f94f
+			printf("list set %s %s %s\n",
e0f94f
+			       ipset_xlate_family(family), table, set);
e0f94f
+		}
e0f94f
+		break;
e0f94f
+	case IPSET_CMD_SAVE:
e0f94f
+		printf("# %s", ipset->cmdline);
e0f94f
+		return -1;
e0f94f
+	case IPSET_CMD_ADD:
e0f94f
+	case IPSET_CMD_DEL:
e0f94f
+	case IPSET_CMD_TEST:
e0f94f
+		/* Not supported. */
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_NOMATCH) ||
e0f94f
+		    ipset_data_test(data, IPSET_OPT_SKBINFO) ||
e0f94f
+		    ipset_data_test(data, IPSET_OPT_SKBMARK) ||
e0f94f
+		    ipset_data_test(data, IPSET_OPT_SKBPRIO) ||
e0f94f
+		    ipset_data_test(data, IPSET_OPT_SKBQUEUE) ||
e0f94f
+		    ipset_data_test(data, IPSET_OPT_IFACE_WILDCARD)) {
e0f94f
+			printf("# %s", ipset->cmdline);
e0f94f
+			break;
e0f94f
+		}
e0f94f
+		printf("%s element %s %s %s { ",
e0f94f
+		       cmd == IPSET_CMD_ADD ? "add" :
e0f94f
+				cmd == IPSET_CMD_DEL ? "delete" : "get",
e0f94f
+		       ipset_xlate_family(family), table, set);
e0f94f
+
e0f94f
+		typename = ipset_data_get(data, IPSET_OPT_TYPENAME);
e0f94f
+		type = ipset_xlate_set_type(typename);
e0f94f
+
e0f94f
+		xlate_set = (struct ipset_xlate_set *)
e0f94f
+				ipset_xlate_set_get(ipset, set);
e0f94f
+		if (xlate_set && xlate_set->interval)
e0f94f
+			netmask = &xlate_set->netmask;
e0f94f
+		else
e0f94f
+			netmask = NULL;
e0f94f
+
e0f94f
+		concat = false;
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_IP)) {
e0f94f
+			ipset_print_data(buf, sizeof(buf), data, IPSET_OPT_IP, 0);
e0f94f
+			printf("%s", buf);
e0f94f
+			if (netmask)
e0f94f
+				printf("/%u ", *netmask);
e0f94f
+			else
e0f94f
+				printf(" ");
e0f94f
+
e0f94f
+			concat = true;
e0f94f
+		}
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_MARK)) {
e0f94f
+			ipset_print_mark(buf, sizeof(buf), data, IPSET_OPT_MARK, 0);
e0f94f
+			printf("%s%s ", concat ? ". " : "", buf);
e0f94f
+		}
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_IFACE)) {
e0f94f
+			ipset_print_data(buf, sizeof(buf), data, IPSET_OPT_IFACE, 0);
e0f94f
+			printf("%s%s ", concat ? ". " : "", buf);
e0f94f
+		}
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_ETHER)) {
e0f94f
+			ipset_print_ether(buf, sizeof(buf), data, IPSET_OPT_ETHER, 0);
e0f94f
+			for (i = 0; i < strlen(buf); i++)
e0f94f
+				buf[i] = tolower(buf[i]);
e0f94f
+
e0f94f
+			printf("%s%s ", concat ? ". " : "", buf);
e0f94f
+			concat = true;
e0f94f
+		}
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_PORT)) {
e0f94f
+			ipset_print_proto_port(buf, sizeof(buf), data, IPSET_OPT_PORT, 0);
e0f94f
+			term = strchr(buf, ':');
e0f94f
+			if (term) {
e0f94f
+				*term = '\0';
e0f94f
+				printf("%s%s ", concat ? ". " : "", buf);
e0f94f
+			}
e0f94f
+			ipset_print_data(buf, sizeof(buf), data, IPSET_OPT_PORT, 0);
e0f94f
+			printf("%s%s ", concat ? ". " : "", buf);
e0f94f
+		}
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_IP2)) {
e0f94f
+			ipset_print_ip(buf, sizeof(buf), data, IPSET_OPT_IP2, 0);
e0f94f
+			printf("%s%s", concat ? ". " : "", buf);
e0f94f
+			if (netmask)
e0f94f
+				printf("/%u ", *netmask);
e0f94f
+			else
e0f94f
+				printf(" ");
e0f94f
+		}
e0f94f
+		if (ipset_data_test(data, IPSET_OPT_PACKETS) &&
e0f94f
+		    ipset_data_test(data, IPSET_OPT_BYTES)) {
e0f94f
+			const uint64_t *pkts, *bytes;
e0f94f
+
e0f94f
+			pkts = ipset_data_get(data, IPSET_OPT_PACKETS);
e0f94f
+			bytes = ipset_data_get(data, IPSET_OPT_BYTES);
e0f94f
+
e0f94f
+			printf("counter packets %" PRIu64 " bytes %" PRIu64 " ",
e0f94f
+			       *pkts, *bytes);
e0f94f
+		}
e0f94f
+		timeout = ipset_data_get(data, IPSET_OPT_TIMEOUT);
e0f94f
+		if (timeout)
e0f94f
+			printf("timeout %us ", *timeout);
e0f94f
+
e0f94f
+		comment = ipset_data_get(data, IPSET_OPT_ADT_COMMENT);
e0f94f
+		if (comment)
e0f94f
+			printf("comment \"%s\" ", comment);
e0f94f
+
e0f94f
+		printf("}\n");
e0f94f
+		break;
e0f94f
+	case IPSET_CMD_GET_BYNAME:
e0f94f
+		printf("# %s", ipset->cmdline);
e0f94f
+		return -1;
e0f94f
+	case IPSET_CMD_GET_BYINDEX:
e0f94f
+		printf("# %s", ipset->cmdline);
e0f94f
+		return -1;
e0f94f
+	default:
e0f94f
+		break;
e0f94f
+	}
e0f94f
+
e0f94f
+	return 0;
e0f94f
+}
e0f94f
+
e0f94f
+static int ipset_xlate_restore(struct ipset *ipset)
e0f94f
+{
e0f94f
+	struct ipset_session *session = ipset_session(ipset);
e0f94f
+	struct ipset_data *data = ipset_session_data(session);
e0f94f
+	void *p = ipset_session_printf_private(session);
e0f94f
+	const char *filename;
e0f94f
+	enum ipset_cmd cmd;
e0f94f
+	FILE *f = stdin;
e0f94f
+	int ret = 0;
e0f94f
+	char *c;
e0f94f
+
e0f94f
+	if (ipset->filename) {
e0f94f
+		f = fopen(ipset->filename, "r");
e0f94f
+		if (!f) {
e0f94f
+			fprintf(stderr, "cannot open file `%s'\n", filename);
e0f94f
+			return -1;
e0f94f
+		}
e0f94f
+	}
e0f94f
+
e0f94f
+	/* TODO: Allow to specify the table name other than 'global'. */
e0f94f
+	printf("add table inet global\n");
e0f94f
+
e0f94f
+	while (fgets(ipset->cmdline, sizeof(ipset->cmdline), f)) {
e0f94f
+		ipset->restore_line++;
e0f94f
+		c = ipset->cmdline;
e0f94f
+		while (isspace(c[0]))
e0f94f
+			c++;
e0f94f
+		if (c[0] == '\0' || c[0] == '#')
e0f94f
+			continue;
e0f94f
+		else if (STREQ(c, "COMMIT\n") || STREQ(c, "COMMIT\r\n"))
e0f94f
+			continue;
e0f94f
+
e0f94f
+		ret = build_argv(ipset, c);
e0f94f
+		if (ret < 0)
e0f94f
+			return ret;
e0f94f
+
e0f94f
+		cmd = ipset_parser(ipset, ipset->newargc, ipset->newargv);
e0f94f
+		if (cmd < 0)
e0f94f
+			ipset->standard_error(ipset, p);
e0f94f
+
e0f94f
+		/* TODO: Allow to specify the table name other than 'global'. */
e0f94f
+		ret = ipset_xlate(ipset, cmd, "global");
e0f94f
+		if (ret < 0)
e0f94f
+			break;
e0f94f
+
e0f94f
+		ipset_data_reset(data);
e0f94f
+	}
e0f94f
+
e0f94f
+	if (filename)
e0f94f
+		fclose(f);
e0f94f
+
e0f94f
+	return ret;
e0f94f
+}
e0f94f
+
e0f94f
+int ipset_xlate_argv(struct ipset *ipset, int argc, char *argv[])
e0f94f
+{
e0f94f
+	enum ipset_cmd cmd;
e0f94f
+	int ret;
e0f94f
+
e0f94f
+	ipset->xlate = true;
e0f94f
+
e0f94f
+	cmd = ipset_parser(ipset, argc, argv);
e0f94f
+	if (cmd < 0)
e0f94f
+		return cmd;
e0f94f
+
e0f94f
+	if (cmd == IPSET_CMD_RESTORE) {
e0f94f
+		ret = ipset_xlate_restore(ipset);
e0f94f
+	} else {
e0f94f
+		fprintf(stderr, "This command is not supported, "
e0f94f
+				"use `ipset-translate restore < file'\n");
e0f94f
+		ret = -1;
e0f94f
+	}
e0f94f
+
e0f94f
+	return ret;
e0f94f
+}
e0f94f
diff --git a/src/Makefile.am b/src/Makefile.am
e0f94f
index 438fcec0f1f10..95dea07701391 100644
e0f94f
--- a/src/Makefile.am
e0f94f
+++ b/src/Makefile.am
e0f94f
@@ -12,10 +12,16 @@ AM_LDFLAGS	= -static
e0f94f
 endif
e0f94f
 endif
e0f94f
 
e0f94f
-dist_man_MANS = ipset.8
e0f94f
+dist_man_MANS = ipset.8 ipset-translate.8
e0f94f
 
e0f94f
 sparse-check: $(ipset_SOURCES:.c=.d)
e0f94f
 
e0f94f
 %.d: %.c
e0f94f
 	$(IPSET_AM_V_CHECK)\
e0f94f
 	$(SPARSE) -I.. $(SPARSE_FLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) $< || :
e0f94f
+
e0f94f
+install-exec-hook:
e0f94f
+	${LN_S} -f "${sbindir}/ipset" "${DESTDIR}${sbindir}/ipset-translate";
e0f94f
+
e0f94f
+uninstall-hook:
e0f94f
+	rm -f ${DESTDIR}${sbindir}/ipset-translate
e0f94f
diff --git a/src/ipset-translate.8 b/src/ipset-translate.8
e0f94f
new file mode 100644
e0f94f
index 0000000000000..bb4e737e14806
e0f94f
--- /dev/null
e0f94f
+++ b/src/ipset-translate.8
e0f94f
@@ -0,0 +1,91 @@
e0f94f
+.\"
e0f94f
+.\" (C) Copyright 2021, Pablo Neira Ayuso <pablo@netfilter.org>
e0f94f
+.\"
e0f94f
+.\" %%%LICENSE_START(GPLv2+_DOC_FULL)
e0f94f
+.\" This is free documentation; you can redistribute it and/or
e0f94f
+.\" modify it under the terms of the GNU General Public License as
e0f94f
+.\" published by the Free Software Foundation; either version 2 of
e0f94f
+.\" the License, or (at your option) any later version.
e0f94f
+.\"
e0f94f
+.\" The GNU General Public License's references to "object code"
e0f94f
+.\" and "executables" are to be interpreted as the output of any
e0f94f
+.\" document formatting or typesetting system, including
e0f94f
+.\" intermediate and printed output.
e0f94f
+.\"
e0f94f
+.\" This manual is distributed in the hope that it will be useful,
e0f94f
+.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
e0f94f
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e0f94f
+.\" GNU General Public License for more details.
e0f94f
+.\"
e0f94f
+.\" You should have received a copy of the GNU General Public
e0f94f
+.\" License along with this manual; if not, see
e0f94f
+.\" <http://www.gnu.org/licenses/>.
e0f94f
+.\" %%%LICENSE_END
e0f94f
+.\"
e0f94f
+.TH IPSET-TRANSLATE 8 "May 31, 2021"
e0f94f
+
e0f94f
+.SH NAME
e0f94f
+ipset-translate \(em translation tool to migrate from ipset to nftables
e0f94f
+.SH DESCRIPTION
e0f94f
+This tool allows system administrators to translate a given IP sets file
e0f94f
+to \fBnftables(8)\fP.
e0f94f
+
e0f94f
+The only available command is:
e0f94f
+
e0f94f
+.IP \[bu] 2
e0f94f
+ipset-translate restores < file.ipt
e0f94f
+
e0f94f
+.SH USAGE
e0f94f
+The \fBipset-translate\fP tool reads an IP sets file in the syntax produced by
e0f94f
+\fBipset(8)\fP save. No set modifications occur, this tool is a text converter.
e0f94f
+
e0f94f
+.SH EXAMPLES
e0f94f
+Basic operation examples.
e0f94f
+
e0f94f
+Single command translation, assuming the original file:
e0f94f
+
e0f94f
+.nf
e0f94f
+create test1 hash:ip,port family inet counters timeout 300 hashsize 1024 maxelem 65536 bucketsize 12 initval 0xb5c4be5d
e0f94f
+add test1 1.1.1.1,udp:20
e0f94f
+add test1 1.1.1.1,21
e0f94f
+create test2 hash:ip,port family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xb5c4be5d
e0f94f
+.fi
e0f94f
+
e0f94f
+which results in the following translation:
e0f94f
+
e0f94f
+.nf
e0f94f
+root@machine:~# ipset-translate restore < file.ipt
e0f94f
+add set inet global test1 { type ipv4_addr . inet_proto . inet_service; counter; timeout 300s; size 65536; }
e0f94f
+add element inet global test1 { 1.1.1.1 . udp . 20 }
e0f94f
+add element inet global test1 { 1.1.1.1 . tcp . 21 }
e0f94f
+add set inet global test2 { type ipv4_addr . inet_proto . inet_service; size 65536; }
e0f94f
+.fi
e0f94f
+
e0f94f
+.SH LIMITATIONS
e0f94f
+A few IP sets options may be not supported because they are not yet implemented
e0f94f
+in \fBnftables(8)\fP.
e0f94f
+
e0f94f
+Contrary to \fBnftables(8)\fP, IP sets are not attached to a specific table.
e0f94f
+The translation utility assumes that sets are created in a table whose name
e0f94f
+is \fBglobal\fP and family is \fBinet\fP. You might want to update the
e0f94f
+resulting translation to use a different table name and family for your sets.
e0f94f
+
e0f94f
+To get up-to-date information about this, please head to
e0f94f
+\fBhttps://wiki.nftables.org/\fP.
e0f94f
+
e0f94f
+.SH SEE ALSO
e0f94f
+\fBnft(8)\fP, \fBipset(8)\fP
e0f94f
+
e0f94f
+.SH AUTHORS
e0f94f
+The nftables framework has been written by the Netfilter Project
e0f94f
+(https://www.netfilter.org).
e0f94f
+
e0f94f
+This manual page was written by Pablo Neira Ayuso
e0f94f
+<pablo@netfilter.org>.
e0f94f
+
e0f94f
+This documentation is free/libre under the terms of the GPLv2+.
e0f94f
+
e0f94f
+This tool was funded through the NGI0 PET Fund, a fund established by NLnet with
e0f94f
+financial support from the European Commission's Next Generation Internet
e0f94f
+programme, under the aegis of DG Communications Networks, Content and Technology
e0f94f
+under grant agreement No 825310.
e0f94f
diff --git a/src/ipset.c b/src/ipset.c
e0f94f
index ee36a06e595de..6d42b60d2fe9d 100644
e0f94f
--- a/src/ipset.c
e0f94f
+++ b/src/ipset.c
e0f94f
@@ -9,9 +9,11 @@
e0f94f
 #include <assert.h>			/* assert */
e0f94f
 #include <stdio.h>			/* fprintf */
e0f94f
 #include <stdlib.h>			/* exit */
e0f94f
+#include <string.h>			/* strcmp */
e0f94f
 
e0f94f
 #include <config.h>
e0f94f
 #include <libipset/ipset.h>		/* ipset library */
e0f94f
+#include <libipset/xlate.h>		/* translate to nftables */
e0f94f
 
e0f94f
 int
e0f94f
 main(int argc, char *argv[])
e0f94f
@@ -29,7 +31,11 @@ main(int argc, char *argv[])
e0f94f
 		exit(1);
e0f94f
 	}
e0f94f
 
e0f94f
-	ret = ipset_parse_argv(ipset, argc, argv);
e0f94f
+	if (!strcmp(argv[0], "ipset-translate")) {
e0f94f
+		ret = ipset_xlate_argv(ipset, argc, argv);
e0f94f
+	} else {
e0f94f
+		ret = ipset_parse_argv(ipset, argc, argv);
e0f94f
+	}
e0f94f
 
e0f94f
 	ipset_fini(ipset);
e0f94f
 
e0f94f
-- 
e0f94f
2.38.0
e0f94f