[PATCH] new general purpose conntrack match

Marc Boucher marc+netfilter-devel@mbsi.ca
Fri, 7 Dec 2001 01:30:06 -0500


--W/nzBZO5zC0uMSeA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

I've just implemented a general conntrack match module (as discussed in
Enschede), which is a superset of the state match. The ability to match
on additional conntrack information is very useful, for example to do proper
routing with NAT on gateways with multiple internet links or tunnels..

It presently supports the following options:


conntrack match v1.2.4 options:
 [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|SNAT|DNAT][,...]
                                State(s) to match
 [!] --ctproto  proto           Protocol to match; by number or name, eg. `tcp'
     --ctorigsrc  [!] address[/mask]
                                Original source specification
     --ctorigdst  [!] address[/mask]
                                Original destination specification
     --ctreplsrc  [!] address[/mask]
                                Reply source specification
     --ctrepldst  [!] address[/mask]
                                Reply destination specification
 [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED][,...]
                                Status(es) to match
 [!] --ctexpire  time[:time]    Match remaining lifetime in seconds against
                                value or range of values (inclusive)


The "new" SNAT and DNAT states are virtual ones, matching if the original
source address is differs from the reply destination, or if the original
destination differs from the reply source..  The ability to detect
these differences is quite useful (however I have a few minor doubts about
addition of virtual states as it may be confusing for the user; introducing
new options for this feature might be a better approach)

Any comments and/or review of the code would be appreciated.

I would like to add the stuff into patch-o-matic and userspace/extensions
soon, as there has been some demand for it.

Cheers
Marc


--W/nzBZO5zC0uMSeA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="conntrack-match.patch"

--- linux-2.4.17-pre4-mb/net/ipv4/netfilter/Config.in	2001/12/06 09:58:04	1.1
+++ linux-2.4.17-pre4-mb/net/ipv4/netfilter/Config.in	2001/12/06 09:58:41
@@ -26,6 +26,7 @@
   dep_tristate '  tcpmss match support' CONFIG_IP_NF_MATCH_TCPMSS $CONFIG_IP_NF_IPTABLES
   if [ "$CONFIG_IP_NF_CONNTRACK" != "n" ]; then
     dep_tristate '  Connection state match support' CONFIG_IP_NF_MATCH_STATE $CONFIG_IP_NF_CONNTRACK $CONFIG_IP_NF_IPTABLES 
+    dep_tristate '  Connection tracking match support' CONFIG_IP_NF_MATCH_CONNTRACK $CONFIG_IP_NF_CONNTRACK $CONFIG_IP_NF_IPTABLES 
   fi
   if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
     dep_tristate '  Unclean match support (EXPERIMENTAL)' CONFIG_IP_NF_MATCH_UNCLEAN $CONFIG_IP_NF_IPTABLES
--- linux-2.4.17-pre4-mb/net/ipv4/netfilter/Makefile	2001/12/06 09:58:47	1.1
+++ linux-2.4.17-pre4-mb/net/ipv4/netfilter/Makefile	2001/12/06 09:59:03
@@ -61,6 +61,7 @@
 
 obj-$(CONFIG_IP_NF_MATCH_TTL) += ipt_ttl.o
 obj-$(CONFIG_IP_NF_MATCH_STATE) += ipt_state.o
+obj-$(CONFIG_IP_NF_MATCH_CONNTRACK) += ipt_conntrack.o
 obj-$(CONFIG_IP_NF_MATCH_UNCLEAN) += ipt_unclean.o
 obj-$(CONFIG_IP_NF_MATCH_TCPMSS) += ipt_tcpmss.o
 
--- linux-2.4.17-pre4-mb/include/linux/netfilter_ipv4/ipt_conntrack.h	2001/12/06 09:55:15	1.1
+++ linux-2.4.17-pre4-mb/include/linux/netfilter_ipv4/ipt_conntrack.h	2001/12/06 14:34:25
@@ -0,0 +1,38 @@
+/* Header file for kernel module to match connection tracking information.
+ * GPL (C) 2001  Marc Boucher (marc@mbsi.ca).
+ */
+
+#ifndef _IPT_CONNTRACK_H
+#define _IPT_CONNTRACK_H
+
+#define IPT_CONNTRACK_STATE_BIT(ctinfo) (1 << ((ctinfo)%IP_CT_IS_REPLY+1))
+#define IPT_CONNTRACK_STATE_INVALID (1 << 0)
+
+#define IPT_CONNTRACK_STATE_SNAT (1 << (IP_CT_NUMBER + 1))
+#define IPT_CONNTRACK_STATE_DNAT (1 << (IP_CT_NUMBER + 2))
+
+/* flags, invflags: */
+#define IPT_CONNTRACK_STATE	0x01
+#define IPT_CONNTRACK_PROTO	0x02
+#define IPT_CONNTRACK_ORIGSRC	0x04
+#define IPT_CONNTRACK_ORIGDST	0x08
+#define IPT_CONNTRACK_REPLSRC	0x10
+#define IPT_CONNTRACK_REPLDST	0x20
+#define IPT_CONNTRACK_STATUS	0x40
+#define IPT_CONNTRACK_EXPIRES	0x80
+
+struct ipt_conntrack_info
+{
+	unsigned int statemask, statusmask;
+
+	struct ip_conntrack_tuple tuple[IP_CT_DIR_MAX];
+	struct in_addr sipmsk[IP_CT_DIR_MAX], dipmsk[IP_CT_DIR_MAX];
+
+	unsigned long expires_min, expires_max;
+
+	/* Flags word */
+	u_int8_t flags;
+	/* Inverse flags */
+	u_int8_t invflags;
+};
+#endif /*_IPT_CONNTRACK_H*/
--- linux-2.4.17-pre4-mb/net/ipv4/netfilter/ipt_conntrack.c	2001/12/06 09:54:28	1.1
+++ linux-2.4.17-pre4-mb/net/ipv4/netfilter/ipt_conntrack.c	2001/12/06 15:26:00
@@ -0,0 +1,123 @@
+/* Kernel module to match connection tracking information.
+ * Superset of Rusty's minimalistic state match.
+ * GPL (C) 2001  Marc Boucher (marc@mbsi.ca).
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_conntrack.h>
+
+static int
+match(const struct sk_buff *skb,
+      const struct net_device *in,
+      const struct net_device *out,
+      const void *matchinfo,
+      int offset,
+      const void *hdr,
+      u_int16_t datalen,
+      int *hotdrop)
+{
+	const struct ipt_conntrack_info *sinfo = matchinfo;
+	struct ip_conntrack *ct;
+	enum ip_conntrack_info ctinfo;
+	unsigned int statebit;
+
+	ct = ip_conntrack_get((struct sk_buff *)skb, &ctinfo);
+
+#define FWINV(bool,invflg) ((bool) ^ !!(sinfo->invflags & invflg))
+
+	statebit = ct ? IPT_CONNTRACK_STATE_INVALID : IPT_CONNTRACK_STATE_BIT(ctinfo);
+	if(sinfo->flags & IPT_CONNTRACK_STATE) {
+		if (ct) {
+			if(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip !=
+			    ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip)
+				statebit |= IPT_CONNTRACK_STATE_SNAT;
+
+			if(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip !=
+			    ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip)
+				statebit |= IPT_CONNTRACK_STATE_DNAT;
+		}
+
+		if (FWINV((statebit & sinfo->statemask) == 0, IPT_CONNTRACK_STATE))
+			return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_PROTO) {
+		if (!ct || FWINV(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum != sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum, IPT_CONNTRACK_PROTO))
+                	return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
+		if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip&sinfo->sipmsk[IP_CT_DIR_ORIGINAL].s_addr) != sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip, IPT_CONNTRACK_ORIGSRC))
+			return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
+		if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip&sinfo->dipmsk[IP_CT_DIR_ORIGINAL].s_addr) != sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip, IPT_CONNTRACK_ORIGDST))
+			return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
+		if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip&sinfo->sipmsk[IP_CT_DIR_REPLY].s_addr) != sinfo->tuple[IP_CT_DIR_REPLY].src.ip, IPT_CONNTRACK_REPLSRC))
+			return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
+		if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip&sinfo->dipmsk[IP_CT_DIR_REPLY].s_addr) != sinfo->tuple[IP_CT_DIR_REPLY].dst.ip, IPT_CONNTRACK_REPLDST))
+			return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_STATUS) {
+		if (!ct || FWINV((ct->status & sinfo->statusmask) == 0, IPT_CONNTRACK_STATUS))
+			return 0;
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
+		unsigned long expires;
+
+		if(!ct)
+			return 0;
+
+		expires = timer_pending(&ct->timeout) ? (ct->timeout.expires - jiffies)/HZ : 0;
+
+		if (FWINV(!(expires >= sinfo->expires_min && expires <= sinfo->expires_max), IPT_CONNTRACK_EXPIRES))
+			return 0;
+	}
+
+	return 1;
+}
+
+static int check(const char *tablename,
+		 const struct ipt_ip *ip,
+		 void *matchinfo,
+		 unsigned int matchsize,
+		 unsigned int hook_mask)
+{
+	if (matchsize != IPT_ALIGN(sizeof(struct ipt_conntrack_info)))
+		return 0;
+
+	return 1;
+}
+
+static struct ipt_match conntrack_match
+= { { NULL, NULL }, "conntrack", &match, &check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+	/* NULL if ip_conntrack not a module */
+	if (ip_conntrack_module)
+		__MOD_INC_USE_COUNT(ip_conntrack_module);
+	return ipt_register_match(&conntrack_match);
+}
+
+static void __exit fini(void)
+{
+	ipt_unregister_match(&conntrack_match);
+	if (ip_conntrack_module)
+		__MOD_DEC_USE_COUNT(ip_conntrack_module);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
--- linux-2.4.17-pre4-mb/include/linux/netfilter_ipv4/ip_conntrack.h	2001/12/06 13:45:33	1.1
+++ linux-2.4.17-pre4-mb/include/linux/netfilter_ipv4/ip_conntrack.h	2001/12/06 13:46:06
@@ -27,6 +27,21 @@
 	IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1
 };
 
+/* Bitset representing status of connection. */
+enum ip_conntrack_status {
+	/* It's an expected connection: bit 0 set.  This bit never changed */
+	IPS_EXPECTED_BIT = 0,
+	IPS_EXPECTED = (1 << IPS_EXPECTED_BIT),
+
+	/* We've seen packets both ways: bit 1 set.  Can be set, not unset. */
+	IPS_SEEN_REPLY_BIT = 1,
+	IPS_SEEN_REPLY = (1 << IPS_SEEN_REPLY_BIT),
+
+	/* Conntrack should never be early-expired. */
+	IPS_ASSURED_BIT = 2,
+	IPS_ASSURED = (1 << IPS_ASSURED_BIT),
+};
+
 #ifdef __KERNEL__
 
 #include <linux/types.h>
@@ -46,21 +61,6 @@
 #else
 #define IP_NF_ASSERT(x)
 #endif
-
-/* Bitset representing status of connection. */
-enum ip_conntrack_status {
-	/* It's an expected connection: bit 0 set.  This bit never changed */
-	IPS_EXPECTED_BIT = 0,
-	IPS_EXPECTED = (1 << IPS_EXPECTED_BIT),
-
-	/* We've seen packets both ways: bit 1 set.  Can be set, not unset. */
-	IPS_SEEN_REPLY_BIT = 1,
-	IPS_SEEN_REPLY = (1 << IPS_SEEN_REPLY_BIT),
-
-	/* Conntrack should never be early-expired. */
-	IPS_ASSURED_BIT = 2,
-	IPS_ASSURED = (1 << IPS_ASSURED_BIT),
-};
 
 struct ip_conntrack_expect
 {
--- linux-2.4.17-pre4-mb/include/linux/netfilter_ipv4/ip_conntrack_tuple.h	2001/12/06 11:23:11	1.1
+++ linux-2.4.17-pre4-mb/include/linux/netfilter_ipv4/ip_conntrack_tuple.h	2001/12/06 11:25:28
@@ -62,6 +62,13 @@
 	} dst;
 };
 
+enum ip_conntrack_dir
+{
+	IP_CT_DIR_ORIGINAL,
+	IP_CT_DIR_REPLY,
+	IP_CT_DIR_MAX
+};
+
 #ifdef __KERNEL__
 
 #define DUMP_TUPLE(tp)						\
@@ -75,13 +82,19 @@
 /* If we're the first tuple, it's the original dir. */
 #define DIRECTION(h) ((enum ip_conntrack_dir)(&(h)->ctrack->tuplehash[1] == (h)))
 
-enum ip_conntrack_dir
+/* Connections have two entries in the hash table: one for each way */
+struct ip_conntrack_tuple_hash
 {
-	IP_CT_DIR_ORIGINAL,
-	IP_CT_DIR_REPLY,
-	IP_CT_DIR_MAX
+	struct list_head list;
+
+	struct ip_conntrack_tuple tuple;
+
+	/* this == &ctrack->tuplehash[DIRECTION(this)]. */
+	struct ip_conntrack *ctrack;
 };
 
+#endif /* __KERNEL__ */
+
 static inline int ip_ct_tuple_src_equal(const struct ip_conntrack_tuple *t1,
 				        const struct ip_conntrack_tuple *t2)
 {
@@ -115,16 +128,4 @@
 		     & mask->dst.protonum));
 }
 
-/* Connections have two entries in the hash table: one for each way */
-struct ip_conntrack_tuple_hash
-{
-	struct list_head list;
-
-	struct ip_conntrack_tuple tuple;
-
-	/* this == &ctrack->tuplehash[DIRECTION(this)]. */
-	struct ip_conntrack *ctrack;
-};
-
-#endif /* __KERNEL__ */
 #endif /* _IP_CONNTRACK_TUPLE_H */

--W/nzBZO5zC0uMSeA
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="conntrack-match-userspace.patch"

--- netfilter/userspace/extensions/.conntrack-test	2001/12/06 10:03:11	1.1
+++ netfilter/userspace/extensions/.conntrack-test	2001/12/06 10:03:26
@@ -0,0 +1,3 @@
+#!/bin/sh
+# True if conntrack match patch is applied.
+[ -f $KERNEL_DIR/include/linux/netfilter_ipv4/ipt_conntrack.h ] && echo conntrack
--- netfilter/userspace/extensions/libipt_conntrack.c	2001/12/06 09:59:23	1.1
+++ netfilter/userspace/extensions/libipt_conntrack.c	2001/12/06 15:10:51
@@ -0,0 +1,517 @@
+/* Shared library add-on to iptables to add conntrack matching support.
+ * GPL (C) 2001  Marc Boucher (marc@mbsi.ca).
+ */
+
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <iptables.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_tuple.h>
+#include <linux/netfilter_ipv4/ipt_conntrack.h>
+
+/* Function which prints out usage message. */
+static void
+help(void)
+{
+	printf(
+"conntrack match v%s options:\n"
+" [!] --ctstate [INVALID|ESTABLISHED|NEW|RELATED|SNAT|DNAT][,...]\n"
+"				State(s) to match\n"
+" [!] --ctproto	proto		Protocol to match; by number or name, eg. `tcp'\n"
+"     --ctorigsrc  [!] address[/mask]\n"
+"				Original source specification\n"
+"     --ctorigdst  [!] address[/mask]\n"
+"				Original destination specification\n"
+"     --ctreplsrc  [!] address[/mask]\n"
+"				Reply source specification\n"
+"     --ctrepldst  [!] address[/mask]\n"
+"				Reply destination specification\n"
+" [!] --ctstatus [NONE|EXPECTED|SEEN_REPLY|ASSURED][,...]\n"
+"				Status(es) to match\n"
+" [!] --ctexpire  time[:time]	Match remaining lifetime in seconds against\n"
+"				value or range of values (inclusive)\n"
+"\n", NETFILTER_VERSION);
+}
+
+
+
+static struct option opts[] = {
+	{ "ctstate", 1, 0, '1' },
+	{ "ctproto", 1, 0, '2' },
+	{ "ctorigsrc", 1, 0, '3' },
+	{ "ctorigdst", 1, 0, '4' },
+	{ "ctreplsrc", 1, 0, '5' },
+	{ "ctrepldst", 1, 0, '6' },
+	{ "ctstatus", 1, 0, '7' },
+	{ "ctexpire", 1, 0, '8' },
+	{0}
+};
+
+/* Initialize the match. */
+static void
+init(struct ipt_entry_match *m, unsigned int *nfcache)
+{
+	/* Can't cache this */
+	*nfcache |= NFC_UNKNOWN;
+}
+
+static int
+parse_state(const char *state, size_t strlen, struct ipt_conntrack_info *sinfo)
+{
+	if (strncasecmp(state, "INVALID", strlen) == 0)
+		sinfo->statemask |= IPT_CONNTRACK_STATE_INVALID;
+	else if (strncasecmp(state, "NEW", strlen) == 0)
+		sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_NEW);
+	else if (strncasecmp(state, "ESTABLISHED", strlen) == 0)
+		sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED);
+	else if (strncasecmp(state, "RELATED", strlen) == 0)
+		sinfo->statemask |= IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED);
+	else if (strncasecmp(state, "SNAT", strlen) == 0)
+		sinfo->statemask |= IPT_CONNTRACK_STATE_SNAT;
+	else if (strncasecmp(state, "DNAT", strlen) == 0)
+		sinfo->statemask |= IPT_CONNTRACK_STATE_DNAT;
+	else
+		return 0;
+	return 1;
+}
+
+static void
+parse_states(const char *arg, struct ipt_conntrack_info *sinfo)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !parse_state(arg, comma-arg, sinfo))
+			exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
+		arg = comma+1;
+	}
+
+	if (strlen(arg) == 0 || !parse_state(arg, strlen(arg), sinfo))
+		exit_error(PARAMETER_PROBLEM, "Bad ctstate `%s'", arg);
+}
+
+static int
+parse_status(const char *status, size_t strlen, struct ipt_conntrack_info *sinfo)
+{
+	if (strncasecmp(status, "NONE", strlen) == 0)
+		sinfo->statusmask |= 0;
+	else if (strncasecmp(status, "EXPECTED", strlen) == 0)
+		sinfo->statusmask |= IPS_EXPECTED;
+	else if (strncasecmp(status, "SEEN_REPLY", strlen) == 0)
+		sinfo->statusmask |= IPS_SEEN_REPLY;
+	else if (strncasecmp(status, "ASSURED", strlen) == 0)
+		sinfo->statusmask |= IPS_ASSURED;
+	else
+		return 0;
+	return 1;
+}
+
+static void
+parse_statuses(const char *arg, struct ipt_conntrack_info *sinfo)
+{
+	const char *comma;
+
+	while ((comma = strchr(arg, ',')) != NULL) {
+		if (comma == arg || !parse_status(arg, comma-arg, sinfo))
+			exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
+		arg = comma+1;
+	}
+
+	if (strlen(arg) == 0 || !parse_status(arg, strlen(arg), sinfo))
+		exit_error(PARAMETER_PROBLEM, "Bad ctstatus `%s'", arg);
+}
+
+
+static unsigned long
+parse_expire(const char *s)
+{
+	unsigned int len;
+	
+	if (string_to_number(s, 0, 0xFFFFFFFF, &len) == -1)
+		exit_error(PARAMETER_PROBLEM, "expire value invalid: `%s'\n", s);
+	else
+		return len;
+}
+
+/* If a single value is provided, min and max are both set to the value */
+static void
+parse_expires(const char *s, struct ipt_conntrack_info *sinfo)
+{
+	char *buffer;
+	char *cp;
+
+	buffer = strdup(s);
+	if ((cp = strchr(buffer, ':')) == NULL)
+		sinfo->expires_min = sinfo->expires_max = parse_expire(buffer);
+	else {
+		*cp = '\0';
+		cp++;
+
+		sinfo->expires_min = buffer[0] ? parse_expire(buffer) : 0;
+		sinfo->expires_max = cp[0] ? parse_expire(cp) : 0xFFFFFFFF;
+	}
+	free(buffer);
+	
+	if (sinfo->expires_min > sinfo->expires_max)
+		exit_error(PARAMETER_PROBLEM,
+		           "expire min. range value `%lu' greater than max. "
+		           "range value `%lu'", sinfo->expires_min, sinfo->expires_max);
+	
+}
+
+/* Function which parses command options; returns true if it
+   ate an option */
+static int
+parse(int c, char **argv, int invert, unsigned int *flags,
+      const struct ipt_entry *entry,
+      unsigned int *nfcache,
+      struct ipt_entry_match **match)
+{
+	struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)(*match)->data;
+	char *protocol = NULL;
+	unsigned int naddrs = 0;
+	struct in_addr *addrs = NULL;
+
+
+	switch (c) {
+	case '1':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		parse_states(argv[optind-1], sinfo);
+		if (invert) {
+			sinfo->invflags |= IPT_CONNTRACK_STATE;
+		}
+		sinfo->flags |= IPT_CONNTRACK_STATE;
+		break;
+
+	case '2':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		if(invert)
+			sinfo->invflags |= IPT_CONNTRACK_PROTO;
+
+		/* Canonicalize into lower case */
+		for (protocol = argv[optind-1]; *protocol; protocol++)
+			*protocol = tolower(*protocol);
+
+		protocol = argv[optind-1];
+		sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum = parse_protocol(protocol);
+
+		if (sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum == 0
+		    && (sinfo->invflags & IPT_INV_PROTO))
+			exit_error(PARAMETER_PROBLEM,
+				   "rule would never match protocol");
+
+		sinfo->flags |= IPT_CONNTRACK_PROTO;
+		break;
+
+	case '3':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		if (invert)
+			sinfo->invflags |= IPT_CONNTRACK_ORIGSRC;
+
+		parse_hostnetworkmask(argv[optind-1], &addrs,
+					&sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
+					&naddrs);
+		if(naddrs > 1)
+			exit_error(PARAMETER_PROBLEM,
+				"multiple IP addresses not allowed");
+
+		if(naddrs == 1) {
+			sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip = addrs[0].s_addr;
+		}
+
+		sinfo->flags |= IPT_CONNTRACK_ORIGSRC;
+		break;
+
+	case '4':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		if (invert)
+			sinfo->invflags |= IPT_CONNTRACK_ORIGDST;
+
+		parse_hostnetworkmask(argv[optind-1], &addrs,
+					&sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
+					&naddrs);
+		if(naddrs > 1)
+			exit_error(PARAMETER_PROBLEM,
+				"multiple IP addresses not allowed");
+
+		if(naddrs == 1) {
+			sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip = addrs[0].s_addr;
+		}
+
+		sinfo->flags |= IPT_CONNTRACK_ORIGDST;
+		break;
+
+	case '5':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		if (invert)
+			sinfo->invflags |= IPT_CONNTRACK_REPLSRC;
+
+		parse_hostnetworkmask(argv[optind-1], &addrs,
+					&sinfo->sipmsk[IP_CT_DIR_REPLY],
+					&naddrs);
+		if(naddrs > 1)
+			exit_error(PARAMETER_PROBLEM,
+				"multiple IP addresses not allowed");
+
+		if(naddrs == 1) {
+			sinfo->tuple[IP_CT_DIR_REPLY].src.ip = addrs[0].s_addr;
+		}
+
+		sinfo->flags |= IPT_CONNTRACK_REPLSRC;
+		break;
+
+	case '6':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		if (invert)
+			sinfo->invflags |= IPT_CONNTRACK_REPLDST;
+
+		parse_hostnetworkmask(argv[optind-1], &addrs,
+					&sinfo->dipmsk[IP_CT_DIR_REPLY],
+					&naddrs);
+		if(naddrs > 1)
+			exit_error(PARAMETER_PROBLEM,
+				"multiple IP addresses not allowed");
+
+		if(naddrs == 1) {
+			sinfo->tuple[IP_CT_DIR_REPLY].dst.ip = addrs[0].s_addr;
+		}
+
+		sinfo->flags |= IPT_CONNTRACK_REPLDST;
+		break;
+
+	case '7':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		parse_statuses(argv[optind-1], sinfo);
+		if (invert) {
+			sinfo->invflags |= IPT_CONNTRACK_STATUS;
+		}
+		sinfo->flags |= IPT_CONNTRACK_STATUS;
+		break;
+
+	case '8':
+		if (check_inverse(optarg, &invert))
+			optind++;
+
+		parse_expires(argv[optind-1], sinfo);
+		if (invert) {
+			sinfo->invflags |= IPT_CONNTRACK_EXPIRES;
+		}
+		sinfo->flags |= IPT_CONNTRACK_EXPIRES;
+		break;
+
+	default:
+		return 0;
+	}
+
+	*flags = sinfo->flags;
+	return 1;
+}
+
+static void
+final_check(unsigned int flags)
+{
+	if (!flags)
+		exit_error(PARAMETER_PROBLEM, "You must specify one or more options");
+}
+
+static void
+print_state(unsigned int statemask)
+{
+	const char *sep = "";
+
+	if (statemask & IPT_CONNTRACK_STATE_INVALID) {
+		printf("%sINVALID", sep);
+		sep = ",";
+	}
+	if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_NEW)) {
+		printf("%sNEW", sep);
+		sep = ",";
+	}
+	if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_RELATED)) {
+		printf("%sRELATED", sep);
+		sep = ",";
+	}
+	if (statemask & IPT_CONNTRACK_STATE_BIT(IP_CT_ESTABLISHED)) {
+		printf("%sESTABLISHED", sep);
+		sep = ",";
+	}
+	if (statemask & IPT_CONNTRACK_STATE_SNAT) {
+		printf("%sSNAT", sep);
+		sep = ",";
+	}
+	if (statemask & IPT_CONNTRACK_STATE_DNAT) {
+		printf("%sDNAT", sep);
+		sep = ",";
+	}
+	printf(" ");
+}
+
+static void
+print_status(unsigned int statusmask)
+{
+	const char *sep = "";
+
+	if (statusmask & IPS_EXPECTED) {
+		printf("%sEXPECTED", sep);
+		sep = ",";
+	}
+	if (statusmask & IPS_SEEN_REPLY) {
+		printf("%sSEEN_REPLY", sep);
+		sep = ",";
+	}
+	if (statusmask & IPS_ASSURED) {
+		printf("%sASSURED", sep);
+		sep = ",";
+	}
+	if (statusmask == 0) {
+		printf("%sNONE", sep);
+		sep = ",";
+	}
+	printf(" ");
+}
+
+static void
+print_addr(struct in_addr *addr, struct in_addr *mask, int inv, int numeric)
+{
+	char buf[BUFSIZ];
+
+        if (inv)
+               	fputc('!', stdout);
+
+	if (mask->s_addr == 0L && !numeric)
+		printf("%s ", "anywhere");
+	else {
+		if (numeric)
+			sprintf(buf, "%s", addr_to_dotted(addr));
+		else
+			sprintf(buf, "%s", addr_to_anyname(addr));
+		strcat(buf, mask_to_dotted(mask));
+		printf("%s ", buf);
+	}
+}
+
+/* Saves the matchinfo in parsable form to stdout. */
+static void
+matchinfo_print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric, const char *optpfx)
+{
+	struct ipt_conntrack_info *sinfo = (struct ipt_conntrack_info *)match->data;
+
+	if(sinfo->flags & IPT_CONNTRACK_STATE) {
+		printf("%sctstate ", optpfx);
+        	if (sinfo->invflags & IPT_CONNTRACK_STATE)
+                	fputc('!', stdout);
+		print_state(sinfo->statemask);
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
+		printf("%sctorigsrc ", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip,
+		    &sinfo->sipmsk[IP_CT_DIR_ORIGINAL],
+		    sinfo->invflags & IPT_CONNTRACK_ORIGSRC,
+		    numeric);
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
+		printf("%sctorigdst ", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip,
+		    &sinfo->dipmsk[IP_CT_DIR_ORIGINAL],
+		    sinfo->invflags & IPT_CONNTRACK_ORIGDST,
+		    numeric);
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
+		printf("%sctorigsrc ", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].src.ip,
+		    &sinfo->sipmsk[IP_CT_DIR_REPLY],
+		    sinfo->invflags & IPT_CONNTRACK_REPLSRC,
+		    numeric);
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
+		printf("%sctorigdst ", optpfx);
+
+		print_addr(
+		    (struct in_addr *)&sinfo->tuple[IP_CT_DIR_REPLY].dst.ip,
+		    &sinfo->dipmsk[IP_CT_DIR_REPLY],
+		    sinfo->invflags & IPT_CONNTRACK_REPLDST,
+		    numeric);
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_STATUS) {
+		printf("%sctstatus ", optpfx);
+        	if (sinfo->invflags & IPT_CONNTRACK_STATE)
+                	fputc('!', stdout);
+		print_status(sinfo->statusmask);
+	}
+
+	if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
+		printf("%sctexpire ", optpfx);
+        	if (sinfo->invflags & IPT_CONNTRACK_EXPIRES)
+                	fputc('!', stdout);
+
+        	if (sinfo->expires_max == sinfo->expires_min)
+                	printf("%lu ", sinfo->expires_min);
+        	else
+                	printf("%lu:%lu ", sinfo->expires_min, sinfo->expires_max);
+	}
+}
+
+/* Prints out the matchinfo. */
+static void
+print(const struct ipt_ip *ip,
+      const struct ipt_entry_match *match,
+      int numeric)
+{
+	matchinfo_print(ip, match, numeric, "");
+}
+
+/* Saves the matchinfo in parsable form to stdout. */
+static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
+{
+	matchinfo_print(ip, match, 0, "--");
+}
+
+static
+struct iptables_match conntrack
+= { NULL,
+    "conntrack",
+    NETFILTER_VERSION,
+    IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
+    IPT_ALIGN(sizeof(struct ipt_conntrack_info)),
+    &help,
+    &init,
+    &parse,
+    &final_check,
+    &print,
+    &save,
+    opts
+};
+
+void _init(void)
+{
+	register_match(&conntrack);
+}

--W/nzBZO5zC0uMSeA--