80ddfa2
From 746a409113ab837c55b8cfaf819c7905c8f9e295 Mon Sep 17 00:00:00 2001
80ddfa2
From: Bernie Harris <bernie.harris@alliedtelesis.co.nz>
80ddfa2
Date: Wed, 21 Mar 2018 15:42:29 +1300
80ddfa2
Subject: [PATCH] extensions: Add string filter to ebtables
80ddfa2
80ddfa2
This patch is part of a proposal to add a string filter to
80ddfa2
ebtables, which would be similar to the string filter in
80ddfa2
iptables.
80ddfa2
80ddfa2
Like iptables, the ebtables filter uses the xt_string module,
80ddfa2
however some modifications have been made for this to work
80ddfa2
correctly.
80ddfa2
80ddfa2
Currently ebtables assumes that the revision number of all match
80ddfa2
modules is 0. The xt_string module doesn't register a match with
80ddfa2
revision 0 so the solution is to modify ebtables to allow
80ddfa2
extensions to specify a revision number, similar to iptables.
80ddfa2
This gets passed down to the kernel, which is then able to find
80ddfa2
the match module correctly.
80ddfa2
80ddfa2
Signed-off-by: Bernie Harris <bernie.harris@alliedtelesis.co.nz>
80ddfa2
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
80ddfa2
Signed-off-by: Phil Sutter <psutter@redhat.com>
80ddfa2
---
80ddfa2
 ebtables.8              |  20 +++
80ddfa2
 extensions/Makefile     |   2 +-
80ddfa2
 extensions/ebt_string.c | 319 ++++++++++++++++++++++++++++++++++++++++
80ddfa2
 include/ebtables.h      |  16 +-
80ddfa2
 include/ebtables_u.h    |   1 +
80ddfa2
 libebtc.c               |   6 +-
80ddfa2
 6 files changed, 359 insertions(+), 5 deletions(-)
80ddfa2
 create mode 100644 extensions/ebt_string.c
80ddfa2
80ddfa2
diff --git a/ebtables.8 b/ebtables.8
80ddfa2
index 45a88b2347de6..00c4562d20036 100644
80ddfa2
--- a/ebtables.8
80ddfa2
+++ b/ebtables.8
80ddfa2
@@ -810,6 +810,26 @@ The hello time timer (0-65535) range.
80ddfa2
 .TP
80ddfa2
 .BR "--stp-forward-delay " "[!] [\fIdelay\fP][:\fIdelay\fP]"
80ddfa2
 The forward delay timer (0-65535) range.
80ddfa2
+.SS string
80ddfa2
+This module matches on a given string using some pattern matching strategy.
80ddfa2
+.TP
80ddfa2
+.BR "--string-algo " "\fIalgorithm\fP"
80ddfa2
+The pattern matching strategy. (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)
80ddfa2
+.TP
80ddfa2
+.BR "--string-from " "\fIoffset\fP"
80ddfa2
+The lowest offset from which a match can start. (default: 0)
80ddfa2
+.TP
80ddfa2
+.BR "--string-to " "\fIoffset\fP"
80ddfa2
+The highest offset from which a match can start. (default: size of frame)
80ddfa2
+.TP
80ddfa2
+.BR "--string " "[!] \fIpattern\fP"
80ddfa2
+Matches the given pattern.
80ddfa2
+.TP
80ddfa2
+.BR "--string-hex " "[!] \fIpattern\fP"
80ddfa2
+Matches the given pattern in hex notation, e.g. '|0D 0A|', '|0D0A|', 'www|09|netfilter|03|org|00|'
80ddfa2
+.TP
80ddfa2
+.BR "--string-icase"
80ddfa2
+Ignore case when searching.
80ddfa2
 .SS vlan
80ddfa2
 Specify 802.1Q Tag Control Information fields.
80ddfa2
 The protocol must be specified as
