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

pdw committed
6
#include <sys/types.h>
pdw committed
7
#include <sys/socket.h>
pdw committed
8
#include <netinet/in.h>
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 committed
17 18

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

pdw committed
21 22
#include "threadprof.h"

23
#include "options.h"
pdw committed
24 25


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 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 committed
51

chris committed
52
/* 
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 committed
58 59
 */

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 committed
72
    char buf[NI_MAXHOST]; /* 1025 */
73
    int ret;
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 committed
100 101 102
}

#elif defined(USE_GETHOSTBYADDR_R)
pdw committed
103 104
/**
 * Implementation of do_resolve for platforms with working gethostbyaddr_r
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 committed
108
 */
109
char* do_resolve(struct addr_storage *addr) {
110
    struct hostent hostbuf, *hp = NULL;
pdw committed
111 112
    size_t hstbuflen = 1024;
    char *tmphstbuf;
113
    int res = 0;
pdw committed
114 115 116
    int herr;
    char * ret = NULL;

117
    /* Allocate buffer, remember to free it to avoid memory leakage. */
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 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 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 committed
134 135 136 137
            && errno == ERANGE)
#endif
            {
        
pdw committed
138 139 140 141
        /* Enlarge the buffer.  */
        hstbuflen *= 2;
        tmphstbuf = realloc (tmphstbuf, hstbuflen);
      }
142
    }
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 committed
157 158 159
#elif defined(USE_GETHOSTBYADDR)

/**
160
 * Implementation using gethostbyname. Since this is nonreentrant, we have to
chris committed
161 162
 * wrap it in a mutex, losing all benefit of multithreaded resolution.
 */
163
char *do_resolve(struct addr_storage *addr) {
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 committed
169 170 171 172 173 174
    if (he)
        s = xstrdup(he->h_name);
    pthread_mutex_unlock(&ghba_mtx);
    return s;
}

175

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

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

/**
pdw committed
182 183
 * libresolv implementation 
 * resolver functions may not be thread safe
pdw committed
184
 */
185
char* do_resolve(struct addr_storage *addr) {
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 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 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 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 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 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 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 committed
310 311 312
#elif defined(USE_FORKING_RESOLVER)

/**
pdw committed
313
 * Resolver which forks a process, then uses gethostbyname.
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 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 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 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 committed
359
        if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
pdw committed
360 361 362 363 364
            return NULL;

        workerinfo = xmalloc(sizeof *workerinfo);
        pthread_setspecific(worker_key, workerinfo);
        workerinfo->fd = p[0];
chris committed
365
        
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 committed
386
        wait(NULL);
pdw committed
387 388 389
        close(workerinfo->fd);
        xfree(workerinfo);
        pthread_setspecific(worker_key, NULL);
chris committed
390
        *name = 0;
pdw committed
391 392 393 394 395 396 397
    }
    if (!*name)
        return NULL;
    else
        return xstrdup(name);
}

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 committed
403 404 405
    return NULL;
}

pdw committed
406 407
#endif

pdw committed
408
void resolver_worker(void* ptr) {
chris committed
409
/*    int thread_number = *(int*)ptr;*/
410
    pthread_mutex_lock(&resolver_queue_mutex);
chris committed
411
    sethostent(1);
pdw committed
412 413
    while(1) {
        /* Wait until we are told that an address has been added to the 
chris committed
414
         * queue. */
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 committed
419
            char * hostname;
420
            struct addr_storage addr = resolve_queue[tail];
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 committed
428
            hostname = do_resolve(&addr);
pdw committed
429 430 431 432

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

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 committed
442
                    hash_delete(ns_hash, &addr);
pdw committed
443
                    xfree(old);
pdw committed
444
                }
445
                hash_insert(ns_hash, &addr.as_addr6, (void*)hostname);
pdw committed
446
            }
pdw committed
447

pdw committed
448 449 450 451 452
        }
    }
}

void resolver_initialise() {
453 454
    int* n;
    int i;
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 committed
464
        n = (int*)xmalloc(sizeof *n);
465 466 467
        *n = i;
        pthread_create(&thread, NULL, (void*)&resolver_worker, (void*)n);
    }
pdw committed
468 469 470

}

471
void resolve(int af, void* addr, char* result, int buflen) {
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 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 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 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 committed
515
        }
516
        pthread_mutex_unlock(&resolver_queue_mutex);
517

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

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