netlink: fix segfault & make advanced options work again
[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
235 static void check_ignorelist_and_submit(const char *dev,
236     struct rtnl_link_stats *stats)
237 {
238
239   if (check_ignorelist (dev, "interface", NULL) == 0)
240   {
241     submit_two (dev, "if_octets", NULL, stats->rx_bytes, stats->tx_bytes);
242     submit_two (dev, "if_packets", NULL, stats->rx_packets, stats->tx_packets);
243     submit_two (dev, "if_errors", NULL, stats->rx_errors, stats->tx_errors);
244   }
245   else
246   {
247     DEBUG ("netlink plugin: Ignoring %s/interface.", dev);
248   }
249
250   if (check_ignorelist (dev, "if_detail", NULL) == 0)
251   {
252     submit_two (dev, "if_dropped", NULL, stats->rx_dropped, stats->tx_dropped);
253     submit_one (dev, "if_multicast", NULL, stats->multicast);
254     submit_one (dev, "if_collisions", NULL, stats->collisions);
255
256     submit_one (dev, "if_rx_errors", "length", stats->rx_length_errors);
257     submit_one (dev, "if_rx_errors", "over", stats->rx_over_errors);
258     submit_one (dev, "if_rx_errors", "crc", stats->rx_crc_errors);
259     submit_one (dev, "if_rx_errors", "frame", stats->rx_frame_errors);
260     submit_one (dev, "if_rx_errors", "fifo", stats->rx_fifo_errors);
261     submit_one (dev, "if_rx_errors", "missed", stats->rx_missed_errors);
262
263     submit_one (dev, "if_tx_errors", "aborted", stats->tx_aborted_errors);
264     submit_one (dev, "if_tx_errors", "carrier", stats->tx_carrier_errors);
265     submit_one (dev, "if_tx_errors", "fifo", stats->tx_fifo_errors);
266     submit_one (dev, "if_tx_errors", "heartbeat", stats->tx_heartbeat_errors);
267     submit_one (dev, "if_tx_errors", "window", stats->tx_window_errors);
268   }
269   else
270   {
271     DEBUG ("netlink plugin: Ignoring %s/if_detail.", dev);
272   }
273
274 }
275
276 static int link_filter_cb (const struct nlmsghdr *nlh,
277     void __attribute__((unused)) *args)
278 {
279   struct ifinfomsg *ifm = mnl_nlmsg_get_payload (nlh); 
280   struct nlattr *attr;
281   struct rtnl_link_stats *stats = NULL;
282   const char *dev = NULL;
283
284   if (nlh->nlmsg_type != RTM_NEWLINK)
285   {
286     ERROR ("netlink plugin: link_filter_cb: Don't know how to handle type %i.",
287         nlh->nlmsg_type);
288     return MNL_CB_ERROR;
289   }
290
291   mnl_attr_for_each (attr, nlh, sizeof (*ifm))
292   {
293     if (mnl_attr_get_type (attr) != IFLA_IFNAME)
294       continue;
295
296     if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
297     {
298       ERROR ("netlink plugin: link_filter_cb: IFLA_IFNAME mnl_attr_validate failed.");
299       return MNL_CB_ERROR;
300     }
301
302     dev = mnl_attr_get_str (attr);
303     if (update_iflist (ifm, dev) < 0)
304       return MNL_CB_ERROR;
305     break;
306   }
307
308   if (dev == NULL)
309   {
310     ERROR ("netlink plugin: link_filter_cb: dev == NULL");
311     return MNL_CB_ERROR;
312   }
313
314   mnl_attr_for_each (attr, nlh, sizeof (*ifm))
315   {
316     if (mnl_attr_get_type (attr) != IFLA_STATS)
317       continue;
318
319     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof(*stats)) < 0)
320     {
321       ERROR ("netlink plugin: link_filter_cb: IFLA_STATS mnl_attr_validate2 failed.");
322       return MNL_CB_ERROR;
323     }
324     stats = mnl_attr_get_payload(attr);
325
326     check_ignorelist_and_submit(dev, stats);
327     break;
328   }
329
330   if (stats == NULL)
331   {
332     DEBUG ("netlink plugin: link_filter: No statistics for interface %s.", dev);
333     return MNL_CB_OK;
334   }
335
336   return MNL_CB_OK;
337 } /* int link_filter_cb */
338
339 #if HAVE_TCA_STATS2
340 static int qos_attr_cb (const struct nlattr *attr, void *data)
341 {
342   struct gnet_stats_basic *bs = *(struct gnet_stats_basic **)data;
343
344   /* skip unsupported attribute in user-space */ 
345   if (mnl_attr_type_valid(attr, TCA_STATS_MAX) < 0)
346     return MNL_CB_OK;
347
348   if (mnl_attr_get_type(attr) == TCA_STATS_BASIC)
349   {
350     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*bs)) < 0)
351     {
352       ERROR ("netlink plugin: qos_attr_cb: TCA_STATS_BASIC mnl_attr_validate2 failed.");
353       return MNL_CB_ERROR;
354     }
355     bs = mnl_attr_get_payload(attr);
356     return MNL_CB_STOP;
357   }
358
359   return MNL_CB_OK;
360 } /* qos_attr_cb */
361 #endif
362
363 static int qos_filter_cb (const struct nlmsghdr *nlh, void *args)
364 {
365   struct tcmsg *tm = mnl_nlmsg_get_payload(nlh);
366   struct nlattr *attr;
367
368   int wanted_ifindex = *((int *) args);
369
370   const char *dev;
371   const char *kind = NULL;
372
373   /* char *type_instance; */
374   char *tc_type;
375   char tc_inst[DATA_MAX_NAME_LEN];
376
377   int __attribute__((unused)) stats_submitted = 0;
378
379   if (nlh->nlmsg_type == RTM_NEWQDISC)
380     tc_type = "qdisc";
381   else if (nlh->nlmsg_type == RTM_NEWTCLASS)
382     tc_type = "class";
383   else if (nlh->nlmsg_type == RTM_NEWTFILTER)
384     tc_type = "filter";
385   else
386   {
387     ERROR ("netlink plugin: qos_filter_cb: Don't know how to handle type %i.",
388         nlh->nlmsg_type);
389     return MNL_CB_ERROR;
390   }
391
392   if (tm->tcm_ifindex != wanted_ifindex)
393   {
394     DEBUG ("netlink plugin: qos_filter_cb: Got %s for interface #%i, "
395         "but expected #%i.",
396         tc_type, tm->tcm_ifindex, wanted_ifindex);
397     return MNL_CB_OK;
398   }
399
400   if ((tm->tcm_ifindex >= 0)
401       && ((size_t) tm->tcm_ifindex >= iflist_len))
402   {
403     ERROR ("netlink plugin: qos_filter_cb: tm->tcm_ifindex = %i "
404         ">= iflist_len = %zu",
405         tm->tcm_ifindex, iflist_len);
406     return MNL_CB_ERROR;
407   }
408
409   dev = iflist[tm->tcm_ifindex];
410   if (dev == NULL)
411   {
412     ERROR ("netlink plugin: qos_filter_cb: iflist[%i] == NULL",
413         tm->tcm_ifindex);
414     return MNL_CB_ERROR;
415   }
416
417   mnl_attr_for_each (attr, nlh, sizeof (*tm))
418   {
419     if (mnl_attr_get_type(attr) != TCA_KIND)
420       continue;
421
422     if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
423     {
424       ERROR ("netlink plugin: qos_filter_cb: TCA_KIND mnl_attr_validate failed.");
425       return MNL_CB_ERROR;
426     }
427
428     kind = mnl_attr_get_str(attr);
429     break;
430   }
431
432   if (kind == NULL)
433   {
434     ERROR ("netlink plugin: qos_filter_cb: kind == NULL");
435     return (-1);
436   }
437
438   { /* The the ID */
439     uint32_t numberic_id;
440
441     numberic_id = tm->tcm_handle;
442     if (strcmp (tc_type, "filter") == 0)
443       numberic_id = tm->tcm_parent;
444
445     ssnprintf (tc_inst, sizeof (tc_inst), "%s-%x:%x",
446         kind,
447         numberic_id >> 16,
448         numberic_id & 0x0000FFFF);
449   }
450
451   DEBUG ("netlink plugin: qos_filter_cb: got %s for %s (%i).",
452       tc_type, dev, tm->tcm_ifindex);
453
454   if (check_ignorelist (dev, tc_type, tc_inst))
455     return MNL_CB_OK;
456
457 #if HAVE_TCA_STATS2
458   mnl_attr_for_each (attr, nlh, sizeof (*tm))
459   {
460     struct gnet_stats_basic *bs = NULL;
461
462     if (mnl_attr_get_type(attr) != TCA_STATS2)
463       continue;
464
465     if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
466     {
467       ERROR ("netlink plugin: qos_filter_cb: TCA_STATS2 mnl_attr_validate failed.");
468       return MNL_CB_ERROR;
469     }
470
471     mnl_attr_parse_nested(attr, qos_attr_cb, &bs);
472
473     if (bs != NULL)
474     {
475       char type_instance[DATA_MAX_NAME_LEN];
476
477       stats_submitted = 1;
478
479       ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
480           tc_type, tc_inst);
481
482       submit_one (dev, "ipt_bytes", type_instance, bs->bytes);
483       submit_one (dev, "ipt_packets", type_instance, bs->packets);
484     }
485
486     break;
487   }
488 #endif /* TCA_STATS2 */
489
490 #if HAVE_TCA_STATS
491   mnl_attr_for_each (attr, nlh, sizeof (*tm))
492   {
493     struct tc_stats *ts = NULL;
494
495     if (mnl_attr_get_type(attr) != TCA_STATS)
496       continue;
497
498     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*ts)) < 0)
499     {
500       ERROR ("netlink plugin: qos_filter_cb: TCA_STATS mnl_attr_validate2 failed.");
501       return MNL_CB_ERROR;
502     }
503     ts = mnl_attr_get_payload(attr);
504
505     if (!stats_submitted && ts != NULL)
506     {
507       char type_instance[DATA_MAX_NAME_LEN];
508
509       ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
510           tc_type, tc_inst);
511
512       submit_one (dev, "ipt_bytes", type_instance, ts->bytes);
513       submit_one (dev, "ipt_packets", type_instance, ts->packets);
514     }
515
516     break;
517   }
518
519 #endif /* TCA_STATS */
520
521 #if !(HAVE_TCA_STATS && HAVE_TCA_STATS2)
522   DEBUG ("netlink plugin: qos_filter_cb: Have neither TCA_STATS2 nor "
523       "TCA_STATS.");
524 #endif
525
526   return MNL_CB_OK;
527 } /* int qos_filter_cb */
528
529 static int ir_config (const char *key, const char *value)
530 {
531   char *new_val;
532   char *fields[8];
533   int fields_num;
534   int status = 1;
535
536   new_val = strdup (value);
537   if (new_val == NULL)
538     return (-1);
539
540   fields_num = strsplit (new_val, fields, STATIC_ARRAY_SIZE (fields));
541   if ((fields_num < 1) || (fields_num > 8))
542   {
543     sfree (new_val);
544     return (-1);
545   }
546
547   if ((strcasecmp (key, "Interface") == 0)
548       || (strcasecmp (key, "VerboseInterface") == 0))
549   {
550     if (fields_num != 1)
551     {
552       ERROR ("netlink plugin: Invalid number of fields for option "
553           "`%s'. Got %i, expected 1.", key, fields_num);
554       status = -1;
555     }
556     else
557     {
558       add_ignorelist (fields[0], "interface", NULL);
559       if (strcasecmp (key, "VerboseInterface") == 0)
560         add_ignorelist (fields[0], "if_detail", NULL);
561       status = 0;
562     }
563   }
564   else if ((strcasecmp (key, "QDisc") == 0)
565       || (strcasecmp (key, "Class") == 0)
566       || (strcasecmp (key, "Filter") == 0))
567   {
568     if ((fields_num < 1) || (fields_num > 2))
569     {
570       ERROR ("netlink plugin: Invalid number of fields for option "
571           "`%s'. Got %i, expected 1 or 2.", key, fields_num);
572       return (-1);
573     }
574     else
575     {
576       add_ignorelist (fields[0], key,
577           (fields_num == 2) ? fields[1] : NULL);
578       status = 0;
579     }
580   }
581   else if (strcasecmp (key, "IgnoreSelected") == 0)
582   {
583     if (fields_num != 1)
584     {
585       ERROR ("netlink plugin: Invalid number of fields for option "
586           "`IgnoreSelected'. Got %i, expected 1.", fields_num);
587       status = -1;
588     }
589     else
590     {
591       if (IS_TRUE (fields[0]))
592         ir_ignorelist_invert = 0;
593       else
594         ir_ignorelist_invert = 1;
595       status = 0;
596     }
597   }
598
599   sfree (new_val);
600
601   return (status);
602 } /* int ir_config */
603
604 static int ir_init (void)
605 {
606   nl = mnl_socket_open(NETLINK_ROUTE);
607   if (nl == NULL)
608   {
609     ERROR ("netlink plugin: ir_init: mnl_socket_open failed.");
610     return (-1);
611   }
612
613   if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
614   {
615     ERROR ("netlink plugin: ir_init: mnl_socket_bind failed.");
616     return (-1);
617   }
618
619   return (0);
620 } /* int ir_init */
621
622 static int ir_read (void)
623 {
624   char buf[MNL_SOCKET_BUFFER_SIZE];
625   struct nlmsghdr *nlh;
626   struct rtgenmsg *rt;
627   int ret;
628   unsigned int seq, portid;
629
630   size_t ifindex;
631
632   static const int type_id[] = { RTM_GETQDISC, RTM_GETTCLASS, RTM_GETTFILTER };
633   static const char *type_name[] = { "qdisc", "class", "filter" };
634
635   portid = mnl_socket_get_portid(nl);
636
637   nlh = mnl_nlmsg_put_header (buf);
638   nlh->nlmsg_type = RTM_GETLINK;
639   nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
640   nlh->nlmsg_seq = seq = time (NULL);
641   rt = mnl_nlmsg_put_extra_header (nlh, sizeof (*rt));
642   rt->rtgen_family = AF_PACKET;
643
644   if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
645   {
646     ERROR ("netlink plugin: ir_read: rtnl_wilddump_request failed.");
647     return (-1);
648   }
649
650   ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
651   while (ret > 0)
652   {
653     ret = mnl_cb_run (buf, ret, seq, portid, link_filter_cb, NULL);
654     if (ret <= MNL_CB_STOP)
655       break;
656     ret = mnl_socket_recvfrom (nl, buf, sizeof(buf));
657   }
658   if (ret < 0)
659   {
660     ERROR ("netlink plugin: ir_read: mnl_socket_recvfrom failed.");
661     return (-1);
662   }
663
664   /* `link_filter_cb' will update `iflist' which is used here to iterate
665    * over all interfaces. */
666   for (ifindex = 1; ifindex < iflist_len; ifindex++)
667   {
668     struct tcmsg *tm;
669     size_t type_index;
670
671     if (iflist[ifindex] == NULL)
672       continue;
673
674     for (type_index = 0; type_index < STATIC_ARRAY_SIZE (type_id); type_index++)
675     {
676       if (check_ignorelist (iflist[ifindex], type_name[type_index], NULL))
677       {
678         DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
679             "== TRUE", iflist[ifindex], type_name[type_index]);
680         continue;
681       }
682
683       DEBUG ("netlink plugin: ir_read: querying %s from %s (%lu).",
684           type_name[type_index], iflist[ifindex], ifindex);
685
686       nlh = mnl_nlmsg_put_header (buf);
687       nlh->nlmsg_type = type_id[type_index];
688       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
689       nlh->nlmsg_seq = seq = time (NULL);
690       tm = mnl_nlmsg_put_extra_header (nlh, sizeof (*tm));
691       tm->tcm_family = AF_PACKET;
692       tm->tcm_ifindex = ifindex;
693
694       if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
695       {
696         ERROR ("netlink plugin: ir_read: mnl_socket_sendto failed.");
697         continue;
698       }
699
700       ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
701       while (ret > 0)
702       {
703         ret = mnl_cb_run (buf, ret, seq, portid, qos_filter_cb, &ifindex);
704         if (ret <= MNL_CB_STOP)
705           break;
706         ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
707       }
708       if (ret < 0)
709       {
710         ERROR ("netlink plugin: ir_read:mnl_socket_recvfrom failed.");
711         continue;
712       }
713
714     } /* for (type_index) */
715   } /* for (if_index) */
716
717   return (0);
718 } /* int ir_read */
719
720 static int ir_shutdown (void)
721 {
722   if (nl)
723   {
724     mnl_socket_close(nl);
725     nl = NULL;
726   }
727
728   return (0);
729 } /* int ir_shutdown */
730
731 void module_register (void)
732 {
733   plugin_register_config ("netlink", ir_config, config_keys, config_keys_num);
734   plugin_register_init ("netlink", ir_init);
735   plugin_register_read ("netlink", ir_read);
736   plugin_register_shutdown ("netlink", ir_shutdown);
737 } /* void module_register */
738
739 /*
740  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
741  */