powerdns plugin: Added a plugin to read statistics from PowerDNS' control socket.
[collectd.git] / src / powerdns.c
1 /**
2  * collectd - src/powerdns.c
3  * Copyright (C) 2007-2008  C-Ware, Inc.
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  * Author:
19  *   Luke Heberling <lukeh at c-ware.com>
20  *
21  * DESCRIPTION
22  *      Queries a PowerDNS control socket for statistics
23  *
24  **/
25
26 #include "collectd.h"
27 #include "plugin.h"
28 #include "configfile.h"
29 #include "utils_llist.h"
30
31 #include <sys/stat.h>
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <sys/un.h>
40 #include <malloc.h>
41
42 #define BUFFER_SIZE 1000
43
44 #define FUNC_ERROR(func) ERROR ("%s: `%s' failed\n", "powerdns", func)
45
46 #define COMMAND_SERVER "SHOW *"
47 #define COMMAND_RECURSOR "get all-outqueries answers0-1 answers100-1000 answers10-100 answers1-10 answers-slow cache-entries cache-hits cache-misses chain-resends client-parse-errors concurrent-queries dlg-only-drops ipv6-outqueries negcache-entries noerror-answers nsset-invalidations nsspeeds-entries nxdomain-answers outgoing-timeouts qa-latency questions resource-limits server-parse-errors servfail-answers spoof-prevents sys-msec tcp-client-overflow tcp-outqueries tcp-questions throttled-out throttled-outqueries throttle-entries unauthorized-tcp unauthorized-udp unexpected-packets unreachables user-msec"
48
49 typedef void item_func (void*);
50 typedef ssize_t io_func (int, void*, size_t, int);
51
52 struct list_item_s
53 {
54   item_func *func;
55   char *instance;
56   char *command;
57   struct sockaddr_un remote;
58   struct sockaddr_un local;
59 };
60 typedef struct list_item_s list_item_t;
61
62 static llist_t *list = NULL;
63
64 static void submit (const char *instance, const char *name, const char *value)
65 {
66   value_list_t vl = VALUE_LIST_INIT;
67   value_t values[1];
68   const data_set_t *ds;
69   float f;
70   long l;
71
72   ds = plugin_get_ds (name);
73   if (ds == NULL)
74   {
75     ERROR( "%s: DS %s not defined\n", "powerdns", name );
76     return;
77   }
78
79   errno = 0;
80   if (ds->ds->type == DS_TYPE_GAUGE)
81   {
82     f = atof(value);
83     if (errno != 0)
84     {
85       ERROR ("%s: atof failed (%s->%s)", "powerdns", name, value);
86       return;
87     }
88     else
89     {
90       values[0].gauge = f<0?-f:f;
91     }
92   }
93   else
94   {
95     l = atol(value);
96     if (errno != 0)
97     {
98       ERROR ("%s: atol failed (%s->%s)", "powerdns", name, value);
99       return;
100     }
101     else
102     {
103       values[0].counter = l < 0 ? -l : l;
104     }
105   }
106
107   vl.values = values;
108   vl.values_len = 1;
109   vl.time = time (NULL);
110   strncpy (vl.host, hostname_g, sizeof (vl.host));
111   strncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
112   strncpy (vl.type_instance, "", sizeof (vl.type_instance));
113   strncpy (vl.plugin_instance,instance, sizeof (vl.plugin_instance));
114
115   plugin_dispatch_values (name, &vl);
116 } /* static void submit */
117
118 static int io (io_func *func, int fd, char* buf, int buflen)
119 {
120   int bytes = 0;
121   int cc = 1;
122   for (; buflen > 0 && (cc = func (fd, buf, buflen, 0)) > 0; 
123       buf += cc, bytes += cc, buflen -= cc)
124     ;
125
126   return bytes;
127 } /* static int io */
128
129 static void powerdns_read_server (list_item_t *item)
130 {
131   int bytes;
132   int sck;
133   char *name_token,*value_token,*pos;
134   char *buffer;
135   char *delims = ",=";
136
137   if ((sck = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
138   {
139     FUNC_ERROR ("socket");
140     return;
141   }
142
143   if (connect( sck,(struct sockaddr *) &item->remote, 
144         sizeof(item->remote)) == -1)
145   {
146     FUNC_ERROR( "connect" );
147     close (sck);
148     return;
149   }
150
151   buffer = malloc (BUFFER_SIZE + 1);
152   if (buffer == NULL)
153   {
154     FUNC_ERROR ("malloc");
155     close (sck);
156     return;
157   }
158   strncpy (buffer, 
159       item->command == NULL ? COMMAND_SERVER : item->command,
160       BUFFER_SIZE);
161   buffer[BUFFER_SIZE] = '\0';
162
163   if (io ((io_func*) &send, sck, buffer, strlen(buffer)) < strlen(buffer))
164   {
165     FUNC_ERROR ("send");
166     free (buffer);
167     close (sck);
168     return;
169   }
170
171   bytes = io ((io_func*) &recv, sck, buffer, BUFFER_SIZE);
172   if (bytes < 1)
173   {
174     FUNC_ERROR ("recv");
175     free (buffer);
176     close (sck);
177     return;
178   }
179
180   close(sck);
181
182   buffer[bytes] = '\0';
183
184   for (name_token = strtok_r (buffer, delims, &pos),
185       value_token = strtok_r (NULL, delims, &pos);
186       name_token != NULL && value_token != NULL;
187       name_token = strtok_r (NULL, delims, &pos ),
188       value_token = strtok_r (NULL, delims, &pos) )
189     submit (item->instance, name_token, value_token);
190
191   free (buffer);
192   return;
193 } /* static void powerdns_read_server */
194
195 static void powerdns_read_recursor (list_item_t *item) {
196   int sck,tmp,bytes;
197   char *ptr;
198   char *name_token, *name_pos;
199   char *value_token, *value_pos;
200   char *send_buffer;
201   char *recv_buffer;
202   char *delims = " \n"; 
203
204   for (ptr = item->local.sun_path
205       + strlen(item->local.sun_path) - 1;
206       ptr > item->local.sun_path && *ptr != '/'; --ptr)
207     ;
208
209   if (ptr <= item->local.sun_path)
210   {
211     ERROR("%s: Bad path %s\n", "powerdns", item->local.sun_path);
212     return;
213   }
214
215   *ptr='\0';
216   strncat (item->local.sun_path, "/lsockXXXXXX",
217       sizeof (item->local.sun_path) - strlen (item->local.sun_path));
218
219   if ((sck = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
220     FUNC_ERROR ("socket");
221     return;
222   }
223
224   tmp = 1;
225   if (setsockopt (sck, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)) < 0)
226   {
227     FUNC_ERROR ("setsockopt");
228     close (sck);
229     return;
230   }
231
232   if ((tmp=mkstemp(item->local.sun_path))< 0)
233   {
234     FUNC_ERROR ("mkstemp");
235     close (sck);
236     return;
237   }
238   close (tmp);
239
240   if (unlink(item->local.sun_path) < 0 && errno != ENOENT)
241   {
242     FUNC_ERROR ("unlink");
243     close (sck);
244     return;
245   }
246
247   if (bind(sck, (struct sockaddr*)&item->local, sizeof(item->local)) < 0)
248   {
249     FUNC_ERROR ("bind");
250     close (sck);
251     unlink (item->local.sun_path);
252     return;
253   }
254
255   if (chmod(item->local.sun_path,0666) < 0)
256   {
257     FUNC_ERROR ("chmod");
258     close (sck);
259     unlink (item->local.sun_path);
260     return;
261   }
262
263   if (connect (sck,(struct sockaddr *) &item->remote, sizeof(item->remote)) == -1)
264   {
265     FUNC_ERROR ("connect");
266     close (sck);
267     unlink (item->local.sun_path);
268     return;
269   }
270
271   send_buffer = strdup (item->command == NULL ? COMMAND_RECURSOR : item->command);
272   if (send_buffer == NULL)
273   {
274     FUNC_ERROR ("strdup");
275     close (sck);
276     unlink (item->local.sun_path);
277     return;
278   }
279
280   if (io((io_func*)&send, sck, send_buffer, strlen (send_buffer)) < strlen (send_buffer))
281   {
282     FUNC_ERROR ("send");
283     close (sck);
284     unlink (item->local.sun_path);
285     free (send_buffer);
286     return;
287   }
288
289   recv_buffer = malloc (BUFFER_SIZE + 1);
290   if (recv_buffer == NULL)
291   {
292     FUNC_ERROR ("malloc");
293     close (sck);
294     unlink (item->local.sun_path);
295     free (send_buffer);
296     return;
297   }
298
299   bytes = recv (sck, recv_buffer, BUFFER_SIZE, 0);
300   if (bytes < 1) {
301     FUNC_ERROR ("recv");
302     close (sck);
303     unlink (item->local.sun_path);
304     free (send_buffer);
305     free (recv_buffer);
306     return;
307   }
308   recv_buffer[bytes]='\0';
309
310   close (sck);
311   unlink (item->local.sun_path);
312
313   for( name_token = strtok_r (send_buffer, delims, &name_pos),
314       name_token = strtok_r (NULL, delims, &name_pos),
315       value_token = strtok_r (recv_buffer, delims, &value_pos);
316       name_token != NULL && value_token != NULL;
317       name_token = strtok_r (NULL, delims, &name_pos),
318       value_token = strtok_r (NULL, delims, &value_pos) )
319     submit (item->instance, name_token, value_token);
320
321   free (send_buffer);
322   free (recv_buffer);
323   return;
324
325 } /* static void powerdns_read_recursor */
326
327 static int powerdns_term() {
328   llentry_t *e_this;
329   llentry_t *e_next;
330   list_item_t *item;
331
332   if (list != NULL)
333   {
334     for (e_this = llist_head(list); e_this != NULL; e_this = e_next)
335     {
336       item = e_this->value;
337       free (item->instance);
338
339       if (item->command != COMMAND_SERVER &&
340           item->command != COMMAND_RECURSOR)
341         free (item->command);
342
343       free (item);
344
345       e_next = e_this->next;
346     }
347
348     llist_destroy (list);
349     list = NULL;
350   }
351
352   return (0);
353 } /* static int powerdns_term */
354
355 static int powerdns_config (oconfig_item_t *ci)
356 {
357   oconfig_item_t *gchild;
358   int gchildren;
359
360   oconfig_item_t *child = ci->children;
361   int children = ci->children_num;
362
363   llentry_t *entry;
364   list_item_t *item;
365
366   if (list == NULL && (list = llist_create()) == NULL )
367   {
368     ERROR ("powerdns plugin: `llist_create' failed.");
369     return 1;
370   }             
371
372   for (; children; --children, ++child)
373   {
374     item = malloc (sizeof (list_item_t));
375     if (item == NULL)
376     {
377       ERROR ("powerdns plugin: `malloc' failed.");
378       return 1;
379     }
380
381     if (strcmp (child->key, "Server") == 0)
382     {
383       item->func = (item_func*)&powerdns_read_server;
384       item->command = COMMAND_SERVER;
385     }
386     else if (strcmp (child->key, "Recursor") == 0)
387     {
388       item->func = (item_func*)&powerdns_read_recursor;
389       item->command = COMMAND_RECURSOR;
390     }
391     else
392     {
393       WARNING ("powerdns plugin: Ignoring unknown"
394           " config option `%s'.", child->key);
395       free (item);
396       continue;
397     }
398
399     if ((child->values_num != 1) ||
400         (child->values[0].type != OCONFIG_TYPE_STRING))
401     {
402       WARNING ("powerdns plugin: `%s' needs exactly"
403           " one string argument.", child->key);
404       free (item);
405       continue;
406     }
407
408     if (llist_search (list, child->values[0].value.string) != NULL)
409     {
410       ERROR ("powerdns plugin: multiple instances for %s",
411           child->values[0].value.string);
412       free (item);
413       return 1;
414     }
415
416     item->instance = strdup (child->values[0].value.string);
417     if (item->instance == NULL)
418     {
419       ERROR ("powerdns plugin: `strdup' failed.");
420       free (item);
421       return 1;
422     }
423
424     entry = llentry_create (item->instance, item);
425     if (entry == NULL)
426     {
427       ERROR ("powerdns plugin: `llentry_create' failed.");
428       free (item->instance);
429       free (item);
430       return 1;
431     }
432
433     item->remote.sun_family = ~AF_UNIX;
434
435     gchild = child->children;
436     gchildren = child->children_num;
437
438     for (; gchildren; --gchildren, ++gchild)
439     {
440       if (strcmp (gchild->key, "Socket") == 0)
441       {
442         if (gchild->values_num != 1 || 
443             gchild->values[0].type != OCONFIG_TYPE_STRING)
444         {
445           WARNING ("powerdns plugin: config option `%s'"
446               " should have exactly one string value.",
447               gchild->key);
448           continue;
449         }
450         if (item->remote.sun_family == AF_UNIX)
451         {
452           WARNING ("powerdns plugin: ignoring extraneous"
453               " `%s' config option.", gchild->key);
454           continue;
455         }
456         item->remote.sun_family = item->local.sun_family = AF_UNIX;
457         strncpy (item->remote.sun_path, gchild->values[0].value.string,
458             sizeof (item->remote.sun_path));
459         strncpy (item->local.sun_path, gchild->values[0].value.string,
460             sizeof (item->remote.sun_path));
461       }
462       else if (strcmp (gchild->key, "Command") == 0)
463       {
464         if (gchild->values_num != 1 
465             || gchild->values[0].type != OCONFIG_TYPE_NUMBER)
466         {
467           WARNING ("powerdns plugin: config option `%s'"
468               " should have exactly one string value.",
469               gchild->key);
470           continue;
471         }
472         if (item->command != COMMAND_RECURSOR &&
473             item->command != COMMAND_SERVER)
474         {
475           WARNING ("powerdns plugin: ignoring extraneous"
476               " `%s' config option.", gchild->key);
477           continue;
478         }
479         item->command = strdup (gchild->values[0].value.string);
480         if (item->command == NULL)
481         {
482           ERROR ("powerdns plugin: `strdup' failed.");
483           llentry_destroy (entry);
484           free (item->instance);
485           free (item);
486           return 1;
487         }
488       }
489       else
490       {
491         WARNING ("powerdns plugin: Ignoring unknown config option"
492             " `%s'.", gchild->key);
493         continue;
494       }
495
496       if (gchild->children_num)
497       {
498         WARNING ("powerdns plugin: config option `%s' should not"
499             " have children.", gchild->key);
500       }
501     }
502
503
504     if (item->remote.sun_family != AF_UNIX)
505     {
506       if (item->func == (item_func*)&powerdns_read_server)
507       {
508         item->remote.sun_family = item->local.sun_family = AF_UNIX;
509         strncpy (item->remote.sun_path, "/var/run/pdns.controlsocket",
510             sizeof (item->remote.sun_path));
511         strncpy (item->local.sun_path, "/var/run/pdns.controlsocket",
512             sizeof (item->remote.sun_path));
513       }
514       else
515       {
516         item->remote.sun_family = item->local.sun_family = AF_UNIX;
517         strncpy (item->remote.sun_path, "/var/run/pdns_recursor.controlsocket",
518             sizeof (item->remote.sun_path));
519         strncpy (item->local.sun_path, "/var/run/pdns_recursor.controlsocket",
520             sizeof (item->remote.sun_path));
521       }
522     }
523
524     llist_append (list, entry);
525   }
526
527   return 0;
528 } /* static int powerdns_config */
529
530 static int powerdns_read(void)
531 {
532   llentry_t *e_this;
533   list_item_t *item;
534
535   for (e_this = llist_head(list); e_this != NULL; e_this = e_this->next)
536   {
537     item = e_this->value;
538     item->func(item);
539   }
540
541   return (0);
542 } /* static int powerdns_read */
543
544 void module_register (void)
545 {
546   plugin_register_complex_config ("powerdns", powerdns_config);
547   plugin_register_read ("powerdns", powerdns_read);
548   plugin_register_shutdown ("powerdns", powerdns_term );
549 } /* void module_register */
550
551 /* vim: set sw=2 sts=2 ts=8 : */