netfilter-devel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [ulogd RFC PATCH 0/2] introduce new string output plugin
@ 2014-03-29  4:23 Ken-ichirou MATSUZAWA
  2014-03-29  4:27 ` [ulogd RFC PATCH 1/2] sprint: introduce new " Ken-ichirou MATSUZAWA
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Ken-ichirou MATSUZAWA @ 2014-03-29  4:23 UTC (permalink / raw)
  To: The netfilter developer mailinglist; +Cc: Eric Leblond

  Hello,
 
I started to add new output plugin which work with graphite or statsd. Would you
review these patches? I think this SPRINT output plugin has two issues and may
I ask?
 
* multiple files for one plugin
  I use flex and bison and need to separate source files. Is it acceptable?
 
* use double calc operation
  To tell statsd total amount bytes, I introduce calc operation (but '+' only now)
  not using correct ULOGD_RET_ type but regard all of them as double.
  It seems it is enough as of my purpose.

Thanks.

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

* [ulogd RFC PATCH 1/2] sprint: introduce new output plugin
  2014-03-29  4:23 [ulogd RFC PATCH 0/2] introduce new string output plugin Ken-ichirou MATSUZAWA
@ 2014-03-29  4:27 ` Ken-ichirou MATSUZAWA
  2014-03-31 21:06   ` Eric Leblond
  2014-03-29  4:29 ` [ulogd RFC PATCH 2/2] ip2str: introduce changable address separator Ken-ichirou MATSUZAWA
  2014-03-31 20:51 ` [ulogd RFC PATCH 0/2] introduce new string output plugin Eric Leblond
  2 siblings, 1 reply; 9+ messages in thread
From: Ken-ichirou MATSUZAWA @ 2014-03-29  4:27 UTC (permalink / raw)
  To: The netfilter developer mailinglist; +Cc: Eric Leblond

This patch introduces a new string output plugin. The output string can be
specified by "form" in config file. Format is consists of:

    key: struct ulogd_key name enclosed by <>, e.g. <orig.ip.saddr.str>
    group: enclosed by () and separated by |, pick first one if exists.
        (<orig.l4.dport>|<icmp.type>|unknown) means
        pick orig.l4.dport value if exist, or icmp.type value. if both
        of them do not exist, select "unknown" string.
    +: add two key value if it can be
    anything else: as is

meta character <>()|+\ needs to be escaped by \. Sink can be specified by
"proto" and "dest" in config file. "proto" is either file, tcp and udp.
"dest" is file name if "proto" is file, or port@address in tcp or udp.

More patch is needed to work, I think this will be suited for graphite and
statsd to see whole of traffic.

Signed-off-by: Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
---
 configure.ac                                |  15 +-
 output/Makefile.am                          |   2 +-
 output/sprint/Makefile.am                   |  21 ++
 output/sprint/ulogd_output_SPRINT-parser.y  | 358 ++++++++++++++++++++
 output/sprint/ulogd_output_SPRINT-scanner.l | 112 +++++++
 output/sprint/ulogd_output_SPRINT.c         | 495 ++++++++++++++++++++++++++++
 output/sprint/ulogd_output_SPRINT.h         |  45 +++
 7 files changed, 1046 insertions(+), 2 deletions(-)
 create mode 100644 output/sprint/Makefile.am
 create mode 100644 output/sprint/ulogd_output_SPRINT-parser.y
 create mode 100644 output/sprint/ulogd_output_SPRINT-scanner.l
 create mode 100644 output/sprint/ulogd_output_SPRINT.c
 create mode 100644 output/sprint/ulogd_output_SPRINT.h

diff --git a/configure.ac b/configure.ac
index 544a256..8ab2b27 100644
--- a/configure.ac
+++ b/configure.ac
@@ -14,6 +14,8 @@ dnl Checks for programs.
 AC_PROG_MAKE_SET
 AC_PROG_CC
 AC_PROG_INSTALL
+AC_PROG_YACC
+AC_PROG_LEX
 AC_DISABLE_STATIC
 AC_PROG_LIBTOOL
 
@@ -128,6 +130,16 @@ else
 	enable_jansson="no"
 fi
 
+AC_ARG_WITH([sprint], AS_HELP_STRING([--without-sprint], [Build without SPRINT output plugin [default=test]]))
+AS_IF([test "x$with_sprint" != "xno"], [
+if test "x$LEX" = "xflex" -a "x$YACC" = "xbison -y"; then
+	enable_sprint="yes"
+else
+	enable_sprint="no"
+fi
+])
+AM_CONDITIONAL([BUILD_SPRINT], [test "x$enable_sprint" = "xyes"])
+
 dnl AC_SUBST(DATABASE_DIR)
 dnl AC_SUBST(DATABASE_LIB)
 dnl AC_SUBST(DATABASE_LIB_DIR)
@@ -147,7 +159,7 @@ AC_CONFIG_FILES(include/Makefile include/ulogd/Makefile include/libipulog/Makefi
 	  input/sum/Makefile \
 	  filter/Makefile filter/raw2packet/Makefile filter/packet2flow/Makefile \
 	  output/Makefile output/pcap/Makefile output/mysql/Makefile output/pgsql/Makefile output/sqlite3/Makefile \
-	  output/dbi/Makefile \
+	  output/dbi/Makefile output/sprint/Makefile \
 	  src/Makefile Makefile Rules.make)
 AC_OUTPUT
 
@@ -164,5 +176,6 @@ Ulogd configuration:
     SQLITE3 plugin:			${enable_sqlite3}
     DBI plugin:				${enable_dbi}
     JSON plugin:			${enable_jansson}
+    SPRINT plugin:			${enable_sprint}
 "
 echo "You can now run 'make' and 'make install'"
diff --git a/output/Makefile.am b/output/Makefile.am
index ff851ad..7a39150 100644
--- a/output/Makefile.am
+++ b/output/Makefile.am
@@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include ${LIBNETFILTER_ACCT_CFLAGS} \
               ${LIBNETFILTER_CONNTRACK_CFLAGS} ${LIBNETFILTER_LOG_CFLAGS}
 AM_CFLAGS = ${regular_CFLAGS}
 
-SUBDIRS= pcap mysql pgsql sqlite3 dbi
+SUBDIRS= pcap mysql pgsql sqlite3 dbi sprint
 
 pkglib_LTLIBRARIES = ulogd_output_LOGEMU.la ulogd_output_SYSLOG.la \
 			 ulogd_output_OPRINT.la ulogd_output_GPRINT.la \
diff --git a/output/sprint/Makefile.am b/output/sprint/Makefile.am
new file mode 100644
index 0000000..90cbb34
--- /dev/null
+++ b/output/sprint/Makefile.am
@@ -0,0 +1,21 @@
+AM_CPPFLAGS = -I$(top_srcdir)/include
+AM_CFLAGS = ${regular_CFLAGS}
+AM_YFLAGS = -d
+#AM_LFLAGS = --header-file=scanner.h
+
+if BUILD_SPRINT
+
+pkglib_LTLIBRARIES = ulogd_output_SPRINT.la
+
+ulogd_output_SPRINT_la_SOURCES = ulogd_output_SPRINT.c ulogd_output_SPRINT-scanner.l ulogd_output_SPRINT-parser.y
+ulogd_output_SPRINT_la_LDFLAGS = -avoid-version -module
+# ulogd_output_SPRINT_la_LFLAGS =  --header-file=scanner.h
+
+BUILT_SOURCES = ulogd_output_SPRINT-parser.h ulogd_output_SPRINT-parser.c \
+		ulogd_output_SPRINT-scanner.h ulogd_output_SPRINT-scanner.c
+CLEANFILES = $(BUILT_SOURCES)
+
+ulogd_output_SPRINT-scanner.h: ulogd_output_SPRINT-scanner.l
+	$(LEX) -o /dev/null --header-file=$@ $<
+
+endif
diff --git a/output/sprint/ulogd_output_SPRINT-parser.y b/output/sprint/ulogd_output_SPRINT-parser.y
new file mode 100644
index 0000000..83f5af9
--- /dev/null
+++ b/output/sprint/ulogd_output_SPRINT-parser.y
@@ -0,0 +1,358 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+%{
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ulogd/ulogd.h>
+#include <ulogd/linuxlist.h>
+#include "ulogd_output_SPRINT.h"
+#include "ulogd_output_SPRINT-scanner.h"
+
+static int yyerror(YYLTYPE *loc, yyscan_t scanner, const char *msg, ...);
+
+static struct node *sprint_string_node(char *string)
+{
+	struct node *node = calloc(sizeof(struct node), 1);
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_STRING;
+	node->string = string;
+
+	return node;
+}
+
+static int sprint_key_index(struct outform *form, char *name)
+{
+	struct keysym *cur;
+	int i = 0;
+
+	llist_for_each_entry(cur, &form->keysyms, list) {
+		if (!strcmp(cur->name, name))
+			return i;
+		i++;
+	}
+
+	return -1;
+}
+
+static struct node *sprint_key_node(struct outform *form, char *name)
+{
+	struct node *node;
+	struct keysym *sym;
+
+	if (strlen(name) > ULOGD_MAX_KEYLEN) {
+		ulogd_log(ULOGD_ERROR, "too long key: %s\n", name);
+		return NULL;
+	}
+
+	node = calloc(sizeof(struct node), 1);
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_KEY;
+	node->kindex = sprint_key_index(form, name);
+	if (node->kindex < 0) {
+		sym = calloc(sizeof(struct keysym), 1);
+		if (sym == NULL) {
+			free(node);
+			return NULL;
+		}
+		sym->name = name;
+		node->kindex = form->num_keys++;
+		llist_add_tail(&sym->list, &form->keysyms);
+	}
+
+	return node;
+}
+
+static struct node *sprint_list_node(enum sprint_node_type type, struct node *term)
+{
+	struct node *node = calloc(sizeof(struct node), 1);
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = type;
+	INIT_LLIST_HEAD(&node->group);
+	llist_add_tail(&term->list, &node->group);
+	return node;
+}
+
+static struct node *sprint_group_add(struct node *group, struct node *term)
+{
+	llist_add_tail(&term->list, &group->group);
+	return group;
+}
+
+static struct node *sprint_keycalc_node(int opcode, struct node *l, struct node *r)
+{
+	struct node *node = calloc(sizeof(struct node), 1);
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_KEYCALC;
+	node->keycalc.opcode = opcode;
+	node->keycalc.l = l;
+	node->keycalc.r = r;
+
+	return node;
+}
+%}
+
+%code requires {
+	#ifndef YY_TYPEDEF_YY_SCANNER_T
+	#define YY_TYPEDEF_YY_SCANNER_T
+	typedef void* yyscan_t;
+	#endif
+
+	#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+	#define YY_TYPEDEF_YY_BUFFER_STATE
+	typedef struct yy_buffer_state *YY_BUFFER_STATE;
+	#endif
+}
+
+%debug
+%pure-parser
+%lex-param { scanner }
+%parse-param { yyscan_t scanner }
+%error-verbose
+%locations
+
+%union {
+	char *string;
+	struct node *node;
+}
+
+%token <string> STRING
+%token <string> KEY
+%token <string> ERR_TERM /* just notifying from scanner */
+
+%type <node> form part selector group term key
+
+%%
+
+form:
+	  /* empty */		{
+		$$ = &(yyget_extra(scanner))->head;
+	  }
+	| form part		{
+		llist_add_tail(&$2->list, &$1->list);
+		$$ = $1;
+	  }
+	;
+
+part:
+	  term
+	| group
+	;
+
+group:
+	  '(' selector ')'	{
+		$$ = $2;
+	  }
+	;
+
+selector:
+	  term			{
+		$$ = sprint_list_node(NODE_GROUP, $1);
+		if ($$ == NULL) {
+			yyerror(&yylloc, scanner, "could not create group node");
+			YYABORT;
+		}
+	  }
+	| selector '|' term	{
+		$$ = sprint_group_add($1, $3);
+	  }
+	;
+
+term:
+	  key
+	| STRING		{
+		$$ = sprint_string_node($1);
+		if ($$ == NULL) {
+			yyerror(&yylloc, scanner, "could not create string node");
+			YYABORT;
+		}
+	  }
+	| term key		{
+		if ($1->type != NODE_CONCAT) {
+			$1 = sprint_list_node(NODE_CONCAT, $1);
+			if ($1 == NULL) {
+				yyerror(&yylloc, scanner, "could not concat term");
+				YYABORT;
+			}
+		}
+		$$ = sprint_group_add($1, $2);
+	  }
+	| term STRING		{
+		if ($1->type == NODE_STRING) { /* concat string by using realloc */
+			int len1 = strlen($1->string), len2 = strlen($2);
+			$1->string = realloc($1->string, len1 + len2);
+			if ($1->string == NULL) {
+				yyerror(&yylloc, scanner, "could not reallocate string area");
+				YYABORT;
+			}
+			strncpy($1->string + len1, $2, len2);
+		} else {
+			struct node *n = sprint_string_node($2);
+			if ($1->type != NODE_CONCAT) {
+				$1 = sprint_list_node(NODE_CONCAT, $1);
+				if ($1 == NULL) {
+					yyerror(&yylloc, scanner, "could not concat term\n");
+					YYABORT;
+				}
+			}
+			$$ = sprint_group_add($1, n);
+		}
+	  }
+	| ERR_TERM		{
+		$$ = NULL; /* supress warning */
+		yyerror(&yylloc, scanner, $1);
+		YYABORT;
+	  }
+	;
+
+key:
+	KEY			{
+		$$ = sprint_key_node(yyget_extra(scanner), $1);
+		if ($$ == NULL) {
+			yyerror(&yylloc, scanner, "could not create key node");
+			YYABORT;
+		}
+	  }
+	| key '+' key		{
+		$$ = sprint_keycalc_node('+', $1, $3);
+		if ($$ == NULL) {
+			yyerror(&yylloc, scanner, "could not create key calc node");
+			YYABORT;
+		}
+	  }
+	;
+%%
+
+int yyerror(YYLTYPE *loc, yyscan_t scanner, const char *msg, ...)
+{
+	va_list ap;
+	char buf[4096];
+
+	va_start(ap, msg);
+	snprintf(buf, sizeof(buf), msg, ap);
+	va_end(ap);
+
+	ulogd_log(ULOGD_ERROR, "form error - %s, at: %d\n", buf, yyget_column(scanner));
+
+	return 0;
+}
+
+char *sprint_key_name(struct llist_head *head, int kindex)
+{
+	struct keysym *sym;
+	int i = 0;
+
+	llist_for_each_entry(sym, head, list) {
+		if (i++ == kindex)
+			return sym->name;
+	}
+
+	return NULL;
+}
+
+void sprint_free_nodes(struct llist_head *nodes);
+
+void sprint_free_node(struct node *node)
+{
+	switch (node->type) {
+	case NODE_STRING:
+		free(node->string);
+		break;
+	case NODE_KEY:
+		break;
+	case NODE_GROUP:
+	case NODE_CONCAT:
+		sprint_free_nodes(&node->group);
+		break;
+	case NODE_KEYCALC:
+		sprint_free_node(node->keycalc.l);
+		sprint_free_node(node->keycalc.r);
+		break;
+	default:
+		ulogd_log(ULOGD_ERROR, "unknown node: %p"
+			  " type: %d\n", node, node->type);
+		break;
+	}
+}
+
+void sprint_free_nodes(struct llist_head *nodes)
+{
+	struct node *node, *nnode;
+
+	llist_for_each_entry_safe(node, nnode, nodes, list) {
+		sprint_free_node(node);
+		llist_del(&node->list);
+		free(node);
+	}
+}
+
+void sprint_free_keysyms(struct llist_head *head)
+{
+	struct keysym *sym, *nsym;
+
+	llist_for_each_entry_safe(sym, nsym, head, list) {
+		llist_del(&sym->list);
+		free(sym->name);
+		free(sym);
+	}
+}
+
+/*
+ * This function returns 0 on success
+ * error on parsing: > 0
+ * otherwise < 0 means negative errno
+ */
+int parse_form(char *str, struct outform *form)
+{
+	yyscan_t scanner;
+	YY_BUFFER_STATE buf;
+	int ret = 0;
+
+	if (yylex_init_extra(form, &scanner))
+		return -errno;
+	buf = yy_scan_string(str, scanner);
+	if (buf == NULL) {
+		ret = -errno;
+		/* XXX: needs free? what's the status of extra data and buffer */
+		goto free_scanner;
+	}
+
+	ret = yyparse(scanner);
+	if (ret == 0)
+		ret = form->yy_fatal_errno;
+	if (ret != 0) {
+		sprint_free_nodes(&form->head.list);
+		sprint_free_keysyms(&form->keysyms);
+	}
+
+	yy_delete_buffer(buf, scanner);
+free_scanner:
+	yylex_destroy(scanner);
+
+	return ret;
+}
diff --git a/output/sprint/ulogd_output_SPRINT-scanner.l b/output/sprint/ulogd_output_SPRINT-scanner.l
new file mode 100644
index 0000000..a401aea
--- /dev/null
+++ b/output/sprint/ulogd_output_SPRINT-scanner.l
@@ -0,0 +1,112 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+%{
+#include <string.h>
+#include <ulogd/ulogd.h>
+#include <ulogd/linuxlist.h>
+#include "ulogd_output_SPRINT.h"
+#include "ulogd_output_SPRINT-parser.h"
+
+#define YY_USER_ACTION \
+	yyset_column(yyget_column(yyscanner) \
+	+ yyget_leng(yyscanner), yyscanner);
+
+#define YY_FATAL_ERROR(msg) { \
+	ulogd_log(ULOGD_FATAL, msg);\
+	yyget_extra(yyscanner)->yy_fatal_errno = \
+		errno != 0 ? -errno : 1;\
+}
+%}
+
+%option debug
+%option warn
+
+%option reentrant
+%option noyywrap
+%option nounput
+%option noinput
+%option bison-bridge
+%option bison-locations
+%option nodefault
+%option never-interactive
+%option extra-type="struct outform *"
+
+%x escape
+%x key
+
+%%
+
+<INITIAL>"\\"	{ BEGIN(escape); }
+<INITIAL>"<"	{ BEGIN(key); }
+<INITIAL>">"	{
+		yylval->string = "unexpected key end";
+		return ERR_TERM;
+	}
+<INITIAL>[()|]	{
+		return *yytext;
+	}
+
+<INITIAL>[ \t]*"+"[ \t]*	{
+		return '+';
+	}
+<INITIAL>[^\\<>()|]+	{
+		yylval->string = strdup(yytext);
+		return STRING;
+	}
+
+<escape><<EOF>>	{ 
+		yylval->string = "EOF in escaped char";
+		BEGIN(INITIAL);
+		return ERR_TERM;
+	}
+<escape>[nt\\<>()|\+] {
+		switch(*yytext) {
+		case 'n':
+			yylval->string = strdup("\n");
+			break;
+		case 't':
+			yylval->string = strdup("\t");
+			break;
+		default:
+			yylval->string = strdup(yytext);
+			break;
+		}
+		BEGIN(INITIAL);
+		return STRING;
+	}
+<escape>(.|"\n")	{
+		yylval->string = "invalid escape char";
+		BEGIN(INITIAL);
+		return ERR_TERM;
+	}
+
+	/* XXX: no empty key `<>' handling. */
+<key><<EOF>>	{
+		yylval->string = "EOF in key";
+		BEGIN(INITIAL);
+		return ERR_TERM;
+	}
+<key>[a-zA-Z][a-zA-Z0-9\._-]* {
+		yylval->string = strdup(yytext);
+		return KEY;
+	}
+<key>">"	{ BEGIN(INITIAL); }
+<key>(.|"\n")	{
+		yylval->string = "invalid key char";
+		BEGIN(INITIAL);
+		return ERR_TERM;
+	}
+%%
diff --git a/output/sprint/ulogd_output_SPRINT.c b/output/sprint/ulogd_output_SPRINT.c
new file mode 100644
index 0000000..92ca663
--- /dev/null
+++ b/output/sprint/ulogd_output_SPRINT.c
@@ -0,0 +1,495 @@
+/* ulogd_output_SPRINT.c
+ *
+ * ulogd output target for sending value specified `form' in config.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <netdb.h>
+
+#include <ulogd/ulogd.h>
+#include <ulogd/conffile.h>
+
+#include "ulogd_output_SPRINT.h"
+
+#ifndef ULOGD_SPRINT_DEFAULT
+#define ULOGD_SPRINT_DEFAULT	"/var/log/ulogd.sprint"
+#endif
+
+struct sprint_priv {
+	int ofd;
+	struct llist_head form_head;
+};
+
+enum sprint_conf {
+	SPRINT_CONF_FORM = 0,
+	SPRINT_CONF_PROTO,
+	SPRINT_CONF_DEST,
+	SPRINT_CONF_MAX
+};
+
+static struct config_keyset sprint_kset = {
+	.num_ces = SPRINT_CONF_MAX,
+	.ces = {
+		[SPRINT_CONF_FORM] = {
+			.key = "form",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+		},
+		[SPRINT_CONF_PROTO] = {
+			.key = "proto",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = "file" },
+
+		},
+		[SPRINT_CONF_DEST] = {
+			.key = "dest",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = ULOGD_SPRINT_DEFAULT },
+		},
+	},
+};
+
+static int open_connect_descriptor(struct ulogd_pluginstance *upi)
+{
+	char *proto, *host, *port;
+	struct addrinfo hint, *result, *rp;
+	int ret, fd;
+
+	proto = upi->config_kset->ces[SPRINT_CONF_PROTO].u.string;
+	port = upi->config_kset->ces[SPRINT_CONF_DEST].u.string;
+
+	/* file */
+	if (!strcasecmp(proto, "file")) {
+		if (strlen(port) == 0)
+			return STDOUT_FILENO;
+		return open(port, O_CREAT|O_WRONLY|O_APPEND);
+	}
+
+	/* socket */
+	host = strchr(port, '@');
+	if (host == NULL) {
+		ulogd_log(ULOGD_ERROR, "unknown destination `%s'\n",
+			  port);
+		errno = EINVAL;
+		return -1;
+	}
+	*host++ = '\0';
+
+	memset(&hint, 0, sizeof(struct addrinfo));
+	hint.ai_family = AF_UNSPEC;
+	if (!strcasecmp(proto, "udp")) {
+		hint.ai_socktype = SOCK_DGRAM;
+		hint.ai_protocol = IPPROTO_UDP;
+	} else if (!strcasecmp(proto, "tcp")) {
+		hint.ai_socktype = SOCK_STREAM;
+		hint.ai_protocol = IPPROTO_TCP;
+	} else {
+		ulogd_log(ULOGD_ERROR, "unknown protocol `%s'\n",
+			  proto);
+		errno = EINVAL;
+		return -1;
+	}
+
+	ret = getaddrinfo(host, port, &hint, &result);
+	if (ret != 0) {
+		ulogd_log(ULOGD_ERROR, "can't resolve host/service: %s\n",
+			  gai_strerror(ret));
+		if (ret != EAI_SYSTEM)
+			errno = EINVAL;
+		return -1;
+	}
+
+	for (rp = result; rp != NULL; rp = rp->ai_next) {
+		int on = 1;
+
+		fd = socket(rp->ai_family, rp->ai_socktype,
+			     rp->ai_protocol);
+		if (fd == -1)
+			continue;
+
+		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+			   (void *)&on, sizeof(on));
+		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
+			break;
+	}
+
+	freeaddrinfo(result);
+
+	if (rp == NULL) {
+		ulogd_log(ULOGD_ERROR, "could not connect\n");
+		/* XXX: errno? */
+		return -1;
+	}
+
+	return fd;
+}
+
+static double sprint_key_calc(struct ulogd_key *keys, struct node *node,
+			      bool *is_valid)
+{
+	*is_valid = false;
+	if (node->type == NODE_KEY) {
+		struct ulogd_key *key = keys[node->kindex].u.source;
+		if (!(key->flags & ULOGD_RETF_VALID))
+			return 0.0;
+
+		switch (key->type) {
+		case ULOGD_RET_BOOL:
+		case ULOGD_RET_INT8:
+		case ULOGD_RET_INT16:
+		case ULOGD_RET_INT32:
+			*is_valid = true;
+			return (double)key->u.value.i32;
+			break;
+		case ULOGD_RET_UINT8:
+		case ULOGD_RET_UINT16:
+		case ULOGD_RET_UINT32:
+		case ULOGD_RET_UINT64:
+			*is_valid = true;
+			return (double)key->u.value.ui64;
+			break;
+		default:
+			ulogd_log(ULOGD_INFO, "could not calc"
+				  " key: %s type: %d\n", key->name, key->type);
+		}
+	} else if (node->type == NODE_KEYCALC) {
+		bool lvalid, rvalid;
+		double lval = sprint_key_calc(keys, node->keycalc.l, &lvalid),
+			rval = sprint_key_calc(keys, node->keycalc.r, &rvalid);
+
+		if (!lvalid || !rvalid)
+			return 0.0; /* without setting is_valid */
+
+		switch (node->keycalc.opcode) {
+		case '+':
+			*is_valid = true;
+			return lval + rval;
+			break;
+		default:
+			ulogd_log(ULOGD_NOTICE, "unknown opcode: %c\n",
+				  node->keycalc.opcode);
+			break;
+		}
+	} else {
+		ulogd_log(ULOGD_NOTICE, "invalid node type in keycalc: %d\n",
+			  node->type);
+	}
+
+	return 0.0; /* without setting is_valid */
+}
+
+static int sprint_keycalc_puts(char *buf, size_t size, bool in_group,
+			       struct ulogd_key *keys, struct node *node)
+{
+	bool is_valid;
+	double ret = sprint_key_calc(keys, node, &is_valid);
+
+	if (!is_valid && in_group)
+		return 0;
+
+	return snprintf(buf, size, "%.0f", ret);
+}
+
+static int sprint_key_puts(char *buf, size_t size, bool in_group,
+			   struct ulogd_key *keys, struct node *node)
+{
+	struct ulogd_key *key = keys[node->kindex].u.source;
+
+	if (!(key->flags & ULOGD_RETF_VALID)) {
+		if (!in_group) {
+			ulogd_log(ULOGD_INFO, "no key value: %s\n", key->name);
+			return printf("<>");
+		}
+		return 0;
+	}
+
+	switch (key->type) {
+	case ULOGD_RET_STRING:
+		return snprintf(buf, size, "%s", (char *)key->u.value.ptr);
+		break;
+	case ULOGD_RET_BOOL:
+	case ULOGD_RET_INT8:
+	case ULOGD_RET_INT16:
+	case ULOGD_RET_INT32:
+		return snprintf(buf, size, "%d", key->u.value.i32);
+		break;
+	case ULOGD_RET_UINT8:
+	case ULOGD_RET_UINT16:
+	case ULOGD_RET_UINT32:
+	case ULOGD_RET_UINT64:
+		return snprintf(buf, size, "%" PRIu64, key->u.value.ui64);
+		break;
+	default:
+		ulogd_log(ULOGD_INFO, "could not interpret"
+			  " key: %s, type: %d\n", key->name, key->type);
+		break;
+	}
+	return 0; /* default */
+}
+
+static int sprint_term_puts(char *buf, size_t size, bool in_group,
+			    struct ulogd_key *keys, struct node *node)
+{
+	struct node *n;
+	int ret;
+	size_t len = 0;
+
+	switch (node->type) {
+	case NODE_KEY:
+		return sprint_key_puts(buf, size, in_group, keys, node);
+		break;
+	case NODE_STRING:
+		return snprintf(buf, size, "%s", node->string);
+		break;
+	case NODE_KEYCALC:
+		return sprint_keycalc_puts(buf, size, in_group, keys, node);
+		break;
+	case NODE_CONCAT:
+		llist_for_each_entry(n, &node->group, list) {
+			ret = sprint_term_puts(buf + len, size - len,
+					       in_group, keys, n);
+			if ((n->type == NODE_KEY || n->type == NODE_KEYCALC)
+			    && ret <= 0) {
+				/* no key value found in a group */
+				return 0;
+			}
+			len += ret;
+			if (len >= size) {
+				ulogd_log(ULOGD_NOTICE, "exceeds bufsize\n");
+				return len;
+			}
+		}
+		return len;
+		break;
+	default:
+		ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
+			  node->type);
+		break;
+	}
+
+	return 0; /* unknown node type */
+}
+
+static int sprint_group_puts(char *buf, size_t size,
+			     struct ulogd_key *keys, struct node *node)
+{
+	int ret;
+	struct node *n;
+
+	llist_for_each_entry(n, &node->group, list) {
+		ret = sprint_term_puts(buf, size, true, keys, n);
+		if (ret > 0) /* put first valid value and return */
+			return ret;
+	}
+
+	ulogd_log(ULOGD_NOTICE, "no value found in group\n");
+	return snprintf(buf, size, "()");
+}
+
+static int sprint_interp(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
+	struct node *cur;
+	char buf[4096];
+	int rem = sizeof(buf) - 1, len = 0, ret;
+
+	llist_for_each_entry(cur, &sp->form_head, list) {
+		switch (cur->type) {
+		case NODE_KEY:
+		case NODE_STRING:
+		case NODE_CONCAT:
+		case NODE_KEYCALC:
+			len += sprint_term_puts(buf + len, rem, false,
+						upi->input.keys, cur);
+			break;
+		case NODE_GROUP:
+			len += sprint_group_puts(buf + len, rem,
+						 upi->input.keys, cur);
+			break;
+		default:
+			ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
+				  cur->type);
+		}
+		rem -= len;
+		if (rem <= 0) {
+			ulogd_log(ULOGD_NOTICE,
+				  "sprint_term_puts exceeds bufsize\n");
+			len = sizeof(buf);
+			break;
+		}
+	}
+
+	ret = write(sp->ofd, buf, len);
+	if (ret != len) {
+		buf[len] = '\0';
+		ulogd_log(ULOGD_ERROR, "Failure sending message: %s\n", buf);
+		if (ret == -1) {
+			sp->ofd = open_connect_descriptor(upi);
+			if (sp->ofd == -1)
+				return ULOGD_IRET_ERR;
+		}
+	}
+	return ULOGD_IRET_OK;
+}
+
+static void sighup_handler_print(struct ulogd_pluginstance *upi, int signal)
+{
+	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
+	int old = sp->ofd;
+
+	switch (signal) {
+	case SIGHUP:
+		ulogd_log(ULOGD_NOTICE, "SPRINT: reopening logfile\n");
+		sp->ofd = open_connect_descriptor(upi);
+		if (sp->ofd == -1) {
+			ulogd_log(ULOGD_ERROR, "can't open SPRINT "
+					       "log file: %s\n",
+				  strerror(errno));
+			sp->ofd = old;
+		} else {
+			close(old);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int sprint_set_inputkeys(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *priv = (struct sprint_priv *)&upi->private;
+	struct keysym *sym, *nsym;
+	struct ulogd_key *ikey;
+	int ret;
+	struct outform form;
+
+	INIT_LLIST_HEAD(&priv->form_head);
+	INIT_LLIST_HEAD(&form.keysyms);
+	INIT_LLIST_HEAD(&form.head.list);
+	form.head.type = NODE_HEAD;
+	form.yy_fatal_errno = 0;
+
+	ret = parse_form(upi->config_kset->ces[SPRINT_CONF_FORM].u.string,
+			 &form);
+	if (ret > 0) {
+		/* parser error, already logged by yyerror */
+		return -ret;
+	} else if (ret < 0) { /* errno */
+		ulogd_log(ULOGD_ERROR, "could not parse form: %s\n",
+			  strerror(-ret));
+		return ret;
+	}
+
+	llist_add(&priv->form_head, &form.head.list);
+	llist_del(&form.head.list);
+
+	ulogd_log(ULOGD_DEBUG, "allocating %u input keys for SPRINT\n",
+		  form.num_keys);
+	upi->input.keys = ikey = calloc(sizeof(struct ulogd_key),
+					form.num_keys);
+
+	if (!upi->input.keys)
+		return -ENOMEM;
+
+	/* create input keys from key symbol list created by form parsing */
+	llist_for_each_entry_safe(sym, nsym, &form.keysyms, list) {
+		ikey->flags = ULOGD_RETF_NONE;
+		strncpy(ikey->name, sym->name, strlen(sym->name));
+		free(sym->name);
+		free(sym);
+		ikey++;
+	}
+	upi->input.num_keys = form.num_keys;
+
+	return ret;
+}
+
+static int sprint_configure(struct ulogd_pluginstance *upi,
+			    struct ulogd_pluginstance_stack *stack)
+{
+	int ret;
+
+	ret = config_parse_file(upi->id, upi->config_kset);
+	if (ret < 0)
+		return ret;
+
+	ret = sprint_set_inputkeys(upi);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int sprint_init(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *) &upi->private;
+
+	sp->ofd = open_connect_descriptor(upi);
+	if (sp->ofd < 0) {
+		ulogd_log(ULOGD_FATAL, "can't open SPRINT destination: %s\n",
+			  strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int sprint_fini(struct ulogd_pluginstance *pi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *) &pi->private;
+
+	if (sp->ofd != STDOUT_FILENO)
+		close(sp->ofd);
+
+	return 0;
+}
+
+static struct ulogd_plugin sprint_plugin = {
+	.name = "SPRINT",
+	.input = {
+		.type	= ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW | ULOGD_DTYPE_SUM,
+	},
+	.output = {
+		.type	= ULOGD_DTYPE_SINK,
+	},
+	.configure	= &sprint_configure,
+	.interp		= &sprint_interp,
+	.start		= &sprint_init,
+	.stop		= &sprint_fini,
+	.signal		= &sighup_handler_print,
+	.config_kset	= &sprint_kset,
+	.version	= VERSION,
+};
+
+void __attribute__ ((constructor)) init(void);
+
+void init(void)
+{
+	ulogd_register_plugin(&sprint_plugin);
+}
diff --git a/output/sprint/ulogd_output_SPRINT.h b/output/sprint/ulogd_output_SPRINT.h
new file mode 100644
index 0000000..729f1f9
--- /dev/null
+++ b/output/sprint/ulogd_output_SPRINT.h
@@ -0,0 +1,45 @@
+#ifndef _SPRINT_H
+#define _SPRINT_H
+
+#include "ulogd_output_SPRINT-parser.h"
+
+enum sprint_node_type {
+	NODE_HEAD,
+	NODE_STRING,
+	NODE_KEY,
+	NODE_CONCAT,
+	NODE_GROUP,
+	NODE_KEYCALC,
+};
+
+struct keyop {
+	int opcode;
+	struct node *l;
+	struct node *r;
+};
+
+struct node {
+	enum sprint_node_type type;
+	struct llist_head list;
+	union {
+		char *string;			/* NODE_STRING */
+		int kindex;			/* NODE_KEY */
+		struct llist_head group;	/* NODE_CONCAT, NODE_GROUP */
+		struct keyop keycalc;		/* NODE_KEYCALC */
+	};
+};
+
+struct keysym {
+	struct llist_head list;
+	char *name;
+};
+
+struct outform {
+  	int yy_fatal_errno;		/* ugly way of avoiding YY_FATAL_ERROR exit() call */
+	int num_keys;			/* number of keys */
+	struct node head;		/* list of sprint node */
+	struct llist_head keysyms;	/* key symbol list generating ulogd_key */
+};
+
+int parse_form(char *str, struct outform *form);
+#endif
-- 
1.8.5.3


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

* [ulogd RFC PATCH 2/2] ip2str: introduce changable address separator
  2014-03-29  4:23 [ulogd RFC PATCH 0/2] introduce new string output plugin Ken-ichirou MATSUZAWA
  2014-03-29  4:27 ` [ulogd RFC PATCH 1/2] sprint: introduce new " Ken-ichirou MATSUZAWA
@ 2014-03-29  4:29 ` Ken-ichirou MATSUZAWA
  2014-03-31 20:17   ` Eric Leblond
  2014-03-31 20:51 ` [ulogd RFC PATCH 0/2] introduce new string output plugin Eric Leblond
  2 siblings, 1 reply; 9+ messages in thread
From: Ken-ichirou MATSUZAWA @ 2014-03-29  4:29 UTC (permalink / raw)
  To: The netfilter developer mailinglist; +Cc: Eric Leblond

This patch make change address string separator by "v4sep" or "v6sep" in config
file, because graphite uses `.' as path separator and statsd uses `:' as path
and value separator. Now I am testing ulogd and statsd (actually statsite) using
ulogd config:

  stack=ct1:NFCT,ip2str1:IP2STR,sprint:SPRINT
  [ct1]
  # in event mode, NF_NETLINK_CONNTRACK_DESTROY only
  event_mask=4
  [ip2str1]
  v4sep="_"
  v6sep="_"
  [sprint]
  form="myrouter.<orig.ip.daddr.str>.<orig.ip.protocol>.(<icmp.type>|<orig.l4.dport>|unknown).<orig.ip.saddr.str>:<orig.raw.pktlen> + <reply.raw.pktlen>\|kv\n"
  proto="tcp"
  dest="8125@192.168.1.1"

Signed-off-by: Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
---
 filter/ulogd_filter_IP2STR.c | 74 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 72 insertions(+), 2 deletions(-)

diff --git a/filter/ulogd_filter_IP2STR.c b/filter/ulogd_filter_IP2STR.c
index 732e1ef..774b9cf 100644
--- a/filter/ulogd_filter_IP2STR.c
+++ b/filter/ulogd_filter_IP2STR.c
@@ -137,10 +137,55 @@ static struct ulogd_key ip2str_keys[] = {
 	},
 };
 
+enum ip2str_conf {
+	IP2STR_CONF_V6SEP = 0,
+	IP2STR_CONF_V4SEP,
+	IP2STR_CONF_MAX
+};
+
+static struct config_keyset ip2str_config_kset = {
+	.num_ces = 2,
+	.ces = {
+		[IP2STR_CONF_V6SEP] = {
+			.key = "v6sep",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = ":"},
+		},
+		[IP2STR_CONF_V4SEP] = {
+			.key = "v4sep",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = "."},
+		},
+	},
+};
+
+#define v6sep_ce(x)	(x->ces[IP2STR_CONF_V6SEP])
+#define v4sep_ce(x)	(x->ces[IP2STR_CONF_V4SEP])
+
 static char ipstr_array[MAX_KEY-START_KEY][IPADDR_LENGTH];
 
-static int ip2str(struct ulogd_key *inp, int index, int oindex)
+void change_separator(char family, char *addr, char to)
 {
+	char from;
+	char *cur;
+
+	switch(family) {
+	case AF_INET6: from = ':'; break;
+	case AF_INET: from = '.'; break;
+	default:
+		ulogd_log(ULOGD_NOTICE, "Unknown protocol family\n");
+		return;
+	}
+
+	for (cur = strchr(addr, from); cur != NULL; cur = strchr(cur + 1, from))
+		*cur = to;
+}
+
+static int ip2str(struct ulogd_pluginstance *upi, int index, int oindex)
+{
+	struct ulogd_key *inp = upi->input.keys;
 	char family = ikey_get_u8(&inp[KEY_OOB_FAMILY]);
 	char convfamily = family;
 
@@ -173,11 +218,17 @@ static int ip2str(struct ulogd_key *inp, int index, int oindex)
 		inet_ntop(AF_INET6,
 			  ikey_get_u128(&inp[index]),
 			  ipstr_array[oindex], sizeof(ipstr_array[oindex]));
+		if (*v6sep_ce(upi->config_kset).u.string != ':')
+			change_separator(convfamily, ipstr_array[oindex],
+					 *v6sep_ce(upi->config_kset).u.string);
 		break;
 	case AF_INET:
 		ip = ikey_get_u32(&inp[index]);
 		inet_ntop(AF_INET, &ip,
 			  ipstr_array[oindex], sizeof(ipstr_array[oindex]));
+		if (*v4sep_ce(upi->config_kset).u.string != '.')
+			change_separator(convfamily, ipstr_array[oindex],
+					 *v4sep_ce(upi->config_kset).u.string);
 		break;
 	default:
 		/* TODO error handling */
@@ -197,7 +248,7 @@ static int interp_ip2str(struct ulogd_pluginstance *pi)
 	/* Iter on all addr fields */
 	for (i = START_KEY; i <= MAX_KEY; i++) {
 		if (pp_is_valid(inp, i)) {
-			fret = ip2str(inp, i, i-START_KEY);
+			fret = ip2str(pi, i, i-START_KEY);
 			if (fret != ULOGD_IRET_OK)
 				return fret;
 			okey_set_ptr(&ret[i-START_KEY],
@@ -208,6 +259,23 @@ static int interp_ip2str(struct ulogd_pluginstance *pi)
 	return ULOGD_IRET_OK;
 }
 
+static int configure_ip2str(struct ulogd_pluginstance *upi,
+			    struct ulogd_pluginstance_stack *stack)
+{
+	int ret = config_parse_file(upi->id, upi->config_kset);
+
+	if (ret < 0)
+		return ret;
+
+	if (strlen(v6sep_ce(upi->config_kset).u.string) > 1)
+		ulogd_log(ULOGD_NOTICE, "only one char v6 separator is allowed,"
+			  " using: %c\n", *v6sep_ce(upi->config_kset).u.string);
+	if (strlen(v4sep_ce(upi->config_kset).u.string) > 1)
+		ulogd_log(ULOGD_NOTICE, "only one char v4 separator is allowed,"
+			  " using: %c\n", *v4sep_ce(upi->config_kset).u.string);
+	return ret;
+}
+
 static struct ulogd_plugin ip2str_pluging = {
 	.name = "IP2STR",
 	.input = {
@@ -220,7 +288,9 @@ static struct ulogd_plugin ip2str_pluging = {
 		.num_keys = ARRAY_SIZE(ip2str_keys),
 		.type = ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW,
 		},
+	.config_kset = &ip2str_config_kset,
 	.interp = &interp_ip2str,
+	.configure = &configure_ip2str,
 	.version = VERSION,
 };
 
-- 
1.8.5.3


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

* Re: [ulogd RFC PATCH 2/2] ip2str: introduce changable address separator
  2014-03-29  4:29 ` [ulogd RFC PATCH 2/2] ip2str: introduce changable address separator Ken-ichirou MATSUZAWA
@ 2014-03-31 20:17   ` Eric Leblond
  0 siblings, 0 replies; 9+ messages in thread
From: Eric Leblond @ 2014-03-31 20:17 UTC (permalink / raw)
  To: Ken-ichirou MATSUZAWA; +Cc: The netfilter developer mailinglist

[-- Attachment #1: Type: text/plain, Size: 5510 bytes --]

Hello,

On Sat, 2014-03-29 at 13:29 +0900, Ken-ichirou MATSUZAWA wrote:
> This patch make change address string separator by "v4sep" or "v6sep" in config
> file, because graphite uses `.' as path separator and statsd uses `:' as path
> and value separator. Now I am testing ulogd and statsd (actually statsite) using
> ulogd config:
> 
>   stack=ct1:NFCT,ip2str1:IP2STR,sprint:SPRINT
>   [ct1]
>   # in event mode, NF_NETLINK_CONNTRACK_DESTROY only
>   event_mask=4
>   [ip2str1]
>   v4sep="_"
>   v6sep="_"
>   [sprint]
>   form="myrouter.<orig.ip.daddr.str>.<orig.ip.protocol>.(<icmp.type>|<orig.l4.dport>|unknown).<orig.ip.saddr.str>:<orig.raw.pktlen> + <reply.raw.pktlen>\|kv\n"
>   proto="tcp"
>   dest="8125@192.168.1.1"

I'm not ok with this patch because it induces some tests for all stack
using the ip2str plugin. Furthermore it is making configuration of
ip2str a bit too complex.

I see two possible solutions:
      * You create a plugin dedicated to the conversion of separator in
        IP address. This will lead to add a plugin to your existing
        stack.
      * Your code does know what field it is using as address. So it
        could simply do the translation before creating the message.

BR,

> Signed-off-by: Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
> ---
>  filter/ulogd_filter_IP2STR.c | 74 ++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 72 insertions(+), 2 deletions(-)
> 
> diff --git a/filter/ulogd_filter_IP2STR.c b/filter/ulogd_filter_IP2STR.c
> index 732e1ef..774b9cf 100644
> --- a/filter/ulogd_filter_IP2STR.c
> +++ b/filter/ulogd_filter_IP2STR.c
> @@ -137,10 +137,55 @@ static struct ulogd_key ip2str_keys[] = {
>  	},
>  };
>  
> +enum ip2str_conf {
> +	IP2STR_CONF_V6SEP = 0,
> +	IP2STR_CONF_V4SEP,
> +	IP2STR_CONF_MAX
> +};
> +
> +static struct config_keyset ip2str_config_kset = {
> +	.num_ces = 2,
> +	.ces = {
> +		[IP2STR_CONF_V6SEP] = {
> +			.key = "v6sep",
> +			.type = CONFIG_TYPE_STRING,
> +			.options = CONFIG_OPT_NONE,
> +			.u = {.string = ":"},
> +		},
> +		[IP2STR_CONF_V4SEP] = {
> +			.key = "v4sep",
> +			.type = CONFIG_TYPE_STRING,
> +			.options = CONFIG_OPT_NONE,
> +			.u = {.string = "."},
> +		},
> +	},
> +};
> +
> +#define v6sep_ce(x)	(x->ces[IP2STR_CONF_V6SEP])
> +#define v4sep_ce(x)	(x->ces[IP2STR_CONF_V4SEP])
> +
>  static char ipstr_array[MAX_KEY-START_KEY][IPADDR_LENGTH];
>  
> -static int ip2str(struct ulogd_key *inp, int index, int oindex)
> +void change_separator(char family, char *addr, char to)
>  {
> +	char from;
> +	char *cur;
> +
> +	switch(family) {
> +	case AF_INET6: from = ':'; break;
> +	case AF_INET: from = '.'; break;
> +	default:
> +		ulogd_log(ULOGD_NOTICE, "Unknown protocol family\n");
> +		return;
> +	}
> +
> +	for (cur = strchr(addr, from); cur != NULL; cur = strchr(cur + 1, from))
> +		*cur = to;
> +}
> +
> +static int ip2str(struct ulogd_pluginstance *upi, int index, int oindex)
> +{
> +	struct ulogd_key *inp = upi->input.keys;
>  	char family = ikey_get_u8(&inp[KEY_OOB_FAMILY]);
>  	char convfamily = family;
>  
> @@ -173,11 +218,17 @@ static int ip2str(struct ulogd_key *inp, int index, int oindex)
>  		inet_ntop(AF_INET6,
>  			  ikey_get_u128(&inp[index]),
>  			  ipstr_array[oindex], sizeof(ipstr_array[oindex]));
> +		if (*v6sep_ce(upi->config_kset).u.string != ':')
> +			change_separator(convfamily, ipstr_array[oindex],
> +					 *v6sep_ce(upi->config_kset).u.string);
>  		break;
>  	case AF_INET:
>  		ip = ikey_get_u32(&inp[index]);
>  		inet_ntop(AF_INET, &ip,
>  			  ipstr_array[oindex], sizeof(ipstr_array[oindex]));
> +		if (*v4sep_ce(upi->config_kset).u.string != '.')
> +			change_separator(convfamily, ipstr_array[oindex],
> +					 *v4sep_ce(upi->config_kset).u.string);
>  		break;
>  	default:
>  		/* TODO error handling */
> @@ -197,7 +248,7 @@ static int interp_ip2str(struct ulogd_pluginstance *pi)
>  	/* Iter on all addr fields */
>  	for (i = START_KEY; i <= MAX_KEY; i++) {
>  		if (pp_is_valid(inp, i)) {
> -			fret = ip2str(inp, i, i-START_KEY);
> +			fret = ip2str(pi, i, i-START_KEY);
>  			if (fret != ULOGD_IRET_OK)
>  				return fret;
>  			okey_set_ptr(&ret[i-START_KEY],
> @@ -208,6 +259,23 @@ static int interp_ip2str(struct ulogd_pluginstance *pi)
>  	return ULOGD_IRET_OK;
>  }
>  
> +static int configure_ip2str(struct ulogd_pluginstance *upi,
> +			    struct ulogd_pluginstance_stack *stack)
> +{
> +	int ret = config_parse_file(upi->id, upi->config_kset);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	if (strlen(v6sep_ce(upi->config_kset).u.string) > 1)
> +		ulogd_log(ULOGD_NOTICE, "only one char v6 separator is allowed,"
> +			  " using: %c\n", *v6sep_ce(upi->config_kset).u.string);
> +	if (strlen(v4sep_ce(upi->config_kset).u.string) > 1)
> +		ulogd_log(ULOGD_NOTICE, "only one char v4 separator is allowed,"
> +			  " using: %c\n", *v4sep_ce(upi->config_kset).u.string);
> +	return ret;
> +}
> +
>  static struct ulogd_plugin ip2str_pluging = {
>  	.name = "IP2STR",
>  	.input = {
> @@ -220,7 +288,9 @@ static struct ulogd_plugin ip2str_pluging = {
>  		.num_keys = ARRAY_SIZE(ip2str_keys),
>  		.type = ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW,
>  		},
> +	.config_kset = &ip2str_config_kset,
>  	.interp = &interp_ip2str,
> +	.configure = &configure_ip2str,
>  	.version = VERSION,
>  };
>  

-- 
Eric Leblond <eric@regit.org>
Blog: https://home.regit.org/

[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

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

* Re: [ulogd RFC PATCH 0/2] introduce new string output plugin
  2014-03-29  4:23 [ulogd RFC PATCH 0/2] introduce new string output plugin Ken-ichirou MATSUZAWA
  2014-03-29  4:27 ` [ulogd RFC PATCH 1/2] sprint: introduce new " Ken-ichirou MATSUZAWA
  2014-03-29  4:29 ` [ulogd RFC PATCH 2/2] ip2str: introduce changable address separator Ken-ichirou MATSUZAWA
@ 2014-03-31 20:51 ` Eric Leblond
  2014-04-02 10:14   ` Ken-ichirou MATSUZAWA
  2 siblings, 1 reply; 9+ messages in thread
From: Eric Leblond @ 2014-03-31 20:51 UTC (permalink / raw)
  To: Ken-ichirou MATSUZAWA; +Cc: The netfilter developer mailinglist

Hello,

On Sat, 2014-03-29 at 13:23 +0900, Ken-ichirou MATSUZAWA wrote:
>   Hello,
>  
> I started to add new output plugin which work with graphite or statsd. Would you
> review these patches? I think this SPRINT output plugin has two issues and may
> I ask?
>  
> * multiple files for one plugin
>   I use flex and bison and need to separate source files. Is it acceptable?

I'm a bit reluctant to use flex and bison for that. How complicated
would it be to use a hand-made parsing here ?
 
> * use double calc operation
>   To tell statsd total amount bytes, I introduce calc operation (but '+' only now)
>   not using correct ULOGD_RET_ type but regard all of them as double.
>   It seems it is enough as of my purpose.

BR,
-- 
Eric Leblond <eric@regit.org>
Blog: https://home.regit.org/


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

* Re: [ulogd RFC PATCH 1/2] sprint: introduce new output plugin
  2014-03-29  4:27 ` [ulogd RFC PATCH 1/2] sprint: introduce new " Ken-ichirou MATSUZAWA
@ 2014-03-31 21:06   ` Eric Leblond
  0 siblings, 0 replies; 9+ messages in thread
From: Eric Leblond @ 2014-03-31 21:06 UTC (permalink / raw)
  To: Ken-ichirou MATSUZAWA; +Cc: The netfilter developer mailinglist

Hello,

Some comments on the form below. I will review later code if needed.

On Sat, 2014-03-29 at 13:27 +0900, Ken-ichirou MATSUZAWA wrote:
> This patch introduces a new string output plugin. The output string can be
> specified by "form" in config file. Format is consists of:
> 
>     key: struct ulogd_key name enclosed by <>, e.g. <orig.ip.saddr.str>
>     group: enclosed by () and separated by |, pick first one if exists.
>         (<orig.l4.dport>|<icmp.type>|unknown) means
>         pick orig.l4.dport value if exist, or icmp.type value. if both
>         of them do not exist, select "unknown" string.
>     +: add two key value if it can be
>     anything else: as is
> 
> meta character <>()|+\ needs to be escaped by \. Sink can be specified by
> "proto" and "dest" in config file. "proto" is either file, tcp and udp.
> "dest" is file name if "proto" is file, or port@address in tcp or udp.

I'm not ok with address format. Is it used in some other project ? It
would seem more natural to use a URI like syntax: tcp://address:port.

> More patch is needed to work, I think this will be suited for graphite and
> statsd to see whole of traffic.
> 
> Signed-off-by: Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
> ---
>  configure.ac                                |  15 +-
>  output/Makefile.am                          |   2 +-
>  output/sprint/Makefile.am                   |  21 ++
>  output/sprint/ulogd_output_SPRINT-parser.y  | 358 ++++++++++++++++++++
>  output/sprint/ulogd_output_SPRINT-scanner.l | 112 +++++++
>  output/sprint/ulogd_output_SPRINT.c         | 495 ++++++++++++++++++++++++++++
>  output/sprint/ulogd_output_SPRINT.h         |  45 +++
>  7 files changed, 1046 insertions(+), 2 deletions(-)
>  create mode 100644 output/sprint/Makefile.am
>  create mode 100644 output/sprint/ulogd_output_SPRINT-parser.y
>  create mode 100644 output/sprint/ulogd_output_SPRINT-scanner.l
>  create mode 100644 output/sprint/ulogd_output_SPRINT.c
>  create mode 100644 output/sprint/ulogd_output_SPRINT.h
> 
> diff --git a/configure.ac b/configure.ac
> index 544a256..8ab2b27 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -14,6 +14,8 @@ dnl Checks for programs.
>  AC_PROG_MAKE_SET
>  AC_PROG_CC
>  AC_PROG_INSTALL
> +AC_PROG_YACC
> +AC_PROG_LEX
>  AC_DISABLE_STATIC
>  AC_PROG_LIBTOOL
>  
> @@ -128,6 +130,16 @@ else
>  	enable_jansson="no"
>  fi
>  
> +AC_ARG_WITH([sprint], AS_HELP_STRING([--without-sprint], [Build without SPRINT output plugin [default=test]]))
> +AS_IF([test "x$with_sprint" != "xno"], [
> +if test "x$LEX" = "xflex" -a "x$YACC" = "xbison -y"; then
> +	enable_sprint="yes"
> +else
> +	enable_sprint="no"
> +fi
> +])
> +AM_CONDITIONAL([BUILD_SPRINT], [test "x$enable_sprint" = "xyes"])
> +
>  dnl AC_SUBST(DATABASE_DIR)
>  dnl AC_SUBST(DATABASE_LIB)
>  dnl AC_SUBST(DATABASE_LIB_DIR)
> @@ -147,7 +159,7 @@ AC_CONFIG_FILES(include/Makefile include/ulogd/Makefile include/libipulog/Makefi
>  	  input/sum/Makefile \
>  	  filter/Makefile filter/raw2packet/Makefile filter/packet2flow/Makefile \
>  	  output/Makefile output/pcap/Makefile output/mysql/Makefile output/pgsql/Makefile output/sqlite3/Makefile \
> -	  output/dbi/Makefile \
> +	  output/dbi/Makefile output/sprint/Makefile \
>  	  src/Makefile Makefile Rules.make)
>  AC_OUTPUT
>  
> @@ -164,5 +176,6 @@ Ulogd configuration:
>      SQLITE3 plugin:			${enable_sqlite3}
>      DBI plugin:				${enable_dbi}
>      JSON plugin:			${enable_jansson}
> +    SPRINT plugin:			${enable_sprint}
>  "
>  echo "You can now run 'make' and 'make install'"
> diff --git a/output/Makefile.am b/output/Makefile.am
> index ff851ad..7a39150 100644
> --- a/output/Makefile.am
> +++ b/output/Makefile.am
> @@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include ${LIBNETFILTER_ACCT_CFLAGS} \
>                ${LIBNETFILTER_CONNTRACK_CFLAGS} ${LIBNETFILTER_LOG_CFLAGS}
>  AM_CFLAGS = ${regular_CFLAGS}
>  
> -SUBDIRS= pcap mysql pgsql sqlite3 dbi
> +SUBDIRS= pcap mysql pgsql sqlite3 dbi sprint
>  
>  pkglib_LTLIBRARIES = ulogd_output_LOGEMU.la ulogd_output_SYSLOG.la \
>  			 ulogd_output_OPRINT.la ulogd_output_GPRINT.la \
> diff --git a/output/sprint/Makefile.am b/output/sprint/Makefile.am
> new file mode 100644
> index 0000000..90cbb34
> --- /dev/null
> +++ b/output/sprint/Makefile.am
> @@ -0,0 +1,21 @@
> +AM_CPPFLAGS = -I$(top_srcdir)/include
> +AM_CFLAGS = ${regular_CFLAGS}
> +AM_YFLAGS = -d
> +#AM_LFLAGS = --header-file=scanner.h
> +
> +if BUILD_SPRINT
> +
> +pkglib_LTLIBRARIES = ulogd_output_SPRINT.la
> +
> +ulogd_output_SPRINT_la_SOURCES = ulogd_output_SPRINT.c ulogd_output_SPRINT-scanner.l ulogd_output_SPRINT-parser.y
> +ulogd_output_SPRINT_la_LDFLAGS = -avoid-version -module
> +# ulogd_output_SPRINT_la_LFLAGS =  --header-file=scanner.h
> +
> +BUILT_SOURCES = ulogd_output_SPRINT-parser.h ulogd_output_SPRINT-parser.c \
> +		ulogd_output_SPRINT-scanner.h ulogd_output_SPRINT-scanner.c
> +CLEANFILES = $(BUILT_SOURCES)
> +
> +ulogd_output_SPRINT-scanner.h: ulogd_output_SPRINT-scanner.l
> +	$(LEX) -o /dev/null --header-file=$@ $<
> +
> +endif
> diff --git a/output/sprint/ulogd_output_SPRINT-parser.y b/output/sprint/ulogd_output_SPRINT-parser.y
> new file mode 100644
> index 0000000..83f5af9
> --- /dev/null
> +++ b/output/sprint/ulogd_output_SPRINT-parser.y
> @@ -0,0 +1,358 @@
> +/*

Some clarifications about who is holding copyright are needed before
code can be accepted.

> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2
> + *  as published by the Free Software Foundation.

BR,
-- 
Eric Leblond <eric@regit.org>
Blog: https://home.regit.org/


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

* Re: [ulogd RFC PATCH 0/2] introduce new string output plugin
  2014-03-31 20:51 ` [ulogd RFC PATCH 0/2] introduce new string output plugin Eric Leblond
@ 2014-04-02 10:14   ` Ken-ichirou MATSUZAWA
  2014-04-21  9:05     ` Eric Leblond
  0 siblings, 1 reply; 9+ messages in thread
From: Ken-ichirou MATSUZAWA @ 2014-04-02 10:14 UTC (permalink / raw)
  To: Eric Leblond; +Cc: netfilter-devel

 Hello Eric. 
 
Thank you for taking your time.

On Mon, Mar 31, 2014 at 10:51:19PM +0200, Eric Leblond wrote:
> I'm a bit reluctant to use flex and bison for that. How complicated
> would it be to use a hand-made parsing here ?

Thanks to you, I've got an interesting work and could find odd syntax. 
Here is a hand-made, which one would you like? It includes configurations
about URI like destination and address separator changing.

This patch introduces a new string output plugin. The output string can be
specified by "form" in config file. Format is consists of:

    key: struct ulogd_key name enclosed by <>, e.g. <orig.ip.saddr.str>
    group: enclosed by () and separated by |, pick first one if exists.
        (<orig.l4.dport>|<icmp.type>|unknown) means
        pick orig.l4.dport value if exist, or icmp.type value. if both
        of them do not exist, select "unknown" string.
    +: add two key value if it can be
    anything else: as is

meta character <>()|+\ needs to be escaped by \. Sink can be specified by "dest"
like URI syntax, proto://host:port, proto can be either file, tcp and udp,
e.g. dest="tcp://192.168.1.1:8125" for statsite. host is filename in case of
proto is file or stdout if dest is "file://"

'.' and ':' in ip address format can be changed by specifying "addrsep" for use
of graphite and statsd.

Signed-off-by Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
---
 output/Makefile.am           |    5 +-
 output/ulogd_output_SPRINT.c | 1049 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1053 insertions(+), 1 deletion(-)
 create mode 100644 output/ulogd_output_SPRINT.c

diff --git a/output/Makefile.am b/output/Makefile.am
index ff851ad..5f2cb0e 100644
--- a/output/Makefile.am
+++ b/output/Makefile.am
@@ -7,7 +7,7 @@ SUBDIRS= pcap mysql pgsql sqlite3 dbi
 pkglib_LTLIBRARIES = ulogd_output_LOGEMU.la ulogd_output_SYSLOG.la \
 			 ulogd_output_OPRINT.la ulogd_output_GPRINT.la \
 			 ulogd_output_NACCT.la ulogd_output_XML.la \
-			 ulogd_output_GRAPHITE.la
+			 ulogd_output_GRAPHITE.la ulogd_output_SPRINT.la
 
 if HAVE_JANSSON
 pkglib_LTLIBRARIES += ulogd_output_JSON.la
@@ -42,3 +42,6 @@ ulogd_output_JSON_la_SOURCES = ulogd_output_JSON.c
 ulogd_output_JSON_la_LIBADD  = ${libjansson_LIBS}
 ulogd_output_JSON_la_LDFLAGS = -avoid-version -module
 endif
+
+ulogd_output_SPRINT_la_SOURCES = ulogd_output_SPRINT.c
+ulogd_output_SPRINT_la_LDFLAGS = -avoid-version -module
diff --git a/output/ulogd_output_SPRINT.c b/output/ulogd_output_SPRINT.c
new file mode 100644
index 0000000..decea49
--- /dev/null
+++ b/output/ulogd_output_SPRINT.c
@@ -0,0 +1,1049 @@
+/* ulogd_output_SPRINT.c
+ *
+ * ulogd output target for sending value specified `form' in config.
+ *
+ * (C) 2014 by Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include <ulogd/ulogd.h>
+#include <ulogd/conffile.h>
+
+#define IPADDR_LENGTH 128
+
+#ifndef ULOGD_SPRINT_DEFAULT
+#define ULOGD_SPRINT_DEFAULT	"file:///var/log/ulogd.sprint"
+#endif
+
+struct sprint_priv {
+	int ofd;
+	struct llist_head form_head;
+};
+
+enum sprint_conf {
+	SPRINT_CONF_FORM = 0,
+	SPRINT_CONF_DEST,
+	SPRINT_CONF_ADDRSEP,
+	SPRINT_CONF_MAX
+};
+
+static struct config_keyset sprint_kset = {
+	.num_ces = SPRINT_CONF_MAX,
+	.ces = {
+		[SPRINT_CONF_FORM] = {
+			.key = "form",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+		},
+		[SPRINT_CONF_DEST] = {
+			.key = "dest",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = ULOGD_SPRINT_DEFAULT },
+		},
+		[SPRINT_CONF_ADDRSEP] = {
+			.key = "addrsep",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = "" },
+		},
+	},
+};
+
+enum sprint_node_type {
+	NODE_HEAD,
+	NODE_STRING,
+	NODE_KEY,
+	NODE_CONCAT,
+	NODE_GROUP,
+	NODE_KEYCALC,
+};
+
+enum {
+	TOKEN_STRING = 256,
+	TOKEN_KEY,
+	TOKEN_ERROR,
+};
+
+struct keyop {
+	int opcode;
+	struct node *l;
+	struct node *r;
+};
+
+struct node {
+	enum sprint_node_type type;
+	struct llist_head list;
+	union {
+		char *string;			/* NODE_STRING */
+		int kindex;			/* NODE_KEY */
+		struct llist_head group;	/* NODE_CONCAT, NODE_GROUP */
+		struct keyop keycalc;		/* NODE_KEYCALC */
+	};
+};
+
+struct keysym {
+	struct llist_head list;
+	char *name;
+};
+
+struct outform {
+	char *formstr;
+	char *cur;
+	char *prev;		/* for unput */
+	char *prev_lval;
+	struct llist_head form_head;
+	int num_keys;
+	struct llist_head keysyms;
+};
+
+static int unput(struct outform *scan)
+{
+	if (scan->cur == scan->prev)
+		return -1;
+	scan->cur = scan->prev;
+	if (scan->prev_lval)
+		free(scan->prev_lval);
+
+	return 0;
+}
+
+static int lval_term(struct outform *scan, int type,
+		     char **dst, char *from, char *to, char *prev)
+{
+	char c;
+
+	if (to == NULL) {
+		*dst = scan->prev_lval = strdup(from);
+	} else {
+		c = *to;
+		*to = '\0';
+		*dst = scan->prev_lval = strdup(from);
+		*to = c;
+	}
+	if (*dst == NULL) {
+		ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
+		return TOKEN_ERROR;
+	}
+	scan->prev = prev;
+	return type;
+}
+
+static int lval_char(struct outform *scan, int type, char *prev)
+{
+	scan->prev = prev;
+	scan->prev_lval = NULL;
+	return type;
+}
+
+static int lval_error(struct outform *scan, char **dst, char *msg)
+{
+	/* *dst = msg */
+	ulogd_log(ULOGD_ERROR, "%s around %d\n",
+		  msg, scan->cur - scan->formstr);
+	scan->prev_lval = NULL;
+	return TOKEN_ERROR;
+}
+
+static int lex_key(struct outform *scan, char **lval)
+{
+	char c, *start;
+
+	for (start = scan->cur; (c = *scan->cur) != '\0'; scan->cur++) {
+		if (c == '>') {
+			scan->cur++;
+			return lval_term(scan, TOKEN_KEY, lval,
+					 start - 1, scan->cur - 1, start - 2);
+		}
+		if (!isascii(c) && !isalnum(c)
+		    && c != '.' && c != '_' && c != '-')
+			return lval_error(scan, lval,
+					  "invalid key char");
+	}
+
+	return lval_error(scan, lval, "EOF in key");
+}
+
+static int lex_escape(struct outform *scan, char **lval)
+{
+	char sbuf[2];
+	char c = *scan->cur++;
+
+	switch (c) {
+	case 'n':
+		return lval_term(scan, TOKEN_STRING, lval,
+				 "\n", NULL, scan->cur - 2);
+	case 't':
+		return lval_term(scan, TOKEN_STRING, lval,
+				 "\t", NULL, scan->cur - 2);
+	case '\\':
+	case '<':
+	case '>':
+	case '(':
+	case ')':
+	case '|':
+	case '+':
+		snprintf(sbuf, 2, "%c", c);
+		return lval_term(scan, TOKEN_STRING, lval,
+				 sbuf, NULL, scan->cur - 2);
+	case '\0':
+		return lval_error(scan, lval, "EOF in escape");
+	default:
+		return lval_error(scan, lval,
+				  "invalid escape char");
+	}
+}
+
+static int lex(struct outform *scan, char **lval)
+{
+	char c, *start = scan->cur;
+	
+	while ((c = *scan->cur) != '\0') {
+		switch(c) {
+		case '\\':
+			if (scan->cur != start)
+				return lval_term(scan, TOKEN_STRING, lval,
+						 start, scan->cur, start);
+			scan->cur++;
+			return lex_escape(scan, lval);
+			break;
+		case '<':
+			if (scan->cur != start)
+				return lval_term(scan, TOKEN_STRING, lval,
+						 start, scan->cur, start);
+			scan->cur++;
+			if (!isascii(*scan->cur) && !isalpha(*scan->cur))
+				return lval_error(scan, lval, 
+						  "invalid key start");
+			scan->cur++; /* consume key's first char */
+			return lex_key(scan, lval);
+			break;
+		case '>':
+			return lval_error(scan, lval,
+					  "unexpected key end");
+		case ')':
+		case '(':
+		case '|':
+			if (scan->cur != start)
+				return lval_term(scan, TOKEN_STRING, lval,
+						 start, scan->cur, start);
+			scan->cur++;
+			return lval_char(scan, c, start);
+		case '+':
+			do
+				scan->cur++;
+			while (*scan->cur == ' ' || *scan->cur == '\t');
+			return lval_char(scan, c, start);
+		case ' ':
+		case '\t':
+		default:
+			scan->cur++;
+			break;
+		}
+	}
+
+	if (scan->cur != start)
+		return lval_term(scan, TOKEN_STRING, lval,
+				 start, scan->cur, start);
+
+	return 0;
+}
+
+static void *sprint_calloc(size_t len)
+{
+	void *p = calloc(len, 1);
+	if (p == NULL) {
+		ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
+		return NULL;
+	}
+	return p;
+}
+
+static struct node *sprint_string_node(char *string)
+{
+	struct node *node = sprint_calloc(sizeof(struct node));
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_STRING;
+	node->string = string;
+
+	return node;
+}
+
+static int sprint_key_index(struct outform *form, char *name)
+{
+	struct keysym *cur;
+	int i = 0;
+
+	llist_for_each_entry(cur, &form->keysyms, list) {
+		if (!strcmp(cur->name, name))
+			return i;
+		i++;
+	}
+
+	return -1;
+}
+
+static struct node *sprint_key_node(struct outform *form, char *name)
+{
+	struct node *node;
+	struct keysym *sym;
+
+	if (strlen(name) > ULOGD_MAX_KEYLEN) {
+		ulogd_log(ULOGD_ERROR, "too long key: %s\n", name);
+		return NULL;
+	}
+
+	node = sprint_calloc(sizeof(struct node));
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_KEY;
+	node->kindex = sprint_key_index(form, name);
+	if (node->kindex < 0) {
+		sym = sprint_calloc(sizeof(struct keysym));
+		if (sym == NULL) {
+			free(node);
+			return NULL;
+		}
+		sym->name = name;
+		node->kindex = form->num_keys++;
+		llist_add_tail(&sym->list, &form->keysyms);
+	}
+
+	return node;
+}
+
+static struct node *sprint_list_node(enum sprint_node_type type,
+				     struct node *term)
+{
+	struct node *node = sprint_calloc(sizeof(struct node));
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = type;
+	INIT_LLIST_HEAD(&node->group);
+	llist_add_tail(&term->list, &node->group);
+	return node;
+}
+
+static struct node *sprint_keycalc_node(int opcode,
+					struct node *l, struct node *r)
+{
+	struct node *node = sprint_calloc(sizeof(struct node));
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_KEYCALC;
+	node->keycalc.opcode = opcode;
+	node->keycalc.l = l;
+	node->keycalc.r = r;
+
+	return node;
+}
+
+static void sprint_free_nodes(struct llist_head *nodes);
+
+static void sprint_free_node(struct node *node)
+{
+	switch (node->type) {
+	case NODE_STRING:
+		free(node->string);
+		break;
+	case NODE_KEY:
+		break;
+	case NODE_GROUP:
+	case NODE_CONCAT:
+		sprint_free_nodes(&node->group);
+		break;
+	case NODE_KEYCALC:
+		sprint_free_node(node->keycalc.l);
+		sprint_free_node(node->keycalc.r);
+		break;
+	default:
+		ulogd_log(ULOGD_ERROR, "unknown node: %p"
+			  " type: %d\n", node, node->type);
+		break;
+	}
+}
+
+static void sprint_free_nodes(struct llist_head *nodes)
+{
+	struct node *node, *nnode;
+
+	llist_for_each_entry_safe(node, nnode, nodes, list) {
+		sprint_free_node(node);
+		llist_del(&node->list);
+		free(node);
+	}
+}
+
+static void sprint_free_keysyms(struct llist_head *head)
+{
+	struct keysym *sym, *nsym;
+
+	llist_for_each_entry_safe(sym, nsym, head, list) {
+		llist_del(&sym->list);
+		free(sym->name);
+		free(sym);
+	}
+}
+
+/*
+ * form		:=	part*
+ * part		:=	concat | '(' selector ')'
+ * selector	:=	concat ('|' concat)*
+ * concat	:=	term term*
+ * term		:=	STRING | KEY ('+' KEY)*
+ */
+static struct node *term(struct outform *form)
+{
+	char *lval;
+	struct node *nl, *nr;
+	int opcode, ret = lex(form, &lval);
+
+	if (ret == TOKEN_ERROR)
+		return NULL;
+	if (ret == TOKEN_STRING)
+		return sprint_string_node(lval);
+	if (ret != TOKEN_KEY) {
+		ulogd_log(ULOGD_ERROR,
+			  "form char: %d, invalid meta char: %c\n",
+			  form->cur - form->formstr, ret);
+		return NULL;
+	}
+	
+	/* ret == TOKEN_KEY */
+	nl = sprint_key_node(form, lval);
+	if (nl == NULL)
+		return NULL;
+
+	opcode = lex(form, &lval);
+	if (opcode == TOKEN_ERROR)
+		return NULL;
+	if (opcode != '+') {
+		if (opcode != '\0')
+			unput(form);
+		return nl;
+	}
+
+	ret = lex(form, &lval);
+	if (ret == TOKEN_ERROR)
+		return NULL;
+	if (ret != TOKEN_KEY) {
+		ulogd_log(ULOGD_ERROR,
+			  "form char: %d, right operand must be a KEY\n",
+			  form->cur - form->formstr);
+		return NULL;
+	}
+	unput(form);
+
+	nr = term(form);
+	if (nr == NULL)
+		return NULL;
+
+	return sprint_keycalc_node(opcode, nl, nr);
+}
+
+static struct node *concat(struct outform *form)
+{
+	char *lval;
+	int ret;
+	struct node *terms, *last, *n = term(form);
+
+	if (n == NULL)
+		return NULL;
+
+	terms = sprint_list_node(NODE_CONCAT, n);
+	if (terms == NULL)
+		return NULL;
+
+	ret = lex(form, &lval);
+	while (ret == TOKEN_STRING || ret == TOKEN_KEY) {
+		unput(form);
+		n = term(form);
+		if (n == NULL)
+			return NULL;
+		last = llist_entry(terms->group.prev, struct node, list);
+		if (last->type == NODE_STRING && n->type == NODE_STRING) {
+			/* a little bit optimize */
+			int len1 = strlen(last->string),
+				len2 = strlen(n->string);
+
+			last->string = realloc(last->string, len1 + len2);
+			if (last->string == NULL) {
+				ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
+				return NULL;
+			}
+			strncpy(last->string + len1, n->string, len2);
+			sprint_free_node(n);
+			free(n);
+		} else {
+			llist_add_tail(&n->list, &terms->group);
+		}
+		ret = lex(form, &lval);
+	}
+	if (ret != '\0')
+		unput(form);
+
+	return terms;
+}
+
+static struct node *selector(struct outform *form)
+{
+	int ret;
+	char *lval;
+	struct node *concats, *n = concat(form);
+
+	if (n == NULL)
+		return NULL;
+
+	concats = sprint_list_node(NODE_GROUP, n);
+	if (concats == NULL)
+		return NULL;
+
+	while ((ret = lex(form, &lval)) == '|') {
+		n = concat(form);
+		if (n == NULL)
+			return NULL;
+		llist_add_tail(&n->list, &concats->group);
+	}
+	if (ret != '\0')
+		unput(form);
+
+	return concats;
+}
+
+static struct node *part(struct outform *form)
+{
+	char *lval;
+	struct node *n;
+	int ret = lex(form, &lval);
+
+	if (ret == TOKEN_ERROR)
+		return NULL;
+
+	if (ret == '(') {
+		n = selector(form);
+		ret = lex(form, &lval);
+		if (ret != ')') {
+			ulogd_log(ULOGD_ERROR,
+				  "form char: %d, no right parenthesis\n",
+				  form->cur - form->formstr);
+			return NULL;
+		}
+	} else {
+		unput(form);
+		n = concat(form);
+	}
+
+	return n;
+}
+
+static int parse_form(struct outform *form)
+{
+	struct node *n;
+
+	while (*form->cur) {
+		n = part(form);
+		if (n == NULL)
+			return -1;
+		llist_add_tail(&n->list, &form->form_head);
+	}
+
+	return 0;
+}
+
+static void init_outform(struct outform *form, char *s)
+{
+	form->formstr = form->cur = form->prev = s;
+	INIT_LLIST_HEAD(&form->form_head);
+	form->num_keys = 1; /* 0 is reserved for oob.family */
+	INIT_LLIST_HEAD(&form->keysyms);
+}
+
+static int open_connect_descriptor(struct ulogd_pluginstance *upi)
+{
+	char *proto, *host, *port;
+	struct addrinfo hint, *result, *rp;
+	int ret, fd;
+
+	proto = upi->config_kset->ces[SPRINT_CONF_DEST].u.string;
+	host = strchr(proto, ':');
+	if (host == NULL) {
+		ulogd_log(ULOGD_ERROR, "invalid dest\n");
+		return -1;
+	}
+	*host++ = '\0';
+	if (*host++ != '/') {
+		ulogd_log(ULOGD_ERROR, "invalid dest\n");
+		return -1;
+	}
+	if (*host++ != '/') {
+		ulogd_log(ULOGD_ERROR, "invalid dest\n");
+		return -1;
+	}
+
+	/* file */
+	if (!strcasecmp(proto, "file")) {
+		if (strlen(host) == 0)
+			return STDOUT_FILENO;
+		return open(host, O_CREAT|O_WRONLY|O_APPEND);
+	}
+
+	/* socket */
+	port = strrchr(host, ':');
+	if (port == NULL) {
+		ulogd_log(ULOGD_ERROR, "no port in dest\n");
+		return -1;
+	}
+	*port++ = '\0';
+
+	memset(&hint, 0, sizeof(struct addrinfo));
+	hint.ai_family = AF_UNSPEC;
+	if (!strcasecmp(proto, "udp")) {
+		hint.ai_socktype = SOCK_DGRAM;
+		hint.ai_protocol = IPPROTO_UDP;
+	} else if (!strcasecmp(proto, "tcp")) {
+		hint.ai_socktype = SOCK_STREAM;
+		hint.ai_protocol = IPPROTO_TCP;
+	} else {
+		ulogd_log(ULOGD_ERROR, "unknown protocol `%s'\n",
+			  proto);
+		return -1;
+	}
+
+	ret = getaddrinfo(host, port, &hint, &result);
+	if (ret != 0) {
+		ulogd_log(ULOGD_ERROR, "can't resolve host/service: %s\n",
+			  gai_strerror(ret));
+		if (ret != EAI_SYSTEM)
+			errno = EINVAL;
+		return -1;
+	}
+
+	for (rp = result; rp != NULL; rp = rp->ai_next) {
+		int on = 1;
+
+		fd = socket(rp->ai_family, rp->ai_socktype,
+			     rp->ai_protocol);
+		if (fd == -1)
+			continue;
+
+		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+			   (void *)&on, sizeof(on));
+		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
+			break;
+	}
+	freeaddrinfo(result);
+
+	if (rp == NULL) {
+		ulogd_log(ULOGD_ERROR, "could not connect\n");
+		return -1;
+	}
+
+	return fd;
+}
+
+static double sprint_key_calc(struct ulogd_key *keys, struct node *node,
+			      bool *is_valid)
+{
+	*is_valid = false;
+	if (node->type == NODE_KEY) {
+		struct ulogd_key *key = keys[node->kindex].u.source;
+		if (!(key->flags & ULOGD_RETF_VALID))
+			return 0.0;
+
+		switch (key->type) {
+		case ULOGD_RET_BOOL:
+		case ULOGD_RET_INT8:
+		case ULOGD_RET_INT16:
+		case ULOGD_RET_INT32:
+			*is_valid = true;
+			return (double)key->u.value.i32;
+			break;
+		case ULOGD_RET_UINT8:
+		case ULOGD_RET_UINT16:
+		case ULOGD_RET_UINT32:
+		case ULOGD_RET_UINT64:
+			*is_valid = true;
+			return (double)key->u.value.ui64;
+			break;
+		default:
+			ulogd_log(ULOGD_INFO, "could not calc"
+				  " key: %s type: %d\n", key->name, key->type);
+		}
+	} else if (node->type == NODE_KEYCALC) {
+		bool lvalid, rvalid;
+		double lval = sprint_key_calc(keys, node->keycalc.l, &lvalid),
+			rval = sprint_key_calc(keys, node->keycalc.r, &rvalid);
+
+		if (!lvalid || !rvalid)
+			return 0.0; /* without setting is_valid */
+
+		switch (node->keycalc.opcode) {
+		case '+':
+			*is_valid = true;
+			return lval + rval;
+			break;
+		default:
+			ulogd_log(ULOGD_NOTICE, "unknown opcode: %c\n",
+				  node->keycalc.opcode);
+			break;
+		}
+	} else {
+		ulogd_log(ULOGD_NOTICE, "invalid node type in keycalc: %d\n",
+			  node->type);
+	}
+
+	return 0.0; /* without setting is_valid */
+}
+
+static int sprint_keycalc_puts(char *buf, size_t size, bool in_group,
+			       struct ulogd_key *keys, struct node *node)
+{
+	bool is_valid;
+	double ret = sprint_key_calc(keys, node, &is_valid);
+
+	if (!is_valid && in_group)
+		return 0;
+
+	return snprintf(buf, size, "%.0f", ret);
+}
+
+static int sprint_key_puts(char *buf, size_t size, bool in_group,
+			   struct ulogd_key *keys, struct node *node,
+			   int addrsep)
+{
+	struct ulogd_key *key = keys[node->kindex].u.source;
+	char family, *p;
+	int i;
+
+	if (!(key->flags & ULOGD_RETF_VALID)) {
+		if (!in_group) {
+			ulogd_log(ULOGD_INFO, "no key value: %s\n", key->name);
+			return printf("<>");
+		}
+		return 0;
+	}
+
+	switch (key->type) {
+	case ULOGD_RET_STRING:
+		return snprintf(buf, size, "%s", (char *)key->u.value.ptr);
+		break;
+	case ULOGD_RET_BOOL:
+	case ULOGD_RET_INT8:
+	case ULOGD_RET_INT16:
+	case ULOGD_RET_INT32:
+		return snprintf(buf, size, "%d", key->u.value.i32);
+		break;
+	case ULOGD_RET_UINT8:
+	case ULOGD_RET_UINT16:
+	case ULOGD_RET_UINT32:
+	case ULOGD_RET_UINT64:
+		return snprintf(buf, size, "%" PRIu64, key->u.value.ui64);
+		break;
+	case ULOGD_RET_IPADDR:
+		family = ikey_get_u8(keys);
+		if (family == AF_INET6) {
+			inet_ntop(AF_INET6, ikey_get_u128(&keys[node->kindex]),
+				  buf, size);
+			i = ':';
+		} else if (family == AF_INET) {
+			u_int32_t ip = ikey_get_u32(&keys[node->kindex]);
+			inet_ntop(AF_INET, &ip, buf, size);
+			i = '.';
+		} else {
+			ulogd_log(ULOGD_ERROR,
+				  "unknown address family: %d\n", family);
+			return 0;
+		}
+		if (addrsep)
+			for (p = strchr(buf, i); p; p = strchr(p + 1, i))
+				*p = addrsep;
+		for (i = 0, p = buf; *p != '\0'; p++, i++)
+			;
+		return i;
+	default:
+		ulogd_log(ULOGD_INFO, "could not interpret"
+			  " key: %s, type: %d\n", key->name, key->type);
+		break;
+	}
+	return 0; /* default */
+}
+
+static int sprint_term_puts(char *buf, size_t size, bool in_group,
+			    struct ulogd_key *keys, struct node *node,
+			    int addrsep)
+{
+	struct node *n;
+	int ret;
+	size_t len = 0;
+
+	switch (node->type) {
+	case NODE_KEY:
+		return sprint_key_puts(buf, size, in_group, keys, node,
+				       addrsep);
+		break;
+	case NODE_STRING:
+		return snprintf(buf, size, "%s", node->string);
+		break;
+	case NODE_KEYCALC:
+		return sprint_keycalc_puts(buf, size, in_group, keys, node);
+		break;
+	case NODE_CONCAT:
+		llist_for_each_entry(n, &node->group, list) {
+			ret = sprint_term_puts(buf + len, size - len,
+					       in_group, keys, n, addrsep);
+			if ((n->type == NODE_KEY || n->type == NODE_KEYCALC)
+			    && ret <= 0) {
+				/* no key value found in a group */
+				return 0;
+			}
+			len += ret;
+			if (len >= size) {
+				ulogd_log(ULOGD_NOTICE, "exceeds bufsize\n");
+				return len;
+			}
+		}
+		return len;
+		break;
+	default:
+		ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
+			  node->type);
+		break;
+	}
+
+	return 0; /* unknown node type */
+}
+
+static int sprint_group_puts(char *buf, size_t size, struct ulogd_key *keys,
+			     struct node *node, int addrsep)
+{
+	int ret;
+	struct node *n;
+
+	llist_for_each_entry(n, &node->group, list) {
+		ret = sprint_term_puts(buf, size, true, keys, n, addrsep);
+		if (ret > 0) /* put first valid value and return */
+			return ret;
+	}
+
+	ulogd_log(ULOGD_NOTICE, "no value found in group\n");
+	return snprintf(buf, size, "()");
+}
+
+static int sprint_interp(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
+	struct node *cur;
+	char buf[4096];
+	int rem = sizeof(buf) - 1, len = 0, ret;
+	int addrsep = *upi->config_kset->ces[SPRINT_CONF_ADDRSEP].u.string;
+
+	llist_for_each_entry(cur, &sp->form_head, list) {
+		switch (cur->type) {
+		case NODE_KEY:
+		case NODE_STRING:
+		case NODE_CONCAT:
+		case NODE_KEYCALC:
+			len += sprint_term_puts(buf + len, rem, false,
+						upi->input.keys, cur, addrsep);
+			break;
+		case NODE_GROUP:
+			len += sprint_group_puts(buf + len, rem,
+						 upi->input.keys, cur, addrsep);
+			break;
+		default:
+			ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
+				  cur->type);
+		}
+		rem -= len;
+		if (rem <= 0) {
+			ulogd_log(ULOGD_NOTICE,
+				  "sprint_term_puts exceeds bufsize\n");
+			len = sizeof(buf);
+			break;
+		}
+	}
+
+	ret = write(sp->ofd, buf, len);
+	if (ret != len) {
+		buf[len] = '\0';
+		ulogd_log(ULOGD_ERROR, "Failure sending message: %s\n", buf);
+		if (ret == -1) {
+			sp->ofd = open_connect_descriptor(upi);
+			if (sp->ofd == -1)
+				return ULOGD_IRET_ERR;
+		}
+	}
+	return ULOGD_IRET_OK;
+}
+
+static void sighup_handler_print(struct ulogd_pluginstance *upi, int signal)
+{
+	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
+	int old = sp->ofd;
+
+	switch (signal) {
+	case SIGHUP:
+		ulogd_log(ULOGD_NOTICE, "SPRINT: reopening logfile\n");
+		sp->ofd = open_connect_descriptor(upi);
+		if (sp->ofd == -1) {
+			ulogd_log(ULOGD_ERROR, "can't open SPRINT "
+					       "log file: %s\n",
+				  strerror(errno));
+			sp->ofd = old;
+		} else {
+			close(old);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int sprint_configure_form(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *priv = (struct sprint_priv *)&upi->private;
+	struct keysym *sym, *nsym;
+	struct ulogd_key *ikey;
+	int ret;
+	struct outform form;
+
+	init_outform(&form,
+		     upi->config_kset->ces[SPRINT_CONF_FORM].u.string);
+
+	ret = parse_form(&form);
+	if (ret == -1) {
+		/* parser error, already logged */
+		sprint_free_nodes(&form.form_head);
+		sprint_free_keysyms(&form.keysyms);
+		return ULOGD_IRET_ERR;
+	}
+
+	llist_add(&priv->form_head, &form.form_head);
+	llist_del(&form.form_head);
+
+	ulogd_log(ULOGD_DEBUG, "allocating %u input keys for SPRINT\n",
+		  form.num_keys);
+	upi->input.keys = ikey = calloc(sizeof(struct ulogd_key),
+					form.num_keys);
+
+	if (!upi->input.keys)
+		return -ENOMEM;
+
+	/* reserve for putting ULOGD_RET_IPADDR */
+	ikey->type = ULOGD_RET_UINT8;
+	ikey->flags = ULOGD_RETF_NONE;
+	strcpy(ikey->name, "oob.family");
+	ikey++;
+
+	/* create input keys from key symbol list created by form parsing */
+	llist_for_each_entry_safe(sym, nsym, &form.keysyms, list) {
+		ikey->flags = ULOGD_RETF_NONE;
+		strncpy(ikey->name, sym->name, strlen(sym->name));
+		free(sym->name);
+		free(sym);
+		ikey++;
+	}
+	upi->input.num_keys = form.num_keys;
+
+	return ret;
+}
+
+static int sprint_configure(struct ulogd_pluginstance *upi,
+			    struct ulogd_pluginstance_stack *stack)
+{
+	int ret;
+
+	ret = config_parse_file(upi->id, upi->config_kset);
+	if (ret < 0)
+		return ret;
+
+	ret = sprint_configure_form(upi);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int sprint_init(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *) &upi->private;
+
+	sp->ofd = open_connect_descriptor(upi);
+	if (sp->ofd < 0) {
+		ulogd_log(ULOGD_FATAL, "can't open SPRINT destination: %s\n",
+			  strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int sprint_fini(struct ulogd_pluginstance *pi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *) &pi->private;
+
+	if (sp->ofd != STDOUT_FILENO)
+		close(sp->ofd);
+
+	return 0;
+}
+
+static struct ulogd_plugin sprint_plugin = {
+	.name = "SPRINT",
+	.input = {
+		.type	= ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW | ULOGD_DTYPE_SUM,
+	},
+	.output = {
+		.type	= ULOGD_DTYPE_SINK,
+	},
+	.configure	= &sprint_configure,
+	.interp		= &sprint_interp,
+	.start		= &sprint_init,
+	.stop		= &sprint_fini,
+	.signal		= &sighup_handler_print,
+	.config_kset	= &sprint_kset,
+	.version	= VERSION,
+};
+
+void __attribute__ ((constructor)) init(void);
+
+void init(void)
+{
+	ulogd_register_plugin(&sprint_plugin);
+}
-- 
1.8.5.3


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

* Re: [ulogd RFC PATCH 0/2] introduce new string output plugin
  2014-04-02 10:14   ` Ken-ichirou MATSUZAWA
@ 2014-04-21  9:05     ` Eric Leblond
  2014-04-22 11:51       ` [ulogd RFC PATCH 0/2 resend] " Ken-ichirou MATSUZAWA
  0 siblings, 1 reply; 9+ messages in thread
From: Eric Leblond @ 2014-04-21  9:05 UTC (permalink / raw)
  To: Ken-ichirou MATSUZAWA; +Cc: netfilter-devel

Hello,

On Wed, 2014-04-02 at 19:14 +0900, Ken-ichirou MATSUZAWA wrote:
>  Hello Eric. 
>  
> Thank you for taking your time.
> 
> On Mon, Mar 31, 2014 at 10:51:19PM +0200, Eric Leblond wrote:
> > I'm a bit reluctant to use flex and bison for that. How complicated
> > would it be to use a hand-made parsing here ?

I go for that one. The patch size is similar and I don't think we will
have a lot of evolution on the format.

Can you rebase and resubmit with a clean message ?

BR,
--
Eric


> Thanks to you, I've got an interesting work and could find odd syntax. 
> Here is a hand-made, which one would you like? It includes configurations
> about URI like destination and address separator changing.
> 
> This patch introduces a new string output plugin. The output string can be
> specified by "form" in config file. Format is consists of:
> 
>     key: struct ulogd_key name enclosed by <>, e.g. <orig.ip.saddr.str>
>     group: enclosed by () and separated by |, pick first one if exists.
>         (<orig.l4.dport>|<icmp.type>|unknown) means
>         pick orig.l4.dport value if exist, or icmp.type value. if both
>         of them do not exist, select "unknown" string.
>     +: add two key value if it can be
>     anything else: as is
> 
> meta character <>()|+\ needs to be escaped by \. Sink can be specified by "dest"
> like URI syntax, proto://host:port, proto can be either file, tcp and udp,
> e.g. dest="tcp://192.168.1.1:8125" for statsite. host is filename in case of
> proto is file or stdout if dest is "file://"
> 
> '.' and ':' in ip address format can be changed by specifying "addrsep" for use
> of graphite and statsd.
> 
> Signed-off-by Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
> ---
>  output/Makefile.am           |    5 +-
>  output/ulogd_output_SPRINT.c | 1049 ++++++++++++++++++++++++++++++++++++++++++
>  2 files changed, 1053 insertions(+), 1 deletion(-)
>  create mode 100644 output/ulogd_output_SPRINT.c
> 
> diff --git a/output/Makefile.am b/output/Makefile.am
> index ff851ad..5f2cb0e 100644
> --- a/output/Makefile.am
> +++ b/output/Makefile.am
> @@ -7,7 +7,7 @@ SUBDIRS= pcap mysql pgsql sqlite3 dbi
>  pkglib_LTLIBRARIES = ulogd_output_LOGEMU.la ulogd_output_SYSLOG.la \
>  			 ulogd_output_OPRINT.la ulogd_output_GPRINT.la \
>  			 ulogd_output_NACCT.la ulogd_output_XML.la \
> -			 ulogd_output_GRAPHITE.la
> +			 ulogd_output_GRAPHITE.la ulogd_output_SPRINT.la
>  
>  if HAVE_JANSSON
>  pkglib_LTLIBRARIES += ulogd_output_JSON.la
> @@ -42,3 +42,6 @@ ulogd_output_JSON_la_SOURCES = ulogd_output_JSON.c
>  ulogd_output_JSON_la_LIBADD  = ${libjansson_LIBS}
>  ulogd_output_JSON_la_LDFLAGS = -avoid-version -module
>  endif
> +
> +ulogd_output_SPRINT_la_SOURCES = ulogd_output_SPRINT.c
> +ulogd_output_SPRINT_la_LDFLAGS = -avoid-version -module
> diff --git a/output/ulogd_output_SPRINT.c b/output/ulogd_output_SPRINT.c
> new file mode 100644
> index 0000000..decea49
> --- /dev/null
> +++ b/output/ulogd_output_SPRINT.c
> @@ -0,0 +1,1049 @@
> +/* ulogd_output_SPRINT.c
> + *
> + * ulogd output target for sending value specified `form' in config.
> + *
> + * (C) 2014 by Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License version 2
> + *  as published by the Free Software Foundation.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; if not, write to the Free Software
> + *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <stdarg.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <sys/socket.h>
> +#include <fcntl.h>
> +#include <netdb.h>
> +#include <arpa/inet.h>
> +#include <ctype.h>
> +
> +#include <ulogd/ulogd.h>
> +#include <ulogd/conffile.h>
> +
> +#define IPADDR_LENGTH 128
> +
> +#ifndef ULOGD_SPRINT_DEFAULT
> +#define ULOGD_SPRINT_DEFAULT	"file:///var/log/ulogd.sprint"
> +#endif
> +
> +struct sprint_priv {
> +	int ofd;
> +	struct llist_head form_head;
> +};
> +
> +enum sprint_conf {
> +	SPRINT_CONF_FORM = 0,
> +	SPRINT_CONF_DEST,
> +	SPRINT_CONF_ADDRSEP,
> +	SPRINT_CONF_MAX
> +};
> +
> +static struct config_keyset sprint_kset = {
> +	.num_ces = SPRINT_CONF_MAX,
> +	.ces = {
> +		[SPRINT_CONF_FORM] = {
> +			.key = "form",
> +			.type = CONFIG_TYPE_STRING,
> +			.options = CONFIG_OPT_NONE,
> +		},
> +		[SPRINT_CONF_DEST] = {
> +			.key = "dest",
> +			.type = CONFIG_TYPE_STRING,
> +			.options = CONFIG_OPT_NONE,
> +			.u = {.string = ULOGD_SPRINT_DEFAULT },
> +		},
> +		[SPRINT_CONF_ADDRSEP] = {
> +			.key = "addrsep",
> +			.type = CONFIG_TYPE_STRING,
> +			.options = CONFIG_OPT_NONE,
> +			.u = {.string = "" },
> +		},
> +	},
> +};
> +
> +enum sprint_node_type {
> +	NODE_HEAD,
> +	NODE_STRING,
> +	NODE_KEY,
> +	NODE_CONCAT,
> +	NODE_GROUP,
> +	NODE_KEYCALC,
> +};
> +
> +enum {
> +	TOKEN_STRING = 256,
> +	TOKEN_KEY,
> +	TOKEN_ERROR,
> +};
> +
> +struct keyop {
> +	int opcode;
> +	struct node *l;
> +	struct node *r;
> +};
> +
> +struct node {
> +	enum sprint_node_type type;
> +	struct llist_head list;
> +	union {
> +		char *string;			/* NODE_STRING */
> +		int kindex;			/* NODE_KEY */
> +		struct llist_head group;	/* NODE_CONCAT, NODE_GROUP */
> +		struct keyop keycalc;		/* NODE_KEYCALC */
> +	};
> +};
> +
> +struct keysym {
> +	struct llist_head list;
> +	char *name;
> +};
> +
> +struct outform {
> +	char *formstr;
> +	char *cur;
> +	char *prev;		/* for unput */
> +	char *prev_lval;
> +	struct llist_head form_head;
> +	int num_keys;
> +	struct llist_head keysyms;
> +};
> +
> +static int unput(struct outform *scan)
> +{
> +	if (scan->cur == scan->prev)
> +		return -1;
> +	scan->cur = scan->prev;
> +	if (scan->prev_lval)
> +		free(scan->prev_lval);
> +
> +	return 0;
> +}
> +
> +static int lval_term(struct outform *scan, int type,
> +		     char **dst, char *from, char *to, char *prev)
> +{
> +	char c;
> +
> +	if (to == NULL) {
> +		*dst = scan->prev_lval = strdup(from);
> +	} else {
> +		c = *to;
> +		*to = '\0';
> +		*dst = scan->prev_lval = strdup(from);
> +		*to = c;
> +	}
> +	if (*dst == NULL) {
> +		ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
> +		return TOKEN_ERROR;
> +	}
> +	scan->prev = prev;
> +	return type;
> +}
> +
> +static int lval_char(struct outform *scan, int type, char *prev)
> +{
> +	scan->prev = prev;
> +	scan->prev_lval = NULL;
> +	return type;
> +}
> +
> +static int lval_error(struct outform *scan, char **dst, char *msg)
> +{
> +	/* *dst = msg */
> +	ulogd_log(ULOGD_ERROR, "%s around %d\n",
> +		  msg, scan->cur - scan->formstr);
> +	scan->prev_lval = NULL;
> +	return TOKEN_ERROR;
> +}
> +
> +static int lex_key(struct outform *scan, char **lval)
> +{
> +	char c, *start;
> +
> +	for (start = scan->cur; (c = *scan->cur) != '\0'; scan->cur++) {
> +		if (c == '>') {
> +			scan->cur++;
> +			return lval_term(scan, TOKEN_KEY, lval,
> +					 start - 1, scan->cur - 1, start - 2);
> +		}
> +		if (!isascii(c) && !isalnum(c)
> +		    && c != '.' && c != '_' && c != '-')
> +			return lval_error(scan, lval,
> +					  "invalid key char");
> +	}
> +
> +	return lval_error(scan, lval, "EOF in key");
> +}
> +
> +static int lex_escape(struct outform *scan, char **lval)
> +{
> +	char sbuf[2];
> +	char c = *scan->cur++;
> +
> +	switch (c) {
> +	case 'n':
> +		return lval_term(scan, TOKEN_STRING, lval,
> +				 "\n", NULL, scan->cur - 2);
> +	case 't':
> +		return lval_term(scan, TOKEN_STRING, lval,
> +				 "\t", NULL, scan->cur - 2);
> +	case '\\':
> +	case '<':
> +	case '>':
> +	case '(':
> +	case ')':
> +	case '|':
> +	case '+':
> +		snprintf(sbuf, 2, "%c", c);
> +		return lval_term(scan, TOKEN_STRING, lval,
> +				 sbuf, NULL, scan->cur - 2);
> +	case '\0':
> +		return lval_error(scan, lval, "EOF in escape");
> +	default:
> +		return lval_error(scan, lval,
> +				  "invalid escape char");
> +	}
> +}
> +
> +static int lex(struct outform *scan, char **lval)
> +{
> +	char c, *start = scan->cur;
> +	
> +	while ((c = *scan->cur) != '\0') {
> +		switch(c) {
> +		case '\\':
> +			if (scan->cur != start)
> +				return lval_term(scan, TOKEN_STRING, lval,
> +						 start, scan->cur, start);
> +			scan->cur++;
> +			return lex_escape(scan, lval);
> +			break;
> +		case '<':
> +			if (scan->cur != start)
> +				return lval_term(scan, TOKEN_STRING, lval,
> +						 start, scan->cur, start);
> +			scan->cur++;
> +			if (!isascii(*scan->cur) && !isalpha(*scan->cur))
> +				return lval_error(scan, lval, 
> +						  "invalid key start");
> +			scan->cur++; /* consume key's first char */
> +			return lex_key(scan, lval);
> +			break;
> +		case '>':
> +			return lval_error(scan, lval,
> +					  "unexpected key end");
> +		case ')':
> +		case '(':
> +		case '|':
> +			if (scan->cur != start)
> +				return lval_term(scan, TOKEN_STRING, lval,
> +						 start, scan->cur, start);
> +			scan->cur++;
> +			return lval_char(scan, c, start);
> +		case '+':
> +			do
> +				scan->cur++;
> +			while (*scan->cur == ' ' || *scan->cur == '\t');
> +			return lval_char(scan, c, start);
> +		case ' ':
> +		case '\t':
> +		default:
> +			scan->cur++;
> +			break;
> +		}
> +	}
> +
> +	if (scan->cur != start)
> +		return lval_term(scan, TOKEN_STRING, lval,
> +				 start, scan->cur, start);
> +
> +	return 0;
> +}
> +
> +static void *sprint_calloc(size_t len)
> +{
> +	void *p = calloc(len, 1);
> +	if (p == NULL) {
> +		ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
> +		return NULL;
> +	}
> +	return p;
> +}
> +
> +static struct node *sprint_string_node(char *string)
> +{
> +	struct node *node = sprint_calloc(sizeof(struct node));
> +
> +	if (node == NULL)
> +		return NULL;
> +
> +	node->type = NODE_STRING;
> +	node->string = string;
> +
> +	return node;
> +}
> +
> +static int sprint_key_index(struct outform *form, char *name)
> +{
> +	struct keysym *cur;
> +	int i = 0;
> +
> +	llist_for_each_entry(cur, &form->keysyms, list) {
> +		if (!strcmp(cur->name, name))
> +			return i;
> +		i++;
> +	}
> +
> +	return -1;
> +}
> +
> +static struct node *sprint_key_node(struct outform *form, char *name)
> +{
> +	struct node *node;
> +	struct keysym *sym;
> +
> +	if (strlen(name) > ULOGD_MAX_KEYLEN) {
> +		ulogd_log(ULOGD_ERROR, "too long key: %s\n", name);
> +		return NULL;
> +	}
> +
> +	node = sprint_calloc(sizeof(struct node));
> +	if (node == NULL)
> +		return NULL;
> +
> +	node->type = NODE_KEY;
> +	node->kindex = sprint_key_index(form, name);
> +	if (node->kindex < 0) {
> +		sym = sprint_calloc(sizeof(struct keysym));
> +		if (sym == NULL) {
> +			free(node);
> +			return NULL;
> +		}
> +		sym->name = name;
> +		node->kindex = form->num_keys++;
> +		llist_add_tail(&sym->list, &form->keysyms);
> +	}
> +
> +	return node;
> +}
> +
> +static struct node *sprint_list_node(enum sprint_node_type type,
> +				     struct node *term)
> +{
> +	struct node *node = sprint_calloc(sizeof(struct node));
> +
> +	if (node == NULL)
> +		return NULL;
> +
> +	node->type = type;
> +	INIT_LLIST_HEAD(&node->group);
> +	llist_add_tail(&term->list, &node->group);
> +	return node;
> +}
> +
> +static struct node *sprint_keycalc_node(int opcode,
> +					struct node *l, struct node *r)
> +{
> +	struct node *node = sprint_calloc(sizeof(struct node));
> +
> +	if (node == NULL)
> +		return NULL;
> +
> +	node->type = NODE_KEYCALC;
> +	node->keycalc.opcode = opcode;
> +	node->keycalc.l = l;
> +	node->keycalc.r = r;
> +
> +	return node;
> +}
> +
> +static void sprint_free_nodes(struct llist_head *nodes);
> +
> +static void sprint_free_node(struct node *node)
> +{
> +	switch (node->type) {
> +	case NODE_STRING:
> +		free(node->string);
> +		break;
> +	case NODE_KEY:
> +		break;
> +	case NODE_GROUP:
> +	case NODE_CONCAT:
> +		sprint_free_nodes(&node->group);
> +		break;
> +	case NODE_KEYCALC:
> +		sprint_free_node(node->keycalc.l);
> +		sprint_free_node(node->keycalc.r);
> +		break;
> +	default:
> +		ulogd_log(ULOGD_ERROR, "unknown node: %p"
> +			  " type: %d\n", node, node->type);
> +		break;
> +	}
> +}
> +
> +static void sprint_free_nodes(struct llist_head *nodes)
> +{
> +	struct node *node, *nnode;
> +
> +	llist_for_each_entry_safe(node, nnode, nodes, list) {
> +		sprint_free_node(node);
> +		llist_del(&node->list);
> +		free(node);
> +	}
> +}
> +
> +static void sprint_free_keysyms(struct llist_head *head)
> +{
> +	struct keysym *sym, *nsym;
> +
> +	llist_for_each_entry_safe(sym, nsym, head, list) {
> +		llist_del(&sym->list);
> +		free(sym->name);
> +		free(sym);
> +	}
> +}
> +
> +/*
> + * form		:=	part*
> + * part		:=	concat | '(' selector ')'
> + * selector	:=	concat ('|' concat)*
> + * concat	:=	term term*
> + * term		:=	STRING | KEY ('+' KEY)*
> + */
> +static struct node *term(struct outform *form)
> +{
> +	char *lval;
> +	struct node *nl, *nr;
> +	int opcode, ret = lex(form, &lval);
> +
> +	if (ret == TOKEN_ERROR)
> +		return NULL;
> +	if (ret == TOKEN_STRING)
> +		return sprint_string_node(lval);
> +	if (ret != TOKEN_KEY) {
> +		ulogd_log(ULOGD_ERROR,
> +			  "form char: %d, invalid meta char: %c\n",
> +			  form->cur - form->formstr, ret);
> +		return NULL;
> +	}
> +	
> +	/* ret == TOKEN_KEY */
> +	nl = sprint_key_node(form, lval);
> +	if (nl == NULL)
> +		return NULL;
> +
> +	opcode = lex(form, &lval);
> +	if (opcode == TOKEN_ERROR)
> +		return NULL;
> +	if (opcode != '+') {
> +		if (opcode != '\0')
> +			unput(form);
> +		return nl;
> +	}
> +
> +	ret = lex(form, &lval);
> +	if (ret == TOKEN_ERROR)
> +		return NULL;
> +	if (ret != TOKEN_KEY) {
> +		ulogd_log(ULOGD_ERROR,
> +			  "form char: %d, right operand must be a KEY\n",
> +			  form->cur - form->formstr);
> +		return NULL;
> +	}
> +	unput(form);
> +
> +	nr = term(form);
> +	if (nr == NULL)
> +		return NULL;
> +
> +	return sprint_keycalc_node(opcode, nl, nr);
> +}
> +
> +static struct node *concat(struct outform *form)
> +{
> +	char *lval;
> +	int ret;
> +	struct node *terms, *last, *n = term(form);
> +
> +	if (n == NULL)
> +		return NULL;
> +
> +	terms = sprint_list_node(NODE_CONCAT, n);
> +	if (terms == NULL)
> +		return NULL;
> +
> +	ret = lex(form, &lval);
> +	while (ret == TOKEN_STRING || ret == TOKEN_KEY) {
> +		unput(form);
> +		n = term(form);
> +		if (n == NULL)
> +			return NULL;
> +		last = llist_entry(terms->group.prev, struct node, list);
> +		if (last->type == NODE_STRING && n->type == NODE_STRING) {
> +			/* a little bit optimize */
> +			int len1 = strlen(last->string),
> +				len2 = strlen(n->string);
> +
> +			last->string = realloc(last->string, len1 + len2);
> +			if (last->string == NULL) {
> +				ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
> +				return NULL;
> +			}
> +			strncpy(last->string + len1, n->string, len2);
> +			sprint_free_node(n);
> +			free(n);
> +		} else {
> +			llist_add_tail(&n->list, &terms->group);
> +		}
> +		ret = lex(form, &lval);
> +	}
> +	if (ret != '\0')
> +		unput(form);
> +
> +	return terms;
> +}
> +
> +static struct node *selector(struct outform *form)
> +{
> +	int ret;
> +	char *lval;
> +	struct node *concats, *n = concat(form);
> +
> +	if (n == NULL)
> +		return NULL;
> +
> +	concats = sprint_list_node(NODE_GROUP, n);
> +	if (concats == NULL)
> +		return NULL;
> +
> +	while ((ret = lex(form, &lval)) == '|') {
> +		n = concat(form);
> +		if (n == NULL)
> +			return NULL;
> +		llist_add_tail(&n->list, &concats->group);
> +	}
> +	if (ret != '\0')
> +		unput(form);
> +
> +	return concats;
> +}
> +
> +static struct node *part(struct outform *form)
> +{
> +	char *lval;
> +	struct node *n;
> +	int ret = lex(form, &lval);
> +
> +	if (ret == TOKEN_ERROR)
> +		return NULL;
> +
> +	if (ret == '(') {
> +		n = selector(form);
> +		ret = lex(form, &lval);
> +		if (ret != ')') {
> +			ulogd_log(ULOGD_ERROR,
> +				  "form char: %d, no right parenthesis\n",
> +				  form->cur - form->formstr);
> +			return NULL;
> +		}
> +	} else {
> +		unput(form);
> +		n = concat(form);
> +	}
> +
> +	return n;
> +}
> +
> +static int parse_form(struct outform *form)
> +{
> +	struct node *n;
> +
> +	while (*form->cur) {
> +		n = part(form);
> +		if (n == NULL)
> +			return -1;
> +		llist_add_tail(&n->list, &form->form_head);
> +	}
> +
> +	return 0;
> +}
> +
> +static void init_outform(struct outform *form, char *s)
> +{
> +	form->formstr = form->cur = form->prev = s;
> +	INIT_LLIST_HEAD(&form->form_head);
> +	form->num_keys = 1; /* 0 is reserved for oob.family */
> +	INIT_LLIST_HEAD(&form->keysyms);
> +}
> +
> +static int open_connect_descriptor(struct ulogd_pluginstance *upi)
> +{
> +	char *proto, *host, *port;
> +	struct addrinfo hint, *result, *rp;
> +	int ret, fd;
> +
> +	proto = upi->config_kset->ces[SPRINT_CONF_DEST].u.string;
> +	host = strchr(proto, ':');
> +	if (host == NULL) {
> +		ulogd_log(ULOGD_ERROR, "invalid dest\n");
> +		return -1;
> +	}
> +	*host++ = '\0';
> +	if (*host++ != '/') {
> +		ulogd_log(ULOGD_ERROR, "invalid dest\n");
> +		return -1;
> +	}
> +	if (*host++ != '/') {
> +		ulogd_log(ULOGD_ERROR, "invalid dest\n");
> +		return -1;
> +	}
> +
> +	/* file */
> +	if (!strcasecmp(proto, "file")) {
> +		if (strlen(host) == 0)
> +			return STDOUT_FILENO;
> +		return open(host, O_CREAT|O_WRONLY|O_APPEND);
> +	}
> +
> +	/* socket */
> +	port = strrchr(host, ':');
> +	if (port == NULL) {
> +		ulogd_log(ULOGD_ERROR, "no port in dest\n");
> +		return -1;
> +	}
> +	*port++ = '\0';
> +
> +	memset(&hint, 0, sizeof(struct addrinfo));
> +	hint.ai_family = AF_UNSPEC;
> +	if (!strcasecmp(proto, "udp")) {
> +		hint.ai_socktype = SOCK_DGRAM;
> +		hint.ai_protocol = IPPROTO_UDP;
> +	} else if (!strcasecmp(proto, "tcp")) {
> +		hint.ai_socktype = SOCK_STREAM;
> +		hint.ai_protocol = IPPROTO_TCP;
> +	} else {
> +		ulogd_log(ULOGD_ERROR, "unknown protocol `%s'\n",
> +			  proto);
> +		return -1;
> +	}
> +
> +	ret = getaddrinfo(host, port, &hint, &result);
> +	if (ret != 0) {
> +		ulogd_log(ULOGD_ERROR, "can't resolve host/service: %s\n",
> +			  gai_strerror(ret));
> +		if (ret != EAI_SYSTEM)
> +			errno = EINVAL;
> +		return -1;
> +	}
> +
> +	for (rp = result; rp != NULL; rp = rp->ai_next) {
> +		int on = 1;
> +
> +		fd = socket(rp->ai_family, rp->ai_socktype,
> +			     rp->ai_protocol);
> +		if (fd == -1)
> +			continue;
> +
> +		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
> +			   (void *)&on, sizeof(on));
> +		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
> +			break;
> +	}
> +	freeaddrinfo(result);
> +
> +	if (rp == NULL) {
> +		ulogd_log(ULOGD_ERROR, "could not connect\n");
> +		return -1;
> +	}
> +
> +	return fd;
> +}
> +
> +static double sprint_key_calc(struct ulogd_key *keys, struct node *node,
> +			      bool *is_valid)
> +{
> +	*is_valid = false;
> +	if (node->type == NODE_KEY) {
> +		struct ulogd_key *key = keys[node->kindex].u.source;
> +		if (!(key->flags & ULOGD_RETF_VALID))
> +			return 0.0;
> +
> +		switch (key->type) {
> +		case ULOGD_RET_BOOL:
> +		case ULOGD_RET_INT8:
> +		case ULOGD_RET_INT16:
> +		case ULOGD_RET_INT32:
> +			*is_valid = true;
> +			return (double)key->u.value.i32;
> +			break;
> +		case ULOGD_RET_UINT8:
> +		case ULOGD_RET_UINT16:
> +		case ULOGD_RET_UINT32:
> +		case ULOGD_RET_UINT64:
> +			*is_valid = true;
> +			return (double)key->u.value.ui64;
> +			break;
> +		default:
> +			ulogd_log(ULOGD_INFO, "could not calc"
> +				  " key: %s type: %d\n", key->name, key->type);
> +		}
> +	} else if (node->type == NODE_KEYCALC) {
> +		bool lvalid, rvalid;
> +		double lval = sprint_key_calc(keys, node->keycalc.l, &lvalid),
> +			rval = sprint_key_calc(keys, node->keycalc.r, &rvalid);
> +
> +		if (!lvalid || !rvalid)
> +			return 0.0; /* without setting is_valid */
> +
> +		switch (node->keycalc.opcode) {
> +		case '+':
> +			*is_valid = true;
> +			return lval + rval;
> +			break;
> +		default:
> +			ulogd_log(ULOGD_NOTICE, "unknown opcode: %c\n",
> +				  node->keycalc.opcode);
> +			break;
> +		}
> +	} else {
> +		ulogd_log(ULOGD_NOTICE, "invalid node type in keycalc: %d\n",
> +			  node->type);
> +	}
> +
> +	return 0.0; /* without setting is_valid */
> +}
> +
> +static int sprint_keycalc_puts(char *buf, size_t size, bool in_group,
> +			       struct ulogd_key *keys, struct node *node)
> +{
> +	bool is_valid;
> +	double ret = sprint_key_calc(keys, node, &is_valid);
> +
> +	if (!is_valid && in_group)
> +		return 0;
> +
> +	return snprintf(buf, size, "%.0f", ret);
> +}
> +
> +static int sprint_key_puts(char *buf, size_t size, bool in_group,
> +			   struct ulogd_key *keys, struct node *node,
> +			   int addrsep)
> +{
> +	struct ulogd_key *key = keys[node->kindex].u.source;
> +	char family, *p;
> +	int i;
> +
> +	if (!(key->flags & ULOGD_RETF_VALID)) {
> +		if (!in_group) {
> +			ulogd_log(ULOGD_INFO, "no key value: %s\n", key->name);
> +			return printf("<>");
> +		}
> +		return 0;
> +	}
> +
> +	switch (key->type) {
> +	case ULOGD_RET_STRING:
> +		return snprintf(buf, size, "%s", (char *)key->u.value.ptr);
> +		break;
> +	case ULOGD_RET_BOOL:
> +	case ULOGD_RET_INT8:
> +	case ULOGD_RET_INT16:
> +	case ULOGD_RET_INT32:
> +		return snprintf(buf, size, "%d", key->u.value.i32);
> +		break;
> +	case ULOGD_RET_UINT8:
> +	case ULOGD_RET_UINT16:
> +	case ULOGD_RET_UINT32:
> +	case ULOGD_RET_UINT64:
> +		return snprintf(buf, size, "%" PRIu64, key->u.value.ui64);
> +		break;
> +	case ULOGD_RET_IPADDR:
> +		family = ikey_get_u8(keys);
> +		if (family == AF_INET6) {
> +			inet_ntop(AF_INET6, ikey_get_u128(&keys[node->kindex]),
> +				  buf, size);
> +			i = ':';
> +		} else if (family == AF_INET) {
> +			u_int32_t ip = ikey_get_u32(&keys[node->kindex]);
> +			inet_ntop(AF_INET, &ip, buf, size);
> +			i = '.';
> +		} else {
> +			ulogd_log(ULOGD_ERROR,
> +				  "unknown address family: %d\n", family);
> +			return 0;
> +		}
> +		if (addrsep)
> +			for (p = strchr(buf, i); p; p = strchr(p + 1, i))
> +				*p = addrsep;
> +		for (i = 0, p = buf; *p != '\0'; p++, i++)
> +			;
> +		return i;
> +	default:
> +		ulogd_log(ULOGD_INFO, "could not interpret"
> +			  " key: %s, type: %d\n", key->name, key->type);
> +		break;
> +	}
> +	return 0; /* default */
> +}
> +
> +static int sprint_term_puts(char *buf, size_t size, bool in_group,
> +			    struct ulogd_key *keys, struct node *node,
> +			    int addrsep)
> +{
> +	struct node *n;
> +	int ret;
> +	size_t len = 0;
> +
> +	switch (node->type) {
> +	case NODE_KEY:
> +		return sprint_key_puts(buf, size, in_group, keys, node,
> +				       addrsep);
> +		break;
> +	case NODE_STRING:
> +		return snprintf(buf, size, "%s", node->string);
> +		break;
> +	case NODE_KEYCALC:
> +		return sprint_keycalc_puts(buf, size, in_group, keys, node);
> +		break;
> +	case NODE_CONCAT:
> +		llist_for_each_entry(n, &node->group, list) {
> +			ret = sprint_term_puts(buf + len, size - len,
> +					       in_group, keys, n, addrsep);
> +			if ((n->type == NODE_KEY || n->type == NODE_KEYCALC)
> +			    && ret <= 0) {
> +				/* no key value found in a group */
> +				return 0;
> +			}
> +			len += ret;
> +			if (len >= size) {
> +				ulogd_log(ULOGD_NOTICE, "exceeds bufsize\n");
> +				return len;
> +			}
> +		}
> +		return len;
> +		break;
> +	default:
> +		ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
> +			  node->type);
> +		break;
> +	}
> +
> +	return 0; /* unknown node type */
> +}
> +
> +static int sprint_group_puts(char *buf, size_t size, struct ulogd_key *keys,
> +			     struct node *node, int addrsep)
> +{
> +	int ret;
> +	struct node *n;
> +
> +	llist_for_each_entry(n, &node->group, list) {
> +		ret = sprint_term_puts(buf, size, true, keys, n, addrsep);
> +		if (ret > 0) /* put first valid value and return */
> +			return ret;
> +	}
> +
> +	ulogd_log(ULOGD_NOTICE, "no value found in group\n");
> +	return snprintf(buf, size, "()");
> +}
> +
> +static int sprint_interp(struct ulogd_pluginstance *upi)
> +{
> +	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
> +	struct node *cur;
> +	char buf[4096];
> +	int rem = sizeof(buf) - 1, len = 0, ret;
> +	int addrsep = *upi->config_kset->ces[SPRINT_CONF_ADDRSEP].u.string;
> +
> +	llist_for_each_entry(cur, &sp->form_head, list) {
> +		switch (cur->type) {
> +		case NODE_KEY:
> +		case NODE_STRING:
> +		case NODE_CONCAT:
> +		case NODE_KEYCALC:
> +			len += sprint_term_puts(buf + len, rem, false,
> +						upi->input.keys, cur, addrsep);
> +			break;
> +		case NODE_GROUP:
> +			len += sprint_group_puts(buf + len, rem,
> +						 upi->input.keys, cur, addrsep);
> +			break;
> +		default:
> +			ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
> +				  cur->type);
> +		}
> +		rem -= len;
> +		if (rem <= 0) {
> +			ulogd_log(ULOGD_NOTICE,
> +				  "sprint_term_puts exceeds bufsize\n");
> +			len = sizeof(buf);
> +			break;
> +		}
> +	}
> +
> +	ret = write(sp->ofd, buf, len);
> +	if (ret != len) {
> +		buf[len] = '\0';
> +		ulogd_log(ULOGD_ERROR, "Failure sending message: %s\n", buf);
> +		if (ret == -1) {
> +			sp->ofd = open_connect_descriptor(upi);
> +			if (sp->ofd == -1)
> +				return ULOGD_IRET_ERR;
> +		}
> +	}
> +	return ULOGD_IRET_OK;
> +}
> +
> +static void sighup_handler_print(struct ulogd_pluginstance *upi, int signal)
> +{
> +	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
> +	int old = sp->ofd;
> +
> +	switch (signal) {
> +	case SIGHUP:
> +		ulogd_log(ULOGD_NOTICE, "SPRINT: reopening logfile\n");
> +		sp->ofd = open_connect_descriptor(upi);
> +		if (sp->ofd == -1) {
> +			ulogd_log(ULOGD_ERROR, "can't open SPRINT "
> +					       "log file: %s\n",
> +				  strerror(errno));
> +			sp->ofd = old;
> +		} else {
> +			close(old);
> +		}
> +		break;
> +	default:
> +		break;
> +	}
> +}
> +
> +static int sprint_configure_form(struct ulogd_pluginstance *upi)
> +{
> +	struct sprint_priv *priv = (struct sprint_priv *)&upi->private;
> +	struct keysym *sym, *nsym;
> +	struct ulogd_key *ikey;
> +	int ret;
> +	struct outform form;
> +
> +	init_outform(&form,
> +		     upi->config_kset->ces[SPRINT_CONF_FORM].u.string);
> +
> +	ret = parse_form(&form);
> +	if (ret == -1) {
> +		/* parser error, already logged */
> +		sprint_free_nodes(&form.form_head);
> +		sprint_free_keysyms(&form.keysyms);
> +		return ULOGD_IRET_ERR;
> +	}
> +
> +	llist_add(&priv->form_head, &form.form_head);
> +	llist_del(&form.form_head);
> +
> +	ulogd_log(ULOGD_DEBUG, "allocating %u input keys for SPRINT\n",
> +		  form.num_keys);
> +	upi->input.keys = ikey = calloc(sizeof(struct ulogd_key),
> +					form.num_keys);
> +
> +	if (!upi->input.keys)
> +		return -ENOMEM;
> +
> +	/* reserve for putting ULOGD_RET_IPADDR */
> +	ikey->type = ULOGD_RET_UINT8;
> +	ikey->flags = ULOGD_RETF_NONE;
> +	strcpy(ikey->name, "oob.family");
> +	ikey++;
> +
> +	/* create input keys from key symbol list created by form parsing */
> +	llist_for_each_entry_safe(sym, nsym, &form.keysyms, list) {
> +		ikey->flags = ULOGD_RETF_NONE;
> +		strncpy(ikey->name, sym->name, strlen(sym->name));
> +		free(sym->name);
> +		free(sym);
> +		ikey++;
> +	}
> +	upi->input.num_keys = form.num_keys;
> +
> +	return ret;
> +}
> +
> +static int sprint_configure(struct ulogd_pluginstance *upi,
> +			    struct ulogd_pluginstance_stack *stack)
> +{
> +	int ret;
> +
> +	ret = config_parse_file(upi->id, upi->config_kset);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = sprint_configure_form(upi);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int sprint_init(struct ulogd_pluginstance *upi)
> +{
> +	struct sprint_priv *sp = (struct sprint_priv *) &upi->private;
> +
> +	sp->ofd = open_connect_descriptor(upi);
> +	if (sp->ofd < 0) {
> +		ulogd_log(ULOGD_FATAL, "can't open SPRINT destination: %s\n",
> +			  strerror(errno));
> +		return -1;
> +	}
> +
> +	return 0;
> +}
> +
> +static int sprint_fini(struct ulogd_pluginstance *pi)
> +{
> +	struct sprint_priv *sp = (struct sprint_priv *) &pi->private;
> +
> +	if (sp->ofd != STDOUT_FILENO)
> +		close(sp->ofd);
> +
> +	return 0;
> +}
> +
> +static struct ulogd_plugin sprint_plugin = {
> +	.name = "SPRINT",
> +	.input = {
> +		.type	= ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW | ULOGD_DTYPE_SUM,
> +	},
> +	.output = {
> +		.type	= ULOGD_DTYPE_SINK,
> +	},
> +	.configure	= &sprint_configure,
> +	.interp		= &sprint_interp,
> +	.start		= &sprint_init,
> +	.stop		= &sprint_fini,
> +	.signal		= &sighup_handler_print,
> +	.config_kset	= &sprint_kset,
> +	.version	= VERSION,
> +};
> +
> +void __attribute__ ((constructor)) init(void);
> +
> +void init(void)
> +{
> +	ulogd_register_plugin(&sprint_plugin);
> +}

-- 
Eric Leblond <eric@regit.org>


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

* [ulogd RFC PATCH 0/2 resend] introduce new string output plugin
  2014-04-21  9:05     ` Eric Leblond
@ 2014-04-22 11:51       ` Ken-ichirou MATSUZAWA
  0 siblings, 0 replies; 9+ messages in thread
From: Ken-ichirou MATSUZAWA @ 2014-04-22 11:51 UTC (permalink / raw)
  To: Eric Leblond; +Cc: netfilter-devel

This patch introduces a new string output plugin. The output string can be
specified by "form" in config file. Format is consists of:

    key: struct ulogd_key name enclosed by <>, e.g. <orig.ip.saddr.str>
    group: enclosed by () and separated by |, pick first one if exists.
        (<orig.l4.dport>|<icmp.type>|unknown) means
        pick orig.l4.dport value if exist, or icmp.type value. if both
        of them do not exist, select "unknown" string.
    +: add two key value if it can be
    anything else: as is

meta character <>()|+\ needs to be escaped by \. Sink can be specified by "dest"
like URI syntax, proto://host:port, proto can be either file, tcp and udp,
e.g. dest="tcp://192.168.1.1:8125" for statsite. host is filename in case of
proto is file or stdout if dest is "file://"

'.' and ':' in ip address format can be changed by specifying "addrsep" for use
of graphite and statsd.

Signed-off-by Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>

---
 output/Makefile.am           |    5 +-
 output/ulogd_output_SPRINT.c | 1063 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1067 insertions(+), 1 deletion(-)
 create mode 100644 output/ulogd_output_SPRINT.c

diff --git a/output/Makefile.am b/output/Makefile.am
index ff851ad..5f2cb0e 100644
--- a/output/Makefile.am
+++ b/output/Makefile.am
@@ -7,7 +7,7 @@ SUBDIRS= pcap mysql pgsql sqlite3 dbi
 pkglib_LTLIBRARIES = ulogd_output_LOGEMU.la ulogd_output_SYSLOG.la \
 			 ulogd_output_OPRINT.la ulogd_output_GPRINT.la \
 			 ulogd_output_NACCT.la ulogd_output_XML.la \
-			 ulogd_output_GRAPHITE.la
+			 ulogd_output_GRAPHITE.la ulogd_output_SPRINT.la
 
 if HAVE_JANSSON
 pkglib_LTLIBRARIES += ulogd_output_JSON.la
@@ -42,3 +42,6 @@ ulogd_output_JSON_la_SOURCES = ulogd_output_JSON.c
 ulogd_output_JSON_la_LIBADD  = ${libjansson_LIBS}
 ulogd_output_JSON_la_LDFLAGS = -avoid-version -module
 endif
+
+ulogd_output_SPRINT_la_SOURCES = ulogd_output_SPRINT.c
+ulogd_output_SPRINT_la_LDFLAGS = -avoid-version -module
diff --git a/output/ulogd_output_SPRINT.c b/output/ulogd_output_SPRINT.c
new file mode 100644
index 0000000..7b7860e
--- /dev/null
+++ b/output/ulogd_output_SPRINT.c
@@ -0,0 +1,1063 @@
+/* ulogd_output_SPRINT.c
+ *
+ * ulogd output target for sending value specified `form' in config.
+ *
+ * (C) 2014 by Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include <ulogd/ulogd.h>
+#include <ulogd/conffile.h>
+
+#define IPADDR_LENGTH 128
+
+#ifndef ULOGD_SPRINT_DEFAULT
+#define ULOGD_SPRINT_DEFAULT	"file:///var/log/ulogd.sprint"
+#endif
+
+struct sprint_priv {
+	int ofd;
+	struct llist_head form_head;
+};
+
+enum sprint_conf {
+	SPRINT_CONF_FORM = 0,
+	SPRINT_CONF_DEST,
+	SPRINT_CONF_ADDRSEP,
+	SPRINT_CONF_MAX
+};
+
+static struct config_keyset sprint_kset = {
+	.num_ces = SPRINT_CONF_MAX,
+	.ces = {
+		[SPRINT_CONF_FORM] = {
+			.key = "form",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+		},
+		[SPRINT_CONF_DEST] = {
+			.key = "dest",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = ULOGD_SPRINT_DEFAULT },
+		},
+		[SPRINT_CONF_ADDRSEP] = {
+			.key = "addrsep",
+			.type = CONFIG_TYPE_STRING,
+			.options = CONFIG_OPT_NONE,
+			.u = {.string = "" },
+		},
+	},
+};
+
+enum sprint_node_type {
+	NODE_HEAD,
+	NODE_STRING,
+	NODE_KEY,
+	NODE_CONCAT,
+	NODE_GROUP,
+	NODE_KEYCALC,
+};
+
+enum {
+	TOKEN_STRING = 256,
+	TOKEN_KEY,
+	TOKEN_ERROR,
+};
+
+struct keyop {
+	int opcode;
+	struct node *l;
+	struct node *r;
+};
+
+struct node {
+	enum sprint_node_type type;
+	struct llist_head list;
+	union {
+		char *string;			/* NODE_STRING */
+		int kindex;			/* NODE_KEY */
+		struct llist_head group;	/* NODE_CONCAT, NODE_GROUP */
+		struct keyop keycalc;		/* NODE_KEYCALC */
+	};
+};
+
+struct keysym {
+	struct llist_head list;
+	char *name;
+};
+
+struct outform {
+	char *formstr;
+	char *cur;
+	char *prev;		/* for unput */
+	char *prev_lval;
+	struct llist_head form_head;
+	int num_keys;
+	struct llist_head keysyms;
+};
+
+static int unput(struct outform *scan)
+{
+	if (scan->cur == scan->prev)
+		return -1;
+	scan->cur = scan->prev;
+	if (scan->prev_lval)
+		free(scan->prev_lval);
+
+	return 0;
+}
+
+static int lval_term(struct outform *scan, int type,
+		     char **dst, char *from, char *to, char *prev)
+{
+	char c;
+
+	if (to == NULL) {
+		*dst = scan->prev_lval = strdup(from);
+	} else {
+		c = *to;
+		*to = '\0';
+		*dst = scan->prev_lval = strdup(from);
+		*to = c;
+	}
+	if (*dst == NULL) {
+		ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
+		return TOKEN_ERROR;
+	}
+	scan->prev = prev;
+	return type;
+}
+
+static int lval_char(struct outform *scan, int type, char *prev)
+{
+	scan->prev = prev;
+	scan->prev_lval = NULL;
+	return type;
+}
+
+static int lval_error(struct outform *scan, char **dst, char *msg)
+{
+	/* *dst = msg */
+	ulogd_log(ULOGD_ERROR, "%s around %d\n",
+		  msg, scan->cur - scan->formstr);
+	scan->prev_lval = NULL;
+	return TOKEN_ERROR;
+}
+
+static int lex_key(struct outform *scan, char **lval)
+{
+	char c, *start;
+
+	for (start = scan->cur; (c = *scan->cur) != '\0'; scan->cur++) {
+		if (c == '>') {
+			scan->cur++;
+			return lval_term(scan, TOKEN_KEY, lval,
+					 start - 1, scan->cur - 1, start - 2);
+		}
+		if (!isascii(c) && !isalnum(c)
+		    && c != '.' && c != '_' && c != '-')
+			return lval_error(scan, lval,
+					  "invalid key char");
+	}
+
+	return lval_error(scan, lval, "EOF in key");
+}
+
+static int lex_escape(struct outform *scan, char **lval)
+{
+	char sbuf[2];
+	char c = *scan->cur++;
+
+	switch (c) {
+	case 'n':
+		return lval_term(scan, TOKEN_STRING, lval,
+				 "\n", NULL, scan->cur - 2);
+	case 't':
+		return lval_term(scan, TOKEN_STRING, lval,
+				 "\t", NULL, scan->cur - 2);
+	case '\\':
+	case '<':
+	case '>':
+	case '(':
+	case ')':
+	case '|':
+	case '+':
+		snprintf(sbuf, 2, "%c", c);
+		return lval_term(scan, TOKEN_STRING, lval,
+				 sbuf, NULL, scan->cur - 2);
+	case '\0':
+		return lval_error(scan, lval, "EOF in escape");
+	default:
+		return lval_error(scan, lval,
+				  "invalid escape char");
+	}
+}
+
+static int lex(struct outform *scan, char **lval)
+{
+	char c, *start = scan->cur;
+
+	while ((c = *scan->cur) != '\0') {
+		switch(c) {
+		case '\\':
+			if (scan->cur != start)
+				return lval_term(scan, TOKEN_STRING, lval,
+						 start, scan->cur, start);
+			scan->cur++;
+			return lex_escape(scan, lval);
+			break;
+		case '<':
+			if (scan->cur != start)
+				return lval_term(scan, TOKEN_STRING, lval,
+						 start, scan->cur, start);
+			scan->cur++;
+			if (!isascii(*scan->cur) && !isalpha(*scan->cur))
+				return lval_error(scan, lval,
+						  "invalid key start");
+			scan->cur++; /* consume key's first char */
+			return lex_key(scan, lval);
+			break;
+		case '>':
+			return lval_error(scan, lval,
+					  "unexpected key end");
+		case ')':
+		case '(':
+		case '|':
+			if (scan->cur != start)
+				return lval_term(scan, TOKEN_STRING, lval,
+						 start, scan->cur, start);
+			scan->cur++;
+			return lval_char(scan, c, start);
+		case '+':
+			do
+				scan->cur++;
+			while (*scan->cur == ' ' || *scan->cur == '\t');
+			return lval_char(scan, c, start);
+		case ' ':
+		case '\t':
+		default:
+			scan->cur++;
+			break;
+		}
+	}
+
+	if (scan->cur != start)
+		return lval_term(scan, TOKEN_STRING, lval,
+				 start, scan->cur, start);
+
+	return 0;
+}
+
+static void *sprint_calloc(size_t len)
+{
+	void *p = calloc(len, 1);
+	if (p == NULL) {
+		ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
+		return NULL;
+	}
+	return p;
+}
+
+static struct node *sprint_string_node(char *string)
+{
+	struct node *node = sprint_calloc(sizeof(struct node));
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_STRING;
+	node->string = string;
+
+	return node;
+}
+
+static int sprint_key_index(struct outform *form, char *name)
+{
+	struct keysym *cur;
+	int i = 0;
+
+	llist_for_each_entry(cur, &form->keysyms, list) {
+		if (!strcmp(cur->name, name))
+			return i;
+		i++;
+	}
+
+	return -1;
+}
+
+static struct node *sprint_key_node(struct outform *form, char *name)
+{
+	struct node *node;
+	struct keysym *sym;
+
+	if (strlen(name) > ULOGD_MAX_KEYLEN) {
+		ulogd_log(ULOGD_ERROR, "too long key: %s\n", name);
+		return NULL;
+	}
+
+	node = sprint_calloc(sizeof(struct node));
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_KEY;
+	node->kindex = sprint_key_index(form, name);
+	if (node->kindex < 0) {
+		sym = sprint_calloc(sizeof(struct keysym));
+		if (sym == NULL) {
+			free(node);
+			return NULL;
+		}
+		sym->name = name;
+		node->kindex = form->num_keys++;
+		llist_add_tail(&sym->list, &form->keysyms);
+	}
+
+	return node;
+}
+
+static struct node *sprint_list_node(enum sprint_node_type type,
+				     struct node *term)
+{
+	struct node *node = sprint_calloc(sizeof(struct node));
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = type;
+	INIT_LLIST_HEAD(&node->group);
+	llist_add_tail(&term->list, &node->group);
+	return node;
+}
+
+static struct node *sprint_keycalc_node(int opcode,
+					struct node *l, struct node *r)
+{
+	struct node *node = sprint_calloc(sizeof(struct node));
+
+	if (node == NULL)
+		return NULL;
+
+	node->type = NODE_KEYCALC;
+	node->keycalc.opcode = opcode;
+	node->keycalc.l = l;
+	node->keycalc.r = r;
+
+	return node;
+}
+
+static void sprint_free_nodes(struct llist_head *nodes);
+
+static void sprint_free_node(struct node *node)
+{
+	switch (node->type) {
+	case NODE_STRING:
+		free(node->string);
+		break;
+	case NODE_KEY:
+		break;
+	case NODE_GROUP:
+	case NODE_CONCAT:
+		sprint_free_nodes(&node->group);
+		break;
+	case NODE_KEYCALC:
+		sprint_free_node(node->keycalc.l);
+		sprint_free_node(node->keycalc.r);
+		break;
+	default:
+		ulogd_log(ULOGD_ERROR, "unknown node: %p"
+			  " type: %d\n", node, node->type);
+		break;
+	}
+}
+
+static void sprint_free_nodes(struct llist_head *nodes)
+{
+	struct node *node, *nnode;
+
+	llist_for_each_entry_safe(node, nnode, nodes, list) {
+		sprint_free_node(node);
+		llist_del(&node->list);
+		free(node);
+	}
+}
+
+static void sprint_free_keysyms(struct llist_head *head)
+{
+	struct keysym *sym, *nsym;
+
+	llist_for_each_entry_safe(sym, nsym, head, list) {
+		llist_del(&sym->list);
+		free(sym->name);
+		free(sym);
+	}
+}
+
+/*
+ * form		:=	part*
+ * part		:=	concat | '(' selector ')'
+ * selector	:=	concat ('|' concat)*
+ * concat	:=	term term*
+ * term		:=	STRING | KEY ('+' KEY)*
+ */
+static struct node *term(struct outform *form)
+{
+	char *lval;
+	struct node *nl, *nr;
+	int opcode, ret = lex(form, &lval);
+
+	if (ret == TOKEN_ERROR)
+		return NULL;
+	if (ret == TOKEN_STRING)
+		return sprint_string_node(lval);
+	if (ret != TOKEN_KEY) {
+		ulogd_log(ULOGD_ERROR,
+			  "form char: %d, invalid meta char: %c\n",
+			  form->cur - form->formstr, ret);
+		return NULL;
+	}
+
+	/* ret == TOKEN_KEY */
+	nl = sprint_key_node(form, lval);
+	if (nl == NULL)
+		return NULL;
+
+	opcode = lex(form, &lval);
+	if (opcode == TOKEN_ERROR)
+		return NULL;
+	if (opcode != '+') {
+		if (opcode != '\0')
+			unput(form);
+		return nl;
+	}
+
+	ret = lex(form, &lval);
+	if (ret == TOKEN_ERROR)
+		return NULL;
+	if (ret != TOKEN_KEY) {
+		ulogd_log(ULOGD_ERROR,
+			  "form char: %d, right operand must be a KEY\n",
+			  form->cur - form->formstr);
+		return NULL;
+	}
+	unput(form);
+
+	nr = term(form);
+	if (nr == NULL)
+		return NULL;
+
+	return sprint_keycalc_node(opcode, nl, nr);
+}
+
+static struct node *concat(struct outform *form)
+{
+	char *lval;
+	int ret;
+	struct node *terms, *last, *n = term(form);
+
+	if (n == NULL)
+		return NULL;
+
+	terms = sprint_list_node(NODE_CONCAT, n);
+	if (terms == NULL)
+		return NULL;
+
+	ret = lex(form, &lval);
+	while (ret == TOKEN_STRING || ret == TOKEN_KEY) {
+		unput(form);
+		n = term(form);
+		if (n == NULL)
+			return NULL;
+		last = llist_entry(terms->group.prev, struct node, list);
+		if (last->type == NODE_STRING && n->type == NODE_STRING) {
+			/* a little bit optimize */
+			int len1 = strlen(last->string),
+				len2 = strlen(n->string);
+
+			last->string = realloc(last->string, len1 + len2 + 1);
+			if (last->string == NULL) {
+				ulogd_log(ULOGD_ERROR, "%s\n", strerror(errno));
+				return NULL;
+			}
+			strncpy(last->string + len1, n->string, len2);
+			sprint_free_node(n);
+			free(n);
+		} else {
+			llist_add_tail(&n->list, &terms->group);
+		}
+		ret = lex(form, &lval);
+	}
+	if (ret != '\0')
+		unput(form);
+
+	return terms;
+}
+
+static struct node *selector(struct outform *form)
+{
+	int ret;
+	char *lval;
+	struct node *concats, *n = concat(form);
+
+	if (n == NULL)
+		return NULL;
+
+	concats = sprint_list_node(NODE_GROUP, n);
+	if (concats == NULL)
+		return NULL;
+
+	while ((ret = lex(form, &lval)) == '|') {
+		n = concat(form);
+		if (n == NULL)
+			return NULL;
+		llist_add_tail(&n->list, &concats->group);
+	}
+	if (ret != '\0')
+		unput(form);
+
+	return concats;
+}
+
+static struct node *part(struct outform *form)
+{
+	char *lval;
+	struct node *n;
+	int ret = lex(form, &lval);
+
+	if (ret == TOKEN_ERROR)
+		return NULL;
+
+	if (ret == '(') {
+		n = selector(form);
+		ret = lex(form, &lval);
+		if (ret != ')') {
+			ulogd_log(ULOGD_ERROR,
+				  "form char: %d, no right parenthesis\n",
+				  form->cur - form->formstr);
+			return NULL;
+		}
+	} else {
+		unput(form);
+		n = concat(form);
+	}
+
+	return n;
+}
+
+static int parse_form(struct outform *form)
+{
+	struct node *n;
+
+	while (*form->cur) {
+		n = part(form);
+		if (n == NULL)
+			return -1;
+		llist_add_tail(&n->list, &form->form_head);
+	}
+
+	return 0;
+}
+
+static int init_outform(struct outform *form, char *s)
+{
+	struct keysym *oob_family = calloc(sizeof(struct keysym), 1);
+
+	if (oob_family == NULL)
+		return -1;
+
+	form->formstr = form->cur = form->prev = s;
+	INIT_LLIST_HEAD(&form->form_head);
+
+	INIT_LLIST_HEAD(&form->keysyms);
+	/* for ULOGD_RET_IPADDR in sprint_key_puts() */
+	oob_family->name = strdup("oob.family");
+	if (oob_family->name == NULL)
+		return -1;
+	llist_add_tail(&oob_family->list, &form->keysyms);
+	form->num_keys = 1;
+
+	return 0;
+}
+
+static int open_connect_descriptor(struct ulogd_pluginstance *upi)
+{
+	char *proto, *host, *port;
+	struct addrinfo hint, *result, *rp;
+	int ret, fd;
+
+	proto = upi->config_kset->ces[SPRINT_CONF_DEST].u.string;
+	host = strchr(proto, ':');
+	if (host == NULL) {
+		ulogd_log(ULOGD_ERROR, "invalid dest\n");
+		return -1;
+	}
+	*host++ = '\0';
+	if (*host++ != '/') {
+		ulogd_log(ULOGD_ERROR, "invalid dest\n");
+		return -1;
+	}
+	if (*host++ != '/') {
+		ulogd_log(ULOGD_ERROR, "invalid dest\n");
+		return -1;
+	}
+
+	/* file */
+	if (!strcasecmp(proto, "file")) {
+		if (strlen(host) == 0)
+			return STDOUT_FILENO;
+		return open(host, O_CREAT|O_WRONLY|O_APPEND);
+	}
+
+	/* socket */
+	port = strrchr(host, ':');
+	if (port == NULL) {
+		ulogd_log(ULOGD_ERROR, "no port in dest\n");
+		return -1;
+	}
+	*port++ = '\0';
+
+	memset(&hint, 0, sizeof(struct addrinfo));
+	hint.ai_family = AF_UNSPEC;
+	if (!strcasecmp(proto, "udp")) {
+		hint.ai_socktype = SOCK_DGRAM;
+		hint.ai_protocol = IPPROTO_UDP;
+	} else if (!strcasecmp(proto, "tcp")) {
+		hint.ai_socktype = SOCK_STREAM;
+		hint.ai_protocol = IPPROTO_TCP;
+	} else {
+		ulogd_log(ULOGD_ERROR, "unknown protocol `%s'\n",
+			  proto);
+		return -1;
+	}
+
+	ret = getaddrinfo(host, port, &hint, &result);
+	if (ret != 0) {
+		ulogd_log(ULOGD_ERROR, "can't resolve host/service: %s\n",
+			  gai_strerror(ret));
+		if (ret != EAI_SYSTEM)
+			errno = EINVAL;
+		return -1;
+	}
+
+	for (rp = result; rp != NULL; rp = rp->ai_next) {
+		int on = 1;
+
+		fd = socket(rp->ai_family, rp->ai_socktype,
+			     rp->ai_protocol);
+		if (fd == -1)
+			continue;
+
+		setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+			   (void *)&on, sizeof(on));
+		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
+			break;
+	}
+	freeaddrinfo(result);
+
+	if (rp == NULL) {
+		ulogd_log(ULOGD_ERROR, "could not connect\n");
+		return -1;
+	}
+
+	return fd;
+}
+
+static double sprint_key_calc(struct ulogd_key *keys, struct node *node,
+			      bool *is_valid)
+{
+	*is_valid = false;
+	if (node->type == NODE_KEY) {
+		struct ulogd_key *key = keys[node->kindex].u.source;
+		if (!(key->flags & ULOGD_RETF_VALID))
+			return 0.0;
+
+		switch (key->type) {
+		case ULOGD_RET_BOOL:
+		case ULOGD_RET_INT8:
+		case ULOGD_RET_INT16:
+		case ULOGD_RET_INT32:
+			*is_valid = true;
+			return (double)key->u.value.i32;
+			break;
+		case ULOGD_RET_UINT8:
+		case ULOGD_RET_UINT16:
+		case ULOGD_RET_UINT32:
+		case ULOGD_RET_UINT64:
+			*is_valid = true;
+			return (double)key->u.value.ui64;
+			break;
+		default:
+			ulogd_log(ULOGD_INFO, "could not calc"
+				  " key: %s type: %d\n", key->name, key->type);
+		}
+	} else if (node->type == NODE_KEYCALC) {
+		bool lvalid, rvalid;
+		double lval = sprint_key_calc(keys, node->keycalc.l, &lvalid),
+			rval = sprint_key_calc(keys, node->keycalc.r, &rvalid);
+
+		if (!lvalid || !rvalid)
+			return 0.0; /* without setting is_valid */
+
+		switch (node->keycalc.opcode) {
+		case '+':
+			*is_valid = true;
+			return lval + rval;
+			break;
+		default:
+			ulogd_log(ULOGD_NOTICE, "unknown opcode: %c\n",
+				  node->keycalc.opcode);
+			break;
+		}
+	} else {
+		ulogd_log(ULOGD_NOTICE, "invalid node type in keycalc: %d\n",
+			  node->type);
+	}
+
+	return 0.0; /* without setting is_valid */
+}
+
+static int sprint_keycalc_puts(char *buf, size_t size, bool in_group,
+			       struct ulogd_key *keys, struct node *node)
+{
+	bool is_valid;
+	double ret = sprint_key_calc(keys, node, &is_valid);
+
+	if (!is_valid && in_group)
+		return 0;
+
+	return snprintf(buf, size, "%.0f", ret);
+}
+
+static int sprint_key_puts(char *buf, size_t size, bool in_group,
+			   struct ulogd_key *keys, struct node *node,
+			   int addrsep)
+{
+	struct ulogd_key *key = keys[node->kindex].u.source;
+	char family, *p;
+	int i;
+
+	if (!(key->flags & ULOGD_RETF_VALID)) {
+		if (!in_group) {
+			ulogd_log(ULOGD_INFO, "no key value: %s\n", key->name);
+			return printf("<>");
+		}
+		return 0;
+	}
+
+	switch (key->type) {
+	case ULOGD_RET_STRING:
+		return snprintf(buf, size, "%s", (char *)key->u.value.ptr);
+		break;
+	case ULOGD_RET_BOOL:
+	case ULOGD_RET_INT8:
+	case ULOGD_RET_INT16:
+	case ULOGD_RET_INT32:
+		return snprintf(buf, size, "%d", key->u.value.i32);
+		break;
+	case ULOGD_RET_UINT8:
+	case ULOGD_RET_UINT16:
+	case ULOGD_RET_UINT32:
+	case ULOGD_RET_UINT64:
+		return snprintf(buf, size, "%" PRIu64, key->u.value.ui64);
+		break;
+	case ULOGD_RET_IPADDR:
+		family = ikey_get_u8(keys);
+		if (family == AF_INET6) {
+			inet_ntop(AF_INET6, ikey_get_u128(&keys[node->kindex]),
+				  buf, size);
+			i = ':';
+		} else if (family == AF_INET) {
+			u_int32_t ip = ikey_get_u32(&keys[node->kindex]);
+			inet_ntop(AF_INET, &ip, buf, size);
+			i = '.';
+		} else {
+			ulogd_log(ULOGD_ERROR,
+				  "unknown address family: %d\n", family);
+			return 0;
+		}
+		if (addrsep)
+			for (p = strchr(buf, i); p; p = strchr(p + 1, i))
+				*p = addrsep;
+		for (i = 0, p = buf; *p != '\0'; p++, i++)
+			;
+		return i;
+	default:
+		ulogd_log(ULOGD_INFO, "could not interpret"
+			  " key: %s, type: %d\n", key->name, key->type);
+		break;
+	}
+	return 0; /* default */
+}
+
+static int sprint_term_puts(char *buf, size_t size, bool in_group,
+			    struct ulogd_key *keys, struct node *node,
+			    int addrsep)
+{
+	struct node *n;
+	int ret;
+	size_t len = 0;
+
+	switch (node->type) {
+	case NODE_KEY:
+		return sprint_key_puts(buf, size, in_group, keys, node,
+				       addrsep);
+		break;
+	case NODE_STRING:
+		return snprintf(buf, size, "%s", node->string);
+		break;
+	case NODE_KEYCALC:
+		return sprint_keycalc_puts(buf, size, in_group, keys, node);
+		break;
+	case NODE_CONCAT:
+		llist_for_each_entry(n, &node->group, list) {
+			ret = sprint_term_puts(buf + len, size - len,
+					       in_group, keys, n, addrsep);
+			if ((n->type == NODE_KEY || n->type == NODE_KEYCALC)
+			    && ret <= 0) {
+				/* no key value found in a group */
+				return 0;
+			}
+			len += ret;
+			if (len >= size) {
+				ulogd_log(ULOGD_NOTICE, "exceeds bufsize\n");
+				return len;
+			}
+		}
+		return len;
+		break;
+	default:
+		ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
+			  node->type);
+		break;
+	}
+
+	return 0; /* unknown node type */
+}
+
+static int sprint_group_puts(char *buf, size_t size, struct ulogd_key *keys,
+			     struct node *node, int addrsep)
+{
+	int ret;
+	struct node *n;
+
+	llist_for_each_entry(n, &node->group, list) {
+		ret = sprint_term_puts(buf, size, true, keys, n, addrsep);
+		if (ret > 0) /* put first valid value and return */
+			return ret;
+	}
+
+	ulogd_log(ULOGD_NOTICE, "no value found in group\n");
+	return snprintf(buf, size, "()");
+}
+
+static int sprint_interp(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
+	struct node *cur;
+	char buf[4096];
+	int rem = sizeof(buf) - 1, len = 0, ret;
+	int addrsep = *upi->config_kset->ces[SPRINT_CONF_ADDRSEP].u.string;
+
+	llist_for_each_entry(cur, &sp->form_head, list) {
+		switch (cur->type) {
+		case NODE_KEY:
+		case NODE_STRING:
+		case NODE_CONCAT:
+		case NODE_KEYCALC:
+			len += sprint_term_puts(buf + len, rem, false,
+						upi->input.keys, cur, addrsep);
+			break;
+		case NODE_GROUP:
+			len += sprint_group_puts(buf + len, rem,
+						 upi->input.keys, cur, addrsep);
+			break;
+		default:
+			ulogd_log(ULOGD_NOTICE, "unknown node type: %d\n",
+				  cur->type);
+		}
+		rem -= len;
+		if (rem <= 0) {
+			ulogd_log(ULOGD_NOTICE,
+				  "sprint_term_puts exceeds bufsize\n");
+			len = sizeof(buf);
+			break;
+		}
+	}
+
+	ret = write(sp->ofd, buf, len);
+	if (ret != len) {
+		buf[len] = '\0';
+		ulogd_log(ULOGD_ERROR, "Failure sending message: %s\n", buf);
+		if (ret == -1) {
+			sp->ofd = open_connect_descriptor(upi);
+			if (sp->ofd == -1)
+				return ULOGD_IRET_ERR;
+		}
+	}
+	return ULOGD_IRET_OK;
+}
+
+static void sighup_handler_print(struct ulogd_pluginstance *upi, int signal)
+{
+	struct sprint_priv *sp = (struct sprint_priv *)&upi->private;
+	int old = sp->ofd;
+
+	switch (signal) {
+	case SIGHUP:
+		ulogd_log(ULOGD_NOTICE, "SPRINT: reopening logfile\n");
+		sp->ofd = open_connect_descriptor(upi);
+		if (sp->ofd == -1) {
+			ulogd_log(ULOGD_ERROR, "can't open SPRINT "
+					       "log file: %s\n",
+				  strerror(errno));
+			sp->ofd = old;
+		} else {
+			close(old);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static int sprint_configure_form(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *priv = (struct sprint_priv *)&upi->private;
+	struct keysym *sym, *nsym;
+	struct ulogd_key *ikey;
+	int ret;
+	struct outform form;
+
+	if (init_outform(&form,
+			 upi->config_kset->ces[SPRINT_CONF_FORM].u.string)) {
+		ulogd_log(ULOGD_FATAL, "could not init form data\n");
+		return ULOGD_IRET_ERR;
+	}
+
+	ret = parse_form(&form);
+	if (ret == -1) {
+		/* parser error, already logged */
+		sprint_free_nodes(&form.form_head);
+		sprint_free_keysyms(&form.keysyms);
+		return ULOGD_IRET_ERR;
+	}
+
+	llist_add(&priv->form_head, &form.form_head);
+	llist_del(&form.form_head);
+
+	ulogd_log(ULOGD_DEBUG, "allocating %u input keys for SPRINT\n",
+		  form.num_keys);
+	upi->input.keys = ikey = calloc(sizeof(struct ulogd_key),
+					form.num_keys);
+	if (!upi->input.keys)
+		return -ENOMEM;
+
+	/* create input keys from key symbol list created by form parsing */
+	llist_for_each_entry_safe(sym, nsym, &form.keysyms, list) {
+		ikey->flags = ULOGD_RETF_NONE;
+		strncpy(ikey->name, sym->name, strlen(sym->name));
+		free(sym->name);
+		free(sym);
+		ikey++;
+	}
+	upi->input.num_keys = form.num_keys;
+
+	return ret;
+}
+
+static int sprint_configure(struct ulogd_pluginstance *upi,
+			    struct ulogd_pluginstance_stack *stack)
+{
+	int ret;
+
+	ret = config_parse_file(upi->id, upi->config_kset);
+	if (ret < 0)
+		return ret;
+
+	ret = sprint_configure_form(upi);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int sprint_init(struct ulogd_pluginstance *upi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *) &upi->private;
+
+	sp->ofd = open_connect_descriptor(upi);
+	if (sp->ofd < 0) {
+		ulogd_log(ULOGD_FATAL, "can't open SPRINT destination: %s\n",
+			  strerror(errno));
+		return -1;
+	}
+
+	return 0;
+}
+
+static int sprint_fini(struct ulogd_pluginstance *pi)
+{
+	struct sprint_priv *sp = (struct sprint_priv *) &pi->private;
+
+	if (sp->ofd != STDOUT_FILENO)
+		close(sp->ofd);
+
+	/* sprint_priv->form_head is initialized at configure pharse.
+	 * Should it be released here? by:
+	 *     sprint_free_nodes(&sp->form_head);
+	 */
+
+	return 0;
+}
+
+static struct ulogd_plugin sprint_plugin = {
+	.name = "SPRINT",
+	.input = {
+		.type	= ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW | ULOGD_DTYPE_SUM,
+	},
+	.output = {
+		.type	= ULOGD_DTYPE_SINK,
+	},
+	.configure	= &sprint_configure,
+	.interp		= &sprint_interp,
+	.start		= &sprint_init,
+	.stop		= &sprint_fini,
+	.signal		= &sighup_handler_print,
+	.config_kset	= &sprint_kset,
+	.version	= VERSION,
+};
+
+void __attribute__ ((constructor)) init(void);
+
+void init(void)
+{
+	ulogd_register_plugin(&sprint_plugin);
+}
-- 
1.9.1


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

end of thread, other threads:[~2014-04-22 11:51 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-03-29  4:23 [ulogd RFC PATCH 0/2] introduce new string output plugin Ken-ichirou MATSUZAWA
2014-03-29  4:27 ` [ulogd RFC PATCH 1/2] sprint: introduce new " Ken-ichirou MATSUZAWA
2014-03-31 21:06   ` Eric Leblond
2014-03-29  4:29 ` [ulogd RFC PATCH 2/2] ip2str: introduce changable address separator Ken-ichirou MATSUZAWA
2014-03-31 20:17   ` Eric Leblond
2014-03-31 20:51 ` [ulogd RFC PATCH 0/2] introduce new string output plugin Eric Leblond
2014-04-02 10:14   ` Ken-ichirou MATSUZAWA
2014-04-21  9:05     ` Eric Leblond
2014-04-22 11:51       ` [ulogd RFC PATCH 0/2 resend] " Ken-ichirou MATSUZAWA

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).