{GPL, other}: Relicense to MIT license.
[collectd.git] / src / olsrd.c
1 /**
2  * collectd - src/olsrd.c
3  * Copyright (C) 2009       Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30
31 #include <sys/types.h>
32 #include <netdb.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <netinet/tcp.h>
36
37 #define OLSRD_DEFAULT_NODE "localhost"
38 #define OLSRD_DEFAULT_SERVICE "2006"
39
40 static const char *config_keys[] =
41 {
42   "Host",
43   "Port",
44   "CollectLinks",
45   "CollectRoutes",
46   "CollectTopology"
47 };
48 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
49
50 static char *config_node = NULL;
51 static char *config_service = NULL;
52
53 #define OLSRD_WANT_NOT     0
54 #define OLSRD_WANT_SUMMARY 1
55 #define OLSRD_WANT_DETAIL  2
56 static int config_want_links    = OLSRD_WANT_DETAIL;
57 static int config_want_routes   = OLSRD_WANT_SUMMARY;
58 static int config_want_topology = OLSRD_WANT_SUMMARY;
59
60 static const char *olsrd_get_node (void) /* {{{ */
61 {
62   if (config_node != NULL)
63     return (config_node);
64   return (OLSRD_DEFAULT_NODE);
65 } /* }}} const char *olsrd_get_node */
66
67 static const char *olsrd_get_service (void) /* {{{ */
68 {
69   if (config_service != NULL)
70     return (config_service);
71   return (OLSRD_DEFAULT_SERVICE);
72 } /* }}} const char *olsrd_get_service */
73
74 static void olsrd_set_node (const char *node) /* {{{ */
75 {
76   char *tmp;
77   if (node == NULL)
78     return;
79   tmp = strdup (node);
80   if (tmp == NULL)
81     return;
82   config_node = tmp;
83 } /* }}} void olsrd_set_node */
84
85 static void olsrd_set_service (const char *service) /* {{{ */
86 {
87   char *tmp;
88   if (service == NULL)
89     return;
90   tmp = strdup (service);
91   if (tmp == NULL)
92     return;
93   config_service = tmp;
94 } /* }}} void olsrd_set_service */
95
96 static void olsrd_set_detail (int *varptr, const char *detail, /* {{{ */
97     const char *key)
98 {
99   if (strcasecmp ("No", detail) == 0)
100     *varptr = OLSRD_WANT_NOT;
101   else if (strcasecmp ("Summary", detail) == 0)
102     *varptr = OLSRD_WANT_SUMMARY;
103   else if (strcasecmp ("Detail", detail) == 0)
104     *varptr = OLSRD_WANT_DETAIL;
105   else
106   {
107     ERROR ("olsrd plugin: Invalid argument given to the `%s' configuration "
108         "option: `%s'. Expected: `No', `Summary', or `Detail'.",
109         key, detail);
110   }
111 } /* }}} void olsrd_set_detail */
112
113 /* Strip trailing newline characters. Returns length of string. */
114 static size_t strchomp (char *buffer) /* {{{ */
115 {
116   size_t buffer_len;
117
118   buffer_len = strlen (buffer);
119   while ((buffer_len > 0)
120       && ((buffer[buffer_len - 1] == '\r')
121         || (buffer[buffer_len - 1] == '\n')))
122   {
123     buffer_len--;
124     buffer[buffer_len] = 0;
125   }
126
127   return (buffer_len);
128 } /* }}} size_t strchomp */
129
130 static size_t strtabsplit (char *string, char **fields, size_t size) /* {{{ */
131 {
132   size_t i;
133   char *ptr;
134   char *saveptr;
135
136   i = 0;
137   ptr = string;
138   saveptr = NULL;
139   while ((fields[i] = strtok_r (ptr, " \t\r\n", &saveptr)) != NULL)
140   {
141     ptr = NULL;
142     i++;
143
144     if (i >= size)
145       break;
146   }
147
148   return (i);
149 } /* }}} size_t strtabsplit */
150
151 static FILE *olsrd_connect (void) /* {{{ */
152 {
153   struct addrinfo  ai_hints;
154   struct addrinfo *ai_list, *ai_ptr;
155   int              ai_return;
156
157   FILE *fh;
158
159   memset (&ai_hints, 0, sizeof (ai_hints));
160   ai_hints.ai_flags    = 0;
161 #ifdef AI_ADDRCONFIG
162   ai_hints.ai_flags   |= AI_ADDRCONFIG;
163 #endif
164   ai_hints.ai_family   = PF_UNSPEC;
165   ai_hints.ai_socktype = SOCK_STREAM;
166   ai_hints.ai_protocol = IPPROTO_TCP;
167
168   ai_list = NULL;
169   ai_return = getaddrinfo (olsrd_get_node (), olsrd_get_service (),
170       &ai_hints, &ai_list);
171   if (ai_return != 0)
172   {
173     ERROR ("olsrd plugin: getaddrinfo (%s, %s) failed: %s",
174         olsrd_get_node (), olsrd_get_service (),
175         gai_strerror (ai_return));
176     return (NULL);
177   }
178
179   fh = NULL;
180   for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
181   {
182     int fd;
183     int status;
184     char errbuf[1024];
185
186     fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
187     if (fd < 0)
188     {
189       ERROR ("olsrd plugin: socket failed: %s",
190           sstrerror (errno, errbuf, sizeof (errbuf)));
191       continue;
192     }
193
194     status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
195     if (status != 0)
196     {
197       ERROR ("olsrd plugin: connect failed: %s",
198           sstrerror (errno, errbuf, sizeof (errbuf)));
199       close (fd);
200       continue;
201     }
202
203     fh = fdopen (fd, "r+");
204     if (fh == NULL)
205     {
206       ERROR ("olsrd plugin: fdopen failed.");
207       close (fd);
208       continue;
209     }
210
211     break;
212   } /* for (ai_ptr) */
213
214   freeaddrinfo (ai_list);
215
216   return (fh);
217 } /* }}} FILE *olsrd_connect */
218
219 __attribute__ ((nonnull(2)))
220 static void olsrd_submit (const char *plugin_instance, /* {{{ */
221     const char *type, const char *type_instance, gauge_t value)
222 {
223   value_t values[1];
224   value_list_t vl = VALUE_LIST_INIT;
225
226   values[0].gauge = value;
227
228   vl.values = values;
229   vl.values_len = 1;
230
231   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
232   sstrncpy (vl.plugin, "olsrd", sizeof (vl.plugin));
233   if (plugin_instance != NULL)
234     sstrncpy (vl.plugin_instance, plugin_instance,
235         sizeof (vl.plugin_instance));
236   sstrncpy (vl.type, type, sizeof (vl.type));
237   if (type_instance != NULL)
238     sstrncpy (vl.type_instance, type_instance,
239         sizeof (vl.type_instance));
240
241   plugin_dispatch_values (&vl);
242 } /* }}} void olsrd_submit */
243
244 static int olsrd_cb_ignore (int lineno, /* {{{ */
245     size_t fields_num, char **fields)
246 {
247   return (0);
248 } /* }}} int olsrd_cb_ignore */
249
250 static int olsrd_cb_links (int lineno, /* {{{ */
251     size_t fields_num, char **fields)
252 {
253   /* Fields:
254    *  0 = Local IP
255    *  1 = Remote IP
256    *  2 = Hyst.
257    *  3 = LQ
258    *  4 = NLQ
259    *  5 = Cost */
260
261   static uint32_t links_num;
262   static double    lq_sum;
263   static uint32_t  lq_num;
264   static double   nlq_sum;
265   static uint32_t nlq_num;
266
267   double lq;
268   double nlq;
269
270   char *endptr;
271
272   if (config_want_links == OLSRD_WANT_NOT)
273     return (0);
274
275   /* Special handling of the first line. */
276   if (lineno <= 0)
277   {
278     links_num = 0;
279     lq_sum = 0.0;
280     lq_num = 0;
281     nlq_sum = 0.0;
282     nlq_num = 0;
283
284     return (0);
285   }
286
287   /* Special handling of the last line. */
288   if (fields_num == 0)
289   {
290     DEBUG ("olsrd plugin: Number of links: %"PRIu32, links_num);
291     olsrd_submit (/* p.-inst = */ "links", /* type = */ "links",
292         /* t.-inst = */ NULL, (gauge_t) links_num);
293
294     lq = NAN;
295     if (lq_num > 0)
296       lq = lq_sum / ((double) lq_num);
297     DEBUG ("olsrd plugin: Average  LQ: %g", lq);
298     olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
299         "average-lq", lq);
300
301     nlq = NAN;
302     if (nlq_num > 0)
303       nlq = nlq_sum / ((double) nlq_num);
304     DEBUG ("olsrd plugin: Average NLQ: %g", nlq);
305     olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
306         "average-nlq", nlq);
307
308     return (0);
309   }
310
311   if (fields_num != 6)
312     return (-1);
313
314   links_num++;
315
316   errno = 0;
317   endptr = NULL;
318   lq = strtod (fields[3], &endptr);
319   if ((errno != 0) || (endptr == fields[3]))
320   {
321     ERROR ("olsrd plugin: Cannot parse link quality: %s", fields[3]);
322   }
323   else
324   {
325     if (!isnan (lq))
326     {
327       lq_sum += lq;
328       lq_num++;
329     }
330
331     if (config_want_links == OLSRD_WANT_DETAIL)
332     {
333       char type_instance[DATA_MAX_NAME_LEN];
334
335       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-lq",
336           fields[0], fields[1]);
337
338       DEBUG ("olsrd plugin: links: type_instance = %s;  lq = %g;",
339           type_instance, lq);
340       olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
341           type_instance, lq);
342     }
343   }
344
345   errno = 0;
346   endptr = NULL;
347   nlq = strtod (fields[4], &endptr);
348   if ((errno != 0) || (endptr == fields[4]))
349   {
350     ERROR ("olsrd plugin: Cannot parse neighbor link quality: %s", fields[4]);
351   }
352   else
353   {
354     if (!isnan (nlq))
355     {
356       nlq_sum += nlq;
357       nlq_num++;
358     }
359
360     if (config_want_links == OLSRD_WANT_DETAIL)
361     {
362       char type_instance[DATA_MAX_NAME_LEN];
363
364       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-rx",
365           fields[0], fields[1]);
366
367       DEBUG ("olsrd plugin: links: type_instance = %s; nlq = %g;",
368           type_instance, lq);
369       olsrd_submit (/* p.-inst = */ "links", /* type = */ "signal_quality",
370           type_instance, nlq);
371     }
372   }
373
374   return (0);
375 } /* }}} int olsrd_cb_links */
376
377 static int olsrd_cb_routes (int lineno, /* {{{ */
378     size_t fields_num, char **fields)
379 {
380   /* Fields:
381    *  0 = Destination
382    *  1 = Gateway IP
383    *  2 = Metric
384    *  3 = ETX
385    *  4 = Interface */
386
387   static uint32_t routes_num;
388   static uint32_t metric_sum;
389   static uint32_t metric_num;
390   static double   etx_sum;
391   static uint32_t etx_num;
392
393   uint32_t metric;
394   double etx;
395   char *endptr;
396
397   if (config_want_routes == OLSRD_WANT_NOT)
398     return (0);
399
400   /* Special handling of the first line */
401   if (lineno <= 0)
402   {
403     routes_num = 0;
404     metric_num = 0;
405     metric_sum = 0;
406     etx_sum = 0.0;
407     etx_num = 0;
408
409     return (0);
410   }
411
412   /* Special handling after the last line */
413   if (fields_num == 0)
414   {
415     double metric_avg;
416
417     DEBUG ("olsrd plugin: Number of routes: %"PRIu32, routes_num);
418     olsrd_submit (/* p.-inst = */ "routes", /* type = */ "routes",
419         /* t.-inst = */ NULL, (gauge_t) routes_num);
420
421     metric_avg = NAN;
422     if (metric_num > 0)
423       metric_avg = ((double) metric_sum) / ((double) metric_num);
424     DEBUG ("olsrd plugin: Average metric: %g", metric_avg);
425     olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_metric",
426         "average", metric_avg);
427
428     etx = NAN;
429     if (etx_num > 0)
430       etx = etx_sum / ((double) etx_sum);
431     DEBUG ("olsrd plugin: Average ETX: %g", etx);
432     olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_etx",
433         "average", etx);
434
435     return (0);
436   }
437
438   if (fields_num != 5)
439     return (-1);
440
441   routes_num++;
442
443   errno = 0;
444   endptr = NULL;
445   metric = (uint32_t) strtoul (fields[2], &endptr, 0);
446   if ((errno != 0) || (endptr == fields[2]))
447   {
448     ERROR ("olsrd plugin: Unable to parse metric: %s", fields[2]);
449   }
450   else
451   {
452     metric_num++;
453     metric_sum += metric;
454
455     if (config_want_routes == OLSRD_WANT_DETAIL)
456     {
457       DEBUG ("olsrd plugin: destination = %s; metric = %"PRIu32";",
458           fields[0], metric);
459       olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_metric",
460           /* t.-inst = */ fields[0], (gauge_t) metric);
461     }
462   }
463
464   errno = 0;
465   endptr = NULL;
466   etx = strtod (fields[3], &endptr);
467   if ((errno != 0) || (endptr == fields[3]))
468   {
469     ERROR ("olsrd plugin: Unable to parse ETX: %s", fields[3]);
470   }
471   else
472   {
473     if (!isnan (etx))
474     {
475       etx_sum += etx;
476       etx_num++;
477     }
478
479     if (config_want_routes == OLSRD_WANT_DETAIL)
480     {
481       DEBUG ("olsrd plugin: destination = %s; etx = %g;",
482           fields[0], etx);
483       olsrd_submit (/* p.-inst = */ "routes", /* type = */ "route_etx",
484           /* t.-inst = */ fields[0], etx);
485     }
486   }
487
488   return (0);
489 } /* }}} int olsrd_cb_routes */
490
491 static int olsrd_cb_topology (int lineno, /* {{{ */
492     size_t fields_num, char **fields)
493 {
494   /* Fields:
495    *  0 = Dest. IP
496    *  1 = Last hop IP
497    *  2 = LQ
498    *  3 = NLQ
499    *  4 = Cost */
500
501   static double   lq_sum;
502   static uint32_t lq_num;
503
504   static uint32_t links_num;
505
506   double lq;
507   char *endptr;
508
509   if (config_want_topology == OLSRD_WANT_NOT)
510     return (0);
511
512   /* Special handling of the first line */
513   if (lineno <= 0)
514   {
515     lq_sum = 0.0;
516     lq_num = 0;
517     links_num = 0;
518
519     return (0);
520   }
521
522   /* Special handling after the last line */
523   if (fields_num == 0)
524   {
525     DEBUG ("olsrd plugin: topology: Number of links: %"PRIu32, links_num);
526     olsrd_submit (/* p.-inst = */ "topology", /* type = */ "links",
527         /* t.-inst = */ NULL, (gauge_t) links_num);
528
529     lq = NAN;
530     if (lq_num > 0)
531       lq = lq_sum / ((double) lq_sum);
532     DEBUG ("olsrd plugin: topology: Average link quality: %g", lq);
533     olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
534         /* t.-inst = */ "average", lq);
535
536     return (0);
537   }
538
539   if (fields_num != 5)
540     return (-1);
541
542   links_num++;
543
544   errno = 0;
545   endptr = NULL;
546   lq = strtod (fields[2], &endptr);
547   if ((errno != 0) || (endptr == fields[2]))
548   {
549     ERROR ("olsrd plugin: Unable to parse LQ: %s", fields[2]);
550   }
551   else
552   {
553     if (!isnan (lq))
554     {
555       lq_sum += lq;
556       lq_num++;
557     }
558
559     if (config_want_topology == OLSRD_WANT_DETAIL)
560     {
561       char type_instance[DATA_MAX_NAME_LEN];
562
563       memset (type_instance, 0, sizeof (type_instance));
564       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-lq",
565           fields[0], fields[1]);
566       DEBUG ("olsrd plugin: type_instance = %s; lq = %g;", type_instance, lq);
567       olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
568           type_instance, lq);
569     }
570   }
571
572   if (config_want_topology == OLSRD_WANT_DETAIL)
573   {
574     double nlq;
575
576     errno = 0;
577     endptr = NULL;
578     nlq = strtod (fields[3], &endptr);
579     if ((errno != 0) || (endptr == fields[3]))
580     {
581       ERROR ("olsrd plugin: Unable to parse NLQ: %s", fields[3]);
582     }
583     else
584     {
585       char type_instance[DATA_MAX_NAME_LEN];
586
587       memset (type_instance, 0, sizeof (type_instance));
588       ssnprintf (type_instance, sizeof (type_instance), "%s-%s-nlq",
589           fields[0], fields[1]);
590       DEBUG ("olsrd plugin: type_instance = %s; nlq = %g;", type_instance, nlq);
591       olsrd_submit (/* p.-inst = */ "topology", /* type = */ "signal_quality",
592           type_instance, nlq);
593     }
594   }
595
596   return (0);
597 } /* }}} int olsrd_cb_topology */
598
599 static int olsrd_read_table (FILE *fh, /* {{{ */
600     int (*callback) (int lineno, size_t fields_num, char **fields))
601 {
602   char buffer[1024];
603   size_t buffer_len;
604
605   char *fields[32];
606   size_t fields_num;
607
608   int lineno;
609
610   lineno = 0;
611   while (fgets (buffer, sizeof (buffer), fh) != NULL)
612   {
613     /* An empty line ends the table. */
614     buffer_len = strchomp (buffer);
615     if (buffer_len <= 0)
616     {
617       (*callback) (lineno, /* fields_num = */ 0, /* fields = */ NULL);
618       break;
619     }
620
621     fields_num = strtabsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
622
623     (*callback) (lineno, fields_num, fields);
624     lineno++;
625   } /* while (fgets) */
626   
627   return (0);
628 } /* }}} int olsrd_read_table */
629
630 static int olsrd_config (const char *key, const char *value) /* {{{ */
631 {
632   if (strcasecmp ("Host", key) == 0)
633     olsrd_set_node (value);
634   else if (strcasecmp ("Port", key) == 0)
635     olsrd_set_service (value);
636   else if (strcasecmp ("CollectLinks", key) == 0)
637     olsrd_set_detail (&config_want_links, value, key);
638   else if (strcasecmp ("CollectRoutes", key) == 0)
639     olsrd_set_detail (&config_want_routes, value, key);
640   else if (strcasecmp ("CollectTopology", key) == 0)
641     olsrd_set_detail (&config_want_topology, value, key);
642   else
643   {
644     ERROR ("olsrd plugin: Unknown configuration option given: %s", key);
645     return (-1);
646   }
647
648   return (0);
649 } /* }}} int olsrd_config */
650
651 static int olsrd_read (void) /* {{{ */
652 {
653   FILE *fh;
654   char buffer[1024];
655   size_t buffer_len;
656
657   fh = olsrd_connect ();
658   if (fh == NULL)
659     return (-1);
660
661   fputs ("\r\n", fh);
662   fflush (fh);
663
664   while (fgets (buffer, sizeof (buffer), fh) != NULL)
665   {
666     buffer_len = strchomp (buffer);
667     if (buffer_len <= 0)
668       continue;
669     
670     if (strcmp ("Table: Links", buffer) == 0)
671       olsrd_read_table (fh, olsrd_cb_links);
672     else if (strcmp ("Table: Neighbors", buffer) == 0)
673       olsrd_read_table (fh, olsrd_cb_ignore);
674     else if (strcmp ("Table: Topology", buffer) == 0)
675       olsrd_read_table (fh, olsrd_cb_topology);
676     else if (strcmp ("Table: HNA", buffer) == 0)
677       olsrd_read_table (fh, olsrd_cb_ignore);
678     else if (strcmp ("Table: MID", buffer) == 0)
679       olsrd_read_table (fh, olsrd_cb_ignore);
680     else if (strcmp ("Table: Routes", buffer) == 0)
681       olsrd_read_table (fh, olsrd_cb_routes);
682     else if ((strcmp ("HTTP/1.0 200 OK", buffer) == 0)
683         || (strcmp ("Content-type: text/plain", buffer) == 0))
684     {
685       /* ignore */
686     }
687     else
688     {
689       DEBUG ("olsrd plugin: Unable to handle line: %s", buffer);
690     }
691   } /* while (fgets) */
692
693   fclose (fh);
694   
695   return (0);
696 } /* }}} int olsrd_read */
697
698 static int olsrd_shutdown (void) /* {{{ */
699 {
700   sfree (config_node);
701   sfree (config_service);
702
703   return (0);
704 } /* }}} int olsrd_shutdown */
705
706 void module_register (void)
707 {
708   plugin_register_config ("olsrd", olsrd_config,
709       config_keys, config_keys_num);
710   plugin_register_read ("olsrd", olsrd_read);
711   plugin_register_shutdown ("olsrd", olsrd_shutdown);
712 } /* void module_register */
713
714 /* vim: set sw=2 sts=2 et fdm=marker : */