dns plugin: Collect and transfer byte counters for queries and replies.
[collectd.git] / src / dns.c
1 /**
2  * collectd - src/dns.c
3  * Copyright (C) 2006  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; either version 2 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at verplant.org>
21  **/
22
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "configfile.h"
27 #include "utils_debug.h"
28
29 #if HAVE_SYS_POLL_H
30 # include <sys/poll.h>
31 #endif
32
33 #define MODULE_NAME "dns"
34
35 #if HAVE_LIBPCAP
36 # define NAMED_HAVE_CONFIG 1
37 #else
38 # define NAMED_HAVE_CONFIG 0
39 #endif
40
41 #if HAVE_LIBPCAP
42 # include "utils_dns.h"
43 # define NAMED_HAVE_READ 1
44 #else
45 # define NAMED_HAVE_READ 0
46 #endif
47
48 struct counter_list_s
49 {
50         unsigned int key;
51         unsigned int value;
52         struct counter_list_s *next;
53 };
54 typedef struct counter_list_s counter_list_t;
55
56 static char *qtype_file   = "dns/qtype-%s.rrd";
57 static char *opcode_file  = "dns/opcode-%s.rrd";
58 static char *rcode_file   = "dns/rcode-%s.rrd";
59
60 static char *qtype_ds_def[] =
61 {
62         "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535",
63         NULL
64 };
65 static int qtype_ds_num = 1;
66
67 static char *opcode_ds_def[] =
68 {
69         "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535",
70         NULL
71 };
72 static int opcode_ds_num = 1;
73
74 static char *rcode_ds_def[] =
75 {
76         "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535",
77         NULL
78 };
79 static int rcode_ds_num = 1;
80
81 #if NAMED_HAVE_CONFIG
82 #if HAVE_LIBPCAP
83 static char *config_keys[] =
84 {
85         "Interface",
86         "IgnoreSource",
87         NULL
88 };
89 static int config_keys_num = 2;
90 #endif /* HAVE_LIBPCAP */
91 #endif /* NAMED_HAVE_CONFIG */
92
93 #if HAVE_LIBPCAP
94 #define PCAP_SNAPLEN 1460
95 static char   *pcap_device = NULL;
96 static int     pipe_fd = -1;
97
98 static unsigned int    tr_queries;
99 static unsigned int    tr_responses;
100 static counter_list_t *qtype_list;
101 static counter_list_t *opcode_list;
102 static counter_list_t *rcode_list;
103 #endif
104
105 static counter_list_t *counter_list_search (counter_list_t **list, unsigned int key)
106 {
107         counter_list_t *entry;
108
109         DBG ("counter_list_search (list = %p, key = %u)",
110                         (void *) *list, key);
111
112         for (entry = *list; entry != NULL; entry = entry->next)
113                 if (entry->key == key)
114                         break;
115
116         DBG ("return (%p)", (void *) entry);
117         return (entry);
118 }
119
120 static counter_list_t *counter_list_create (counter_list_t **list,
121                 unsigned int key, unsigned int value)
122 {
123         counter_list_t *entry;
124
125         DBG ("counter_list_create (list = %p, key = %u, value = %u)",
126                         (void *) *list, key, value);
127
128         entry = (counter_list_t *) malloc (sizeof (counter_list_t));
129         if (entry == NULL)
130                 return (NULL);
131
132         memset (entry, 0, sizeof (counter_list_t));
133         entry->key = key;
134         entry->value = value;
135
136         if (*list == NULL)
137         {
138                 *list = entry;
139         }
140         else
141         {
142                 counter_list_t *last;
143
144                 last = *list;
145                 while (last->next != NULL)
146                         last = last->next;
147
148                 last->next = entry;
149         }
150
151         DBG ("return (%p)", (void *) entry);
152         return (entry);
153 }
154
155 static void counter_list_add (counter_list_t **list,
156                 unsigned int key, unsigned int increment)
157 {
158         counter_list_t *entry;
159
160         DBG ("counter_list_add (list = %p, key = %u, increment = %u)",
161                         (void *) *list, key, increment);
162
163         entry = counter_list_search (list, key);
164
165         if (entry != NULL)
166         {
167                 entry->value += increment;
168         }
169         else
170         {
171                 counter_list_create (list, key, increment);
172         }
173         DBG ("return ()");
174 }
175
176 static int counter_list_send (counter_list_t *list, int fd)
177 {
178         counter_list_t *cl;
179         unsigned int values[2 * T_MAX];
180         unsigned int values_num;
181
182         if (fd < 0)
183                 return (-1);
184
185         values_num = 0;
186
187         for (cl = list;
188                         (cl != NULL) && (values_num < T_MAX);
189                         cl = cl->next)
190         {
191                 values[2 * values_num] = cl->key;
192                 values[(2 * values_num) + 1] = cl->value;
193                 values_num++;
194         }
195
196         DBG ("swrite (fd = %i, values_num = %i)", fd, values_num);
197         if (swrite (fd, (const void *) &values_num, sizeof (values_num)) != 0)
198         {
199                 DBG ("Writing to fd failed: %s", strerror (errno));
200                 syslog (LOG_ERR, "dns plugin: Writing to fd failed: %s",
201                                 strerror (errno));
202                 return (-1);
203         }
204
205         if (values_num == 0)
206                 return (0);
207
208         DBG ("swrite (fd = %i, values = %p, size = %i)",
209                         fd, (void *) values, (int) (sizeof (int) * values_num));
210         if (swrite (fd, (const void *) values, 2 * sizeof (int) * values_num) != 0)
211         {
212                 DBG ("Writing to pipe failed: %s", strerror (errno));
213                 syslog (LOG_ERR, "dns plugin: Writing to pipe failed: %s",
214                                 strerror (errno));
215                 return (-1);
216         }
217
218         return (values_num);
219 }
220 #if NAMED_HAVE_CONFIG
221 static int dns_config (char *key, char *value)
222 {
223 #if HAVE_LIBPCAP
224         if (strcasecmp (key, "Interface") == 0)
225         {
226                 if (pcap_device != NULL)
227                         free (pcap_device);
228                 if ((pcap_device = strdup (value)) == NULL)
229                         return (1);
230         }
231         else if (strcasecmp (key, "IgnoreSource") == 0)
232         {
233                 if (value != NULL)
234                         ignore_list_add_name (value);
235         }
236         else
237         {
238                 return (-1);
239         }
240
241         return (0);
242 #endif /* HAVE_LIBPCAP */
243 }
244 #endif /* NAMED_HAVE_CONFIG */
245
246 static void dns_child_callback (const rfc1035_header_t *dns)
247 {
248         if (dns->qr == 0)
249         {
250                 /* This is a query */
251                 tr_queries += dns->length;
252                 counter_list_add (&qtype_list,  dns->qtype,  1);
253         }
254         else
255         {
256                 /* This is a reply */
257                 tr_responses += dns->length;
258                 counter_list_add (&rcode_list,  dns->rcode,  1);
259         }
260
261         /* FIXME: Are queries, replies or both interesting? */
262         counter_list_add (&opcode_list, dns->opcode, 1);
263 }
264
265 static void dns_child_loop (void)
266 {
267         pcap_t *pcap_obj;
268         char    pcap_error[PCAP_ERRBUF_SIZE];
269         struct  bpf_program fp;
270
271         struct pollfd poll_fds[2];
272         int status;
273
274         /* Passing `pcap_device == NULL' is okay and the same as passign "any" */
275         DBG ("Creating PCAP object..");
276         pcap_obj = pcap_open_live (pcap_device,
277                         PCAP_SNAPLEN,
278                         0 /* Not promiscuous */,
279                         0 /* no read timeout */,
280                         pcap_error);
281         if (pcap_obj == NULL)
282         {
283                 syslog (LOG_ERR, "dns plugin: Opening interface `%s' failed: %s",
284                                 (pcap_device != NULL) ? pcap_device : "any",
285                                 pcap_error);
286                 close (pipe_fd);
287                 pipe_fd = -1;
288                 return;
289         }
290
291         memset (&fp, 0, sizeof (fp));
292         if (pcap_compile (pcap_obj, &fp, "udp port 53", 1, 0) < 0)
293         {
294                 DBG ("pcap_compile failed");
295                 syslog (LOG_ERR, "dns plugin: pcap_compile failed");
296                 close (pipe_fd);
297                 pipe_fd = -1;
298                 return;
299         }
300         if (pcap_setfilter (pcap_obj, &fp) < 0)
301         {
302                 DBG ("pcap_setfilter failed");
303                 syslog (LOG_ERR, "dns plugin: pcap_setfilter failed");
304                 close (pipe_fd);
305                 pipe_fd = -1;
306                 return;
307         }
308
309         DBG ("PCAP object created.");
310
311         dnstop_set_pcap_obj (pcap_obj);
312         dnstop_set_callback (dns_child_callback);
313
314         /* Set up pipe end */
315         poll_fds[0].fd = pipe_fd;
316         poll_fds[0].events = POLLOUT;
317
318         /* Set up pcap device */
319         poll_fds[1].fd = pcap_fileno (pcap_obj);
320         poll_fds[1].events = POLLIN | POLLPRI;
321
322         while (pipe_fd > 0)
323         {
324                 DBG ("poll (...)");
325                 status = poll (poll_fds, 2, -1 /* wait forever for a change */);
326
327                 if (status < 0)
328                 {
329                         syslog (LOG_ERR, "dns plugin: poll(2) failed: %s",
330                                         strerror (errno));
331                         break;
332                 }
333
334                 if (poll_fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
335                 {
336                         DBG ("Pipe closed. Exiting.");
337                         syslog (LOG_NOTICE, "dns plugin: Pipe closed. Exiting.");
338                         break;
339                 }
340                 else if (poll_fds[0].revents & POLLOUT)
341                 {
342                         DBG ("Sending data..");
343
344                         DBG ("swrite (pipe_fd = %i, tr_queries = %i)", pipe_fd, tr_queries);
345                         if (swrite (pipe_fd, (const void *) &tr_queries, sizeof (tr_queries)) != 0)
346                         {
347                                 DBG ("Writing to pipe_fd failed: %s", strerror (errno));
348                                 syslog (LOG_ERR, "dns plugin: Writing to pipe_fd failed: %s",
349                                                 strerror (errno));
350                                 return;
351                         }
352
353                         DBG ("swrite (pipe_fd = %i, tr_responses = %i)", pipe_fd, tr_responses);
354                         if (swrite (pipe_fd, (const void *) &tr_responses, sizeof (tr_responses)) != 0)
355                         {
356                                 DBG ("Writing to pipe_fd failed: %s", strerror (errno));
357                                 syslog (LOG_ERR, "dns plugin: Writing to pipe_fd failed: %s",
358                                                 strerror (errno));
359                                 return;
360                         }
361
362                         counter_list_send (qtype_list, pipe_fd);
363                         counter_list_send (opcode_list, pipe_fd);
364                         counter_list_send (rcode_list, pipe_fd);
365                 }
366
367                 if (poll_fds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
368                 {
369                         DBG ("pcap-device closed. Exiting.");
370                         syslog (LOG_ERR, "dns plugin: pcap-device closed. Exiting.");
371                         break;
372                 }
373                 else if (poll_fds[1].revents & (POLLIN | POLLPRI))
374                 {
375                         status = pcap_dispatch (pcap_obj,
376                                         10 /* Only handle 10 packets at a time */,
377                                         handle_pcap /* callback */,
378                                         NULL /* Whatever this means.. */);
379                         if (status < 0)
380                         {
381                                 DBG ("pcap_dispatch failed: %s", pcap_geterr (pcap_obj));
382                                 syslog (LOG_ERR, "dns plugin: pcap_dispatch failed: %s",
383                                                 pcap_geterr (pcap_obj));
384                                 break;
385                         }
386                 }
387         } /* while (42) */
388
389         DBG ("child is exiting");
390
391         close (pipe_fd);
392         pipe_fd = -1;
393         pcap_close (pcap_obj);
394 } /* static void dns_child_loop (void) */
395
396 static void dns_init (void)
397 {
398 #if HAVE_LIBPCAP
399         int pipe_fds[2];
400         pid_t pid_child;
401
402         tr_queries   = 0;
403         tr_responses = 0;
404
405         if (pipe (pipe_fds) != 0)
406         {
407                 syslog (LOG_ERR, "dns plugin: pipe(2) failed: %s",
408                                 strerror (errno));
409                 return;
410         }
411
412         /* Fork off child */
413         pid_child = fork ();
414         if (pid_child < 0)
415         {
416                 syslog (LOG_ERR, "dns plugin: fork(2) failed: %s",
417                                 strerror (errno));
418                 close (pipe_fds[0]);
419                 close (pipe_fds[1]);
420                 return;
421         }
422         else if (pid_child != 0)
423         {
424                 /* parent: Close the writing end, keep the reading end. */
425                 pipe_fd = pipe_fds[0];
426                 close (pipe_fds[1]);
427         }
428         else
429         {
430                 /* child: Close the reading end, keep the writing end. */
431                 pipe_fd = pipe_fds[1];
432                 close (pipe_fds[0]);
433
434                 dns_child_loop ();
435                 exit (0);
436         }
437
438         /* fcntl (pipe_fd, F_SETFL, O_NONBLOCK); */
439 #endif
440 }
441
442 static void qtype_write (char *host, char *inst, char *val)
443 {
444         char file[512];
445         int status;
446
447         status = snprintf (file, 512, qtype_file, inst);
448         if (status < 1)
449                 return;
450         else if (status >= 512)
451                 return;
452
453         rrd_update_file (host, file, val, qtype_ds_def, qtype_ds_num);
454 }
455
456 static void rcode_write (char *host, char *inst, char *val)
457 {
458         char file[512];
459         int status;
460
461         status = snprintf (file, 512, rcode_file, inst);
462         if (status < 1)
463                 return;
464         else if (status >= 512)
465                 return;
466
467         rrd_update_file (host, file, val, rcode_ds_def, rcode_ds_num);
468 }
469
470 static void opcode_write (char *host, char *inst, char *val)
471 {
472         char file[512];
473         int status;
474
475         status = snprintf (file, 512, opcode_file, inst);
476         if (status < 1)
477                 return;
478         else if (status >= 512)
479                 return;
480
481         rrd_update_file (host, file, val, opcode_ds_def, opcode_ds_num);
482 }
483
484 static void qtype_submit (int qtype, unsigned int counter)
485 {
486         char inst[32];
487         char buffer[32];
488         int  status;
489
490         strncpy (inst, qtype_str (qtype), 32);
491         inst[31] = '\0';
492
493         status = snprintf (buffer, 32, "N:%u", counter);
494         if ((status < 1) || (status >= 32))
495                 return;
496
497         plugin_submit ("dns_qtype", inst, buffer);
498 }
499
500 static void rcode_submit (int rcode, unsigned int counter)
501 {
502         char inst[32];
503         char buffer[32];
504         int  status;
505
506         strncpy (inst, rcode_str (rcode), 32);
507         inst[31] = '\0';
508
509         status = snprintf (buffer, 32, "N:%u", counter);
510         if ((status < 1) || (status >= 32))
511                 return;
512
513         plugin_submit ("dns_rcode", inst, buffer);
514 }
515
516 static void opcode_submit (int opcode, unsigned int counter)
517 {
518         char inst[32];
519         char buffer[32];
520         int  status;
521
522         strncpy (inst, opcode_str (opcode), 32);
523         inst[31] = '\0';
524
525         status = snprintf (buffer, 32, "N:%u", counter);
526         if ((status < 1) || (status >= 32))
527                 return;
528
529         plugin_submit ("dns_opcode", inst, buffer);
530 }
531
532 #if NAMED_HAVE_READ
533 static unsigned int dns_read_array (unsigned int *values)
534 {
535         unsigned int values_num;
536
537         if (pipe_fd < 0)
538                 return (0);
539
540         if (sread (pipe_fd, (void *) &values_num, sizeof (values_num)) != 0)
541         {
542                 DBG ("Reading from the pipe failed: %s",
543                                 strerror (errno));
544                 syslog (LOG_ERR, "dns plugin: Reading from the pipe failed: %s",
545                                 strerror (errno));
546                 pipe_fd = -1;
547                 return (0);
548         }
549         DBG ("sread (pipe_fd = %i, values_num = %u)", pipe_fd, values_num);
550
551         assert (values_num <= T_MAX);
552
553         if (values_num == 0)
554                 return (0);
555
556         if (sread (pipe_fd, (void *) values, 2 * sizeof (unsigned int) * values_num) != 0)
557         {
558                 DBG ("Reading from the pipe failed: %s",
559                                 strerror (errno));
560                 syslog (LOG_ERR, "dns plugin: Reading from the pipe failed: %s",
561                                 strerror (errno));
562                 pipe_fd = -1;
563                 return (0);
564         }
565
566         return (values_num);
567 }
568
569 static void dns_read (void)
570 {
571         unsigned int values[2 * T_MAX];
572         unsigned int values_num;
573         int i;
574
575         if (pipe_fd < 0)
576                 return;
577
578         if (sread (pipe_fd, (void *) &tr_queries, sizeof (tr_queries)) != 0)
579         {
580                 DBG ("Reading from the pipe failed: %s",
581                                 strerror (errno));
582                 syslog (LOG_ERR, "dns plugin: Reading from the pipe failed: %s",
583                                 strerror (errno));
584                 pipe_fd = -1;
585                 return;
586         }
587         DBG ("sread (pipe_fd = %i, tr_queries = %u)", pipe_fd, tr_queries);
588
589         if (sread (pipe_fd, (void *) &tr_responses, sizeof (tr_responses)) != 0)
590         {
591                 DBG ("Reading from the pipe failed: %s",
592                                 strerror (errno));
593                 syslog (LOG_ERR, "dns plugin: Reading from the pipe failed: %s",
594                                 strerror (errno));
595                 pipe_fd = -1;
596                 return;
597         }
598         DBG ("sread (pipe_fd = %i, tr_responses = %u)", pipe_fd, tr_responses);
599
600         values_num = dns_read_array (values);
601         for (i = 0; i < values_num; i++)
602         {
603                 DBG ("qtype = %u; counter = %u;", values[2 * i], values[(2 * i) + 1]);
604                 qtype_submit (values[2 * i], values[(2 * i) + 1]);
605         }
606
607         values_num = dns_read_array (values);
608         for (i = 0; i < values_num; i++)
609         {
610                 DBG ("opcode = %u; counter = %u;", values[2 * i], values[(2 * i) + 1]);
611                 opcode_submit (values[2 * i], values[(2 * i) + 1]);
612         }
613
614         values_num = dns_read_array (values);
615         for (i = 0; i < values_num; i++)
616         {
617                 DBG ("rcode = %u; counter = %u;", values[2 * i], values[(2 * i) + 1]);
618                 rcode_submit (values[2 * i], values[(2 * i) + 1]);
619         }
620 }
621 #else /* if !NAMED_HAVE_READ */
622 # define dns_read NULL
623 #endif
624
625 void module_register (void)
626 {
627         plugin_register (MODULE_NAME, dns_init, dns_read, NULL);
628         plugin_register ("dns_qtype", NULL, NULL, qtype_write);
629         plugin_register ("dns_rcode", NULL, NULL, rcode_write);
630         plugin_register ("dns_opcode", NULL, NULL, opcode_write);
631         cf_register (MODULE_NAME, dns_config, config_keys, config_keys_num);
632 }
633
634 #undef MODULE_NAME