Merge branch 'collectd-5.7'
[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
29 #include "common.h"
30 #include "plugin.h"
31
32 #include <poll.h>
33 #include "utils_dns.h"
34
35 #include <pcap.h>
36
37 #ifdef HAVE_SYS_CAPABILITY_H
38 #include <sys/capability.h>
39 #endif
40
41 /*
42  * Private data types
43  */
44 struct counter_list_s {
45   unsigned int key;
46   unsigned int value;
47   struct counter_list_s *next;
48 };
49 typedef struct counter_list_s counter_list_t;
50
51 /*
52  * Private variables
53  */
54 static const char *config_keys[] = {"Interface", "IgnoreSource",
55                                     "SelectNumericQueryTypes"};
56 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
57 static int select_numeric_qtype = 1;
58
59 #define PCAP_SNAPLEN 1460
60 static char *pcap_device = NULL;
61
62 static derive_t tr_queries;
63 static derive_t tr_responses;
64 static counter_list_t *qtype_list;
65 static counter_list_t *opcode_list;
66 static counter_list_t *rcode_list;
67
68 static pthread_t listen_thread;
69 static int listen_thread_init = 0;
70 /* The `traffic' mutex if for `tr_queries' and `tr_responses' */
71 static pthread_mutex_t traffic_mutex = PTHREAD_MUTEX_INITIALIZER;
72 static pthread_mutex_t qtype_mutex = PTHREAD_MUTEX_INITIALIZER;
73 static pthread_mutex_t opcode_mutex = PTHREAD_MUTEX_INITIALIZER;
74 static pthread_mutex_t rcode_mutex = PTHREAD_MUTEX_INITIALIZER;
75
76 /*
77  * Private functions
78  */
79 static counter_list_t *counter_list_search(counter_list_t **list,
80                                            unsigned int key) {
81   counter_list_t *entry;
82
83   for (entry = *list; entry != NULL; entry = entry->next)
84     if (entry->key == key)
85       break;
86
87   return (entry);
88 }
89
90 static counter_list_t *counter_list_create(counter_list_t **list,
91                                            unsigned int key,
92                                            unsigned int value) {
93   counter_list_t *entry;
94
95   entry = calloc(1, sizeof(*entry));
96   if (entry == NULL)
97     return (NULL);
98
99   entry->key = key;
100   entry->value = value;
101
102   if (*list == NULL) {
103     *list = entry;
104   } else {
105     counter_list_t *last;
106
107     last = *list;
108     while (last->next != NULL)
109       last = last->next;
110
111     last->next = entry;
112   }
113
114   return (entry);
115 }
116
117 static void counter_list_add(counter_list_t **list, unsigned int key,
118                              unsigned int increment) {
119   counter_list_t *entry;
120
121   entry = counter_list_search(list, key);
122
123   if (entry != NULL) {
124     entry->value += increment;
125   } else {
126     counter_list_create(list, key, increment);
127   }
128 }
129
130 static int dns_config(const char *key, const char *value) {
131   if (strcasecmp(key, "Interface") == 0) {
132     if (pcap_device != NULL)
133       free(pcap_device);
134     if ((pcap_device = strdup(value)) == NULL)
135       return (1);
136   } else if (strcasecmp(key, "IgnoreSource") == 0) {
137     if (value != NULL)
138       ignore_list_add_name(value);
139   } else if (strcasecmp(key, "SelectNumericQueryTypes") == 0) {
140     if ((value != NULL) && IS_FALSE(value))
141       select_numeric_qtype = 0;
142     else
143       select_numeric_qtype = 1;
144   } else {
145     return (-1);
146   }
147
148   return (0);
149 }
150
151 static void dns_child_callback(const rfc1035_header_t *dns) {
152   if (dns->qr == 0) {
153     /* This is a query */
154     int skip = 0;
155     if (!select_numeric_qtype) {
156       const char *str = qtype_str(dns->qtype);
157       if ((str == NULL) || (str[0] == '#'))
158         skip = 1;
159     }
160
161     pthread_mutex_lock(&traffic_mutex);
162     tr_queries += dns->length;
163     pthread_mutex_unlock(&traffic_mutex);
164
165     if (skip == 0) {
166       pthread_mutex_lock(&qtype_mutex);
167       counter_list_add(&qtype_list, dns->qtype, 1);
168       pthread_mutex_unlock(&qtype_mutex);
169     }
170   } else {
171     /* This is a reply */
172     pthread_mutex_lock(&traffic_mutex);
173     tr_responses += dns->length;
174     pthread_mutex_unlock(&traffic_mutex);
175
176     pthread_mutex_lock(&rcode_mutex);
177     counter_list_add(&rcode_list, dns->rcode, 1);
178     pthread_mutex_unlock(&rcode_mutex);
179   }
180
181   /* FIXME: Are queries, replies or both interesting? */
182   pthread_mutex_lock(&opcode_mutex);
183   counter_list_add(&opcode_list, dns->opcode, 1);
184   pthread_mutex_unlock(&opcode_mutex);
185 }
186
187 static int dns_run_pcap_loop(void) {
188   pcap_t *pcap_obj;
189   char pcap_error[PCAP_ERRBUF_SIZE];
190   struct bpf_program fp = {0};
191
192   int status;
193
194   /* Don't block any signals */
195   {
196     sigset_t sigmask;
197     sigemptyset(&sigmask);
198     pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
199   }
200
201   /* Passing `pcap_device == NULL' is okay and the same as passign "any" */
202   DEBUG("dns plugin: Creating PCAP object..");
203   pcap_obj = pcap_open_live((pcap_device != NULL) ? pcap_device : "any",
204                             PCAP_SNAPLEN, 0 /* Not promiscuous */,
205                             (int)CDTIME_T_TO_MS(plugin_get_interval() / 2),
206                             pcap_error);
207   if (pcap_obj == NULL) {
208     ERROR("dns plugin: Opening interface `%s' "
209           "failed: %s",
210           (pcap_device != NULL) ? pcap_device : "any", pcap_error);
211     return (PCAP_ERROR);
212   }
213
214   status = pcap_compile(pcap_obj, &fp, "udp port 53", 1, 0);
215   if (status < 0) {
216     ERROR("dns plugin: pcap_compile failed: %s", pcap_statustostr(status));
217     return (status);
218   }
219
220   status = pcap_setfilter(pcap_obj, &fp);
221   if (status < 0) {
222     ERROR("dns plugin: pcap_setfilter failed: %s", pcap_statustostr(status));
223     return (status);
224   }
225
226   DEBUG("dns plugin: PCAP object created.");
227
228   dnstop_set_pcap_obj(pcap_obj);
229   dnstop_set_callback(dns_child_callback);
230
231   status = pcap_loop(pcap_obj, -1 /* loop forever */,
232                      handle_pcap /* callback */, NULL /* user data */);
233   INFO("dns plugin: pcap_loop exited with status %i.", status);
234   /* We need to handle "PCAP_ERROR" specially because libpcap currently
235    * doesn't return PCAP_ERROR_IFACE_NOT_UP for compatibility reasons. */
236   if (status == PCAP_ERROR)
237     status = PCAP_ERROR_IFACE_NOT_UP;
238
239   pcap_close(pcap_obj);
240   return (status);
241 } /* int dns_run_pcap_loop */
242
243 static int dns_sleep_one_interval(void) /* {{{ */
244 {
245   struct timespec ts = CDTIME_T_TO_TIMESPEC(plugin_get_interval());
246   while (nanosleep(&ts, &ts) != 0) {
247     if ((errno == EINTR) || (errno == EAGAIN))
248       continue;
249
250     return (errno);
251   }
252
253   return (0);
254 } /* }}} int dns_sleep_one_interval */
255
256 static void *dns_child_loop(__attribute__((unused)) void *dummy) /* {{{ */
257 {
258   int status;
259
260   while (42) {
261     status = dns_run_pcap_loop();
262     if (status != PCAP_ERROR_IFACE_NOT_UP)
263       break;
264
265     dns_sleep_one_interval();
266   }
267
268   if (status != PCAP_ERROR_BREAK)
269     ERROR("dns plugin: PCAP returned error %s.", pcap_statustostr(status));
270
271   listen_thread_init = 0;
272   return (NULL);
273 } /* }}} void *dns_child_loop */
274
275 static int dns_init(void) {
276   /* clean up an old thread */
277   int status;
278
279   pthread_mutex_lock(&traffic_mutex);
280   tr_queries = 0;
281   tr_responses = 0;
282   pthread_mutex_unlock(&traffic_mutex);
283
284   if (listen_thread_init != 0)
285     return (-1);
286
287   status = plugin_thread_create(&listen_thread, NULL, dns_child_loop, (void *)0,
288                                 "dns listen");
289   if (status != 0) {
290     char errbuf[1024];
291     ERROR("dns plugin: pthread_create failed: %s",
292           sstrerror(errno, errbuf, sizeof(errbuf)));
293     return (-1);
294   }
295
296   listen_thread_init = 1;
297
298 #if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_NET_RAW)
299   if (check_capability(CAP_NET_RAW) != 0) {
300     if (getuid() == 0)
301       WARNING("dns plugin: Running collectd as root, but the CAP_NET_RAW "
302               "capability is missing. The plugin's read function will probably "
303               "fail. Is your init system dropping capabilities?");
304     else
305       WARNING("dns plugin: collectd doesn't have the CAP_NET_RAW capability. "
306               "If you don't want to run collectd as root, try running \"setcap "
307               "cap_net_raw=ep\" on the collectd binary.");
308   }
309 #endif
310
311   return (0);
312 } /* int dns_init */
313
314 static void submit_derive(const char *type, const char *type_instance,
315                           derive_t value) {
316   value_list_t vl = VALUE_LIST_INIT;
317
318   vl.values = &(value_t){.derive = value};
319   vl.values_len = 1;
320   sstrncpy(vl.plugin, "dns", sizeof(vl.plugin));
321   sstrncpy(vl.type, type, sizeof(vl.type));
322   sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
323
324   plugin_dispatch_values(&vl);
325 } /* void submit_derive */
326
327 static void submit_octets(derive_t queries, derive_t responses) {
328   value_t values[] = {
329       {.derive = queries}, {.derive = responses},
330   };
331   value_list_t vl = VALUE_LIST_INIT;
332
333   vl.values = values;
334   vl.values_len = STATIC_ARRAY_SIZE(values);
335   sstrncpy(vl.plugin, "dns", sizeof(vl.plugin));
336   sstrncpy(vl.type, "dns_octets", sizeof(vl.type));
337
338   plugin_dispatch_values(&vl);
339 } /* void submit_octets */
340
341 static int dns_read(void) {
342   unsigned int keys[T_MAX];
343   unsigned int values[T_MAX];
344   int len;
345
346   counter_list_t *ptr;
347
348   pthread_mutex_lock(&traffic_mutex);
349   values[0] = tr_queries;
350   values[1] = tr_responses;
351   pthread_mutex_unlock(&traffic_mutex);
352
353   if ((values[0] != 0) || (values[1] != 0))
354     submit_octets(values[0], values[1]);
355
356   pthread_mutex_lock(&qtype_mutex);
357   for (ptr = qtype_list, len = 0; (ptr != NULL) && (len < T_MAX);
358        ptr = ptr->next, len++) {
359     keys[len] = ptr->key;
360     values[len] = ptr->value;
361   }
362   pthread_mutex_unlock(&qtype_mutex);
363
364   for (int i = 0; i < len; i++) {
365     DEBUG("dns plugin: qtype = %u; counter = %u;", keys[i], values[i]);
366     submit_derive("dns_qtype", qtype_str(keys[i]), values[i]);
367   }
368
369   pthread_mutex_lock(&opcode_mutex);
370   for (ptr = opcode_list, len = 0; (ptr != NULL) && (len < T_MAX);
371        ptr = ptr->next, len++) {
372     keys[len] = ptr->key;
373     values[len] = ptr->value;
374   }
375   pthread_mutex_unlock(&opcode_mutex);
376
377   for (int i = 0; i < len; i++) {
378     DEBUG("dns plugin: opcode = %u; counter = %u;", keys[i], values[i]);
379     submit_derive("dns_opcode", opcode_str(keys[i]), values[i]);
380   }
381
382   pthread_mutex_lock(&rcode_mutex);
383   for (ptr = rcode_list, len = 0; (ptr != NULL) && (len < T_MAX);
384        ptr = ptr->next, len++) {
385     keys[len] = ptr->key;
386     values[len] = ptr->value;
387   }
388   pthread_mutex_unlock(&rcode_mutex);
389
390   for (int i = 0; i < len; i++) {
391     DEBUG("dns plugin: rcode = %u; counter = %u;", keys[i], values[i]);
392     submit_derive("dns_rcode", rcode_str(keys[i]), values[i]);
393   }
394
395   return (0);
396 } /* int dns_read */
397
398 void module_register(void) {
399   plugin_register_config("dns", dns_config, config_keys, config_keys_num);
400   plugin_register_init("dns", dns_init);
401   plugin_register_read("dns", dns_read);
402 } /* void module_register */