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