resolver.c 13.7 KB
Newer Older
pdw's avatar
pdw committed
1 2 3 4 5
/*
 * resolver.c:
 *
 */

pdw's avatar
pdw committed
6
#include <sys/types.h>
pdw's avatar
pdw committed
7
#include <sys/socket.h>
pdw's avatar
pdw committed
8
#include <netinet/in.h>
pdw's avatar
pdw committed
9 10 11 12 13 14 15
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
16
#include <unistd.h>
pdw's avatar
pdw committed
17 18

#include "ns_hash.h"
chris's avatar
chris committed
19
#include "iftop.h"
pdw's avatar
pdw committed
20

pdw's avatar
pdw committed
21 22
#include "threadprof.h"

23
#include "options.h"
pdw's avatar
pdw committed
24 25


pdw's avatar
pdw committed
26 27
#define RESOLVE_QUEUE_LENGTH 20

28 29 30 31 32 33 34 35 36 37 38 39
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];
pdw's avatar
pdw committed
40 41 42 43 44 45 46 47 48

pthread_cond_t resolver_queue_cond;
pthread_mutex_t resolver_queue_mutex;

hash_type* ns_hash;

int head;
int tail;

49 50
extern options_t options;

pdw's avatar
pdw committed
51

chris's avatar
chris committed
52
/* 
chris's avatar
chris committed
53 54 55 56 57
 * 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.
chris's avatar
chris committed
58 59
 */

chris's avatar
chris committed
60 61 62 63 64 65 66 67 68
#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.
 */
69
char *do_resolve(struct addr_storage *addr) {
70 71
    struct sockaddr_in sin;
    struct sockaddr_in6 sin6;
chris's avatar
chris committed
72
    char buf[NI_MAXHOST]; /* 1025 */
73
    int ret;
pdw's avatar
pdw committed
74

75
    switch (addr->af) {
76
        case AF_INET:
77
            sin.sin_family = addr->af;
78
            sin.sin_port = 0;
79
            memcpy(&sin.sin_addr, &addr->as_addr4, addr->len);
80

81 82
            ret = getnameinfo((struct sockaddr*)&sin, sizeof sin,
                              buf, sizeof buf, NULL, 0, NI_NAMEREQD);
83 84
            break;
        case AF_INET6:
85
            sin6.sin6_family = addr->af;
86
            sin6.sin6_port = 0;
87
            memcpy(&sin6.sin6_addr, &addr->as_addr6, addr->len);
88

89 90 91
            ret = getnameinfo((struct sockaddr*)&sin6, sizeof sin6,
                              buf, sizeof buf, NULL, 0, NI_NAMEREQD);
	    break;
92 93 94
        default:
            return NULL;
    }
95 96 97 98 99

    if (ret == 0)
        return xstrdup(buf);
    else
        return NULL;
chris's avatar
chris committed
100 101 102
}

#elif defined(USE_GETHOSTBYADDR_R)
pdw's avatar
pdw committed
103 104
/**
 * Implementation of do_resolve for platforms with working gethostbyaddr_r
pdw's avatar
pdw committed
105 106 107
 *
 * Some implementations of libc choose to implement gethostbyaddr_r as
 * a non thread-safe wrapper to gethostbyaddr.  An interesting choice...
pdw's avatar
pdw committed
108
 */
109
char* do_resolve(struct addr_storage *addr) {
110
    struct hostent hostbuf, *hp = NULL;
pdw's avatar
pdw committed
111 112
    size_t hstbuflen = 1024;
    char *tmphstbuf;
113
    int res = 0;
pdw's avatar
pdw committed
114 115 116
    int herr;
    char * ret = NULL;

117
    /* Allocate buffer, remember to free it to avoid memory leakage. */
pdw's avatar
pdw committed
118 119
    tmphstbuf = xmalloc (hstbuflen);

120 121 122 123
    /* nss-myhostname's gethostbyaddr_r() causes an assertion failure if an
     * "invalid" (as in outside of IPv4 or IPv6) address family is passed */
    if (addr->af == AF_INET || addr->af == AF_INET6) {

chris's avatar
chris committed
124 125 126
    /* Some machines have gethostbyaddr_r returning an integer error code; on
     * others, it returns a struct hostent*. */
#ifdef GETHOSTBYADDR_R_RETURNS_INT
127
    while ((res = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
chris's avatar
chris committed
128 129 130 131
                                  &hostbuf, tmphstbuf, hstbuflen,
                                  &hp, &herr)) == ERANGE)
#else
    /* ... also assume one fewer argument.... */
132 133
    while ((hp = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
                                 &hostbuf, tmphstbuf, hstbuflen, &herr)) == NULL
chris's avatar
chris committed
134 135 136 137
            && errno == ERANGE)
