Merge pull request #3339 from jkohen/patch-1
[collectd.git] / src / dns.c
1 /**
2  * collectd - src/dns.c
3  * Copyright (C) 2006-2011  Florian octo Forster
4  * Copyright (C) 2009       Mirko Buffoni
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
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 collectd.org>
21  *   Mirko Buffoni <briareos at eswat.org>
22  **/
23
24 #define _DEFAULT_SOURCE
25 #define _BSD_SOURCE
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "plugin.h"
30 #include "configfile.h"
31
32 #include "utils_dns.h"
33 #include <pthread.h>
34 #include <poll.h>
35
36 #include <pcap.h>
37 #include <pcap-bpf.h>
38
39 /*
40  * Private data types
41  */
42 struct counter_list_s
43 {
44         unsigned int key;
45         unsigned int value;
46         struct counter_list_s *next;
47 };
48 typedef struct counter_list_s counter_list_t;
49
50 /*
51  * Private variables
52  */
53 static const char *config_keys[] =
54 {
55         "Interface",
56         "IgnoreSource",
57         "SelectNumericQueryTypes"
58 };
59 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
60 static int select_numeric_qtype = 1;
61
62 #define PCAP_SNAPLEN 1460
63 static char   *pcap_device = NULL;
64
65 static derive_t       tr_queries;
66 static derive_t       tr_responses;
67 static counter_list_t *qtype_list;
68 static counter_list_t *opcode_list;
69 static counter_list_t *rcode_list;
70
71 static pthread_t       listen_thread;
72 static int             listen_thread_init = 0;
73 /* The `traffic' mutex if for `tr_queries' and `tr_responses' */
74 static pthread_mutex_t traffic_mutex = PTHREAD_MUTEX_INITIALIZER;
75 static pthread_mutex_t qtype_mutex   = PTHREAD_MUTEX_INITIALIZER;
76 static pthread_mutex_t opcode_mutex  = PTHREAD_MUTEX_INITIALIZER;
77 static pthread_mutex_t rcode_mutex   = PTHREAD_MUTEX_INITIALIZER;
78
79 /*
80  * Private functions
81  */
82 static counter_list_t *counter_list_search (counter_list_t **list, unsigned int key)
83 {
84         counter_list_t *entry;
85
86         for (entry = *list; entry != NULL; entry = entry->next)
87                 if (entry->key == key)
88                         break;
89
90         return (entry);
91 }
92
93 static counter_list_t *counter_list_create (counter_list_t **list,
94                 unsigned int key, unsigned int value)
95 {
96         counter_list_t *entry;
97
98         entry = (counter_list_t *) malloc (sizeof (counter_list_t));
99         if (entry == NULL)
100                 return (NULL);
101
102         memset (entry, 0, sizeof (counter_list_t));
103         entry->key = key;
104         entry->value = value;
105
106         if (*list == NULL)
107         {
108                 *list = entry;
109         }
110         else
111         {
112                 counter_list_t *last;
113
114                 last = *list;
115                 while (last->next != NULL)
116                         last = last->next;
117
118                 last->next = entry;
119         }
120
121         return (entry);
122 }
123
124 static void counter_list_add (counter_list_t **list,
125                 unsigned int key, unsigned int increment)
126 {
127         counter_list_t *entry;
128
129         entry = counter_list_search (list, key);
130
131         if (entry != NULL)
132         {
133                 entry->value += increment;
134         }
135         else
136         {
137                 counter_list_create (list, key, increment);
138         }
139 }
140
141 static int dns_config (const char *key, const char *value)
142 {
143         if (strcasecmp (key, "Interface") == 0)
144         {
145                 if (pcap_device != NULL)
146                         free (pcap_device);
147                 if ((pcap_device = strdup (value)) == NULL)
148                         return (1);
149         }
150         else if (strcasecmp (key, "IgnoreSource") == 0)
151         {
152                 if (value != NULL)
153                         ignore_list_add_name (value);
154         }
155         else if (strcasecmp (key, "SelectNumericQueryTypes") == 0)
156         {
157                 if ((value != NULL) && IS_FALSE (value))
158                         select_numeric_qtype = 0;
159                 else
160                         select_numeric_qtype = 1;
161         }
162         else
163         {
164                 return (-1);
165         }
166
167         return (0);
168 }
169
170 static void dns_child_callback (const rfc1035_header_t *dns)
171 {
172         if (dns->qr == 0)
173         {
174                 /* This is a query */
175                 int skip = 0;
176                 if (!select_numeric_qtype)
177                 {
178                         const char *str = qtype_str(dns->qtype);
179                         if ((str == NULL) || (str[0] == '#'))
180                                 skip = 1;
181                 }
182
183                 pthread_mutex_lock (&traffic_mutex);
184                 tr_queries += dns->length;
185                 pthread_mutex_unlock (&traffic_mutex);
186
187                 if (skip == 0)
188                 {
189                         pthread_mutex_lock (&qtype_mutex);
190                         counter_list_add (&qtype_list, dns->qtype,  1);
191                         pthread_mutex_unlock (&qtype_mutex);
192                 }
193         }
194         else
195         {
196                 /* This is a reply */
197                 pthread_mutex_lock (&traffic_mutex);
198                 tr_responses += dns->length;
199                 pthread_mutex_unlock (&traffic_mutex);
200
201                 pthread_mutex_lock (&rcode_mutex);
202                 counter_list_add (&rcode_list,  dns->rcode,  1);
203                 pthread_mutex_unlock (&rcode_mutex);
204         }
205
206         /* FIXME: Are queries, replies or both interesting? */
207         pthread_mutex_lock (&opcode_mutex);
208         counter_list_add (&opcode_list, dns->opcode, 1);
209         pthread_mutex_unlock (&opcode_mutex);
210 }
211
212 static int dns_run_pcap_loop (void)
213 {
214         pcap_t *pcap_obj;
215         char    pcap_error[PCAP_ERRBUF_SIZE];
216         struct  bpf_program fp;
217
218         int status;
219
220         /* Don't block any signals */
221         {
222                 sigset_t sigmask;
223                 sigemptyset (&sigmask);
224                 pthread_sigmask (SIG_SETMASK, &sigmask, NULL);
225         }
226
227         /* Passing `pcap_device == NULL' is okay and the same as passign "any" */
228         DEBUG ("dns plugin: Creating PCAP object..");
229         pcap_obj = pcap_open_live ((pcap_device != NULL) ? pcap_device : "any",
230                         PCAP_SNAPLEN,
231                         0 /* Not promiscuous */,
232                         (int) CDTIME_T_TO_MS (plugin_get_interval () / 2),
233                         pcap_error);
234         if (pcap_obj == NULL)
235         {
236                 ERROR ("dns plugin: Opening interface `%s' "
237                                 "failed: %s",
238                                 (pcap_device != NULL) ? pcap_device : "any",
239                                 pcap_error);
240                 return (PCAP_ERROR);
241         }
242
243         memset (&fp, 0, sizeof (fp));
244         status = pcap_compile (pcap_obj, &fp, "udp port 53", 1, 0);
245         if (status < 0)
246         {
247                 ERROR ("dns plugin: pcap_compile failed: %s",
248                                 pcap_statustostr (status));
249                 return (status);
250         }
251
252         status = pcap_setfilter (pcap_obj, &fp);
253         if (status < 0)
254         {
255                 ERROR ("dns plugin: pcap_setfilter failed: %s",
256                                 pcap_statustostr (status));
257                 return (status);
258         }
259
260         DEBUG ("dns plugin: PCAP object created.");
261
262         dnstop_set_pcap_obj (pcap_obj);
263         dnstop_set_callback (dns_child_callback);
264
265         status = pcap_loop (pcap_obj,
266                         -1 /* loop forever */,
267                         handle_pcap /* callback */,
268                         NULL /* user data */);
269         INFO ("dns plugin: pcap_loop exited with status %i.", status);
270         /* We need to handle "PCAP_ERROR" specially because libpcap currently
271          * doesn't return PCAP_ERROR_IFACE_NOT_UP for compatibility reasons. */
272         if (status == PCAP_ERROR)
273                 status = PCAP_ERROR_IFACE_NOT_UP;
274
275         pcap_close (pcap_obj);
276         return (status);
277 } /* int dns_run_pcap_loop */
278
279 static int dns_sleep_one_interval (void) /* {{{ */
280 {
281         cdtime_t interval;
282         struct timespec ts = { 0, 0 };
283         int status = 0;
284
285         interval = plugin_get_interval ();
286         CDTIME_T_TO_TIMESPEC (interval, &ts);
287
288         while (42)
289         {
290                 struct timespec rem = { 0, 0 };
291
292                 status = nanosleep (&ts, &rem);
293                 if (status == 0)
294                         break;
295                 else if ((errno == EINTR) || (errno == EAGAIN))
296                 {
297                         ts = rem;
298                         continue;
299                 }
300                 else
301                         break;
302         }
303
304         return (status);
305 } /* }}} int dns_sleep_one_interval */
306
307 static void *dns_child_loop (__attribute__((unused)) void *dummy) /* {{{ */
308 {
309         int status;
310
311         while (42)
312         {
313                 status = dns_run_pcap_loop ();
314                 if (status != PCAP_ERROR_IFACE_NOT_UP)
315                         break;
316
317                 dns_sleep_one_interval ();
318         }
319
320         if (status != PCAP_ERROR_BREAK)
321                 ERROR ("dns plugin: PCAP returned error %s.",
322                                 pcap_statustostr (status));
323
324         listen_thread_init = 0;
325         return (NULL);
326 } /* }}} void *dns_child_loop */
327
328 static int dns_init (void)
329 {
330         /* clean up an old thread */
331         int status;
332
333         pthread_mutex_lock (&traffic_mutex);
334         tr_queries   = 0;
335         tr_responses = 0;
336         pthread_mutex_unlock (&traffic_mutex);
337
338         if (listen_thread_init != 0)
339                 return (-1);
340
341         status = plugin_thread_create (&listen_thread, NULL, dns_child_loop,
342                         (void *) 0);
343         if (status != 0)
344         {
345                 char errbuf[1024];
346                 ERROR ("dns plugin: pthread_create failed: %s",
347                                 sstrerror (errno, errbuf, sizeof (errbuf)));
348                 return (-1);
349         }
350
351         listen_thread_init = 1;
352
353         return (0);
354 } /* int dns_init */
355
356 static void submit_derive (const char *type, const char *type_instance,
357                 derive_t value)
358 {
359         value_t values[1];
360         value_list_t vl = VALUE_LIST_INIT;
361
362         values[0].derive = value;
363
364         vl.values = values;
365         vl.values_len = 1;
366         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
367         sstrncpy (vl.plugin, "dns", sizeof (vl.plugin));
368         sstrncpy (vl.type, type, sizeof (vl.type));
369         sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
370
371         plugin_dispatch_values (&vl);
372 } /* void submit_derive */
373
374 static void submit_octets (derive_t queries, derive_t responses)
375 {
376         value_t values[2];
377         value_list_t vl = VALUE_LIST_INIT;
378
379         values[0].derive = queries;
380         values[1].derive = responses;
381
382         vl.values = values;
383         vl.values_len = 2;
384         sstrncpy (vl.host, hostname_g, sizeof (vl.host));
385         sstrncpy (vl.plugin, "dns", sizeof (vl.plugin));
386         sstrncpy (vl.type, "dns_octets", sizeof (vl.type));
387
388         plugin_dispatch_values (&vl);
389 } /* void submit_octets */
390
391 static int dns_read (void)
392 {
393         unsigned int keys[T_MAX];
394         unsigned int values[T_MAX];
395         int len;
396         int i;
397
398         counter_list_t *ptr;
399
400         pthread_mutex_lock (&traffic_mutex);
401         values[0] = tr_queries;
402         values[1] = tr_responses;
403         pthread_mutex_unlock (&traffic_mutex);
404
405         if ((values[0] != 0) || (values[1] != 0))
406                 submit_octets (values[0], values[1]);
407
408         pthread_mutex_lock (&qtype_mutex);
409         for (ptr = qtype_list, len = 0;
410                         (ptr != NULL) && (len < T_MAX);
411                         ptr = ptr->next, len++)
412         {
413                 keys[len]   = ptr->key;
414                 values[len] = ptr->value;
415         }
416         pthread_mutex_unlock (&qtype_mutex);
417
418         for (i = 0; i < len; i++)
419         {
420                 DEBUG ("dns plugin: qtype = %u; counter = %u;", keys[i], values[i]);
421                 submit_derive ("dns_qtype", qtype_str (keys[i]), values[i]);
422         }
423
424         pthread_mutex_lock (&opcode_mutex);
425         for (ptr = opcode_list, len = 0;
426                         (ptr != NULL) && (len < T_MAX);
427                         ptr = ptr->next, len++)
428         {
429                 keys[len]   = ptr->key;
430                 values[len] = ptr->value;
431         }
432         pthread_mutex_unlock (&opcode_mutex);
433
434         for (i = 0; i < len; i++)
435         {
436                 DEBUG ("dns plugin: opcode = %u; counter = %u;", keys[i], values[i]);
437                 submit_derive ("dns_opcode", opcode_str (keys[i]), values[i]);
438         }
439
440         pthread_mutex_lock (&rcode_mutex);
441         for (ptr = rcode_list, len = 0;
442                         (ptr != NULL) && (len < T_MAX);
443                         ptr = ptr->next, len++)
444         {
445                 keys[len]   = ptr->key;
446                 values[len] = ptr->value;
447         }
448         pthread_mutex_unlock (&rcode_mutex);
449
450         for (i = 0; i < len; i++)
451         {
452                 DEBUG ("dns plugin: rcode = %u; counter = %u;", keys[i], values[i]);
453                 submit_derive ("dns_rcode", rcode_str (keys[i]), values[i]);
454         }
455
456         return (0);
457 } /* int dns_read */
458
459 void module_register (void)
460 {
461         plugin_register_config ("dns", dns_config, config_keys, config_keys_num);
462         plugin_register_init ("dns", dns_init);
463         plugin_register_read ("dns", dns_read);
464 } /* void module_register */