Merge pull request #3329 from efuss/fix-3311
[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
29 #include "plugin.h"
30 #include "utils/common/common.h"
31
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <netinet/tcp.h>
35 #include <sys/types.h>
36
37 #define OLSRD_DEFAULT_NODE "localhost"
38 #define OLSRD_DEFAULT_SERVICE "2006"
39
40 static const char *config_keys[] = {"Host", "Port", "CollectLinks",
41                                     "CollectRoutes", "CollectTopology"};
42 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
43
44 static char *config_node;
45 static char *config_service;
46
47 #define OLSRD_WANT_NOT 0
48 #define OLSRD_WANT_SUMMARY 1
49 #define OLSRD_WANT_DETAIL 2
50 static int config_want_links = OLSRD_WANT_DETAIL;
51 static int config_want_routes = OLSRD_WANT_SUMMARY;
52 static int config_want_topology = OLSRD_WANT_SUMMARY;
53
54 static const char *olsrd_get_node(void) /* {{{ */
55 {
56   if (config_node != NULL)
57     return config_node;
58   return OLSRD_DEFAULT_NODE;
59 } /* }}} const char *olsrd_get_node */
60
61 static const char *olsrd_get_service(void) /* {{{ */
62 {
63   if (config_service != NULL)
64     return config_service;
65   return OLSRD_DEFAULT_SERVICE;
66 } /* }}} const char *olsrd_get_service */
67
68 static void olsrd_set_node(const char *node) /* {{{ */
69 {
70   char *tmp;
71   if (node == NULL)
72     return;
73   tmp = strdup(node);
74   if (tmp == NULL)
75     return;
76   config_node = tmp;
77 } /* }}} void olsrd_set_node */
78
79 static void olsrd_set_service(const char *service) /* {{{ */
80 {
81   char *tmp;
82   if (service == NULL)
83     return;
84   tmp = strdup(service);
85   if (tmp == NULL)
86     return;
87   config_service = tmp;
88 } /* }}} void olsrd_set_service */
89
90 static void olsrd_set_detail(int *varptr, const char *detail, /* {{{ */
91                              const char *key) {
92   if (strcasecmp("No", detail) == 0)
93     *varptr = OLSRD_WANT_NOT;
94   else if (strcasecmp("Summary", detail) == 0)
95     *varptr = OLSRD_WANT_SUMMARY;
96   else if (strcasecmp("Detail", detail) == 0)
97     *varptr = OLSRD_WANT_DETAIL;
98   else {
99     ERROR("olsrd plugin: Invalid argument given to the `%s' configuration "
100           "option: `%s'. Expected: `No', `Summary', or `Detail'.",
101           key, detail);
102   }
103 } /* }}} void olsrd_set_detail */
104
105 /* Strip trailing newline characters. Returns length of string. */
106 static size_t strchomp(char *buffer) /* {{{ */
107 {
108   size_t buffer_len;
109
110   buffer_len = strlen(buffer);
111   while ((buffer_len > 0) && ((buffer[buffer_len - 1] == '\r') ||
112                               (buffer[buffer_len - 1] == '\n'))) {
113     buffer_len--;
114     buffer[buffer_len] = 0;
115   }
116
117   return buffer_len;
118 } /* }}} size_t strchomp */
119
120 static size_t strtabsplit(char *string, char **fields, size_t size) /* {{{ */
121 {
122   size_t i;
123   char *ptr;
124   char *saveptr;
125
126   i = 0;
127   ptr = string;
128   saveptr = NULL;
129   while ((fields[i] = strtok_r(ptr, " \t\r\n", &saveptr)) != NULL) {
130     ptr = NULL;
131     i++;
132
133     if (i >= size)
134       break;
135   }
136
137   return i;
138 } /* }}} size_t strtabsplit */
139
140 static FILE *olsrd_connect(void) /* {{{ */
141 {
142   struct addrinfo *ai_list;
143   int ai_return;
144
145   FILE *fh;
146
147   struct addrinfo ai_hints = {.ai_family = AF_UNSPEC,
148                               .ai_flags = AI_ADDRCONFIG,
149                               .ai_protocol = IPPROTO_TCP,
150                               .ai_socktype = SOCK_STREAM};
151
152   ai_return =
153       getaddrinfo(olsrd_get_node(), olsrd_get_service(), &ai_hints, &ai_list);
154   if (ai_return != 0) {
155     ERROR("olsrd plugin: getaddrinfo (%s, %s) failed: %s", olsrd_get_node(),
156           olsrd_get_service(), gai_strerror(ai_return));
157     return NULL;
158   }
159
160   fh = NULL;
161   for (struct addrinfo *ai_ptr = ai_list; ai_ptr != NULL;
162        ai_ptr = ai_ptr->ai_next) {
163     int fd;
164     int status;
165
166     fd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
167     if (fd < 0) {
168       ERROR("olsrd plugin: socket failed: %s", STRERRNO);
169       continue;
170     }
171
172     status = connect(fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
173     if (status != 0) {
174       ERROR("olsrd plugin: connect failed: %s", STRERRNO);
175       close(fd);
176       continue;
177     }
178
179     fh = fdopen(fd, "r+");
180     if (fh == NULL) {
181       ERROR("olsrd plugin: fdopen failed.");
182       close(fd);
183       continue;
184     }
185
186     break;
187   } /* for (ai_ptr) */
188
189   freeaddrinfo(ai_list);
190
191   return fh;
192 } /* }}} FILE *olsrd_connect */
193
194 __attribute__((nonnull(2))) static void
195 olsrd_submit(const char *plugin_instance, /* {{{ */
196              const char *type, const char *type_instance, gauge_t value) {
197   value_list_t vl = VALUE_LIST_INIT;
198
199   vl.values = &(value_t){.gauge = value};
200   vl.values_len = 1;
201
202   sstrncpy(vl.plugin, "olsrd", sizeof(vl.plugin));
203   if (plugin_instance != NULL)
204     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
205   sstrncpy(vl.type, type, sizeof(vl.type));
206   if (type_instance != NULL)
207     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
208
209   plugin_dispatch_values(&vl);
210 } /* }}} void olsrd_submit */
211
212 static int olsrd_cb_ignore(int lineno, /* {{{ */
213                            size_t fields_num, char **fields) {
214   return 0;
215 } /* }}} int olsrd_cb_ignore */
216
217 static int olsrd_cb_links(int lineno, /* {{{ */
218                           size_t fields_num, char **fields) {
219   /* Fields:
220    *  0 = Local IP
221    *  1 = Remote IP
222    *  2 = Hyst.
223    *  3 = LQ
224    *  4 = NLQ
225    *  5 = Cost */
226
227   static uint32_t links_num;
228   static double lq_sum;
229   static uint32_t lq_num;
230   static double nlq_sum;
231   static uint32_t nlq_num;
232
233   double lq;
234   double nlq;
235
236   char *endptr;
237
238   if (config_want_links == OLSRD_WANT_NOT)
239     return 0;
240
241   /* Special handling of the first line. */
242   if (lineno <= 0) {
243     links_num = 0;
244     lq_sum = 0.0;
245     lq_num = 0;
246     nlq_sum = 0.0;
247     nlq_num = 0;
248
249     return 0;
250   }
251
252   /* Special handling of the last line. */
253   if (fields_num == 0) {
254     DEBUG("olsrd plugin: Number of links: %" PRIu32, links_num);
255     olsrd_submit(/* p.-inst = */ "links", /* type = */ "links",
256                  /* t.-inst = */ NULL, (gauge_t)links_num);
257
258     lq = NAN;
259     if (lq_num > 0)
260       lq = lq_sum / ((double)lq_num);
261     DEBUG("olsrd plugin: Average  LQ: %g", lq);
262     olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
263                  "average-lq", lq);
264
265     nlq = NAN;
266     if (nlq_num > 0)
267       nlq = nlq_sum / ((double)nlq_num);
268     DEBUG("olsrd plugin: Average NLQ: %g", nlq);
269     olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
270                  "average-nlq", nlq);
271
272     return 0;
273   }
274
275   if (fields_num != 6)
276     return -1;
277
278   links_num++;
279
280   errno = 0;
281   endptr = NULL;
282   lq = strtod(fields[3], &endptr);
283   if ((errno != 0) || (endptr == fields[3])) {
284     ERROR("olsrd plugin: Cannot parse link quality: %s", fields[3]);
285   } else {
286     if (!isnan(lq)) {
287       lq_sum += lq;
288       lq_num++;
289     }
290
291     if (config_want_links == OLSRD_WANT_DETAIL) {
292       char type_instance[DATA_MAX_NAME_LEN];
293
294       snprintf(type_instance, sizeof(type_instance), "%s-%s-lq", fields[0],
295                fields[1]);
296
297       DEBUG("olsrd plugin: links: type_instance = %s;  lq = %g;", type_instance,
298             lq);
299       olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
300                    type_instance, lq);
301     }
302   }
303
304   errno = 0;
305   endptr = NULL;
306   nlq = strtod(fields[4], &endptr);
307   if ((errno != 0) || (endptr == fields[4])) {
308     ERROR("olsrd plugin: Cannot parse neighbor link quality: %s", fields[4]);
309   } else {
310     if (!isnan(nlq)) {
311       nlq_sum += nlq;
312       nlq_num++;
313     }
314
315     if (config_want_links == OLSRD_WANT_DETAIL) {
316       char type_instance[DATA_MAX_NAME_LEN];
317
318       snprintf(type_instance, sizeof(type_instance), "%s-%s-rx", fields[0],
319                fields[1]);
320
321       DEBUG("olsrd plugin: links: type_instance = %s; nlq = %g;", type_instance,
322             lq);
323       olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
324                    type_instance, nlq);
325     }
326   }
327
328   return 0;
329 } /* }}} int olsrd_cb_links */
330
331 static int olsrd_cb_routes(int lineno, /* {{{ */
332                            size_t fields_num, char **fields) {
333   /* Fields:
334    *  0 = Destination
335    *  1 = Gateway IP
336    *  2 = Metric
337    *  3 = ETX
338    *  4 = Interface */
339
340   static uint32_t routes_num;
341   static uint32_t metric_sum;
342   static uint32_t metric_num;
343   static double etx_sum;
344   static uint32_t etx_num;
345
346   uint32_t metric;
347   double etx;
348   char *endptr;
349
350   if (config_want_routes == OLSRD_WANT_NOT)
351     return 0;
352
353   /* Special handling of the first line */
354   if (lineno <= 0) {
355     routes_num = 0;
356     metric_num = 0;
357     metric_sum = 0;
358     etx_sum = 0.0;
359     etx_num = 0;
360
361     return 0;
362   }
363
364   /* Special handling after the last line */
365   if (fields_num == 0) {
366     double metric_avg;
367
368     DEBUG("olsrd plugin: Number of routes: %" PRIu32, routes_num);
369     olsrd_submit(/* p.-inst = */ "routes", /* type = */ "routes",
370                  /* t.-inst = */ NULL, (gauge_t)routes_num);
371
372     metric_avg = NAN;
373     if (metric_num > 0)
374       metric_avg = ((double)metric_sum) / ((double)metric_num);
375     DEBUG("olsrd plugin: Average metric: %g", metric_avg);
376     olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_metric",
377                  "average", metric_avg);
378
379     etx = NAN;
380     if (etx_num > 0)
381       etx = etx_sum / ((double)etx_sum);
382     DEBUG("olsrd plugin: Average ETX: %g", etx);
383     olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_etx", "average",
384                  etx);
385
386     return 0;
387   }
388
389   if (fields_num != 5)
390     return -1;
391
392   routes_num++;
393
394   errno = 0;
395   endptr = NULL;
396   metric = (uint32_t)strtoul(fields[2], &endptr, 0);
397   if ((errno != 0) || (endptr == fields[2])) {
398     ERROR("olsrd plugin: Unable to parse metric: %s", fields[2]);
399   } else {
400     metric_num++;
401     metric_sum += metric;
402
403     if (config_want_routes == OLSRD_WANT_DETAIL) {
404       DEBUG("olsrd plugin: destination = %s; metric = %" PRIu32 ";", fields[0],
405             metric);
406       olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_metric",
407                    /* t.-inst = */ fields[0], (gauge_t)metric);
408     }
409   }
410
411   errno = 0;
412   endptr = NULL;
413   etx = strtod(fields[3], &endptr);
414   if ((errno != 0) || (endptr == fields[3])) {
415     ERROR("olsrd plugin: Unable to parse ETX: %s", fields[3]);
416   } else {
417     if (!isnan(etx)) {
418       etx_sum += etx;
419       etx_num++;
420     }
421
422     if (config_want_routes == OLSRD_WANT_DETAIL) {
423       DEBUG("olsrd plugin: destination = %s; etx = %g;", fields[0], etx);
424       olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_etx",
425                    /* t.-inst = */ fields[0], etx);
426     }
427   }
428
429   return 0;
430 } /* }}} int olsrd_cb_routes */
431
432 static int olsrd_cb_topology(int lineno, /* {{{ */
433                              size_t fields_num, char **fields) {
434   /* Fields:
435    *  0 = Dest. IP
436    *  1 = Last hop IP
437    *  2 = LQ
438    *  3 = NLQ
439    *  4 = Cost */
440
441   static double lq_sum;
442   static uint32_t lq_num;
443
444   static uint32_t links_num;
445
446   double lq;
447   char *endptr;
448
449   if (config_want_topology == OLSRD_WANT_NOT)
450     return 0;
451
452   /* Special handling of the first line */
453   if (lineno <= 0) {
454     lq_sum = 0.0;
455     lq_num = 0;
456     links_num = 0;
457
458     return 0;
459   }
460
461   /* Special handling after the last line */
462   if (fields_num == 0) {
463     DEBUG("olsrd plugin: topology: Number of links: %" PRIu32, links_num);
464     olsrd_submit(/* p.-inst = */ "topology", /* type = */ "links",
465                  /* t.-inst = */ NULL, (gauge_t)links_num);
466
467     lq = NAN;
468     if (lq_num > 0)
469       lq = lq_sum / ((double)lq_sum);
470     DEBUG("olsrd plugin: topology: Average link quality: %g", lq);
471     olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
472                  /* t.-inst = */ "average", lq);
473
474     return 0;
475   }
476
477   if (fields_num != 5)
478     return -1;
479
480   links_num++;
481
482   errno = 0;
483   endptr = NULL;
484   lq = strtod(fields[2], &endptr);
485   if ((errno != 0) || (endptr == fields[2])) {
486     ERROR("olsrd plugin: Unable to parse LQ: %s", fields[2]);
487   } else {
488     if (!isnan(lq)) {
489       lq_sum += lq;
490       lq_num++;
491     }
492
493     if (config_want_topology == OLSRD_WANT_DETAIL) {
494       char type_instance[DATA_MAX_NAME_LEN] = {0};
495
496       snprintf(type_instance, sizeof(type_instance), "%s-%s-lq", fields[0],
497                fields[1]);
498       DEBUG("olsrd plugin: type_instance = %s; lq = %g;", type_instance, lq);
499       olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
500                    type_instance, lq);
501     }
502   }
503
504   if (config_want_topology == OLSRD_WANT_DETAIL) {
505     double nlq;
506
507     errno = 0;
508     endptr = NULL;
509     nlq = strtod(fields[3], &endptr);
510     if ((errno != 0) || (endptr == fields[3])) {
511       ERROR("olsrd plugin: Unable to parse NLQ: %s", fields[3]);
512     } else {
513       char type_instance[DATA_MAX_NAME_LEN] = {0};
514
515       snprintf(type_instance, sizeof(type_instance), "%s-%s-nlq", fields[0],
516                fields[1]);
517       DEBUG("olsrd plugin: type_instance = %s; nlq = %g;", type_instance, nlq);
518       olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
519                    type_instance, nlq);
520     }
521   }
522
523   return 0;
524 } /* }}} int olsrd_cb_topology */
525
526 static int olsrd_read_table(FILE *fh, /* {{{ */
527                             int (*callback)(int lineno, size_t fields_num,
528                                             char **fields)) {
529   char buffer[1024];
530   size_t buffer_len;
531
532   char *fields[32];
533   size_t fields_num;
534
535   int lineno;
536
537   lineno = 0;
538   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
539     /* An empty line ends the table. */
540     buffer_len = strchomp(buffer);
541     if (buffer_len == 0) {
542       (*callback)(lineno, /* fields_num = */ 0, /* fields = */ NULL);
543       break;
544     }
545
546     fields_num = strtabsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
547
548     (*callback)(lineno, fields_num, fields);
549     lineno++;
550   } /* while (fgets) */
551
552   return 0;
553 } /* }}} int olsrd_read_table */
554
555 static int olsrd_config(const char *key, const char *value) /* {{{ */
556 {
557   if (strcasecmp("Host", key) == 0)
558     olsrd_set_node(value);
559   else if (strcasecmp("Port", key) == 0)
560     olsrd_set_service(value);
561   else if (strcasecmp("CollectLinks", key) == 0)
562     olsrd_set_detail(&config_want_links, value, key);
563   else if (strcasecmp("CollectRoutes", key) == 0)
564     olsrd_set_detail(&config_want_routes, value, key);
565   else if (strcasecmp("CollectTopology", key) == 0)
566     olsrd_set_detail(&config_want_topology, value, key);
567   else {
568     ERROR("olsrd plugin: Unknown configuration option given: %s", key);
569     return -1;
570   }
571
572   return 0;
573 } /* }}} int olsrd_config */
574
575 static int olsrd_read(void) /* {{{ */
576 {
577   FILE *fh;
578   char buffer[1024];
579   size_t buffer_len;
580
581   fh = olsrd_connect();
582   if (fh == NULL)
583     return -1;
584
585   fputs("\r\n", fh);
586   fflush(fh);
587
588   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
589     buffer_len = strchomp(buffer);
590     if (buffer_len == 0)
591       continue;
592
593     if (strcmp("Table: Links", buffer) == 0)
594       olsrd_read_table(fh, olsrd_cb_links);
595     else if (strcmp("Table: Neighbors", buffer) == 0)
596       olsrd_read_table(fh, olsrd_cb_ignore);
597     else if (strcmp("Table: Topology", buffer) == 0)
598       olsrd_read_table(fh, olsrd_cb_topology);
599     else if (strcmp("Table: HNA", buffer) == 0)
600       olsrd_read_table(fh, olsrd_cb_ignore);
601     else if (strcmp("Table: MID", buffer) == 0)
602       olsrd_read_table(fh, olsrd_cb_ignore);
603     else if (strcmp("Table: Routes", buffer) == 0)
604       olsrd_read_table(fh, olsrd_cb_routes);
605     else if ((strcmp("HTTP/1.0 200 OK", buffer) == 0) ||
606              (strcmp("Content-type: text/plain", buffer) == 0)) {
607       /* ignore */
608     } else {
609       DEBUG("olsrd plugin: Unable to handle line: %s", buffer);
610     }
611   } /* while (fgets) */
612
613   fclose(fh);
614
615   return 0;
616 } /* }}} int olsrd_read */
617
618 static int olsrd_shutdown(void) /* {{{ */
619 {
620   sfree(config_node);
621   sfree(config_service);
622
623   return 0;
624 } /* }}} int olsrd_shutdown */
625
626 void module_register(void) {
627   plugin_register_config("olsrd", olsrd_config, config_keys, config_keys_num);
628   plugin_register_read("olsrd", olsrd_read);
629   plugin_register_shutdown("olsrd", olsrd_shutdown);
630 } /* void module_register */