Tree wide: Reformat with clang-format.
[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 "common.h"
30 #include "plugin.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 = NULL;
45 static char *config_service = NULL;
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     char errbuf[1024];
166
167     fd = socket(ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
168     if (fd < 0) {
169       ERROR("olsrd plugin: socket failed: %s",
170             sstrerror(errno, errbuf, sizeof(errbuf)));
171       continue;
172     }
173
174     status = connect(fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
175     if (status != 0) {
176       ERROR("olsrd plugin: connect failed: %s",
177             sstrerror(errno, errbuf, sizeof(errbuf)));
178       close(fd);
179       continue;
180     }
181
182     fh = fdopen(fd, "r+");
183     if (fh == NULL) {
184       ERROR("olsrd plugin: fdopen failed.");
185       close(fd);
186       continue;
187     }
188
189     break;
190   } /* for (ai_ptr) */
191
192   freeaddrinfo(ai_list);
193
194   return (fh);
195 } /* }}} FILE *olsrd_connect */
196
197 __attribute__((nonnull(2))) static void
198 olsrd_submit(const char *plugin_instance, /* {{{ */
199              const char *type, const char *type_instance, gauge_t value) {
200   value_t values[1];
201   value_list_t vl = VALUE_LIST_INIT;
202
203   values[0].gauge = value;
204
205   vl.values = values;
206   vl.values_len = 1;
207
208   sstrncpy(vl.host, hostname_g, sizeof(vl.host));
209   sstrncpy(vl.plugin, "olsrd", sizeof(vl.plugin));
210   if (plugin_instance != NULL)
211     sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
212   sstrncpy(vl.type, type, sizeof(vl.type));
213   if (type_instance != NULL)
214     sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
215
216   plugin_dispatch_values(&vl);
217 } /* }}} void olsrd_submit */
218
219 static int olsrd_cb_ignore(int lineno, /* {{{ */
220                            size_t fields_num, char **fields) {
221   return (0);
222 } /* }}} int olsrd_cb_ignore */
223
224 static int olsrd_cb_links(int lineno, /* {{{ */
225                           size_t fields_num, char **fields) {
226   /* Fields:
227    *  0 = Local IP
228    *  1 = Remote IP
229    *  2 = Hyst.
230    *  3 = LQ
231    *  4 = NLQ
232    *  5 = Cost */
233
234   static uint32_t links_num;
235   static double lq_sum;
236   static uint32_t lq_num;
237   static double nlq_sum;
238   static uint32_t nlq_num;
239
240   double lq;
241   double nlq;
242
243   char *endptr;
244
245   if (config_want_links == OLSRD_WANT_NOT)
246     return (0);
247
248   /* Special handling of the first line. */
249   if (lineno <= 0) {
250     links_num = 0;
251     lq_sum = 0.0;
252     lq_num = 0;
253     nlq_sum = 0.0;
254     nlq_num = 0;
255
256     return (0);
257   }
258
259   /* Special handling of the last line. */
260   if (fields_num == 0) {
261     DEBUG("olsrd plugin: Number of links: %" PRIu32, links_num);
262     olsrd_submit(/* p.-inst = */ "links", /* type = */ "links",
263                  /* t.-inst = */ NULL, (gauge_t)links_num);
264
265     lq = NAN;
266     if (lq_num > 0)
267       lq = lq_sum / ((double)lq_num);
268     DEBUG("olsrd plugin: Average  LQ: %g", lq);
269     olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
270                  "average-lq", lq);
271
272     nlq = NAN;
273     if (nlq_num > 0)
274       nlq = nlq_sum / ((double)nlq_num);
275     DEBUG("olsrd plugin: Average NLQ: %g", nlq);
276     olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
277                  "average-nlq", nlq);
278
279     return (0);
280   }
281
282   if (fields_num != 6)
283     return (-1);
284
285   links_num++;
286
287   errno = 0;
288   endptr = NULL;
289   lq = strtod(fields[3], &endptr);
290   if ((errno != 0) || (endptr == fields[3])) {
291     ERROR("olsrd plugin: Cannot parse link quality: %s", fields[3]);
292   } else {
293     if (!isnan(lq)) {
294       lq_sum += lq;
295       lq_num++;
296     }
297
298     if (config_want_links == OLSRD_WANT_DETAIL) {
299       char type_instance[DATA_MAX_NAME_LEN];
300
301       ssnprintf(type_instance, sizeof(type_instance), "%s-%s-lq", fields[0],
302                 fields[1]);
303
304       DEBUG("olsrd plugin: links: type_instance = %s;  lq = %g;", type_instance,
305             lq);
306       olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
307                    type_instance, lq);
308     }
309   }
310
311   errno = 0;
312   endptr = NULL;
313   nlq = strtod(fields[4], &endptr);
314   if ((errno != 0) || (endptr == fields[4])) {
315     ERROR("olsrd plugin: Cannot parse neighbor link quality: %s", fields[4]);
316   } else {
317     if (!isnan(nlq)) {
318       nlq_sum += nlq;
319       nlq_num++;
320     }
321
322     if (config_want_links == OLSRD_WANT_DETAIL) {
323       char type_instance[DATA_MAX_NAME_LEN];
324
325       ssnprintf(type_instance, sizeof(type_instance), "%s-%s-rx", fields[0],
326                 fields[1]);
327
328       DEBUG("olsrd plugin: links: type_instance = %s; nlq = %g;", type_instance,
329             lq);
330       olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
331                    type_instance, nlq);
332     }
333   }
334
335   return (0);
336 } /* }}} int olsrd_cb_links */
337
338 static int olsrd_cb_routes(int lineno, /* {{{ */
339                            size_t fields_num, char **fields) {
340   /* Fields:
341    *  0 = Destination
342    *  1 = Gateway IP
343    *  2 = Metric
344    *  3 = ETX
345    *  4 = Interface */
346
347   static uint32_t routes_num;
348   static uint32_t metric_sum;
349   static uint32_t metric_num;
350   static double etx_sum;
351   static uint32_t etx_num;
352
353   uint32_t metric;
354   double etx;
355   char *endptr;
356
357   if (config_want_routes == OLSRD_WANT_NOT)
358     return (0);
359
360   /* Special handling of the first line */
361   if (lineno <= 0) {
362     routes_num = 0;
363     metric_num = 0;
364     metric_sum = 0;
365     etx_sum = 0.0;
366     etx_num = 0;
367
368     return (0);
369   }
370
371   /* Special handling after the last line */
372   if (fields_num == 0) {
373     double metric_avg;
374
375     DEBUG("olsrd plugin: Number of routes: %" PRIu32, routes_num);
376     olsrd_submit(/* p.-inst = */ "routes", /* type = */ "routes",
377                  /* t.-inst = */ NULL, (gauge_t)routes_num);
378
379     metric_avg = NAN;
380     if (metric_num > 0)
381       metric_avg = ((double)metric_sum) / ((double)metric_num);
382     DEBUG("olsrd plugin: Average metric: %g", metric_avg);
383     olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_metric",
384                  "average", metric_avg);
385
386     etx = NAN;
387     if (etx_num > 0)
388       etx = etx_sum / ((double)etx_sum);
389     DEBUG("olsrd plugin: Average ETX: %g", etx);
390     olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_etx", "average",
391                  etx);
392
393     return (0);
394   }
395
396   if (fields_num != 5)
397     return (-1);
398
399   routes_num++;
400
401   errno = 0;
402   endptr = NULL;
403   metric = (uint32_t)strtoul(fields[2], &endptr, 0);
404   if ((errno != 0) || (endptr == fields[2])) {
405     ERROR("olsrd plugin: Unable to parse metric: %s", fields[2]);
406   } else {
407     metric_num++;
408     metric_sum += metric;
409
410     if (config_want_routes == OLSRD_WANT_DETAIL) {
411       DEBUG("olsrd plugin: destination = %s; metric = %" PRIu32 ";", fields[0],
412             metric);
413       olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_metric",
414                    /* t.-inst = */ fields[0], (gauge_t)metric);
415     }
416   }
417
418   errno = 0;
419   endptr = NULL;
420   etx = strtod(fields[3], &endptr);
421   if ((errno != 0) || (endptr == fields[3])) {
422     ERROR("olsrd plugin: Unable to parse ETX: %s", fields[3]);
423   } else {
424     if (!isnan(etx)) {
425       etx_sum += etx;
426       etx_num++;
427     }
428
429     if (config_want_routes == OLSRD_WANT_DETAIL) {
430       DEBUG("olsrd plugin: destination = %s; etx = %g;", fields[0], etx);
431       olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_etx",
432                    /* t.-inst = */ fields[0], etx);
433     }
434   }
435
436   return (0);
437 } /* }}} int olsrd_cb_routes */
438
439 static int olsrd_cb_topology(int lineno, /* {{{ */
440                              size_t fields_num, char **fields) {
441   /* Fields:
442    *  0 = Dest. IP
443    *  1 = Last hop IP
444    *  2 = LQ
445    *  3 = NLQ
446    *  4 = Cost */
447
448   static double lq_sum;
449   static uint32_t lq_num;
450
451   static uint32_t links_num;
452
453   double lq;
454   char *endptr;
455
456   if (config_want_topology == OLSRD_WANT_NOT)
457     return (0);
458
459   /* Special handling of the first line */
460   if (lineno <= 0) {
461     lq_sum = 0.0;
462     lq_num = 0;
463     links_num = 0;
464
465     return (0);
466   }
467
468   /* Special handling after the last line */
469   if (fields_num == 0) {
470     DEBUG("olsrd plugin: topology: Number of links: %" PRIu32, links_num);
471     olsrd_submit(/* p.-inst = */ "topology", /* type = */ "links",
472                  /* t.-inst = */ NULL, (gauge_t)links_num);
473
474     lq = NAN;
475     if (lq_num > 0)
476       lq = lq_sum / ((double)lq_sum);
477     DEBUG("olsrd plugin: topology: Average link quality: %g", lq);
478     olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
479                  /* t.-inst = */ "average", lq);
480
481     return (0);
482   }
483
484   if (fields_num != 5)
485     return (-1);
486
487   links_num++;
488
489   errno = 0;
490   endptr = NULL;
491   lq = strtod(fields[2], &endptr);
492   if ((errno != 0) || (endptr == fields[2])) {
493     ERROR("olsrd plugin: Unable to parse LQ: %s", fields[2]);
494   } else {
495     if (!isnan(lq)) {
496       lq_sum += lq;
497       lq_num++;
498     }
499
500     if (config_want_topology == OLSRD_WANT_DETAIL) {
501       char type_instance[DATA_MAX_NAME_LEN] = {0};
502
503       ssnprintf(type_instance, sizeof(type_instance), "%s-%s-lq", fields[0],
504                 fields[1]);
505       DEBUG("olsrd plugin: type_instance = %s; lq = %g;", type_instance, lq);
506       olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
507                    type_instance, lq);
508     }
509   }
510
511   if (config_want_topology == OLSRD_WANT_DETAIL) {
512     double nlq;
513
514     errno = 0;
515     endptr = NULL;
516     nlq = strtod(fields[3], &endptr);
517     if ((errno != 0) || (endptr == fields[3])) {
518       ERROR("olsrd plugin: Unable to parse NLQ: %s", fields[3]);
519     } else {
520       char type_instance[DATA_MAX_NAME_LEN] = {0};
521
522       ssnprintf(type_instance, sizeof(type_instance), "%s-%s-nlq", fields[0],
523                 fields[1]);
524       DEBUG("olsrd plugin: type_instance = %s; nlq = %g;", type_instance, nlq);
525       olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
526                    type_instance, nlq);
527     }
528   }
529
530   return (0);
531 } /* }}} int olsrd_cb_topology */
532
533 static int olsrd_read_table(FILE *fh, /* {{{ */
534                             int (*callback)(int lineno, size_t fields_num,
535                                             char **fields)) {
536   char buffer[1024];
537   size_t buffer_len;
538
539   char *fields[32];
540   size_t fields_num;
541
542   int lineno;
543
544   lineno = 0;
545   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
546     /* An empty line ends the table. */
547     buffer_len = strchomp(buffer);
548     if (buffer_len == 0) {
549       (*callback)(lineno, /* fields_num = */ 0, /* fields = */ NULL);
550       break;
551     }
552
553     fields_num = strtabsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
554
555     (*callback)(lineno, fields_num, fields);
556     lineno++;
557   } /* while (fgets) */
558
559   return (0);
560 } /* }}} int olsrd_read_table */
561
562 static int olsrd_config(const char *key, const char *value) /* {{{ */
563 {
564   if (strcasecmp("Host", key) == 0)
565     olsrd_set_node(value);
566   else if (strcasecmp("Port", key) == 0)
567     olsrd_set_service(value);
568   else if (strcasecmp("CollectLinks", key) == 0)
569     olsrd_set_detail(&config_want_links, value, key);
570   else if (strcasecmp("CollectRoutes", key) == 0)
571     olsrd_set_detail(&config_want_routes, value, key);
572   else if (strcasecmp("CollectTopology", key) == 0)
573     olsrd_set_detail(&config_want_topology, value, key);
574   else {
575     ERROR("olsrd plugin: Unknown configuration option given: %s", key);
576     return (-1);
577   }
578
579   return (0);
580 } /* }}} int olsrd_config */
581
582 static int olsrd_read(void) /* {{{ */
583 {
584   FILE *fh;
585   char buffer[1024];
586   size_t buffer_len;
587
588   fh = olsrd_connect();
589   if (fh == NULL)
590     return (-1);
591
592   fputs("\r\n", fh);
593   fflush(fh);
594
595   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
596     buffer_len = strchomp(buffer);
597     if (buffer_len == 0)
598       continue;
599
600     if (strcmp("Table: Links", buffer) == 0)
601       olsrd_read_table(fh, olsrd_cb_links);
602     else if (strcmp("Table: Neighbors", buffer) == 0)
603       olsrd_read_table(fh, olsrd_cb_ignore);
604     else if (strcmp("Table: Topology", buffer) == 0)
605       olsrd_read_table(fh, olsrd_cb_topology);
606     else if (strcmp("Table: HNA", buffer) == 0)
607       olsrd_read_table(fh, olsrd_cb_ignore);
608     else if (strcmp("Table: MID", buffer) == 0)
609       olsrd_read_table(fh, olsrd_cb_ignore);
610     else if (strcmp("Table: Routes", buffer) == 0)
611       olsrd_read_table(fh, olsrd_cb_routes);
612     else if ((strcmp("HTTP/1.0 200 OK", buffer) == 0) ||
613              (strcmp("Content-type: text/plain", buffer) == 0)) {
614       /* ignore */
615     } else {
616       DEBUG("olsrd plugin: Unable to handle line: %s", buffer);
617     }
618   } /* while (fgets) */
619
620   fclose(fh);
621
622   return (0);
623 } /* }}} int olsrd_read */
624
625 static int olsrd_shutdown(void) /* {{{ */
626 {
627   sfree(config_node);
628   sfree(config_service);
629
630   return (0);
631 } /* }}} int olsrd_shutdown */
632
633 void module_register(void) {
634   plugin_register_config("olsrd", olsrd_config, config_keys, config_keys_num);
635   plugin_register_read("olsrd", olsrd_read);
636   plugin_register_shutdown("olsrd", olsrd_shutdown);
637 } /* void module_register */
638
639 /* vim: set sw=2 sts=2 et fdm=marker : */