This patch introduces a NFLOW9 output plugin which sends Netflow v9 encoded NFCT destroy events. Signed-off-by: Ken-ichirou MATSUZAWA <chamas@xxxxxxxxxxxxx> --- output/Makefile.am | 10 + output/ulogd_output_NFLOW9.c | 1696 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1706 insertions(+) create mode 100644 output/ulogd_output_NFLOW9.c diff --git a/output/Makefile.am b/output/Makefile.am index ff851ad..6f62b48 100644 --- a/output/Makefile.am +++ b/output/Makefile.am @@ -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 diff --git a/output/ulogd_output_NFLOW9.c b/output/ulogd_output_NFLOW9.c new file mode 100644 index 0000000..7af2422 --- /dev/null +++ b/output/ulogd_output_NFLOW9.c @@ -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@xxxxxxxxxxxxx> + * + * 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 -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html