80ddfa2
diff --git a/extensions/Makefile b/extensions/Makefile
80ddfa2
index b3548e81eca85..60a70a2298357 100644
80ddfa2
--- a/extensions/Makefile
80ddfa2
+++ b/extensions/Makefile
80ddfa2
@@ -1,7 +1,7 @@
80ddfa2
 #! /usr/bin/make
80ddfa2
 
80ddfa2
 EXT_FUNC+=802_3 nat arp arpreply ip ip6 standard log redirect vlan mark_m mark \
80ddfa2
-          pkttype stp among limit ulog nflog
80ddfa2
+          pkttype stp among limit ulog nflog string
80ddfa2
 EXT_TABLES+=filter nat broute
80ddfa2
 EXT_OBJS+=$(foreach T,$(EXT_FUNC), extensions/ebt_$(T).o)
80ddfa2
 EXT_OBJS+=$(foreach T,$(EXT_TABLES), extensions/ebtable_$(T).o)
80ddfa2
diff --git a/extensions/ebt_string.c b/extensions/ebt_string.c
80ddfa2
new file mode 100644
80ddfa2
index 0000000000000..793f5df312f10
80ddfa2
--- /dev/null
80ddfa2
+++ b/extensions/ebt_string.c
80ddfa2
@@ -0,0 +1,319 @@
80ddfa2
+/* ebt_string
80ddfa2
+ *
80ddfa2
+ * Author:
80ddfa2
+ * Bernie Harris <bernie.harris@alliedtelesis.co.nz>
80ddfa2
+ *
80ddfa2
+ * February, 2018
80ddfa2
+ *
80ddfa2
+ * Based on:
80ddfa2
+ *  libxt_string.c, Copyright (C) 2000 Emmanuel Roger  <winfield@freegates.be>
80ddfa2
+ */
80ddfa2
+
80ddfa2
+#include <stdio.h>
80ddfa2
+#include <stdlib.h>
80ddfa2
+#include <string.h>
80ddfa2
+#include <stdint.h>
80ddfa2
+#include <getopt.h>
80ddfa2
+#include <netdb.h>
80ddfa2
+#include <ctype.h>
80ddfa2
+#include "../include/ebtables_u.h"
80ddfa2
+#include <linux/if_packet.h>
80ddfa2
+#include <linux/netfilter/xt_string.h>
80ddfa2
+
80ddfa2
+#define STRING_FROM  '1'
80ddfa2
+#define STRING_TO    '2'
80ddfa2
+#define STRING_ALGO  '3'
80ddfa2
+#define STRING_ICASE '4'
80ddfa2
+#define STRING       '5'
80ddfa2
+#define STRING_HEX   '6'
80ddfa2
+#define OPT_STRING_FROM  (1 << 0)
80ddfa2
+#define OPT_STRING_TO    (1 << 1)
80ddfa2
+#define OPT_STRING_ALGO  (1 << 2)
80ddfa2
+#define OPT_STRING_ICASE (1 << 3)
80ddfa2
+#define OPT_STRING       (1 << 4)
80ddfa2
+#define OPT_STRING_HEX   (1 << 5)
80ddfa2
+
80ddfa2
+static const struct option opts[] =
80ddfa2
+{
80ddfa2
+	{ "string-from"             , required_argument, 0, STRING_FROM },
80ddfa2
+	{ "string-to"               , required_argument, 0, STRING_TO },
80ddfa2
+	{ "string-algo"             , required_argument, 0, STRING_ALGO },
80ddfa2
+	{ "string-icase"            , no_argument,       0, STRING_ICASE },
80ddfa2
+	{ "string"                  , required_argument, 0, STRING },
80ddfa2
+	{ "string-hex"              , required_argument, 0, STRING_HEX },
80ddfa2
+	{ 0 }
80ddfa2
+};
80ddfa2
+
80ddfa2
+static void print_help()
80ddfa2
+{
80ddfa2
+	printf(
80ddfa2
+"string options:\n"
80ddfa2
+"--string-from offset    : Offset to start searching from (default: 0)\n"
80ddfa2
+"--string-to   offset    : Offset to stop searching (default: packet size)\n"
80ddfa2
+"--string-algo algorithm : Algorithm (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)\n"
80ddfa2
+"--string-icase          : Ignore case when searching\n"
80ddfa2
+"--string     [!] string : Match a string in a packet\n"
80ddfa2
+"--string-hex [!] string : Match a hex string in a packet, e.g. |0D 0A|, |0D0A|, netfilter|03|org\n");
80ddfa2
+}
80ddfa2
+
80ddfa2
+static void init(struct ebt_entry_match *match)
80ddfa2
+{
80ddfa2
+	struct xt_string_info *info = (struct xt_string_info *)match->data;
80ddfa2
+
80ddfa2
+	info->to_offset = UINT16_MAX;
80ddfa2
+}
80ddfa2
+
80ddfa2
+static void parse_string(const char *s, struct xt_string_info *info)
80ddfa2
+{
80ddfa2
+	/* xt_string does not need \0 at the end of the pattern */
80ddfa2
+	if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) {
80ddfa2
+		strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE);
80ddfa2
+		info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE);
80ddfa2
+		return;
80ddfa2
+	}
80ddfa2
+	ebt_print_error2("STRING too long \"%s\"", s);
80ddfa2
+}
80ddfa2
+
80ddfa2
+static void parse_hex_string(const char *s, struct xt_string_info *info)
80ddfa2
+{
80ddfa2
+	int i=0, slen, sindex=0, schar;
80ddfa2
+	short hex_f = 0, literal_f = 0;
80ddfa2
+	char hextmp[3];
80ddfa2
+
80ddfa2
+	slen = strlen(s);
80ddfa2
+
80ddfa2
+	if (slen == 0) {
80ddfa2
+		ebt_print_error2("STRING must contain at least one char");
80ddfa2
+	}
80ddfa2
+
80ddfa2
+	while (i < slen) {
80ddfa2
+		if (s[i] == '\\' && !hex_f) {
80ddfa2
+			literal_f = 1;
80ddfa2
+		} else if (s[i] == '\\') {
80ddfa2
+			ebt_print_error2("Cannot include literals in hex data");
80ddfa2
+		} else if (s[i] == '|') {
80ddfa2
+			if (hex_f)
80ddfa2
+				hex_f = 0;
80ddfa2
+			else {
80ddfa2
+				hex_f = 1;
80ddfa2
+				/* get past any initial whitespace just after the '|' */
80ddfa2
+				while (s[i+1] == ' ')
80ddfa2
+					i++;
80ddfa2
+			}
80ddfa2
+			if (i+1 >= slen)
80ddfa2
+				break;
80ddfa2
+			else
80ddfa2
+				i++;  /* advance to the next character */
80ddfa2
+		}
80ddfa2
+
80ddfa2
+		if (literal_f) {
80ddfa2
+			if (i+1 >= slen) {
80ddfa2
+				ebt_print_error2("Bad literal placement at end of string");
80ddfa2
+			}
80ddfa2
+			info->pattern[sindex] = s[i+1];
80ddfa2
+			i += 2;  /* skip over literal char */
80ddfa2
+			literal_f = 0;
80ddfa2
+		} else if (hex_f) {
80ddfa2
+			if (i+1 >= slen) {
80ddfa2
+				ebt_print_error2("Odd number of hex digits");
80ddfa2
+			}
80ddfa2
+			if (i+2 >= slen) {
80ddfa2
+				/* must end with a "|" */
80ddfa2
+				ebt_print_error2("Invalid hex block");
80ddfa2
+			}
80ddfa2
+			if (! isxdigit(s[i])) /* check for valid hex char */
80ddfa2
+				ebt_print_error2("Invalid hex char '%c'", s[i]);
80ddfa2
+			if (! isxdigit(s[i+1])) /* check for valid hex char */
80ddfa2
+				ebt_print_error2("Invalid hex char '%c'", s[i+1]);
80ddfa2
+			hextmp[0] = s[i];
80ddfa2
+			hextmp[1] = s[i+1];
80ddfa2
+			hextmp[2] = '\0';
80ddfa2
+			if (! sscanf(hextmp, "%x", &schar))
80ddfa2
+				ebt_print_error2("Invalid hex char `%c'", s[i]);
80ddfa2
+			info->pattern[sindex] = (char) schar;
80ddfa2
+			if (s[i+2] == ' ')
80ddfa2
+				i += 3;  /* spaces included in the hex block */
80ddfa2
+			else
80ddfa2
+				i += 2;
80ddfa2
+		} else {  /* the char is not part of hex data, so just copy */
80ddfa2
+			info->pattern[sindex] = s[i];
80ddfa2
+			i++;
80ddfa2
+		}
80ddfa2
+		if (sindex > XT_STRING_MAX_PATTERN_SIZE)
80ddfa2
+			ebt_print_error2("STRING too long \"%s\"", s);
80ddfa2
+		sindex++;
80ddfa2
+	}
80ddfa2
+	info->patlen = sindex;
80ddfa2
+}
80ddfa2
+
80ddfa2
+static int parse(int c, char **argv, int argc, const struct ebt_u_entry *entry,
80ddfa2
+		 unsigned int *flags, struct ebt_entry_match **match)
80ddfa2
+{
80ddfa2
+	struct xt_string_info *info = (struct xt_string_info *)(*match)->data;
80ddfa2
+	int i;
80ddfa2
+	int input_string_length = 0;
80ddfa2
+	char buf[3] = { 0 };
80ddfa2
+
80ddfa2
+	switch (c) {
80ddfa2
+	case STRING_FROM:
80ddfa2
+		ebt_check_option2(flags, OPT_STRING_FROM);
80ddfa2
+		if (ebt_check_inverse2(optarg))
80ddfa2
+			ebt_print_error2("Unexpected `!' after --string-from");
80ddfa2
+		info->from_offset = (__u16)strtoul(optarg, NULL, 10);
80ddfa2
+		break;
80ddfa2
+	case STRING_TO:
80ddfa2
+		ebt_check_option2(flags, OPT_STRING_TO);
80ddfa2
+		if (ebt_check_inverse2(optarg))
80ddfa2
+			ebt_print_error2("Unexpected `!' after --string-to");
80ddfa2
+		info->to_offset = (__u16)strtoul(optarg, NULL, 10);
80ddfa2
+		break;
80ddfa2
+	case STRING_ALGO:
80ddfa2
+		ebt_check_option2(flags, OPT_STRING_ALGO);
80ddfa2
+		if (ebt_check_inverse2(optarg))
80ddfa2
+			ebt_print_error2("Unexpected `!' after --string-algo");
80ddfa2
+		strncpy(info->algo, optarg, XT_STRING_MAX_ALGO_NAME_SIZE);
80ddfa2
+		break;
80ddfa2
+	case STRING_ICASE:
80ddfa2
+		ebt_check_option2(flags, OPT_STRING_ICASE);
80ddfa2
+		if (ebt_check_inverse2(optarg))
80ddfa2
+			ebt_print_error2("Unexpected `!' after --string-icase");
80ddfa2
+		info->u.v1.flags |= XT_STRING_FLAG_IGNORECASE;
80ddfa2
+		break;
80ddfa2
+	case STRING:
80ddfa2
+		ebt_check_option2(flags, OPT_STRING);
80ddfa2
+		parse_string(optarg, info);
80ddfa2
+		if (ebt_check_inverse2(optarg)) {
80ddfa2
+			info->u.v1.flags |= XT_STRING_FLAG_INVERT;
80ddfa2
+		}
80ddfa2
+		break;
80ddfa2
+	case STRING_HEX:
80ddfa2
+		ebt_check_option2(flags, OPT_STRING_HEX);
80ddfa2
+		parse_hex_string(optarg, info);
80ddfa2
+		if (ebt_check_inverse2(optarg)) {
80ddfa2
+			info->u.v1.flags |= XT_STRING_FLAG_INVERT;
80ddfa2
+		}
80ddfa2
+		break;
80ddfa2
+	default:
80ddfa2
+		return 0;
80ddfa2
+	}
80ddfa2
+	return 1;
80ddfa2
+}
80ddfa2
+
80ddfa2
+static void final_check(const struct ebt_u_entry *entry,
80ddfa2
+			const struct ebt_entry_match *match, const char *name,
80ddfa2
+			unsigned int hookmask, unsigned int time)
80ddfa2
+{
80ddfa2
+	struct xt_string_info *info = (struct xt_string_info *)match->data;
80ddfa2
+
80ddfa2
+	if (info->to_offset < info->from_offset) {
80ddfa2
+		ebt_print_error2("'to' offset should not be less than 'from' "
80ddfa2
+				 "offset");
80ddfa2
+	}
80ddfa2
+}
80ddfa2
+
80ddfa2
+/* Test to see if the string contains non-printable chars or quotes */
80ddfa2
+static unsigned short int is_hex_string(const char *str,
80ddfa2
+					const unsigned short int len)
80ddfa2
+{
80ddfa2
+	unsigned int i;
80ddfa2
+	for (i=0; i < len; i++) {
80ddfa2
+		if (! isprint(str[i])) {
80ddfa2
+			/* string contains at least one non-printable char */
80ddfa2
+			return 1;
80ddfa2
+		}
80ddfa2
+	}
80ddfa2
+	/* use hex output if the last char is a "\" */
80ddfa2
+	if (str[len-1] == '\\')
80ddfa2
+		return 1;
80ddfa2
+	return 0;
80ddfa2
+}
80ddfa2
+
80ddfa2
+/* Print string with "|" chars included as one would pass to --string-hex */
80ddfa2
+static void print_hex_string(const char *str, const unsigned short int len)
80ddfa2
+{
80ddfa2
+	unsigned int i;
80ddfa2
+	/* start hex block */
80ddfa2
+	printf("\"|");
80ddfa2
+	for (i=0; i < len; i++)
80ddfa2
+		printf("%02x", (unsigned char)str[i]);
80ddfa2
+	/* close hex block */
80ddfa2
+	printf("|\" ");
80ddfa2
+}
80ddfa2
+
80ddfa2
+static void print_string(const char *str, const unsigned short int len)
80ddfa2
+{
80ddfa2
+	unsigned int i;
80ddfa2
+	printf("\"");
80ddfa2
+	for (i=0; i < len; i++) {
80ddfa2
+		if (str[i] == '\"' || str[i] == '\\')
80ddfa2
+			putchar('\\');
80ddfa2
+		printf("%c", (unsigned char) str[i]);
80ddfa2
+	}
80ddfa2
+	printf("\" ");  /* closing quote */
80ddfa2
+}
80ddfa2
+
80ddfa2
+static void print(const struct ebt_u_entry *entry,
80ddfa2
+		  const struct ebt_entry_match *match)
80ddfa2
+{
80ddfa2
+	const struct xt_string_info *info =
80ddfa2
+		(const struct xt_string_info *) match->data;
80ddfa2
+	int invert = info->u.v1.flags & XT_STRING_FLAG_INVERT;
80ddfa2
+
80ddfa2
+	if (is_hex_string(info->pattern, info->patlen)) {
80ddfa2
+		printf("--string-hex %s", invert ? "! " : "");
80ddfa2
+		print_hex_string(info->pattern, info->patlen);
80ddfa2
+	} else {
80ddfa2
+		printf("--string %s", invert ? "! " : "");
80ddfa2
+		print_string(info->pattern, info->patlen);
80ddfa2
+	}
80ddfa2
+	printf("--string-algo %s ", info->algo);
80ddfa2
+	if (info->from_offset != 0)
80ddfa2
+		printf("--string-from %u ", info->from_offset);
80ddfa2
+	if (info->to_offset != 0)
80ddfa2
+		printf("--string-to %u ", info->to_offset);
80ddfa2
+	if (info->u.v1.flags & XT_STRING_FLAG_IGNORECASE)
80ddfa2
+		printf("--string-icase ");
80ddfa2
+}
80ddfa2
+
80ddfa2
+static int compare(const struct ebt_entry_match *m1,
80ddfa2
+		   const struct ebt_entry_match *m2)
80ddfa2
+{
80ddfa2
+	const struct xt_string_info *info1 =
80ddfa2
+		(const struct xt_string_info *) m1->data;
80ddfa2
+	const struct xt_string_info *info2 =
80ddfa2
+		(const struct xt_string_info *) m2->data;
80ddfa2
+
80ddfa2
+	if (info1->from_offset != info2->from_offset)
80ddfa2
+		return 0;
80ddfa2
+	if (info1->to_offset != info2->to_offset)
80ddfa2
+		return 0;
80ddfa2
+	if (info1->u.v1.flags != info2->u.v1.flags)
80ddfa2
+		return 0;
80ddfa2
+	if (info1->patlen != info2->patlen)
80ddfa2
+		return 0;
80ddfa2
+	if (strncmp (info1->algo, info2->algo, XT_STRING_MAX_ALGO_NAME_SIZE) != 0)
80ddfa2
+		return 0;
80ddfa2
+	if (strncmp (info1->pattern, info2->pattern, info1->patlen) != 0)
80ddfa2
+		return 0;
80ddfa2
+
80ddfa2
+	return 1;
80ddfa2
+}
80ddfa2
+
80ddfa2
+static struct ebt_u_match string_match =
80ddfa2
+{
80ddfa2
+	.name		= "string",
80ddfa2
+	.revision	= 1,
80ddfa2
+	.size		= sizeof(struct xt_string_info),
80ddfa2
+	.help		= print_help,
80ddfa2
+	.init		= init,
80ddfa2
+	.parse		= parse,
80ddfa2
+	.final_check	= final_check,
80ddfa2
+	.print		= print,
80ddfa2
+	.compare	= compare,
80ddfa2
+	.extra_ops	= opts,
80ddfa2
+};
80ddfa2
+
80ddfa2
+void _init(void)
80ddfa2
+{
80ddfa2
+	ebt_register_match(&string_match);
80ddfa2
+}
80ddfa2
diff --git a/include/ebtables.h b/include/ebtables.h
80ddfa2
index 8f520c600b356..9bbedbb72eea5 100644
80ddfa2
--- a/include/ebtables.h
80ddfa2
+++ b/include/ebtables.h
80ddfa2
@@ -20,6 +20,7 @@
80ddfa2
 #define EBT_TABLE_MAXNAMELEN 32
