netfilter-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/4] conntrack: accept commands from file + tests
@ 2020-11-09 18:17 Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 1/4] conntrack: accept commands from file Mikhail Sennikovsky
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Mikhail Sennikovsky @ 2020-11-09 18:17 UTC (permalink / raw)
  To: netfilter-devel, pablo; +Cc: Mikhail Sennikovsky

Hi Pablo & all,

Here is the adjusted version of patches for conntrack
to support loading ct entries from file in the "save"
aka "options" format.

It uses the approach proposed by Pablo to first create
a list of command objects and then go through the list 
and apply it. 
At least the way I understood it ;)
Pablo, please let me know whether this is what you meant.
The approach definitely seems cleaner, but is more invasive
on the other hand.

Thanks,
Mikhail

Mikhail Sennikovsky (4):
  conntrack: accept commands from file
  conntrack.8: man update for --load-file support
  tests: saving and loading ct entries, save format
  tests: conntrack -L/-D ip family filtering

 conntrack.8                         |    8 +
 extensions/libct_proto_dccp.c       |   60 +-
 extensions/libct_proto_gre.c        |   52 +-
 extensions/libct_proto_icmp.c       |   38 +-
 extensions/libct_proto_icmpv6.c     |   38 +-
 extensions/libct_proto_sctp.c       |   56 +-
 extensions/libct_proto_tcp.c        |   68 +-
 extensions/libct_proto_udp.c        |   52 +-
 extensions/libct_proto_udplite.c    |   50 +-
 include/conntrack.h                 |   63 +-
 src/conntrack.c                     | 1214 ++++++++++++++++-----------
 tests/conntrack/test-conntrack.c    |   84 +-
 tests/conntrack/testsuite/08stdin   |   80 ++
 tests/conntrack/testsuite/09dumpopt |  147 ++++
 14 files changed, 1295 insertions(+), 715 deletions(-)
 create mode 100644 tests/conntrack/testsuite/08stdin
 create mode 100644 tests/conntrack/testsuite/09dumpopt

-- 
2.25.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 1/4] conntrack: accept commands from file
  2020-11-09 18:17 [PATCH v2 0/4] conntrack: accept commands from file + tests Mikhail Sennikovsky
@ 2020-11-09 18:17 ` Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 2/4] conntrack.8: man update for --load-file support Mikhail Sennikovsky
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Mikhail Sennikovsky @ 2020-11-09 18:17 UTC (permalink / raw)
  To: netfilter-devel, pablo; +Cc: Mikhail Sennikovsky

This commit implements the --load-file option which
allows processing conntrack commands stored in file.
Most often this would be used as a counter-part for the
-o save option, which outputs conntrack entries
in the format of the conntrack tool options.
This could be useful when one needs to add/update/delete a large
set of ct entries with a single conntrack tool invocation.

Expected syntax is "conntrack --load-file file".
If "-" is given as a file name, stdin is used.
No other commands or options are allowed to be specified
in conjunction with the --load-file command.
It is however possible to specify multiple --load-file file pairs.

Example:
Copy all entries from ct zone 11 to ct zone 12:

conntrack -L -w 11 -o save | sed "s/-w 11/-w 12/g" | \
	conntrack --load-file -

Signed-off-by: Mikhail Sennikovsky <mikhail.sennikovskii@cloud.ionos.com>
---
 extensions/libct_proto_dccp.c    |   60 +-
 extensions/libct_proto_gre.c     |   52 +-
 extensions/libct_proto_icmp.c    |   38 +-
 extensions/libct_proto_icmpv6.c  |   38 +-
 extensions/libct_proto_sctp.c    |   56 +-
 extensions/libct_proto_tcp.c     |   68 +-
 extensions/libct_proto_udp.c     |   52 +-
 extensions/libct_proto_udplite.c |   50 +-
 include/conntrack.h              |   63 +-
 src/conntrack.c                  | 1214 ++++++++++++++++++------------
 10 files changed, 988 insertions(+), 703 deletions(-)

diff --git a/extensions/libct_proto_dccp.c b/extensions/libct_proto_dccp.c
index e9da474..057a817 100644
--- a/extensions/libct_proto_dccp.c
+++ b/extensions/libct_proto_dccp.c
@@ -111,11 +111,7 @@ static void help(void)
 	fprintf(stdout, "  --state\t\t\tDCCP state, fe. RESPOND\n");
 }
 
-static int parse_options(char c,
-			 struct nf_conntrack *ct,
-			 struct nf_conntrack *exptuple,
-			 struct nf_conntrack *mask,
-			 unsigned int *flags)
+static int parse_options(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	int i;
 	uint16_t port;
@@ -123,73 +119,73 @@ static int parse_options(char c,
 	switch(c) {
 	case 1:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
 		*flags |= CT_DCCP_ORIG_SPORT;
 		break;
 	case 2:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
 		*flags |= CT_DCCP_ORIG_DPORT;
 		break;
 	case 3:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_DCCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_DCCP);
 		*flags |= CT_DCCP_REPL_SPORT;
 		break;
 	case 4:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_DCCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_DCCP);
 		*flags |= CT_DCCP_REPL_DPORT;
 		break;
 	case 5:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
 		*flags |= CT_DCCP_MASK_SPORT;
 		break;
 	case 6:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_DCCP);
 		*flags |= CT_DCCP_MASK_DPORT;
 		break;
 	case 7:
 		for (i=0; i<DCCP_CONNTRACK_MAX; i++) {
 			if (strcmp(optarg, dccp_states[i]) == 0) {
-				nfct_set_attr_u8(ct, ATTR_DCCP_STATE, i);
+				nfct_set_attr_u8(cmd->ct, ATTR_DCCP_STATE, i);
 				break;
 			}
 		}
 		if (i == DCCP_CONNTRACK_MAX)
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "Unknown DCCP state `%s'", optarg);
 		*flags |= CT_DCCP_STATE;
 		break;
 	case 8:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, port);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, port);
 		*flags |= CT_DCCP_EXPTUPLE_SPORT;
 		break;
 	case 9:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_DST, port); 
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, port);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, port);
 		*flags |= CT_DCCP_EXPTUPLE_DPORT;
 		break;
 	case 10:
 		if (strncasecmp(optarg, "client", strlen(optarg)) == 0) {
-			nfct_set_attr_u8(ct, ATTR_DCCP_ROLE,
+			nfct_set_attr_u8(cmd->ct, ATTR_DCCP_ROLE,
 					 DCCP_CONNTRACK_ROLE_CLIENT);
 		} else if (strncasecmp(optarg, "server", strlen(optarg)) == 0) {
-			nfct_set_attr_u8(ct, ATTR_DCCP_ROLE,
+			nfct_set_attr_u8(cmd->ct, ATTR_DCCP_ROLE,
 					 DCCP_CONNTRACK_ROLE_SERVER);
 		} else {
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "Unknown DCCP role `%s'", optarg);
 		}
 		*flags |= CT_DCCP_ROLE;
@@ -221,24 +217,26 @@ static unsigned int dccp_valid_flags[DCCP_VALID_FLAGS_MAX] = {
 };
 
 static void 
-final_check(unsigned int flags, unsigned int cmd, struct nf_conntrack *ct)
+final_check(struct ct_cmd *cmd, unsigned int flags)
 {
 	int ret, partial;
 
-	ret = generic_opt_check(flags, DCCP_OPT_MAX,
-				dccp_commands_v_options[cmd], dccp_optflags,
+	ret = generic_opt_check(cmd, flags, DCCP_OPT_MAX,
+				dccp_commands_v_options[cmd->cmd_bit], dccp_optflags,
 				dccp_valid_flags, DCCP_VALID_FLAGS_MAX,
 				&partial);
 	if (!ret) {
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--sport' and "
 						      "`--dport'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--reply-port-src' and "
 						      "`--report-port-dst'");
 			break;
diff --git a/extensions/libct_proto_gre.c b/extensions/libct_proto_gre.c
index a36d111..0ec890e 100644
--- a/extensions/libct_proto_gre.c
+++ b/extensions/libct_proto_gre.c
@@ -84,60 +84,56 @@ static char gre_commands_v_options[NUMBER_OF_CMD][GRE_OPT_MAX] =
 /*EXP_EVENT*/	  {0,0,0,0,0,0,0,0},
 };
 
-static int parse_options(char c,
-			 struct nf_conntrack *ct,
-			 struct nf_conntrack *exptuple,
-			 struct nf_conntrack *mask,
-			 unsigned int *flags)
+static int parse_options(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	switch(c) {
 	uint16_t port;
 	case '1':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_ORIG_SKEY;
 		break;
 	case '2':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_ORIG_DKEY;
 		break;
 	case '3':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_REPL_SKEY;
 		break;
 	case '4':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_REPL_DKEY;
 		break;
 	case '5':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_MASK_SKEY;
 		break;
 	case '6':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_MASK_DKEY;
 		break;
 	case '7':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_EXPTUPLE_SKEY;
 		break;
 	case '8':
 		port = htons(strtoul(optarg, NULL, 0));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, IPPROTO_GRE);
 		*flags |= CT_GRE_EXPTUPLE_DKEY;
 		break;
 	}
@@ -158,25 +154,25 @@ static unsigned int gre_valid_flags[GRE_VALID_FLAGS_MAX] = {
        CT_GRE_REPL_SKEY | CT_GRE_REPL_DKEY,
 };
 
-static void final_check(unsigned int flags,
-		        unsigned int cmd,
-		        struct nf_conntrack *ct)
+static void final_check(struct ct_cmd *cmd, unsigned int flags)
 {
 	int ret, partial;
 
-	ret = generic_opt_check(flags, GRE_OPT_MAX,
-				gre_commands_v_options[cmd], gre_optflags,
+	ret = generic_opt_check(cmd, flags, GRE_OPT_MAX,
+				gre_commands_v_options[cmd->cmd_bit], gre_optflags,
 				gre_valid_flags, GRE_VALID_FLAGS_MAX, &partial);
 	if (!ret) {
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--srckey' and "
 						      "`--dstkey'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--reply-src-key' and "
 						      "`--reply-dst-key'");
 			break;
diff --git a/extensions/libct_proto_icmp.c b/extensions/libct_proto_icmp.c
index ec52c39..e3ac2cc 100644
--- a/extensions/libct_proto_icmp.c
+++ b/extensions/libct_proto_icmp.c
@@ -65,37 +65,33 @@ static void help(void)
 	fprintf(stdout, "  --icmp-id\t\t\ticmp id\n");
 }
 
-static int parse(char c,
-		 struct nf_conntrack *ct,
-		 struct nf_conntrack *exptuple,
-		 struct nf_conntrack *mask,
-		 unsigned int *flags)
+static int parse(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	switch(c) {
 		uint8_t tmp;
 		uint16_t id;
 		case '1':
 			tmp = atoi(optarg);
-			nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, tmp);
-			nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_ICMP);
-			if (nfct_attr_is_set(ct, ATTR_REPL_L3PROTO))
-				nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_ICMP);
+			nfct_set_attr_u8(cmd->ct, ATTR_ICMP_TYPE, tmp);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, IPPROTO_ICMP);
+			if (nfct_attr_is_set(cmd->ct, ATTR_REPL_L3PROTO))
+				nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_ICMP);
 			*flags |= CT_ICMP_TYPE;
 			break;
 		case '2':
 			tmp = atoi(optarg);
-			nfct_set_attr_u8(ct, ATTR_ICMP_CODE, tmp);
-			nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_ICMP);
-			if (nfct_attr_is_set(ct, ATTR_REPL_L3PROTO))
-				nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_ICMP);
+			nfct_set_attr_u8(cmd->ct, ATTR_ICMP_CODE, tmp);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, IPPROTO_ICMP);
+			if (nfct_attr_is_set(cmd->ct, ATTR_REPL_L3PROTO))
+				nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_ICMP);
 			*flags |= CT_ICMP_CODE;
 			break;
 		case '3':
 			id = htons(atoi(optarg));
-			nfct_set_attr_u16(ct, ATTR_ICMP_ID, id);
-			nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_ICMP);
-			if (nfct_attr_is_set(ct, ATTR_REPL_L3PROTO))
-				nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_ICMP);
+			nfct_set_attr_u16(cmd->ct, ATTR_ICMP_ID, id);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, IPPROTO_ICMP);
+			if (nfct_attr_is_set(cmd->ct, ATTR_REPL_L3PROTO))
+				nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_ICMP);
 			*flags |= CT_ICMP_ID;
 			break;
 	}
@@ -109,13 +105,11 @@ static const struct ct_print_opts icmp_print_opts[] = {
 	{}
 };
 
-static void final_check(unsigned int flags,
-		        unsigned int cmd,
-		        struct nf_conntrack *ct)
+static void final_check(struct ct_cmd *cmd, unsigned int flags)
 {
-	generic_opt_check(flags,
+	generic_opt_check(cmd, flags,
 			  ICMP_NUMBER_OF_OPT,
-			  icmp_commands_v_options[cmd],
+			  icmp_commands_v_options[cmd->cmd_bit],
 			  icmp_optflags, NULL, 0, NULL);
 }
 
diff --git a/extensions/libct_proto_icmpv6.c b/extensions/libct_proto_icmpv6.c
index fe16a1d..65aa3b5 100644
--- a/extensions/libct_proto_icmpv6.c
+++ b/extensions/libct_proto_icmpv6.c
@@ -68,37 +68,33 @@ static void help(void)
 	fprintf(stdout, "  --icmpv6-id\t\t\ticmpv6 id\n");
 }
 
-static int parse(char c,
-		 struct nf_conntrack *ct,
-		 struct nf_conntrack *exptuple,
-		 struct nf_conntrack *mask,
-		 unsigned int *flags)
+static int parse(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	switch(c) {
 		uint8_t tmp;
 		uint16_t id;
 		case '1':
 			tmp = atoi(optarg);
-			nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, tmp);
-			nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_ICMPV6);
-			if (nfct_attr_is_set(ct, ATTR_REPL_L3PROTO))
-				nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_ICMPV6);
+			nfct_set_attr_u8(cmd->ct, ATTR_ICMP_TYPE, tmp);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, IPPROTO_ICMPV6);
+			if (nfct_attr_is_set(cmd->ct, ATTR_REPL_L3PROTO))
+				nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_ICMPV6);
 			*flags |= CT_ICMP_TYPE;
 			break;
 		case '2':
 			tmp = atoi(optarg);
-			nfct_set_attr_u8(ct, ATTR_ICMP_CODE, tmp);
-			nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_ICMPV6);
-			if (nfct_attr_is_set(ct, ATTR_REPL_L3PROTO))
-				nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_ICMPV6);
+			nfct_set_attr_u8(cmd->ct, ATTR_ICMP_CODE, tmp);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, IPPROTO_ICMPV6);
+			if (nfct_attr_is_set(cmd->ct, ATTR_REPL_L3PROTO))
+				nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_ICMPV6);
 			*flags |= CT_ICMP_CODE;
 			break;
 		case '3':
 			id = htons(atoi(optarg));
-			nfct_set_attr_u16(ct, ATTR_ICMP_ID, id);
-			nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_ICMPV6);
-			if (nfct_attr_is_set(ct, ATTR_REPL_L3PROTO))
-				nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_ICMPV6);
+			nfct_set_attr_u16(cmd->ct, ATTR_ICMP_ID, id);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, IPPROTO_ICMPV6);
+			if (nfct_attr_is_set(cmd->ct, ATTR_REPL_L3PROTO))
+				nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_ICMPV6);
 			*flags |= CT_ICMP_ID;
 			break;
 	}
@@ -112,12 +108,10 @@ static const struct ct_print_opts icmpv6_print_opts[] = {
 	{},
 };
 
-static void final_check(unsigned int flags,
-		        unsigned int cmd,
-		        struct nf_conntrack *ct)
+static void final_check(struct ct_cmd *cmd, unsigned int flags)
 {
-	generic_opt_check(flags, ICMPV6_NUMBER_OF_OPT,
-			  icmpv6_commands_v_options[cmd], icmpv6_optflags,
+	generic_opt_check(cmd, flags, ICMPV6_NUMBER_OF_OPT,
+			  icmpv6_commands_v_options[cmd->cmd_bit], icmpv6_optflags,
 			  NULL, 0, NULL);
 }
 
diff --git a/extensions/libct_proto_sctp.c b/extensions/libct_proto_sctp.c
index a58ccde..5dfe0ff 100644
--- a/extensions/libct_proto_sctp.c
+++ b/extensions/libct_proto_sctp.c
@@ -115,9 +115,7 @@ static void help(void)
 }
 
 static int
