netlink: indentation & whitespace/tab consistency
[collectd.git] / src / netlink.c
1 /**
2  * collectd - src/netlink.c
3  * Copyright (C) 2007-2010  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 collectd.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
29 #include <linux/netlink.h>
30 #include <linux/rtnetlink.h>
31 #if HAVE_LINUX_GEN_STATS_H
32 # include <linux/gen_stats.h>
33 #endif
34 #if HAVE_LINUX_PKT_SCHED_H
35 # include <linux/pkt_sched.h>
36 #endif
37
38 #include <time.h>
39 #include <libmnl/libmnl.h>
40 //#include <linux/if.h>
41 //#include <linux/if_link.h>
42
43 typedef struct ir_ignorelist_s
44 {
45   char *device;
46   char *type;
47   char *inst;
48   struct ir_ignorelist_s *next;
49 } ir_ignorelist_t;
50
51 static int ir_ignorelist_invert = 1;
52 static ir_ignorelist_t *ir_ignorelist_head = NULL;
53
54 static struct mnl_socket *nl;
55
56 static char **iflist = NULL;
57 static size_t iflist_len = 0;
58
59 static const char *config_keys[] =
60 {
61         "Interface",
62         "VerboseInterface",
63         "QDisc",
64         "Class",
65         "Filter",
66         "IgnoreSelected"
67 };
68 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
69
70 static int add_ignorelist (const char *dev, const char *type,
71     const char *inst)
72 {
73   ir_ignorelist_t *entry;
74
75   entry = (ir_ignorelist_t *) malloc (sizeof (ir_ignorelist_t));
76   if (entry == NULL)
77     return (-1);
78
79   memset (entry, '\0', sizeof (ir_ignorelist_t));
80
81   if (strcasecmp (dev, "All") != 0)
82   {
83     entry->device = strdup (dev);
84     if (entry->device == NULL)
85     {
86       sfree (entry);
87       return (-1);
88     }
89   }
90
91   entry->type = strdup (type);
92   if (entry->type == NULL)
93   {
94     sfree (entry->device);
95     sfree (entry);
96     return (-1);
97   }
98
99   if (inst != NULL)
100   {
101     entry->inst = strdup (inst);
102     if (entry->inst == NULL)
103     {
104       sfree (entry->type);
105       sfree (entry->device);
106       sfree (entry);
107       return (-1);
108     }
109   }
110
111   entry->next = ir_ignorelist_head;
112   ir_ignorelist_head = entry;
113
114   return (0);
115 } /* int add_ignorelist */
116
117 /*
118  * Checks wether a data set should be ignored. Returns `true' is the value
119  * should be ignored, `false' otherwise.
120  */
121 static int check_ignorelist (const char *dev,
122     const char *type, const char *type_instance)
123 {
124   ir_ignorelist_t *i;
125
126   assert ((dev != NULL) && (type != NULL));
127
128   if (ir_ignorelist_head == NULL)
129     return (ir_ignorelist_invert ? 0 : 1);
130
131   for (i = ir_ignorelist_head; i != NULL; i = i->next)
132   {
133     /* i->device == NULL  =>  match all devices */
134     if ((i->device != NULL)
135         && (strcasecmp (i->device, dev) != 0))
136       continue;
137
138     if (strcasecmp (i->type, type) != 0)
139       continue;
140
141     if ((i->inst != NULL) && (type_instance != NULL)
142         && (strcasecmp (i->inst, type_instance) != 0))
143       continue;
144
145     DEBUG ("netlink plugin: check_ignorelist: "
146         "(dev = %s; type = %s; inst = %s) matched "
147         "(dev = %s; type = %s; inst = %s)",
148         dev, type,
149         type_instance == NULL ? "(nil)" : type_instance,
150         i->device == NULL ? "(nil)" : i->device,
151         i->type,
152         i->inst == NULL ? "(nil)" : i->inst);
153
154     return (ir_ignorelist_invert ? 0 : 1);
155   } /* for i */
156
157   return (ir_ignorelist_invert);
158 } /* int check_ignorelist */
159
160 static void submit_one (const char *dev, const char *type,
161     const char *type_instance, derive_t value)
162 {
163   value_t values[1];
164   value_list_t vl = VALUE_LIST_INIT;
165
166   values[0].derive = value;
167
168   vl.values = values;
169   vl.values_len = 1;
170   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
171   sstrncpy (vl.plugin, "netlink", sizeof (vl.plugin));
172   sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
173   sstrncpy (vl.type, type, sizeof (vl.type));
174
175   if (type_instance != NULL)
176     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
177
178   plugin_dispatch_values (&vl);
179 } /* void submit_one */
180
181 static void submit_two (const char *dev, const char *type,
182     const char *type_instance,
183     derive_t rx, derive_t tx)
184 {
185   value_t values[2];
186   value_list_t vl = VALUE_LIST_INIT;
187
188   values[0].derive = rx;
189   values[1].derive = tx;
190
191   vl.values = values;
192   vl.values_len = 2;
193   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
194   sstrncpy (vl.plugin, "netlink", sizeof (vl.plugin));
195   sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
196   sstrncpy (vl.type, type, sizeof (vl.type));
197
198   if (type_instance != NULL)
199     sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
200
201   plugin_dispatch_values (&vl);
202 } /* void submit_two */
203
204 static int update_iflist (struct ifinfomsg *msg, const char *dev)
205 {
206   /* Update the `iflist'. It's used to know which interfaces exist and query
207    * them later for qdiscs and classes. */
208   if ((msg->ifi_index >= 0) && ((size_t) msg->ifi_index >= iflist_len))
209   {
210     char **temp;
211
212     temp = (char **) realloc (iflist, (msg->ifi_index + 1) * sizeof (char *));
213     if (temp == NULL)
214     {
215       ERROR ("netlink plugin: update_iflist: realloc failed.");
216       return (-1);
217     }
218
219     memset (temp + iflist_len, '\0',
220         (msg->ifi_index + 1 - iflist_len) * sizeof (char *));
221     iflist = temp;
222     iflist_len = msg->ifi_index + 1;
223   }
224   if ((iflist[msg->ifi_index] == NULL)
225       || (strcmp (iflist[msg->ifi_index], dev) != 0))
226   {
227     sfree (iflist[msg->ifi_index]);
228     iflist[msg->ifi_index] = strdup (dev);
229   }
230
231   return (0);
232 }
233
234 static void check_ignorelist_and_submit (const char *dev,
235     struct rtnl_link_stats *stats)
236 {
237
238   if (check_ignorelist (dev, "interface", NULL) == 0)
239   {
240     submit_two (dev, "if_octets", NULL, stats->rx_bytes, stats->tx_bytes);
241     submit_two (dev, "if_packets", NULL, stats->rx_packets, stats->tx_packets);
242     submit_two (dev, "if_errors", NULL, stats->rx_errors, stats->tx_errors);
243   }
244   else
245   {
246     DEBUG ("netlink plugin: Ignoring %s/interface.", dev);
247   }
248
249   if (check_ignorelist (dev, "if_detail", NULL) == 0)
250   {
251     submit_two (dev, "if_dropped", NULL, stats->rx_dropped, stats->tx_dropped);
252     submit_one (dev, "if_multicast", NULL, stats->multicast);
253     submit_one (dev, "if_collisions", NULL, stats->collisions);
254
255     submit_one (dev, "if_rx_errors", "length", stats->rx_length_errors);
256     submit_one (dev, "if_rx_errors", "over", stats->rx_over_errors);
257     submit_one (dev, "if_rx_errors", "crc", stats->rx_crc_errors);
258     submit_one (dev, "if_rx_errors", "frame", stats->rx_frame_errors);
259     submit_one (dev, "if_rx_errors", "fifo", stats->rx_fifo_errors);
260     submit_one (dev, "if_rx_errors", "missed", stats->rx_missed_errors);
261
262     submit_one (dev, "if_tx_errors", "aborted", stats->tx_aborted_errors);
263     submit_one (dev, "if_tx_errors", "carrier", stats->tx_carrier_errors);
264     submit_one (dev, "if_tx_errors", "fifo", stats->tx_fifo_errors);
265     submit_one (dev, "if_tx_errors", "heartbeat", stats->tx_heartbeat_errors);
266     submit_one (dev, "if_tx_errors", "window", stats->tx_window_errors);
267   }
268   else
269   {
270     DEBUG ("netlink plugin: Ignoring %s/if_detail.", dev);
271   }
272
273 }
274
275 static int link_filter_cb (const struct nlmsghdr *nlh,
276     void __attribute__((unused)) *args)
277 {
278   struct ifinfomsg *ifm = mnl_nlmsg_get_payload (nlh);
279   struct nlattr *attr;
280   struct rtnl_link_stats *stats = NULL;
281   const char *dev = NULL;
282
283   if (nlh->nlmsg_type != RTM_NEWLINK)
284   {
285     ERROR ("netlink plugin: link_filter_cb: Don't know how to handle type %i.",
286         nlh->nlmsg_type);
287     return MNL_CB_ERROR;
288   }
289
290   mnl_attr_for_each (attr, nlh, sizeof (*ifm))
291   {
292     if (mnl_attr_get_type (attr) != IFLA_IFNAME)
293       continue;
294
295     if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
296     {
297       ERROR ("netlink plugin: link_filter_cb: IFLA_IFNAME mnl_attr_validate failed.");
298       return MNL_CB_ERROR;
299     }
300
301     dev = mnl_attr_get_str (attr);
302     if (update_iflist (ifm, dev) < 0)
303       return MNL_CB_ERROR;
304     break;
305   }
306
307   if (dev == NULL)
308   {
309     ERROR ("netlink plugin: link_filter_cb: dev == NULL");
310     return MNL_CB_ERROR;
311   }
312
313   mnl_attr_for_each (attr, nlh, sizeof (*ifm))
314   {
315     if (mnl_attr_get_type (attr) != IFLA_STATS)
316       continue;
317
318     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*stats)) < 0)
319     {
320       ERROR ("netlink plugin: link_filter_cb: IFLA_STATS mnl_attr_validate2 failed.");
321       return MNL_CB_ERROR;
322     }
323     stats = mnl_attr_get_payload (attr);
324
325     check_ignorelist_and_submit (dev, stats);
326     break;
327   }
328
329   if (stats == NULL)
330   {
331     DEBUG ("netlink plugin: link_filter: No statistics for interface %s.", dev);
332     return MNL_CB_OK;
333   }
334
335   return MNL_CB_OK;
336 } /* int link_filter_cb */
337
338 #if HAVE_TCA_STATS2
339 static int qos_attr_cb (const struct nlattr *attr, void *data)
340 {
341   struct gnet_stats_basic *bs = *(struct gnet_stats_basic **)data;
342
343   /* skip unsupported attribute in user-space */
344   if (mnl_attr_type_valid (attr, TCA_STATS_MAX) < 0)
345     return MNL_CB_OK;
346
347   if (mnl_attr_get_type (attr) == TCA_STATS_BASIC)
348   {
349     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*bs)) < 0)
350     {
351       ERROR ("netlink plugin: qos_attr_cb: TCA_STATS_BASIC mnl_attr_validate2 failed.");
352       return MNL_CB_ERROR;
353     }
354     bs = mnl_attr_get_payload (attr);
355     return MNL_CB_STOP;
356   }
357
358   return MNL_CB_OK;
359 } /* qos_attr_cb */
360 #endif
361
362 static int qos_filter_cb (const struct nlmsghdr *nlh, void *args)
363 {
364   struct tcmsg *tm = mnl_nlmsg_get_payload (nlh);
365   struct nlattr *attr;
366
367   int wanted_ifindex = *((int *) args);
368
369   const char *dev;
370   const char *kind = NULL;
371
372   /* char *type_instance; */
373   char *tc_type;
374   char tc_inst[DATA_MAX_NAME_LEN];
375
376   int __attribute__((unused)) stats_submitted = 0;
377
378   if (nlh->nlmsg_type == RTM_NEWQDISC)
379     tc_type = "qdisc";
380   else if (nlh->nlmsg_type == RTM_NEWTCLASS)
381     tc_type = "class";
382   else if (nlh->nlmsg_type == RTM_NEWTFILTER)
383     tc_type = "filter";
384   else
385   {
386     ERROR ("netlink plugin: qos_filter_cb: Don't know how to handle type %i.",
387         nlh->nlmsg_type);
388     return MNL_CB_ERROR;
389   }
390
391   if (tm->tcm_ifindex != wanted_ifindex)
392   {
393     DEBUG ("netlink plugin: qos_filter_cb: Got %s for interface #%i, "
394         "but expected #%i.",
395         tc_type, tm->tcm_ifindex, wanted_ifindex);
396     return MNL_CB_OK;
397   }
398
399   if ((tm->tcm_ifindex >= 0)
400       && ((size_t) tm->tcm_ifindex >= iflist_len))
401   {
402     ERROR ("netlink plugin: qos_filter_cb: tm->tcm_ifindex = %i "
403         ">= iflist_len = %zu",
404         tm->tcm_ifindex, iflist_len);
405     return MNL_CB_ERROR;
406   }
407
408   dev = iflist[tm->tcm_ifindex];
409   if (dev == NULL)
410   {
411     ERROR ("netlink plugin: qos_filter_cb: iflist[%i] == NULL",
412         tm->tcm_ifindex);
413     return MNL_CB_ERROR;
414   }
415
416   mnl_attr_for_each (attr, nlh, sizeof (*tm))
417   {
418     if (mnl_attr_get_type (attr) != TCA_KIND)
419       continue;
420
421     if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
422     {
423       ERROR ("netlink plugin: qos_filter_cb: TCA_KIND mnl_attr_validate failed.");
424       return MNL_CB_ERROR;
425     }
426
427     kind = mnl_attr_get_str (attr);
428     break;
429   }
430
431   if (kind == NULL)
432   {
433     ERROR ("netlink plugin: qos_filter_cb: kind == NULL");
434     return (-1);
435   }
436
437   { /* The the ID */
438     uint32_t numberic_id;
439
440     numberic_id = tm->tcm_handle;
441     if (strcmp (tc_type, "filter") == 0)
442       numberic_id = tm->tcm_parent;
443
444     ssnprintf (tc_inst, sizeof (tc_inst), "%s-%x:%x",
445         kind,
446         numberic_id >> 16,
447         numberic_id & 0x0000FFFF);
448   }
449
450   DEBUG ("netlink plugin: qos_filter_cb: got %s for %s (%i).",
451       tc_type, dev, tm->tcm_ifindex);
452
453   if (check_ignorelist (dev, tc_type, tc_inst))
454     return MNL_CB_OK;
455
456 #if HAVE_TCA_STATS2
457   mnl_attr_for_each (attr, nlh, sizeof (*tm))
458   {
459     struct gnet_stats_basic *bs = NULL;
460
461     if (mnl_attr_get_type (attr) != TCA_STATS2)
462       continue;
463
464     if (mnl_attr_validate (attr, MNL_TYPE_NESTED) < 0)
465     {
466       ERROR ("netlink plugin: qos_filter_cb: TCA_STATS2 mnl_attr_validate failed.");
467       return MNL_CB_ERROR;
468     }
469
470     mnl_attr_parse_nested (attr, qos_attr_cb, &bs);
471
472     if (bs != NULL)
473     {
474       char type_instance[DATA_MAX_NAME_LEN];
475
476       stats_submitted = 1;
477
478       ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
479           tc_type, tc_inst);
480
481       submit_one (dev, "ipt_bytes", type_instance, bs->bytes);
482       submit_one (dev, "ipt_packets", type_instance, bs->packets);
483     }
484
485     break;
486   }
487 #endif /* TCA_STATS2 */
488
489 #if HAVE_TCA_STATS
490   mnl_attr_for_each (attr, nlh, sizeof (*tm))
491   {
492     struct tc_stats *ts = NULL;
493
494     if (mnl_attr_get_type (attr) != TCA_STATS)
495       continue;
496
497     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*ts)) < 0)
498     {
499       ERROR ("netlink plugin: qos_filter_cb: TCA_STATS mnl_attr_validate2 failed.");
500       return MNL_CB_ERROR;
501     }
502     ts = mnl_attr_get_payload (attr);
503
504     if (!stats_submitted && ts != NULL)
505     {
506       char type_instance[DATA_MAX_NAME_LEN];
507
508       ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
509           tc_type, tc_inst);
510
511       submit_one (dev, "ipt_bytes", type_instance, ts->bytes);
512       submit_one (dev, "ipt_packets", type_instance, ts->packets);
513     }
514
515     break;
516   }
517
518 #endif /* TCA_STATS */
519
520 #if !(HAVE_TCA_STATS && HAVE_TCA_STATS2)
521   DEBUG ("netlink plugin: qos_filter_cb: Have neither TCA_STATS2 nor "
522       "TCA_STATS.");
523 #endif
524
525   return MNL_CB_OK;
526 } /* int qos_filter_cb */
527
528 static int ir_config (const char *key, const char *value)
529 {
530   char *new_val;
531   char *fields[8];
532   int fields_num;
533   int status = 1;
534
535   new_val = strdup (value);
536   if (new_val == NULL)
537     return (-1);
538
539   fields_num = strsplit (new_val, fields, STATIC_ARRAY_SIZE (fields));
540   if ((fields_num < 1) || (fields_num > 8))
541   {
542     sfree (new_val);
543     return (-1);
544   }
545
546   if ((strcasecmp (key, "Interface") == 0)
547       || (strcasecmp (key, "VerboseInterface") == 0))
548   {
549     if (fields_num != 1)
550     {
551       ERROR ("netlink plugin: Invalid number of fields for option "
552           "`%s'. Got %i, expected 1.", key, fields_num);
553       status = -1;
554     }
555     else
556     {
557       add_ignorelist (fields[0], "interface", NULL);
558       if (strcasecmp (key, "VerboseInterface") == 0)
559         add_ignorelist (fields[0], "if_detail", NULL);
560       status = 0;
561     }
562   }
563   else if ((strcasecmp (key, "QDisc") == 0)
564       || (strcasecmp (key, "Class") == 0)
565       || (strcasecmp (key, "Filter") == 0))
566   {
567     if ((fields_num < 1) || (fields_num > 2))
568     {
569       ERROR ("netlink plugin: Invalid number of fields for option "
570           "`%s'. Got %i, expected 1 or 2.", key, fields_num);
571       return (-1);
572     }
573     else
574     {
575       add_ignorelist (fields[0], key,
576           (fields_num == 2) ? fields[1] : NULL);
577       status = 0;
578     }
579   }
580   else if (strcasecmp (key, "IgnoreSelected") == 0)
581   {
582     if (fields_num != 1)
583     {
584       ERROR ("netlink plugin: Invalid number of fields for option "
585           "`IgnoreSelected'. Got %i, expected 1.", fields_num);
586       status = -1;
587     }
588     else
589     {
590       if (IS_TRUE (fields[0]))
591         ir_ignorelist_invert = 0;
592       else
593         ir_ignorelist_invert = 1;
594       status = 0;
595     }
596   }
597
598   sfree (new_val);
599
600   return (status);
601 } /* int ir_config */
602
603 static int ir_init (void)
604 {
605   nl = mnl_socket_open (NETLINK_ROUTE);
606   if (nl == NULL)
607   {
608     ERROR ("netlink plugin: ir_init: mnl_socket_open failed.");
609     return (-1);
610   }
611
612   if (mnl_socket_bind (nl, 0, MNL_SOCKET_AUTOPID) < 0)
613   {
614     ERROR ("netlink plugin: ir_init: mnl_socket_bind failed.");
615     return (-1);
616   }
617
618   return (0);
619 } /* int ir_init */
620
621 static int ir_read (void)
622 {
623   char buf[MNL_SOCKET_BUFFER_SIZE];
624   struct nlmsghdr *nlh;
625   struct rtgenmsg *rt;
626   int ret;
627   unsigned int seq, portid;
628
629   size_t ifindex;
630
631   static const int type_id[] = { RTM_GETQDISC, RTM_GETTCLASS, RTM_GETTFILTER };
632   static const char *type_name[] = { "qdisc", "class", "filter" };
633
634   portid = mnl_socket_get_portid (nl);
635
636   nlh = mnl_nlmsg_put_header (buf);
637   nlh->nlmsg_type = RTM_GETLINK;
638   nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
639   nlh->nlmsg_seq = seq = time (NULL);
640   rt = mnl_nlmsg_put_extra_header (nlh, sizeof (*rt));
641   rt->rtgen_family = AF_PACKET;
642
643   if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
644   {
645     ERROR ("netlink plugin: ir_read: rtnl_wilddump_request failed.");
646     return (-1);
647   }
648
649   ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
650   while (ret > 0)
651   {
652     ret = mnl_cb_run (buf, ret, seq, portid, link_filter_cb, NULL);
653     if (ret <= MNL_CB_STOP)
654       break;
655     ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
656   }
657   if (ret < 0)
658   {
659     ERROR ("netlink plugin: ir_read: mnl_socket_recvfrom failed.");
660     return (-1);
661   }
662
663   /* `link_filter_cb' will update `iflist' which is used here to iterate
664    * over all interfaces. */
665   for (ifindex = 1; ifindex < iflist_len; ifindex++)
666   {
667     struct tcmsg *tm;
668     size_t type_index;
669
670     if (iflist[ifindex] == NULL)
671       continue;
672
673     for (type_index = 0; type_index < STATIC_ARRAY_SIZE (type_id); type_index++)
674     {
675       if (check_ignorelist (iflist[ifindex], type_name[type_index], NULL))
676       {
677         DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
678             "== TRUE", iflist[ifindex], type_name[type_index]);
679         continue;
680       }
681
682       DEBUG ("netlink plugin: ir_read: querying %s from %s (%lu).",
683           type_name[type_index], iflist[ifindex], ifindex);
684
685       nlh = mnl_nlmsg_put_header (buf);
686       nlh->nlmsg_type = type_id[type_index];
687       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
688       nlh->nlmsg_seq = seq = time (NULL);
689       tm = mnl_nlmsg_put_extra_header (nlh, sizeof (*tm));
690       tm->tcm_family = AF_PACKET;
691       tm->tcm_ifindex = ifindex;
692
693       if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
694       {
695         ERROR ("netlink plugin: ir_read: mnl_socket_sendto failed.");
696         continue;
697       }
698
699       ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
700       while (ret > 0)
701       {
702         ret = mnl_cb_run (buf, ret, seq, portid, qos_filter_cb, &ifindex);
703         if (ret <= MNL_CB_STOP)
704           break;
705         ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
706       }
707       if (ret < 0)
708       {
709         ERROR ("netlink plugin: ir_read:mnl_socket_recvfrom failed.");
710         continue;
711       }
712
713     } /* for (type_index) */
714   } /* for (if_index) */
715
716   return (0);
717 } /* int ir_read */
718
719 static int ir_shutdown (void)
720 {
721   if (nl)
722   {
723     mnl_socket_close (nl);
724     nl = NULL;
725   }
726
727   return (0);
728 } /* int ir_shutdown */
729
730 void module_register (void)
731 {
732   plugin_register_config ("netlink", ir_config, config_keys, config_keys_num);
733   plugin_register_init ("netlink", ir_init);
734   plugin_register_read ("netlink", ir_read);
735   plugin_register_shutdown ("netlink", ir_shutdown);
736 } /* void module_register */
737
738 /*
739  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
740  */