Merge branch 'collectd-4.0' into collectd-4.1
[collectd.git] / src / netlink.c
1 /**
2  * collectd - src/netlink.c
3  * Copyright (C) 2007  Florian octo Forster
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian octo Forster <octo at verplant.org>
20  **/
21
22 #include "collectd.h"
23 #include "plugin.h"
24 #include "common.h"
25
26 #include <asm/types.h>
27 #include <sys/socket.h>
28 #include <linux/netlink.h>
29 #include <linux/rtnetlink.h>
30 #include <linux/gen_stats.h>
31
32 #if HAVE_LIBNETLINK_H
33 # include <libnetlink.h>
34 #elif HAVE_IPROUTE_LIBNETLINK_H
35 # include <iproute/libnetlink.h>
36 #elif HAVE_LINUX_LIBNETLINK_H
37 # include <linux/libnetlink.h>
38 #endif
39
40 typedef struct ir_ignorelist_s
41 {
42   char *device;
43   char *type;
44   char *inst;
45   struct ir_ignorelist_s *next;
46 } ir_ignorelist_t;
47
48 static int ir_ignorelist_invert = 1;
49 static ir_ignorelist_t *ir_ignorelist_head = NULL;
50
51 static struct rtnl_handle rth;
52
53 static char **iflist = NULL;
54 static size_t iflist_len = 0;
55
56 static const char *config_keys[] =
57 {
58         "Interface",
59         "VerboseInterface",
60         "QDisc",
61         "Class",
62         "Filter",
63         "IgnoreSelected"
64 };
65 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
66
67 static int add_ignorelist (const char *dev, const char *type,
68     const char *inst)
69 {
70   ir_ignorelist_t *entry;
71
72   entry = (ir_ignorelist_t *) malloc (sizeof (ir_ignorelist_t));
73   if (entry == NULL)
74     return (-1);
75
76   memset (entry, '\0', sizeof (ir_ignorelist_t));
77
78   if (strcasecmp (dev, "All") != 0)
79   {
80     entry->device = strdup (dev);
81     if (entry->device == NULL)
82     {
83       sfree (entry);
84       return (-1);
85     }
86   }
87
88   entry->type = strdup (type);
89   if (entry->type == NULL)
90   {
91     sfree (entry->device);
92     sfree (entry);
93     return (-1);
94   }
95
96   if (inst != NULL)
97   {
98     entry->inst = strdup (inst);
99     if (entry->inst == NULL)
100     {
101       sfree (entry->type);
102       sfree (entry->device);
103       sfree (entry);
104       return (-1);
105     }
106   }
107
108   entry->next = ir_ignorelist_head;
109   ir_ignorelist_head = entry;
110
111   return (0);
112 } /* int add_ignorelist */
113
114 /* 
115  * Checks wether a data set should be ignored. Returns `true' is the value
116  * should be ignored, `false' otherwise.
117  */
118 static int check_ignorelist (const char *dev,
119     const char *type, const char *type_instance)
120 {
121   ir_ignorelist_t *i;
122
123   assert ((dev != NULL) && (type != NULL));
124
125   if (ir_ignorelist_head == NULL)
126     return (ir_ignorelist_invert ? 0 : 1);
127
128   for (i = ir_ignorelist_head; i != NULL; i = i->next)
129   {
130     /* i->device == NULL  =>  match all devices */
131     if ((i->device != NULL)
132         && (strcasecmp (i->device, dev) != 0))
133       continue;
134
135     if (strcasecmp (i->type, type) != 0)
136       continue;
137
138     if ((i->inst != NULL) && (type_instance != NULL)
139         && (strcasecmp (i->inst, type_instance) != 0))
140       continue;
141
142     DEBUG ("netlink plugin: check_ignorelist: "
143         "(dev = %s; type = %s; inst = %s) matched "
144         "(dev = %s; type = %s; inst = %s)",
145         dev, type,
146         type_instance == NULL ? "(nil)" : type_instance,
147         i->device == NULL ? "(nil)" : i->device,
148         i->type,
149         i->inst == NULL ? "(nil)" : i->inst);
150
151     return (ir_ignorelist_invert ? 0 : 1);
152   } /* for i */
153
154   return (ir_ignorelist_invert);
155 } /* int check_ignorelist */
156
157 static void submit_one (const char *dev, const char *type,
158     const char *type_instance, counter_t value)
159 {
160   value_t values[1];
161   value_list_t vl = VALUE_LIST_INIT;
162
163   values[0].counter = value;
164
165   vl.values = values;
166   vl.values_len = 1;
167   vl.time = time (NULL);
168   strcpy (vl.host, hostname_g);
169   strcpy (vl.plugin, "netlink");
170   strncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
171
172   if (type_instance != NULL)
173     strncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
174
175   plugin_dispatch_values (type, &vl);
176 } /* void submit_one */
177
178 static void submit_two (const char *dev, const char *type,
179     const char *type_instance,
180     counter_t rx, counter_t tx)
181 {
182   value_t values[2];
183   value_list_t vl = VALUE_LIST_INIT;
184
185   values[0].counter = rx;
186   values[1].counter = tx;
187
188   vl.values = values;
189   vl.values_len = 2;
190   vl.time = time (NULL);
191   strcpy (vl.host, hostname_g);
192   strcpy (vl.plugin, "netlink");
193   strncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
194
195   if (type_instance != NULL)
196     strncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
197
198   plugin_dispatch_values (type, &vl);
199 } /* void submit_two */
200
201 static int link_filter (const struct sockaddr_nl *sa, struct nlmsghdr *nmh,
202     void *args)
203 {
204   struct ifinfomsg *msg;
205   int msg_len;
206   struct rtattr *attrs[IFLA_MAX + 1];
207   struct rtnl_link_stats *stats;
208
209   const char *dev;
210
211   if (nmh->nlmsg_type != RTM_NEWLINK)
212   {
213     ERROR ("netlink plugin: link_filter: Don't know how to handle type %i.",
214         nmh->nlmsg_type);
215     return (-1);
216   }
217
218   msg = NLMSG_DATA (nmh);
219
220   msg_len = nmh->nlmsg_len - sizeof (struct ifinfomsg);
221   if (msg_len < 0)
222   {
223     ERROR ("netlink plugin: link_filter: msg_len = %i < 0;", msg_len);
224     return (-1);
225   }
226
227   memset (attrs, '\0', sizeof (attrs));
228   if (parse_rtattr (attrs, IFLA_MAX, IFLA_RTA (msg), msg_len) != 0)
229   {
230     ERROR ("netlink plugin: link_filter: parse_rtattr failed.");
231     return (-1);
232   }
233
234   if (attrs[IFLA_STATS] == NULL)
235     return (-1);
236   stats = RTA_DATA (attrs[IFLA_STATS]);
237
238   if (attrs[IFLA_IFNAME] == NULL)
239   {
240     ERROR ("netlink plugin: link_filter: attrs[IFLA_IFNAME] == NULL");
241     return (-1);
242   }
243   dev = RTA_DATA (attrs[IFLA_IFNAME]);
244
245   /* Update the `iflist'. It's used to know which interfaces exist and query
246    * them later for qdiscs and classes. */
247   if (msg->ifi_index >= iflist_len)
248   {
249     char **temp;
250
251     temp = (char **) realloc (iflist, (msg->ifi_index + 1) * sizeof (char *));
252     if (temp == NULL)
253     {
254       ERROR ("netlink plugin: link_filter: realloc failed.");
255       return (-1);
256     }
257
258     memset (temp + iflist_len, '\0',
259         (msg->ifi_index + 1 - iflist_len) * sizeof (char *));
260     iflist = temp;
261     iflist_len = msg->ifi_index + 1;
262   }
263   if ((iflist[msg->ifi_index] == NULL)
264       || (strcmp (iflist[msg->ifi_index], dev) != 0))
265   {
266     sfree (iflist[msg->ifi_index]);
267     iflist[msg->ifi_index] = strdup (dev);
268   }
269
270   if (check_ignorelist (dev, "interface", NULL) == 0)
271   {
272     submit_two (dev, "if_octets", NULL, stats->rx_bytes, stats->tx_bytes);
273     submit_two (dev, "if_packets", NULL, stats->rx_packets, stats->tx_packets);
274     submit_two (dev, "if_errors", NULL, stats->rx_errors, stats->tx_errors);
275   }
276   else
277   {
278     DEBUG ("netlink plugin: Ignoring %s/interface.", dev);
279   }
280
281   if (check_ignorelist (dev, "if_detail", NULL) == 0)
282   {
283     submit_two (dev, "if_dropped", NULL, stats->rx_dropped, stats->tx_dropped);
284     submit_one (dev, "if_multicast", NULL, stats->multicast);
285     submit_one (dev, "if_collisions", NULL, stats->collisions);
286
287     submit_one (dev, "if_rx_errors", "length", stats->rx_length_errors);
288     submit_one (dev, "if_rx_errors", "over", stats->rx_over_errors);
289     submit_one (dev, "if_rx_errors", "crc", stats->rx_crc_errors);
290     submit_one (dev, "if_rx_errors", "frame", stats->rx_frame_errors);
291     submit_one (dev, "if_rx_errors", "fifo", stats->rx_fifo_errors);
292     submit_one (dev, "if_rx_errors", "missed", stats->rx_missed_errors);
293
294     submit_one (dev, "if_tx_errors", "aborted", stats->tx_aborted_errors);
295     submit_one (dev, "if_tx_errors", "carrier", stats->tx_carrier_errors);
296     submit_one (dev, "if_tx_errors", "fifo", stats->tx_fifo_errors);
297     submit_one (dev, "if_tx_errors", "heartbeat", stats->tx_heartbeat_errors);
298     submit_one (dev, "if_tx_errors", "window", stats->tx_window_errors);
299   }
300   else
301   {
302     DEBUG ("netlink plugin: Ignoring %s/if_detail.", dev);
303   }
304
305   return (0);
306 } /* int link_filter */
307
308 static int qos_filter (const struct sockaddr_nl *sa, struct nlmsghdr *nmh,
309     void *args)
310 {
311   struct tcmsg *msg;
312   int msg_len;
313   struct rtattr *attrs[TCA_MAX + 1];
314
315   int wanted_ifindex = *((int *) args);
316
317   const char *dev;
318
319   /* char *type_instance; */
320   char *tc_type;
321   char tc_inst[DATA_MAX_NAME_LEN];
322
323   if (nmh->nlmsg_type == RTM_NEWQDISC)
324     tc_type = "qdisc";
325   else if (nmh->nlmsg_type == RTM_NEWTCLASS)
326     tc_type = "class";
327   else if (nmh->nlmsg_type == RTM_NEWTFILTER)
328     tc_type = "filter";
329   else
330   {
331     ERROR ("netlink plugin: qos_filter: Don't know how to handle type %i.",
332         nmh->nlmsg_type);
333     return (-1);
334   }
335
336   msg = NLMSG_DATA (nmh);
337
338   msg_len = nmh->nlmsg_len - sizeof (struct tcmsg);
339   if (msg_len < 0)
340   {
341     ERROR ("netlink plugin: qos_filter: msg_len = %i < 0;", msg_len);
342     return (-1);
343   }
344
345   if (msg->tcm_ifindex != wanted_ifindex)
346   {
347     DEBUG ("netlink plugin: qos_filter: Got %s for interface #%i, "
348         "but expected #%i.",
349         tc_type, msg->tcm_ifindex, wanted_ifindex);
350     return (0);
351   }
352
353   if (msg->tcm_ifindex >= iflist_len)
354   {
355     ERROR ("netlink plugin: qos_filter: msg->tcm_ifindex = %i "
356         ">= iflist_len = %i",
357         msg->tcm_ifindex, iflist_len);
358     return (-1);
359   }
360
361   dev = iflist[msg->tcm_ifindex];
362   if (dev == NULL)
363   {
364     ERROR ("netlink plugin: qos_filter: iflist[%i] == NULL",
365         msg->tcm_ifindex);
366     return (-1);
367   }
368
369   memset (attrs, '\0', sizeof (attrs));
370   if (parse_rtattr (attrs, TCA_MAX, TCA_RTA (msg), msg_len) != 0)
371   {
372     ERROR ("netlink plugin: qos_filter: parse_rtattr failed.");
373     return (-1);
374   }
375
376   if (attrs[TCA_KIND] == NULL)
377   {
378     ERROR ("netlink plugin: qos_filter: attrs[TCA_KIND] == NULL");
379     return (-1);
380   }
381
382   { /* The the ID */
383     uint32_t numberic_id;
384
385     numberic_id = msg->tcm_handle;
386     if (strcmp (tc_type, "filter") == 0)
387       numberic_id = msg->tcm_parent;
388
389     snprintf (tc_inst, sizeof (tc_inst), "%s-%x:%x",
390         (const char *) RTA_DATA (attrs[TCA_KIND]),
391         numberic_id >> 16,
392         numberic_id & 0x0000FFFF);
393     tc_inst[sizeof (tc_inst) - 1] = '\0';
394   }
395
396   DEBUG ("netlink plugin: qos_filter: got %s for %s (%i).",
397       tc_type, dev, msg->tcm_ifindex);
398   
399   if (check_ignorelist (dev, tc_type, tc_inst))
400     return (0);
401
402   if (attrs[TCA_STATS2])
403   {
404     struct rtattr *attrs_stats[TCA_STATS_MAX + 1];
405
406     memset (attrs_stats, '\0', sizeof (attrs_stats));
407     parse_rtattr_nested (attrs_stats, TCA_STATS_MAX, attrs[TCA_STATS2]);
408
409     if (attrs_stats[TCA_STATS_BASIC])
410     {
411       struct gnet_stats_basic bs;
412       char type_instance[DATA_MAX_NAME_LEN];
413
414       snprintf (type_instance, sizeof (type_instance), "%s-%s",
415           tc_type, tc_inst);
416       type_instance[sizeof (type_instance) - 1] = '\0';
417
418       memset (&bs, '\0', sizeof (bs));
419       memcpy (&bs, RTA_DATA (attrs_stats[TCA_STATS_BASIC]),
420           MIN (RTA_PAYLOAD (attrs_stats[TCA_STATS_BASIC]), sizeof(bs)));
421
422       submit_one (dev, "ipt_bytes", type_instance, bs.bytes);
423       submit_one (dev, "ipt_packets", type_instance, bs.packets);
424     }
425   }
426
427   return (0);
428 } /* int qos_filter */
429
430 static int ir_config (const char *key, const char *value)
431 {
432   char *new_val;
433   char *fields[8];
434   int fields_num;
435   int status = 1;
436
437   new_val = strdup (value);
438   if (new_val == NULL)
439     return (-1);
440
441   fields_num = strsplit (new_val, fields, STATIC_ARRAY_SIZE (fields));
442   if ((fields_num < 1) || (fields_num > 8))
443   {
444     sfree (new_val);
445     return (-1);
446   }
447
448   if ((strcasecmp (key, "Interface") == 0)
449       || (strcasecmp (key, "VerboseInterface") == 0))
450   {
451     if (fields_num != 1)
452     {
453       ERROR ("netlink plugin: Invalid number of fields for option "
454           "`%s'. Got %i, expected 1.", key, fields_num);
455       status = -1;
456     }
457     else
458     {
459       add_ignorelist (fields[0], "interface", NULL);
460       if (strcasecmp (key, "VerboseInterface") == 0)
461         add_ignorelist (fields[0], "if_detail", NULL);
462       status = 0;
463     }
464   }
465   else if ((strcasecmp (key, "QDisc") == 0)
466       || (strcasecmp (key, "Class") == 0)
467       || (strcasecmp (key, "Filter") == 0))
468   {
469     if ((fields_num < 1) || (fields_num > 2))
470     {
471       ERROR ("netlink plugin: Invalid number of fields for option "
472           "`%s'. Got %i, expected 1 or 2.", key, fields_num);
473       return (-1);
474     }
475     else
476     {
477       add_ignorelist (fields[0], key,
478           (fields_num == 2) ? fields[1] : NULL);
479       status = 0;
480     }
481   }
482   else if (strcasecmp (key, "IgnoreSelected") == 0)
483   {
484     if (fields_num != 1)
485     {
486       ERROR ("netlink plugin: Invalid number of fields for option "
487           "`IgnoreSelected'. Got %i, expected 1.", fields_num);
488       status = -1;
489     }
490     else
491     {
492       if ((strcasecmp (fields[0], "yes") == 0)
493           || (strcasecmp (fields[0], "true") == 0)
494           || (strcasecmp (fields[0], "on") == 0))
495         ir_ignorelist_invert = 0;
496       else
497         ir_ignorelist_invert = 1;
498       status = 0;
499     }
500   }
501
502   sfree (new_val);
503
504   return (status);
505 } /* int ir_config */
506
507 static int ir_init (void)
508 {
509   memset (&rth, '\0', sizeof (rth));
510
511   if (rtnl_open (&rth, 0) != 0)
512   {
513     ERROR ("netlink plugin: ir_init: rtnl_open failed.");
514     return (-1);
515   }
516
517   return (0);
518 } /* int ir_init */
519
520 static int ir_read (void)
521 {
522   struct ifinfomsg im;
523   struct tcmsg tm;
524   int ifindex;
525
526   static const int type_id[] = { RTM_GETQDISC, RTM_GETTCLASS, RTM_GETTFILTER };
527   static const char *type_name[] = { "qdisc", "class", "filter" };
528
529   memset (&im, '\0', sizeof (im));
530   im.ifi_type = AF_UNSPEC;
531
532   if (rtnl_dump_request (&rth, RTM_GETLINK, &im, sizeof (im)) < 0)
533   {
534     ERROR ("netlink plugin: ir_read: rtnl_dump_request failed.");
535     return (-1);
536   }
537
538   if (rtnl_dump_filter (&rth, link_filter, /* arg1 = */ NULL,
539         NULL, NULL) != 0)
540   {
541     ERROR ("netlink plugin: ir_read: rtnl_dump_filter failed.");
542     return (-1);
543   }
544
545   /* `link_filter' will update `iflist' which is used here to iterate over all
546    * interfaces. */
547   for (ifindex = 0; ifindex < iflist_len; ifindex++)
548   {
549     int type_index;
550
551     if (iflist[ifindex] == NULL)
552       continue;
553
554     for (type_index = 0; type_index < STATIC_ARRAY_SIZE (type_id); type_index++)
555     {
556       if (check_ignorelist (iflist[ifindex], type_name[type_index], NULL))
557       {
558         DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
559             "== TRUE", iflist[ifindex], type_name[type_index]);
560         continue;
561       }
562
563       DEBUG ("netlink plugin: ir_read: querying %s from %s (%i).",
564           type_name[type_index], iflist[ifindex], ifindex);
565
566       memset (&tm, '\0', sizeof (tm));
567       tm.tcm_family = AF_UNSPEC;
568       tm.tcm_ifindex = ifindex;
569
570       if (rtnl_dump_request (&rth, type_id[type_index], &tm, sizeof (tm)) < 0)
571       {
572         ERROR ("netlink plugin: ir_read: rtnl_dump_request failed.");
573         continue;
574       }
575
576       if (rtnl_dump_filter (&rth, qos_filter, (void *) &ifindex,
577             NULL, NULL) != 0)
578       {
579         ERROR ("netlink plugin: ir_read: rtnl_dump_filter failed.");
580         continue;
581       }
582     } /* for (type_index) */
583   } /* for (if_index) */
584
585   return (0);
586 } /* int ir_read */
587
588 static int ir_shutdown (void)
589 {
590   if ((rth.fd != 0) || (rth.seq != 0) || (rth.dump != 0))
591   {
592     rtnl_close(&rth);
593     memset (&rth, '\0', sizeof (rth));
594   }
595   
596   return (0);
597 } /* int ir_shutdown */
598
599 void module_register (void)
600 {
601   plugin_register_config ("netlink", ir_config, config_keys, config_keys_num);
602   plugin_register_init ("netlink", ir_init);
603   plugin_register_read ("netlink", ir_read);
604   plugin_register_shutdown ("netlink", ir_shutdown);
605 } /* void module_register */
606
607 /*
608  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
609  */