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