#endif
            {
        
pdw's avatar
pdw committed
138 139 140 141
        /* Enlarge the buffer.  */
        hstbuflen *= 2;
        tmphstbuf = realloc (tmphstbuf, hstbuflen);
      }
142
    }
pdw's avatar
pdw committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156

    /*  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;
}

chris's avatar
chris committed
157 158 159
#elif defined(USE_GETHOSTBYADDR)

/**
160
 * Implementation using gethostbyname. Since this is nonreentrant, we have to
chris's avatar
chris committed
161 162
 * wrap it in a mutex, losing all benefit of multithreaded resolution.
 */
163
char *do_resolve(struct addr_storage *addr) {
chris's avatar
chris committed
164 165 166 167
    static pthread_mutex_t ghba_mtx = PTHREAD_MUTEX_INITIALIZER;
    char *s = NULL;
    struct hostent *he;
    pthread_mutex_lock(&ghba_mtx);
168
    he = gethostbyaddr((char*)&addr->addr, addr->len, addr->af);
chris's avatar
chris committed
169 170 171 172 173 174
    if (he)
        s = xstrdup(he->h_name);
    pthread_mutex_unlock(&ghba_mtx);
    return s;
}

175

chris's avatar
chris committed
176 177 178 179
#elif defined(USE_LIBRESOLV)

#include <arpa/nameser.h>
#include <resolv.h>
pdw's avatar
pdw committed
180 181

/**
pdw's avatar
pdw committed
182 183
 * libresolv implementation 
 * resolver functions may not be thread safe
pdw's avatar
pdw committed
184
 */
185
char* do_resolve(struct addr_storage *addr) {
pdw's avatar
pdw committed
186 187 188 189 190 191
  char msg[PACKETSZ];
  char s[35];
  int l;
  unsigned char* a;
  char * ret = NULL;

192 193 194 195
  if (addr->af != AF_INET)
    return NULL;

  a = (unsigned char*)&addr->addr;
pdw's avatar
pdw committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

  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;
}
chris's avatar
chris committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

#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;
    }
}

253
char *do_resolve(struct addr_storage * addr) {
chris's avatar
chris committed
254 255 256 257 258 259 260 261
    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;

262 263 264
    if (addr->af != AF_INET)
        return NULL;

chris's avatar
chris committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    /* 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;
    }
    
281
    a = (unsigned char*)&addr->as_addr4;
chris's avatar
chris committed
282 283 284
    sprintf(s, "%d.%d.%d.%d.in-addr.arpa.", a[3], a[2], a[1], a[0]);
    
    C.result = 0;
285
    C.addr = &addr->as_addr4;
chris's avatar
chris committed
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
    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;
    }
}

pdw's avatar
pdw committed
310 311 312
#elif defined(USE_FORKING_RESOLVER)

/**
pdw's avatar
pdw committed
313
 * Resolver which forks a process, then uses gethostbyname.
pdw's avatar
pdw committed
314 315 316 317 318 319 320 321
 */

#include <signal.h>

#define NAMESIZE        64

int forking_resolver_worker(int fd) {
    while (1) {
322
        struct addr_storage a;
pdw's avatar
pdw committed
323 324 325 326 327
        struct hostent *he;
        char buf[NAMESIZE] = {0};
        if (read(fd, &a, sizeof a) != sizeof a)
            return -1;

328
        he = gethostbyaddr((char*)&a.addr, a.len, a.af);
pdw's avatar
pdw committed
329 330 331 332 333 334 335 336
        if (he)
            strncpy(buf, he->h_name, NAMESIZE - 1);

        if (write(fd, buf, NAMESIZE) != NAMESIZE)
            return -1;
    }
}

337
char *do_resolve(struct in6_addr *addr) {
pdw's avatar
pdw committed
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
    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];

chris's avatar
chris committed
359
        if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
pdw's avatar
pdw committed
360 361 362 363 364
            return NULL;

        workerinfo = xmalloc(sizeof *workerinfo);
        pthread_setspecific(worker_key, workerinfo);
        workerinfo->fd = p[0];
chris's avatar
chris committed
365
        
pdw's avatar
pdw committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
        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);
chris's avatar
chris committed
386
        wait(NULL);
pdw's avatar
pdw committed
387 388 389
        close(workerinfo->fd);
        xfree(workerinfo);
        pthread_setspecific(worker_key, NULL);
chris's avatar
chris committed
390
        *name = 0;
pdw's avatar
pdw committed
391 392 393 394 395 396 397
    }
    if (!*name)
        return NULL;
    else
        return xstrdup(name);
}

chris's avatar
chris committed
398 399 400 401
#else

#   warning No name resolution method specified; name resolution will not work

402
char *do_resolve(struct addr_storage *addr) {
chris's avatar
chris committed
403 404 405
    return NULL;
}

pdw's avatar
pdw committed
406 407
#endif

