3 * Copyright (C) 2006-2011 Florian octo Forster
4 * Copyright (C) 2009 Mirko Buffoni
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.
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.
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
20 * Florian octo Forster <octo at collectd.org>
21 * Mirko Buffoni <briareos at eswat.org>
24 #define _DEFAULT_SOURCE
33 #include "utils_dns.h"
37 #ifdef HAVE_SYS_CAPABILITY_H
38 #include <sys/capability.h>
44 struct counter_list_s {
47 struct counter_list_s *next;
49 typedef struct counter_list_s counter_list_t;
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;
59 #define PCAP_SNAPLEN 1460
60 static char *pcap_device = NULL;
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;
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;
79 static counter_list_t *counter_list_search(counter_list_t **list,
81 counter_list_t *entry;
83 for (entry = *list; entry != NULL; entry = entry->next)
84 if (entry->key == key)
90 static counter_list_t *counter_list_create(counter_list_t **list,
93 counter_list_t *entry;
95 entry = calloc(1, sizeof(*entry));
100 entry->value = value;
105 counter_list_t *last;
108 while (last->next != NULL)
117 static void counter_list_add(counter_list_t **list, unsigned int key,
118 unsigned int increment) {
119 counter_list_t *entry;
121 entry = counter_list_search(list, key);
124 entry->value += increment;
126 counter_list_create(list, key, increment);
130 static int dns_config(const char *key, const char *value) {
131 if (strcasecmp(key, "Interface") == 0) {
132 if (pcap_device != NULL)
134 if ((pcap_device = strdup(value)) == NULL)
136 } else if (strcasecmp(key, "IgnoreSource") == 0) {
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;
143 select_numeric_qtype = 1;
151 static void dns_child_callback(const rfc1035_header_t *dns) {
153 /* This is a query */
155 if (!select_numeric_qtype) {
156 const char *str = qtype_str(dns->qtype);
157 if ((str == NULL) || (str[0] == '#'))
161 pthread_mutex_lock(&traffic_mutex);
162 tr_queries += dns->length;
163 pthread_mutex_unlock(&traffic_mutex);
166 pthread_mutex_lock(&qtype_mutex);
167 counter_list_add(&qtype_list, dns->qtype, 1);
168 pthread_mutex_unlock(&qtype_mutex);
171 /* This is a reply */
172 pthread_mutex_lock(&traffic_mutex);
173 tr_responses += dns->length;
174 pthread_mutex_unlock(&traffic_mutex);
176 pthread_mutex_lock(&rcode_mutex);
177 counter_list_add(&rcode_list, dns->rcode, 1);
178 pthread_mutex_unlock(&rcode_mutex);
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);
187 static int dns_run_pcap_loop(void) {
189 char pcap_error[PCAP_ERRBUF_SIZE];
190 struct bpf_program fp = {0};
194 /* Don't block any signals */
197 sigemptyset(&sigmask);
198 pthread_sigmask(SIG_SETMASK, &sigmask, NULL);
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),
207 if (pcap_obj == NULL) {
208 ERROR("dns plugin: Opening interface `%s' "
210 (pcap_device != NULL) ? pcap_device : "any", pcap_error);
214 status = pcap_compile(pcap_obj, &fp, "udp port 53", 1, 0);
216 ERROR("dns plugin: pcap_compile failed: %s", pcap_statustostr(status));
220 status = pcap_setfilter(pcap_obj, &fp);
222 ERROR("dns plugin: pcap_setfilter failed: %s", pcap_statustostr(status));
226 DEBUG("dns plugin: PCAP object created.");
228 dnstop_set_pcap_obj(pcap_obj);
229 dnstop_set_callback(dns_child_callback);
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;
239 pcap_close(pcap_obj);
241 } /* int dns_run_pcap_loop */
243 static int dns_sleep_one_interval(void) /* {{{ */
246 struct timespec ts = {0, 0};
249 interval = plugin_get_interval();
250 CDTIME_T_TO_TIMESPEC(interval, &ts);
253 struct timespec rem = {0, 0};
255 status = nanosleep(&ts, &rem);
258 else if ((errno == EINTR) || (errno == EAGAIN)) {
266 } /* }}} int dns_sleep_one_interval */
268 static void *dns_child_loop(__attribute__((unused)) void *dummy) /* {{{ */
273 status = dns_run_pcap_loop();
274 if (status != PCAP_ERROR_IFACE_NOT_UP)
277 dns_sleep_one_interval();
280 if (status != PCAP_ERROR_BREAK)
281 ERROR("dns plugin: PCAP returned error %s.", pcap_statustostr(status));
283 listen_thread_init = 0;
285 } /* }}} void *dns_child_loop */
287 static int dns_init(void) {
288 /* clean up an old thread */
291 pthread_mutex_lock(&traffic_mutex);
294 pthread_mutex_unlock(&traffic_mutex);
296 if (listen_thread_init != 0)
300 plugin_thread_create(&listen_thread, NULL, dns_child_loop, (void *)0);
303 ERROR("dns plugin: pthread_create failed: %s",
304 sstrerror(errno, errbuf, sizeof(errbuf)));
308 listen_thread_init = 1;
310 #if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_NET_RAW)
311 if (check_capability(CAP_NET_RAW) != 0) {
313 WARNING("dns plugin: Running collectd as root, but the CAP_NET_RAW "
314 "capability is missing. The plugin's read function will probably "
315 "fail. Is your init system dropping capabilities?");
317 WARNING("dns plugin: collectd doesn't have the CAP_NET_RAW capability. "
318 "If you don't want to run collectd as root, try running \"setcap "
319 "cap_net_raw=ep\" on the collectd binary.");
326 static void submit_derive(const char *type, const char *type_instance,
329 value_list_t vl = VALUE_LIST_INIT;
331 values[0].derive = value;
335 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
336 sstrncpy(vl.plugin, "dns", sizeof(vl.plugin));
337 sstrncpy(vl.type, type, sizeof(vl.type));
338 sstrncpy(vl.type_instance, type_instance, sizeof(vl.type_instance));
340 plugin_dispatch_values(&vl);
341 } /* void submit_derive */
343 static void submit_octets(derive_t queries, derive_t responses) {
345 value_list_t vl = VALUE_LIST_INIT;
347 values[0].derive = queries;
348 values[1].derive = responses;
352 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
353 sstrncpy(vl.plugin, "dns", sizeof(vl.plugin));
354 sstrncpy(vl.type, "dns_octets", sizeof(vl.type));
356 plugin_dispatch_values(&vl);
357 } /* void submit_octets */
359 static int dns_read(void) {
360 unsigned int keys[T_MAX];
361 unsigned int values[T_MAX];
366 pthread_mutex_lock(&traffic_mutex);
367 values[0] = tr_queries;
368 values[1] = tr_responses;
369 pthread_mutex_unlock(&traffic_mutex);
371 if ((values[0] != 0) || (values[1] != 0))
372 submit_octets(values[0], values[1]);
374 pthread_mutex_lock(&qtype_mutex);
375 for (ptr = qtype_list, len = 0; (ptr != NULL) && (len < T_MAX);
376 ptr = ptr->next, len++) {
377 keys[len] = ptr->key;
378 values[len] = ptr->value;
380 pthread_mutex_unlock(&qtype_mutex);
382 for (int i = 0; i < len; i++) {
383 DEBUG("dns plugin: qtype = %u; counter = %u;", keys[i], values[i]);
384 submit_derive("dns_qtype", qtype_str(keys[i]), values[i]);
387 pthread_mutex_lock(&opcode_mutex);
388 for (ptr = opcode_list, len = 0; (ptr != NULL) && (len < T_MAX);
389 ptr = ptr->next, len++) {
390 keys[len] = ptr->key;
391 values[len] = ptr->value;
393 pthread_mutex_unlock(&opcode_mutex);
395 for (int i = 0; i < len; i++) {
396 DEBUG("dns plugin: opcode = %u; counter = %u;", keys[i], values[i]);
397 submit_derive("dns_opcode", opcode_str(keys[i]), values[i]);
400 pthread_mutex_lock(&rcode_mutex);
401 for (ptr = rcode_list, len = 0; (ptr != NULL) && (len < T_MAX);
402 ptr = ptr->next, len++) {
403 keys[len] = ptr->key;
404 values[len] = ptr->value;
406 pthread_mutex_unlock(&rcode_mutex);
408 for (int i = 0; i < len; i++) {
409 DEBUG("dns plugin: rcode = %u; counter = %u;", keys[i], values[i]);
410 submit_derive("dns_rcode", rcode_str(keys[i]), values[i]);
416 void module_register(void) {
417 plugin_register_config("dns", dns_config, config_keys, config_keys_num);
418 plugin_register_init("dns", dns_init);
419 plugin_register_read("dns", dns_read);
420 } /* void module_register */