1 /*-
2  * Copyright (c) 2004-2005 Gleb Smirnoff <glebius@FreeBSD.org>
3  * Copyright (c) 2001-2003 Roman V. Palagin <romanp@unshadow.net>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $SourceForge: ng_netflow.c,v 1.30 2004/09/05 11:37:43 glebius Exp $
28  * $FreeBSD: src/sys/netgraph/netflow/ng_netflow.c,v 1.17 2008/04/16 16:47:14 kris Exp $
29  */
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 #include <sys/kernel.h>
34 #include <sys/limits.h>
35 #include <sys/mbuf.h>
36 #include <sys/socket.h>
37 #include <sys/syslog.h>
38 #include <sys/ctype.h>
39 
40 #include <net/if.h>
41 #include <net/ethernet.h>
42 #include <net/if_arp.h>
43 #include <net/if_var.h>
44 #include <net/if_vlan_var.h>
45 #include <net/bpf.h>
46 #include <netinet/in.h>
47 #include <netinet/in_systm.h>
48 #include <netinet/ip.h>
49 #include <netinet/tcp.h>
50 #include <netinet/udp.h>
51 
52 #include "ng_message.h"
53 #include "ng_parse.h"
54 #include "netgraph.h"
55 #include "netflow/netflow.h"
56 #include "netflow/ng_netflow.h"
57 
58 /* Netgraph methods */
59 static ng_constructor_t	ng_netflow_constructor;
60 static ng_rcvmsg_t	ng_netflow_rcvmsg;
61 static ng_close_t	ng_netflow_close;
62 static ng_shutdown_t	ng_netflow_rmnode;
63 static ng_newhook_t	ng_netflow_newhook;
64 static ng_rcvdata_t	ng_netflow_rcvdata;
65 static ng_disconnect_t	ng_netflow_disconnect;
66 
67 /* Parse type for struct ng_netflow_info */
68 static const struct ng_parse_struct_field ng_netflow_info_type_fields[]
69 	= NG_NETFLOW_INFO_TYPE;
70 static const struct ng_parse_type ng_netflow_info_type = {
71 	&ng_parse_struct_type,
72 	&ng_netflow_info_type_fields
73 };
74 
75 /*  Parse type for struct ng_netflow_ifinfo */
76 static const struct ng_parse_struct_field ng_netflow_ifinfo_type_fields[]
77 	= NG_NETFLOW_IFINFO_TYPE;
78 static const struct ng_parse_type ng_netflow_ifinfo_type = {
79 	&ng_parse_struct_type,
80 	&ng_netflow_ifinfo_type_fields
81 };
82 
83 /* Parse type for struct ng_netflow_setdlt */
84 static const struct ng_parse_struct_field ng_netflow_setdlt_type_fields[]
85 	= NG_NETFLOW_SETDLT_TYPE;
86 static const struct ng_parse_type ng_netflow_setdlt_type = {
87 	&ng_parse_struct_type,
88 	&ng_netflow_setdlt_type_fields
89 };
90 
91 /* Parse type for ng_netflow_setifindex */
92 static const struct ng_parse_struct_field ng_netflow_setifindex_type_fields[]
93 	= NG_NETFLOW_SETIFINDEX_TYPE;
94 static const struct ng_parse_type ng_netflow_setifindex_type = {
95 	&ng_parse_struct_type,
96 	&ng_netflow_setifindex_type_fields
97 };
98 
99 /* Parse type for ng_netflow_settimeouts */
100 static const struct ng_parse_struct_field ng_netflow_settimeouts_type_fields[]
101 	= NG_NETFLOW_SETTIMEOUTS_TYPE;
102 static const struct ng_parse_type ng_netflow_settimeouts_type = {
103 	&ng_parse_struct_type,
104 	&ng_netflow_settimeouts_type_fields
105 };
106 
107 /* List of commands and how to convert arguments to/from ASCII */
108 static const struct ng_cmdlist ng_netflow_cmds[] = {
109        {
110 	 NGM_NETFLOW_COOKIE,
111 	 NGM_NETFLOW_INFO,
112 	 "info",
113 	 NULL,
114 	 &ng_netflow_info_type
115        },
116        {
117 	NGM_NETFLOW_COOKIE,
118 	NGM_NETFLOW_IFINFO,
119 	"ifinfo",
120 	&ng_parse_uint16_type,
121 	&ng_netflow_ifinfo_type
122        },
123        {
124 	NGM_NETFLOW_COOKIE,
125 	NGM_NETFLOW_SETDLT,
126 	"setdlt",
127 	&ng_netflow_setdlt_type,
128 	NULL
129        },
130        {
131 	NGM_NETFLOW_COOKIE,
132 	NGM_NETFLOW_SETIFINDEX,
133 	"setifindex",
134 	&ng_netflow_setifindex_type,
135 	NULL
136        },
137        {
138 	NGM_NETFLOW_COOKIE,
139 	NGM_NETFLOW_SETTIMEOUTS,
140 	"settimeouts",
141 	&ng_netflow_settimeouts_type,
142 	NULL
143        },
144        { 0 }
145 };
146 
147 
148 /* Netgraph node type descriptor */
149 static struct ng_type ng_netflow_typestruct = {
150 	.version =	NG_ABI_VERSION,
151 	.name =		NG_NETFLOW_NODE_TYPE,
152 	.constructor =	ng_netflow_constructor,
153 	.rcvmsg =	ng_netflow_rcvmsg,
154 	.close =	ng_netflow_close,
155 	.shutdown =	ng_netflow_rmnode,
156 	.newhook =	ng_netflow_newhook,
157 	.rcvdata =	ng_netflow_rcvdata,
158 	.disconnect =	ng_netflow_disconnect,
159 	.cmdlist =	ng_netflow_cmds,
160 };
161 NETGRAPH_INIT(netflow, &ng_netflow_typestruct);
162 
163 /* Called at node creation */
164 static int
165 ng_netflow_constructor(node_p node)
166 {
167 	priv_p priv;
168 	int error = 0;
169 
170 	/* Initialize private data */
171 	priv = kmalloc(sizeof(*priv), M_NETGRAPH, M_WAITOK | M_NULLOK | M_ZERO);
172 	if (priv == NULL)
173 		return (ENOMEM);
174 
175 	/* Make node and its data point at each other */
176 	NG_NODE_SET_PRIVATE(node, priv);
177 	priv->node = node;
178 
179 	/* Initialize timeouts to default values */
180 	priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT;
181 	priv->info.nfinfo_act_t = ACTIVE_TIMEOUT;
182 
183 	/* Initialize callout handle */
184 	callout_init(&priv->exp_callout, CALLOUT_MPSAFE);
185 
186 	/* Allocate memory and set up flow cache */
187 	if ((error = ng_netflow_cache_init(priv)))
188 		return (error);
189 
190 	return (0);
191 }
192 
193 /*
194  * ng_netflow supports two hooks: data and export.
195  * Incoming traffic is expected on data, and expired
196  * netflow datagrams are sent to export.
197  */
198 static int
199 ng_netflow_newhook(node_p node, hook_p hook, const char *name)
200 {
201 	const priv_p priv = NG_NODE_PRIVATE(node);
202 
203 	if (strncmp(name, NG_NETFLOW_HOOK_DATA,	/* an iface hook? */
204 	    strlen(NG_NETFLOW_HOOK_DATA)) == 0) {
205 		iface_p iface;
206 		int ifnum = -1;
207 		const char *cp;
208 		char *eptr;
209 
210 		cp = name + strlen(NG_NETFLOW_HOOK_DATA);
211 		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
212 			return (EINVAL);
213 
214 		ifnum = (int)strtoul(cp, &eptr, 10);
215 		if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
216 			return (EINVAL);
217 
218 		/* See if hook is already connected */
219 		if (priv->ifaces[ifnum].hook != NULL)
220 			return (EISCONN);
221 
222 		iface = &priv->ifaces[ifnum];
223 
224 		/* Link private info and hook together */
225 		NG_HOOK_SET_PRIVATE(hook, iface);
226 		iface->hook = hook;
227 
228 		/*
229 		 * In most cases traffic accounting is done on an
230 		 * Ethernet interface, so default data link type
231 		 * will be DLT_EN10MB.
232 		 */
233 		iface->info.ifinfo_dlt = DLT_EN10MB;
234 
235 	} else if (strncmp(name, NG_NETFLOW_HOOK_OUT,
236 	    strlen(NG_NETFLOW_HOOK_OUT)) == 0) {
237 		iface_p iface;
238 		int ifnum = -1;
239 		const char *cp;
240 		char *eptr;
241 
242 		cp = name + strlen(NG_NETFLOW_HOOK_OUT);
243 		if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
244 			return (EINVAL);
245 
246 		ifnum = (int)strtoul(cp, &eptr, 10);
247 		if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
248 			return (EINVAL);
249 
250 		/* See if hook is already connected */
251 		if (priv->ifaces[ifnum].out != NULL)
252 			return (EISCONN);
253 
254 		iface = &priv->ifaces[ifnum];
255 
256 		/* Link private info and hook together */
257 		NG_HOOK_SET_PRIVATE(hook, iface);
258 		iface->out = hook;
259 
260 	} else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) {
261 
262 		if (priv->export != NULL)
263 			return (EISCONN);
264 
265 		priv->export = hook;
266 
267 #if 0	/* TODO: profile & test first */
268 		/*
269 		 * We send export dgrams in interrupt handlers and in
270 		 * callout threads. We'd better queue data for later
271 		 * netgraph ISR processing.
272 		 */
273 		NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
274 #endif
275 
276 		/* Exporter is ready. Let's schedule expiry. */
277 		callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
278 		    (void *)priv);
279 	} else
280 		return (EINVAL);
281 
282 	return (0);
283 }
284 
285 /* Get a netgraph control message. */
286 static int
287 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook)
288 {
289 	const priv_p priv = NG_NODE_PRIVATE(node);
290 	struct ng_mesg *resp = NULL;
291 	int error = 0;
292 	struct ng_mesg *msg;
293 
294 	NGI_GET_MSG(item, msg);
295 
296 	/* Deal with message according to cookie and command */
297 	switch (msg->header.typecookie) {
298 	case NGM_NETFLOW_COOKIE:
299 		switch (msg->header.cmd) {
300 		case NGM_NETFLOW_INFO:
301 		{
302 			struct ng_netflow_info *i;
303 
304 			NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info),
305 			    M_WAITOK | M_NULLOK);
306 			i = (struct ng_netflow_info *)resp->data;
307 			ng_netflow_copyinfo(priv, i);
308 
309 			break;
310 		}
311 		case NGM_NETFLOW_IFINFO:
312 		{
313 			struct ng_netflow_ifinfo *i;
314 			const uint16_t *index;
315 
316 			if (msg->header.arglen != sizeof(uint16_t))
317 				 ERROUT(EINVAL);
318 
319 			index  = (uint16_t *)msg->data;
320 			if (*index >= NG_NETFLOW_MAXIFACES)
321 				ERROUT(EINVAL);
322 
323 			/* connected iface? */
324 			if (priv->ifaces[*index].hook == NULL)
325 				 ERROUT(EINVAL);
326 
327 			NG_MKRESPONSE(resp, msg,
328 			     sizeof(struct ng_netflow_ifinfo), M_WAITOK | M_NULLOK);
329 			i = (struct ng_netflow_ifinfo *)resp->data;
330 			memcpy((void *)i, (void *)&priv->ifaces[*index].info,
331 			    sizeof(priv->ifaces[*index].info));
332 
333 			break;
334 		}
335 		case NGM_NETFLOW_SETDLT:
336 		{
337 			struct ng_netflow_setdlt *set;
338 			struct ng_netflow_iface *iface;
339 
340 			if (msg->header.arglen != sizeof(struct ng_netflow_setdlt))
341 				ERROUT(EINVAL);
342 
343 			set = (struct ng_netflow_setdlt *)msg->data;
344 			if (set->iface >= NG_NETFLOW_MAXIFACES)
345 				ERROUT(EINVAL);
346 			iface = &priv->ifaces[set->iface];
347 
348 			/* connected iface? */
349 			if (iface->hook == NULL)
350 				ERROUT(EINVAL);
351 
352 			switch (set->dlt) {
353 			case	DLT_EN10MB:
354 				iface->info.ifinfo_dlt = DLT_EN10MB;
355 				break;
356 			case	DLT_RAW:
357 				iface->info.ifinfo_dlt = DLT_RAW;
358 				break;
359 			default:
360 				ERROUT(EINVAL);
361 			}
362 			break;
363 		}
364 		case NGM_NETFLOW_SETIFINDEX:
365 		{
366 			struct ng_netflow_setifindex *set;
367 			struct ng_netflow_iface *iface;
368 
369 			if (msg->header.arglen != sizeof(struct ng_netflow_setifindex))
370 				ERROUT(EINVAL);
371 
372 			set = (struct ng_netflow_setifindex *)msg->data;
373 			if (set->iface >= NG_NETFLOW_MAXIFACES)
374 				ERROUT(EINVAL);
375 			iface = &priv->ifaces[set->iface];
376 
377 			/* connected iface? */
378 			if (iface->hook == NULL)
379 				ERROUT(EINVAL);
380 
381 			iface->info.ifinfo_index = set->index;
382 
383 			break;
384 		}
385 		case NGM_NETFLOW_SETTIMEOUTS:
386 		{
387 			struct ng_netflow_settimeouts *set;
388 
389 			if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts))
390 				ERROUT(EINVAL);
391 
392 			set = (struct ng_netflow_settimeouts *)msg->data;
393 
394 			priv->info.nfinfo_inact_t = set->inactive_timeout;
395 			priv->info.nfinfo_act_t = set->active_timeout;
396 
397 			break;
398 		}
399 		case NGM_NETFLOW_SHOW:
400 		{
401 			uint32_t *last;
402 
403 			if (msg->header.arglen != sizeof(uint32_t))
404 				ERROUT(EINVAL);
405 
406 			last = (uint32_t *)msg->data;
407 
408 			NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_WAITOK | M_NULLOK);
409 
410 			if (!resp)
411 				ERROUT(ENOMEM);
412 
413 			error = ng_netflow_flow_show(priv, *last, resp);
414 
415 			break;
416 		}
417 		default:
418 			ERROUT(EINVAL);		/* unknown command */
419 			break;
420 		}
421 		break;
422 	default:
423 		ERROUT(EINVAL);		/* incorrect cookie */
424 		break;
425 	}
426 
427 	/*
428 	 * Take care of synchronous response, if any.
429 	 * Free memory and return.
430 	 */
431 done:
432 	NG_RESPOND_MSG(error, node, item, resp);
433 	NG_FREE_MSG(msg);
434 
435 	return (error);
436 }
437 
438 /* Receive data on hook. */
439 static int
440 ng_netflow_rcvdata (hook_p hook, item_p item)
441 {
442 	const node_p node = NG_HOOK_NODE(hook);
443 	const priv_p priv = NG_NODE_PRIVATE(node);
444 	const iface_p iface = NG_HOOK_PRIVATE(hook);
445 	struct mbuf *m = NULL;
446 	struct ip *ip;
447 	int pullup_len = 0;
448 	int error = 0;
449 
450 	if (hook == priv->export) {
451 		/*
452 		 * Data arrived on export hook.
453 		 * This must not happen.
454 		 */
455 		log(LOG_ERR, "ng_netflow: incoming data on export hook!\n");
456 		ERROUT(EINVAL);
457 	};
458 
459 	if (hook == iface->out) {
460 		/*
461 		 * Data arrived on out hook. Bypass it.
462 		 */
463 		if (iface->hook == NULL)
464 			ERROUT(ENOTCONN);
465 
466 		NG_FWD_ITEM_HOOK(error, item, iface->hook);
467 		return (error);
468 	}
469 
470 	NGI_GET_M(item, m);
471 
472 	/* Increase counters. */
473 	iface->info.ifinfo_packets++;
474 
475 	/*
476 	 * Depending on interface data link type and packet contents
477 	 * we pullup enough data, so that ng_netflow_flow_add() does not
478 	 * need to know about mbuf at all. We keep current length of data
479 	 * needed to be contiguous in pullup_len. mtod() is done at the
480 	 * very end one more time, since m can had changed after pulluping.
481 	 *
482 	 * In case of unrecognized data we don't return error, but just
483 	 * pass data to downstream hook, if it is available.
484 	 */
485 
486 #define	M_CHECK(length)	do {					\
487 	pullup_len += length;					\
488 	if ((m)->m_pkthdr.len < (pullup_len)) {			\
489 		error = EINVAL;					\
490 		goto bypass;					\
491 	} 							\
492 	if ((m)->m_len < (pullup_len) &&			\
493 	   (((m) = m_pullup((m),(pullup_len))) == NULL)) {	\
494 		error = ENOBUFS;				\
495 		goto done;					\
496 	}							\
497 } while (0)
498 
499 	switch (iface->info.ifinfo_dlt) {
500 	case DLT_EN10MB:	/* Ethernet */
501 	    {
502 		struct ether_header *eh;
503 		uint16_t etype;
504 
505 		M_CHECK(sizeof(struct ether_header));
506 		eh = mtod(m, struct ether_header *);
507 
508 		/* Make sure this is IP frame. */
509 		etype = ntohs(eh->ether_type);
510 		switch (etype) {
511 		case ETHERTYPE_IP:
512 			M_CHECK(sizeof(struct ip));
513 			eh = mtod(m, struct ether_header *);
514 			ip = (struct ip *)(eh + 1);
515 			break;
516 		case ETHERTYPE_VLAN:
517 		    {
518 			struct ether_vlan_header *evh;
519 
520 			M_CHECK(sizeof(struct ether_vlan_header) -
521 			    sizeof(struct ether_header));
522 			evh = mtod(m, struct ether_vlan_header *);
523 			if (ntohs(evh->evl_proto) == ETHERTYPE_IP) {
524 				M_CHECK(sizeof(struct ip));
525 				ip = (struct ip *)(evh + 1);
526 				break;
527 			}
528 		    }
529 		default:
530 			goto bypass;	/* pass this frame */
531 		}
532 		break;
533 	    }
534 	case DLT_RAW:		/* IP packets */
535 		M_CHECK(sizeof(struct ip));
536 		ip = mtod(m, struct ip *);
537 		break;
538 	default:
539 		goto bypass;
540 		break;
541 	}
542 
543 	if ((ip->ip_off & htons(IP_OFFMASK)) == 0) {
544 		/*
545 		 * In case of IP header with options, we haven't pulled
546 		 * up enough, yet.
547 		 */
548 		pullup_len += (ip->ip_hl << 2) - sizeof(struct ip);
549 
550 		switch (ip->ip_p) {
551 		case IPPROTO_TCP:
552 			M_CHECK(sizeof(struct tcphdr));
553 			break;
554 		case IPPROTO_UDP:
555 			M_CHECK(sizeof(struct udphdr));
556 			break;
557 		}
558 	}
559 
560 	switch (iface->info.ifinfo_dlt) {
561 	case DLT_EN10MB:
562 	    {
563 		struct ether_header *eh;
564 
565 		eh = mtod(m, struct ether_header *);
566 		switch (ntohs(eh->ether_type)) {
567 		case ETHERTYPE_IP:
568 			ip = (struct ip *)(eh + 1);
569 			break;
570 		case ETHERTYPE_VLAN:
571 		    {
572 			struct ether_vlan_header *evh;
573 
574 			evh = mtod(m, struct ether_vlan_header *);
575 			ip = (struct ip *)(evh + 1);
576 			break;
577 		     }
578 		default:
579 			panic("ng_netflow entered deadcode");
580 		}
581 		break;
582 	     }
583 	case DLT_RAW:
584 		ip = mtod(m, struct ip *);
585 		break;
586 	default:
587 		panic("ng_netflow entered deadcode");
588 	}
589 
590 #undef	M_CHECK
591 
592 	error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif);
593 
594 bypass:
595 	if (iface->out != NULL) {
596 		/* XXX: error gets overwritten here */
597 		NG_FWD_NEW_DATA(error, item, iface->out, m);
598 		return (error);
599 	}
600 done:
601 	if (item)
602 		NG_FREE_ITEM(item);
603 	if (m)
604 		NG_FREE_M(m);
605 
606 	return (error);
607 }
608 
609 /* We will be shut down in a moment */
610 static int
611 ng_netflow_close(node_p node)
612 {
613 	const priv_p priv = NG_NODE_PRIVATE(node);
614 
615 	callout_drain(&priv->exp_callout);
616 	ng_netflow_cache_flush(priv);
617 
618 	return (0);
619 }
620 
621 /* Do local shutdown processing. */
622 static int
623 ng_netflow_rmnode(node_p node)
624 {
625 	const priv_p priv = NG_NODE_PRIVATE(node);
626 
627 	NG_NODE_SET_PRIVATE(node, NULL);
628 	NG_NODE_UNREF(priv->node);
629 
630 	kfree(priv, M_NETGRAPH);
631 
632 	return (0);
633 }
634 
635 /* Hook disconnection. */
636 static int
637 ng_netflow_disconnect(hook_p hook)
638 {
639 	node_p node = NG_HOOK_NODE(hook);
640 	priv_p priv = NG_NODE_PRIVATE(node);
641 	iface_p iface = NG_HOOK_PRIVATE(hook);
642 
643 	if (iface != NULL) {
644 		if (iface->hook == hook)
645 			iface->hook = NULL;
646 		if (iface->out == hook)
647 			iface->out = NULL;
648 	}
649 
650 	/* if export hook disconnected stop running expire(). */
651 	if (hook == priv->export) {
652 		callout_drain(&priv->exp_callout);
653 		priv->export = NULL;
654 	}
655 
656 	/* Removal of the last link destroys the node. */
657 	if (NG_NODE_NUMHOOKS(node) == 0)
658 		ng_rmnode_self(node);
659 
660 	return (0);
661 }
662