pdw's avatar
pdw committed
408
void resolver_worker(void* ptr) {
chris's avatar
chris committed
409
/*    int thread_number = *(int*)ptr;*/
410
    pthread_mutex_lock(&resolver_queue_mutex);
chris's avatar
chris committed
411
    sethostent(1);
pdw's avatar
pdw committed
412 413
    while(1) {
        /* Wait until we are told that an address has been added to the 
chris's avatar
chris committed
414
         * queue. */
pdw's avatar
pdw committed
415 416 417 418
        pthread_cond_wait(&resolver_queue_cond, &resolver_queue_mutex);

        /* Keep resolving until the queue is empty */
        while(head != tail) {
pdw's avatar
pdw committed
419
            char * hostname;
420
            struct addr_storage addr = resolve_queue[tail];
pdw's avatar
pdw committed
421 422 423 424 425

            /* mutex always locked at this point */

            tail = (tail + 1) % RESOLVE_QUEUE_LENGTH;

426 427
            pthread_mutex_unlock(&resolver_queue_mutex);

pdw's avatar
pdw committed
428
            hostname = do_resolve(&addr);
pdw's avatar
pdw committed
429 430 431 432

            /*
             * Store the result in ns_hash
             */
433
            pthread_mutex_lock(&resolver_queue_mutex);
pdw's avatar
pdw committed
434

pdw's avatar
pdw committed
435 436
            if(hostname != NULL) {
                char* old;
437 438 439 440
		union {
		    char **ch_pp;
		    void **void_pp;
		} u_old = { &old };
441
                if(hash_find(ns_hash, &addr.as_addr6, u_old.void_pp) == HASH_STATUS_OK) {
pdw's avatar
pdw committed
442
                    hash_delete(ns_hash, &addr);
pdw's avatar
pdw committed
443
                    xfree(old);
pdw's avatar
pdw committed
444
                }
445
                hash_insert(ns_hash, &addr.as_addr6, (void*)hostname);
pdw's avatar
pdw committed
446
            }
pdw's avatar
pdw committed
447

pdw's avatar
pdw committed
448 449 450 451 452
        }
    }
}

void resolver_initialise() {
453 454
    int* n;
    int i;
pdw's avatar
pdw committed
455 456 457 458 459 460 461 462
    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);

463
    for(i = 0; i < 2; i++) {
chris's avatar
chris committed
464
        n = (int*)xmalloc(sizeof *n);
465 466 467
        *n = i;
        pthread_create(&thread, NULL, (void*)&resolver_worker, (void*)n);
    }
pdw's avatar
pdw committed
468 469 470

}

471
void resolve(int af, void* addr, char* result, int buflen) {
pdw's avatar
pdw committed
472
    char* hostname;
473 474 475 476
    union {
	char **ch_pp;
	void **void_pp;
    } u_hostname = { &hostname };
477
    int added = 0;
478 479
    struct addr_storage *raddr;

480
    if(options.dnsresolution == 1) {
pdw's avatar
pdw committed
481

482 483 484 485 486 487 488
        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);

489
        pthread_mutex_lock(&resolver_queue_mutex);
pdw's avatar
pdw committed
490

491
        if(hash_find(ns_hash, &raddr->as_addr6, u_hostname.void_pp) == HASH_STATUS_OK) {
492 493 494
            /* Found => already resolved, or on the queue, no need to keep
	     * it around */
            free(raddr);
pdw's avatar
pdw committed
495 496
        }
        else {
497
            hostname = xmalloc(INET6_ADDRSTRLEN);
498 499
            inet_ntop(af, &raddr->addr, hostname, INET6_ADDRSTRLEN);

500
            hash_insert(ns_hash, &raddr->as_addr6, hostname);
501 502 503 504

            if(((head + 1) % RESOLVE_QUEUE_LENGTH) == tail) {
                /* queue full */
            }
505 506 507
            else if ((af == AF_INET6)
                     && (IN6_IS_ADDR_LINKLOCAL(&raddr->as_addr6)
                         || IN6_IS_ADDR_SITELOCAL(&raddr->as_addr6))) {
508 509
                /* Link-local and site-local stay numerical. */
            }
510
            else {
511
                resolve_queue[head] = *raddr;
512 513 514
                head = (head + 1) % RESOLVE_QUEUE_LENGTH;
                added = 1;
            }
pdw's avatar
pdw committed
515
        }
516
        pthread_mutex_unlock(&resolver_queue_mutex);
517

518 519 520
        if(added == 1) {
            pthread_cond_signal(&resolver_queue_cond);
        }
pdw's avatar
pdw committed
521

522 523 524 525
        if(result != NULL && buflen > 1) {
            strncpy(result, hostname, buflen - 1);
            result[buflen - 1] = '\0';
        }
pdw's avatar
pdw committed
526 527
    }
}