From 4f639231c83b09ea004c03e95c702b7750bf9930 Mon Sep 17 00:00:00 2001
From: Ander Juaristi <a@juaristi.eus>
Date: Fri, 26 Apr 2019 09:58:06 +0200
Subject: IPFIX: Add IPFIX output plugin

This patch adds an IPFIX output plugin to ulogd2. It generates NetFlow/IPFIX
traces and sends them to a remote server (collector) via TCP or UDP.

Based on original work by Holger Eitzenberger <holger@eitzenberger.org>.

How to test this
----------------

I am currently testing this with the NFCT input and Wireshark.

Place the following in ulogd.conf:

      # this will print all flows on screen
      loglevel=1

      # load NFCT and IPFIX plugins
      plugin="/lib/ulogd/ulogd_inpflow_NFCT.so"
      plugin="/lib/ulogd/ulogd_output_IPFIX.so"

      stack=ct1:NFCT,ipfix1:IPFIX

      [ct1]
      netlink_socket_buffer_size=217088
      netlink_socket_buffer_maxsize=1085440
      accept_proto_filter=tcp,sctp

      [ipfix1]
      oid=1
      host="127.0.0.1"
      #port=4739
      #send_template="once"

I am currently testing it by launching a plain NetCat listener on port
4739 (the default for IPFIX) and then running Wireshark and see that it
dissects the IPFIX/NetFlow traffic correctly (obviously this relies on
the Wireshark NetFlow dissector being correct).

First:

      nc -vvvv -l 127.0.0.1 4739

Then:

      sudo ulogd -vc ulogd.conf

Signed-off-by: Ander Juaristi <a@juaristi.eus>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 configure.ac                      |   2 +-
 include/ulogd/ulogd.h             |   5 +
 input/flow/ulogd_inpflow_IPFIX.c  |   2 -
 output/Makefile.am                |   2 +-
 output/ipfix/Makefile.am          |   7 +
 output/ipfix/ipfix.c              | 141 ++++++++++
 output/ipfix/ipfix.h              |  89 +++++++
 output/ipfix/ulogd_output_IPFIX.c | 503 +++++++++++++++++++++++++++++++++++
 output/ulogd_output_IPFIX.c       | 546 --------------------------------------
 9 files changed, 747 insertions(+), 550 deletions(-)
 delete mode 100644 input/flow/ulogd_inpflow_IPFIX.c
 create mode 100644 output/ipfix/Makefile.am
 create mode 100644 output/ipfix/ipfix.c
 create mode 100644 output/ipfix/ipfix.h
 create mode 100644 output/ipfix/ulogd_output_IPFIX.c
 delete mode 100644 output/ulogd_output_IPFIX.c

--- a/configure.ac
+++ b/configure.ac
@@ -179,7 +179,7 @@ AC_CONFIG_FILES(include/Makefile include
 	  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/ipfix/Makefile \
 	  src/Makefile Makefile Rules.make)
 AC_OUTPUT
 
--- a/include/ulogd/ulogd.h
+++ b/include/ulogd/ulogd.h
@@ -28,6 +28,11 @@
 
 /* types without length */
 #define ULOGD_RET_NONE		0x0000
+#define __packed		__attribute__((packed))
+#define __noreturn		__attribute__((noreturn))
+#define __cold			__attribute__((cold))
+
+#define __packed		__attribute__((packed))
 
 #define ULOGD_RET_INT8		0x0001
 #define ULOGD_RET_INT16		0x0002