-parse_options(char c, struct nf_conntrack *ct,
-	      struct nf_conntrack *exptuple, struct nf_conntrack *mask,
-	      unsigned int *flags)
+parse_options(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	int i;
 	uint16_t port;
@@ -126,72 +124,72 @@ parse_options(char c, struct nf_conntrack *ct,
 	switch(c) {
 	case 1:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
 		*flags |= CT_SCTP_ORIG_SPORT;
 		break;
 	case 2:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
 		*flags |= CT_SCTP_ORIG_DPORT;
 		break;
 	case 3:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_SCTP);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_SCTP);
 		*flags |= CT_SCTP_REPL_SPORT;
 		break;
 	case 4:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_SCTP);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_SCTP);
 		*flags |= CT_SCTP_REPL_DPORT;
 		break;
 	case 5:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
 		*flags |= CT_SCTP_MASK_SPORT;
 		break;
 	case 6:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_SCTP);
 		*flags |= CT_SCTP_MASK_DPORT;
 		break;
 	case 7:
 		for (i=0; i<SCTP_CONNTRACK_MAX; i++) {
 			if (strcmp(optarg, sctp_states[i]) == 0) {
-				nfct_set_attr_u8(ct, ATTR_SCTP_STATE, i);
+				nfct_set_attr_u8(cmd->ct, ATTR_SCTP_STATE, i);
 				break;
 			}
 		}
 		if (i == SCTP_CONNTRACK_MAX)
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "unknown SCTP state `%s'", optarg);
 		*flags |= CT_SCTP_STATE;
 		break;
 	case 8:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, port);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, port);
 		*flags |= CT_SCTP_EXPTUPLE_SPORT;
 		break;
 	case 9:
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_DST, port); 
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, port);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, port);
 		*flags |= CT_SCTP_EXPTUPLE_DPORT;
 		break;
 	case 10:
 		vtag = htonl(atoi(optarg));
-		nfct_set_attr_u32(ct, ATTR_SCTP_VTAG_ORIG, vtag); 
+		nfct_set_attr_u32(cmd->ct, ATTR_SCTP_VTAG_ORIG, vtag);
 		*flags |= CT_SCTP_ORIG_VTAG;
 		break;
 	case 11:
 		vtag = htonl(atoi(optarg));
-		nfct_set_attr_u32(ct, ATTR_SCTP_VTAG_REPL, vtag); 
+		nfct_set_attr_u32(cmd->ct, ATTR_SCTP_VTAG_REPL, vtag);
 		*flags |= CT_SCTP_REPL_VTAG;
 		break;
 	}
@@ -216,24 +214,26 @@ static unsigned int dccp_valid_flags[SCTP_VALID_FLAGS_MAX] = {
 };
 
 static void
-final_check(unsigned int flags, unsigned int cmd, struct nf_conntrack *ct)
+final_check(struct ct_cmd *cmd, unsigned int flags)
 {
 	int ret, partial;
 
-	ret = generic_opt_check(flags, SCTP_OPT_MAX,
-				sctp_commands_v_options[cmd], sctp_optflags,
+	ret = generic_opt_check(cmd, flags, SCTP_OPT_MAX,
+				sctp_commands_v_options[cmd->cmd_bit], sctp_optflags,
 				dccp_valid_flags, SCTP_VALID_FLAGS_MAX,
 				&partial);
 	if (!ret) {
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--sport' and "
 						      "`--dport'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--reply-src-port' and "
 						      "`--reply-dst-port'");
 			break;
diff --git a/extensions/libct_proto_tcp.c b/extensions/libct_proto_tcp.c
index 3da0dc6..ebb3a98 100644
--- a/extensions/libct_proto_tcp.c
+++ b/extensions/libct_proto_tcp.c
@@ -99,11 +99,7 @@ static void help(void)
 	fprintf(stdout, "  --state\t\t\tTCP state, fe. ESTABLISHED\n");
 }
 
-static int parse_options(char c,
-			 struct nf_conntrack *ct,
-			 struct nf_conntrack *exptuple,
-			 struct nf_conntrack *mask,
-			 unsigned int *flags)
+static int parse_options(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	int i;
 	uint16_t port;
@@ -111,66 +107,66 @@ static int parse_options(char c,
 	switch(c) {
 	case '1':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
 		*flags |= CT_TCP_ORIG_SPORT;
 		break;
 	case '2':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
 		*flags |= CT_TCP_ORIG_DPORT;
 		break;
 	case '3':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_TCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_TCP);
 		*flags |= CT_TCP_REPL_SPORT;
 		break;
 	case '4':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_TCP);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_TCP);
 		*flags |= CT_TCP_REPL_DPORT;
 		break;
 	case '5':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
 		*flags |= CT_TCP_MASK_SPORT;
 		break;
 	case '6':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_TCP);
 		*flags |= CT_TCP_MASK_DPORT;
 		break;
 	case '7':
 		for (i=0; i<TCP_CONNTRACK_MAX; i++) {
 			if (strcmp(optarg, tcp_states[i]) == 0) {
-				nfct_set_attr_u8(ct, ATTR_TCP_STATE, i);
+				nfct_set_attr_u8(cmd->ct, ATTR_TCP_STATE, i);
 				break;
 			}
 		}
 		/* For backward compatibility with Linux kernel < 2.6.31. */
 		if (strcmp(optarg, "LISTEN") == 0) {
-			nfct_set_attr_u8(ct, ATTR_TCP_STATE,
+			nfct_set_attr_u8(cmd->ct, ATTR_TCP_STATE,
 					 TCP_CONNTRACK_LISTEN);
 		} else if (i == TCP_CONNTRACK_MAX)
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "unknown TCP state `%s'", optarg);
 		*flags |= CT_TCP_STATE;
 		break;
 	case '8':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, port);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, port);
 		*flags |= CT_TCP_EXPTUPLE_SPORT;
 		break;
 	case '9':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_DST, port); 
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, port);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, port);
 		*flags |= CT_TCP_EXPTUPLE_DPORT;
 		break;
 	}
@@ -192,25 +188,25 @@ static unsigned int tcp_valid_flags[TCP_VALID_FLAGS_MAX] = {
        CT_TCP_REPL_SPORT | CT_TCP_REPL_DPORT,
 };
 
-static void final_check(unsigned int flags,
-			unsigned int cmd,
-			struct nf_conntrack *ct)
+static void final_check(struct ct_cmd *cmd, unsigned int flags)
 {
 	int ret, partial;
 
-	ret = generic_opt_check(flags, TCP_NUMBER_OF_OPT,
-				tcp_commands_v_options[cmd], tcp_optflags,
+	ret = generic_opt_check(cmd, flags, TCP_NUMBER_OF_OPT,
+				tcp_commands_v_options[cmd->cmd_bit], tcp_optflags,
 				tcp_valid_flags, TCP_VALID_FLAGS_MAX, &partial);
 	if (!ret) {
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--sport' and "
 						      "`--dport'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--reply-src-port' and "
 						      "`--reply-dst-port'");
 			break;
@@ -223,13 +219,13 @@ static void final_check(unsigned int flags,
 
 	/* This allows to reopen a new connection directly from TIME-WAIT
 	 * as RFC 1122 states. See nf_conntrack_proto_tcp.c for more info. */
-	if (nfct_get_attr_u8(ct, ATTR_TCP_STATE) >= TCP_CONNTRACK_TIME_WAIT)
+	if (nfct_get_attr_u8(cmd->ct, ATTR_TCP_STATE) >= TCP_CONNTRACK_TIME_WAIT)
 		tcp_flags |= IP_CT_TCP_FLAG_CLOSE_INIT;
 
-	nfct_set_attr_u8(ct, ATTR_TCP_FLAGS_ORIG, tcp_flags);
-	nfct_set_attr_u8(ct, ATTR_TCP_MASK_ORIG, tcp_flags);
-	nfct_set_attr_u8(ct, ATTR_TCP_FLAGS_REPL, tcp_flags);
-	nfct_set_attr_u8(ct, ATTR_TCP_MASK_REPL, tcp_flags);
+	nfct_set_attr_u8(cmd->ct, ATTR_TCP_FLAGS_ORIG, tcp_flags);
+	nfct_set_attr_u8(cmd->ct, ATTR_TCP_MASK_ORIG, tcp_flags);
+	nfct_set_attr_u8(cmd->ct, ATTR_TCP_FLAGS_REPL, tcp_flags);
+	nfct_set_attr_u8(cmd->ct, ATTR_TCP_MASK_REPL, tcp_flags);
 }
 
 static struct ctproto_handler tcp = {
diff --git a/extensions/libct_proto_udp.c b/extensions/libct_proto_udp.c
index fe43548..d0450c0 100644
--- a/extensions/libct_proto_udp.c
+++ b/extensions/libct_proto_udp.c
@@ -80,62 +80,58 @@ static char udp_commands_v_options[NUMBER_OF_CMD][UDP_NUMBER_OF_OPT] =
 /*EXP_EVENT*/     {0,0,0,0,0,0,0,0},
 };
 
-static int parse_options(char c,
-			 struct nf_conntrack *ct,
-			 struct nf_conntrack *exptuple,
-			 struct nf_conntrack *mask,
-			 unsigned int *flags)
+static int parse_options(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	switch(c) {
 		uint16_t port;
 		case '1':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
-			nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
+			nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_SRC, port);
+			nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
 			*flags |= CT_UDP_ORIG_SPORT;
 			break;
 		case '2':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, port);
-			nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
+			nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_DST, port);
+			nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
 			*flags |= CT_UDP_ORIG_DPORT;
 			break;
 		case '3':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, port);
-			nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_UDP);
+			nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_SRC, port);
+			nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_UDP);
 			*flags |= CT_UDP_REPL_SPORT;
 			break;
 		case '4':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, port);
-			nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_UDP);
+			nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_DST, port);
+			nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_UDP);
 			*flags |= CT_UDP_REPL_DPORT;
 			break;
 		case '5':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(mask, ATTR_ORIG_PORT_SRC, port);
-			nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
+			nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_SRC, port);
+			nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
 			*flags |= CT_UDP_MASK_SPORT;
 			break;
 		case '6':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(mask, ATTR_ORIG_PORT_DST, port);
-			nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
+			nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_DST, port);
+			nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_UDP);
 			*flags |= CT_UDP_MASK_DPORT;
 			break;
 		case '7':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_SRC, port);
-			nfct_set_attr_u8(exptuple,
+			nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_SRC, port);
+			nfct_set_attr_u8(cmd->exptuple,
 					 ATTR_ORIG_L4PROTO,
 					 IPPROTO_UDP);
 			*flags |= CT_UDP_EXPTUPLE_SPORT;
 			break;
 		case '8':
 			port = htons(atoi(optarg));
-			nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_DST, port);
-			nfct_set_attr_u8(exptuple,
+			nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_DST, port);
+			nfct_set_attr_u8(cmd->exptuple,
 					 ATTR_ORIG_L4PROTO,
 					 IPPROTO_UDP);
 			*flags |= CT_UDP_EXPTUPLE_DPORT;
@@ -158,25 +154,25 @@ static unsigned int udp_valid_flags[UDP_VALID_FLAGS_MAX] = {
        CT_UDP_REPL_SPORT | CT_UDP_REPL_DPORT,
 };
 
