Initial libmnl porting attempt
[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)
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) < 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 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
361 static int qos_filter_cb (constr struct nlmsghdr *nlh, void *args)
362 {
363   struct tcmsg *tm = mnl_nlmsg_get_payload(nlh);
364   int wanted_ifindex = *((int *) args);
365
366   const char *dev;
367   const char *kind = NULL;
368   struct gnet_stats_basic *bs = NULL;
369   struct tc_stats *ts = NULL;
370
371   /* char *type_instance; */
372   char *tc_type;
373   char tc_inst[DATA_MAX_NAME_LEN];
374
375   if (tm->tcm_ifindex != wanted_ifindex)
376   {
377     DEBUG ("netlink plugin: qos_filter_cb: Got %s for interface #%i, "
378         "but expected #%i.",
379         tc_type, msg->tcm_ifindex, wanted_ifindex);
380     return MNL_CB_OK;
381   }
382
383   if (nlh->nlmsg_type == RTM_NEWQDISC)
384     tc_type = "qdisc";
385   else if (nlh->nlmsg_type == RTM_NEWTCLASS)
386     tc_type = "class";
387   else if (nlh->nlmsg_type == RTM_NEWTFILTER)
388     tc_type = "filter";
389   else
390   {
391     ERROR ("netlink plugin: qos_filter_cb: Don't know how to handle type %i.",
392         nlh->nlmsg_type);
393     return MNL_CB_ERROR;
394   }
395
396   if ((tm->tcm_ifindex >= 0)
397       && ((size_t) tm->tcm_ifindex >= iflist_len))
398   {
399     ERROR ("netlink plugin: qos_filter_cb: tm->tcm_ifindex = %i "
400         ">= iflist_len = %zu",
401         tm->tcm_ifindex, iflist_len);
402     return MNL_CB_ERROR;
403   }
404
405   dev = iflist[msg->tcm_ifindex];
406   if (dev == NULL)
407   {
408     ERROR ("netlink plugin: qos_filter_cb: iflist[%i] == NULL",
409         msg->tcm_ifindex);
410     return MNL_CB_ERROR;
411   }
412
413   mnl_attr_for_each (attr, nlh, sizeof (*tm))
414   {
415     if (mnl_attr_get_type(attr) != TCA_KIND)
416       continue;
417
418     if (mnl_attr_validate (attr, MNL_TYPE_STRING) < 0)
419     {
420       ERROR ("netlink plugin: qos_filter_cb: TCA_KIND mnl_attr_validate failed.");
421       return MNL_CB_ERROR;
422     }
423
424     kind = mnl_attr_get_str(attr);
425     break;
426   }
427
428   if (kind == NULL)
429   {
430     ERROR ("netlink plugin: qos_filter_cb: kind == NULL");
431     return (-1);
432   }
433
434   { /* The the ID */
435     uint32_t numberic_id;
436
437     numberic_id = tm->tcm_handle;
438     if (strcmp (tc_type, "filter") == 0)
439       numberic_id = tm->tcm_parent;
440
441     ssnprintf (tc_inst, sizeof (tc_inst), "%s-%x:%x",
442         kind,
443         numberic_id >> 16,
444         numberic_id & 0x0000FFFF);
445   }
446
447   DEBUG ("netlink plugin: qos_filter_cb: got %s for %s (%i).",
448       tc_type, dev, msg->tcm_ifindex);
449
450   if (check_ignorelist (dev, tc_type, tc_inst))
451     return MNL_CB_OK;
452
453 #if HAVE_TCA_STATS2
454   mnl_attr_for_each (attr, nlh, sizeof (*tm))
455   {
456     if (mnl_attr_get_type(attr) != TCA_STATS2)
457       continue;
458
459     if (mnl_attr_validate(attr, MNL_TYPE_NESTED) < 0)
460     {
461       ERROR ("netlink plugin: qos_filter_cb: TCA_STATS2 mnl_attr_validate failed.");
462       return MNL_CB_ERROR;
463     }
464
465     mnl_attr_parse_nested(attr, qos_attr_cb, &bs);
466
467     break;
468   }
469
470   if (bs != NULL)
471   {
472     char type_instance[DATA_MAX_NAME_LEN];
473
474     ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
475         tc_type, tc_inst);
476
477     submit_one (dev, "ipt_bytes", type_instance, bs->bytes);
478     submit_one (dev, "ipt_packets", type_instance, bs->packets);
479   }
480 #endif /* TCA_STATS2 */
481
482 #if HAVE_TCA_STATS
483   mnl_attr_for_each (attr, nlh, sizeof (*tm))
484   {
485     if (mnl_attr_get_type(attr) != TCA_STATS)
486       continue;
487
488     if (mnl_attr_validate2 (attr, MNL_TYPE_UNSPEC, sizeof (*ts)) < 0)
489     {
490       ERROR ("netlink plugin: qos_filter_cb: TCA_STATS mnl_attr_validate2 failed.");
491       return MNL_CB_ERROR;
492     }
493     ts = mnl_attr_get_payload(attr);
494     break;
495   }
496
497   if (bs == NULL && ts != NULL)
498   {
499     char type_instance[DATA_MAX_NAME_LEN];
500
501     ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
502         tc_type, tc_inst);
503
504     submit_one (dev, "ipt_bytes", type_instance, ts->bytes);
505     submit_one (dev, "ipt_packets", type_instance, ts->packets);
506   }
507 #endif /* TCA_STATS */
508
509 #if !(HAVE_TCA_STATS && HAVE_TCA_STATS2)
510   DEBUG ("netlink plugin: qos_filter_cb: Have neither TCA_STATS2 nor "
511       "TCA_STATS.");
512 #endif
513
514   return MNL_CB_OK;
515 } /* int qos_filter_cb */
516
517 static int ir_config (const char *key, const char *value)
518 {
519   char *new_val;
520   char *fields[8];
521   int fields_num;
522   int status = 1;
523
524   new_val = strdup (value);
525   if (new_val == NULL)
526     return (-1);
527
528   fields_num = strsplit (new_val, fields, STATIC_ARRAY_SIZE (fields));
529   if ((fields_num < 1) || (fields_num > 8))
530   {
531     sfree (new_val);
532     return (-1);
533   }
534
535   if ((strcasecmp (key, "Interface") == 0)
536       || (strcasecmp (key, "VerboseInterface") == 0))
537   {
538     if (fields_num != 1)
539     {
540       ERROR ("netlink plugin: Invalid number of fields for option "
541           "`%s'. Got %i, expected 1.", key, fields_num);
542       status = -1;
543     }
544     else
545     {
546       add_ignorelist (fields[0], "interface", NULL);
547       if (strcasecmp (key, "VerboseInterface") == 0)
548         add_ignorelist (fields[0], "if_detail", NULL);
549       status = 0;
550     }
551   }
552   else if ((strcasecmp (key, "QDisc") == 0)
553       || (strcasecmp (key, "Class") == 0)
554       || (strcasecmp (key, "Filter") == 0))
555   {
556     if ((fields_num < 1) || (fields_num > 2))
557     {
558       ERROR ("netlink plugin: Invalid number of fields for option "
559           "`%s'. Got %i, expected 1 or 2.", key, fields_num);
560       return (-1);
561     }
562     else
563     {
564       add_ignorelist (fields[0], key,
565           (fields_num == 2) ? fields[1] : NULL);
566       status = 0;
567     }
568   }
569   else if (strcasecmp (key, "IgnoreSelected") == 0)
570   {
571     if (fields_num != 1)
572     {
573       ERROR ("netlink plugin: Invalid number of fields for option "
574           "`IgnoreSelected'. Got %i, expected 1.", fields_num);
575       status = -1;
576     }
577     else
578     {
579       if (IS_TRUE (fields[0]))
580         ir_ignorelist_invert = 0;
581       else
582         ir_ignorelist_invert = 1;
583       status = 0;
584     }
585   }
586
587   sfree (new_val);
588
589   return (status);
590 } /* int ir_config */
591
592 static int ir_init (void)
593 {
594   nl = mnl_socket_open(NETLINK_ROUTE);
595   if (nl == NULL)
596   {
597     ERROR ("netlink plugin: ir_init: mnl_socket_open failed.");
598     return (-1);
599   }
600
601   if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
602   {
603     ERROR ("netlink plugin: ir_init: mnl_socket_bind failed.");
604     return (-1);
605   }
606
607   return (0);
608 } /* int ir_init */
609
610 static int ir_read (void)
611 {
612   char buf[MNL_SOCKET_BUFFER_SIZE];
613   struct nlmsghdr *nlh;
614   struct rtgenmsg *rt;
615   int ret;
616   unsigned int seq, portid;
617
618   static const int type_id[] = { RTM_GETQDISC, RTM_GETTCLASS, RTM_GETTFILTER };
619   static const char *type_name[] = { "qdisc", "class", "filter" };
620
621   portid = mnl_socket_get_portid(nl);
622
623   nlh = mnl_nlmsg_put_header (buf);
624   nlh->nlmsg_type = RTM_GETLINK;
625   nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
626   nlh->nlmsg_seq = seq = time (NULL);
627   rt = mnl_nlmsg_put_extra_header (nlh, sizeof (*rt));
628   rt->rtgen_family = AF_PACKET;
629
630   if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
631   {
632     ERROR ("netlink plugin: ir_read: rtnl_wilddump_request failed.");
633     return (-1);
634   }
635
636   ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
637   while (ret > 0)
638   {
639     ret = mnl_cb_run (buf, ret, seq, portid, link_filter_cb, NULL);
640     if (ret <= MNL_CB_STOP)
641       break;
642     ret = mnl_socket_recvfrom (nl, buf, sizeof(buf));
643   }
644   if (ret < 0)
645   {
646     ERROR ("netlink plugin: ir_read: mnl_socket_recvfrom failed.");
647     return (-1);
648   }
649
650   /* `link_filter_cb' will update `iflist' which is used here to iterate
651    * over all interfaces. */
652   for (ifindex = 0; (size_t) ifindex < iflist_len; ifindex++)
653   {
654     struct tcmsg *tm;
655     int ifindex;
656     size_t type_index;
657
658     if (iflist[ifindex] == NULL)
659       continue;
660
661     for (type_index = 0; type_index < STATIC_ARRAY_SIZE (type_id); type_index++)
662     {
663       if (check_ignorelist (iflist[ifindex], type_name[type_index], NULL))
664       {
665         DEBUG ("netlink plugin: ir_read: check_ignorelist (%s, %s, (nil)) "
666             "== TRUE", iflist[ifindex], type_name[type_index]);
667         continue;
668       }
669
670       DEBUG ("netlink plugin: ir_read: querying %s from %s (%i).",
671           type_name[type_index], iflist[ifindex], ifindex);
672
673       nlh = mnl_nlmsg_put_header (buf);
674       nlh->nlmsg_type = type_id[type_index];
675       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
676       nlh->nlmsg_seq = seq = time (NULL);
677       tm = mnl_nlmsg_put_extra_header (nlh, sizeof (*tm));
678       tm->tcm_family = AF_PACKET;
679       tm->tcm_ifindex = ifindex;
680
681       if (mnl_socket_sendto (nl, nlh, nlh->nlmsg_len) < 0)
682       {
683         ERROR ("netlink plugin: ir_read: mnl_socket_sendto failed.");
684         continue;
685       }
686
687       ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
688       while (ret > 0)
689       {
690         ret = mnl_cb_run (buf, ret, seq, portid, qos_filter_cb, &ifindex);
691         if (ret <= MNL_CB_STOP)
692           break;
693         ret = mnl_socket_recvfrom (nl, buf, sizeof (buf));
694       }
695       if (ret < 0)
696       {
697         ERROR ("netlink plugin: ir_read:mnl_socket_recvfrom failed.");
698         continue;
699       }
700
701     } /* for (type_index) */
702   } /* for (if_index) */
703
704   return (0);
705 } /* int ir_read */
706
707 static int ir_shutdown (void)
708 {
709   if (nl)
710   {
711     mnl_socket_close(nl);
712     nl = NULL;
713   }
714
715   return (0);
716 } /* int ir_shutdown */
717
718 void module_register (void)
719 {
720   plugin_register_config ("netlink", ir_config, config_keys, config_keys_num);
721   plugin_register_init ("netlink", ir_init);
722   plugin_register_read ("netlink", ir_read);
723   plugin_register_shutdown ("netlink", ir_shutdown);
724 } /* void module_register */
725
726 /*
727  * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
728  */