d1caf461d1deb1c88659adb9c2a8b3a2cd5472d1
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 **/
27 #include "collectd.h"
29 #include "common.h"
30 #include "plugin.h"
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <netinet/tcp.h>
35 #include <sys/types.h>
37 #define OLSRD_DEFAULT_NODE "localhost"
38 #define OLSRD_DEFAULT_SERVICE "2006"
40 static const char *config_keys[] = {"Host", "Port", "CollectLinks",
41 "CollectRoutes", "CollectTopology"};
42 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
44 static char *config_node = NULL;
45 static char *config_service = NULL;
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;
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 */
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 */
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 */
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 */
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 */
105 /* Strip trailing newline characters. Returns length of string. */
106 static size_t strchomp(char *buffer) /* {{{ */
107 {
108 size_t buffer_len;
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 }
117 return (buffer_len);
118 } /* }}} size_t strchomp */
120 static size_t strtabsplit(char *string, char **fields, size_t size) /* {{{ */
121 {
122 size_t i;
123 char *ptr;
124 char *saveptr;
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++;
133 if (i >= size)
134 break;
135 }
137 return (i);
138 } /* }}} size_t strtabsplit */
140 static FILE *olsrd_connect(void) /* {{{ */
141 {
142 struct addrinfo *ai_list;
143 int ai_return;
145 FILE *fh;
147 struct addrinfo ai_hints = {.ai_family = AF_UNSPEC,
148 .ai_flags = AI_ADDRCONFIG,
149 .ai_protocol = IPPROTO_TCP,
150 .ai_socktype = SOCK_STREAM};
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 }
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];
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 }
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 }
182 fh = fdopen(fd, "r+");
183 if (fh == NULL) {
184 ERROR("olsrd plugin: fdopen failed.");
185 close(fd);
186 continue;
187 }
189 break;
190 } /* for (ai_ptr) */
192 freeaddrinfo(ai_list);
194 return (fh);
195 } /* }}} FILE *olsrd_connect */
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_list_t vl = VALUE_LIST_INIT;
202 vl.values = &(value_t){.gauge = value};
203 vl.values_len = 1;
205 sstrncpy(vl.plugin, "olsrd", sizeof(vl.plugin));
206 if (plugin_instance != NULL)
207 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
208 sstrncpy(vl.type, type, sizeof(vl.type));
209 if (type_instance != NULL)
210 sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
212 plugin_dispatch_values(&vl);
213 } /* }}} void olsrd_submit */
215 static int olsrd_cb_ignore(int lineno, /* {{{ */
216 size_t fields_num, char **fields) {
217 return (0);
218 } /* }}} int olsrd_cb_ignore */
220 static int olsrd_cb_links(int lineno, /* {{{ */
221 size_t fields_num, char **fields) {
222 /* Fields:
223 * 0 = Local IP
224 * 1 = Remote IP
225 * 2 = Hyst.
226 * 3 = LQ
227 * 4 = NLQ
228 * 5 = Cost */
230 static uint32_t links_num;
231 static double lq_sum;
232 static uint32_t lq_num;
233 static double nlq_sum;
234 static uint32_t nlq_num;
236 double lq;
237 double nlq;
239 char *endptr;
241 if (config_want_links == OLSRD_WANT_NOT)
242 return (0);
244 /* Special handling of the first line. */
245 if (lineno <= 0) {
246 links_num = 0;
247 lq_sum = 0.0;
248 lq_num = 0;
249 nlq_sum = 0.0;
250 nlq_num = 0;
252 return (0);
253 }
255 /* Special handling of the last line. */
256 if (fields_num == 0) {
257 DEBUG("olsrd plugin: Number of links: %" PRIu32, links_num);
258 olsrd_submit(/* p.-inst = */ "links", /* type = */ "links",
259 /* t.-inst = */ NULL, (gauge_t)links_num);
261 lq = NAN;
262 if (lq_num > 0)
263 lq = lq_sum / ((double)lq_num);
264 DEBUG("olsrd plugin: Average LQ: %g", lq);
265 olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
266 "average-lq", lq);
268 nlq = NAN;
269 if (nlq_num > 0)
270 nlq = nlq_sum / ((double)nlq_num);
271 DEBUG("olsrd plugin: Average NLQ: %g", nlq);
272 olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
273 "average-nlq", nlq);
275 return (0);
276 }
278 if (fields_num != 6)
279 return (-1);
281 links_num++;
283 errno = 0;
284 endptr = NULL;
285 lq = strtod(fields[3], &endptr);
286 if ((errno != 0) || (endptr == fields[3])) {
287 ERROR("olsrd plugin: Cannot parse link quality: %s", fields[3]);
288 } else {
289 if (!isnan(lq)) {
290 lq_sum += lq;
291 lq_num++;
292 }
294 if (config_want_links == OLSRD_WANT_DETAIL) {
295 char type_instance[DATA_MAX_NAME_LEN];
297 ssnprintf(type_instance, sizeof(type_instance), "%s-%s-lq", fields[0],
298 fields[1]);
300 DEBUG("olsrd plugin: links: type_instance = %s; lq = %g;", type_instance,
301 lq);
302 olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
303 type_instance, lq);
304 }
305 }
307 errno = 0;
308 endptr = NULL;
309 nlq = strtod(fields[4], &endptr);
310 if ((errno != 0) || (endptr == fields[4])) {
311 ERROR("olsrd plugin: Cannot parse neighbor link quality: %s", fields[4]);
312 } else {
313 if (!isnan(nlq)) {
314 nlq_sum += nlq;
315 nlq_num++;
316 }
318 if (config_want_links == OLSRD_WANT_DETAIL) {
319 char type_instance[DATA_MAX_NAME_LEN];
321 ssnprintf(type_instance, sizeof(type_instance), "%s-%s-rx", fields[0],
322 fields[1]);
324 DEBUG("olsrd plugin: links: type_instance = %s; nlq = %g;", type_instance,
325 lq);
326 olsrd_submit(/* p.-inst = */ "links", /* type = */ "signal_quality",
327 type_instance, nlq);
328 }
329 }
331 return (0);
332 } /* }}} int olsrd_cb_links */
334 static int olsrd_cb_routes(int lineno, /* {{{ */
335 size_t fields_num, char **fields) {
336 /* Fields:
337 * 0 = Destination
338 * 1 = Gateway IP
339 * 2 = Metric
340 * 3 = ETX
341 * 4 = Interface */
343 static uint32_t routes_num;
344 static uint32_t metric_sum;
345 static uint32_t metric_num;
346 static double etx_sum;
347 static uint32_t etx_num;
349 uint32_t metric;
350 double etx;
351 char *endptr;
353 if (config_want_routes == OLSRD_WANT_NOT)
354 return (0);
356 /* Special handling of the first line */
357 if (lineno <= 0) {
358 routes_num = 0;
359 metric_num = 0;
360 metric_sum = 0;
361 etx_sum = 0.0;
362 etx_num = 0;
364 return (0);
365 }
367 /* Special handling after the last line */
368 if (fields_num == 0) {
369 double metric_avg;
371 DEBUG("olsrd plugin: Number of routes: %" PRIu32, routes_num);
372 olsrd_submit(/* p.-inst = */ "routes", /* type = */ "routes",
373 /* t.-inst = */ NULL, (gauge_t)routes_num);
375 metric_avg = NAN;
376 if (metric_num > 0)
377 metric_avg = ((double)metric_sum) / ((double)metric_num);
378 DEBUG("olsrd plugin: Average metric: %g", metric_avg);
379 olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_metric",
380 "average", metric_avg);
382 etx = NAN;
383 if (etx_num > 0)
384 etx = etx_sum / ((double)etx_sum);
385 DEBUG("olsrd plugin: Average ETX: %g", etx);
386 olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_etx", "average",
387 etx);
389 return (0);
390 }
392 if (fields_num != 5)
393 return (-1);
395 routes_num++;
397 errno = 0;
398 endptr = NULL;
399 metric = (uint32_t)strtoul(fields[2], &endptr, 0);
400 if ((errno != 0) || (endptr == fields[2])) {
401 ERROR("olsrd plugin: Unable to parse metric: %s", fields[2]);
402 } else {
403 metric_num++;
404 metric_sum += metric;
406 if (config_want_routes == OLSRD_WANT_DETAIL) {
407 DEBUG("olsrd plugin: destination = %s; metric = %" PRIu32 ";", fields[0],
408 metric);
409 olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_metric",
410 /* t.-inst = */ fields[0], (gauge_t)metric);
411 }
412 }
414 errno = 0;
415 endptr = NULL;
416 etx = strtod(fields[3], &endptr);
417 if ((errno != 0) || (endptr == fields[3])) {
418 ERROR("olsrd plugin: Unable to parse ETX: %s", fields[3]);
419 } else {
420 if (!isnan(etx)) {
421 etx_sum += etx;
422 etx_num++;
423 }
425 if (config_want_routes == OLSRD_WANT_DETAIL) {
426 DEBUG("olsrd plugin: destination = %s; etx = %g;", fields[0], etx);
427 olsrd_submit(/* p.-inst = */ "routes", /* type = */ "route_etx",
428 /* t.-inst = */ fields[0], etx);
429 }
430 }
432 return (0);
433 } /* }}} int olsrd_cb_routes */
435 static int olsrd_cb_topology(int lineno, /* {{{ */
436 size_t fields_num, char **fields) {
437 /* Fields:
438 * 0 = Dest. IP
439 * 1 = Last hop IP
440 * 2 = LQ
441 * 3 = NLQ
442 * 4 = Cost */
444 static double lq_sum;
445 static uint32_t lq_num;
447 static uint32_t links_num;
449 double lq;
450 char *endptr;
452 if (config_want_topology == OLSRD_WANT_NOT)
453 return (0);
455 /* Special handling of the first line */
456 if (lineno <= 0) {
457 lq_sum = 0.0;
458 lq_num = 0;
459 links_num = 0;
461 return (0);
462 }
464 /* Special handling after the last line */
465 if (fields_num == 0) {
466 DEBUG("olsrd plugin: topology: Number of links: %" PRIu32, links_num);
467 olsrd_submit(/* p.-inst = */ "topology", /* type = */ "links",
468 /* t.-inst = */ NULL, (gauge_t)links_num);
470 lq = NAN;
471 if (lq_num > 0)
472 lq = lq_sum / ((double)lq_sum);
473 DEBUG("olsrd plugin: topology: Average link quality: %g", lq);
474 olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
475 /* t.-inst = */ "average", lq);
477 return (0);
478 }
480 if (fields_num != 5)
481 return (-1);
483 links_num++;
485 errno = 0;
486 endptr = NULL;
487 lq = strtod(fields[2], &endptr);
488 if ((errno != 0) || (endptr == fields[2])) {
489 ERROR("olsrd plugin: Unable to parse LQ: %s", fields[2]);
490 } else {
491 if (!isnan(lq)) {
492 lq_sum += lq;
493 lq_num++;
494 }
496 if (config_want_topology == OLSRD_WANT_DETAIL) {
497 char type_instance[DATA_MAX_NAME_LEN] = {0};
499 ssnprintf(type_instance, sizeof(type_instance), "%s-%s-lq", fields[0],
500 fields[1]);
501 DEBUG("olsrd plugin: type_instance = %s; lq = %g;", type_instance, lq);
502 olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
503 type_instance, lq);
504 }
505 }
507 if (config_want_topology == OLSRD_WANT_DETAIL) {
508 double nlq;
510 errno = 0;
511 endptr = NULL;
512 nlq = strtod(fields[3], &endptr);
513 if ((errno != 0) || (endptr == fields[3])) {
514 ERROR("olsrd plugin: Unable to parse NLQ: %s", fields[3]);
515 } else {
516 char type_instance[DATA_MAX_NAME_LEN] = {0};
518 ssnprintf(type_instance, sizeof(type_instance), "%s-%s-nlq", fields[0],
519 fields[1]);
520 DEBUG("olsrd plugin: type_instance = %s; nlq = %g;", type_instance, nlq);
521 olsrd_submit(/* p.-inst = */ "topology", /* type = */ "signal_quality",
522 type_instance, nlq);
523 }
524 }
526 return (0);
527 } /* }}} int olsrd_cb_topology */
529 static int olsrd_read_table(FILE *fh, /* {{{ */
530 int (*callback)(int lineno, size_t fields_num,
531 char **fields)) {
532 char buffer[1024];
533 size_t buffer_len;
535 char *fields[32];
536 size_t fields_num;
538 int lineno;
540 lineno = 0;
541 while (fgets(buffer, sizeof(buffer), fh) != NULL) {
542 /* An empty line ends the table. */
543 buffer_len = strchomp(buffer);
544 if (buffer_len == 0) {
545 (*callback)(lineno, /* fields_num = */ 0, /* fields = */ NULL);
546 break;
547 }
549 fields_num = strtabsplit(buffer, fields, STATIC_ARRAY_SIZE(fields));
551 (*callback)(lineno, fields_num, fields);
552 lineno++;
553 } /* while (fgets) */
555 return (0);
556 } /* }}} int olsrd_read_table */
558 static int olsrd_config(const char *key, const char *value) /* {{{ */
559 {
560 if (strcasecmp("Host", key) == 0)
561 olsrd_set_node(value);
562 else if (strcasecmp("Port", key) == 0)
563 olsrd_set_service(value);
564 else if (strcasecmp("CollectLinks", key) == 0)
565 olsrd_set_detail(&config_want_links, value, key);
566 else if (strcasecmp("CollectRoutes", key) == 0)
567 olsrd_set_detail(&config_want_routes, value, key);
568 else if (strcasecmp("CollectTopology", key) == 0)
569 olsrd_set_detail(&config_want_topology, value, key);
570 else {
571 ERROR("olsrd plugin: Unknown configuration option given: %s", key);
572 return (-1);
573 }
575 return (0);
576 } /* }}} int olsrd_config */
578 static int olsrd_read(void) /* {{{ */
579 {
580 FILE *fh;
581 char buffer[1024];
582 size_t buffer_len;
584 fh = olsrd_connect();
585 if (fh == NULL)
586 return (-1);
588 fputs("\r\n", fh);
589 fflush(fh);
591 while (fgets(buffer, sizeof(buffer), fh) != NULL) {
592 buffer_len = strchomp(buffer);
593 if (buffer_len == 0)
594 continue;
596 if (strcmp("Table: Links", buffer) == 0)
597 olsrd_read_table(fh, olsrd_cb_links);
598 else if (strcmp("Table: Neighbors", buffer) == 0)
599 olsrd_read_table(fh, olsrd_cb_ignore);
600 else if (strcmp("Table: Topology", buffer) == 0)
601 olsrd_read_table(fh, olsrd_cb_topology);
602 else if (strcmp("Table: HNA", buffer) == 0)
603 olsrd_read_table(fh, olsrd_cb_ignore);
604 else if (strcmp("Table: MID", buffer) == 0)
605 olsrd_read_table(fh, olsrd_cb_ignore);
606 else if (strcmp("Table: Routes", buffer) == 0)
607 olsrd_read_table(fh, olsrd_cb_routes);
608 else if ((strcmp("HTTP/1.0 200 OK", buffer) == 0) ||
609 (strcmp("Content-type: text/plain", buffer) == 0)) {
610 /* ignore */
611 } else {
612 DEBUG("olsrd plugin: Unable to handle line: %s", buffer);
613 }
614 } /* while (fgets) */
616 fclose(fh);
618 return (0);
619 } /* }}} int olsrd_read */
621 static int olsrd_shutdown(void) /* {{{ */
622 {
623 sfree(config_node);
624 sfree(config_service);
626 return (0);
627 } /* }}} int olsrd_shutdown */
629 void module_register(void) {
630 plugin_register_config("olsrd", olsrd_config, config_keys, config_keys_num);
631 plugin_register_read("olsrd", olsrd_read);
632 plugin_register_shutdown("olsrd", olsrd_shutdown);
633 } /* void module_register */
635 /* vim: set sw=2 sts=2 et fdm=marker : */