80ddfa2
 #define EBT_CHAIN_MAXNAMELEN EBT_TABLE_MAXNAMELEN
80ddfa2
 #define EBT_FUNCTION_MAXNAMELEN EBT_TABLE_MAXNAMELEN
80ddfa2
+#define EBT_EXTENSION_MAXNAMELEN 31
80ddfa2
 
80ddfa2
 /* verdicts >0 are "branches" */
80ddfa2
 #define EBT_ACCEPT   -1
80ddfa2
@@ -113,7 +114,10 @@ struct ebt_entries {
80ddfa2
 struct ebt_entry_match
80ddfa2
 {
80ddfa2
 	union {
80ddfa2
-		char name[EBT_FUNCTION_MAXNAMELEN];
80ddfa2
+		struct {
80ddfa2
+			char name[EBT_EXTENSION_MAXNAMELEN];
80ddfa2
+			uint8_t revision;
80ddfa2
+		};
80ddfa2
 		struct ebt_match *match;
80ddfa2
 	} u;
80ddfa2
 	/* size of data */
80ddfa2
@@ -127,7 +131,10 @@ struct ebt_entry_match
80ddfa2
 struct ebt_entry_watcher
80ddfa2
 {
80ddfa2
 	union {
80ddfa2
-		char name[EBT_FUNCTION_MAXNAMELEN];
80ddfa2
+		struct {
80ddfa2
+			char name[EBT_EXTENSION_MAXNAMELEN];
80ddfa2
+			uint8_t revision;
80ddfa2
+		};
80ddfa2
 		struct ebt_watcher *watcher;
80ddfa2
 	} u;
80ddfa2
 	/* size of data */
80ddfa2
@@ -141,7 +148,10 @@ struct ebt_entry_watcher
80ddfa2
 struct ebt_entry_target
80ddfa2
 {
80ddfa2
 	union {
80ddfa2
-		char name[EBT_FUNCTION_MAXNAMELEN];
80ddfa2
+		struct {
80ddfa2
+			char name[EBT_EXTENSION_MAXNAMELEN];
80ddfa2
+			uint8_t revision;
80ddfa2
+		};
80ddfa2
 		struct ebt_target *target;
80ddfa2
 	} u;
80ddfa2
 	/* size of data */
80ddfa2
diff --git a/include/ebtables_u.h b/include/ebtables_u.h
80ddfa2
index 17afa9487f5ad..c8589969bd8e0 100644
80ddfa2
--- a/include/ebtables_u.h
80ddfa2
+++ b/include/ebtables_u.h
80ddfa2
@@ -144,6 +144,7 @@ struct ebt_u_entry
80ddfa2
 struct ebt_u_match
80ddfa2
 {
80ddfa2
 	char name[EBT_FUNCTION_MAXNAMELEN];
80ddfa2
+	uint8_t revision;
80ddfa2
 	/* size of the real match data */
80ddfa2
 	unsigned int size;
80ddfa2
 	void (*help)(void);
80ddfa2
diff --git a/libebtc.c b/libebtc.c
80ddfa2
index d47424872dc51..92fd76485c723 100644
80ddfa2
--- a/libebtc.c
80ddfa2
+++ b/libebtc.c
80ddfa2
@@ -272,6 +272,7 @@ void ebt_reinit_extensions()
80ddfa2
 			if (!m->m)
80ddfa2
 				ebt_print_memory();
80ddfa2
 			strcpy(m->m->u.name, m->name);
80ddfa2
+			m->m->u.revision = m->revision;
80ddfa2
 			m->m->match_size = EBT_ALIGN(m->size);
80ddfa2
 			m->used = 0;
80ddfa2
 		}
80ddfa2
@@ -550,8 +551,10 @@ int ebt_check_rule_exists(struct ebt_u_replace *replace,
80ddfa2
 		while (m_l) {
80ddfa2
 			m = (struct ebt_u_match *)(m_l->m);
80ddfa2
 			m_l2 = u_e->m_list;
80ddfa2
-			while (m_l2 && strcmp(m_l2->m->u.name, m->m->u.name))
80ddfa2
+			while (m_l2 && (strcmp(m_l2->m->u.name, m->m->u.name) ||
80ddfa2
+			       m_l2->m->u.revision != m->m->u.revision)) {
80ddfa2
 				m_l2 = m_l2->next;
80ddfa2
+			}
80ddfa2
 			if (!m_l2 || !m->compare(m->m, m_l2->m))
80ddfa2
 				goto letscontinue;
80ddfa2
 			j++;
80ddfa2
@@ -1209,6 +1212,7 @@ void ebt_register_match(struct ebt_u_match *m)
80ddfa2
 	if (!m->m)
80ddfa2
 		ebt_print_memory();
80ddfa2
 	strcpy(m->m->u.name, m->name);
80ddfa2
+	m->m->u.revision = m->revision;
80ddfa2
 	m->m->match_size = EBT_ALIGN(m->size);
80ddfa2
 	m->init(m->m);
80ddfa2
 
80ddfa2
-- 
80ddfa2
2.21.0
80ddfa2