--- a/output/Makefile.am
+++ b/output/Makefile.am
@@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include ${
               ${LIBNETFILTER_CONNTRACK_CFLAGS} ${LIBNETFILTER_LOG_CFLAGS}
 AM_CFLAGS = ${regular_CFLAGS}
 
-SUBDIRS= pcap mysql pgsql sqlite3 dbi
+SUBDIRS= pcap mysql pgsql sqlite3 dbi ipfix
 
 pkglib_LTLIBRARIES = ulogd_output_LOGEMU.la ulogd_output_SYSLOG.la \
 			 ulogd_output_OPRINT.la ulogd_output_GPRINT.la \
--- /dev/null
+++ b/output/ipfix/Makefile.am
@@ -0,0 +1,7 @@
+AM_CPPFLAGS = -I$(top_srcdir)/include
+AM_CFLAGS = $(regular_CFLAGS)
+
+pkglib_LTLIBRARIES = ulogd_output_IPFIX.la
+
+ulogd_output_IPFIX_la_SOURCES = ulogd_output_IPFIX.c ipfix.c
+ulogd_output_IPFIX_la_LDFLAGS = -avoid-version -module
--- /dev/null
+++ b/output/ipfix/ipfix.c
@@ -0,0 +1,141 @@
+/*
+ * ipfix.c
+ *
+ * Holger Eitzenberger, 2009.
+ */
+
+/* These forward declarations are needed since ulogd.h doesn't like to be the first */
+#include <ulogd/linuxlist.h>
+
+#define __packed		__attribute__((packed))
+
+#include "ipfix.h"
+
+#include <ulogd/ulogd.h>
+#include <ulogd/common.h>
+
+struct ipfix_msg *ipfix_msg_alloc(size_t len, uint32_t oid)
+{
+	struct ipfix_msg *msg;
+	struct ipfix_hdr *hdr;
+
+	if (len < IPFIX_HDRLEN + IPFIX_SET_HDRLEN)
+		return NULL;
+
+	msg = malloc(sizeof(struct ipfix_msg) + len);
+	memset(msg, 0, sizeof(struct ipfix_msg));
+	msg->tail = msg->data + IPFIX_HDRLEN;
+	msg->end = msg->data + len;
+
+	hdr = ipfix_msg_hdr(msg);
+	memset(hdr, 0, IPFIX_HDRLEN);
+	hdr->version = htons(IPFIX_VERSION);
+	hdr->oid = htonl(oid);
+
+	return msg;
+}
+
+void ipfix_msg_free(struct ipfix_msg *msg)
+{
+	if (!msg)
+		return;
+
+	if (msg->nrecs > 0)
+		ulogd_log(ULOGD_DEBUG, "%s: %d flows have been lost\n", __func__,
+			msg->nrecs);
+
+	free(msg);
+}
+
+struct ipfix_hdr *ipfix_msg_hdr(const struct ipfix_msg *msg)
+{
+	return (struct ipfix_hdr *)msg->data;
+}
+
+void *ipfix_msg_data(struct ipfix_msg *msg)
+{
+	return msg->data;
+}
+
+size_t ipfix_msg_len(const struct ipfix_msg *msg)
+{
+	return msg->tail - msg->data;
+}
+
+struct ipfix_set_hdr *ipfix_msg_add_set(struct ipfix_msg *msg, uint16_t sid)
+{
+	struct ipfix_set_hdr *shdr;
+
+	if (msg->end - msg->tail < (int) IPFIX_SET_HDRLEN)
+		return NULL;
+
+	shdr = (struct ipfix_set_hdr *)msg->tail;
+	shdr->id = sid;
+	shdr->len = IPFIX_SET_HDRLEN;
+	msg->tail += IPFIX_SET_HDRLEN;
+	msg->last_set = shdr;
+	return shdr;
+}
+
+struct ipfix_set_hdr *ipfix_msg_get_set(const struct ipfix_msg *msg)
+{
+	return msg->last_set;
+}
+
+/**
+ * Add data record to an IPFIX message.  The data is accounted properly.
+ *
+ * @return pointer to data or %NULL if not that much space left.
+ */
+void *ipfix_msg_add_data(struct ipfix_msg *msg, size_t len)
+{
+	void *data;
+
+	if (!msg->last_set) {
+		ulogd_log(ULOGD_FATAL, "msg->last_set is NULL\n");
+		return NULL;
+	}
+
+	if ((ssize_t) len > msg->end - msg->tail)
+		return NULL;
+
+	data = msg->tail;
+	msg->tail += len;
+	msg->nrecs++;
+	msg->last_set->len += len;
+
+	return data;
+}
+
+/* check and dump message */
+int ipfix_dump_msg(const struct ipfix_msg *msg)
+{
+	const struct ipfix_hdr *hdr = ipfix_msg_hdr(msg);
+	const struct ipfix_set_hdr *shdr = (struct ipfix_set_hdr *) hdr->data;
+
+	if (ntohs(hdr->len) < IPFIX_HDRLEN) {
+		ulogd_log(ULOGD_FATAL, "Invalid IPFIX message header length\n");
+		return -1;
+	}
+	if (ipfix_msg_len(msg) != IPFIX_HDRLEN + ntohs(shdr->len)) {
+		ulogd_log(ULOGD_FATAL, "Invalid IPFIX message length\n");
+		return -1;
+	}
+
+	ulogd_log(ULOGD_DEBUG, "msg: ver=%#x len=%#x t=%#x seq=%#x oid=%d\n",
+			  ntohs(hdr->version), ntohs(hdr->len), htonl(hdr->time),
+			  ntohl(hdr->seqno), ntohl(hdr->oid));
+
+	return 0;
+}
+
+/* template management */
+size_t ipfix_rec_len(uint16_t sid)
+{
+	if (sid != htons(VY_IPFIX_SID)) {
+		ulogd_log(ULOGD_FATAL, "Invalid SID\n");
+		return 0;
+	}
+
+	return sizeof(struct vy_ipfix_data);
+}
--- /dev/null
+++ b/output/ipfix/ipfix.h
@@ -0,0 +1,89 @@
+/*
+ * ipfix.h
+ *
+ * Holger Eitzenberger <holger@eitzenberger.org>, 2009.
+ */
+#ifndef IPFIX_H
+#define IPFIX_H
+
+#include <stdint.h>
+#include <netinet/in.h>
+
+
+struct ipfix_hdr {
+#define IPFIX_VERSION			0xa
+	uint16_t version;
+	uint16_t len;
+	uint32_t time;
+	uint32_t seqno;
+	uint32_t oid;				/* Observation Domain ID */
+	uint8_t data[];
+} __packed;
+
+#define IPFIX_HDRLEN	sizeof(struct ipfix_hdr)
+
+/*
+ * IDs 0-255 are reserved for Template Sets.  IDs of Data Sets are > 255.
+ */
+struct ipfix_templ_hdr {
+	uint16_t id;
+	uint16_t cnt;
+	uint8_t data[];
+} __packed;
+
+struct ipfix_set_hdr {
+#define IPFIX_SET_TEMPL			2
+#define IPFIX_SET_OPT_TEMPL		3
+	uint16_t id;
+	uint16_t len;
+	uint8_t data[];
+} __packed;
+
+#define IPFIX_SET_HDRLEN		sizeof(struct ipfix_set_hdr)
+
+struct ipfix_msg {
+	struct llist_head link;
+	uint8_t *tail;
+	uint8_t *end;
+	unsigned nrecs;
+	struct ipfix_set_hdr *last_set;
+	uint8_t data[];
+};
+
+struct vy_ipfix_data {
+	struct in_addr saddr;
+	struct in_addr daddr;
+	uint16_t ifi_in;
+	uint16_t ifi_out;
+	uint32_t packets;
+	uint32_t bytes;
+	uint32_t start;				/* Unix time */
+	uint32_t end;				/* Unix time */
+	uint16_t sport;
+	uint16_t dport;
+	uint32_t aid;				/* Application ID */
+	uint8_t l4_proto;
+	uint8_t dscp;
+	uint16_t __padding;
+} __packed;
+
+#define VY_IPFIX_SID		256
+
+#define VY_IPFIX_FLOWS		36
+#define VY_IPFIX_PKT_LEN	(IPFIX_HDRLEN + IPFIX_SET_HDRLEN \
+							 + VY_IPFIX_FLOWS * sizeof(struct vy_ipfix_data))
+
+/* template management */
+size_t ipfix_rec_len(uint16_t);
+
+/* message handling */
+struct ipfix_msg *ipfix_msg_alloc(size_t, uint32_t);
+void ipfix_msg_free(struct ipfix_msg *);
+struct ipfix_hdr *ipfix_msg_hdr(const struct ipfix_msg *);
+size_t ipfix_msg_len(const struct ipfix_msg *);
+void *ipfix_msg_data(struct ipfix_msg *);
+struct ipfix_set_hdr *ipfix_msg_add_set(struct ipfix_msg *, uint16_t);
+void *ipfix_msg_add_data(struct ipfix_msg *, size_t);
+int ipfix_dump_msg(const struct ipfix_msg *);
+
+#endif /* IPFIX_H */
--- /dev/null
+++ b/output/ipfix/ulogd_output_IPFIX.c
@@ -0,0 +1,503 @@
+/*
+ * ulogd_output_IPFIX.c
+ *
+ * ulogd IPFIX Exporter plugin.
+ *
+ * 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
+ *
+ * Holger Eitzenberger <holger@eitzenberger.org>  Astaro AG 2009
+ */
+#include <unistd.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <ulogd/ulogd.h>
+#include <ulogd/common.h>
+
+#include "ipfix.h"
+
+#define DEFAULT_MTU		512 /* RFC 5101, 10.3.3 */
+#define DEFAULT_PORT		4739 /* RFC 5101, 10.3.4 */
+#define DEFAULT_SPORT		4740
+
+enum {
+	OID_CE = 0,
+	HOST_CE,
+	PORT_CE,
+	PROTO_CE,
+	MTU_CE,
+};
+
+#define oid_ce(x)	(x->ces[OID_CE])
+#define host_ce(x)	(x->ces[HOST_CE])
+#define port_ce(x)	(x->ces[PORT_CE])
+#define proto_ce(x)	(x->ces[PROTO_CE])
+#define mtu_ce(x)	(x->ces[MTU_CE])
+
+static const struct config_keyset ipfix_kset = {
+	.num_ces = 5,
+	.ces = {
+		{
+			.key = "oid",
+			.type = CONFIG_TYPE_INT,
+			.u.value = 0
+		},
+		{
+			.key = "host",
+			.type = CONFIG_TYPE_STRING,
+			.u.string = ""
+		},
+		{
+			.key = "port",
+			.type = CONFIG_TYPE_INT,
+			.u.value = DEFAULT_PORT
+		},
+		{
+			.key = "proto",
+			.type = CONFIG_TYPE_STRING,
+			.u.string = "tcp"
+		},
+		{
+			.key = "mtu",
+			.type = CONFIG_TYPE_INT,
+			.u.value = DEFAULT_MTU
+		}
+	}
+};
+
+struct ipfix_templ {
+	struct ipfix_templ *next;
+};
+
+struct ipfix_priv {
+	struct ulogd_fd ufd;
+	uint32_t seqno;
+	struct ipfix_msg *msg;		/* current message */
+	struct llist_head list;
+	struct ipfix_templ *templates;
+	int proto;
+	struct ulogd_timer timer;
+	struct sockaddr_in sa;
+};
+
+enum {
+	InIpSaddr = 0,
+	InIpDaddr,
+	InRawInPktCount,
+	InRawInPktLen,
+	InRawOutPktCount,
+	InRawOutPktLen,
+	InFlowStartSec,
+	InFlowStartUsec,
+	InFlowEndSec,
+	InFlowEndUsec,
+	InL4SPort,
+	InL4DPort,
+	InIpProto,
+	InCtMark
+};
+
+static struct ulogd_key ipfix_in_keys[] = {
+		[InIpSaddr] = {
+			.type = ULOGD_RET_IPADDR,
+			.name = "orig.ip.saddr"
+		},
+		[InIpDaddr] = {
+			.type = ULOGD_RET_IPADDR,
+			.name = "orig.ip.daddr"
+		},
+		[InRawInPktCount] = {
+			.type = ULOGD_RET_UINT64,
+			.name = "orig.raw.pktcount"
+		},
+		[InRawInPktLen] = {
+			.type = ULOGD_RET_UINT64,
+			.name = "orig.raw.pktlen"
+		},
+		[InRawOutPktCount] = {
+			.type = ULOGD_RET_UINT64,
+			.name = "reply.raw.pktcount"
+		},
+		[InRawOutPktLen] = {
+			.type = ULOGD_RET_UINT64,
+			.name = "reply.raw.pktlen"
+		},
+		[InFlowStartSec] = {
+			.type = ULOGD_RET_UINT32,
+			.name = "flow.start.sec"
+		},
+		[InFlowStartUsec] = {
+			.type = ULOGD_RET_UINT32,
+			.name = "flow.start.usec"
+		},
+		[InFlowEndSec] = {
+			.type = ULOGD_RET_UINT32,
+			.name = "flow.end.sec"
+		},
+		[InFlowEndUsec] = {
+			.type = ULOGD_RET_UINT32,
+			.name = "flow.end.usec"
+		},
+		[InL4SPort] = {
+			.type = ULOGD_RET_UINT16,
+			.name = "orig.l4.sport"
+		},
+		[InL4DPort] = {
+			.type = ULOGD_RET_UINT16,
+			.name = "orig.l4.dport"
+		},
+		[InIpProto] = {
+			.type = ULOGD_RET_UINT8,
+			.name = "orig.ip.protocol"
+		},
+		[InCtMark] = {
+			.type = ULOGD_RET_UINT32,
+			.name = "ct.mark"
+		}
+};
+
+/* do some polishing and enqueue it */
+static void enqueue_msg(struct ipfix_priv *priv, struct ipfix_msg *msg)
+{
+	struct ipfix_hdr *hdr = ipfix_msg_data(msg);
+
+	if (!msg)
+		return;
+
+	hdr->time = htonl(time(NULL));
+	hdr->seqno = htonl(priv->seqno += msg->nrecs);
+	if (msg->last_set) {
+		msg->last_set->id = htons(msg->last_set->id);
+		msg->last_set->len = htons(msg->last_set->len);
+		msg->last_set = NULL;
+	}
+	hdr->len = htons(ipfix_msg_len(msg));
+
+	llist_add(&msg->link, &priv->list);
+}
+
+/**
+ * @return %ULOGD_IRET_OK or error value
+ */
+static int send_msgs(struct ulogd_pluginstance *pi)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+	struct llist_head *curr, *tmp;
+	struct ipfix_msg *msg;
+	int ret = ULOGD_IRET_OK, sent;
+
+	llist_for_each_prev(curr, &priv->list) {
+		msg = llist_entry(curr, struct ipfix_msg, link);
+
+		sent = send(priv->ufd.fd, ipfix_msg_data(msg), ipfix_msg_len(msg), 0);
+		if (sent < 0) {
+			ulogd_log(ULOGD_ERROR, "send: %m\n");
+			ret = ULOGD_IRET_ERR;
+			goto done;
+		}
+
+		/* TODO handle short send() for other protocols */
+		if ((size_t) sent < ipfix_msg_len(msg))
+			ulogd_log(ULOGD_ERROR, "short send: %d < %d\n",
+					sent, ipfix_msg_len(msg));
+	}
+
+	llist_for_each_safe(curr, tmp, &priv->list) {
+		msg = llist_entry(curr, struct ipfix_msg, link);
+		llist_del(curr);
+		msg->nrecs = 0;
+		ipfix_msg_free(msg);
+	}
+
+done:
+	return ret;
+}
+
+static int ipfix_ufd_cb(int fd, unsigned what, void *arg)
+{
+	struct ulogd_pluginstance *pi = arg;
+	struct ipfix_priv *priv = (struct ipfix_priv *) pi->private;
+	ssize_t nread;
+	char buf[16];
+
+	if (what & ULOGD_FD_READ) {
+		nread = recv(priv->ufd.fd, buf, sizeof(buf), MSG_DONTWAIT);
+		if (nread < 0) {
+			ulogd_log(ULOGD_ERROR, "recv: %m\n");
+		} else if (!nread) {
+			ulogd_log(ULOGD_INFO, "connection reset by peer\n");
+			ulogd_unregister_fd(&priv->ufd);
+		} else
+			ulogd_log(ULOGD_INFO, "unexpected data (%d bytes)\n", nread);
+	}
+
+	return 0;
+}
+
+static void ipfix_timer_cb(struct ulogd_timer *t, void *data)
+{
+	struct ulogd_pluginstance *pi = data;
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+
+	if (priv->msg && priv->msg->nrecs > 0) {
+		enqueue_msg(priv, priv->msg);
+		priv->msg = NULL;
+		send_msgs(pi);
+	}
+}
+
+static int ipfix_configure(struct ulogd_pluginstance *pi, struct ulogd_pluginstance_stack *stack)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+	int oid, port, mtu, ret;
+	char *host, *proto;
+	char addr[16];
+
+	ret = config_parse_file(pi->id, pi->config_kset);
+	if (ret < 0)
+		return ret;
+
+	oid = oid_ce(pi->config_kset).u.value;
+	host = host_ce(pi->config_kset).u.string;
+	port = port_ce(pi->config_kset).u.value;
+	proto = proto_ce(pi->config_kset).u.string;
+	mtu = mtu_ce(pi->config_kset).u.value;
+
+	if (!oid) {
+		ulogd_log(ULOGD_FATAL, "invalid Observation ID\n");
+		return ULOGD_IRET_ERR;
+	}
+	if (!host || !strcmp(host, "")) {
+		ulogd_log(ULOGD_FATAL, "no destination host specified\n");
+		return ULOGD_IRET_ERR;
+	}
+
+	if (!strcmp(proto, "udp")) {
+		priv->proto = IPPROTO_UDP;
+	} else if (!strcmp(proto, "tcp")) {
+		priv->proto = IPPROTO_TCP;
+	} else {
+		ulogd_log(ULOGD_FATAL, "unsupported protocol '%s'\n", proto);
+		return ULOGD_IRET_ERR;
+	}
+
+	memset(&priv->sa, 0, sizeof(priv->sa));
+	priv->sa.sin_family = AF_INET;
+	priv->sa.sin_port = htons(port);
+	ret = inet_pton(AF_INET, host, &priv->sa.sin_addr);
+	if (ret <= 0) {
+		ulogd_log(ULOGD_FATAL, "inet_pton: %m\n");
+		return ULOGD_IRET_ERR;
+	}
+
+	INIT_LLIST_HEAD(&priv->list);
+
+	ulogd_init_timer(&priv->timer, pi, ipfix_timer_cb);
+
+	ulogd_log(ULOGD_INFO, "using IPFIX Collector at %s:%d (MTU %d)\n",
+		  inet_ntop(AF_INET, &priv->sa.sin_addr, addr, sizeof(addr)),
+		  port, mtu);
+
+	return ULOGD_IRET_OK;
+}
+
+static int tcp_connect(struct ulogd_pluginstance *pi)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+	int ret = ULOGD_IRET_ERR;
+
+	if ((priv->ufd.fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+		ulogd_log(ULOGD_FATAL, "socket: %m\n");
+		return ULOGD_IRET_ERR;
+	}
+
+	if (connect(priv->ufd.fd, (struct sockaddr *) &priv->sa, sizeof(priv->sa)) < 0) {
+		ulogd_log(ULOGD_ERROR, "connect: %m\n");
+		ret = ULOGD_IRET_ERR;
+		goto err_close;
+	}
+
+	return ULOGD_IRET_OK;
+
+err_close:
+	close(priv->ufd.fd);
+	return ret;
+}
+
+static int udp_connect(struct ulogd_pluginstance *pi)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+
+	if ((priv->ufd.fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+		ulogd_log(ULOGD_FATAL, "socket: %m\n");
+		return ULOGD_IRET_ERR;
+	}
+
+	if (connect(priv->ufd.fd, (struct sockaddr *) &priv->sa, sizeof(priv->sa)) < 0) {
+		ulogd_log(ULOGD_ERROR, "connect: %m\n");
+		return ULOGD_IRET_ERR;
+	}
+
+	return 0;
+}
+
+static int ipfix_start(struct ulogd_pluginstance *pi)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+	char addr[16];
+	int port, ret;
+
+	switch (priv->proto) {
+	case IPPROTO_UDP:
+		if ((ret = udp_connect(pi)) < 0)
+			return ret;
+		break;
+	case IPPROTO_TCP:
+		if ((ret = tcp_connect(pi)) < 0)
+			return ret;
+		break;
+
+	default:
+		break;
+	}
+
+	priv->seqno = 0;
+
+	port = port_ce(pi->config_kset).u.value;
+	ulogd_log(ULOGD_INFO, "connected to %s:%d\n",
+			inet_ntop(AF_INET, &priv->sa.sin_addr, addr, sizeof(addr)),
+			port);
+
+	/* Register the socket FD */
+	priv->ufd.when = ULOGD_FD_READ;
+	priv->ufd.cb = ipfix_ufd_cb;
+	priv->ufd.data = pi;
+
+	if (ulogd_register_fd(&priv->ufd) < 0)
+		return ULOGD_IRET_ERR;
+
+	/* Add a 1 second timer */
+	ulogd_add_timer(&priv->timer, 1);
+
+	return ULOGD_IRET_OK;
+}
+
+static int ipfix_stop(struct ulogd_pluginstance *pi)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+
+	ulogd_unregister_fd(&priv->ufd);
+	close(priv->ufd.fd);
+	priv->ufd.fd = -1;
+
+	ulogd_del_timer(&priv->timer);
+
+	ipfix_msg_free(priv->msg);
+	priv->msg = NULL;
+
+	return 0;
+}
+
+static int ipfix_interp(struct ulogd_pluginstance *pi)
+{
+	struct ipfix_priv *priv = (struct ipfix_priv *) &pi->private;
+	struct vy_ipfix_data *data;
+	int oid, mtu, ret;
+	char addr[16];
+
+	if (!(GET_FLAGS(pi->input.keys, InIpSaddr) & ULOGD_RETF_VALID))
+		return ULOGD_IRET_OK;
+
+	oid = oid_ce(pi->config_kset).u.value;
+	mtu = mtu_ce(pi->config_kset).u.value;
+
+again:
+	if (!priv->msg) {
+		priv->msg = ipfix_msg_alloc(mtu, oid);
+		if (!priv->msg) {
+			/* just drop this flow */
+			ulogd_log(ULOGD_ERROR, "out of memory, dropping flow\n");
+			return ULOGD_IRET_OK;
+		}
+		ipfix_msg_add_set(priv->msg, VY_IPFIX_SID);
+	}
+
+	data = ipfix_msg_add_data(priv->msg, sizeof(struct vy_ipfix_data));
+	if (!data) {
+		enqueue_msg(priv, priv->msg);
+		priv->msg = NULL;
+		/* can't loop because the next will definitely succeed */
+		goto again;
+	}
+
+	data->ifi_in = data->ifi_out = 0;
+
+	data->saddr.s_addr = ikey_get_u32(&pi->input.keys[InIpSaddr]);
+	data->daddr.s_addr = ikey_get_u32(&pi->input.keys[InIpDaddr]);
+
+	data->packets = htonl((uint32_t) (ikey_get_u64(&pi->input.keys[InRawInPktCount])
+						+ ikey_get_u64(&pi->input.keys[InRawOutPktCount])));
+	data->bytes = htonl((uint32_t) (ikey_get_u64(&pi->input.keys[InRawInPktLen])
+						+ ikey_get_u64(&pi->input.keys[InRawOutPktLen])));
+
+	data->start = htonl(ikey_get_u32(&pi->input.keys[InFlowStartSec]));
+	data->end = htonl(ikey_get_u32(&pi->input.keys[InFlowEndSec]));
+
+	if (GET_FLAGS(pi->input.keys, InL4SPort) & ULOGD_RETF_VALID) {
+		data->sport = htons(ikey_get_u16(&pi->input.keys[InL4SPort]));
+		data->dport = htons(ikey_get_u16(&pi->input.keys[InL4DPort]));
+	}
+
+	data->aid = 0;
+	if (GET_FLAGS(pi->input.keys, InCtMark) & ULOGD_RETF_VALID)
+		data->aid = htonl(ikey_get_u32(&pi->input.keys[InCtMark]));
+
+	data->l4_proto = ikey_get_u8(&pi->input.keys[InIpProto]);
+	data->__padding = 0;
+
+	ulogd_log(ULOGD_DEBUG, "Got new packet (packets = %u, bytes = %u, flow = (%u, %u), saddr = %s, daddr = %s, sport = %u, dport = %u)\n",
+			ntohl(data->packets), ntohl(data->bytes), ntohl(data->start), ntohl(data->end),
+			inet_ntop(AF_INET, &data->saddr.s_addr, addr, sizeof(addr)),
+			inet_ntop(AF_INET, &data->daddr.s_addr, addr, sizeof(addr)),
+			ntohs(data->sport), ntohs(data->dport));
+
+	if ((ret = send_msgs(pi)) < 0)
+		return ret;
+
+	return ULOGD_IRET_OK;
+}
+
+static struct ulogd_plugin ipfix_plugin = {
+	.name = "IPFIX",
+	.input = {
+		.keys = ipfix_in_keys,
+		.num_keys = ARRAY_SIZE(ipfix_in_keys),
+		.type = ULOGD_DTYPE_PACKET | ULOGD_DTYPE_FLOW | ULOGD_DTYPE_SUM
+	},
+	.output = {
+		.type = ULOGD_DTYPE_SINK
+	},
+	.config_kset = (struct config_keyset *) &ipfix_kset,
+	.priv_size = sizeof(struct ipfix_priv),
+	.configure = ipfix_configure,
+	.start = ipfix_start,
+	.stop = ipfix_stop,
+	.interp = ipfix_interp,
+	.version = VERSION,
+};
+
+void __attribute__ ((constructor)) init(void);
+
+void init(void)
+{
+	ulogd_register_plugin(&ipfix_plugin);
+}
