diff mbox series

[v4,2/5] conntrack: accept commands from file

Message ID 20210406100947.57579-3-mikhail.sennikovskii@ionos.com
State Accepted
Delegated to: Pablo Neira
Headers show
Series conntrack: save output format | expand

Commit Message

Mikhail Sennikovsky April 6, 2021, 10:09 a.m. UTC
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@ionos.com>
---
 src/conntrack.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 178 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/src/conntrack.c b/src/conntrack.c
index 6040828..905d3a7 100644
--- a/src/conntrack.c
+++ b/src/conntrack.c
@@ -615,6 +615,17 @@  static unsigned int addr_valid_flags[ADDR_VALID_FLAGS_MAX] = {
 	CT_OPT_REPL_SRC | CT_OPT_REPL_DST,
 };
 
+#define CT_COMMANDS_LOAD_FILE_ALLOWED ( 0 \
+						| CT_CREATE       \
+						| CT_UPDATE_BIT   \
+						| CT_DELETE       \
+						| CT_FLUSH        \
+						)
+
+static unsigned int cmd_allowed = ~0;
+
+static bool print_stats_allowed = true;
+
 static LIST_HEAD(proto_list);
 
 static struct nfct_labelmap *labelmap;
@@ -1267,6 +1278,9 @@  add_command(unsigned int *cmd, const int newcmd)
 {
 	if (*cmd)
 		exit_error(PARAMETER_PROBLEM, "Invalid commands combination");
+	if (!(cmd_allowed & newcmd))
+		exit_error(PARAMETER_PROBLEM,
+				"Command can not be used in the current mode");
 	*cmd |= newcmd;
 }
 
@@ -2798,7 +2812,7 @@  nfct_set_nat_details(const int opt, struct nf_conntrack *ct,
 
 static int print_stats(const struct ct_cmd *cmd)
 {
-	if (cmd->command && exit_msg[cmd->cmd][0]) {
+	if (print_stats_allowed && cmd->command && exit_msg[cmd->cmd][0]) {
 		fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION);
 		fprintf(stderr, exit_msg[cmd->cmd], counter);
 		if (counter == 0 && !(cmd->command & (CT_LIST | EXP_LIST)))
@@ -3606,6 +3620,168 @@  static void ct_cmd_list_parse_argv(struct ct_cmd_list *list,
 	ct_cmd_list_add(list, ct_cmd_create(argc, argv));
 }
 
+#define MAX_ARGC	255
+struct argv_store {
+	int argc;
+	char *argv[MAX_ARGC];
+	int argvattr[MAX_ARGC];
+};
+
+/* function adding one argument to store, updating argc
+ * returns if argument added, does not return otherwise */
+static void add_argv(struct argv_store *store, const char *what, int quoted)
+{
+	if (store->argc + 1 >= MAX_ARGC)
+		exit_error(PARAMETER_PROBLEM,
+			      "Parser cannot handle more arguments\n");
+	if (!what)
+		exit_error(PARAMETER_PROBLEM,
+			      "Trying to store NULL argument\n");
+
+	store->argv[store->argc] = strdup(what);
+	store->argvattr[store->argc] = quoted;
+	store->argv[++store->argc] = NULL;
+}
+
+static void free_argv(struct argv_store *store)
+{
+	while (store->argc) {
+		store->argc--;
+		free(store->argv[store->argc]);
+		store->argvattr[store->argc] = 0;
+	}
+}
+
+struct ct_param_buf {
+	char	buffer[1024];
+	int 	len;
+};
+
+static void add_param(struct ct_param_buf *param, const char *curchar)
+{
+	param->buffer[param->len++] = *curchar;
+	if (param->len >= (int)sizeof(param->buffer))
+		exit_error(PARAMETER_PROBLEM, "Parameter too long!");
+}
+
+static void add_param_to_argv(struct argv_store *store, char *parsestart)
+{
+	int quote_open = 0, escaped = 0, quoted = 0;
+	struct ct_param_buf param = {};
+	char *curchar;
+
+	/* After fighting with strtok enough, here's now
+	 * a 'real' parser. According to Rusty I'm now no
+	 * longer a real hacker, but I can live with that */
+
+	for (curchar = parsestart; *curchar; curchar++) {
+		if (quote_open) {
+			if (escaped) {
+				add_param(&param, curchar);
+				escaped = 0;
+				continue;
+			} else if (*curchar == '\\') {
+				escaped = 1;
+				continue;
+			} else if (*curchar == '"') {
+				quote_open = 0;
+			} else {
+				add_param(&param, curchar);
+				continue;
+			}
+		} else {
+			if (*curchar == '"') {
+				quote_open = 1;
+				quoted = 1;
+				continue;
+			}
+		}
+
+		switch (*curchar) {
+		case '"':
+			break;
+		case ' ':
+		case '\t':
+		case '\n':
+			if (!param.len) {
+				/* two spaces? */
+				continue;
+			}
+			break;
+		default:
+			/* regular character, copy to buffer */
+			add_param(&param, curchar);
+			continue;
+		}
+
+		param.buffer[param.len] = '\0';
+		add_argv(store, param.buffer, quoted);
+		param.len = 0;
+		quoted = 0;
+	}
+	if (param.len) {
+		param.buffer[param.len] = '\0';
+		add_argv(store, param.buffer, 0);
+	}
+}
+
+static void ct_cmd_list_parse_line(struct ct_cmd_list *list,
+		const char *progname, char *buffer)
+{
+	struct argv_store store = {};
+
+	/* skip prepended tabs and spaces */
+	for (; *buffer == ' ' || *buffer == '\t'; buffer++);
+
+	if (buffer[0] == '\n'
+			|| buffer[0] == '#')
+		return;
+
+	add_argv(&store, progname, false);
+
+	add_param_to_argv(&store, buffer);
+
+	ct_cmd_list_parse_argv(list, store.argc, store.argv);
+
+	free_argv(&store);
+}
+
+static void ct_cmd_list_parse_file(struct ct_cmd_list *list,
+			   const char *progname,
+			   const char *file_name)
+{
+	char buffer[10240] = {};
+	FILE *file;
+
+	if (!strcmp(file_name, "-"))
+		file_name = "/dev/stdin";
+
+	file = fopen(file_name, "r");
+	if (!file)
+		exit_error(PARAMETER_PROBLEM, NULL,
+					   "Failed to open file %s for reading", file_name);
+
+	while (fgets(buffer, sizeof(buffer), file))
+		ct_cmd_list_parse_line(list, progname, buffer);
+}
+
+static void ct_cmd_list_parse(struct ct_cmd_list *list, int argc, char *argv[])
+{
+	if (argc > 2
+			&& (!strcmp(argv[1], "-R")
+			|| !strcmp(argv[1], "--load-file"))) {
+		int i;
+
+		cmd_allowed = CT_COMMANDS_LOAD_FILE_ALLOWED;
+		print_stats_allowed = false;
+
+		for (i = 2; i < argc; ++i)
+			ct_cmd_list_parse_file(list, argv[0], argv[i]);
+		return;
+	}
+	ct_cmd_list_parse_argv(list, argc, argv);
+}
+
 int main(int argc, char *argv[])
 {
 	struct ct_cmd_list list;
@@ -3622,7 +3798,7 @@  int main(int argc, char *argv[])
 
 	ct_cmd_list_init(&list);
 
-	ct_cmd_list_parse_argv(&list, argc, argv);
+	ct_cmd_list_parse(&list, argc, argv);
 
 	if (ct_cmd_list_apply(&list, argv[0]) < 0)
 		return EXIT_FAILURE;