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