]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/netgraph/netflow/ng_netflow.c
This commit was generated by cvs2svn to compensate for changes in r156803,
[FreeBSD/FreeBSD.git] / sys / netgraph / 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  */
29
30 static const char rcs_id[] =
31     "@(#) $FreeBSD$";
32
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/kernel.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/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 <netgraph/ng_message.h>
53 #include <netgraph/ng_parse.h>
54 #include <netgraph/netgraph.h>
55 #include <netgraph/netflow/netflow.h>
56 #include <netgraph/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         MALLOC(priv, priv_p, sizeof(*priv), M_NETGRAPH, M_NOWAIT);
172         if (priv == NULL)
173                 return (ENOMEM);
174         bzero(priv, sizeof(*priv));
175
176         /* Make node and its data point at each other */
177         NG_NODE_SET_PRIVATE(node, priv);
178         priv->node = node;
179
180         /* Initialize timeouts to default values */
181         priv->info.nfinfo_inact_t = INACTIVE_TIMEOUT;
182         priv->info.nfinfo_act_t = ACTIVE_TIMEOUT;
183
184         /* Initialize callout handle */
185         callout_init(&priv->exp_callout, 1);
186
187         /* Allocate memory and set up flow cache */
188         if ((error = ng_netflow_cache_init(priv)))
189                 return (error);
190
191         return (0);
192 }
193
194 /*
195  * ng_netflow supports two hooks: data and export.
196  * Incoming traffic is expected on data, and expired
197  * netflow datagrams are sent to export.
198  */
199 static int
200 ng_netflow_newhook(node_p node, hook_p hook, const char *name)
201 {
202         const priv_p priv = NG_NODE_PRIVATE(node);
203
204         if (strncmp(name, NG_NETFLOW_HOOK_DATA, /* an iface hook? */
205             strlen(NG_NETFLOW_HOOK_DATA)) == 0) {
206                 iface_p iface;
207                 int ifnum = -1;
208                 const char *cp;
209                 char *eptr;
210
211                 cp = name + strlen(NG_NETFLOW_HOOK_DATA);
212                 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
213                         return (EINVAL);
214
215                 ifnum = (int)strtoul(cp, &eptr, 10);
216                 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
217                         return (EINVAL);
218
219                 /* See if hook is already connected */
220                 if (priv->ifaces[ifnum].hook != NULL)
221                         return (EISCONN);
222
223                 iface = &priv->ifaces[ifnum];
224
225                 /* Link private info and hook together */
226                 NG_HOOK_SET_PRIVATE(hook, iface);
227                 iface->hook = hook;
228
229                 /*
230                  * In most cases traffic accounting is done on an
231                  * Ethernet interface, so default data link type
232                  * will be DLT_EN10MB.
233                  */
234                 iface->info.ifinfo_dlt = DLT_EN10MB;
235
236         } else if (strncmp(name, NG_NETFLOW_HOOK_OUT,
237             strlen(NG_NETFLOW_HOOK_OUT)) == 0) {
238                 iface_p iface;
239                 int ifnum = -1;
240                 const char *cp;
241                 char *eptr;
242
243                 cp = name + strlen(NG_NETFLOW_HOOK_OUT);
244                 if (!isdigit(*cp) || (cp[0] == '0' && cp[1] != '\0'))
245                         return (EINVAL);
246
247                 ifnum = (int)strtoul(cp, &eptr, 10);
248                 if (*eptr != '\0' || ifnum < 0 || ifnum >= NG_NETFLOW_MAXIFACES)
249                         return (EINVAL);
250
251                 /* See if hook is already connected */
252                 if (priv->ifaces[ifnum].out != NULL)
253                         return (EISCONN);
254
255                 iface = &priv->ifaces[ifnum];
256
257                 /* Link private info and hook together */
258                 NG_HOOK_SET_PRIVATE(hook, iface);
259                 iface->out = hook;
260
261         } else if (strcmp(name, NG_NETFLOW_HOOK_EXPORT) == 0) {
262
263                 if (priv->export != NULL)
264                         return (EISCONN);
265
266                 priv->export = hook;
267
268 #if 0   /* TODO: profile & test first */
269                 /*
270                  * We send export dgrams in interrupt handlers and in
271                  * callout threads. We'd better queue data for later
272                  * netgraph ISR processing.
273                  */
274                 NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
275 #endif
276
277                 /* Exporter is ready. Let's schedule expiry. */
278                 callout_reset(&priv->exp_callout, (1*hz), &ng_netflow_expire,
279                     (void *)priv);
280         } else
281                 return (EINVAL);
282
283         return (0);
284 }
285
286 /* Get a netgraph control message. */
287 static int
288 ng_netflow_rcvmsg (node_p node, item_p item, hook_p lasthook)
289 {
290         const priv_p priv = NG_NODE_PRIVATE(node);
291         struct ng_mesg *resp = NULL;
292         int error = 0;
293         struct ng_mesg *msg;
294
295         NGI_GET_MSG(item, msg);
296
297         /* Deal with message according to cookie and command */
298         switch (msg->header.typecookie) {
299         case NGM_NETFLOW_COOKIE:
300                 switch (msg->header.cmd) {
301                 case NGM_NETFLOW_INFO:
302                 {
303                         struct ng_netflow_info *i;
304
305                         NG_MKRESPONSE(resp, msg, sizeof(struct ng_netflow_info),
306                             M_NOWAIT);
307                         i = (struct ng_netflow_info *)resp->data;
308                         ng_netflow_copyinfo(priv, i);
309
310                         break;
311                 }
312                 case NGM_NETFLOW_IFINFO:
313                 {
314                         struct ng_netflow_ifinfo *i;
315                         const uint16_t *index;
316
317                         if (msg->header.arglen != sizeof(uint16_t))
318                                  ERROUT(EINVAL);
319
320                         index  = (uint16_t *)msg->data;
321                         if (*index >= NG_NETFLOW_MAXIFACES)
322                                 ERROUT(EINVAL);
323
324                         /* connected iface? */
325                         if (priv->ifaces[*index].hook == NULL)
326                                  ERROUT(EINVAL);
327
328                         NG_MKRESPONSE(resp, msg,
329                              sizeof(struct ng_netflow_ifinfo), M_NOWAIT);
330                         i = (struct ng_netflow_ifinfo *)resp->data;
331                         memcpy((void *)i, (void *)&priv->ifaces[*index].info,
332                             sizeof(priv->ifaces[*index].info));
333
334                         break;
335                 }
336                 case NGM_NETFLOW_SETDLT:
337                 {
338                         struct ng_netflow_setdlt *set;
339                         struct ng_netflow_iface *iface;
340
341                         if (msg->header.arglen != sizeof(struct ng_netflow_setdlt))
342                                 ERROUT(EINVAL);
343
344                         set = (struct ng_netflow_setdlt *)msg->data;
345                         if (set->iface >= NG_NETFLOW_MAXIFACES)
346                                 ERROUT(EINVAL);
347                         iface = &priv->ifaces[set->iface];
348
349                         /* connected iface? */
350                         if (iface->hook == NULL)
351                                 ERROUT(EINVAL);
352
353                         switch (set->dlt) {
354                         case    DLT_EN10MB:
355                                 iface->info.ifinfo_dlt = DLT_EN10MB;
356                                 break;
357                         case    DLT_RAW:
358                                 iface->info.ifinfo_dlt = DLT_RAW;
359                                 break;
360                         default:
361                                 ERROUT(EINVAL);
362                         }
363                         break;
364                 }
365                 case NGM_NETFLOW_SETIFINDEX:
366                 {
367                         struct ng_netflow_setifindex *set;
368                         struct ng_netflow_iface *iface;
369
370                         if (msg->header.arglen != sizeof(struct ng_netflow_setifindex))
371                                 ERROUT(EINVAL);
372
373                         set = (struct ng_netflow_setifindex *)msg->data;
374                         if (set->iface >= NG_NETFLOW_MAXIFACES)
375                                 ERROUT(EINVAL);
376                         iface = &priv->ifaces[set->iface];
377
378                         /* connected iface? */
379                         if (iface->hook == NULL)
380                                 ERROUT(EINVAL);
381
382                         iface->info.ifinfo_index = set->index;
383
384                         break;
385                 }
386                 case NGM_NETFLOW_SETTIMEOUTS:
387                 {
388                         struct ng_netflow_settimeouts *set;
389
390                         if (msg->header.arglen != sizeof(struct ng_netflow_settimeouts))
391                                 ERROUT(EINVAL);
392
393                         set = (struct ng_netflow_settimeouts *)msg->data;
394
395                         priv->info.nfinfo_inact_t = set->inactive_timeout;
396                         priv->info.nfinfo_act_t = set->active_timeout;
397
398                         break;
399                 }
400                 case NGM_NETFLOW_SHOW:
401                 {
402                         uint32_t *last;
403
404                         if (msg->header.arglen != sizeof(uint32_t))
405                                 ERROUT(EINVAL);
406
407                         last = (uint32_t *)msg->data;
408
409                         NG_MKRESPONSE(resp, msg, NGRESP_SIZE, M_NOWAIT);
410
411                         if (!resp)
412                                 ERROUT(ENOMEM);
413
414                         error = ng_netflow_flow_show(priv, *last, resp);
415
416                         break;
417                 }
418                 default:
419                         ERROUT(EINVAL);         /* unknown command */
420                         break;
421                 }
422                 break;
423         default:
424                 ERROUT(EINVAL);         /* incorrect cookie */
425                 break;
426         }
427
428         /*
429          * Take care of synchronous response, if any.
430          * Free memory and return.
431          */
432 done:
433         NG_RESPOND_MSG(error, node, item, resp);
434         NG_FREE_MSG(msg);
435
436         return (error);
437 }
438
439 /* Receive data on hook. */
440 static int
441 ng_netflow_rcvdata (hook_p hook, item_p item)
442 {
443         const node_p node = NG_HOOK_NODE(hook);
444         const priv_p priv = NG_NODE_PRIVATE(node);
445         const iface_p iface = NG_HOOK_PRIVATE(hook);
446         struct mbuf *m = NULL;
447         struct ip *ip;
448         int pullup_len = 0;
449         int error = 0;
450
451         if (hook == priv->export) {
452                 /*
453                  * Data arrived on export hook.
454                  * This must not happen.
455                  */
456                 log(LOG_ERR, "ng_netflow: incoming data on export hook!\n");
457                 ERROUT(EINVAL);
458         };
459
460         if (hook == iface->out) {
461                 /*
462                  * Data arrived on out hook. Bypass it.
463                  */
464                 if (iface->hook == NULL)
465                         ERROUT(ENOTCONN);
466
467                 NG_FWD_ITEM_HOOK(error, item, iface->hook);
468                 return (error);
469         }
470
471         NGI_GET_M(item, m);
472
473         /* Increase counters. */
474         iface->info.ifinfo_packets++;
475
476         /*
477          * Depending on interface data link type and packet contents
478          * we pullup enough data, so that ng_netflow_flow_add() does not
479          * need to know about mbuf at all. We keep current length of data
480          * needed to be contiguous in pullup_len. mtod() is done at the
481          * very end one more time, since m can had changed after pulluping.
482          *
483          * In case of unrecognized data we don't return error, but just
484          * pass data to downstream hook, if it is available.
485          */
486
487 #define M_CHECK(length) do {                                    \
488         pullup_len += length;                                   \
489         if ((m)->m_pkthdr.len < (pullup_len)) {                 \
490                 error = EINVAL;                                 \
491                 goto bypass;                                    \
492         }                                                       \
493         if ((m)->m_len < (pullup_len) &&                        \
494            (((m) = m_pullup((m),(pullup_len))) == NULL)) {      \
495                 error = ENOBUFS;                                \
496                 goto done;                                      \
497         }                                                       \
498 } while (0)
499
500         switch (iface->info.ifinfo_dlt) {
501         case DLT_EN10MB:        /* Ethernet */
502             {
503                 struct ether_header *eh;
504                 uint16_t etype;
505
506                 M_CHECK(sizeof(struct ether_header));
507                 eh = mtod(m, struct ether_header *);
508
509                 /* Make sure this is IP frame. */
510                 etype = ntohs(eh->ether_type);
511                 switch (etype) {
512                 case ETHERTYPE_IP:
513                         M_CHECK(sizeof(struct ip));
514                         eh = mtod(m, struct ether_header *);
515                         ip = (struct ip *)(eh + 1);
516                         break;
517                 default:
518                         goto bypass;    /* pass this frame */
519                 }
520                 break;
521             }
522         case DLT_RAW:           /* IP packets */
523                 M_CHECK(sizeof(struct ip));
524                 ip = mtod(m, struct ip *);
525                 break;
526         default:
527                 goto bypass;
528                 break;
529         }
530
531         if ((ip->ip_off & htons(IP_OFFMASK)) == 0) {
532                 /*
533                  * In case of IP header with options, we haven't pulled
534                  * up enough, yet.
535                  */
536                 pullup_len += (ip->ip_hl << 2) - sizeof(struct ip);
537
538                 switch (ip->ip_p) {
539                 case IPPROTO_TCP:
540                         M_CHECK(sizeof(struct tcphdr));
541                         break;
542                 case IPPROTO_UDP:
543                         M_CHECK(sizeof(struct udphdr));
544                         break;
545                 }
546         }
547
548         switch (iface->info.ifinfo_dlt) {
549         case DLT_EN10MB:
550             {
551                 struct ether_header *eh;
552
553                 eh = mtod(m, struct ether_header *);
554                 ip = (struct ip *)(eh + 1);
555                 break;
556              }
557         case DLT_RAW:
558                 ip = mtod(m, struct ip *);
559                 break;
560         default:
561                 panic("ng_netflow entered deadcode");
562         }
563
564 #undef  M_CHECK
565
566         error = ng_netflow_flow_add(priv, ip, iface, m->m_pkthdr.rcvif);
567
568 bypass:
569         if (iface->out != NULL) {
570                 /* XXX: error gets overwritten here */
571                 NG_FWD_NEW_DATA(error, item, iface->out, m);
572                 return (error);
573         }
574 done:
575         if (item)
576                 NG_FREE_ITEM(item);
577         if (m)
578                 NG_FREE_M(m);
579
580         return (error); 
581 }
582
583 /* We will be shut down in a moment */
584 static int
585 ng_netflow_close(node_p node)
586 {
587         const priv_p priv = NG_NODE_PRIVATE(node);
588
589         callout_drain(&priv->exp_callout);
590         ng_netflow_cache_flush(priv);
591
592         return (0);
593 }
594
595 /* Do local shutdown processing. */
596 static int
597 ng_netflow_rmnode(node_p node)
598 {
599         const priv_p priv = NG_NODE_PRIVATE(node);
600
601         NG_NODE_SET_PRIVATE(node, NULL);
602         NG_NODE_UNREF(priv->node);
603
604         FREE(priv, M_NETGRAPH);
605
606         return (0);
607 }
608
609 /* Hook disconnection. */
610 static int
611 ng_netflow_disconnect(hook_p hook)
612 {
613         node_p node = NG_HOOK_NODE(hook);
614         priv_p priv = NG_NODE_PRIVATE(node);
615         iface_p iface = NG_HOOK_PRIVATE(hook);
616
617         if (iface != NULL) {
618                 if (iface->hook == hook)
619                         iface->hook = NULL;
620                 if (iface->out == hook)
621                         iface->out = NULL;
622         }
623
624         /* if export hook disconnected stop running expire(). */
625         if (hook == priv->export) {
626                 callout_drain(&priv->exp_callout);
627                 priv->export = NULL;
628         }
629
630         /* Removal of the last link destroys the node. */
631         if (NG_NODE_NUMHOOKS(node) == 0)
632                 ng_rmnode_self(node);
633
634         return (0);
635 }