netflow_v9.c revision 260278
1/*-
2 * Copyright (c) 2010 Alexander V. Chernikov <melifaro@ipfw.ru>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * 	$FreeBSD: stable/10/sys/netgraph/netflow/netflow_v9.c 260278 2014-01-04 19:04:53Z dim $
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD: stable/10/sys/netgraph/netflow/netflow_v9.c 260278 2014-01-04 19:04:53Z dim $");
31
32#include "opt_inet6.h"
33#include "opt_route.h"
34#include <sys/param.h>
35#include <sys/kernel.h>
36#include <sys/limits.h>
37#include <sys/mbuf.h>
38#include <sys/syslog.h>
39#include <sys/systm.h>
40#include <sys/socket.h>
41#include <sys/endian.h>
42
43#include <machine/atomic.h>
44#include <machine/stdarg.h>
45
46#include <net/if.h>
47#include <net/route.h>
48#include <net/ethernet.h>
49#include <netinet/in.h>
50#include <netinet/in_systm.h>
51#include <netinet/ip.h>
52#include <netinet/ip6.h>
53#include <netinet/tcp.h>
54#include <netinet/udp.h>
55
56#include <netgraph/ng_message.h>
57#include <netgraph/netgraph.h>
58
59#include <netgraph/netflow/netflow.h>
60#include <netgraph/netflow/ng_netflow.h>
61#include <netgraph/netflow/netflow_v9.h>
62
63MALLOC_DECLARE(M_NETFLOW_GENERAL);
64MALLOC_DEFINE(M_NETFLOW_GENERAL, "netflow_general", "plog, V9 templates data");
65
66/*
67 * Base V9 templates for L4+ IPv4/IPv6 protocols
68 */
69struct netflow_v9_template _netflow_v9_record_ipv4_tcp[] =
70{
71	{ NETFLOW_V9_FIELD_IPV4_SRC_ADDR, 4},
72	{ NETFLOW_V9_FIELD_IPV4_DST_ADDR, 4},
73	{ NETFLOW_V9_FIELD_IPV4_NEXT_HOP, 4},
74	{ NETFLOW_V9_FIELD_INPUT_SNMP, 2},
75	{ NETFLOW_V9_FIELD_OUTPUT_SNMP, 2},
76	{ NETFLOW_V9_FIELD_IN_PKTS, sizeof(CNTR)},
77	{ NETFLOW_V9_FIELD_IN_BYTES, sizeof(CNTR)},
78	{ NETFLOW_V9_FIELD_OUT_PKTS, sizeof(CNTR)},
79	{ NETFLOW_V9_FIELD_OUT_BYTES, sizeof(CNTR)},
80	{ NETFLOW_V9_FIELD_FIRST_SWITCHED, 4},
81	{ NETFLOW_V9_FIELD_LAST_SWITCHED, 4},
82	{ NETFLOW_V9_FIELD_L4_SRC_PORT, 2},
83	{ NETFLOW_V9_FIELD_L4_DST_PORT, 2},
84	{ NETFLOW_V9_FIELD_TCP_FLAGS, 1},
85	{ NETFLOW_V9_FIELD_PROTOCOL, 1},
86	{ NETFLOW_V9_FIELD_TOS, 1},
87	{ NETFLOW_V9_FIELD_SRC_AS, 4},
88	{ NETFLOW_V9_FIELD_DST_AS, 4},
89	{ NETFLOW_V9_FIELD_SRC_MASK, 1},
90	{ NETFLOW_V9_FIELD_DST_MASK, 1},
91	{0, 0}
92};
93
94struct netflow_v9_template _netflow_v9_record_ipv6_tcp[] =
95{
96	{ NETFLOW_V9_FIELD_IPV6_SRC_ADDR, 16},
97	{ NETFLOW_V9_FIELD_IPV6_DST_ADDR, 16},
98	{ NETFLOW_V9_FIELD_IPV6_NEXT_HOP, 16},
99	{ NETFLOW_V9_FIELD_INPUT_SNMP, 2},
100	{ NETFLOW_V9_FIELD_OUTPUT_SNMP, 2},
101	{ NETFLOW_V9_FIELD_IN_PKTS, sizeof(CNTR)},
102	{ NETFLOW_V9_FIELD_IN_BYTES, sizeof(CNTR)},
103	{ NETFLOW_V9_FIELD_OUT_PKTS, sizeof(CNTR)},
104	{ NETFLOW_V9_FIELD_OUT_BYTES, sizeof(CNTR)},
105	{ NETFLOW_V9_FIELD_FIRST_SWITCHED, 4},
106	{ NETFLOW_V9_FIELD_LAST_SWITCHED, 4},
107	{ NETFLOW_V9_FIELD_L4_SRC_PORT, 2},
108	{ NETFLOW_V9_FIELD_L4_DST_PORT, 2},
109	{ NETFLOW_V9_FIELD_TCP_FLAGS, 1},
110	{ NETFLOW_V9_FIELD_PROTOCOL, 1},
111	{ NETFLOW_V9_FIELD_TOS, 1},
112	{ NETFLOW_V9_FIELD_SRC_AS, 4},
113	{ NETFLOW_V9_FIELD_DST_AS, 4},
114	{ NETFLOW_V9_FIELD_SRC_MASK, 1},
115	{ NETFLOW_V9_FIELD_DST_MASK, 1},
116	{0, 0}
117};
118
119/*
120 * Pre-compiles flow exporter for all possible FlowSets
121 * so we can add flowset to packet via simple memcpy()
122 */
123static void
124generate_v9_templates(priv_p priv)
125{
126	uint16_t *p, *template_fields_cnt;
127	int cnt;
128
129	int flowset_size = sizeof(struct netflow_v9_flowset_header) +
130		_NETFLOW_V9_TEMPLATE_SIZE(_netflow_v9_record_ipv4_tcp) + /* netflow_v9_record_ipv4_tcp */
131		_NETFLOW_V9_TEMPLATE_SIZE(_netflow_v9_record_ipv6_tcp); /* netflow_v9_record_ipv6_tcp */
132
133	priv->v9_flowsets[0] = malloc(flowset_size, M_NETFLOW_GENERAL, M_WAITOK | M_ZERO);
134
135	if (flowset_size % 4)
136		flowset_size += 4 - (flowset_size % 4); /* Padding to 4-byte boundary */
137
138	priv->flowsets_count = 1;
139	p = (uint16_t *)priv->v9_flowsets[0];
140	*p++ = 0; /* Flowset ID, 0 is reserved for Template FlowSets  */
141	*p++ = htons(flowset_size); /* Total FlowSet length */
142
143	/*
144	 * Most common TCP/UDP IPv4 template, ID = 256
145	 */
146	*p++ = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + NETFLOW_V9_FLOW_V4_L4);
147	template_fields_cnt = p++;
148	for (cnt = 0; _netflow_v9_record_ipv4_tcp[cnt].field_id != 0; cnt++) {
149		*p++ = htons(_netflow_v9_record_ipv4_tcp[cnt].field_id);
150		*p++ = htons(_netflow_v9_record_ipv4_tcp[cnt].field_length);
151	}
152	*template_fields_cnt = htons(cnt);
153
154	/*
155	 * TCP/UDP IPv6 template, ID = 257
156	 */
157	*p++ = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + NETFLOW_V9_FLOW_V6_L4);
158	template_fields_cnt = p++;
159	for (cnt = 0; _netflow_v9_record_ipv6_tcp[cnt].field_id != 0; cnt++) {
160		*p++ = htons(_netflow_v9_record_ipv6_tcp[cnt].field_id);
161		*p++ = htons(_netflow_v9_record_ipv6_tcp[cnt].field_length);
162	}
163	*template_fields_cnt = htons(cnt);
164
165	priv->flowset_records[0] = 2;
166}
167
168/* Closes current data flowset */
169static void inline
170close_flowset(struct mbuf *m, struct netflow_v9_packet_opt *t)
171{
172	struct mbuf *m_old;
173	uint32_t zero = 0;
174	int offset = 0;
175	uint16_t *flowset_length, len;
176
177	/* Hack to ensure we are not crossing mbuf boundary, length is uint16_t  */
178	m_old = m_getptr(m, t->flow_header + offsetof(struct netflow_v9_flowset_header, length), &offset);
179	flowset_length = (uint16_t *)(mtod(m_old, char *) + offset);
180
181	len = (uint16_t)(m_pktlen(m) - t->flow_header);
182	/* Align on 4-byte boundary (RFC 3954, Clause 5.3) */
183	if (len % 4) {
184		if (m_append(m, 4 - (len % 4), (void *)&zero) != 1)
185			panic("ng_netflow: m_append() failed!");
186
187		len += 4 - (len % 4);
188	}
189
190	*flowset_length = htons(len);
191}
192
193/*
194 * Non-static functions called from ng_netflow.c
195 */
196
197/* We have full datagram in fib data. Send it to export hook. */
198int
199export9_send(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags)
200{
201	struct mbuf *m = NGI_M(item);
202	struct netflow_v9_export_dgram *dgram = mtod(m,
203					struct netflow_v9_export_dgram *);
204	struct netflow_v9_header *header = &dgram->header;
205	struct timespec ts;
206	int error = 0;
207
208	if (t == NULL) {
209		CTR0(KTR_NET, "export9_send(): V9 export packet without tag");
210		NG_FREE_ITEM(item);
211		return (0);
212	}
213
214	/* Close flowset if not closed already */
215	if (m_pktlen(m) != t->flow_header)
216		close_flowset(m, t);
217
218	/* Fill export header. */
219	header->count = t->count;
220	header->sys_uptime = htonl(MILLIUPTIME(time_uptime));
221	getnanotime(&ts);
222	header->unix_secs  = htonl(ts.tv_sec);
223	header->seq_num = htonl(atomic_fetchadd_32(&fe->flow9_seq, 1));
224	header->count = htons(t->count);
225	header->source_id = htonl(fe->domain_id);
226
227	if (priv->export9 != NULL)
228		NG_FWD_ITEM_HOOK_FLAGS(error, item, priv->export9, flags);
229	else
230		NG_FREE_ITEM(item);
231
232	free(t, M_NETFLOW_GENERAL);
233
234	return (error);
235}
236
237
238
239/* Add V9 record to dgram. */
240int
241export9_add(item_p item, struct netflow_v9_packet_opt *t, struct flow_entry *fle)
242{
243	size_t len = 0;
244	struct netflow_v9_flowset_header fsh;
245	struct netflow_v9_record_general rg;
246	struct mbuf *m = NGI_M(item);
247	uint16_t flow_type;
248	struct flow_entry_data *fed;
249#ifdef INET6
250	struct flow6_entry_data *fed6;
251#endif
252	if (t == NULL) {
253		CTR0(KTR_NET, "ng_netflow: V9 export packet without tag!");
254		return (0);
255	}
256
257	/* Prepare flow record */
258	fed = (struct flow_entry_data *)&fle->f;
259#ifdef INET6
260	fed6 = (struct flow6_entry_data *)&fle->f;
261#endif
262	/* We can use flow_type field since fle6 offset is equal to fle */
263	flow_type = fed->r.flow_type;
264
265	switch (flow_type) {
266	case NETFLOW_V9_FLOW_V4_L4:
267	{
268		/* IPv4 TCP/UDP/[SCTP] */
269		struct netflow_v9_record_ipv4_tcp *rec = &rg.rec.v4_tcp;
270
271		rec->src_addr = fed->r.r_src.s_addr;
272		rec->dst_addr = fed->r.r_dst.s_addr;
273		rec->next_hop = fed->next_hop.s_addr;
274		rec->i_ifx    = htons(fed->fle_i_ifx);
275		rec->o_ifx    = htons(fed->fle_o_ifx);
276		rec->i_packets  = htonl(fed->packets);
277		rec->i_octets   = htonl(fed->bytes);
278		rec->o_packets  = htonl(0);
279		rec->o_octets   = htonl(0);
280		rec->first    = htonl(MILLIUPTIME(fed->first));
281		rec->last     = htonl(MILLIUPTIME(fed->last));
282		rec->s_port   = fed->r.r_sport;
283		rec->d_port   = fed->r.r_dport;
284		rec->flags    = fed->tcp_flags;
285		rec->prot     = fed->r.r_ip_p;
286		rec->tos      = fed->r.r_tos;
287		rec->dst_mask = fed->dst_mask;
288		rec->src_mask = fed->src_mask;
289
290		/* Not supported fields. */
291		rec->src_as = rec->dst_as = 0;
292
293		len = sizeof(struct netflow_v9_record_ipv4_tcp);
294		break;
295	}
296#ifdef INET6
297	case NETFLOW_V9_FLOW_V6_L4:
298	{
299		/* IPv6 TCP/UDP/[SCTP] */
300		struct netflow_v9_record_ipv6_tcp *rec = &rg.rec.v6_tcp;
301
302		rec->src_addr = fed6->r.src.r_src6;
303		rec->dst_addr = fed6->r.dst.r_dst6;
304		rec->next_hop = fed6->n.next_hop6;
305		rec->i_ifx    = htons(fed6->fle_i_ifx);
306		rec->o_ifx    = htons(fed6->fle_o_ifx);
307		rec->i_packets  = htonl(fed6->packets);
308		rec->i_octets   = htonl(fed6->bytes);
309		rec->o_packets  = htonl(0);
310		rec->o_octets   = htonl(0);
311		rec->first    = htonl(MILLIUPTIME(fed6->first));
312		rec->last     = htonl(MILLIUPTIME(fed6->last));
313		rec->s_port   = fed6->r.r_sport;
314		rec->d_port   = fed6->r.r_dport;
315		rec->flags    = fed6->tcp_flags;
316		rec->prot     = fed6->r.r_ip_p;
317		rec->tos      = fed6->r.r_tos;
318		rec->dst_mask = fed6->dst_mask;
319		rec->src_mask = fed6->src_mask;
320
321		/* Not supported fields. */
322		rec->src_as = rec->dst_as = 0;
323
324		len = sizeof(struct netflow_v9_record_ipv6_tcp);
325		break;
326	}
327#endif
328	default:
329	{
330		CTR1(KTR_NET, "export9_add(): Don't know what to do with %d flow type!", flow_type);
331		return (0);
332	}
333	}
334
335	/* Check if new records has the same template */
336	if (flow_type != t->flow_type) {
337		/* close old flowset */
338		if (t->flow_type != 0)
339			close_flowset(m, t);
340
341		t->flow_type = flow_type;
342		t->flow_header = m_pktlen(m);
343
344		/* Generate data flowset ID */
345		fsh.id = htons(NETFLOW_V9_MAX_RESERVED_FLOWSET + flow_type);
346		fsh.length = 0;
347
348		/* m_append should not fail since all data is already allocated */
349		if (m_append(m, sizeof(fsh), (void *)&fsh) != 1)
350			panic("ng_netflow: m_append() failed");
351
352	}
353
354	if (m_append(m, len, (void *)&rg.rec) != 1)
355		panic("ng_netflow: m_append() failed");
356
357	t->count++;
358
359	if (m_pktlen(m) + sizeof(struct netflow_v9_record_general) + sizeof(struct netflow_v9_flowset_header) >= _NETFLOW_V9_MAX_SIZE(t->mtu))
360		return (1); /* end of datagram */
361	return (0);
362}
363
364/*
365 * Detach export datagram from fib instance, if there is any.
366 * If there is no, allocate a new one.
367 */
368item_p
369get_export9_dgram(priv_p priv, fib_export_p fe, struct netflow_v9_packet_opt **tt)
370{
371	item_p	item = NULL;
372	struct netflow_v9_packet_opt *t = NULL;
373
374	mtx_lock(&fe->export9_mtx);
375	if (fe->exp.item9 != NULL) {
376		item = fe->exp.item9;
377		fe->exp.item9 = NULL;
378		t = fe->exp.item9_opt;
379		fe->exp.item9_opt = NULL;
380	}
381	mtx_unlock(&fe->export9_mtx);
382
383	if (item == NULL) {
384		struct netflow_v9_export_dgram *dgram;
385		struct mbuf *m;
386		uint16_t mtu = priv->mtu;
387
388		/* Allocate entire packet at once, allowing easy m_append() calls */
389		m = m_getm(NULL, mtu, M_NOWAIT, MT_DATA);
390		if (m == NULL)
391			return (NULL);
392
393		t = malloc(sizeof(struct netflow_v9_packet_opt), M_NETFLOW_GENERAL, M_NOWAIT | M_ZERO);
394		if (t == NULL) {
395			m_free(m);
396			return (NULL);
397		}
398
399		item = ng_package_data(m, NG_NOFLAGS);
400		if (item == NULL) {
401			free(t, M_NETFLOW_GENERAL);
402			return (NULL);
403		}
404
405		dgram = mtod(m, struct netflow_v9_export_dgram *);
406		dgram->header.count = 0;
407		dgram->header.version = htons(NETFLOW_V9);
408		/* Set mbuf current data length */
409		m->m_len = m->m_pkthdr.len = sizeof(struct netflow_v9_header);
410
411		t->count = 0;
412		t->mtu = mtu;
413		t->flow_header = m->m_len;
414
415		/*
416		 * Check if we need to insert templates into packet
417		 */
418
419		struct netflow_v9_flowset_header	*fl;
420
421		if ((time_uptime >= priv->templ_time + fe->templ_last_ts) ||
422				(fe->sent_packets >= priv->templ_packets + fe->templ_last_pkt)) {
423
424			fe->templ_last_ts = time_uptime;
425			fe->templ_last_pkt = fe->sent_packets;
426
427			fl = priv->v9_flowsets[0];
428			m_append(m, ntohs(fl->length), (void *)fl);
429			t->flow_header = m->m_len;
430			t->count += priv->flowset_records[0];
431		}
432
433	}
434
435	*tt = t;
436	return (item);
437}
438
439/*
440 * Re-attach incomplete datagram back to fib instance.
441 * If there is already another one, then send incomplete.
442 */
443void
444return_export9_dgram(priv_p priv, fib_export_p fe, item_p item, struct netflow_v9_packet_opt *t, int flags)
445{
446	/*
447	 * It may happen on SMP, that some thread has already
448	 * put its item there, in this case we bail out and
449	 * send what we have to collector.
450	 */
451	mtx_lock(&fe->export9_mtx);
452	if (fe->exp.item9 == NULL) {
453		fe->exp.item9 = item;
454		fe->exp.item9_opt = t;
455		mtx_unlock(&fe->export9_mtx);
456	} else {
457		mtx_unlock(&fe->export9_mtx);
458		export9_send(priv, fe, item, t, flags);
459	}
460}
461
462/* Allocate memory and set up flow cache */
463void
464ng_netflow_v9_cache_init(priv_p priv)
465{
466	generate_v9_templates(priv);
467
468	priv->templ_time = NETFLOW_V9_MAX_TIME_TEMPL;
469	priv->templ_packets = NETFLOW_V9_MAX_PACKETS_TEMPL;
470	priv->mtu = BASE_MTU;
471}
472
473/* Free all flow cache memory. Called from ng_netflow_cache_flush() */
474void
475ng_netflow_v9_cache_flush(priv_p priv)
476{
477	int i;
478
479	/* Free flowsets*/
480	for (i = 0; i < priv->flowsets_count; i++)
481		free(priv->v9_flowsets[i], M_NETFLOW_GENERAL);
482}
483
484/* Get a snapshot of NetFlow v9 settings */
485void
486ng_netflow_copyv9info(priv_p priv, struct ng_netflow_v9info *i)
487{
488
489	i->templ_time = priv->templ_time;
490	i->templ_packets = priv->templ_packets;
491	i->mtu = priv->mtu;
492}
493
494