@@ -13,6 +13,10 @@ if HAVE_JANSSON
pkglib_LTLIBRARIES += ulogd_output_JSON.la
endif
+if BUILD_NFCT
+pkglib_LTLIBRARIES += ulogd_output_NFLOW9.la
+endif
+
ulogd_output_GPRINT_la_SOURCES = ulogd_output_GPRINT.c
ulogd_output_GPRINT_la_LDFLAGS = -avoid-version -module
@@ -42,3 +46,9 @@ 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
+
+if BUILD_NFCT
+ulogd_output_NFLOW9_la_SOURCES = ulogd_output_NFLOW9.c
+ulogd_output_NFLOW9_la_LIBADD = $(LIBNETFILTER_CONNTRACK_LIBS)
+ulogd_output_NFLOW9_la_LDFLAGS = -avoid-version -module
+endif
new file mode 100644
@@ -0,0 +1,1696 @@
+/* ulogd_output_NFLOW9.c
+ *
+ * ulogd output plugin for NetFlow version9
+ *
+ * This target produces a NetFlow v9 data and send it.
+ *
+ * (C) 2014 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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <sys/uio.h>
+#include <asm/byteorder.h> /* __be64_to_cpu */
+
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+#include <ulogd/linuxlist.h>
+#include <ulogd/ulogd.h>
+#include <ulogd/conffile.h>
+#include <ulogd/ipfix_protocol.h>
+
+/* #define DEBUG_TMMAP */
+#ifdef DEBUG_TMMAP
+#include <sys/mman.h>
+int mmfd;
+void *mmaddr;
+static int nflow9_fprintf_header(FILE *fd, const struct netflow9_instance *ii);
+#endif
+
+/*
+ * This implementation sends NetFlow v9 entry only if ORIG or REPLY counter is
+ * greater than 0. Single NFCT entry contains duplex data, orig and reply but
+ * NetFlow v9 can represents simplex entry only, so that sigle NFCT entry may
+ * create two NetFlow v9 data entries. for example:
+ *
+ * 192.168.1.1 -> 172.16.1.1 will nat 1.1.1.1 -> 2.2.2.2
+ *
+ * NFCT:
+ * orig.ip.saddr 192.168.1.1
+ * orig.ip.daddr 172.16.1.1
+ * reply.ip.saddr 2.2.2.2
+ * reply.ip.daddr 1.1.1.1
+ * orig.raw.pktcount 111
+ * reply.raw.pktcount 222
+ *
+ * NFLOW9 entry1 entry2
+ * SRC_ADDR 192.168.1.1 172.16.1.1
+ * DST_ADDR 172.16.1.1 192.168.1.1
+ * XLATE_SRC_ADDR 1.1.1.1 2.2.2.2
+ * XLATE_DST_ADDR 2.2.2.2 1.1.1.1
+ * IN_PKTS 111 222
+ *
+ * then:
+ * orig.raw.pktcount.delta > 0: swap reply.*
+ * reply.raw.pktcount.delta > 0: swap orig.* and ifindex.
+ * invert flowDirection
+ *
+ * This means a NetFlow v9 entry has only one conter and same can be said to
+ * ip.protocol. Because of this, corksets_max should be greater than 3 since
+ * bidirectional handling above, and a template may be added.
+ *
+ * Note about NFCT:
+ * - To use same template, assume the number of keys starting with "orig." and
+ * "reply." is the same.
+ * - not handle both Count and DeltaCount, only either of them.
+ * NFCT plugin currently propagate Count even on polling mode. it's not
+ * suitable for this plugin, only Destroy NFCT event is acceptable I think.
+ *
+ * Example configuration and usage:
+ * minimum configuration - ulogd.conf
+ * [global]
+ * logfile="ulogd.log"
+ * loglevel=1
+ * plugin="/usr/local/lib/ulogd/ulogd_inpflow_NFCT.so"
+ * plugin="/usr/local/lib/ulogd/ulogd_filter_TIMECONV.so"
+ * plugin="/usr/local/lib/ulogd/ulogd_filter_PACKICMP.so"
+ * plugin="/usr/local/lib/ulogd/ulogd_output_NFLOW9.so"
+ * stack=ct:NFCT,timeconv:TIMECONV,packicmp:PACKICMP,nflow9:NFLOW9
+ *
+ * [ct]
+ * hash_enable=0
+ * event_mask=0x00000004
+ * [timeconv]
+ * [packicmp]
+ * [nflow9]
+ * dest=udp://127.0.0.1:9995
+ *
+ * run nfcapd (http://nfdump.sourceforge.net/)
+ * $ mkdir nfdata
+ * $ nfcapd -b 127.0.0.1 -l nfdata -T all
+ *
+ * run ulogd
+ * # ulogd -v -c ulogd.conf
+ *
+ * show flow data
+ * (wait for more than 5min or stop nfcapd by ^C)
+ * $ nfdump -o line -R nfdata | less -S
+ */
+
+/* index for ikey which needs special handling */
+enum {
+ CII_ORIG_RAW_PKTLEN_DELTA,
+ CII_ORIG_RAW_PKTCOUNT_DELTA,
+ CII_REPLY_RAW_PKTLEN_DELTA,
+ CII_REPLY_RAW_PKTCOUNT_DELTA,
+ CII_REPLY_IP_PROTOCOL, /* use only orig ip.protocol */
+ CII_FAMILY, /* illigal dirty hack */
+
+ /* XXX: will be removed if NFCT propagate delta counter */
+ CII_ORIG_RAW_PKTLEN,
+ CII_ORIG_RAW_PKTCOUNT,
+ CII_REPLY_RAW_PKTLEN,
+ CII_REPLY_RAW_PKTCOUNT,
+ CII_MAX,
+};
+
+static char *count_keys[] = {
+ [CII_ORIG_RAW_PKTLEN_DELTA] = "orig.raw.pktlen.delta",
+ [CII_ORIG_RAW_PKTCOUNT_DELTA] = "orig.raw.pktcount.delta",
+ [CII_REPLY_RAW_PKTLEN_DELTA] = "reply.raw.pktlen.delta",
+ [CII_REPLY_RAW_PKTCOUNT_DELTA] = "reply.raw.pktcount.delta",
+ [CII_REPLY_IP_PROTOCOL] = "reply.ip.protocol",
+ [CII_FAMILY] = "oob.family",
+
+ /* XXX: will be removed if NFCT propagate delta counter */
+ [CII_ORIG_RAW_PKTLEN] = "orig.raw.pktlen",
+ [CII_ORIG_RAW_PKTCOUNT] = "orig.raw.pktcount",
+ [CII_REPLY_RAW_PKTLEN] = "reply.raw.pktlen",
+ [CII_REPLY_RAW_PKTCOUNT] = "reply.raw.pktcount",
+};
+
+/* index for data field offset to swap by direction */
+enum {
+ FOI_ORIG_IP_SADDR = 0,
+ FOI_ORIG_IP_DADDR,
+ FOI_ORIG_IP6_SADDR,
+ FOI_ORIG_IP6_DADDR,
+ FOI_ORIG_L4_SPORT,
+ FOI_ORIG_L4_DPORT,
+ FOI_REPLY_IP_SADDR,
+ FOI_REPLY_IP_DADDR,
+ FOI_REPLY_IP6_SADDR,
+ FOI_REPLY_IP6_DADDR,
+ FOI_REPLY_L4_SPORT,
+ FOI_REPLY_L4_DPORT,
+ FOI_IF_INPUT,
+ FOI_IF_OUTPUT,
+ FOI_FLOW_DIR,
+ FOI_IN_BYTES,
+ FOI_IN_PKTS,
+ FOI_XXX_IN_BYTES,
+ FOI_XXX_IN_PKTS,
+ FOI_MAX,
+};
+
+static char *dir_keys[] = {
+ [FOI_ORIG_IP_SADDR] = "orig.ip.saddr",
+ [FOI_ORIG_IP_DADDR] = "orig.ip.daddr",
+ [FOI_ORIG_IP6_SADDR] = "orig.ip6.saddr",
+ [FOI_ORIG_IP6_DADDR] = "orig.ip6.daddr",
+ [FOI_ORIG_L4_SPORT] = "orig.l4.sport",
+ [FOI_ORIG_L4_DPORT] = "orig.l4.dport",
+ [FOI_REPLY_IP_SADDR] = "reply.ip.saddr",
+ [FOI_REPLY_IP_DADDR] = "reply.ip.daddr",
+ [FOI_REPLY_IP6_SADDR] = "reply.ip6.saddr",
+ [FOI_REPLY_IP6_DADDR] = "reply.ip6.daddr",
+ [FOI_REPLY_L4_SPORT] = "reply.l4.sport",
+ [FOI_REPLY_L4_DPORT] = "reply.l4.dport",
+ [FOI_IF_INPUT] = "oob.ifindex_in",
+ [FOI_IF_OUTPUT] = "oob.ifindex_out",
+ [FOI_FLOW_DIR] = "flow.direction",
+ [FOI_IN_BYTES] = "orig.raw.pktlen.delta",
+ [FOI_IN_PKTS] = "orig.raw.pktcount.delta",
+ /* XXX: will be removed if NFCT propagate delta counter */
+ [FOI_XXX_IN_BYTES] = "orig.raw.pktlen",
+ [FOI_XXX_IN_PKTS] = "orig.raw.pktcount",
+};
+
+enum {
+ NFLOW9_DIR_NONE = 0,
+ NFLOW9_DIR_ORIG = 1,
+ NFLOW9_DIR_REPLY = 2,
+ NFLOW9_DIR_BOTH = NFLOW9_DIR_ORIG | NFLOW9_DIR_REPLY,
+};
+
+enum {
+ NFLOW9_CONF_DEST,
+ NFLOW9_CONF_DOMAIN_ID,
+ NFLOW9_CONF_NTH_TEMPLATE,
+ NFLOW9_CONF_CORKSETS_MAX,
+ NFLOW9_CONF_MAX,
+};
+
+static struct config_keyset netflow9_kset = {
+ .num_ces = NFLOW9_CONF_MAX,
+ .ces = {
+ [NFLOW9_CONF_DEST] = {
+ .key = "dest",
+ .type = CONFIG_TYPE_STRING,
+ .options = CONFIG_OPT_NONE,
+ .u = { .string = "udp://localhost:9996" },
+ },
+ [NFLOW9_CONF_DOMAIN_ID] = {
+ .key = "domain_id",
+ .type = CONFIG_TYPE_INT,
+ .options = CONFIG_OPT_NONE,
+ .u.value = 0,
+ },
+ [NFLOW9_CONF_NTH_TEMPLATE] = {
+ .key = "nth_template",
+ .type = CONFIG_TYPE_INT,
+ .options = CONFIG_OPT_NONE,
+ .u.value = 16,
+ },
+ [NFLOW9_CONF_CORKSETS_MAX] = {
+ .key = "corksets_max",
+ .type = CONFIG_TYPE_INT,
+ .options = CONFIG_OPT_NONE,
+ .u.value = 20,
+ },
+ },
+};
+
+#define dest_ce(x) ((x)->config_kset->ces[NFLOW9_CONF_DEST].u.string)
+#define domain_ce(x) ((x)->config_kset->ces[NFLOW9_CONF_DOMAIN_ID].u.value)
+#define nth_template_ce(x) ((x)->config_kset->ces[NFLOW9_CONF_NTH_TEMPLATE].u.value)
+#define corksets_max_ce(x) ((x)->config_kset->ces[NFLOW9_CONF_CORKSETS_MAX].u.value)
+
+/* Section 5.1 */
+struct netflow9_msg_hdr {
+ uint16_t version;
+ uint16_t count;
+ uint32_t sys_uptime;
+ uint32_t unix_secs;
+ uint32_t sequence_number;
+ uint32_t source_id;
+};
+
+/* Section 5.2, 5.3 */
+struct netflow9_set_hdr {
+ uint16_t set_id;
+ uint16_t length;
+};
+
+/* Section 5.2 */
+struct netflow9_templ_hdr {
+ uint16_t template_id;
+ uint16_t field_count;
+};
+
+/* Section 5.2 */
+struct netflow9_templ_rec {
+ uint16_t type;
+ uint16_t length;
+};
+
+/* 8. Field Type Definitions octet (or default)*/
+enum {
+ NETFLOW9_IN_BYTES = 1, /* (4) octetDeltaCount */
+ NETFLOW9_IN_PKTS = 2, /* (4) packetDeltaCount */
+ NETFLOW9_FLOWS = 3, /* (4) */
+ NETFLOW9_PROTOCOL = 4, /* 1 protocolIdentifier */
+ NETFLOW9_TOS = 5, /* 1 classOfServiceIPv4 */
+ NETFLOW9_TCP_FLAGS = 6, /* 1 tcpControlBits */
+ NETFLOW9_L4_SRC_PORT = 7, /* 2 sourceTransportPort */
+ NETFLOW9_IPV4_SRC_ADDR = 8, /* 4 sourceIPv4Address */
+ NETFLOW9_SRC_MASK = 9, /* 1 sourceIPv4Mask */
+ NETFLOW9_INPUT_SNMP = 10, /* (2) ingressInterface */
+ NETFLOW9_L4_DST_PORT = 11, /* 2 destinationTransportPort */
+ NETFLOW9_IPV4_DST_ADDR = 12, /* 4 destinationIPv4Address */
+ NETFLOW9_DST_MASK = 13, /* 1 destinationIPv4Mask */
+ NETFLOW9_OUTPUT_SNMP = 14, /* (2) egressInterface */
+ NETFLOW9_IPV4_NEXT_HOP = 15, /* 4 ipNextHopIPv4Address */
+ NETFLOW9_SRC_AS = 16, /* (2) bgpSourceAsNumber */
+ NETFLOW9_DST_AS = 17, /* (2) bgpDestinationAsNumber */
+ NETFLOW9_BGP_IPV4_NEXT_HOP = 18, /* 4 bgpNextHopIPv4Address */
+ NETFLOW9_MUL_DST_PKTS = 19, /* (4) postMCastPacketDeltaCount */
+ NETFLOW9_MUL_DST_BYTES = 20, /* (4) postMCastOctetDeltaCount */
+ NETFLOW9_LAST_SWITCHED = 21, /* 4 flowEndSysUpTime */
+ NETFLOW9_FIRST_SWITCHED = 22, /* 4 flowStartSysUpTime */
+ NETFLOW9_OUT_BYTES = 23, /* (4) postOctetDeltaCount */
+ NETFLOW9_OUT_PKTS = 24, /* (4) postPacketDeltaCount */
+ /* reserved */
+ /* reserved */
+ NETFLOW9_IPV6_SRC_ADDR = 27, /* 16 sourceIPv6Address */
+ NETFLOW9_IPV6_DST_ADDR = 28, /* 16 destinationIPv6Address */
+ NETFLOW9_IPV6_SRC_MASK = 29, /* 1 sourceIPv6Mask */
+ NETFLOW9_IPV6_DST_MASK = 30, /* 1 destinationIPv6Mask */
+ NETFLOW9_FLOW_LABEL = 31, /* 3 flowLabelIPv6 */
+ NETFLOW9_ICMP_TYPE = 32, /* 2 icmpTypeCodeIPv4 */
+ NETFLOW9_MUL_IGMP_TYPE = 33, /* 1 igmpType */
+ NETFLOW9_SAMPLING_INTERVAL = 34, /* 4 */
+ /* reserved */
+ NETFLOW9_SAMPLING_ALGORITHM = 35, /* 1 */
+ NETFLOW9_FLOW_ACTIVE_TIMEOUT = 36, /* 2 flowActiveTimeOut */
+ NETFLOW9_FLOW_INAVTIVE_TIMEOUT = 37, /* 2 flowInactiveTimeout */
+ NETFLOW9_ENGINE_TYPE = 38, /* 1 */
+ NETFLOW9_ENGINE_ID = 39, /* 1 */
+ NETFLOW9_TOTAL_BYTES_EXP = 40, /* (4) exportedOctetTotalCount */
+ NETFLOW9_TOTAL_PKTS_EXP = 41, /* (4) exportedMessageTotalCount */
+ NETFLOW9_TOTAL_FLOWS_EXP = 42, /* (4) exportedFlowTotalCount */
+ /* reserved */
+ /* reserved */
+ /* reserved */
+ NETFLOW9_MPLS_TOP_LABEL_TYPE = 46, /* 1 mplsTopLabelType */
+ NETFLOW9_MPLS_TOP_LABEL_IP_ADDR = 47, /* 4 mplsTopLabelIPv4Address */
+ NETFLOW9_FLOW_SAMPLER_ID = 48, /* 1 */
+ NETFLOW9_FLOW_SAMPLER_MODE = 49, /* 1 */
+ NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL = 50, /* 4 */
+ /* reserved */
+ /* reserved */
+ /* reserved */
+ /* reserved */
+ NETFLOW9_DST_TOS = 55, /* 1 postClassOfServiceIPv4 */
+ NETFLOW9_SRC_MAC = 56, /* 6 sourceMacAddress */
+ NETFLOW9_DST_MAC = 57, /* 6 postDestinationMacAddr */
+ NETFLOW9_SRC_VLAN = 58, /* 2 vlanId */
+ NETFLOW9_DST_VLAN = 59, /* 2 postVlanId */
+ NETFLOW9_IP_PROTOCOL_VERSION = 60, /* 1 ipVersion */
+ NETFLOW9_DIRECTION = 61, /* 1 flowDirection */
+ NETFLOW9_IPV6_NEXT_HOP = 62, /* 16 ipNextHopIPv6Address */
+ NETFLOW9_BGP_IPV6_NEXT_HOP = 63, /* 16 bgpNexthopIPv6Address */
+ NETFLOW9_IPV6_OPTION_HEADERS = 64, /* 4 ipv6ExtensionHeaders */
+ /* reserved */
+ /* reserved */
+ /* reserved */
+ /* reserved */
+ /* reserved */
+ NETFLOW9_MPLS_LABEL_1 = 70, /* 3 mplsTopLabelStackEntry */
+ NETFLOW9_MPLS_LABEL_2 = 71, /* 3 mplsLabelStackEntry2 */
+ NETFLOW9_MPLS_LABEL_3 = 72, /* 3 mplsLabelStackEntry3 */
+ NETFLOW9_MPLS_LABEL_4 = 73, /* 3 mplsLabelStackEntry4 */
+ NETFLOW9_MPLS_LABEL_5 = 74, /* 3 mplsLabelStackEntry5 */
+ NETFLOW9_MPLS_LABEL_6 = 75, /* 3 mplsLabelStackEntry6 */
+ NETFLOW9_MPLS_LABEL_7 = 76, /* 3 mplsLabelStackEntry7 */
+ NETFLOW9_MPLS_LABEL_8 = 77, /* 3 mplsLabelStackEntry8 */
+ NETFLOW9_MPLS_LABEL_9 = 78, /* 3 mplsLabelStackEntry9 */
+ NETFLOW9_MPLS_LABEL_10 = 79, /* 3 mplsLabelStackEntry10 */
+
+ /* pick up usefuls from:
+ * http://www.cisco.com/c/en/us/td/docs/security/asa/special/netflow/guide/asa_netflow.html */
+ NETFLOW9_IPV4_XLATE_SRC_ADDR = 225, /* 4 NF_F_XLATE_SRC_ADDR_IPV4 */
+ NETFLOW9_IPV4_XLATE_DST_ADDR = 226, /* 4 NF_F_XLATE_DST_ADDR_IPV4 */
+ NETFLOW9_L4_XLATE_SRC_PORT = 227, /* 2 NF_F_XLATE_SRC_PORT */
+ NETFLOW9_L4_XLATE_DST_PORT = 228, /* 2 NF_F_XLATE_DST_PORT */
+ NETFLOW9_IPV6_XLATE_SRC_ADDR = 281, /* 16 NF_F_XLATE_SRC_ADDR_IPV6 */
+ NETFLOW9_IPV6_XLATE_DST_ADDR = 282, /* 16 NF_F_XLATE_DST_ADDR_IPV6 */
+
+ NETFLOW9_FIELD_MAX = NETFLOW9_IPV6_XLATE_DST_ADDR,
+};
+
+static int ipfix_map[] = {
+ /* XXX: current NFCT does not propagate delta count
+ * [IPFIX_octetDeltaCount] = NETFLOW9_IN_BYTES,
+ * [IPFIX_packetDeltaCount] = NETFLOW9_IN_PKTS,
+ */
+ [IPFIX_octetTotalCount] = NETFLOW9_IN_BYTES,
+ [IPFIX_packetTotalCount] = NETFLOW9_IN_PKTS,
+ /* [3] = NETFLOW9_FLOWS, */
+ [IPFIX_protocolIdentifier] = NETFLOW9_PROTOCOL,
+ [IPFIX_classOfServiceIPv4] = NETFLOW9_TOS,
+ [IPFIX_tcpControlBits] = NETFLOW9_TCP_FLAGS,
+ [IPFIX_sourceTransportPort] = NETFLOW9_L4_SRC_PORT,
+ [IPFIX_sourceIPv4Address] = NETFLOW9_IPV4_SRC_ADDR,
+ [IPFIX_sourceIPv4Mask] = NETFLOW9_SRC_MASK,
+ [IPFIX_ingressInterface] = NETFLOW9_INPUT_SNMP,
+ [IPFIX_destinationTransportPort] = NETFLOW9_L4_DST_PORT,
+ [IPFIX_destinationIPv4Address] = NETFLOW9_IPV4_DST_ADDR,
+ [IPFIX_destinationIPv4Mask] = NETFLOW9_DST_MASK,
+ [IPFIX_egressInterface] = NETFLOW9_OUTPUT_SNMP,
+ [IPFIX_ipNextHopIPv4Address] = NETFLOW9_IPV4_NEXT_HOP,
+ [IPFIX_bgpSourceAsNumber] = NETFLOW9_SRC_AS,
+ [IPFIX_bgpDestinationAsNumber] = NETFLOW9_DST_AS,
+ [IPFIX_bgpNextHopIPv4Address] = NETFLOW9_BGP_IPV4_NEXT_HOP,
+ [IPFIX_postMCastPacketDeltaCount] = NETFLOW9_MUL_DST_PKTS,
+ [IPFIX_postMCastOctetDeltaCount] = NETFLOW9_MUL_DST_BYTES,
+ [IPFIX_flowEndSysUpTime] = NETFLOW9_LAST_SWITCHED,
+ [IPFIX_flowStartSysUpTime] = NETFLOW9_FIRST_SWITCHED,
+ [IPFIX_postOctetDeltaCount] = NETFLOW9_OUT_BYTES,
+ [IPFIX_postPacketDeltaCount] = NETFLOW9_OUT_PKTS,
+ [IPFIX_minimumPacketLength] = 0,
+ [IPFIX_maximumPacketLength] = 0,
+ [IPFIX_sourceIPv6Address] = NETFLOW9_IPV6_SRC_ADDR,
+ [IPFIX_destinationIPv6Address] = NETFLOW9_IPV6_DST_ADDR,
+ [IPFIX_sourceIPv6Mask] = NETFLOW9_IPV6_SRC_MASK,
+ [IPFIX_destinationIPv6Mask] = NETFLOW9_IPV6_DST_MASK,
+ [IPFIX_flowLabelIPv6] = NETFLOW9_FLOW_LABEL,
+ [IPFIX_icmpTypeCodeIPv4] = NETFLOW9_ICMP_TYPE,
+ [IPFIX_igmpType] = NETFLOW9_MUL_IGMP_TYPE,
+ /* [34] = [NETFLOW9_SAMPLING_INTERVAL], */
+ /* [35] = [NETFLOW9_SAMPLING_ALGORITHM],*/
+ [IPFIX_flowActiveTimeOut] = NETFLOW9_FLOW_ACTIVE_TIMEOUT,
+ [IPFIX_flowInactiveTimeout] = NETFLOW9_FLOW_INAVTIVE_TIMEOUT,
+ /* [38] = NETFLOW9_ENGINE_TYPE, */
+ /* [39] = NETFLOW9_ENGINE_ID, */
+ [IPFIX_exportedOctetTotalCount] = NETFLOW9_TOTAL_BYTES_EXP,
+ [IPFIX_exportedMessageTotalCount] = NETFLOW9_TOTAL_PKTS_EXP,
+ [IPFIX_exportedFlowTotalCount] = NETFLOW9_TOTAL_FLOWS_EXP,
+ /* [43] = , */
+ [IPFIX_sourceIPv4Prefix] = 0,
+ [IPFIX_destinationIPv4Prefix] = 0,
+ [IPFIX_mplsTopLabelType] = NETFLOW9_MPLS_TOP_LABEL_TYPE,
+ [IPFIX_mplsTopLabelIPv4Address] = NETFLOW9_MPLS_TOP_LABEL_IP_ADDR,
+ /* [48] = NETFLOW9_FLOW_SAMPLER_ID, */
+ /* [49] = NETFLOW9_FLOW_SAMPLER_MODE, */
+ /* [50] = NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL, */
+ /* [51] = , */
+ [IPFIX_minimumTtl] = 0,
+ [IPFIX_maximumTtl] = 0,
+ [IPFIX_identificationIPv4] = 0,
+ [IPFIX_postClassOfServiceIPv4] = NETFLOW9_DST_TOS,
+ [IPFIX_sourceMacAddress] = NETFLOW9_SRC_MAC,
+ [IPFIX_postDestinationMacAddr] = NETFLOW9_DST_MAC,
+ [IPFIX_vlanId] = NETFLOW9_SRC_VLAN,
+ [IPFIX_postVlanId] = NETFLOW9_DST_VLAN,
+ [IPFIX_ipVersion] = NETFLOW9_IP_PROTOCOL_VERSION,
+ [IPFIX_flowDirection] = NETFLOW9_DIRECTION,
+ [IPFIX_ipNextHopIPv6Address] = NETFLOW9_IPV6_NEXT_HOP,
+ [IPFIX_bgpNexthopIPv6Address] = NETFLOW9_BGP_IPV6_NEXT_HOP,
+ [IPFIX_ipv6ExtensionHeaders] = NETFLOW9_IPV6_OPTION_HEADERS,
+ /* [65] = , */
+ /* [66] = , */
+ /* [67] = , */
+ /* [68] = , */
+ /* [69] = , */
+ [IPFIX_mplsTopLabelStackEntry] = NETFLOW9_MPLS_LABEL_1,
+ [IPFIX_mplsLabelStackEntry2] = NETFLOW9_MPLS_LABEL_2,
+ [IPFIX_mplsLabelStackEntry3] = NETFLOW9_MPLS_LABEL_3,
+ [IPFIX_mplsLabelStackEntry4] = NETFLOW9_MPLS_LABEL_4,
+ [IPFIX_mplsLabelStackEntry5] = NETFLOW9_MPLS_LABEL_5,
+ [IPFIX_mplsLabelStackEntry6] = NETFLOW9_MPLS_LABEL_6,
+ [IPFIX_mplsLabelStackEntry7] = NETFLOW9_MPLS_LABEL_7,
+ [IPFIX_mplsLabelStackEntry8] = NETFLOW9_MPLS_LABEL_8,
+ [IPFIX_mplsLabelStackEntry9] = NETFLOW9_MPLS_LABEL_9,
+ [IPFIX_mplsLabelStackEntry10] = NETFLOW9_MPLS_LABEL_10,
+ /* [80 - 224] = , */
+ [IPFIX_postNATSourceIPv4Address] = NETFLOW9_IPV4_XLATE_SRC_ADDR,
+ [IPFIX_postNATDestinationIPv4Address] = NETFLOW9_IPV4_XLATE_DST_ADDR,
+ [IPFIX_postNAPTSourceTransportPort] = NETFLOW9_L4_XLATE_SRC_PORT,
+ [IPFIX_postNAPTDestinationTransportPort]= NETFLOW9_L4_XLATE_DST_PORT,
+ [IPFIX_postNATSourceIPv6Address] = NETFLOW9_IPV6_XLATE_SRC_ADDR,
+ [IPFIX_postNATDestinationIPv6Address] = NETFLOW9_IPV6_XLATE_DST_ADDR,
+};
+
+struct ulogd_netflow9_template {
+ struct llist_head list;
+ struct nfct_bitmask *bitmask;
+ int until_template; /* decide if it's time to retransmit template */
+ int offset[FOI_MAX]; /* direction related field offset from data head */
+ int tmplset_len, dataset_len;
+ struct netflow9_set_hdr *template;
+ struct netflow9_set_hdr *databuf;
+ int datapos;
+};
+
+struct netflow9_instance {
+ int fd; /* socket that we use for sending NetFlow v9 data */
+ int uptime_fd; /* /proc/uptime to set sysUpTime */
+ uint16_t next_template_id;
+ struct llist_head template_list; /* ulogd_netflow9_template */
+ struct nfct_bitmask *valid_bitmask; /* bitmask of valid keys */
+ uint32_t seq;
+ unsigned int ikey_count[CII_MAX]; /* ikey indexes to counter fields */
+ struct netflow9_msg_hdr nflow9_msghdr;
+ struct iovec *iovecs; /* index 0 is reserved for nflow9_msghdr */
+ unsigned int iovcnt;
+ unsigned int corksets_max; /* cork limit include template */
+ unsigned int msglen;
+};
+
+#define UPTIME_FILE "/proc/uptime" /* for uptime_fd */
+#define ULOGD_NETFLOW9_TEMPL_BASE 256 /* 5.2 Template FlowSet Format
+ * for next_template_id */
+/*
+ * This function returns file or connected socket descriptor
+ * specified by URL like dest:
+ * <proto>://<filename or address>[:port]
+ * proto is either one of tcp, udp, sctp and file. port is required
+ * in case of socket. file will be stdout if proto is file and
+ * no filename specified.
+ */
+static int open_connect_descriptor(const char *dest)
+{
+ char *proto = NULL, *host, *port;
+ struct addrinfo hint, *result = NULL, *rp = NULL;
+ int ret, fd = -1;
+
+ proto = strdup(dest);
+ if (proto == NULL) {
+ ulogd_log(ULOGD_ERROR, "strdup: %s\n", strerror(errno));
+ return -1;
+ }
+ host = strchr(proto, ':');
+ if (host == NULL) {
+ ulogd_log(ULOGD_ERROR, "invalid dest\n");
+ goto error;
+ }
+ *host++ = '\0';
+ if (*host++ != '/') {
+ ulogd_log(ULOGD_ERROR, "invalid dest\n");
+ goto error;
+ }
+ if (*host++ != '/') {
+ ulogd_log(ULOGD_ERROR, "invalid dest\n");
+ goto error;
+ }
+
+ /* file */
+ if (!strcasecmp(proto, "file")) {
+ if (strlen(host) == 0)
+ fd = STDOUT_FILENO;
+ else
+ fd = open(host, O_CREAT|O_WRONLY|O_APPEND, 0600);
+ free(proto);
+ return fd;
+ }
+
+ /* socket */
+ port = strrchr(host, ':');
+ if (port == NULL) {
+ ulogd_log(ULOGD_ERROR, "no destination port\n");
+ goto error;
+ }
+ *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);
+ goto error;
+ }
+
+ 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;
+ goto error;
+ }
+
+ /* rp == NULL indicates could not get valid sockfd */
+ 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) {
+ switch (errno) {
+ case EACCES:
+ case EAFNOSUPPORT:
+ case EINVAL:
+ case EPROTONOSUPPORT:
+ /* try next result */
+ continue;
+ default:
+ ulogd_log(ULOGD_ERROR, "socket error: %s\n",
+ strerror(errno));
+ rp = NULL;
+ goto error;
+ }
+ }
+ ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ (void *)&on, sizeof(on));
+ if (ret < 0) {
+ ulogd_log(ULOGD_ERROR, "error on set SO_REUSEADDR: %s",
+ strerror(errno));
+ close(fd);
+ rp = NULL;
+ break;
+ }
+
+ if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break;
+ close(fd);
+ }
+
+error:
+ if (proto)
+ free(proto);
+ if (result)
+ freeaddrinfo(result);
+
+ if (rp == NULL) {
+ ulogd_log(ULOGD_ERROR, "could not connect\n");
+ fd = -1;
+ }
+
+ return fd;
+}
+
+/*
+ * This functions stores ulogd key value, specifued by key into
+ * buf. buflen means buf len and is checked exceeds. This function
+ * returns the copied length or -1 on error.
+ */
+static int ulogd_key_putn(struct ulogd_key *key, void *buf, int buflen)
+{
+ int ret = -1;
+
+ switch (key->type) {
+ case ULOGD_RET_INT8:
+ case ULOGD_RET_UINT8:
+ case ULOGD_RET_BOOL:
+ ret = sizeof(uint8_t);
+ if (buflen - ret >= 0)
+ *(uint8_t *)buf = ikey_get_u8(key);
+ break;
+ case ULOGD_RET_INT16:
+ case ULOGD_RET_UINT16:
+ ret = sizeof(uint16_t);
+ if (buflen - ret >= 0)
+ *(uint16_t *)buf = htons(ikey_get_u16(key));
+ break;
+ case ULOGD_RET_INT32:
+ case ULOGD_RET_UINT32:
+ ret = sizeof(uint32_t);
+ if (buflen - ret >= 0)
+ *(uint32_t *)buf = htonl(ikey_get_u32(key));
+ break;
+ case ULOGD_RET_IPADDR:
+ ret = sizeof(uint32_t);
+ if (buflen - ret >= 0)
+ *(uint32_t *)buf = ikey_get_u32(key);
+ break;
+ case ULOGD_RET_INT64:
+ case ULOGD_RET_UINT64:
+ ret = sizeof(uint64_t);
+ if (buflen - ret >= 0)
+ *(uint64_t *)buf = __be64_to_cpu(ikey_get_u64(key));
+ break;
+ case ULOGD_RET_IP6ADDR:
+ ret = 16;
+ if (buflen - ret >= 0)
+ memcpy(buf, ikey_get_u128(key), 16);
+ break;
+ case ULOGD_RET_STRING:
+ ret = strlen(key->u.value.ptr);
+ if (buflen - ret >= 0)
+ memcpy(buf, key->u.value.ptr, ret);
+ break;
+ case ULOGD_RET_RAW:
+ ulogd_log(ULOGD_NOTICE, "put raw data in network byte order "
+ "`%s' type 0x%x\n", key->name, key->type);
+ ret = key->len;
+ if (buflen - ret >= 0)
+ memcpy(buf, key->u.value.ptr, ret);
+ break;
+ default:
+ ulogd_log(ULOGD_ERROR, "unknown size - key "
+ "`%s' type 0x%x\n", key->name, key->type);
+ return -1;
+ break;
+ }
+
+ if (buflen < 0)
+ ulogd_log(ULOGD_ERROR, "excess buflen, do nothing.\n");
+
+ return ret;
+}
+
+static struct ulogd_netflow9_template *
+alloc_ulogd_netflow9_template(struct ulogd_pluginstance *upi,
+ struct nfct_bitmask *bm)
+{
+ struct netflow9_instance *ii = (struct netflow9_instance *)&upi->private;
+ struct ulogd_netflow9_template *tmpl;
+ unsigned int i;
+ int tmpl_len = 0, data_len = 0;
+
+ for (i = 0; i < upi->input.num_keys; i++) {
+ if (!nfct_bitmask_test_bit(bm, i))
+ continue;
+
+ /* ignore reply for unidirection */
+ if (i == ii->ikey_count[CII_REPLY_RAW_PKTLEN_DELTA]
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTCOUNT_DELTA]
+ || i == ii->ikey_count[CII_REPLY_IP_PROTOCOL]
+ /* XXX: will be removed if NFCT propagate delta counter */
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTLEN]
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTCOUNT])
+ continue;
+
+ tmpl_len += sizeof(struct netflow9_templ_rec);
+ data_len += ulogd_key_size(&upi->input.keys[i]);
+ }
+
+ tmpl = calloc(1, sizeof(struct ulogd_netflow9_template));
+ if (tmpl == NULL)
+ return NULL;
+
+ for (i = 0; i < FOI_MAX; i++)
+ tmpl->offset[i] = -1;
+
+ tmpl->bitmask = nfct_bitmask_clone(bm);
+ if (!tmpl->bitmask)
+ goto free_tmpl;
+
+ tmpl->dataset_len = sizeof(struct netflow9_set_hdr) + data_len;
+ tmpl->tmplset_len = sizeof(struct netflow9_set_hdr)
+ + sizeof(struct netflow9_templ_hdr) + tmpl_len;
+ /* 5.3. Data FlowSet Format / Padding */
+ tmpl->dataset_len = (tmpl->dataset_len + 3U) & ~3U;
+ tmpl->tmplset_len = (tmpl->tmplset_len + 3U) & ~3U;
+
+ tmpl->template = calloc(1, tmpl->tmplset_len);
+ if (tmpl->template == NULL)
+ goto free_bitmask;
+ tmpl->databuf = calloc(ii->corksets_max, tmpl->dataset_len);
+ if (tmpl->databuf == NULL)
+ goto free_template;
+
+ return tmpl;
+
+free_template:
+ free(tmpl->template);
+free_bitmask:
+ free(tmpl->bitmask);
+free_tmpl:
+ free(tmpl);
+
+ return NULL;
+}
+
+/* Build the NetFlow v9 template from the input keys */
+static struct ulogd_netflow9_template *
+build_template_for_bitmask(struct ulogd_pluginstance *upi,
+ struct nfct_bitmask *bm)
+{
+ struct netflow9_instance *ii
+ = (struct netflow9_instance *)&upi->private;
+ struct ulogd_netflow9_template *tmpl;
+ struct netflow9_templ_hdr *tmpl_hdr;
+ struct netflow9_templ_rec *tmpl_rec;
+ struct netflow9_set_hdr *set_hdr;
+ uint16_t field_count = 0;
+ unsigned int i, j, offset = 0;
+
+ tmpl = alloc_ulogd_netflow9_template(upi, bm);
+ if (tmpl == NULL)
+ return NULL;
+
+ /* build template records */
+ tmpl_rec = (void *)tmpl->template
+ + sizeof(struct netflow9_set_hdr)
+ + sizeof(struct netflow9_templ_hdr);
+ for (i = 0; i < upi->input.num_keys; i++) {
+ struct ulogd_key *key = &upi->input.keys[i];
+ int length = ulogd_key_size(key);
+
+ if (!nfct_bitmask_test_bit(tmpl->bitmask, i))
+ continue;
+
+ /* XXX: search swap related field and set its offset */
+ for (j = 0; j < FOI_MAX; j++) {
+ if (!strncmp(key->name, dir_keys[j],
+ strlen(dir_keys[j]))) {
+ tmpl->offset[j] = offset;
+ break;
+ }
+ }
+
+ if (i == ii->ikey_count[CII_REPLY_RAW_PKTLEN_DELTA]
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTCOUNT_DELTA]
+ || i == ii->ikey_count[CII_REPLY_IP_PROTOCOL]
+ /* XXX: will be removed if NFCT propagate delta counter */
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTLEN]
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTCOUNT])
+ continue;
+
+ tmpl_rec->type = htons(ipfix_map[key->ipfix.field_id]);
+ tmpl_rec->length = htons(length);
+ tmpl_rec++;
+ field_count++;
+ offset += length;
+ }
+
+ /* initialize template set header */
+ tmpl->template->set_id = htons(0); /* 5.2 Template FlowSet Format */
+ tmpl->template->length = htons(tmpl->tmplset_len);
+
+ /* initialize template record header */
+ tmpl_hdr = (void *)tmpl->template + sizeof(struct netflow9_set_hdr);
+ tmpl_hdr->template_id = htons(ii->next_template_id++);
+ tmpl_hdr->field_count = htons(field_count);
+
+ /* initialize data buffer */
+ for (i = 0; i < ii->corksets_max; i++) {
+ set_hdr = (void *)tmpl->databuf + i * tmpl->dataset_len;
+ set_hdr->set_id = tmpl_hdr->template_id;
+ set_hdr->length = htons(tmpl->dataset_len);
+ }
+
+ return tmpl;
+}
+
+static struct ulogd_netflow9_template *
+find_template_for_bitmask(struct ulogd_pluginstance *upi,
+ struct nfct_bitmask *bm)
+{
+ struct netflow9_instance *ii
+ = (struct netflow9_instance *)&upi->private;
+ struct ulogd_netflow9_template *tmpl;
+
+ /* FIXME: this can be done more efficient! */
+ llist_for_each_entry(tmpl, &ii->template_list, list) {
+ if (nfct_bitmask_equal(bm, tmpl->bitmask))
+ return tmpl;
+ }
+
+ return NULL;
+}
+
+static int put_data_records(struct ulogd_pluginstance *upi,
+ struct ulogd_netflow9_template *tmpl,
+ void *buf, int buflen)
+{
+ struct netflow9_instance *ii
+ = (struct netflow9_instance *)&upi->private;
+ struct ulogd_key *keys = upi->input.keys;
+ unsigned int i;
+ int ret, len = 0;
+
+ for (i = 0; i < upi->input.num_keys; i++) {
+ if (!nfct_bitmask_test_bit(tmpl->bitmask, i))
+ continue;
+
+ /* store orig temporarily to (unidirectional) counter */
+ if (i == ii->ikey_count[CII_REPLY_RAW_PKTLEN_DELTA]
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTCOUNT_DELTA]
+ || i == ii->ikey_count[CII_REPLY_IP_PROTOCOL]
+ /* XXX: will be removed if NFCT propagate delta counter */
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTLEN]
+ || i == ii->ikey_count[CII_REPLY_RAW_PKTCOUNT])
+ continue;
+
+ ret = ulogd_key_putn(&keys[i], buf + len, buflen);
+ if (ret < 0)
+ return ret;
+
+ len += ret;
+ buflen -= ret;
+ if (buflen < 0)
+ return buflen;
+ }
+
+ return len;
+}
+
+static void swap(void *data, ssize_t size, int pos1, int pos2)
+{
+ uint8_t tmp[16] = {}; /* 16: ip6 addr len */
+ memcpy(tmp, data + pos1, size);
+ memcpy(data + pos1, data +pos2, size);
+ memcpy(data + pos2, tmp, size);
+}
+
+#define TOF(i) tmpl->offset[(i)]
+
+static int orig_swap(struct ulogd_netflow9_template *tmpl,
+ uint8_t family, void *buf)
+{
+ switch (family) {
+ case AF_INET:
+ swap(buf, sizeof(uint32_t),
+ TOF(FOI_REPLY_IP_SADDR), TOF(FOI_REPLY_IP_DADDR));
+ break;
+ case AF_INET6:
+ swap(buf, sizeof(struct in6_addr),
+ TOF(FOI_REPLY_IP6_SADDR), TOF(FOI_REPLY_IP6_DADDR));
+ break;
+ default:
+ ulogd_log(ULOGD_ERROR, "unknown family: %d", family);
+ return -1;
+ }
+ if (TOF(FOI_REPLY_L4_SPORT) >= 0
+ && TOF(FOI_REPLY_L4_DPORT) >= 0)
+ swap(buf, sizeof(uint16_t),
+ TOF(FOI_REPLY_L4_SPORT), TOF(FOI_REPLY_L4_DPORT));
+
+ return 0;
+}
+
+static int reply_swap(struct ulogd_netflow9_template *tmpl,
+ uint8_t family, void *buf)
+{
+ switch (family) {
+ case AF_INET:
+ swap(buf, sizeof(uint32_t),
+ TOF(FOI_ORIG_IP_SADDR), TOF(FOI_ORIG_IP_DADDR));
+ break;
+ case AF_INET6:
+ swap(buf, sizeof(struct in6_addr),
+ TOF(FOI_ORIG_IP_SADDR), TOF(FOI_ORIG_IP_DADDR));
+ break;
+ default:
+ ulogd_log(ULOGD_ERROR, "unknown family: %d", family);
+ return -1;
+ }
+ if (TOF(FOI_ORIG_L4_SPORT) >= 0
+ && TOF(FOI_ORIG_L4_DPORT) >= 0)
+ swap(buf, sizeof(uint16_t),
+ TOF(FOI_ORIG_L4_SPORT), TOF(FOI_ORIG_L4_DPORT));
+ if (TOF(FOI_IF_INPUT) >= 0 && TOF(FOI_IF_OUTPUT) >= 0)
+ swap(buf, sizeof(uint16_t),
+ TOF(FOI_IF_INPUT), TOF(FOI_IF_OUTPUT));
+ if (TOF(FOI_FLOW_DIR) >= 0)
+ *(uint8_t *)(buf + TOF(FOI_FLOW_DIR))
+ = !*(uint8_t *)(buf + TOF(FOI_FLOW_DIR));
+
+ return 0;
+}
+
+static int swap_by_dir(struct ulogd_netflow9_template *tmpl,
+ void *buf, uint8_t family,
+ int direction,
+ uint64_t bytes, uint64_t packets)
+{
+ switch (direction) {
+ case NFLOW9_DIR_ORIG:
+ if (orig_swap(tmpl, family, buf) < 0)
+ return -1;
+ break;
+
+ case NFLOW9_DIR_REPLY:
+ if (reply_swap(tmpl, family, buf) < 0)
+ return -1;
+ break;
+ default:
+ ulogd_log(ULOGD_ERROR, "unknown dir: %d", direction);
+ return -1;
+ }
+
+ if (TOF(FOI_IN_BYTES) >= 0)
+ *(uint64_t *)(buf + TOF(FOI_IN_BYTES)) = __cpu_to_be64(bytes);
+ if (TOF(FOI_IN_PKTS) >= 0)
+ *(uint64_t *)(buf + TOF(FOI_IN_PKTS)) = __cpu_to_be64(packets);
+ /* XXX: will be removed if NFCT propagate delta counter */
+ if (TOF(FOI_XXX_IN_BYTES) >= 0)
+ *(uint64_t *)(buf + TOF(FOI_XXX_IN_BYTES)) = __cpu_to_be64(bytes);
+ if (TOF(FOI_XXX_IN_PKTS) >= 0)
+ *(uint64_t *)(buf + TOF(FOI_XXX_IN_PKTS)) = __cpu_to_be64(packets);
+
+ return 0;
+}
+#undef TOF
+
+static int nflow9_direction(struct ulogd_pluginstance *upi, uint8_t *family,
+ uint64_t *orig_bytes, uint64_t *orig_packets,
+ uint64_t *reply_bytes, uint64_t *reply_packets)
+{
+ struct netflow9_instance *ii
+ = (struct netflow9_instance *)&upi->private;
+ struct ulogd_key *keys = upi->input.keys;
+ unsigned int sentry = upi->input.num_keys;
+ int ret = 0;
+
+#define IKC(i) ii->ikey_count[(i)]
+ if (IKC(CII_ORIG_RAW_PKTLEN_DELTA) != sentry
+ && pp_is_valid(keys, IKC(CII_ORIG_RAW_PKTLEN_DELTA))) {
+ *orig_bytes
+ = ikey_get_u64(&keys[IKC(CII_ORIG_RAW_PKTLEN_DELTA)]);
+ if (*orig_bytes > 0) {
+ *orig_packets
+ = ikey_get_u64(&keys[IKC(CII_ORIG_RAW_PKTCOUNT_DELTA)]);
+ ret |= NFLOW9_DIR_ORIG;
+ }
+ }
+ /* XXX: will be removed if NFCT propagate delta counter */
+ else if (IKC(CII_ORIG_RAW_PKTLEN) != sentry
+ && pp_is_valid(keys, IKC(CII_ORIG_RAW_PKTLEN))) {
+ *orig_bytes
+ = ikey_get_u64(&keys[IKC(CII_ORIG_RAW_PKTLEN)]);
+ if (*orig_bytes > 0) {
+ *orig_packets
+ = ikey_get_u64(&keys[IKC(CII_ORIG_RAW_PKTCOUNT)]);
+ ret |= NFLOW9_DIR_ORIG;
+ }
+ }
+ if (IKC(CII_REPLY_RAW_PKTLEN_DELTA) != sentry
+ && pp_is_valid(keys, IKC(CII_REPLY_RAW_PKTLEN_DELTA))) {
+ *reply_bytes
+ = ikey_get_u64(&keys[IKC(CII_REPLY_RAW_PKTLEN_DELTA)]);
+ if (*reply_bytes > 0) {
+ *reply_packets
+ = ikey_get_u64(&keys[IKC(CII_REPLY_RAW_PKTCOUNT_DELTA)]);
+ ret |= NFLOW9_DIR_REPLY;
+ }
+ }
+ /* XXX: will be removed if NFCT propagate delta counter */
+ else if (IKC(CII_REPLY_RAW_PKTLEN) != sentry
+ && pp_is_valid(keys, IKC(CII_REPLY_RAW_PKTLEN))) {
+ *reply_bytes
+ = ikey_get_u64(&keys[IKC(CII_REPLY_RAW_PKTLEN)]);
+ if (*reply_bytes > 0) {
+ *reply_packets
+ = ikey_get_u64(&keys[IKC(CII_REPLY_RAW_PKTCOUNT)]);
+ ret |= NFLOW9_DIR_REPLY;
+ }
+ }
+ *family = ikey_get_u8(&keys[IKC(CII_FAMILY)]);
+#undef IKC
+ return ret;
+}
+
+static void *data_record(struct netflow9_instance *ii,
+ struct ulogd_netflow9_template *tmpl)
+{
+ void *records;
+
+ /* data flowset */
+ ii->iovecs[ii->iovcnt].iov_base = (void *)tmpl->databuf
+ + tmpl->datapos * tmpl->dataset_len;
+ ii->iovecs[ii->iovcnt].iov_len = tmpl->dataset_len;
+
+ /* clear data records */
+ records = ii->iovecs[ii->iovcnt].iov_base
+ + sizeof(struct netflow9_set_hdr);
+ memset(records, 0,
+ tmpl->dataset_len - sizeof(struct netflow9_set_hdr));
+
+ /* increment position */
+ ii->iovcnt++;
+ tmpl->datapos++;
+
+ return records;
+}
+
+static int insert_template(struct ulogd_pluginstance *upi,
+ struct ulogd_netflow9_template *tmpl)
+{
+ struct netflow9_instance *ii
+ = (struct netflow9_instance *)&upi->private;
+
+ if (tmpl->until_template != 0) {
+ tmpl->until_template--;
+ return 0;
+ }
+ tmpl->until_template = nth_template_ce(upi);
+
+ ii->iovecs[ii->iovcnt].iov_base = tmpl->template;
+ ii->iovecs[ii->iovcnt].iov_len = tmpl->tmplset_len;
+
+ ii->iovcnt++;
+ ii->msglen += tmpl->tmplset_len;
+
+ return 1;
+}
+
+static int build_netflow9_msg(struct ulogd_pluginstance *upi,
+ struct ulogd_netflow9_template *tmpl)
+{
+ struct netflow9_instance *ii = (struct netflow9_instance *)&upi->private;
+ uint8_t family = 0;
+ uint64_t obytes = 0, opackets = 0;
+ uint64_t rbytes = 0, rpackets = 0;
+ int dir;
+ void *buf;
+
+ insert_template(upi, tmpl);
+ buf = data_record(ii, tmpl);
+ if (put_data_records(upi, tmpl, buf, tmpl->dataset_len) < 0) {
+ ulogd_log(ULOGD_ERROR, "could not build netflow v9 dataset\n");
+ return -1;
+ }
+
+ dir = nflow9_direction(upi, &family,
+ &obytes, &opackets, &rbytes, &rpackets);
+ switch (dir) {
+ case NFLOW9_DIR_ORIG:
+ swap_by_dir(tmpl, buf, family, dir, obytes, opackets);
+ break;
+
+ case NFLOW9_DIR_REPLY:
+ swap_by_dir(tmpl, buf, family, dir, rbytes, rpackets);
+ break;
+
+ case NFLOW9_DIR_BOTH:
+ swap_by_dir(tmpl, buf, family, NFLOW9_DIR_ORIG,
+ obytes, opackets);
+ ii->msglen += tmpl->dataset_len;
+ buf = data_record(ii, tmpl);
+ if (put_data_records(upi, tmpl, buf, tmpl->dataset_len) < 0) {
+ ulogd_log(ULOGD_ERROR,
+ "could not build netflow v9 dataset");
+ return -1;
+ }
+ swap_by_dir(tmpl, buf, family, NFLOW9_DIR_REPLY,
+ rbytes, rpackets);
+ break;
+
+ case NFLOW9_DIR_NONE:
+ ulogd_log(ULOGD_DEBUG, "receive zero counter data\n");
+ return 0;
+ break;
+
+ default:
+ ulogd_log(ULOGD_ERROR, "nflow9_direction() returns invalid");
+ return -1;
+ break;
+ }
+
+ ii->msglen += tmpl->dataset_len;
+ return 1;
+}
+
+static uint32_t uptime_millis(int fd)
+{
+ char buf[1024] = {0};
+ double up;
+ int nread;
+
+ lseek(fd, 0, SEEK_SET);
+ nread = read(fd, buf, sizeof(buf) - 1);
+ if (nread == -1)
+ return 0;
+ if (sscanf(buf, "%lf", &up) != 1)
+ return 0;
+ return (uint32_t)(up * 1000);
+}
+
+static void reset_counters(struct netflow9_instance *ii)
+{
+ struct ulogd_netflow9_template *tmpl;
+
+ llist_for_each_entry(tmpl, &ii->template_list, list) {
+ tmpl->datapos = 0;
+ }
+ ii->msglen = 0;
+ /* pos 0 is reserved for netflow9_msg_hdr */
+ ii->iovcnt = 1;
+}
+
+static ssize_t send_netflow9(struct netflow9_instance *ii)
+{
+ ssize_t nsent;
+
+ ii->nflow9_msghdr.sys_uptime
+ = htonl((uint32_t)uptime_millis(ii->uptime_fd));
+ ii->nflow9_msghdr.unix_secs = htonl((uint32_t)(time(NULL)));
+ ii->nflow9_msghdr.count = htons(ii->iovcnt - 1);
+ ii->nflow9_msghdr.sequence_number = htonl(ii->seq++);
+ ii->msglen += sizeof(struct netflow9_msg_hdr);
+
+#ifdef DEBUG_TMMAP
+ nflow9_fprintf_header(stdout, ii);
+ fflush(stdout);
+#endif
+ nsent = writev(ii->fd, ii->iovecs, ii->iovcnt);
+ if (nsent != ii->msglen) {
+ if (nsent == -1) {
+ ulogd_log(ULOGD_ERROR, "failed to send: %s\n",
+ strerror(errno));
+ } else {
+ ulogd_log(ULOGD_ERROR, "send - arg: %d, ret: %d\n",
+ ii->msglen, nsent);
+ }
+ }
+
+ return nsent;
+}
+
+static int output_netflow9(struct ulogd_pluginstance *upi)
+{
+ struct netflow9_instance *ii
+ = (struct netflow9_instance *)&upi->private;
+ struct ulogd_netflow9_template *template;
+ unsigned int i;
+ int ret;
+
+ /* FIXME: it would be more cache efficient if the IS_VALID
+ * flags would be a separate bitmask outside of the array.
+ * ulogd core could very easily flush it after every packet,
+ * too. */
+ nfct_bitmask_clear(ii->valid_bitmask);
+
+ for (i = 0; i < upi->input.num_keys; i++) {
+ struct ulogd_key *key = &upi->input.keys[i];
+ int length = ulogd_key_size(key);
+
+ if (length < 0 || length > 0xfffe)
+ continue;
+ if (!(key->u.source->flags & ULOGD_RETF_VALID))
+ continue;
+ if (key->ipfix.vendor != IPFIX_VENDOR_IETF
+ && key->ipfix.vendor != IPFIX_VENDOR_REVERSE)
+ continue;
+ if (ipfix_map[key->ipfix.field_id] == 0)
+ continue;
+
+ /* include both orig. reply. */
+ nfct_bitmask_set_bit(ii->valid_bitmask, i);
+ }
+
+ /* lookup template ID for this bitmask */
+ template = find_template_for_bitmask(upi, ii->valid_bitmask);
+ if (!template) {
+ ulogd_log(ULOGD_INFO, "building new template\n");
+ template = build_template_for_bitmask(upi, ii->valid_bitmask);
+ if (!template) {
+ ulogd_log(ULOGD_ERROR, "can't build new template!\n");
+ return ULOGD_IRET_ERR;
+ }
+ llist_add(&template->list, &ii->template_list);
+ }
+
+ ret = build_netflow9_msg(upi, template);
+ if (ret == -1) {
+ ulogd_log(ULOGD_ERROR, "can't build message\n");
+ reset_counters(ii);
+ return ULOGD_IRET_ERR;
+ }
+
+ /* XXX: magic number. practical UDP max */
+ if (ii->msglen > 65507 - sizeof(struct netflow9_msg_hdr)) {
+ ulogd_log(ULOGD_NOTICE, "We may have lost data since message "
+ "length exceeds practical UDP max size, then reducing "
+ "corksets_max to %d\n", ii->iovcnt);
+ ii->corksets_max = ii->iovcnt;
+ } else if (ii->iovcnt - 1 + 3 < ii->corksets_max) {
+ /* - 1 reserved for header
+ * + 3 for sending template, orig and reply on next */
+ return ULOGD_IRET_OK;
+ }
+
+ ret = send_netflow9(ii);
+ reset_counters(ii);
+ if (ret < 0)
+ return ULOGD_IRET_ERR;
+
+ return ULOGD_IRET_OK;
+}
+
+static int start_netflow9(struct ulogd_pluginstance *upi)
+{
+ struct netflow9_instance *ii = (struct netflow9_instance *)&upi->private;
+ int ret = -ENOMEM;
+
+ ulogd_log(ULOGD_DEBUG, "starting netflow9\n");
+
+ /* +1 for nflow9_msghdr */
+ ii->iovecs = calloc(ii->corksets_max + 1, sizeof(struct iovec));
+ if (ii->iovecs == NULL)
+ return ret;
+
+ ii->valid_bitmask = nfct_bitmask_new(upi->input.num_keys);
+ if (!ii->valid_bitmask)
+ goto out_iovecs_free;
+
+ INIT_LLIST_HEAD(&ii->template_list);
+
+ ii->fd = open_connect_descriptor(dest_ce(upi));
+ if (ii->fd < 0) {
+ ulogd_log(ULOGD_ERROR, "could not connect: %s\n",
+ strerror(errno));
+ goto out_bm_free;
+ }
+
+ ii->uptime_fd = open(UPTIME_FILE, O_RDONLY);
+ if (ii->uptime_fd == -1) {
+ ulogd_log(ULOGD_ERROR, "cound not open file: %s\n",
+ UPTIME_FILE);
+ goto out_close_sock;
+ }
+
+ /* initialize netflow v9 message header */
+ ii->nflow9_msghdr.version = htons(9);
+ ii->nflow9_msghdr.source_id = htonl(domain_ce(upi));
+ ii->iovecs[0].iov_base = &ii->nflow9_msghdr;
+ ii->iovecs[0].iov_len = sizeof(ii->nflow9_msghdr);
+
+ ii->next_template_id = ULOGD_NETFLOW9_TEMPL_BASE;
+ reset_counters(ii);
+
+#ifdef DEBUG_TMMAP
+ mmfd = fileno(tmpfile());
+ if (mmfd == -1) {
+ perror("tmpfile");
+ exit(EXIT_FAILURE);
+ }
+ mmaddr = mmap(NULL, 65507, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, mmfd, 0);
+ if (mmaddr == MAP_FAILED) {
+ perror("mmap");
+ exit(EXIT_FAILURE);
+ }
+#endif
+ return 0;
+
+out_close_sock:
+ close(ii->fd);
+out_bm_free:
+ nfct_bitmask_destroy(ii->valid_bitmask);
+ ii->valid_bitmask = NULL;
+out_iovecs_free:
+ free(ii->iovecs);
+
+ return ret;
+}
+
+static int stop_netflow9(struct ulogd_pluginstance *upi)
+{
+ struct netflow9_instance *ii = (struct netflow9_instance *)&upi->private;
+ struct ulogd_netflow9_template *tmpl, *n;
+
+ if (ii->iovcnt > 1) {
+ ulogd_log(ULOGD_INFO, "send remainded: %d\n", ii->iovcnt);
+ send_netflow9(ii); /* ignore retval, log error only */
+ }
+ reset_counters(ii);
+
+ llist_for_each_entry_safe(tmpl, n, &ii->template_list, list) {
+ nfct_bitmask_destroy(tmpl->bitmask);
+ free(tmpl->template);
+ free(tmpl->databuf);
+ llist_del(&tmpl->list);
+ free(tmpl);
+ }
+ close(ii->uptime_fd);
+ close(ii->fd);
+ nfct_bitmask_destroy(ii->valid_bitmask);
+ ii->valid_bitmask = NULL;
+ free(ii->iovecs);
+
+ return 0;
+}
+
+static void signal_handler_netflow9(struct ulogd_pluginstance *upi, int signal)
+{
+ switch (signal) {
+ case SIGHUP:
+ ulogd_log(ULOGD_NOTICE, "netflow9: reopening connection\n");
+ stop_netflow9(upi);
+ start_netflow9(upi);
+ break;
+ default:
+ break;
+ }
+}
+
+static int configure_netflow9(struct ulogd_pluginstance *upi,
+ struct ulogd_pluginstance_stack *stack)
+{
+ struct netflow9_instance *ii = (struct netflow9_instance *)&upi->private;
+ unsigned int i, j;
+ int ret;
+
+ /* FIXME: error handling */
+ ulogd_log(ULOGD_DEBUG, "parsing config file section %s\n", upi->id);
+ ret = config_parse_file(upi->id, upi->config_kset);
+ if (ret < 0)
+ return ret;
+
+ if (corksets_max_ce(upi) < 3) {
+ ulogd_log(ULOGD_ERROR, "corksets_max is required "
+ "more than 3 from implementation perspective\n");
+ return -EINVAL;
+ }
+ ii->corksets_max = (unsigned int)corksets_max_ce(upi);
+
+ /* postpone address lookup to ->start() time, since we want to
+ * re-lookup an address on SIGHUP */
+ ret = ulogd_wildcard_inputkeys(upi);
+ if (ret < 0)
+ return ret;
+
+ /* search key index for direction conditions and converts */
+ for (i = 0; i < CII_MAX; i++)
+ ii->ikey_count[i] = upi->input.num_keys;
+ for (i = 0; i < upi->input.num_keys; i++) {
+ for (j = 0; j < CII_MAX; j++) {
+ if (!strncmp(upi->input.keys[i].name, count_keys[j],
+ strlen(count_keys[j]))) {
+ ii->ikey_count[j] = i;
+ break;
+ }
+ }
+ }
+ /* XXX: check ii->ikey_count validity */
+
+ return 0;
+}
+
+static struct ulogd_plugin netflow9_plugin = {
+ .name = "NFLOW9",
+ .input = {
+ .type = ULOGD_DTYPE_FLOW,
+ },
+ .output = {
+ .type = ULOGD_DTYPE_SINK,
+ },
+ .config_kset = &netflow9_kset,
+ .priv_size = sizeof(struct netflow9_instance),
+
+ .configure = &configure_netflow9,
+ .start = &start_netflow9,
+ .stop = &stop_netflow9,
+
+ .interp = &output_netflow9,
+ .signal = &signal_handler_netflow9,
+ .version = VERSION,
+};
+
+void __attribute__ ((constructor)) init(void);
+
+void init(void)
+{
+ ulogd_register_plugin(&netflow9_plugin);
+}
+
+#ifdef DEBUG_TMMAP
+static char *nflow9_field_name[] = {
+ [NETFLOW9_IN_BYTES] = "IN_BYTES",
+ [NETFLOW9_IN_PKTS] = "IN_PKTS",
+ [NETFLOW9_FLOWS] = "FLOWS",
+ [NETFLOW9_PROTOCOL] = "PROTOCOL",
+ [NETFLOW9_TOS] = "TOS",
+ [NETFLOW9_TCP_FLAGS] = "TCP_FLAGS",
+ [NETFLOW9_L4_SRC_PORT] = "L4_SRC_PORT",
+ [NETFLOW9_IPV4_SRC_ADDR] = "IPV4_SRC_ADDR",
+ [NETFLOW9_SRC_MASK] = "SRC_MASK",
+ [NETFLOW9_INPUT_SNMP] = "INPUT_SNMP",
+ [NETFLOW9_L4_DST_PORT] = "L4_DST_PORT",
+ [NETFLOW9_IPV4_DST_ADDR] = "IPV4_DST_ADDR",
+ [NETFLOW9_DST_MASK] = "DST_MASK",
+ [NETFLOW9_OUTPUT_SNMP] = "OUTPUT_SNMP",
+ [NETFLOW9_IPV4_NEXT_HOP] = "IPV4_NEXT_HOP",
+ [NETFLOW9_SRC_AS] = "SRC_AS",
+ [NETFLOW9_DST_AS] = "DST_AS",
+ [NETFLOW9_BGP_IPV4_NEXT_HOP] = "BGP_IPV4_NEXT_HOP",
+ [NETFLOW9_MUL_DST_PKTS] = "MUL_DST_PKTS",
+ [NETFLOW9_MUL_DST_BYTES] = "MUL_DST_BYTES",
+ [NETFLOW9_LAST_SWITCHED] = "LAST_SWITCHED",
+ [NETFLOW9_FIRST_SWITCHED] = "FIRST_SWITCHED",
+ [NETFLOW9_OUT_BYTES] = "OUT_BYTES",
+ [NETFLOW9_OUT_PKTS] = "OUT_PKTS",
+ [NETFLOW9_IPV6_SRC_ADDR] = "IPV6_SRC_ADDR",
+ [NETFLOW9_IPV6_DST_ADDR] = "IPV6_DST_ADDR",
+ [NETFLOW9_IPV6_SRC_MASK] = "IPV6_SRC_MASK",
+ [NETFLOW9_IPV6_DST_MASK] = "IPV6_DST_MASK",
+ [NETFLOW9_FLOW_LABEL] = "FLOW_LABEL",
+ [NETFLOW9_ICMP_TYPE] = "ICMP_TYPE",
+ [NETFLOW9_MUL_IGMP_TYPE] = "MUL_IGMP_TYPE",
+ [NETFLOW9_SAMPLING_INTERVAL] = "SAMPLING_INTERVAL",
+ [NETFLOW9_SAMPLING_ALGORITHM] = "SAMPLING_ALGORITHM",
+ [NETFLOW9_FLOW_ACTIVE_TIMEOUT] = "FLOW_ACTIVE_TIMEOUT",
+ [NETFLOW9_FLOW_INAVTIVE_TIMEOUT] = "FLOW_INAVTIVE_TIMEOUT",
+ [NETFLOW9_ENGINE_TYPE] = "ENGINE_TYPE",
+ [NETFLOW9_ENGINE_ID] = "ENGINE_ID",
+ [NETFLOW9_TOTAL_BYTES_EXP] = "TOTAL_BYTES_EXP",
+ [NETFLOW9_TOTAL_PKTS_EXP] = "TOTAL_PKTS_EXP",
+ [NETFLOW9_TOTAL_FLOWS_EXP] = "TOTAL_FLOWS_EXP",
+ [NETFLOW9_MPLS_TOP_LABEL_TYPE] = "MPLS_TOP_LABEL_TYPE",
+ [NETFLOW9_MPLS_TOP_LABEL_IP_ADDR] = "MPLS_TOP_LABEL_IP_ADDR",
+ [NETFLOW9_FLOW_SAMPLER_ID] = "FLOW_SAMPLER_ID",
+ [NETFLOW9_FLOW_SAMPLER_MODE] = "FLOW_SAMPLER_MODE",
+ [NETFLOW9_FLOW_SAMPLER_RANDOM_INTERVAL] = "FLOW_SAMPLER_RANDOM_INTERVAL",
+ [NETFLOW9_DST_TOS] = "DST_TOS",
+ [NETFLOW9_SRC_MAC] = "SRC_MAC",
+ [NETFLOW9_DST_MAC] = "DST_MAC",
+ [NETFLOW9_SRC_VLAN] = "SRC_VLAN",
+ [NETFLOW9_DST_VLAN] = "DST_VLAN",
+ [NETFLOW9_IP_PROTOCOL_VERSION] = "IP_PROTOCOL_VERSION",
+ [NETFLOW9_DIRECTION] = "DIRECTION",
+ [NETFLOW9_IPV6_NEXT_HOP] = "IPV6_NEXT_HOP",
+ [NETFLOW9_BGP_IPV6_NEXT_HOP] = "BGP_IPV6_NEXT_HOP",
+ [NETFLOW9_IPV6_OPTION_HEADERS] = "IPV6_OPTION_HEADERS",
+ [NETFLOW9_MPLS_LABEL_1] = "MPLS_LABEL_1",
+ [NETFLOW9_MPLS_LABEL_2] = "MPLS_LABEL_2",
+ [NETFLOW9_MPLS_LABEL_3] = "MPLS_LABEL_3",
+ [NETFLOW9_MPLS_LABEL_4] = "MPLS_LABEL_4",
+ [NETFLOW9_MPLS_LABEL_5] = "MPLS_LABEL_5",
+ [NETFLOW9_MPLS_LABEL_6] = "MPLS_LABEL_6",
+ [NETFLOW9_MPLS_LABEL_7] = "MPLS_LABEL_7",
+ [NETFLOW9_MPLS_LABEL_8] = "MPLS_LABEL_8",
+ [NETFLOW9_MPLS_LABEL_9] = "MPLS_LABEL_9",
+ [NETFLOW9_MPLS_LABEL_10] = "MPLS_LABEL_10",
+ [NETFLOW9_IPV4_XLATE_SRC_ADDR] = "IPV4_XLATE_SRC_ADDR",
+ [NETFLOW9_IPV4_XLATE_DST_ADDR] = "IPV4_XLATE_DST_ADDR",
+ [NETFLOW9_L4_XLATE_SRC_PORT] = "L4_XLATE_SRC_PORT",
+ [NETFLOW9_L4_XLATE_DST_PORT] = "L4_XLATE_DST_PORT",
+ [NETFLOW9_IPV6_XLATE_SRC_ADDR] = "IPV6_XLATE_SRC_ADDR",
+ [NETFLOW9_IPV6_XLATE_DST_ADDR] = "IPV6_XLATE_DST_ADDR",
+};
+
+static int nflow9_fprintf_field(FILE *fd, const struct netflow9_templ_rec *field, int len)
+{
+ int ret;
+ void *ptr;
+
+ if (len < (int)sizeof(*field)) {
+ fprintf(fd, "ERROR ietf field: too short buflen: %d\n", len);
+ return -1;
+ }
+
+ fprintf(fd, "+---------------------------------+---------------------------------+\n");
+ fprintf(fd, "| Field Type: %19s | Field Length: %5d |\n",
+ nflow9_field_name[ntohs(field->type)], ntohs(field->length));
+
+ len -= sizeof(*field);
+ if (len == 0)
+ return sizeof(*field);
+
+ ptr = (void *)field + sizeof(*field);
+ ret = nflow9_fprintf_field(fd, ptr, len);
+ if (ret == -1)
+ return -1;
+ return ret + sizeof(*field);
+}
+
+static int nflow9_fprintf_data_records(FILE *fd, const void *data, int len)
+{
+ int i;
+
+ fprintf(fd, "+-------------------------------------------------------------------+\n");
+ /* don't say messy...*/
+ for (i = 0; i < len; i += 4) {
+ switch (len - i - 4) {
+ case -3:
+ fprintf(fd, "| 0x%02x |\n",
+ *(uint8_t *)(data + i));
+ break;
+ case -2:
+ fprintf(fd, "| 0x%02x 0x%02x |\n",
+ *(uint8_t *)(data + i), *(uint8_t *)(data + i + 1));
+ break;
+ case -1:
+ fprintf(fd, "| 0x%02x 0x%02x 0x%02x |\n",
+ *(uint8_t *)(data + i), *(uint8_t *)(data + i + 1), *(uint8_t *)(data + i + 2));
+ break;
+ default:
+ fprintf(fd, "| 0x%02x 0x%02x 0x%02x 0x%02x |\n",
+ *(uint8_t *)(data + i), *(uint8_t *)(data + i + 1),
+ *(uint8_t *)(data + i + 2), *(uint8_t *)(data + i + 3));
+ break;
+ }
+ }
+ return len;
+}
+
+static int nflow9_fprintf_template_records(FILE *fd, const struct netflow9_templ_hdr *hdr,
+ int len)
+{
+ int ret;
+ void *field;
+
+ if (len < (int)sizeof(*hdr)) {
+ fprintf(fd, "ERROR template records: too short buflen for template record: %d\n", len);
+ return -1;
+ }
+
+ fprintf(fd, "+---------------------------------+---------------------------------+\n");
+ fprintf(fd, "| Template ID: %5d | Field Count: %5d |\n",
+ ntohs(hdr->template_id), ntohs(hdr->field_count));
+
+ len -= sizeof(*hdr);
+ if (len == 0)
+ return sizeof(*hdr);
+
+ field = (void *)hdr + sizeof(*hdr);
+ ret = nflow9_fprintf_field(fd, field, len);
+ if (ret == -1)
+ return -1;
+ return ret + sizeof(*hdr);
+}
+
+static int nflow9_fprintf_set_header(FILE *fd, const struct netflow9_set_hdr *hdr, int len)
+{
+ int ret, setlen, total_len;
+ void *ptr;
+
+ if (len < (int)sizeof(*hdr)) {
+ fprintf(fd, "ERROR set header: too short buflen for set header: %d\n", len);
+ return -1;
+ }
+ setlen = ntohs(hdr->length);
+ if (len < setlen) {
+ fprintf(fd, "ERROR set header: buflen: %d is smaller than set length field: %d\n", len, setlen);
+ /* return -1; */
+ }
+ if (setlen < (int)sizeof(*hdr)) {
+ fprintf(fd, "ERROR set header: too short set length field: %d\n", setlen);
+ return -1;
+ }
+
+ fprintf(fd, "+---------------------------------+---------------------------------+\n");
+ fprintf(fd, "| Set ID: %5d | Length: %5d |\n",
+ ntohs(hdr->set_id), setlen);
+
+ setlen -= sizeof(*hdr);
+ ptr = (void *)hdr + sizeof(*hdr);
+ total_len = sizeof(*hdr);
+
+ switch (ntohs(hdr->set_id)) {
+ case 0:
+ ret = nflow9_fprintf_template_records(fd, ptr, setlen);
+ break;
+ case 1:
+ /* XXX: ret = nflow9_fprintf_options_template_records(fd, ptr, setlen); */
+ fprintf(fd, "ERROR: options template is not implemented yet, sorry");
+ ret = setlen;
+ break;
+ default:
+ ret = nflow9_fprintf_data_records(fd, ptr, setlen);
+ break;
+ }
+
+ if (ret == -1 || ret != setlen)
+ return -1;
+
+ fprintf(fd, "+-------------------------------------------------------------------+\n");
+ return total_len + ret;
+}
+
+static int _nflow9_fprintf_header(FILE *fd, const struct netflow9_msg_hdr *hdr,
+ int msglen)
+{
+ int ret, len;
+ char outstr[20];
+ void *ptr;
+ time_t t = (time_t)(ntohl(hdr->unix_secs));
+ struct tm *tmp = localtime(&t);
+
+ /* XXX: tmp == NULL and strftime == 0 */
+ strftime(outstr, sizeof(outstr), "%F %T", tmp);
+
+ fprintf(fd, "+---------------------------------+---------------------------------+\n");
+ fprintf(fd, "| Version Number: %5d | Count: %5d | (Length: %d) \n",
+ ntohs(hdr->version), ntohs(hdr->count), msglen);
+ fprintf(fd, "+-------------------------------------------------------------------+\n");
+ fprintf(fd, "| sysUpTime: %10u |\n",
+ ntohl(hdr->sys_uptime));
+ fprintf(fd, "+---------------------------------+---------------------------------+\n");
+ fprintf(fd, "| UNIX Secs: %10u |\t%s\n",
+ ntohl(hdr->unix_secs), outstr);
+ fprintf(fd, "+-------------------------------------------------------------------+\n");
+ fprintf(fd, "| Sequence Number: %10d |\n",
+ ntohl(hdr->sequence_number));
+ fprintf(fd, "+-------------------------------------------------------------------+\n");
+ fprintf(fd, "| Source ID: %10d |\n",
+ ntohl(hdr->source_id));
+ fprintf(fd, "+-------------------------------------------------------------------+\n");
+
+ len = msglen - sizeof(*hdr);
+ ptr = (void *)hdr + sizeof(*hdr);
+
+ while (len > 0) {
+ ret = nflow9_fprintf_set_header(fd, ptr, len);
+ if (ret == -1)
+ return -1;
+ len -= ret;
+ ptr += ret;
+ }
+
+ return msglen - len;
+}
+
+static int nflow9_fprintf_header(FILE *fd, const struct netflow9_instance *ii)
+{
+ lseek(mmfd, 0, SEEK_SET);
+ writev(mmfd, ii->iovecs, ii->iovcnt);
+ return _nflow9_fprintf_header(fd, mmaddr, ii->msglen);
+}
+#endif
This patch introduces a NFLOW9 output plugin which sends Netflow v9 encoded NFCT destroy events. Signed-off-by: Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp> --- output/Makefile.am | 10 + output/ulogd_output_NFLOW9.c | 1696 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1706 insertions(+) create mode 100644 output/ulogd_output_NFLOW9.c