Merge branches 'ff/dns' and 'sh/email' into next
[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 #include "utils_dns.h"
29
30 #define MODULE_NAME "dns"
31
32 #if HAVE_LIBPCAP && HAVE_LIBPTHREAD
33 # include <pthread.h>
34 # include <pcap.h>
35 # include <sys/poll.h>
36 # define DNS_HAVE_READ 1
37 #else
38 # define DNS_HAVE_READ 0
39 #endif
40
41 /*
42  * Private data types
43  */
44 #if DNS_HAVE_READ
45 struct counter_list_s
46 {
47         unsigned int key;
48         unsigned int value;
49         struct counter_list_s *next;
50 };
51 typedef struct counter_list_s counter_list_t;
52 #endif
53
54 /*
55  * Private variables
56  */
57 static char *traffic_file   = "dns/dns_traffic.rrd";
58 static char *qtype_file   = "dns/qtype-%s.rrd";
59 static char *opcode_file  = "dns/opcode-%s.rrd";
60 static char *rcode_file   = "dns/rcode-%s.rrd";
61
62 static char *traffic_ds_def[] =
63 {
64         /* Limit to 1GBit/s */
65         "DS:queries:COUNTER:"COLLECTD_HEARTBEAT":0:125000000",
66         "DS:responses:COUNTER:"COLLECTD_HEARTBEAT":0:125000000",
67         NULL
68 };
69 static int traffic_ds_num = 2;
70
71 static char *qtype_ds_def[] =
72 {
73         "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535",
74         NULL
75 };
76 static int qtype_ds_num = 1;
77
78 static char *opcode_ds_def[] =
79 {
80         "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535",
81         NULL
82 };
83 static int opcode_ds_num = 1;
84
85 static char *rcode_ds_def[] =
86 {
87         "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535",
88         NULL
89 };
90 static int rcode_ds_num = 1;
91
92 #if DNS_HAVE_READ
93 static char *config_keys[] =
94 {
95         "Interface",
96         "IgnoreSource",
97         NULL
98 };
99 static int config_keys_num = 2;
100
101 #define PCAP_SNAPLEN 1460
102 static char   *pcap_device = NULL;
103
104 static unsigned int    tr_queries;
105 static unsigned int    tr_responses;
106 static counter_list_t *qtype_list;
107 static counter_list_t *opcode_list;
108 static counter_list_t *rcode_list;
109
110 static pthread_t       listen_thread;
111 static int             listen_thread_init = 0;
112 /* The `traffic' mutex if for `tr_queries' and `tr_responses' */
113 static pthread_mutex_t traffic_mutex = PTHREAD_MUTEX_INITIALIZER;
114 static pthread_mutex_t qtype_mutex   = PTHREAD_MUTEX_INITIALIZER;
115 static pthread_mutex_t opcode_mutex  = PTHREAD_MUTEX_INITIALIZER;
116 static pthread_mutex_t rcode_mutex   = PTHREAD_MUTEX_INITIALIZER;
117 #endif /* DNS_HAVE_READ */
118
119 /*
120  * Private functions
121  */
122 #if DNS_HAVE_READ
123 static counter_list_t *counter_list_search (counter_list_t **list, unsigned int key)
124 {
125         counter_list_t *entry;
126
127         DBG ("counter_list_search (list = %p, key = %u)",
128                         (void *) *list, key);
129
130         for (entry = *list; entry != NULL; entry = entry->next)
131                 if (entry->key == key)
132                         break;
133
134         DBG ("return (%p)", (void *) entry);
135         return (entry);
136 }
137
138 static counter_list_t *counter_list_create (counter_list_t **list,
139                 unsigned int key, unsigned int value)
140 {
141         counter_list_t *entry;
142
143         DBG ("counter_list_create (list = %p, key = %u, value = %u)",
144                         (void *) *list, key, value);
145
146         entry = (counter_list_t *) malloc (sizeof (counter_list_t));
147         if (entry == NULL)
148                 return (NULL);
149
150         memset (entry, 0, sizeof (counter_list_t));
151         entry->key = key;
152         entry->value = value;
153
154         if (*list == NULL)
155         {
156                 *list = entry;
157         }
158         else
159         {
160                 counter_list_t *last;
161
162                 last = *list;
163                 while (last->next != NULL)
164                         last = last->next;
165
166                 last->next = entry;
167         }
168
169         DBG ("return (%p)", (void *) entry);
170         return (entry);
171 }
172
173 static void counter_list_add (counter_list_t **list,
174                 unsigned int key, unsigned int increment)
175 {
176         counter_list_t *entry;
177
178         DBG ("counter_list_add (list = %p, key = %u, increment = %u)",
179                         (void *) *list, key, increment);
180
181         entry = counter_list_search (list, key);
182
183         if (entry != NULL)
184         {
185                 entry->value += increment;
186         }
187         else
188         {
189                 counter_list_create (list, key, increment);
190         }
191         DBG ("return ()");
192 }
193
194 static int dns_config (char *key, char *value)
195 {
196         if (strcasecmp (key, "Interface") == 0)
197         {
198                 if (pcap_device != NULL)
199                         free (pcap_device);
200                 if ((pcap_device = strdup (value)) == NULL)
201                         return (1);
202         }
203         else if (strcasecmp (key, "IgnoreSource") == 0)
204         {
205                 if (value != NULL)
206                         ignore_list_add_name (value);
207         }
208         else
209         {
210                 return (-1);
211         }
212
213         return (0);
214 }
215
216 static void dns_child_callback (const rfc1035_header_t *dns)
217 {
218         if (dns->qr == 0)
219         {
220                 /* This is a query */
221                 pthread_mutex_lock (&traffic_mutex);
222                 tr_queries += dns->length;
223                 pthread_mutex_unlock (&traffic_mutex);
224
225                 pthread_mutex_lock (&qtype_mutex);
226                 counter_list_add (&qtype_list,  dns->qtype,  1);
227                 pthread_mutex_unlock (&qtype_mutex);
228         }
229         else
230         {
231                 /* This is a reply */
232                 pthread_mutex_lock (&traffic_mutex);
233                 tr_responses += dns->length;
234                 pthread_mutex_unlock (&traffic_mutex);
235
236                 pthread_mutex_lock (&rcode_mutex);
237                 counter_list_add (&rcode_list,  dns->rcode,  1);
238                 pthread_mutex_unlock (&rcode_mutex);
239         }
240
241         /* FIXME: Are queries, replies or both interesting? */
242         pthread_mutex_lock (&opcode_mutex);
243         counter_list_add (&opcode_list, dns->opcode, 1);
244         pthread_mutex_unlock (&opcode_mutex);
245 }
246
247 static void *dns_child_loop (void *dummy)
248 {
249         pcap_t *pcap_obj;
250         char    pcap_error[PCAP_ERRBUF_SIZE];
251         struct  bpf_program fp;
252
253         struct pollfd poll_fds[1];
254         int status;
255
256         /* Don't block any signals */
257         {
258                 sigset_t sigmask;
259                 sigemptyset (&sigmask);
260                 pthread_sigmask (SIG_SETMASK, &sigmask, NULL);
261         }
262
263         /* Passing `pcap_device == NULL' is okay and the same as passign "any" */
264         DBG ("Creating PCAP object..");
265         pcap_obj = pcap_open_live (pcap_device,
266                         PCAP_SNAPLEN,
267                         0 /* Not promiscuous */,
268                         0 /* no read timeout */,
269                         pcap_error);
270         if (pcap_obj == NULL)
271         {
272                 syslog (LOG_ERR, "dns plugin: Opening interface `%s' "
273                                 "failed: %s",
274                                 (pcap_device != NULL) ? pcap_device : "any",
275                                 pcap_error);
276                 return (NULL);
277         }
278
279         memset (&fp, 0, sizeof (fp));
280         if (pcap_compile (pcap_obj, &fp, "udp port 53", 1, 0) < 0)
281         {
282                 DBG ("pcap_compile failed");
283                 syslog (LOG_ERR, "dns plugin: pcap_compile failed");
284                 return (NULL);
285         }
286         if (pcap_setfilter (pcap_obj, &fp) < 0)
287         {
288                 DBG ("pcap_setfilter failed");
289                 syslog (LOG_ERR, "dns plugin: pcap_setfilter failed");
290                 return (NULL);
291         }
292
293         DBG ("PCAP object created.");
294
295         dnstop_set_pcap_obj (pcap_obj);
296         dnstop_set_callback (dns_child_callback);
297
298         /* Set up poll object */
299         poll_fds[0].fd = pcap_fileno (pcap_obj);
300         poll_fds[0].events = POLLIN | POLLPRI;
301
302         while (42)
303         {
304                 DBG ("poll (...)");
305                 status = poll (poll_fds, 1, -1 /* wait forever for a change */);
306
307                 /* Signals are not caught, but this is very handy when
308                  * attaching to the process with a debugger. -octo */
309                 if ((status < 0) && (errno == EINTR))
310                 {
311                         errno = 0;
312                         continue;
313                 }
314
315                 if (status < 0)
316                 {
317                         syslog (LOG_ERR, "dns plugin: poll(2) failed: %s",
318                                         strerror (errno));
319                         break;
320                 }
321
322                 if (poll_fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
323                 {
324                         DBG ("pcap-device closed. Exiting.");
325                         syslog (LOG_ERR, "dns plugin: pcap-device closed. Exiting.");
326                         break;
327                 }
328                 else if (poll_fds[0].revents & (POLLIN | POLLPRI))
329                 {
330                         status = pcap_dispatch (pcap_obj,
331                                         10 /* Only handle 10 packets at a time */,
332                                         handle_pcap /* callback */,
333                                         NULL /* Whatever this means.. */);
334                         if (status < 0)
335                         {
336                                 DBG ("pcap_dispatch failed: %s", pcap_geterr (pcap_obj));
337                                 syslog (LOG_ERR, "dns plugin: pcap_dispatch failed: %s",
338                                                 pcap_geterr (pcap_obj));
339                                 break;
340                         }
341                 }
342         } /* while (42) */
343
344         DBG ("child is exiting");
345
346         pcap_close (pcap_obj);
347         pthread_exit (NULL);
348
349         return (NULL);
350 } /* static void dns_child_loop (void) */
351 #endif /* DNS_HAVE_READ */
352
353 static void dns_init (void)
354 {
355 #if DNS_HAVE_READ
356         /* clean up an old thread */
357         int status;
358
359         pthread_mutex_lock (&traffic_mutex);
360         tr_queries   = 0;
361         tr_responses = 0;
362         pthread_mutex_unlock (&traffic_mutex);
363
364         if (listen_thread_init != 0)
365                 return;
366
367         status = pthread_create (&listen_thread, NULL, dns_child_loop,
368                         (void *) 0);
369         if (status != 0)
370         {
371                 syslog (LOG_ERR, "dns plugin: pthread_create failed: %s",
372                                 strerror (status));
373                 return;
374         }
375
376         listen_thread_init = 1;
377 #endif /* DNS_HAVE_READ */
378 }
379
380 static void traffic_write (char *host, char *inst, char *val)
381 {
382         rrd_update_file (host, traffic_file, val,
383                         traffic_ds_def, traffic_ds_num);
384 }
385
386 static void qtype_write (char *host, char *inst, char *val)
387 {
388         char file[512];
389         int status;
390
391         status = snprintf (file, 512, qtype_file, inst);
392         if (status < 1)
393                 return;
394         else if (status >= 512)
395                 return;
396
397         rrd_update_file (host, file, val, qtype_ds_def, qtype_ds_num);
398 }
399
400 static void rcode_write (char *host, char *inst, char *val)
401 {
402         char file[512];
403         int status;
404
405         status = snprintf (file, 512, rcode_file, inst);
406         if (status < 1)
407                 return;
408         else if (status >= 512)
409                 return;
410
411         rrd_update_file (host, file, val, rcode_ds_def, rcode_ds_num);
412 }
413
414 static void opcode_write (char *host, char *inst, char *val)
415 {
416         char file[512];
417         int status;
418
419         status = snprintf (file, 512, opcode_file, inst);
420         if (status < 1)
421                 return;
422         else if (status >= 512)
423                 return;
424
425         rrd_update_file (host, file, val, opcode_ds_def, opcode_ds_num);
426 }
427
428 #if DNS_HAVE_READ
429 static void traffic_submit (unsigned int queries, unsigned int replies)
430 {
431         char buffer[64];
432         int  status;
433
434         status = snprintf (buffer, 64, "N:%u:%u", queries, replies);
435         if ((status < 1) || (status >= 64))
436                 return;
437
438         plugin_submit ("dns_traffic", "-", buffer);
439 }
440
441 static void qtype_submit (int qtype, unsigned int counter)
442 {
443         char inst[32];
444         char buffer[32];
445         int  status;
446
447         strncpy (inst, qtype_str (qtype), 32);
448         inst[31] = '\0';
449
450         status = snprintf (buffer, 32, "N:%u", counter);
451         if ((status < 1) || (status >= 32))
452                 return;
453
454         plugin_submit ("dns_qtype", inst, buffer);
455 }
456
457 static void rcode_submit (int rcode, unsigned int counter)
458 {
459         char inst[32];
460         char buffer[32];
461         int  status;
462
463         strncpy (inst, rcode_str (rcode), 32);
464         inst[31] = '\0';
465
466         status = snprintf (buffer, 32, "N:%u", counter);
467         if ((status < 1) || (status >= 32))
468                 return;
469
470         plugin_submit ("dns_rcode", inst, buffer);
471 }
472
473 static void opcode_submit (int opcode, unsigned int counter)
474 {
475         char inst[32];
476         char buffer[32];
477         int  status;
478
479         strncpy (inst, opcode_str (opcode), 32);
480         inst[31] = '\0';
481
482         status = snprintf (buffer, 32, "N:%u", counter);
483         if ((status < 1) || (status >= 32))
484                 return;
485
486         plugin_submit ("dns_opcode", inst, buffer);
487 }
488
489 static void dns_read (void)
490 {
491         unsigned int keys[T_MAX];
492         unsigned int values[T_MAX];
493         int len;
494         int i;
495
496         counter_list_t *ptr;
497
498         pthread_mutex_lock (&traffic_mutex);
499         values[0] = tr_queries;
500         values[1] = tr_responses;
501         pthread_mutex_unlock (&traffic_mutex);
502         traffic_submit (values[0], values[1]);
503
504         pthread_mutex_lock (&qtype_mutex);
505         for (ptr = qtype_list, len = 0;
506                         (ptr != NULL) && (len < T_MAX);
507                         ptr = ptr->next, len++)
508         {
509                 keys[len]   = ptr->key;
510                 values[len] = ptr->value;
511         }
512         pthread_mutex_unlock (&qtype_mutex);
513
514         for (i = 0; i < len; i++)
515         {
516                 DBG ("qtype = %u; counter = %u;", keys[i], values[i]);
517                 qtype_submit (keys[i], values[i]);
518         }
519
520         pthread_mutex_lock (&opcode_mutex);
521         for (ptr = opcode_list, len = 0;
522                         (ptr != NULL) && (len < T_MAX);
523                         ptr = ptr->next, len++)
524         {
525                 keys[len]   = ptr->key;
526                 values[len] = ptr->value;
527         }
528         pthread_mutex_unlock (&opcode_mutex);
529
530         for (i = 0; i < len; i++)
531         {
532                 DBG ("opcode = %u; counter = %u;", keys[i], values[i]);
533                 opcode_submit (keys[i], values[i]);
534         }
535
536         pthread_mutex_lock (&rcode_mutex);
537         for (ptr = rcode_list, len = 0;
538                         (ptr != NULL) && (len < T_MAX);
539                         ptr = ptr->next, len++)
540         {
541                 keys[len]   = ptr->key;
542                 values[len] = ptr->value;
543         }
544         pthread_mutex_unlock (&rcode_mutex);
545
546         for (i = 0; i < len; i++)
547         {
548                 DBG ("rcode = %u; counter = %u;", keys[i], values[i]);
549                 rcode_submit (keys[i], values[i]);
550         }
551 }
552 #else /* if !DNS_HAVE_READ */
553 # define dns_read NULL
554 #endif
555
556 void module_register (void)
557 {
558         plugin_register (MODULE_NAME, dns_init, dns_read, NULL);
559         plugin_register ("dns_traffic", NULL, NULL, traffic_write);
560         plugin_register ("dns_qtype", NULL, NULL, qtype_write);
561         plugin_register ("dns_rcode", NULL, NULL, rcode_write);
562         plugin_register ("dns_opcode", NULL, NULL, opcode_write);
563 #if DNS_HAVE_READ
564         cf_register (MODULE_NAME, dns_config, config_keys, config_keys_num);
565 #endif
566 }
567
568 #undef MODULE_NAME