-static void final_check(unsigned int flags,
-		        unsigned int cmd,
-		        struct nf_conntrack *ct)
+static void final_check(struct ct_cmd *cmd, unsigned int flags)
 {
 	int ret, partial;
 
-	ret = generic_opt_check(flags, UDP_NUMBER_OF_OPT,
-				udp_commands_v_options[cmd], udp_optflags,
+	ret = generic_opt_check(cmd, flags, UDP_NUMBER_OF_OPT,
+				udp_commands_v_options[cmd->cmd_bit], udp_optflags,
 				udp_valid_flags, UDP_VALID_FLAGS_MAX, &partial);
 	if (!ret) {
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--sport' and "
 						      "`--dport'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--reply-src-port' and "
 						      "`--reply-dst-port'");
 			break;
diff --git a/extensions/libct_proto_udplite.c b/extensions/libct_proto_udplite.c
index 2bece38..bd3ec6a 100644
--- a/extensions/libct_proto_udplite.c
+++ b/extensions/libct_proto_udplite.c
@@ -88,60 +88,56 @@ static char udplite_commands_v_options[NUMBER_OF_CMD][UDP_OPT_MAX] =
 /*EXP_EVENT*/	  {0,0,0,0,0,0,0,0},
 };
 
-static int parse_options(char c,
-			 struct nf_conntrack *ct,
-			 struct nf_conntrack *exptuple,
-			 struct nf_conntrack *mask,
-			 unsigned int *flags)
+static int parse_options(struct ct_cmd *cmd, char c, unsigned int *flags)
 {
 	switch(c) {
 	uint16_t port;
 	case '1':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_ORIG_SPORT;
 		break;
 	case '2':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->ct, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_ORIG_DPORT;
 		break;
 	case '3':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_REPL_SPORT;
 		break;
 	case '4':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, port);
-		nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->ct, ATTR_REPL_PORT_DST, port);
+		nfct_set_attr_u8(cmd->ct, ATTR_REPL_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_REPL_DPORT;
 		break;
 	case '5':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_MASK_SPORT;
 		break;
 	case '6':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(mask, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(mask, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->mask, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->mask, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_MASK_DPORT;
 		break;
 	case '7':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_SRC, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_SRC, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_EXPTUPLE_SPORT;
 		break;
 	case '8':
 		port = htons(atoi(optarg));
-		nfct_set_attr_u16(exptuple, ATTR_ORIG_PORT_DST, port);
-		nfct_set_attr_u8(exptuple, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
+		nfct_set_attr_u16(cmd->exptuple, ATTR_ORIG_PORT_DST, port);
+		nfct_set_attr_u8(cmd->exptuple, ATTR_ORIG_L4PROTO, IPPROTO_UDPLITE);
 		*flags |= CT_UDPLITE_EXPTUPLE_DPORT;
 		break;
 	}
@@ -163,12 +159,12 @@ static unsigned int udplite_valid_flags[UDPLITE_VALID_FLAGS_MAX] = {
 };
 
 static void
-final_check(unsigned int flags, unsigned int cmd, struct nf_conntrack *ct)
+final_check(struct ct_cmd *cmd, unsigned int flags)
 {
 	int ret, partial;
 
-	ret = generic_opt_check(flags, UDP_OPT_MAX,
-				udplite_commands_v_options[cmd],
+	ret = generic_opt_check(cmd, flags, UDP_OPT_MAX,
+				udplite_commands_v_options[cmd->cmd_bit],
 				udplite_optflags,
 				udplite_valid_flags, UDPLITE_VALID_FLAGS_MAX,
 				&partial);
@@ -176,12 +172,14 @@ final_check(unsigned int flags, unsigned int cmd, struct nf_conntrack *ct)
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--sport' and "
 						      "`--dport'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd,
+						      "you have to specify "
 						      "`--reply-src-port' and "
 						      "`--reply-dst-port'");
 			break;
diff --git a/include/conntrack.h b/include/conntrack.h
index 1c1720e..6126d86 100644
--- a/include/conntrack.h
+++ b/include/conntrack.h
@@ -16,6 +16,53 @@
 
 struct nf_conntrack;
 
+struct u32_mask {
+	uint32_t value;
+	uint32_t mask;
+};
+
+struct ct_cmd {
+	struct list_head list_entry;
+
+	unsigned int command;
+
+	unsigned int cmd_bit;
+
+	unsigned int options;
+
+	int family;
+
+	unsigned int type;
+
+	unsigned int event_mask;
+
+	int protonum;
+
+	/* these two are used purely for error reporting */
+	unsigned int line_number;
+	const char *file_name;
+
+	size_t socketbuffersize;
+
+	struct nf_conntrack *ct;
+	struct nf_expect *exp;
+	/* Expectations require the expectation tuple and the mask. */
+	struct nf_conntrack *exptuple, *mask;
+
+	/* Allows filtering/setting specific bits in the ctmark */
+	struct u32_mask mark;
+
+	/* Allow to filter by mark from kernel-space. */
+	struct nfct_filter_dump_mark filter_mark_kernel;
+	bool filter_mark_kernel_set;
+
+	/* Allows filtering by ctlabels */
+	struct nfct_bitmask *label;
+
+	/* Allows setting/removing specific ctlabels */
+	struct nfct_bitmask *label_modify;
+};
+
 struct ctproto_handler {
 	struct list_head 	head;
 
@@ -25,15 +72,12 @@ struct ctproto_handler {
 
 	uint32_t		protoinfo_attr;
 
-	int (*parse_opts)(char c,
-			  struct nf_conntrack *ct,
-			  struct nf_conntrack *exptuple,
-			  struct nf_conntrack *mask,
+	int (*parse_opts)(struct ct_cmd *cmd,
+			  char c,
 			  unsigned int *flags);
 
-	void (*final_check)(unsigned int flags,
-			    unsigned int command,
-			    struct nf_conntrack *ct);
+	void (*final_check)(struct ct_cmd *cmd,
+			    unsigned int flags);
 
 	const struct ct_print_opts *print_opts;
 
@@ -50,11 +94,12 @@ enum exittype {
 	VERSION_PROBLEM
 };
 
-int generic_opt_check(int options, int nops,
+int generic_opt_check(struct ct_cmd *cmd, int options, int nops,
 		      char *optset, const char *optflg[],
 		      unsigned int *coupled_flags, int coupled_flags_size,
 		      int *partial);
-void exit_error(enum exittype status, const char *msg, ...);
+void exit_error(enum exittype status, struct ct_cmd *cmd,
+		const char *msg, ...);
 
 extern void register_proto(struct ctproto_handler *h);
 
diff --git a/src/conntrack.c b/src/conntrack.c
index d05a599..81ebece 100644
--- a/src/conntrack.c
+++ b/src/conntrack.c
@@ -64,6 +64,7 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <libmnl/libmnl.h>
+#include <linux_list.h>
 #include <linux/netfilter/nf_conntrack_common.h>
 #include <libnetfilter_conntrack/libnetfilter_conntrack.h>
 
@@ -72,59 +73,71 @@ static struct nfct_mnl_socket {
 	uint32_t		portid;
 } sock;
 
-struct u32_mask {
-	uint32_t value;
-	uint32_t mask;
+struct ct_cmd_list {
+	struct list_head list;
 };
 
-/* These are the template objects that are used to send commands. */
-static struct {
-	struct nf_conntrack *ct;
-	struct nf_expect *exp;
-	/* Expectations require the expectation tuple and the mask. */
-	struct nf_conntrack *exptuple, *mask;
+static void ct_cmd_free(struct ct_cmd *cmd)
+{
+	if (cmd->ct)
+		nfct_destroy(cmd->ct);
+	if (cmd->exptuple)
+		nfct_destroy(cmd->exptuple);
+	if (cmd->mask)
+		nfct_destroy(cmd->mask);
+	if (cmd->exp)
+		nfexp_destroy(cmd->exp);
+	if (cmd->label)
+		nfct_bitmask_destroy(cmd->label);
+	if (cmd->label_modify)
+		nfct_bitmask_destroy(cmd->label_modify);
 
-	/* Allows filtering/setting specific bits in the ctmark */
-	struct u32_mask mark;
+	free(cmd);
+}
 
-	/* Allow to filter by mark from kernel-space. */
-	struct nfct_filter_dump_mark filter_mark_kernel;
-	bool filter_mark_kernel_set;
+static struct ct_cmd* ct_cmd_alloc(void)
+{
+	struct ct_cmd *cmd;
 
-	/* Allows filtering by ctlabels */
-	struct nfct_bitmask *label;
+	cmd = calloc(1, sizeof(*cmd));
+	if (!cmd)
+		exit_error(OTHER_PROBLEM, NULL, "malloc ct_cmd failed!");
 
-	/* Allows setting/removing specific ctlabels */
-	struct nfct_bitmask *label_modify;
-} tmpl;
+	cmd->ct = nfct_new();
+	cmd->exptuple = nfct_new();
+	cmd->mask = nfct_new();
+	cmd->exp = nfexp_new();
 
-static int alloc_tmpl_objects(void)
-{
-	tmpl.ct = nfct_new();
-	tmpl.exptuple = nfct_new();
-	tmpl.mask = nfct_new();
-	tmpl.exp = nfexp_new();
+	if (cmd->ct == NULL || cmd->exptuple == NULL ||
+			cmd->mask == NULL || cmd->exp == NULL) {
+		ct_cmd_free(cmd);
+		exit_error(OTHER_PROBLEM, NULL, "failed to allocate ct objects!");
+	}
+	return cmd;
+}
 
-	memset(&tmpl.mark, 0, sizeof(tmpl.mark));
+static void ct_cmd_list_init(struct ct_cmd_list *list)
+{
+	memset(list, 0, sizeof(*list));
+	INIT_LIST_HEAD(&list->list);
+}
 
-	return tmpl.ct != NULL && tmpl.exptuple != NULL &&
-	       tmpl.mask != NULL && tmpl.exp != NULL;
+static void ct_cmd_list_add(struct ct_cmd_list *list, struct ct_cmd *cmd)
+{
+	list_add_tail(&cmd->list_entry, &list->list);
 }
 
-static void free_tmpl_objects(void)
+void ct_cmd_apply(struct ct_cmd *cmd);
+
+static void ct_cmd_list_apply(struct ct_cmd_list *list)
 {
-	if (tmpl.ct)
-		nfct_destroy(tmpl.ct);
-	if (tmpl.exptuple)
-		nfct_destroy(tmpl.exptuple);
-	if (tmpl.mask)
-		nfct_destroy(tmpl.mask);
-	if (tmpl.exp)
-		nfexp_destroy(tmpl.exp);
-	if (tmpl.label)
-		nfct_bitmask_destroy(tmpl.label);
-	if (tmpl.label_modify)
-		nfct_bitmask_destroy(tmpl.label_modify);
+	struct ct_cmd *cmd, *tmp;
+
+	list_for_each_entry_safe(cmd, tmp, &list->list, list_entry) {
+		list_del(&cmd->list_entry);
+		ct_cmd_apply(cmd);
+		ct_cmd_free(cmd);
+	}
 }
 
 enum ct_command {
@@ -374,6 +387,14 @@ static const char *getopt_str = ":L::I::U::D::G::E::F::hVs:d:r:q:"
 				"p:t:u:e:a:z[:]:{:}:m:i:f:o:n::"
 				"g::c:b:C::Sj::w:l:<:>::(:):";
 
+
+static struct option load_file_opts[] = {
+	{"load-file", 1, 0, 'R'},
+	{0, 0, 0, 0}
+};
+
+static const char *load_file_getopt_str = ":R:";
+
 /* Table of legal combinations of commands and options.  If any of the
  * given commands make an option legal, that option is legal (applies to
  * CMD_LIST and CMD_ZERO only).
@@ -521,6 +542,7 @@ static char exit_msg[NUMBER_OF_CMD][64] = {
 	[CT_DELETE_BIT]		= "%d flow entries have been deleted.\n",
 	[CT_GET_BIT] 		= "%d flow entries have been shown.\n",
 	[CT_EVENT_BIT]		= "%d flow events have been shown.\n",
+	[EXP_EVENT_BIT]		= "%d expectation events have been shown.\n",
 	[EXP_LIST_BIT]		= "%d expectations have been shown.\n",
 	[EXP_DELETE_BIT]	= "%d expectations have been shown.\n",
 };
@@ -535,7 +557,8 @@ static const char usage_commands[] =
 	"  -E [table] [options]\t\tShow events\n"
 	"  -F [table]\t\t\tFlush table\n"
 	"  -C [table]\t\t\tShow counter\n"
-	"  -S\t\t\t\tShow statistics\n";
+	"  -S\t\t\t\tShow statistics\n"
+	"  --load-file file\t\t Load conntrack entries from a file";
 
 static const char usage_tables[] =
 	"Tables: conntrack, expect, dying, unconfirmed\n";
@@ -583,10 +606,61 @@ static const char usage_parameters[] =
 
 #define OPTION_OFFSET 256
 
-static struct nfct_handle *cth, *ith;
+static char *programm;
+static struct nfct_handle *cth, *ith, *exh, *evh;
 static struct option *opts = original_opts;
 static unsigned int global_option_offset = 0;
 
+static unsigned int cmd_executed = 0;
+static const unsigned int cmd_no_entries_ok = 0
+						| CT_LIST
+						| EXP_LIST
+						;
+static const unsigned int cmd_load_file_allowed = 0
+						| CT_CREATE
+						| CT_UPDATE_BIT
+						| CT_DELETE
+						| CT_FLUSH
+						| EXP_CREATE
+						| EXP_DELETE
+						| EXP_FLUSH
+						;
+static unsigned int cmd_counters[NUMBER_OF_CMD];
+
+static int
+print_cmd_counters(void)
+{
+	int i, ret = EXIT_FAILURE;
+
+	if (!cmd_executed)
+		return EXIT_SUCCESS;
+
+	for (i = 0;
+		i < (int)(sizeof(cmd_counters) / sizeof(cmd_counters[0]));
+		++i) {
+		if (cmd_executed & 1 << i) {
+			if (exit_msg[i][0]) {
+				fprintf(stderr, "%s v%s (conntrack-tools): ",
+							PROGNAME, VERSION);
+				fprintf(stderr, exit_msg[i], cmd_counters[i]);
+			}
+			/*
+			 * If there is at least one command which is supposed
+			 * to return success, EXIT_SUCCESS is returned.
+			 * I.e. for the --load-file case this would ensure that
+			 * e.g. -D followed by a series of -I's
+			 * would return success in case there are no entries
+			 * to be deleted with the -D command preceding the -I's
+			 */
+			if (!exit_msg[i][0]
+					|| cmd_counters[i] != 0
+					|| cmd_no_entries_ok & 1 << i)
+				ret &= EXIT_SUCCESS;
+		}
+	}
+	return ret;
+}
+
 #define ADDR_VALID_FLAGS_MAX   2
 static unsigned int addr_valid_flags[ADDR_VALID_FLAGS_MAX] = {
 	CT_OPT_ORIG_SRC | CT_OPT_ORIG_DST,
@@ -595,7 +669,6 @@ static unsigned int addr_valid_flags[ADDR_VALID_FLAGS_MAX] = {
 
 static LIST_HEAD(proto_list);
 
-static unsigned int options;
 static struct nfct_labelmap *labelmap;
 static int filter_family;
 
@@ -609,6 +682,51 @@ void register_proto(struct ctproto_handler *h)
 	list_add(&h->head, &proto_list);
 }
 
+static inline struct nfct_handle* glob_cth(void)
+{
+	if (!cth) {
+		cth = nfct_open(CONNTRACK, 0);
+		if (!cth)
+			exit_error(OTHER_PROBLEM, NULL, "Can't open handler");
+	}
+	return cth;
+}
+
+static inline struct nfct_handle* glob_ith(void)
+{
+	if (!ith) {
+		ith = nfct_open(CONNTRACK, 0);
+		if (!ith)
+			exit_error(OTHER_PROBLEM, NULL, "Can't open handler");
+	}
+	return ith;
+}
+
+static inline struct nfct_handle* glob_exh(void)
+{
+	if (!exh) {
+		exh = nfct_open(EXPECT, 0);
+		if (!exh)
+			exit_error(OTHER_PROBLEM, NULL, "Can't open handler");
+	}
+	return exh;
+}
+
+static void glob_cleanup(void)
+{
+	if (ith)
+		nfct_close(ith);
+	if (cth)
+		nfct_close(cth);
+	if (exh)
+		nfct_close(exh);
+	if (evh)
+		nfct_close(evh);
+
+	if (labelmap)
+		nfct_labelmap_destroy(labelmap);
+}
+
 #define BUFFER_SIZE(ret, size, len, offset) do {			\
 	if ((int)ret > (int)len)					\
 		ret = len;						\
@@ -930,21 +1048,35 @@ static void free_options(void)
 	}
 }
 
+static void usage(void);
+
 void __attribute__((noreturn))
-exit_error(enum exittype status, const char *msg, ...)
+exit_error(enum exittype status, struct ct_cmd *cmd, const char *msg, ...)
 {
 	va_list args;
 
+	/* if some operations have been done, print it in any way */
+	print_cmd_counters();
+
 	free_options();
 	va_start(args, msg);
 	fprintf(stderr,"%s v%s (conntrack-tools): ", PROGNAME, VERSION);
 	vfprintf(stderr, msg, args);
-	fprintf(stderr, "\n");
+	fprintf(stderr,"\n");
 	va_end(args);
-	if (status == PARAMETER_PROBLEM)
-		exit_tryhelp(status);
+	if (cmd && cmd->file_name)
+		fprintf(stderr, "file: %s line: %u\n",
+				cmd->file_name, cmd->line_number);
+	else if (status == PARAMETER_PROBLEM) {
+		if (cmd && !cmd->command)
+			/* called w/o arguments? need to print usage */
+			usage();
+		else
+			exit_tryhelp(status);
+	}
+
 	/* release template objects that were allocated in the setup stage. */
-	free_tmpl_objects();
+	glob_cleanup();
 	exit(status);
 }
 
@@ -974,7 +1106,9 @@ static const char *get_long_opt(int opt)
 	return "unknown";
 }
 
-int generic_opt_check(int local_options, int num_opts,
+int generic_opt_check(struct ct_cmd *cmd,
+			  int options,
+			  int num_opts,
 		      char *optset, const char *optflg[],
 		      unsigned int *coupled_flags, int coupled_flags_size,
 		      int *partial)
@@ -982,15 +1116,15 @@ int generic_opt_check(int local_options, int num_opts,
 	int i, matching = -1, special_case = 0;
 
 	for (i = 0; i < num_opts; i++) {
-		if (!(local_options & (1<<i))) {
+		if (!(options & (1<<i))) {
 			if (optset[i] == 1)
-				exit_error(PARAMETER_PROBLEM, 
+				exit_error(PARAMETER_PROBLEM, cmd,
 					   "You need to supply the "
 					   "`--%s' option for this "
 					   "command", optflg[i]);
 		} else {
 			if (optset[i] == 0)
-				exit_error(PARAMETER_PROBLEM, "Illegal "
+				exit_error(PARAMETER_PROBLEM, cmd, "Illegal "
 					   "option `--%s' with this "
 					   "command", optflg[i]);
 		}
@@ -1005,12 +1139,12 @@ int generic_opt_check(int local_options, int num_opts,
 	*partial = -1;
 	for (i=0; i<coupled_flags_size; i++) {
 		/* we look for an exact matching to ensure this is correct */
-		if ((local_options & coupled_flags[i]) == coupled_flags[i]) {
+		if ((options & coupled_flags[i]) == coupled_flags[i]) {
 			matching = i;
 			break;
 		}
 		/* ... otherwise look for the first partial matching */
-		if ((local_options & coupled_flags[i]) && *partial < 0) {
+		if ((options & coupled_flags[i]) && *partial < 0) {
 			*partial = i;
 		}
 	}
@@ -1129,7 +1263,7 @@ static struct parse_parameter {
 };
 
 static int
-do_parse_parameter(const char *str, size_t str_length, unsigned int *value, 
+do_parse_parameter(const char *str, size_t str_length, unsigned int *value,
 		   int parse_type)
 {
 	size_t i;
@@ -1159,20 +1293,23 @@ do_parse_parameter(const char *str, size_t str_length, unsigned int *value,
 }
 
 static void
-parse_parameter(const char *arg, unsigned int *status, int parse_type)
+parse_parameter(struct ct_cmd *cmd,
+		const char *arg, unsigned int *status, int parse_type)
 {
 	const char *comma;
 
 	while ((comma = strchr(arg, ',')) != NULL) {
 		if (comma == arg 
 		    || !do_parse_parameter(arg, comma-arg, status, parse_type))
-			exit_error(PARAMETER_PROBLEM,"Bad parameter `%s'", arg);
+			exit_error(PARAMETER_PROBLEM, cmd,
+					"Bad parameter `%s'", arg);
 		arg = comma+1;
 	}
 
 	if (strlen(arg) == 0
 	    || !do_parse_parameter(arg, strlen(arg), status, parse_type))
-		exit_error(PARAMETER_PROBLEM, "Bad parameter `%s'", arg);
+		exit_error(PARAMETER_PROBLEM, cmd,
+				"Bad parameter `%s'", arg);
 }
 
 static void
@@ -1189,64 +1326,71 @@ parse_u32_mask(const char *arg, struct u32_mask *m)
 }
 
 static int
-get_label(char *name)
+get_label(struct ct_cmd *cmd, char *name)
 {
 	int bit = nfct_labelmap_get_bit(labelmap, name);
 	if (bit < 0)
-		exit_error(PARAMETER_PROBLEM, "unknown label '%s'", name);
+		exit_error(PARAMETER_PROBLEM, cmd, "unknown label '%s'", name);
 	return bit;
 }
 
 static void
-set_label(struct nfct_bitmask *b, char *name)
+set_label(struct ct_cmd *cmd, struct nfct_bitmask *b, char *name)
 {
-	int bit = get_label(name);
+	int bit = get_label(cmd, name);
 	nfct_bitmask_set_bit(b, bit);
 }
 
 static unsigned int
-set_max_label(char *name, unsigned int current_max)
+set_max_label(struct ct_cmd *cmd, char *name, unsigned int current_max)
 {
-	int bit = get_label(name);
+	int bit = get_label(cmd, name);
 	if ((unsigned int) bit > current_max)
 		return (unsigned int) bit;
 	return current_max;
 }
 
 static unsigned int
-parse_label_get_max(char *arg)
+parse_label_get_max(struct ct_cmd *cmd, char *arg)
 {
 	unsigned int max = 0;
 	char *parse;
 
 	while ((parse = strchr(arg, ',')) != NULL) {
 		parse[0] = '\0';
-		max = set_max_label(arg, max);
+		max = set_max_label(cmd, arg, max);
 		arg = &parse[1];
 	}
 
-	max = set_max_label(arg, max);
+	max = set_max_label(cmd, arg, max);
 	return max;
 }
 
 static void
-parse_label(struct nfct_bitmask *b, char *arg)
+parse_label(struct ct_cmd *cmd, struct nfct_bitmask *b, char *arg)
 {
 	char * parse;
 	while ((parse = strchr(arg, ',')) != NULL) {
 		parse[0] = '\0';
-		set_label(b, arg);
+		set_label(cmd, b, arg);
 		arg = &parse[1];
 	}
-	set_label(b, arg);
+	set_label(cmd, b, arg);
 }
 
 static void
-add_command(unsigned int *cmd, const int newcmd)
+add_command(struct ct_cmd *cmd, const int newcmd)
 {
-	if (*cmd)
-		exit_error(PARAMETER_PROBLEM, "Invalid commands combination");
-	*cmd |= newcmd;
+	if (cmd->command)
+		exit_error(PARAMETER_PROBLEM, cmd, "Invalid commands combination");
+	cmd->command |= newcmd;
+	cmd->cmd_bit = bit2cmd(cmd->command);
+	if (cmd->cmd_bit == NUMBER_OF_CMD)
+		exit_error(PARAMETER_PROBLEM, cmd,
+				"Unknown command!");
+	if (cmd->file_name && !(cmd_load_file_allowed & cmd->command))
+		exit_error(PARAMETER_PROBLEM, cmd,
+				"Command not supported with the load-file mode");
 }
 
 static char *get_optional_arg(int argc, char *argv[])
@@ -1272,7 +1416,7 @@ enum {
 	CT_TABLE_UNCONFIRMED,
 };
 
-static unsigned int check_type(int argc, char *argv[])
+static unsigned int check_type(struct ct_cmd *cmd, int argc, char *argv[])
 {
 	const char *table = get_optional_arg(argc, argv);
 
@@ -1289,17 +1433,17 @@ static unsigned int check_type(int argc, char *argv[])
 	else if (strncmp("unconfirmed", table, strlen(table)) == 0)
 		return CT_TABLE_UNCONFIRMED;
 	else
-		exit_error(PARAMETER_PROBLEM, "unknown type `%s'", table);
+		exit_error(PARAMETER_PROBLEM, cmd, "unknown type `%s'", table);
 
 	return 0;
 }
 
-static void set_family(int *family, int new)
+static void set_family(struct ct_cmd *cmd, int new)
 {
-	if (*family == AF_UNSPEC)
-		*family = new;
-	else if (*family != new)
-		exit_error(PARAMETER_PROBLEM, "mismatched address family");
+	if (cmd->family == AF_UNSPEC)
+		cmd->family = new;
+	else if (cmd->family != new)
+		exit_error(PARAMETER_PROBLEM, cmd, "mismatched address family");
 }
 
 struct addr_parse {
@@ -1386,7 +1530,8 @@ valid_port(char *cursor)
 }
 
 static void
-split_address_and_port(const char *arg, char **address, char **port_str)
+split_address_and_port(struct ct_cmd *cmd,
+		const char *arg, char **address, char **port_str)
 {
 	char *cursor = strchr(arg, '[');
 
@@ -1396,10 +1541,10 @@ split_address_and_port(const char *arg, char **address, char **port_str)
 
 		cursor = strchr(start, ']');
 		if (start == cursor) {
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "No IPv6 address specified");
 		} else if (!cursor) {
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "No closing ']' around IPv6 address");
 		}
 		size_t len = cursor - start;
@@ -1423,11 +1568,11 @@ split_address_and_port(const char *arg, char **address, char **port_str)
 		/* Parse port entry */
 		cursor++;
 		if (strlen(cursor) == 0) {
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "No port specified after `:'");
 		}
 		if (!valid_port(cursor)) {
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "Invalid port `%s'", cursor);
 		}
 		*port_str = strdup(cursor);
@@ -1440,11 +1585,11 @@ split_address_and_port(const char *arg, char **address, char **port_str)
 }
 
 static void
-usage(char *prog)
+usage(void)
 {
 	fprintf(stdout, "Command line interface for the connection "
 			"tracking system. Version %s\n", VERSION);
-	fprintf(stdout, "Usage: %s [commands] [options]\n", prog);
+	fprintf(stdout, "Usage: %s [commands] [options]\n", programm);
 
 	fprintf(stdout, "\n%s", usage_commands);
 	fprintf(stdout, "\n%s", usage_tables);
@@ -1457,17 +1602,17 @@ usage(char *prog)
 static unsigned int output_mask;
 
 static int
-filter_label(const struct nf_conntrack *ct)
+filter_label(struct ct_cmd *cmd, const struct nf_conntrack *ct)
 {
-	if (tmpl.label == NULL)
+	if (cmd->label == NULL)
 		return 0;
 
 	const struct nfct_bitmask *ctb = nfct_get_attr(ct, ATTR_CONNLABELS);
 	if (ctb == NULL)
 		return 1;
 
-	for (unsigned int i = 0; i <= nfct_bitmask_maxbit(tmpl.label); i++) {
-		if (nfct_bitmask_test_bit(tmpl.label, i) &&
+	for (unsigned int i = 0; i <= nfct_bitmask_maxbit(cmd->label); i++) {
+		if (nfct_bitmask_test_bit(cmd->label, i) &&
 		    !nfct_bitmask_test_bit(ctb, i))
 				return 1;
 	}
@@ -1476,24 +1621,25 @@ filter_label(const struct nf_conntrack *ct)
 }
 
 static int
-filter_mark(const struct nf_conntrack *ct)
+filter_mark(struct ct_cmd *cmd, const struct nf_conntrack *ct)
 {
-	if ((options & CT_OPT_MARK) &&
-	     !mark_cmp(&tmpl.mark, ct))
+	if ((cmd->options & CT_OPT_MARK) &&
+	     !mark_cmp(&cmd->mark, ct))
 		return 1;
 	return 0;
 }
 
 static int 
-filter_nat(const struct nf_conntrack *obj, const struct nf_conntrack *ct)
+filter_nat(struct ct_cmd *cmd,
+		const struct nf_conntrack *obj, const struct nf_conntrack *ct)
 {
-	int check_srcnat = options & CT_OPT_SRC_NAT ? 1 : 0;
-	int check_dstnat = options & CT_OPT_DST_NAT ? 1 : 0;
+	int check_srcnat = cmd->options & CT_OPT_SRC_NAT ? 1 : 0;
+	int check_dstnat = cmd->options & CT_OPT_DST_NAT ? 1 : 0;
 	int has_srcnat = 0, has_dstnat = 0;
 	uint32_t ip;
 	uint16_t port;
 
-	if (options & CT_OPT_ANY_NAT)
+	if (cmd->options & CT_OPT_ANY_NAT)
 		check_srcnat = check_dstnat = 1;
 
 	if (check_srcnat) {
@@ -1556,13 +1702,13 @@ filter_nat(const struct nf_conntrack *obj, const struct nf_conntrack *ct)
 		     nfct_getobjopt(ct, NFCT_GOPT_IS_DPAT)))
 			has_dstnat = 1;
 	}
-	if (options & CT_OPT_ANY_NAT)
+	if (cmd->options & CT_OPT_ANY_NAT)
 		return !(has_srcnat || has_dstnat);
-	else if ((options & CT_OPT_SRC_NAT) && (options & CT_OPT_DST_NAT))
+	else if ((cmd->options & CT_OPT_SRC_NAT) && (cmd->options & CT_OPT_DST_NAT))
 		return !(has_srcnat && has_dstnat);
-	else if (options & CT_OPT_SRC_NAT)
+	else if (cmd->options & CT_OPT_SRC_NAT)
 		return !has_srcnat;
-	else if (options & CT_OPT_DST_NAT)
+	else if (cmd->options & CT_OPT_DST_NAT)
 		return !has_dstnat;
 
 	return 0;
@@ -1610,14 +1756,14 @@ nfct_filter_network_direction(const struct nf_conntrack *ct, enum ct_direction d
 }
 
 static int
-filter_network(const struct nf_conntrack *ct)
+filter_network(struct ct_cmd *cmd, const struct nf_conntrack *ct)
 {
-	if (options & CT_OPT_MASK_SRC) {
+	if (cmd->options & CT_OPT_MASK_SRC) {
 		if (nfct_filter_network_direction(ct, DIR_SRC))
 			return 1;
 	}
 
-	if (options & CT_OPT_MASK_DST) {
+	if (cmd->options & CT_OPT_MASK_DST) {
 		if (nfct_filter_network_direction(ct, DIR_DST))
 			return 1;
 	}
@@ -1625,22 +1771,22 @@ filter_network(const struct nf_conntrack *ct)
 }
 
 static int
-nfct_filter(struct nf_conntrack *obj, struct nf_conntrack *ct)
+nfct_filter(struct ct_cmd *cmd,
+		struct nf_conntrack *obj, struct nf_conntrack *ct)
 {
-	if (filter_nat(obj, ct) ||
-	    filter_mark(ct) ||
-	    filter_label(ct) ||
-	    filter_network(ct))
+	if (filter_nat(cmd, obj, ct) ||
+	    filter_mark(cmd, ct) ||
+	    filter_label(cmd, ct) ||
+	    filter_network(cmd, ct))
 		return 1;
 
-	if (options & CT_COMPARISON &&
+	if (cmd->options & CT_COMPARISON &&
 	    !nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK))
 		return 1;
 
 	return 0;
 }
 
-static int counter;
 static int dump_xml_header_done = 1;
 
 static void __attribute__((noreturn))
@@ -1651,8 +1797,7 @@ event_sighandler(int s)
 		fflush(stdout);
 	}
 
-	fprintf(stderr, "%s v%s (conntrack-tools): ", PROGNAME, VERSION);
-	fprintf(stderr, "%d flow events have been shown.\n", counter);
+	print_cmd_counters();
 	mnl_socket_close(sock.mnl);
 	exit(0);
 }
@@ -1665,9 +1810,8 @@ exp_event_sighandler(int s)
 		fflush(stdout);
 	}
 
-	fprintf(stderr, "%s v%s (conntrack-tools): ", PROGNAME, VERSION);
-	fprintf(stderr, "%d expectation events have been shown.\n", counter);
-	nfct_close(cth);
+	print_cmd_counters();
+	nfct_close(evh);
 	exit(0);
 }
 
@@ -1675,7 +1819,8 @@ static int event_cb(const struct nlmsghdr *nlh, void *data)
 {
 	struct nfgenmsg *nfh = mnl_nlmsg_get_payload(nlh);
 	unsigned int op_type = NFCT_O_DEFAULT;
-	struct nf_conntrack *obj = data;
+	struct ct_cmd *cmd = data;
+	struct nf_conntrack *obj = cmd->ct;
 	enum nf_conntrack_msg_type type;
 	unsigned int op_flags = 0;
 	struct nf_conntrack *ct;
@@ -1707,7 +1852,7 @@ static int event_cb(const struct nlmsghdr *nlh, void *data)
 
 	if ((filter_family != AF_UNSPEC &&
 	     filter_family != nfh->nfgen_family) ||
-	    nfct_filter(obj, ct))
+	    nfct_filter(cmd, obj, ct))
 		goto out;
 
 	if (output_mask & _O_SAVE) {
@@ -1750,7 +1895,7 @@ done:
 	printf("%s%s\n", buf, userspace ? " [USERSPACE]" : "");
 	fflush(stdout);
 
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 out:
 	nfct_destroy(ct);
 	return MNL_CB_OK;
@@ -1761,11 +1906,12 @@ static int dump_cb(enum nf_conntrack_msg_type type,
 		   void *data)
 {
 	char buf[1024];
-	struct nf_conntrack *obj = data;
+	struct ct_cmd *cmd = data;
+	struct nf_conntrack *obj = cmd->ct;
 	unsigned int op_type = NFCT_O_DEFAULT;
 	unsigned int op_flags = 0;
 
-	if (nfct_filter(obj, ct))
+	if (nfct_filter(cmd, obj, ct))
 		return NFCT_CB_CONTINUE;
 
 	if (output_mask & _O_SAVE) {
@@ -1792,7 +1938,7 @@ static int dump_cb(enum nf_conntrack_msg_type type,
 done:
 	printf("%s\n", buf);
 
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 
 	return NFCT_CB_CONTINUE;
 }
@@ -1803,16 +1949,17 @@ static int delete_cb(enum nf_conntrack_msg_type type,
 {
 	int res;
 	char buf[1024];
-	struct nf_conntrack *obj = data;
+	struct ct_cmd *cmd = data;
+	struct nf_conntrack *obj = cmd->ct;
 	unsigned int op_type = NFCT_O_DEFAULT;
 	unsigned int op_flags = 0;
 
-	if (nfct_filter(obj, ct))
+	if (nfct_filter(cmd, obj, ct))
 		return NFCT_CB_CONTINUE;
 
 	res = nfct_query(ith, NFCT_Q_DESTROY, ct);
 	if (res < 0)
-		exit_error(OTHER_PROBLEM,
+		exit_error(OTHER_PROBLEM, cmd,
 			   "Operation failed: %s",
 			   err2str(errno, CT_DELETE));
 
@@ -1832,7 +1979,7 @@ static int delete_cb(enum nf_conntrack_msg_type type,
 done:
 	printf("%s\n", buf);
 
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 
 	return NFCT_CB_CONTINUE;
 }
@@ -1864,20 +2011,22 @@ done:
 	return NFCT_CB_CONTINUE;
 }
 
-static void copy_mark(struct nf_conntrack *tmp,
+static void copy_mark(struct ct_cmd *cmd,
+		      struct nf_conntrack *tmp,
 		      const struct nf_conntrack *ct,
 		      const struct u32_mask *m)
 {
-	if (options & CT_OPT_MARK) {
+	if (cmd->options & CT_OPT_MARK) {
 		uint32_t mark = nfct_get_attr_u32(ct, ATTR_MARK);
 		mark = (mark & ~m->mask) ^ m->value;
 		nfct_set_attr_u32(tmp, ATTR_MARK, mark);
 	}
 }
 
-static void copy_status(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
+static void copy_status(struct ct_cmd *cmd,
+		      struct nf_conntrack *tmp, const struct nf_conntrack *ct)
 {
-	if (options & CT_OPT_STATUS) {
+	if (cmd->options & CT_OPT_STATUS) {
 		/* copy existing flags, we only allow setting them. */
 		uint32_t status = nfct_get_attr_u32(ct, ATTR_STATUS);
 		status |= nfct_get_attr_u32(tmp, ATTR_STATUS);
@@ -1889,25 +2038,26 @@ static struct nfct_bitmask *xnfct_bitmask_clone(const struct nfct_bitmask *a)
 {
 	struct nfct_bitmask *b = nfct_bitmask_clone(a);
 	if (!b)
-		exit_error(OTHER_PROBLEM, "out of memory");
+		exit_error(OTHER_PROBLEM, NULL, "out of memory");
 	return b;
 }
 
-static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
+static void copy_label(struct ct_cmd *cmd,
+		struct nf_conntrack *tmp, const struct nf_conntrack *ct)
 {
 	struct nfct_bitmask *ctb, *newmask;
 	unsigned int i;
 
-	if ((options & (CT_OPT_ADD_LABEL|CT_OPT_DEL_LABEL)) == 0)
+	if ((cmd->options & (CT_OPT_ADD_LABEL|CT_OPT_DEL_LABEL)) == 0)
 		return;
 
 	nfct_copy_attr(tmp, ct, ATTR_CONNLABELS);
 	ctb = (void *) nfct_get_attr(tmp, ATTR_CONNLABELS);
 
-	if (options & CT_OPT_ADD_LABEL) {
+	if (cmd->options & CT_OPT_ADD_LABEL) {
 		if (ctb == NULL) {
 			nfct_set_attr(tmp, ATTR_CONNLABELS,
-					xnfct_bitmask_clone(tmpl.label_modify));
+					xnfct_bitmask_clone(cmd->label_modify));
 			return;
 		}
 		/* If we send a bitmask shorter than the kernel sent to us, the bits we
@@ -1921,7 +2071,7 @@ static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
 		newmask = nfct_bitmask_new(nfct_bitmask_maxbit(ctb));
 
 		for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) {
-			if (nfct_bitmask_test_bit(tmpl.label_modify, i)) {
+			if (nfct_bitmask_test_bit(cmd->label_modify, i)) {
 				nfct_bitmask_set_bit(ctb, i);
 				nfct_bitmask_set_bit(newmask, i);
 			} else if (nfct_bitmask_test_bit(ctb, i)) {
@@ -1934,7 +2084,7 @@ static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
 		nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask);
 	} else if (ctb != NULL) {
 		/* CT_OPT_DEL_LABEL */
-		if (tmpl.label_modify == NULL) {
+		if (cmd->label_modify == NULL) {
 			newmask = nfct_bitmask_new(0);
 			if (newmask)
 				nfct_set_attr(tmp, ATTR_CONNLABELS, newmask);
@@ -1942,11 +2092,11 @@ static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
 		}
 
 		for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) {
-			if (nfct_bitmask_test_bit(tmpl.label_modify, i))
+			if (nfct_bitmask_test_bit(cmd->label_modify, i))
 				nfct_bitmask_unset_bit(ctb, i);
 		}
 
-		newmask = xnfct_bitmask_clone(tmpl.label_modify);
+		newmask = xnfct_bitmask_clone(cmd->label_modify);
 		nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask);
 	}
 }
@@ -1956,31 +2106,32 @@ static int update_cb(enum nf_conntrack_msg_type type,
 		     void *data)
 {
 	int res;
-	struct nf_conntrack *obj = data, *tmp;
+	struct ct_cmd *cmd = data;
+	struct nf_conntrack *obj = cmd->ct, *tmp;
 
-	if (filter_nat(obj, ct) ||
-	    filter_label(ct) ||
-	    filter_network(ct))
+	if (filter_nat(cmd, obj, ct) ||
+	    filter_label(cmd, ct) ||
+	    filter_network(cmd, ct))
 		return NFCT_CB_CONTINUE;
 
 	if (nfct_attr_is_set(obj, ATTR_ID) && nfct_attr_is_set(ct, ATTR_ID) &&
 	    nfct_get_attr_u32(obj, ATTR_ID) != nfct_get_attr_u32(ct, ATTR_ID))
 	    	return NFCT_CB_CONTINUE;
 
-	if (options & CT_OPT_TUPLE_ORIG && !nfct_cmp(obj, ct, NFCT_CMP_ORIG))
+	if (cmd->options & CT_OPT_TUPLE_ORIG && !nfct_cmp(obj, ct, NFCT_CMP_ORIG))
 		return NFCT_CB_CONTINUE;
-	if (options & CT_OPT_TUPLE_REPL && !nfct_cmp(obj, ct, NFCT_CMP_REPL))
+	if (cmd->options & CT_OPT_TUPLE_REPL && !nfct_cmp(obj, ct, NFCT_CMP_REPL))
 		return NFCT_CB_CONTINUE;
 
 	tmp = nfct_new();
 	if (tmp == NULL)
-		exit_error(OTHER_PROBLEM, "out of memory");
+		exit_error(OTHER_PROBLEM, cmd, "out of memory");
 
 	nfct_copy(tmp, ct, NFCT_CP_ORIG);
 	nfct_copy(tmp, obj, NFCT_CP_META);
-	copy_mark(tmp, ct, &tmpl.mark);
-	copy_status(tmp, ct);
-	copy_label(tmp, ct);
+	copy_mark(cmd, tmp, ct, &cmd->mark);
+	copy_status(cmd, tmp, ct);
+	copy_label(cmd, tmp, ct);
 
 	/* do not send NFCT_Q_UPDATE if ct appears unchanged */
 	if (nfct_cmp(tmp, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) {
@@ -2003,14 +2154,14 @@ static int update_cb(enum nf_conntrack_msg_type type,
 			nfct_callback_unregister(ith);
 			return NFCT_CB_CONTINUE;
 		}
-		exit_error(OTHER_PROBLEM,
+		exit_error(OTHER_PROBLEM, cmd,
 			   "Operation failed: %s",
 			   err2str(errno, CT_UPDATE));
 	}
 	nfct_destroy(tmp);
 	nfct_callback_unregister(ith);
 
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 
 	return NFCT_CB_CONTINUE;
 }
@@ -2020,6 +2171,7 @@ static int dump_exp_cb(enum nf_conntrack_msg_type type,
 		      void *data)
 {
 	char buf[1024];
+	struct ct_cmd *cmd = data;
 	unsigned int op_type = NFCT_O_DEFAULT;
 	unsigned int op_flags = 0;
 
@@ -2042,7 +2194,7 @@ static int dump_exp_cb(enum nf_conntrack_msg_type type,
 
 	nfexp_snprintf(buf,sizeof(buf), exp, NFCT_T_UNKNOWN, op_type, op_flags);
 	printf("%s\n", buf);
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 
 	return NFCT_CB_CONTINUE;
 }
@@ -2051,6 +2203,7 @@ static int event_exp_cb(enum nf_conntrack_msg_type type,
 			struct nf_expect *exp, void *data)
 {
 	char buf[1024];
+	struct ct_cmd *cmd = data;
 	unsigned int op_type = NFCT_O_DEFAULT;
 	unsigned int op_flags = 0;
 
@@ -2074,7 +2227,7 @@ static int event_exp_cb(enum nf_conntrack_msg_type type,
 	nfexp_snprintf(buf,sizeof(buf), exp, type, op_type, op_flags);
 	printf("%s\n", buf);
 	fflush(stdout);
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 
 	return NFCT_CB_CONTINUE;
 }
@@ -2083,7 +2236,9 @@ static int count_exp_cb(enum nf_conntrack_msg_type type,
 			struct nf_expect *exp,
 			void *data)
 {
-	counter++;
+	struct ct_cmd *cmd = data;
+
+	cmd_counters[cmd->cmd_bit]++;
 	return NFCT_CB_CONTINUE;
 }
 
@@ -2196,10 +2351,12 @@ nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type,
 static void nfct_mnl_socket_close(void)
 {
 	mnl_socket_close(sock.mnl);
+	memset(&sock, 0, sizeof(sock));
 }
 
 static int
-nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb, uint8_t family)
+nfct_mnl_dump(struct ct_cmd *cmd,
+		uint16_t subsys, uint16_t type, mnl_cb_t cb, uint8_t family)
 {
 	char buf[MNL_SOCKET_BUFFER_SIZE];
 	struct nlmsghdr *nlh;
@@ -2214,7 +2371,7 @@ nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb, uint8_t family)
 	res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf));
 	while (res > 0) {
 		res = mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid,
-					 cb, NULL);
+					 cb, cmd);
 		if (res <= MNL_CB_STOP)
 			break;
 
@@ -2382,6 +2539,7 @@ static int nfct_global_stats_cb(const struct nlmsghdr *nlh, void *data)
 
 static int mnl_nfct_dump_cb(const struct nlmsghdr *nlh, void *data)
 {
+	struct ct_cmd *cmd = data;
 	struct nf_conntrack *ct;
 	char buf[4096];
 
@@ -2396,7 +2554,7 @@ static int mnl_nfct_dump_cb(const struct nlmsghdr *nlh, void *data)
 
 	nfct_destroy(ct);
 
-	counter++;
+	cmd_counters[cmd->cmd_bit]++;
 
 	return MNL_CB_OK;
 }
@@ -2413,19 +2571,19 @@ static void labelmap_init(void)
 }
 
 static void
-nfct_network_attr_prepare(const int family, enum ct_direction dir)
+nfct_network_attr_prepare(struct ct_cmd *cmd, enum ct_direction dir)
 {
 	const union ct_address *address, *netmask;
 	enum nf_conntrack_attr attr;
 	int i;
 	struct ct_network *net = &dir2network[dir];
 
-	attr = famdir2attr[family == AF_INET6][dir];
+	attr = famdir2attr[cmd->family == AF_INET6][dir];
 
-	address = nfct_get_attr(tmpl.ct, attr);
-	netmask = nfct_get_attr(tmpl.mask, attr);
+	address = nfct_get_attr(cmd->ct, attr);
+	netmask = nfct_get_attr(cmd->mask, attr);
 
-	switch(family) {
+	switch(cmd->family) {
 	case AF_INET:
 		net->network.v4 = address->v4 & netmask->v4;
 		break;
@@ -2438,27 +2596,34 @@ nfct_network_attr_prepare(const int family, enum ct_direction dir)
 	memcpy(&net->netmask, netmask, sizeof(union ct_address));
 
 	/* avoid exact source matching */
-	nfct_attr_unset(tmpl.ct, attr);
+	nfct_attr_unset(cmd->ct, attr);
+}
+
+static void
+nfct_filter_reset(void)
+{
+	filter_family = 0;
+	memset(dir2network, 0, sizeof(dir2network));
 }
 
 static void
-nfct_filter_init(const int family)
+nfct_filter_init(struct ct_cmd *cmd)
 {
-	filter_family = family;
-	if (options & CT_OPT_MASK_SRC) {
-		assert(family != AF_UNSPEC);
-		if (!(options & CT_OPT_ORIG_SRC))
-			exit_error(PARAMETER_PROBLEM,
+	filter_family = cmd->family;
+	if (cmd->options & CT_OPT_MASK_SRC) {
+		assert(cmd->family != AF_UNSPEC);
+		if (!(cmd->options & CT_OPT_ORIG_SRC))
+			exit_error(PARAMETER_PROBLEM, cmd,
 			           "Can't use --mask-src without --src");
-		nfct_network_attr_prepare(family, DIR_SRC);
+		nfct_network_attr_prepare(cmd, DIR_SRC);
 	}
 
-	if (options & CT_OPT_MASK_DST) {
-		assert(family != AF_UNSPEC);
-		if (!(options & CT_OPT_ORIG_DST))
-			exit_error(PARAMETER_PROBLEM,
+	if (cmd->options & CT_OPT_MASK_DST) {
+		assert(cmd->family != AF_UNSPEC);
+		if (!(cmd->options & CT_OPT_ORIG_DST))
+			exit_error(PARAMETER_PROBLEM, cmd,
 			           "Can't use --mask-dst without --dst");
-		nfct_network_attr_prepare(family, DIR_DST);
+		nfct_network_attr_prepare(cmd, DIR_DST);
 	}
 }
 
@@ -2525,39 +2690,41 @@ nfct_set_addr_only(const int opt, struct nf_conntrack *ct, union ct_address *ad,
 }
 
 static void
-nfct_set_addr_opt(const int opt, struct nf_conntrack *ct, union ct_address *ad,
+nfct_set_addr_opt(struct ct_cmd *cmd,
+		  const int opt, struct nf_conntrack *ct, union ct_address *ad,
 		  const int l3protonum)
 {
-	options |= opt2type[opt];
+	cmd->options |= opt2type[opt];
 	nfct_set_addr_only(opt, ct, ad, l3protonum);
 	nfct_set_attr_u8(ct, opt2attr[opt], l3protonum);
 }
 
 static void
-nfct_parse_addr_from_opt(const int opt, const char *arg,
+nfct_parse_addr_from_opt(struct ct_cmd *cmd,
+			 const int opt, const char *arg,
 			 struct nf_conntrack *ct,
 			 struct nf_conntrack *ctmask,
-			 union ct_address *ad, int *family)
+			 union ct_address *ad)
 {
 	int mask, maskopt;
 
 	const int l3protonum = parse_addr(arg, ad, &mask);
 	if (l3protonum == AF_UNSPEC) {
-		exit_error(PARAMETER_PROBLEM,
+		exit_error(PARAMETER_PROBLEM, cmd,
 			   "Invalid IP address `%s'", arg);
 	}
-	set_family(family, l3protonum);
+	set_family(cmd, l3protonum);
 	maskopt = opt2maskopt[opt];
 	if (mask != -1 && !maskopt) {
-		exit_error(PARAMETER_PROBLEM,
+		exit_error(PARAMETER_PROBLEM, cmd,
 		           "CIDR notation unavailable"
 		           " for `--%s'", get_long_opt(opt));
 	} else if (mask == -2) {
-		exit_error(PARAMETER_PROBLEM,
+		exit_error(PARAMETER_PROBLEM, cmd,
 		           "Invalid netmask");
 	}
 
-	nfct_set_addr_opt(opt, ct, ad, l3protonum);
+	nfct_set_addr_opt(cmd, opt, ct, ad, l3protonum);
 
 	/* bail if we don't have a netmask to set*/
 	if (mask == -1 || !maskopt || ctmask == NULL)
@@ -2576,7 +2743,7 @@ nfct_parse_addr_from_opt(const int opt, const char *arg,
 		break;
 	}
 
-	nfct_set_addr_opt(maskopt, ctmask, ad, l3protonum);
+	nfct_set_addr_opt(cmd, maskopt, ctmask, ad, l3protonum);
 }
 
 static void
@@ -2594,50 +2761,80 @@ nfct_set_nat_details(const int opt, struct nf_conntrack *ct,
 		nfct_set_attr_u16(ct, ATTR_DNAT_PORT,
 				  ntohs((uint16_t)atoi(port_str)));
 	}
+}
+
+static int line_to_argcv(char *line, char ***pargv,
+		size_t *pargv_size)
+{
+	char *arg;
+	int argc;
+	char **argv = *pargv;
+	size_t argv_size = *pargv_size;
+	int argc_max = argv_size / sizeof(*argv);
+
+#define _ARG_ADD(_arg) do { \
+		if (argc == argc_max) { \
+			argc_max += 20; \
+			argv_size = argc_max * sizeof (argv[0]); \
+			argv = realloc(argv, argv_size); \
+			if (!argv) \
+				exit_error(OTHER_PROBLEM, NULL, "out of memory"); \
+		} \
+		argv[argc] = _arg; \
+		++argc; \
+} while (0)
+
+#define _ARG_SEP " \t\n\r"
+	for (argc = 0, arg = strtok (line, _ARG_SEP);
+			arg;
+			arg = strtok (NULL, _ARG_SEP)) {
+		/*
+		 * getopt_long expects argv[0] to be the programm name,
+		 * and would always skip it so we need to include it here
+		 */
+		if (!argc && programm)
+			_ARG_ADD(programm);
+		_ARG_ADD(arg);
+	}
 
+#undef _ARG_ADD
+#undef _ARG_SEP
+
+	*pargv = argv;
+	*pargv_size = argv_size;
+
+	return argc;
 }
 
-int main(int argc, char *argv[])
+static struct ct_cmd* ct_cmd_create(int argc, char *argv[],
+		const char *file_name, unsigned int line_number)
 {
-	int c, cmd;
-	unsigned int type = 0, event_mask = 0, l4flags = 0, status = 0;
+	int c;
+	unsigned int l4flags = 0, status = 0;
 	int res = 0, partial;
-	size_t socketbuffersize = 0;
-	int family = AF_UNSPEC;
-	int protonum = 0;
 	union ct_address ad;
-	unsigned int command = 0;
+	struct ct_cmd *cmd;
 
-	/* we release these objects in the exit_error() path. */
-	if (!alloc_tmpl_objects())
-		exit_error(OTHER_PROBLEM, "out of memory");
+	optind = 0;
 
-	register_tcp();
-	register_udp();
-	register_udplite();
-	register_sctp();
-	register_dccp();
-	register_icmp();
-	register_icmpv6();
-	register_gre();
-	register_unknown();
+	cmd = ct_cmd_alloc();
 
-	/* disable explicit missing arguments error output from getopt_long */
-	opterr = 0;
+	cmd->line_number = line_number;
+	cmd->file_name = file_name;
 
 	while ((c = getopt_long(argc, argv, getopt_str, opts, NULL)) != -1) {
 	switch(c) {
 		/* commands */
 		case 'L':
-			type = check_type(argc, argv);
+			cmd->type = check_type(cmd, argc, argv);
 			/* Special case: dumping dying and unconfirmed list
 			 * are handled like normal conntrack dumps.
 			 */
-			if (type == CT_TABLE_DYING ||
-			    type == CT_TABLE_UNCONFIRMED)
-				add_command(&command, cmd2type[c][0]);
+			if (cmd->type == CT_TABLE_DYING ||
+				cmd->type == CT_TABLE_UNCONFIRMED)
+				add_command(cmd, cmd2type[c][0]);
 			else
-				add_command(&command, cmd2type[c][type]);
+				add_command(cmd, cmd2type[c][cmd->type]);
 			break;
 		case 'I':
 		case 'D':
@@ -2648,26 +2845,26 @@ int main(int argc, char *argv[])
 		case 'h':
 		case 'C':
 		case 'S':
-			type = check_type(argc, argv);
-			if (type == CT_TABLE_DYING ||
-			    type == CT_TABLE_UNCONFIRMED) {
-				exit_error(PARAMETER_PROBLEM,
+			cmd->type = check_type(cmd, argc, argv);
+			if (cmd->type == CT_TABLE_DYING ||
+				cmd->type == CT_TABLE_UNCONFIRMED) {
+				exit_error(PARAMETER_PROBLEM, cmd,
 					   "Can't do that command with "
 					   "tables `dying' and `unconfirmed'");
 			}
-			add_command(&command, cmd2type[c][type]);
+			add_command(cmd, cmd2type[c][cmd->type]);
 			break;
 		case 'U':
-			type = check_type(argc, argv);
-			if (type == CT_TABLE_DYING ||
-			    type == CT_TABLE_UNCONFIRMED) {
-				exit_error(PARAMETER_PROBLEM,
+			cmd->type = check_type(cmd, argc, argv);
+			if (cmd->type == CT_TABLE_DYING ||
+				cmd->type == CT_TABLE_UNCONFIRMED) {
+				exit_error(PARAMETER_PROBLEM, cmd,
 					   "Can't do that command with "
 					   "tables `dying' and `unconfirmed'");
-			} else if (type == CT_TABLE_CONNTRACK)
-				add_command(&command, CT_UPDATE);
+			} else if (cmd->type == CT_TABLE_CONNTRACK)
+				add_command(cmd, CT_UPDATE);
 			else
-				exit_error(PARAMETER_PROBLEM,
+				exit_error(PARAMETER_PROBLEM, cmd,
 					   "Can't update expectations");
 			break;
 		/* options */
@@ -2675,123 +2872,123 @@ int main(int argc, char *argv[])
 		case 'd':
 		case 'r':
 		case 'q':
-			nfct_parse_addr_from_opt(c, optarg, tmpl.ct,
-						 tmpl.mask, &ad, &family);
+			nfct_parse_addr_from_opt(cmd, c, optarg, cmd->ct,
+						 cmd->mask, &ad);
 			break;
 		case '[':
 		case ']':
-			nfct_parse_addr_from_opt(c, optarg, tmpl.exptuple,
-						 tmpl.mask, &ad, &family);
+			nfct_parse_addr_from_opt(cmd, c, optarg, cmd->exptuple,
+					cmd->mask, &ad);
 			break;
 		case '{':
 		case '}':
-			nfct_parse_addr_from_opt(c, optarg, tmpl.mask,
-						 NULL, &ad, &family);
+			nfct_parse_addr_from_opt(cmd, c, optarg, cmd->mask,
+						 NULL, &ad);
 			break;
 		case 'p':
-			options |= CT_OPT_PROTO;
-			h = findproto(optarg, &protonum);
+			cmd->options |= CT_OPT_PROTO;
+			h = findproto(optarg, &cmd->protonum);
 			if (!h)
-				exit_error(PARAMETER_PROBLEM,
+				exit_error(PARAMETER_PROBLEM, cmd,
 					   "`%s' unsupported protocol",
 					   optarg);
 
 			opts = merge_options(opts, h->opts, &h->option_offset);
 			if (opts == NULL)
-				exit_error(OTHER_PROBLEM, "out of memory");
+				exit_error(OTHER_PROBLEM, cmd, "out of memory");
 
-			nfct_set_attr_u8(tmpl.ct, ATTR_L4PROTO, protonum);
+			nfct_set_attr_u8(cmd->ct, ATTR_L4PROTO, cmd->protonum);
 			break;
 		case 't':
-			options |= CT_OPT_TIMEOUT;
-			nfct_set_attr_u32(tmpl.ct, ATTR_TIMEOUT, atol(optarg));
-			nfexp_set_attr_u32(tmpl.exp,
+			cmd->options |= CT_OPT_TIMEOUT;
+			nfct_set_attr_u32(cmd->ct, ATTR_TIMEOUT, atol(optarg));
+			nfexp_set_attr_u32(cmd->exp,
 					   ATTR_EXP_TIMEOUT, atol(optarg));
 			break;
 		case 'u':
-			options |= CT_OPT_STATUS;
-			parse_parameter(optarg, &status, PARSE_STATUS);
-			nfct_set_attr_u32(tmpl.ct, ATTR_STATUS, status);
+			cmd->options |= CT_OPT_STATUS;
+			parse_parameter(cmd, optarg, &status, PARSE_STATUS);
+			nfct_set_attr_u32(cmd->ct, ATTR_STATUS, status);
 			break;
 		case 'e':
-			options |= CT_OPT_EVENT_MASK;
-			parse_parameter(optarg, &event_mask, PARSE_EVENT);
+			cmd->options |= CT_OPT_EVENT_MASK;
+			parse_parameter(cmd, optarg, &cmd->event_mask, PARSE_EVENT);
 			break;
 		case 'o':
-			options |= CT_OPT_OUTPUT;
-			parse_parameter(optarg, &output_mask, PARSE_OUTPUT);
+			cmd->options |= CT_OPT_OUTPUT;
+			parse_parameter(cmd, optarg, &output_mask, PARSE_OUTPUT);
 			if (output_mask & _O_CL)
 				labelmap_init();
 			if ((output_mask & _O_SAVE) &&
 			    (output_mask & (_O_EXT |_O_TMS |_O_ID | _O_KTMS | _O_CL | _O_XML)))
-				exit_error(OTHER_PROBLEM,
+				exit_error(OTHER_PROBLEM, cmd,
 					   "cannot combine save output with any other output type, use -o save only");
 			break;
 		case 'z':
-			options |= CT_OPT_ZERO;
+			cmd->options |= CT_OPT_ZERO;
 			break;
 		case 'n':
 		case 'g':
 		case 'j':
-			options |= opt2type[c];
+			cmd->options |= opt2type[c];
 			char *optional_arg = get_optional_arg(argc, argv);
 
 			if (optional_arg) {
 				char *port_str = NULL;
 				char *nat_address = NULL;
 
-				split_address_and_port(optional_arg,
+				split_address_and_port(cmd, optional_arg,
 						       &nat_address,
 						       &port_str);
-				nfct_parse_addr_from_opt(c, nat_address,
-							 tmpl.ct, NULL,
-							 &ad, &family);
+				nfct_parse_addr_from_opt(cmd, c, nat_address,
+							 cmd->ct, NULL,
+							 &ad);
 				if (c == 'j') {
 					/* Set details on both src and dst
 					 * with any-nat
 					 */
-					nfct_set_nat_details('g', tmpl.ct, &ad,
-							     port_str, family);
-					nfct_set_nat_details('n', tmpl.ct, &ad,
-							     port_str, family);
+					nfct_set_nat_details('g', cmd->ct, &ad,
+							     port_str, cmd->family);
+					nfct_set_nat_details('n', cmd->ct, &ad,
+							     port_str, cmd->family);
 				} else {
-					nfct_set_nat_details(c, tmpl.ct, &ad,
-							     port_str, family);
+					nfct_set_nat_details(c, cmd->ct, &ad,
+							     port_str, cmd->family);
 				}
 			}
 			break;
 		case 'w':
 		case '(':
 		case ')':
-			options |= opt2type[c];
-			nfct_set_attr_u16(tmpl.ct,
+			cmd->options |= opt2type[c];
+			nfct_set_attr_u16(cmd->ct,
 					  opt2attr[c],
 					  strtoul(optarg, NULL, 0));
 			break;
 		case 'i':
 		case 'c':
-			options |= opt2type[c];
-			nfct_set_attr_u32(tmpl.ct,
+			cmd->options |= opt2type[c];
+			nfct_set_attr_u32(cmd->ct,
 					  opt2attr[c],
 					  strtoul(optarg, NULL, 0));
 			break;
 		case 'm':
-			options |= opt2type[c];
-			parse_u32_mask(optarg, &tmpl.mark);
-			tmpl.filter_mark_kernel.val = tmpl.mark.value;
-			tmpl.filter_mark_kernel.mask = tmpl.mark.mask;
-			tmpl.filter_mark_kernel_set = true;
+			cmd->options |= opt2type[c];
+			parse_u32_mask(optarg, &cmd->mark);
+			cmd->filter_mark_kernel.val = cmd->mark.value;
+			cmd->filter_mark_kernel.mask = cmd->mark.mask;
+			cmd->filter_mark_kernel_set = true;
 			break;
 		case 'l':
 		case '<':
 		case '>':
-			options |= opt2type[c];
+			cmd->options |= opt2type[c];
 
 			labelmap_init();
 
-			if ((options & (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL)) ==
+			if ((cmd->options & (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL)) ==
 			    (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL))
-				exit_error(OTHER_PROBLEM, "cannot use --label-add and "
+				exit_error(OTHER_PROBLEM, cmd, "cannot use --label-add and "
 							"--label-del at the same time");
 
 			if (c == '>') { /* DELETE */
@@ -2802,18 +2999,18 @@ int main(int argc, char *argv[])
 			}
 
 			char *optarg2 = strdup(optarg);
-			unsigned int max = parse_label_get_max(optarg);
+			unsigned int max = parse_label_get_max(cmd, optarg);
 			struct nfct_bitmask * b = nfct_bitmask_new(max);
 			if (!b)
-				exit_error(OTHER_PROBLEM, "out of memory");
+				exit_error(OTHER_PROBLEM, cmd, "out of memory");
 
-			parse_label(b, optarg2);
+			parse_label(cmd, b, optarg2);
 
 			/* join "-l foo -l bar" into single bitmask object */
 			if (c == 'l') {
-				merge_bitmasks(&tmpl.label, b);
+				merge_bitmasks(&cmd->label, b);
 			} else {
-				merge_bitmasks(&tmpl.label_modify, b);
+				merge_bitmasks(&cmd->label_modify, b);
 			}
 
 			free(optarg2);
@@ -2823,41 +3020,44 @@ int main(int argc, char *argv[])
 					"deprecated option.\n", c);
 			break;
 		case 'f':
-			options |= CT_OPT_FAMILY;
+			cmd->options |= CT_OPT_FAMILY;
 			if (strncmp(optarg, "ipv4", strlen("ipv4")) == 0)
-				set_family(&family, AF_INET);
+				set_family(cmd, AF_INET);
 			else if (strncmp(optarg, "ipv6", strlen("ipv6")) == 0)
-				set_family(&family, AF_INET6);
+				set_family(cmd, AF_INET6);
 			else
-				exit_error(PARAMETER_PROBLEM,
+				exit_error(PARAMETER_PROBLEM, cmd,
 					   "`%s' unsupported protocol",
 					   optarg);
 			break;
 		case 'b':
-			socketbuffersize = atol(optarg);
-			options |= CT_OPT_BUFFERSIZE;
+			cmd->socketbuffersize = atol(optarg);
+			cmd->options |= CT_OPT_BUFFERSIZE;
 			break;
 		case ':':
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "option `%s' requires an "
 				   "argument", argv[optind-1]);
 		case '?':
-			exit_error(PARAMETER_PROBLEM,
+			exit_error(PARAMETER_PROBLEM, cmd,
 				   "unknown option `%s'", argv[optind-1]);
 			break;
 		default:
 			if (h && h->parse_opts 
-			    &&!h->parse_opts(c - h->option_offset, tmpl.ct,
-			    		     tmpl.exptuple, tmpl.mask,
-					     &l4flags))
-				exit_error(PARAMETER_PROBLEM, "parse error");
+			    &&!h->parse_opts(cmd, c - h->option_offset, &l4flags))
+				exit_error(PARAMETER_PROBLEM, cmd, "parse error");
 			break;
 		}
 	}
 
+	if (!cmd->command)
+		/* invalid args */
+		exit_error(PARAMETER_PROBLEM, cmd,
+				   "invalid cmd line syntax");
+
 	/* default family only for the following commands */
-	if (family == AF_UNSPEC) {
-		switch (command) {
+	if (cmd->family == AF_UNSPEC) {
+		switch (cmd->command) {
 		case CT_LIST:
 		case CT_UPDATE:
 		case CT_DELETE:
@@ -2866,97 +3066,104 @@ int main(int argc, char *argv[])
 		case CT_EVENT:
 			break;
 		default:
-			family = AF_INET;
+			cmd->family = AF_INET;
 			break;
 		}
 	}
 
-
 	/* we cannot check this combination with generic_opt_check. */
-	if (options & CT_OPT_ANY_NAT &&
-	   ((options & CT_OPT_SRC_NAT) || (options & CT_OPT_DST_NAT))) {
-		exit_error(PARAMETER_PROBLEM, "cannot specify `--src-nat' or "
+	if (cmd->options & CT_OPT_ANY_NAT &&
+	   ((cmd->options & CT_OPT_SRC_NAT) || (cmd->options & CT_OPT_DST_NAT))) {
+		exit_error(PARAMETER_PROBLEM, cmd, "cannot specify `--src-nat' or "
 					      "`--dst-nat' with `--any-nat'");
 	}
-	cmd = bit2cmd(command);
-	res = generic_opt_check(options, NUMBER_OF_OPT,
-				commands_v_options[cmd], optflags,
+
+	res = generic_opt_check(cmd, cmd->options, NUMBER_OF_OPT,
+				commands_v_options[cmd->cmd_bit], optflags,
 				addr_valid_flags, ADDR_VALID_FLAGS_MAX,
 				&partial);
 	if (!res) {
 		switch(partial) {
 		case -1:
 		case 0:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd, "you have to specify "
 						      "`--src' and `--dst'");
 			break;
 		case 1:
-			exit_error(PARAMETER_PROBLEM, "you have to specify "
+			exit_error(PARAMETER_PROBLEM, cmd, "you have to specify "
 						      "`--reply-src' and "
 						      "`--reply-dst'");
 			break;
 		}
 	}
-	if (!(command & CT_HELP) && h && h->final_check)
-		h->final_check(l4flags, cmd, tmpl.ct);
+	if (!(cmd->command & CT_HELP) && h && h->final_check)
+		h->final_check(cmd, l4flags);
+
+	free_options();
+
+	return cmd;
+}
 
-	switch(command) {
+void ct_cmd_apply(struct ct_cmd *cmd)
+{
+	int res = 0;
+
+	assert(cmd->cmd_bit < sizeof(cmd_counters) / sizeof(cmd_counters[0]));
+	cmd_executed |= cmd->command;
+
+	switch(cmd->command) {
 	struct nfct_filter_dump *filter_dump;
 
 	case CT_LIST:
-		if (type == CT_TABLE_DYING) {
+		if (cmd->type == CT_TABLE_DYING) {
 			if (nfct_mnl_socket_open(0) < 0)
-				exit_error(OTHER_PROBLEM, "Can't open handler");
+				exit_error(OTHER_PROBLEM, cmd, "Can't open handler");
 
-			res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK,
+			res = nfct_mnl_dump(cmd, NFNL_SUBSYS_CTNETLINK,
 					    IPCTNL_MSG_CT_GET_DYING,
-					    mnl_nfct_dump_cb, family);
+					    mnl_nfct_dump_cb, cmd->family);
 
 			nfct_mnl_socket_close();
 			break;
-		} else if (type == CT_TABLE_UNCONFIRMED) {
+		} else if (cmd->type == CT_TABLE_UNCONFIRMED) {
 			if (nfct_mnl_socket_open(0) < 0)
-				exit_error(OTHER_PROBLEM, "Can't open handler");
+				exit_error(OTHER_PROBLEM, cmd, "Can't open handler");
 
-			res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK,
+			res = nfct_mnl_dump(cmd, NFNL_SUBSYS_CTNETLINK,
 					    IPCTNL_MSG_CT_GET_UNCONFIRMED,
-					    mnl_nfct_dump_cb, family);
+					    mnl_nfct_dump_cb, cmd->family);
 
 			nfct_mnl_socket_close();
 			break;
 		}
 
-		cth = nfct_open(CONNTRACK, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-
-		if (options & CT_COMPARISON && 
-		    options & CT_OPT_ZERO)
-			exit_error(PARAMETER_PROBLEM, "Can't use -z with "
+		if (cmd->options & CT_COMPARISON &&
+			cmd->options & CT_OPT_ZERO)
+			exit_error(PARAMETER_PROBLEM, cmd, "Can't use -z with "
 						      "filtering parameters");
 
-		nfct_filter_init(family);
+		nfct_filter_init(cmd);
 
-		nfct_callback_register(cth, NFCT_T_ALL, dump_cb, tmpl.ct);
+		nfct_callback_register(glob_cth(), NFCT_T_ALL, dump_cb, cmd);
 
 		filter_dump = nfct_filter_dump_create();
 		if (filter_dump == NULL)
-			exit_error(OTHER_PROBLEM, "OOM");
+			exit_error(OTHER_PROBLEM, cmd, "OOM");
 
-		if (tmpl.filter_mark_kernel_set) {
+		if (cmd->filter_mark_kernel_set) {
 			nfct_filter_dump_set_attr(filter_dump,
 						  NFCT_FILTER_DUMP_MARK,
-						  &tmpl.filter_mark_kernel);
+						  &cmd->filter_mark_kernel);
 		}
 		nfct_filter_dump_set_attr_u8(filter_dump,
 					     NFCT_FILTER_DUMP_L3NUM,
-					     family);
+						 cmd->family);
 
-		if (options & CT_OPT_ZERO)
-			res = nfct_query(cth, NFCT_Q_DUMP_FILTER_RESET,
+		if (cmd->options & CT_OPT_ZERO)
+			res = nfct_query(glob_cth(), NFCT_Q_DUMP_FILTER_RESET,
 					filter_dump);
 		else
-			res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump);
+			res = nfct_query(glob_cth(), NFCT_Q_DUMP_FILTER, filter_dump);
 
 		nfct_filter_dump_destroy(filter_dump);
 
@@ -2964,18 +3171,11 @@ int main(int argc, char *argv[])
 			printf("</conntrack>\n");
 			fflush(stdout);
 		}
-
-		nfct_close(cth);
 		break;
 
 	case EXP_LIST:
-		cth = nfct_open(EXPECT, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-
-		nfexp_callback_register(cth, NFCT_T_ALL, dump_exp_cb, NULL);
-		res = nfexp_query(cth, NFCT_Q_DUMP, &family);
-		nfct_close(cth);
+		nfexp_callback_register(glob_exh(), NFCT_T_ALL, dump_exp_cb, cmd);
+		res = nfexp_query(glob_exh(), NFCT_Q_DUMP, &cmd->family);
 
 		if (dump_xml_header_done == 0) {
 			printf("</expect>\n");
@@ -2984,150 +3184,108 @@ int main(int argc, char *argv[])
 		break;
 
 	case CT_CREATE:
-		if ((options & CT_OPT_ORIG) && !(options & CT_OPT_REPL))
-		    	nfct_setobjopt(tmpl.ct, NFCT_SOPT_SETUP_REPLY);
-		else if (!(options & CT_OPT_ORIG) && (options & CT_OPT_REPL))
-			nfct_setobjopt(tmpl.ct, NFCT_SOPT_SETUP_ORIGINAL);
-
-		if (options & CT_OPT_MARK)
-			nfct_set_attr_u32(tmpl.ct, ATTR_MARK, tmpl.mark.value);
+		if ((cmd->options & CT_OPT_ORIG) && !(cmd->options & CT_OPT_REPL))
+			nfct_setobjopt(cmd->ct, NFCT_SOPT_SETUP_REPLY);
+		else if (!(cmd->options & CT_OPT_ORIG) && (cmd->options & CT_OPT_REPL))
+			nfct_setobjopt(cmd->ct, NFCT_SOPT_SETUP_ORIGINAL);
 
-		if (options & CT_OPT_ADD_LABEL)
-			nfct_set_attr(tmpl.ct, ATTR_CONNLABELS,
-					xnfct_bitmask_clone(tmpl.label_modify));
+		if (cmd->options & CT_OPT_MARK)
+			nfct_set_attr_u32(cmd->ct, ATTR_MARK, cmd->mark.value);
 
-		cth = nfct_open(CONNTRACK, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
+		if (cmd->options & CT_OPT_ADD_LABEL)
+			nfct_set_attr(cmd->ct, ATTR_CONNLABELS,
+					xnfct_bitmask_clone(cmd->label_modify));
 
-		res = nfct_query(cth, NFCT_Q_CREATE, tmpl.ct);
+		res = nfct_query(glob_cth(), NFCT_Q_CREATE, cmd->ct);
 		if (res != -1)
-			counter++;
-		nfct_close(cth);
+			cmd_counters[cmd->cmd_bit]++;
 		break;
 
 	case EXP_CREATE:
-		nfexp_set_attr(tmpl.exp, ATTR_EXP_MASTER, tmpl.ct);
-		nfexp_set_attr(tmpl.exp, ATTR_EXP_EXPECTED, tmpl.exptuple);
-		nfexp_set_attr(tmpl.exp, ATTR_EXP_MASK, tmpl.mask);
+		nfexp_set_attr(cmd->exp, ATTR_EXP_MASTER, cmd->ct);
+		nfexp_set_attr(cmd->exp, ATTR_EXP_EXPECTED, cmd->exptuple);
+		nfexp_set_attr(cmd->exp, ATTR_EXP_MASK, cmd->mask);
 
-		cth = nfct_open(EXPECT, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-
-		res = nfexp_query(cth, NFCT_Q_CREATE, tmpl.exp);
-		nfct_close(cth);
+		res = nfexp_query(glob_exh(), NFCT_Q_CREATE, cmd->exp);
 		break;
 
 	case CT_UPDATE:
-		cth = nfct_open(CONNTRACK, 0);
-		/* internal handler for delete_cb, otherwise we hit EILSEQ */
-		ith = nfct_open(CONNTRACK, 0);
-		if (!cth || !ith)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
+		/* make sure ith used in update_cb is initialized */
+		glob_ith();
 
-		nfct_filter_init(family);
+		nfct_filter_init(cmd);
 
-		nfct_callback_register(cth, NFCT_T_ALL, update_cb, tmpl.ct);
+		nfct_callback_register(glob_cth(), NFCT_T_ALL, update_cb, cmd);
 
-		res = nfct_query(cth, NFCT_Q_DUMP, &family);
-		nfct_close(ith);
-		nfct_close(cth);
+		res = nfct_query(glob_cth(), NFCT_Q_DUMP, &cmd->family);
 		break;
 		
 	case CT_DELETE:
-		cth = nfct_open(CONNTRACK, 0);
-		ith = nfct_open(CONNTRACK, 0);
-		if (!cth || !ith)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
+		/* make sure ith used in delete_cb is initialized */
+		glob_ith();
 
-		nfct_filter_init(family);
+		nfct_filter_init(cmd);
 
-		nfct_callback_register(cth, NFCT_T_ALL, delete_cb, tmpl.ct);
+		nfct_callback_register(glob_cth(), NFCT_T_ALL, delete_cb, cmd);
 
 		filter_dump = nfct_filter_dump_create();
 		if (filter_dump == NULL)
-			exit_error(OTHER_PROBLEM, "OOM");
+			exit_error(OTHER_PROBLEM, cmd, "OOM");
 
-		if (tmpl.filter_mark_kernel_set) {
+		if (cmd->filter_mark_kernel_set) {
 			nfct_filter_dump_set_attr(filter_dump,
 						  NFCT_FILTER_DUMP_MARK,
-						  &tmpl.filter_mark_kernel);
+						  &cmd->filter_mark_kernel);
 		}
 		nfct_filter_dump_set_attr_u8(filter_dump,
 					     NFCT_FILTER_DUMP_L3NUM,
-					     family);
+						 cmd->family);
 
-		res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump);
+		res = nfct_query(glob_cth(), NFCT_Q_DUMP_FILTER, filter_dump);
 
 		nfct_filter_dump_destroy(filter_dump);
 
-		nfct_close(ith);
-		nfct_close(cth);
 		break;
 
 	case EXP_DELETE:
-		nfexp_set_attr(tmpl.exp, ATTR_EXP_EXPECTED, tmpl.ct);
-
-		cth = nfct_open(EXPECT, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
+		nfexp_set_attr(cmd->exp, ATTR_EXP_EXPECTED, cmd->ct);
 
-		res = nfexp_query(cth, NFCT_Q_DESTROY, tmpl.exp);
-		nfct_close(cth);
+		res = nfexp_query(glob_cth(), NFCT_Q_DESTROY, cmd->exp);
 		break;
 
 	case CT_GET:
-		cth = nfct_open(CONNTRACK, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-
-		nfct_callback_register(cth, NFCT_T_ALL, dump_cb, tmpl.ct);
-		res = nfct_query(cth, NFCT_Q_GET, tmpl.ct);
-		nfct_close(cth);
+		nfct_callback_register(glob_cth(), NFCT_T_ALL, dump_cb, cmd->ct);
+		res = nfct_query(cth, NFCT_Q_GET, cmd->ct);
 		break;
 
 	case EXP_GET:
-		nfexp_set_attr(tmpl.exp, ATTR_EXP_MASTER, tmpl.ct);
+		nfexp_set_attr(cmd->exp, ATTR_EXP_MASTER, cmd->ct);
 
-		cth = nfct_open(EXPECT, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-
-		nfexp_callback_register(cth, NFCT_T_ALL, dump_exp_cb, NULL);
-		res = nfexp_query(cth, NFCT_Q_GET, tmpl.exp);
-		nfct_close(cth);
+		nfexp_callback_register(glob_exh(), NFCT_T_ALL, dump_exp_cb, cmd);
+		res = nfexp_query(exh, NFCT_Q_GET, cmd->exp);
 		break;
 
 	case CT_FLUSH:
-		cth = nfct_open(CONNTRACK, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-		res = nfct_query(cth, NFCT_Q_FLUSH_FILTER, &family);
-		nfct_close(cth);
+		res = nfct_query(glob_cth(), NFCT_Q_FLUSH_FILTER, &cmd->family);
 		fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION);
 		fprintf(stderr,"connection tracking table has been emptied.\n");
 		break;
 
 	case EXP_FLUSH:
-		cth = nfct_open(EXPECT, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-		res = nfexp_query(cth, NFCT_Q_FLUSH, &family);
-		nfct_close(cth);
+		res = nfexp_query(glob_exh(), NFCT_Q_FLUSH, &cmd->family);
 		fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION);
 		fprintf(stderr,"expectation table has been emptied.\n");
 		break;
 
 	case CT_EVENT:
-		if (options & CT_OPT_EVENT_MASK) {
+		if (cmd->options & CT_OPT_EVENT_MASK) {
 			unsigned int nl_events = 0;
 
-			if (event_mask & CT_EVENT_F_NEW)
+			if (cmd->event_mask & CT_EVENT_F_NEW)
 				nl_events |= NF_NETLINK_CONNTRACK_NEW;
-			if (event_mask & CT_EVENT_F_UPD)
+			if (cmd->event_mask & CT_EVENT_F_UPD)
 				nl_events |= NF_NETLINK_CONNTRACK_UPDATE;
-			if (event_mask & CT_EVENT_F_DEL)
+			if (cmd->event_mask & CT_EVENT_F_DEL)
 				nl_events |= NF_NETLINK_CONNTRACK_DESTROY;
 
 			res = nfct_mnl_socket_open(nl_events);
@@ -3138,29 +3296,29 @@ int main(int argc, char *argv[])
 		}
 
 		if (res < 0)
-			exit_error(OTHER_PROBLEM, "Can't open netlink socket");
+			exit_error(OTHER_PROBLEM, cmd, "Can't open netlink socket");
 
-		if (options & CT_OPT_BUFFERSIZE) {
-			socklen_t socklen = sizeof(socketbuffersize);
+		if (cmd->options & CT_OPT_BUFFERSIZE) {
+			socklen_t socklen = sizeof(cmd->socketbuffersize);
 
 			res = setsockopt(mnl_socket_get_fd(sock.mnl),
 					 SOL_SOCKET, SO_RCVBUFFORCE,
-					 &socketbuffersize,
-					 sizeof(socketbuffersize));
+					 &cmd->socketbuffersize,
+					 sizeof(cmd->socketbuffersize));
 			if (res < 0) {
 				setsockopt(mnl_socket_get_fd(sock.mnl),
 					   SOL_SOCKET, SO_RCVBUF,
-					   &socketbuffersize,
-					   socketbuffersize);
+					   &cmd->socketbuffersize,
+					   cmd->socketbuffersize);
 			}
 			getsockopt(mnl_socket_get_fd(sock.mnl), SOL_SOCKET,
-				   SO_RCVBUF, &socketbuffersize, &socklen);
+				   SO_RCVBUF, &cmd->socketbuffersize, &socklen);
 			fprintf(stderr, "NOTICE: Netlink socket buffer size "
 					"has been set to %zu bytes.\n",
-					socketbuffersize);
+					cmd->socketbuffersize);
 		}
 
-		nfct_filter_init(family);
+		nfct_filter_init(cmd);
 
 		signal(SIGINT, event_sighandler);
 		signal(SIGTERM, event_sighandler);
@@ -3180,42 +3338,43 @@ int main(int argc, char *argv[])
 						"conntrack(8) manpage.\n");
 					continue;
 				}
-				exit_error(OTHER_PROBLEM,
+				exit_error(OTHER_PROBLEM, cmd,
 					   "failed to received netlink event: %s",
 					   strerror(errno));
 				break;
 			}
-			res = mnl_cb_run(buf, res, 0, 0, event_cb, tmpl.ct);
+			res = mnl_cb_run(buf, res, 0, 0, event_cb, cmd);
 		}
 		mnl_socket_close(sock.mnl);
 		break;
 
 	case EXP_EVENT:
-		if (options & CT_OPT_EVENT_MASK) {
+		if (cmd->options & CT_OPT_EVENT_MASK) {
 			unsigned int nl_events = 0;
 
-			if (event_mask & CT_EVENT_F_NEW)
+			if (cmd->event_mask & CT_EVENT_F_NEW)
 				nl_events |= NF_NETLINK_CONNTRACK_EXP_NEW;
-			if (event_mask & CT_EVENT_F_UPD)
+			if (cmd->event_mask & CT_EVENT_F_UPD)
 				nl_events |= NF_NETLINK_CONNTRACK_EXP_UPDATE;
-			if (event_mask & CT_EVENT_F_DEL)
+			if (cmd->event_mask & CT_EVENT_F_DEL)
 				nl_events |= NF_NETLINK_CONNTRACK_EXP_DESTROY;
 
-			cth = nfct_open(CONNTRACK, nl_events);
+			evh = nfct_open(CONNTRACK, nl_events);
 		} else {
-			cth = nfct_open(EXPECT,
+			evh = nfct_open(EXPECT,
 					NF_NETLINK_CONNTRACK_EXP_NEW |
 					NF_NETLINK_CONNTRACK_EXP_UPDATE |
 					NF_NETLINK_CONNTRACK_EXP_DESTROY);
 		}
 
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
+		if (!evh)
+			exit_error(OTHER_PROBLEM, cmd, "Can't open handler");
 		signal(SIGINT, exp_event_sighandler);
 		signal(SIGTERM, exp_event_sighandler);
-		nfexp_callback_register(cth, NFCT_T_ALL, event_exp_cb, NULL);
-		res = nfexp_catch(cth);
-		nfct_close(cth);
+		nfexp_callback_register(evh, NFCT_T_ALL, event_exp_cb, cmd);
+		res = nfexp_catch(evh);
+		nfct_close(evh);
+		evh = NULL;
 		break;
 	case CT_COUNT:
 		/* If we fail with netlink, fall back to /proc to ensure
@@ -3241,11 +3400,11 @@ try_proc_count:
 		int count;
 		fd = fopen(NF_CONNTRACK_COUNT_PROC, "r");
 		if (fd == NULL) {
-			exit_error(OTHER_PROBLEM, "Can't open %s",
+			exit_error(OTHER_PROBLEM, cmd, "Can't open %s",
 				   NF_CONNTRACK_COUNT_PROC);
 		}
 		if (fscanf(fd, "%d", &count) != 1) {
-			exit_error(OTHER_PROBLEM, "Can't read %s",
+			exit_error(OTHER_PROBLEM, cmd, "Can't read %s",
 				   NF_CONNTRACK_COUNT_PROC);
 		}
 		fclose(fd);
@@ -3253,14 +3412,9 @@ try_proc_count:
 		break;
 	}
 	case EXP_COUNT:
-		cth = nfct_open(EXPECT, 0);
-		if (!cth)
-			exit_error(OTHER_PROBLEM, "Can't open handler");
-
-		nfexp_callback_register(cth, NFCT_T_ALL, count_exp_cb, NULL);
-		res = nfexp_query(cth, NFCT_Q_DUMP, &family);
-		nfct_close(cth);
-		printf("%d\n", counter);
+		nfexp_callback_register(glob_exh(), NFCT_T_ALL, count_exp_cb, cmd);
+		res = nfexp_query(exh, NFCT_Q_DUMP, &cmd->family);
+		printf("%d\n", cmd_counters[cmd->cmd_bit]);
 		break;
 	case CT_STATS:
 		/* If we fail with netlink, fall back to /proc to ensure
@@ -3269,7 +3423,7 @@ try_proc_count:
 		if (nfct_mnl_socket_open(0) < 0)
 			goto try_proc;
 
-		res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK,
+		res = nfct_mnl_dump(cmd, NFNL_SUBSYS_CTNETLINK,
 				    IPCTNL_MSG_CT_GET_STATS_CPU,
 				    nfct_stats_cb, AF_UNSPEC);
 
@@ -3288,7 +3442,7 @@ try_proc_count:
 		if (nfct_mnl_socket_open(0) < 0)
 			goto try_proc;
 
-		res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK_EXP,
+		res = nfct_mnl_dump(cmd, NFNL_SUBSYS_CTNETLINK_EXP,
 				    IPCTNL_MSG_EXP_GET_STATS_CPU,
 				    nfexp_stats_cb, AF_UNSPEC);
 
@@ -3299,36 +3453,150 @@ try_proc_count:
 			break;
 try_proc:
 		if (display_proc_conntrack_stats() < 0)
-			exit_error(OTHER_PROBLEM, "Can't open /proc interface");
+			exit_error(OTHER_PROBLEM, cmd, "Can't open /proc interface");
 		break;
 	case CT_VERSION:
 		printf("%s v%s (conntrack-tools)\n", PROGNAME, VERSION);
 		break;
 	case CT_HELP:
-		usage(argv[0]);
-		if (options & CT_OPT_PROTO)
-			extension_help(h, protonum);
+		usage();
+		if (cmd->options & CT_OPT_PROTO)
+			extension_help(h, cmd->protonum);
 		break;
 	default:
-		usage(argv[0]);
+		usage();
 		break;
 	}
 
 	if (res < 0)
-		exit_error(OTHER_PROBLEM, "Operation failed: %s",
-			   err2str(errno, command));
+		exit_error(OTHER_PROBLEM, cmd, "Operation failed: %s",
+			   err2str(errno, cmd->command));
 
-	free_tmpl_objects();
-	free_options();
-	if (labelmap)
-		nfct_labelmap_destroy(labelmap);
+	/* reset globals */
+	nfct_filter_reset();
+}
 
-	if (command && exit_msg[cmd][0]) {
-		fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION);
-		fprintf(stderr, exit_msg[cmd], counter);
-		if (counter == 0 && !(command & (CT_LIST | EXP_LIST)))
-			return EXIT_FAILURE;
+static void ct_cmd_list_gen_command(struct ct_cmd_list *list,
+		int argc, char *argv[],
+		const char *file_name, unsigned int line_number)
+{
+	ct_cmd_list_add(list, ct_cmd_create(argc, argv,
+			file_name, line_number));
+}
+
+static void ct_cmd_list_process_file(struct ct_cmd_list *list,
+		const char *file_name)
+{
+	int res, argc;
+	char **argv;
+	int orig_optind;
+	FILE *opts_file = NULL;
+	unsigned int line_number;
+	char **argv_buf = NULL, *getline_buf = NULL;
+	size_t argv_buf_size = 0, getline_buf_size = 0;
+
+	/*
+	 * we need to preserve the optind, because
+	 * nested argument processing will change it
+	 * while we need to be sure the caller is not affected
+	 */
+	orig_optind = optind;
+
+	if (!strcmp(file_name, "-"))
+		file_name = "/dev/stdin";
+
+	opts_file = fopen(file_name,"r");
+	if (!opts_file)
+		exit_error(PARAMETER_PROBLEM, NULL,
+					   "Failed to open file %s for reading", file_name);
+
+	for (line_number = 1;
+		(res = getline(&getline_buf, &getline_buf_size, opts_file)) >= 0;
+		line_number++) {
+		if (!res)
+			continue;
+		argc = line_to_argcv(getline_buf, &argv_buf, &argv_buf_size);
+		if (!argc)
+			continue;
+		argv = argv_buf;
+
+		ct_cmd_list_gen_command(list, argc, argv,
+				file_name, line_number);
+	}
+
+	fclose(opts_file);
+
+	free(argv_buf);
+	free(getline_buf);
+
+	optind = orig_optind;
+}
+
+static bool try_load_file_mode(struct ct_cmd_list *list, int argc, char *argv[])
+{
+	int c, load_file_mode = 0;
+
+	optind = 0;
+
+	while ((c = getopt_long(argc, argv,
+			load_file_getopt_str, load_file_opts, NULL)) != -1) {
+		switch(c) {
+		case 'R':
+			load_file_mode = 1;
+			ct_cmd_list_process_file(list, optarg);
+			break;
+		case ':':
+			exit_error(PARAMETER_PROBLEM, NULL,
+					   "option `%s' requires an "
+					   "argument", argv[optind-1]);
+		case '?':
+			if (!load_file_mode)
+				return false;
+			exit_error(PARAMETER_PROBLEM, NULL,
+			   "unknown option `%s' for load-file arg number %d",
+			   argv[optind-1], optind-1);
+		default:
+			exit_error(PARAMETER_PROBLEM, NULL,
+			   "unexpected behavior `%d' %d", c, optind);
+		}
 	}
+	return load_file_mode;
+}
+
+static void ct_cmd_list_gen_commands(struct ct_cmd_list *list,
+		int argc, char *argv[])
+{
+	if (try_load_file_mode(list, argc, argv))
+		return;
+	ct_cmd_list_gen_command(list, argc, argv, NULL, 0);
+}
+
+int main(int argc, char *argv[])
+{
+	struct ct_cmd_list cmds;
+
+	register_tcp();
+	register_udp();
+	register_udplite();
+	register_sctp();
+	register_dccp();
+	register_icmp();
+	register_icmpv6();
+	register_gre();
+	register_unknown();
+
+	ct_cmd_list_init(&cmds);
+
+	/* disable explicit missing arguments error output from getopt_long */
+	opterr = 0;
+
+	programm = argv[0];
+
+	ct_cmd_list_gen_commands(&cmds, argc, argv);
+
+	ct_cmd_list_apply(&cmds);
+
+	glob_cleanup();
 
-	return EXIT_SUCCESS;
+	return print_cmd_counters();
 }
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 2/4] conntrack.8: man update for --load-file support
  2020-11-09 18:17 [PATCH v2 0/4] conntrack: accept commands from file + tests Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 1/4] conntrack: accept commands from file Mikhail Sennikovsky
@ 2020-11-09 18:17 ` Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 3/4] tests: saving and loading ct entries, save format Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 4/4] tests: conntrack -L/-D ip family filtering Mikhail Sennikovsky
  3 siblings, 0 replies; 5+ messages in thread
From: Mikhail Sennikovsky @ 2020-11-09 18:17 UTC (permalink / raw)
  To: netfilter-devel, pablo; +Cc: Mikhail Sennikovsky

Signed-off-by: Mikhail Sennikovsky <mikhail.sennikovskii@cloud.ionos.com>
---
 conntrack.8 | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/conntrack.8 b/conntrack.8
index 898daae..a14cca6 100644
--- a/conntrack.8
+++ b/conntrack.8
@@ -23,6 +23,8 @@ conntrack \- command line interface for netfilter connection tracking
 .BR "conntrack -C [table]"
 .br
 .BR "conntrack -S "
+.br
+.BR "conntrack -R file"
 .SH DESCRIPTION
 The \fBconntrack\fP utilty provides a full featured userspace interface to the
 Netfilter connection tracking system that is intended to replace the old
@@ -102,6 +104,9 @@ Show the table counter.
 .TP
 .BI "-S, --stats "
 Show the in-kernel connection tracking system statistics.
+.TP
+.BI "-R, --load-file "
+Load entries from a given file. To read from stdin, "\-" should be specified.
 
 .SS PARAMETERS
 .TP
@@ -394,6 +399,9 @@ Delete all flow whose source address is 1.2.3.4
 .TP
 .B conntrack \-U \-s 1.2.3.4 \-m 1
 Set connmark to 1 of all the flows whose source address is 1.2.3.4
+.TP
+.B conntrack -L -w 11 -o save | sed "s/-w 11/-w 12/g" | conntrack --load-file -
+Copy all entries from ct zone 11 to ct zone 12
 
 .SH BUGS
 Please, report them to netfilter-devel@vger.kernel.org or file a bug in
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 3/4] tests: saving and loading ct entries, save format
  2020-11-09 18:17 [PATCH v2 0/4] conntrack: accept commands from file + tests Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 1/4] conntrack: accept commands from file Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 2/4] conntrack.8: man update for --load-file support Mikhail Sennikovsky
@ 2020-11-09 18:17 ` Mikhail Sennikovsky
  2020-11-09 18:17 ` [PATCH v2 4/4] tests: conntrack -L/-D ip family filtering Mikhail Sennikovsky
  3 siblings, 0 replies; 5+ messages in thread
From: Mikhail Sennikovsky @ 2020-11-09 18:17 UTC (permalink / raw)
  To: netfilter-devel, pablo; +Cc: Mikhail Sennikovsky

Signed-off-by: Mikhail Sennikovsky <mikhail.sennikovskii@cloud.ionos.com>
---
 tests/conntrack/test-conntrack.c    | 84 ++++++++++++++++++++++++-----
 tests/conntrack/testsuite/08stdin   | 80 +++++++++++++++++++++++++++
 tests/conntrack/testsuite/09dumpopt | 77 ++++++++++++++++++++++++++
 3 files changed, 229 insertions(+), 12 deletions(-)
 create mode 100644 tests/conntrack/testsuite/08stdin
 create mode 100644 tests/conntrack/testsuite/09dumpopt

diff --git a/tests/conntrack/test-conntrack.c b/tests/conntrack/test-conntrack.c
index 76ab051..372e025 100644
--- a/tests/conntrack/test-conntrack.c
+++ b/tests/conntrack/test-conntrack.c
@@ -28,6 +28,23 @@ int main()
 	struct dirent *dent;
 	char file[1024];
 	int i,n;
+	char cmd_buf[1024 * 8];
+	int i_cmd_buf = 0;
+	char cmd, cur_cmd = 0;
+	char *cmd_opt;
+
+#define cmd_strappend(_s) do { \
+	char * pos = stpncpy(cmd_buf + i_cmd_buf, _s, sizeof(cmd_buf) - i_cmd_buf); \
+	i_cmd_buf = pos - cmd_buf; \
+	if (i_cmd_buf == sizeof(cmd_buf)) { \
+		printf("buffer full!\n"); \
+		exit(EXIT_FAILURE); \
+	} \
+} while (0)
+
+#define cmd_reset() do { \
+		i_cmd_buf = 0; \
+} while (0)
 
 	n = scandir("testsuite", &dents, NULL, alphasort);
 
@@ -48,9 +65,7 @@ int main()
 		}
 
 		while (fgets(buf, sizeof(buf), fp)) {
-			char tmp[1024] = CT_PROG, *res;
-			tmp[strlen(CT_PROG)] = ' ';
-
+			char *res;
 			line++;
 
 			if (buf[0] == '#' || buf[0] == ' ')
@@ -63,27 +78,72 @@ int main()
 				exit(EXIT_FAILURE);
 			}
 			*res = '\0';
-			res+=2;
+			res++;
+			for (; *res == ' ' || *res == '\t'; res++);
+			cmd = res[0];
+			cmd_opt = &res[1];
+			for (; *cmd_opt == ' ' || *cmd_opt == '\t'; cmd_opt++);
+			res = strchr(cmd_opt, '\n');
+			if (res)
+				*res = '\0';
+
+			if (cur_cmd && cmd != cur_cmd) {
+				/* complete current multi-line command */
+				switch (cur_cmd) {
+				case '\n':
+					cmd_strappend("\" | ");
+					break;
+				default:
+					printf("Internal Error: unexpected multiline command %c",
+							cur_cmd);
+					exit(EXIT_FAILURE);
+					break;
+				}
+
+				cur_cmd = 0;
+			}
+
+			switch (cmd) {
+			case '\n':
+				if (!cur_cmd) {
+					cmd_strappend("echo \"");
+					cur_cmd = cmd;
+				} else
+					cmd_strappend("\n");
+				cmd_strappend(buf);
+				continue;
+			default:
+				cmd_strappend(CT_PROG);
+				cmd_strappend(" ");
+				cmd_strappend(buf);
+				if (cmd == '|') {
+					cmd_strappend(" | ");
+					if (cmd_opt[0]) {
+						cmd_strappend("sed \"");
+						cmd_strappend(cmd_opt);
+						cmd_strappend("\" | ");
+					}
+					continue;
+				}
+				cmd_reset();
+				break;
+			}
 
-			strcpy(tmp + strlen(CT_PROG) + 1, buf);
-			printf("(%d) Executing: %s\n", line, tmp);
+			printf("(%d) Executing: %s\n", line, cmd_buf);
 
 			fflush(stdout);
-			ret = system(tmp);
+			ret = system(cmd_buf);
 
 			if (WIFEXITED(ret) &&
 			    WEXITSTATUS(ret) == EXIT_SUCCESS) {
-			    	if (res[0] == 'O' &&
-				    res[1] == 'K')
+				if (cmd == 'O')
 					ok++;
 				else {
 					bad++;
 					printf("^----- BAD\n");
 				}
 			} else {
-				if (res[0] == 'B' &&
-				    res[1] == 'A' &&
-				    res[2] == 'D')
+				if (cmd == 'B')
 					ok++;
 				else {
 					bad++;
diff --git a/tests/conntrack/testsuite/08stdin b/tests/conntrack/testsuite/08stdin
new file mode 100644
index 0000000..38f3b8b
--- /dev/null
+++ b/tests/conntrack/testsuite/08stdin
@@ -0,0 +1,80 @@
+# create
+# create a conntrack
+-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+# create from reply
+-I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ;
+# create a v6 conntrack
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+# creae icmp ping request entry
+-I -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ;
+-R - ; OK
+# create again
+-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+# make sure create again with stdio mode fails as well
+-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+-R - ; BAD
+-I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ;
+-R - ; BAD
+# empty lines are ignored
+;
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+-R - ; BAD
+# spaces or tabs are ignored as well
+  ;
+		;
+-I -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ;
+-R - ; BAD
+# delete
+-D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ;
+# empty lines should be just ignored
+;
+;
+# delete reverse
+-D -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ;
+# empty lines with spaces or tabs should be ignored as well
+ ;
+	;
+		;
+  ;
+	    ;
+	    	    	;
+# delete v6 conntrack
+-D -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ;
+# delete icmp ping request entry
+-D -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ;
+;
+;
+-R - ; OK
+# create again - should succeed now
+-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
+-I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; OK
+-I -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
+-I -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# delete again (for cleanup)
+-D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ;
+-D -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ;
+-D -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ;
+-D -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ;
+;
+-R - ; OK
+# delete no entries - should return err
+-D -w 123 ; BAD
+# delete no entries via stdin - should return err as well
+-D -w 123 ;
+-R - ; BAD
+# delete no entries in parallel with adding entries via stdin - should succeed
+# -D and -I should work in parallel
+-D -w 123 ;
+-I -w 123 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+-R - ; OK
+# now deleting entries should return success
+-D -w 123 ;
+-R - ; OK
+# should fail again
+-D -w 123 ;
+-R - ; BAD
+# validate it via standard command line way
+-D -w 123 ; BAD
\ No newline at end of file
diff --git a/tests/conntrack/testsuite/09dumpopt b/tests/conntrack/testsuite/09dumpopt
new file mode 100644
index 0000000..0d5d9d4
--- /dev/null
+++ b/tests/conntrack/testsuite/09dumpopt
@@ -0,0 +1,77 @@
+# test opts output for -L
+# create
+# create a conntrack
+-I -w 10 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+# create from reply
+-I -w 10 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ;
+# create a v6 conntrack
+-I -w 10 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+# creae icmp ping request entry
+-I -w 10 -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ;
+-R - ; OK
+# copy ipv4 bits to zone 11
+-L -w 10 -o save -f ipv4 ; |s/-w 10/-w 11/g
+-R - ; OK
+# copy ipv6 bits to zone 11
+-L -w 10 -o save -f ipv6 ; |s/-w 10/-w 11/g
+-R - ; OK
+# create again in zone 11
+-I -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -w 11 -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+# delete new entries
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
+# delete reverse
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; OK
+# delete v6 conntrack
+-D -w 11-s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
+# delete icmp ping request entry
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# delete old entries
+-D -w 10 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
+# delete reverse
+-D -w 10 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; OK
+# delete v6 conntrack
+-D -w 10-s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
+# delete icmp ping request entry
+-D -w 10 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+#
+# now test opts output for -D
+# create entries again
+# create a conntrack
+-I -w 10 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+# create from reply
+-I -w 10 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ;
+# create a v6 conntrack
+-I -w 10 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ;
+# creae icmp ping request entry
+-I -w 10 -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ;
+-R - ; OK
+# move ipv4 bits to zone 11
+-D -w 10 -o save -f ipv4 ; |s/-w 10/-w 11/g; s/-D /-I /g
+-R - ; OK
+# move ipv6 bits to zone 11
+-D -w 10 -o save -f ipv6 ; |s/-w 10/-w 11/g; s/-D /-I /g
+-R - ; OK
+# create again in zone 11
+-I -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD
+-I -w 11 -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+# delete new entries
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
+# delete reverse
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; OK
+# delete v6 conntrack
+-D -w 11-s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ; OK
+# delete icmp ping request entry
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# delete old entries
+-D -w 10 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; BAD
+# delete reverse
+-D -w 10 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; BAD
+# delete v6 conntrack
+-D -w 10-s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ; BAD
+# delete icmp ping request entry
+-D -w 10 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
\ No newline at end of file
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 4/4] tests: conntrack -L/-D ip family filtering
  2020-11-09 18:17 [PATCH v2 0/4] conntrack: accept commands from file + tests Mikhail Sennikovsky
                   ` (2 preceding siblings ...)
  2020-11-09 18:17 ` [PATCH v2 3/4] tests: saving and loading ct entries, save format Mikhail Sennikovsky
@ 2020-11-09 18:17 ` Mikhail Sennikovsky
  3 siblings, 0 replies; 5+ messages in thread
From: Mikhail Sennikovsky @ 2020-11-09 18:17 UTC (permalink / raw)
  To: netfilter-devel, pablo; +Cc: Mikhail Sennikovsky

Tests to cover conntrack -L and conntrack -D with and w/o
family (-f) specfied.

conntrack -L and contnrack -D shold list/delete
both IPv4 and IPv6 entries if no family is specified,
and should ony display the corresponding entries if
the family is given.

Signed-off-by: Mikhail Sennikovsky <mikhail.sennikovskii@cloud.ionos.com>
---
 tests/conntrack/testsuite/09dumpopt | 72 ++++++++++++++++++++++++++++-
 1 file changed, 71 insertions(+), 1 deletion(-)

diff --git a/tests/conntrack/testsuite/09dumpopt b/tests/conntrack/testsuite/09dumpopt
index 0d5d9d4..447590b 100644
--- a/tests/conntrack/testsuite/09dumpopt
+++ b/tests/conntrack/testsuite/09dumpopt
@@ -74,4 +74,74 @@
 # delete v6 conntrack
 -D -w 10-s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 ; BAD
 # delete icmp ping request entry
--D -w 10 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
\ No newline at end of file
+-D -w 10 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+#
+# Additional tests to check that family attribute is treated properly
+# for -L and -D commands
+# namely:
+# - if family (-f) is given - only entries of the given family are dumped/deleted
+# - if no family is given - entries of both ipv4 and ipv6 families are dumped/deleted
+# First create some ipv4 and ipv6 entries
+-I -w 10 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
+-I -w 10 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; OK
+-I -w 10 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK
+-I -w 10 -t 29 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# dump all entries to zone 11
+-L -w 10 -o save; |s/-w 10/-w 11/g
+-R - ; OK
+# ensure that both ipv4 and ipv6 entries get copied (delete for each of them should succeed)
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY ; OK
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY ; OK
+-D -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# dump only ipv4 entries to zone 11
+-L -w 10 -o save -f ipv4; |s/-w 10/-w 11/g
+-R - ; OK
+# ensure that only ipv4 entries get copied (delete only for ipv4 entries should succeed)
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# dump only ipv6 entries to zone 11
+-L -w 10 -o save -f ipv6; |s/-w 10/-w 11/g
+-R - ; OK
+# ensure that only ipv6 entries get copied (delete only for ipv6 entries should succeed)
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+# now test deleting w/ and /o family specified
+# for simplicity do it by re-creating entries in zone 11
+# by copying ezisting entries from zone 10 into it
+# re-create entries in ct zone 11
+-L -w 10 -o save; |s/-w 10/-w 11/g
+-R - ; OK
+# delete all entries in zone 11
+-D -w 11 ; OK
+# both ipv4 and ipv6 should be deleted
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+# re-create entries in ct zone 11
+-L -w 10 -o save; |s/-w 10/-w 11/g
+-R - ; OK
+# delete only ipv4 entries in zone 11
+-D -w 11 -f ipv4 ; OK
+# ipv6 should remain
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; BAD
+ # re-create entries in ct zone 11
+-L -w 10 -o save; |s/-w 10/-w 11/g
+-R - ; OK
+# delete only ipv6 entries in zone 11
+-D -w 11 -f ipv6 ; OK
+# ipv4 should remain
+-D -w 11 -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY; OK
+-D -w 11 -s 2001:DB8::1.1.1.1 -d 2001:DB8::2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY; BAD
+-D -w 11 -u SEEN_REPLY -s 1.1.1.1 -d 2.2.2.2 -r 2.2.2.2 -q 1.1.1.1 -p icmp --icmp-type 8 --icmp-code 0 --icmp-id 1226 ; OK
+# clean up after yourself
+-D -w 10 ; OK
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-11-09 18:18 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-09 18:17 [PATCH v2 0/4] conntrack: accept commands from file + tests Mikhail Sennikovsky
2020-11-09 18:17 ` [PATCH v2 1/4] conntrack: accept commands from file Mikhail Sennikovsky
2020-11-09 18:17 ` [PATCH v2 2/4] conntrack.8: man update for --load-file support Mikhail Sennikovsky
2020-11-09 18:17 ` [PATCH v2 3/4] tests: saving and loading ct entries, save format Mikhail Sennikovsky
2020-11-09 18:17 ` [PATCH v2 4/4] tests: conntrack -L/-D ip family filtering Mikhail Sennikovsky

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).