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