summaryrefslogtreecommitdiff
path: root/resolver.c
diff options
context:
space:
mode:
Diffstat (limited to 'resolver.c')
-rw-r--r--resolver.c522
1 files changed, 522 insertions, 0 deletions
diff --git a/resolver.c b/resolver.c
new file mode 100644
index 0000000..c09de9d
--- /dev/null
+++ b/resolver.c
@@ -0,0 +1,522 @@
+/*
+ * resolver.c:
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ns_hash.h"
+#include "iftop.h"
+
+#include "threadprof.h"
+
+#include "options.h"
+
+
+#define RESOLVE_QUEUE_LENGTH 20
+
+struct addr_storage {
+ int af; /* AF_INET or AF_INET6 */
+ int len; /* sizeof(struct in_addr or in6_addr) */
+ union {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ } addr;
+#define as_addr4 addr.addr4
+#define as_addr6 addr.addr6
+};
+
+struct addr_storage resolve_queue[RESOLVE_QUEUE_LENGTH];
+
+pthread_cond_t resolver_queue_cond;
+pthread_mutex_t resolver_queue_mutex;
+
+hash_type* ns_hash;
+
+int head;
+int tail;
+
+extern options_t options;
+
+
+/*
+ * We have a choice of resolver methods. Real computers have getnameinfo or
+ * gethostbyaddr_r, which are reentrant and therefore thread safe. Other
+ * machines don't, and so we can use non-reentrant gethostbyaddr and have only
+ * one resolver thread. Alternatively, we can use the MIT ares asynchronous
+ * DNS library to do this.
+ */
+
+#if defined(USE_GETNAMEINFO)
+/**
+ * Implementation of do_resolve for platforms with getaddrinfo.
+ *
+ * This is a fairly sane function with a uniform interface which is even --
+ * shock! -- standardised by POSIX and in RFC 2553. Unfortunately systems such
+ * as NetBSD break the RFC and implement it in a non-thread-safe fashion, so
+ * for the moment, the configure script won't try to use it.
+ */
+char *do_resolve(struct addr_storage *addr) {
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ char buf[NI_MAXHOST]; /* 1025 */
+ int ret;
+
+ switch (addr->af) {
+ case AF_INET:
+ sin.sin_family = addr->af;
+ sin.sin_port = 0;
+ memcpy(&sin.sin_addr, &addr->as_addr4, addr->len);
+
+ ret = getnameinfo((struct sockaddr*)&sin, sizeof sin,
+ buf, sizeof buf, NULL, 0, NI_NAMEREQD);
+ break;
+ case AF_INET6:
+ sin6.sin6_family = addr->af;
+ sin6.sin6_port = 0;
+ memcpy(&sin6.sin6_addr, &addr->as_addr6, addr->len);
+
+ ret = getnameinfo((struct sockaddr*)&sin6, sizeof sin6,
+ buf, sizeof buf, NULL, 0, NI_NAMEREQD);
+ break;
+ default:
+ return NULL;
+ }
+
+ if (ret == 0)
+ return xstrdup(buf);
+ else
+ return NULL;
+}
+
+#elif defined(USE_GETHOSTBYADDR_R)
+/**
+ * Implementation of do_resolve for platforms with working gethostbyaddr_r
+ *
+ * Some implementations of libc choose to implement gethostbyaddr_r as
+ * a non thread-safe wrapper to gethostbyaddr. An interesting choice...
+ */
+char* do_resolve(struct addr_storage *addr) {
+ struct hostent hostbuf, *hp;
+ size_t hstbuflen = 1024;
+ char *tmphstbuf;
+ int res;
+ int herr;
+ char * ret = NULL;
+
+ /* Allocate buffer, remember to free it to avoid memory leakage. */
+ tmphstbuf = xmalloc (hstbuflen);
+
+ /* Some machines have gethostbyaddr_r returning an integer error code; on
+ * others, it returns a struct hostent*. */
+#ifdef GETHOSTBYADDR_R_RETURNS_INT
+ while ((res = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
+ &hostbuf, tmphstbuf, hstbuflen,
+ &hp, &herr)) == ERANGE)
+#else
+ /* ... also assume one fewer argument.... */
+ while ((hp = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
+ &hostbuf, tmphstbuf, hstbuflen, &herr)) == NULL
+ && errno == ERANGE)
+#endif
+ {
+
+ /* Enlarge the buffer. */
+ hstbuflen *= 2;
+ tmphstbuf = realloc (tmphstbuf, hstbuflen);
+ }
+
+ /* Check for errors. */
+ if (res || hp == NULL) {
+ /* failed */
+ /* Leave the unresolved IP in the hash */
+ }
+ else {
+ ret = xstrdup(hp->h_name);
+
+ }
+ xfree(tmphstbuf);
+ return ret;
+}
+
+#elif defined(USE_GETHOSTBYADDR)
+
+/**
+ * Implementation using gethostbyname. Since this is nonreentrant, we have to
+ * wrap it in a mutex, losing all benefit of multithreaded resolution.
+ */
+char *do_resolve(struct addr_storage *addr) {
+ static pthread_mutex_t ghba_mtx = PTHREAD_MUTEX_INITIALIZER;
+ char *s = NULL;
+ struct hostent *he;
+ pthread_mutex_lock(&ghba_mtx);
+ he = gethostbyaddr((char*)&addr->addr, addr->len, addr->af);
+ if (he)
+ s = xstrdup(he->h_name);
+ pthread_mutex_unlock(&ghba_mtx);
+ return s;
+}
+
+
+#elif defined(USE_LIBRESOLV)
+
+#include <arpa/nameser.h>
+#include <resolv.h>
+
+/**
+ * libresolv implementation
+ * resolver functions may not be thread safe
+ */
+char* do_resolve(struct addr_storage *addr) {
+ char msg[PACKETSZ];
+ char s[35];
+ int l;
+ unsigned char* a;
+ char * ret = NULL;
+
+ if (addr->af != AF_INET)
+ return NULL;
+
+ a = (unsigned char*)&addr->addr;
+
+ snprintf(s, 35, "%d.%d.%d.%d.in-addr.arpa.",a[3], a[2], a[1], a[0]);
+
+ l = res_search(s, C_IN, T_PTR, msg, PACKETSZ);
+ if(l != -1) {
+ ns_msg nsmsg;
+ ns_rr rr;
+ if(ns_initparse(msg, l, &nsmsg) != -1) {
+ int c;
+ int i;
+ c = ns_msg_count(nsmsg, ns_s_an);
+ for(i = 0; i < c; i++) {
+ if(ns_parserr(&nsmsg, ns_s_an, i, &rr) == 0){
+ if(ns_rr_type(rr) == T_PTR) {
+ char buf[256];
+ ns_name_uncompress(msg, msg + l, ns_rr_rdata(rr), buf, 256);
+ ret = xstrdup(buf);
+ }
+ }
+ }
+ }
+ }
+ return ret;
+}
+
+#elif defined(USE_ARES)
+
+/**
+ * ares implementation
+ */
+
+#include <sys/time.h>
+#include <ares.h>
+#include <arpa/nameser.h>
+
+/* callback function for ares */
+struct ares_callback_comm {
+ struct in_addr *addr;
+ int result;
+ char *name;
+};
+
+static void do_resolve_ares_callback(void *arg, int status, unsigned char *abuf, int alen) {
+ struct hostent *he;
+ struct ares_callback_comm *C;
+ C = (struct ares_callback_comm*)arg;
+
+ if (status == ARES_SUCCESS) {
+ C->result = 1;
+ ares_parse_ptr_reply(abuf, alen, C->addr, sizeof *C->addr, AF_INET, &he);
+ C->name = xstrdup(he->h_name);;
+ ares_free_hostent(he);
+ } else {
+ C->result = -1;
+ }
+}
+
+char *do_resolve(struct addr_storage * addr) {
+ struct ares_callback_comm C;
+ char s[35];
+ unsigned char *a;
+ ares_channel *chan;
+ static pthread_mutex_t ares_init_mtx = PTHREAD_MUTEX_INITIALIZER;
+ static pthread_key_t ares_key;
+ static int gotkey;
+
+ if (addr->af != AF_INET)
+ return NULL;
+
+ /* Make sure we have an ARES channel for this thread. */
+ pthread_mutex_lock(&ares_init_mtx);
+ if (!gotkey) {
+ pthread_key_create(&ares_key, NULL);
+ gotkey = 1;
+
+ }
+ pthread_mutex_unlock(&ares_init_mtx);
+
+ chan = pthread_getspecific(ares_key);
+ if (!chan) {
+ chan = xmalloc(sizeof *chan);
+ pthread_setspecific(ares_key, chan);
+ if (ares_init(chan) != ARES_SUCCESS) return NULL;
+ }
+
+ a = (unsigned char*)&addr->as_addr4;
+ sprintf(s, "%d.%d.%d.%d.in-addr.arpa.", a[3], a[2], a[1], a[0]);
+
+ C.result = 0;
+ C.addr = &addr->as_addr4;
+ ares_query(*chan, s, C_IN, T_PTR, do_resolve_ares_callback, &C);
+ while (C.result == 0) {
+ int n;
+ fd_set readfds, writefds;
+ struct timeval tv;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ n = ares_fds(*chan, &readfds, &writefds);
+ ares_timeout(*chan, NULL, &tv);
+ select(n, &readfds, &writefds, NULL, &tv);
+ ares_process(*chan, &readfds, &writefds);
+ }
+
+ /* At this stage, the query should be complete. */
+ switch (C.result) {
+ case -1:
+ case 0: /* shouldn't happen */
+ return NULL;
+
+ default:
+ return C.name;
+ }
+}
+
+#elif defined(USE_FORKING_RESOLVER)
+
+/**
+ * Resolver which forks a process, then uses gethostbyname.
+ */
+
+#include <signal.h>
+
+#define NAMESIZE 64
+
+int forking_resolver_worker(int fd) {
+ while (1) {
+ struct addr_storage a;
+ struct hostent *he;
+ char buf[NAMESIZE] = {0};
+ if (read(fd, &a, sizeof a) != sizeof a)
+ return -1;
+
+ he = gethostbyaddr((char*)&a.addr, a.len, a.af);
+ if (he)
+ strncpy(buf, he->h_name, NAMESIZE - 1);
+
+ if (write(fd, buf, NAMESIZE) != NAMESIZE)
+ return -1;
+ }
+}
+
+char *do_resolve(struct in6_addr *addr) {
+ struct {
+ int fd;
+ pid_t child;
+ } *workerinfo;
+ char name[NAMESIZE];
+ static pthread_mutex_t worker_init_mtx = PTHREAD_MUTEX_INITIALIZER;
+ static pthread_key_t worker_key;
+ static int gotkey;
+
+ /* If no process exists, we need to spawn one. */
+ pthread_mutex_lock(&worker_init_mtx);
+ if (!gotkey) {
+ pthread_key_create(&worker_key, NULL);
+ gotkey = 1;
+ }
+ pthread_mutex_unlock(&worker_init_mtx);
+
+ workerinfo = pthread_getspecific(worker_key);
+ if (!workerinfo) {
+ int p[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
+ return NULL;
+
+ workerinfo = xmalloc(sizeof *workerinfo);
+ pthread_setspecific(worker_key, workerinfo);
+ workerinfo->fd = p[0];
+
+ switch (workerinfo->child = fork()) {
+ case 0:
+ close(p[0]);
+ _exit(forking_resolver_worker(p[1]));
+
+ case -1:
+ close(p[0]);
+ close(p[1]);
+ return NULL;
+
+ default:
+ close(p[1]);
+ }
+ }
+
+ /* Now have a worker to which we can write requests. */
+ if (write(workerinfo->fd, addr, sizeof *addr) != sizeof *addr
+ || read(workerinfo->fd, name, NAMESIZE) != NAMESIZE) {
+ /* Something went wrong. Just kill the child and get on with it. */
+ kill(workerinfo->child, SIGKILL);
+ wait(NULL);
+ close(workerinfo->fd);
+ xfree(workerinfo);
+ pthread_setspecific(worker_key, NULL);
+ *name = 0;
+ }
+ if (!*name)
+ return NULL;
+ else
+ return xstrdup(name);
+}
+
+#else
+
+# warning No name resolution method specified; name resolution will not work
+
+char *do_resolve(struct addr_storage *addr) {
+ return NULL;
+}
+
+#endif
+
+void resolver_worker(void* ptr) {
+/* int thread_number = *(int*)ptr;*/
+ pthread_mutex_lock(&resolver_queue_mutex);
+ sethostent(1);
+ while(1) {
+ /* Wait until we are told that an address has been added to the
+ * queue. */
+ pthread_cond_wait(&resolver_queue_cond, &resolver_queue_mutex);
+
+ /* Keep resolving until the queue is empty */
+ while(head != tail) {
+ char * hostname;
+ struct addr_storage addr = resolve_queue[tail];
+
+ /* mutex always locked at this point */
+
+ tail = (tail + 1) % RESOLVE_QUEUE_LENGTH;
+
+ pthread_mutex_unlock(&resolver_queue_mutex);
+
+ hostname = do_resolve(&addr);
+
+ /*
+ * Store the result in ns_hash
+ */
+ pthread_mutex_lock(&resolver_queue_mutex);
+
+ if(hostname != NULL) {
+ char* old;
+ union {
+ char **ch_pp;
+ void **void_pp;
+ } u_old = { &old };
+ if(hash_find(ns_hash, &addr, u_old.void_pp) == HASH_STATUS_OK) {
+ hash_delete(ns_hash, &addr);
+ xfree(old);
+ }
+ hash_insert(ns_hash, &addr, (void*)hostname);
+ }
+
+ }
+ }
+}
+
+void resolver_initialise() {
+ int* n;
+ int i;
+ pthread_t thread;
+ head = tail = 0;
+
+ ns_hash = ns_hash_create();
+
+ pthread_mutex_init(&resolver_queue_mutex, NULL);
+ pthread_cond_init(&resolver_queue_cond, NULL);
+
+ for(i = 0; i < 2; i++) {
+ n = (int*)xmalloc(sizeof *n);
+ *n = i;
+ pthread_create(&thread, NULL, (void*)&resolver_worker, (void*)n);
+ }
+
+}
+
+void resolve(int af, void* addr, char* result, int buflen) {
+ char* hostname;
+ union {
+ char **ch_pp;
+ void **void_pp;
+ } u_hostname = { &hostname };
+ int added = 0;
+ struct addr_storage *raddr;
+
+ raddr = malloc(sizeof *raddr);
+ memset(raddr, 0, sizeof *raddr);
+ raddr->af = af;
+ raddr->len = (af == AF_INET ? sizeof(struct in_addr)
+ : sizeof(struct in6_addr));
+ memcpy(&raddr->addr, addr, raddr->len);
+
+ if(options.dnsresolution == 1) {
+
+ pthread_mutex_lock(&resolver_queue_mutex);
+
+ if(hash_find(ns_hash, raddr, u_hostname.void_pp) == HASH_STATUS_OK) {
+ /* Found => already resolved, or on the queue, no need to keep
+ * it around */
+ free(raddr);
+ }
+ else {
+ hostname = xmalloc(INET6_ADDRSTRLEN);
+ inet_ntop(af, &raddr->addr, hostname, INET6_ADDRSTRLEN);
+
+ hash_insert(ns_hash, raddr, hostname);
+
+ if(((head + 1) % RESOLVE_QUEUE_LENGTH) == tail) {
+ /* queue full */
+ }
+ else if ((af == AF_INET6)
+ && (IN6_IS_ADDR_LINKLOCAL(&raddr->as_addr6)
+ || IN6_IS_ADDR_SITELOCAL(&raddr->as_addr6))) {
+ /* Link-local and site-local stay numerical. */
+ }
+ else {
+ resolve_queue[head] = *raddr;
+ head = (head + 1) % RESOLVE_QUEUE_LENGTH;
+ added = 1;
+ }
+ }
+ pthread_mutex_unlock(&resolver_queue_mutex);
+
+ if(added == 1) {
+ pthread_cond_signal(&resolver_queue_cond);
+ }
+
+ if(result != NULL && buflen > 1) {
+ strncpy(result, hostname, buflen - 1);
+ result[buflen - 1] = '\0';
+ }
+ }
+}