Import to git/gerrit
[profile/ivi/iftop.git] / resolver.c
1 /*
2  * resolver.c:
3  *
4  */
5
6 #include <sys/types.h>
7 #include <sys/socket.h>
8 #include <netinet/in.h>
9 #include <arpa/inet.h>
10 #include <pthread.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <netdb.h>
14 #include <errno.h>
15 #include <string.h>
16 #include <unistd.h>
17
18 #include "ns_hash.h"
19 #include "iftop.h"
20
21 #include "threadprof.h"
22
23 #include "options.h"
24
25
26 #define RESOLVE_QUEUE_LENGTH 20
27
28 struct addr_storage {
29     int af;                     /* AF_INET or AF_INET6 */
30     int len;                    /* sizeof(struct in_addr or in6_addr) */
31     union {
32         struct in_addr  addr4;
33         struct in6_addr addr6;
34     } addr;
35 #define as_addr4 addr.addr4
36 #define as_addr6 addr.addr6
37 };
38
39 struct addr_storage resolve_queue[RESOLVE_QUEUE_LENGTH];
40
41 pthread_cond_t resolver_queue_cond;
42 pthread_mutex_t resolver_queue_mutex;
43
44 hash_type* ns_hash;
45
46 int head;
47 int tail;
48
49 extern options_t options;
50
51
52 /* 
53  * We have a choice of resolver methods. Real computers have getnameinfo or
54  * gethostbyaddr_r, which are reentrant and therefore thread safe. Other
55  * machines don't, and so we can use non-reentrant gethostbyaddr and have only
56  * one resolver thread.  Alternatively, we can use the MIT ares asynchronous
57  * DNS library to do this.
58  */
59
60 #if defined(USE_GETNAMEINFO)
61 /**
62  * Implementation of do_resolve for platforms with getaddrinfo.
63  *
64  * This is a fairly sane function with a uniform interface which is even --
65  * shock! -- standardised by POSIX and in RFC 2553. Unfortunately systems such
66  * as NetBSD break the RFC and implement it in a non-thread-safe fashion, so
67  * for the moment, the configure script won't try to use it.
68  */
69 char *do_resolve(struct addr_storage *addr) {
70     struct sockaddr_in sin;
71     struct sockaddr_in6 sin6;
72     char buf[NI_MAXHOST]; /* 1025 */
73     int ret;
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             ret = getnameinfo((struct sockaddr*)&sin, sizeof sin,
82                               buf, sizeof buf, NULL, 0, NI_NAMEREQD);
83             break;
84         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             ret = getnameinfo((struct sockaddr*)&sin6, sizeof sin6,
90                               buf, sizeof buf, NULL, 0, NI_NAMEREQD);
91             break;
92         default:
93             return NULL;
94     }
95
96     if (ret == 0)
97         return xstrdup(buf);
98     else
99         return NULL;
100 }
101
102 #elif defined(USE_GETHOSTBYADDR_R)
103 /**
104  * Implementation of do_resolve for platforms with working gethostbyaddr_r
105  *
106  * Some implementations of libc choose to implement gethostbyaddr_r as
107  * a non thread-safe wrapper to gethostbyaddr.  An interesting choice...
108  */
109 char* do_resolve(struct addr_storage *addr) {
110     struct hostent hostbuf, *hp;
111     size_t hstbuflen = 1024;
112     char *tmphstbuf;
113     int res;
114     int herr;
115     char * ret = NULL;
116
117     /* Allocate buffer, remember to free it to avoid memory leakage. */
118     tmphstbuf = xmalloc (hstbuflen);
119
120     /* Some machines have gethostbyaddr_r returning an integer error code; on
121      * others, it returns a struct hostent*. */
122 #ifdef GETHOSTBYADDR_R_RETURNS_INT
123     while ((res = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
124                                   &hostbuf, tmphstbuf, hstbuflen,
125                                   &hp, &herr)) == ERANGE)
126 #else
127     /* ... also assume one fewer argument.... */
128     while ((hp = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
129                                  &hostbuf, tmphstbuf, hstbuflen, &herr)) == NULL
130             && errno == ERANGE)
131 #endif
132             {
133         
134         /* Enlarge the buffer.  */
135         hstbuflen *= 2;
136         tmphstbuf = realloc (tmphstbuf, hstbuflen);
137       }
138
139     /*  Check for errors.  */
140     if (res || hp == NULL) {
141         /* failed */
142         /* Leave the unresolved IP in the hash */
143     }
144     else {
145         ret = xstrdup(hp->h_name);
146
147     }
148     xfree(tmphstbuf);
149     return ret;
150 }
151
152 #elif defined(USE_GETHOSTBYADDR)
153
154 /**
155  * Implementation using gethostbyname. Since this is nonreentrant, we have to
156  * wrap it in a mutex, losing all benefit of multithreaded resolution.
157  */
158 char *do_resolve(struct addr_storage *addr) {
159     static pthread_mutex_t ghba_mtx = PTHREAD_MUTEX_INITIALIZER;
160     char *s = NULL;
161     struct hostent *he;
162     pthread_mutex_lock(&ghba_mtx);
163     he = gethostbyaddr((char*)&addr->addr, addr->len, addr->af);
164     if (he)
165         s = xstrdup(he->h_name);
166     pthread_mutex_unlock(&ghba_mtx);
167     return s;
168 }
169
170
171 #elif defined(USE_LIBRESOLV)
172
173 #include <arpa/nameser.h>
174 #include <resolv.h>
175
176 /**
177  * libresolv implementation 
178  * resolver functions may not be thread safe
179  */
180 char* do_resolve(struct addr_storage *addr) {
181   char msg[PACKETSZ];
182   char s[35];
183   int l;
184   unsigned char* a;
185   char * ret = NULL;
186
187   if (addr->af != AF_INET)
188     return NULL;
189
190   a = (unsigned char*)&addr->addr;
191
192   snprintf(s, 35, "%d.%d.%d.%d.in-addr.arpa.",a[3], a[2], a[1], a[0]);
193
194   l = res_search(s, C_IN, T_PTR, msg, PACKETSZ);
195   if(l != -1) {
196     ns_msg nsmsg;
197     ns_rr rr;
198     if(ns_initparse(msg, l, &nsmsg) != -1) {
199       int c;
200       int i;
201       c = ns_msg_count(nsmsg, ns_s_an);
202       for(i = 0; i < c; i++) {
203         if(ns_parserr(&nsmsg, ns_s_an, i, &rr) == 0){
204           if(ns_rr_type(rr) == T_PTR) {
205             char buf[256];
206             ns_name_uncompress(msg, msg + l, ns_rr_rdata(rr), buf, 256);
207             ret = xstrdup(buf);
208           }
209         }
210       }
211     }
212   }
213   return ret;
214 }
215
216 #elif defined(USE_ARES)
217
218 /**
219  * ares implementation
220  */
221
222 #include <sys/time.h>
223 #include <ares.h>
224 #include <arpa/nameser.h>
225
226 /* callback function for ares */
227 struct ares_callback_comm {
228     struct in_addr *addr;
229     int result;
230     char *name;
231 };
232
233 static void do_resolve_ares_callback(void *arg, int status, unsigned char *abuf, int alen) {
234     struct hostent *he;
235     struct ares_callback_comm *C;
236     C = (struct ares_callback_comm*)arg;
237
238     if (status == ARES_SUCCESS) {
239         C->result = 1;
240         ares_parse_ptr_reply(abuf, alen, C->addr, sizeof *C->addr, AF_INET, &he);
241         C->name = xstrdup(he->h_name);;
242         ares_free_hostent(he);
243     } else {
244         C->result = -1;
245     }
246 }
247
248 char *do_resolve(struct addr_storage * addr) {
249     struct ares_callback_comm C;
250     char s[35];
251     unsigned char *a;
252     ares_channel *chan;
253     static pthread_mutex_t ares_init_mtx = PTHREAD_MUTEX_INITIALIZER;
254     static pthread_key_t ares_key;
255     static int gotkey;
256
257     if (addr->af != AF_INET)
258         return NULL;
259
260     /* Make sure we have an ARES channel for this thread. */
261     pthread_mutex_lock(&ares_init_mtx);
262     if (!gotkey) {
263         pthread_key_create(&ares_key, NULL);
264         gotkey = 1;
265         
266     }
267     pthread_mutex_unlock(&ares_init_mtx);
268     
269     chan = pthread_getspecific(ares_key);
270     if (!chan) {
271         chan = xmalloc(sizeof *chan);
272         pthread_setspecific(ares_key, chan);
273         if (ares_init(chan) != ARES_SUCCESS) return NULL;
274     }
275     
276     a = (unsigned char*)&addr->as_addr4;
277     sprintf(s, "%d.%d.%d.%d.in-addr.arpa.", a[3], a[2], a[1], a[0]);
278     
279     C.result = 0;
280     C.addr = &addr->as_addr4;
281     ares_query(*chan, s, C_IN, T_PTR, do_resolve_ares_callback, &C);
282     while (C.result == 0) {
283         int n;
284         fd_set readfds, writefds;
285         struct timeval tv;
286         FD_ZERO(&readfds);
287         FD_ZERO(&writefds);
288         n = ares_fds(*chan, &readfds, &writefds);
289         ares_timeout(*chan, NULL, &tv);
290         select(n, &readfds, &writefds, NULL, &tv);
291         ares_process(*chan, &readfds, &writefds);
292     }
293
294     /* At this stage, the query should be complete. */
295     switch (C.result) {
296         case -1:
297         case 0:     /* shouldn't happen */
298             return NULL;
299
300         default:
301             return C.name;
302     }
303 }
304
305 #elif defined(USE_FORKING_RESOLVER)
306
307 /**
308  * Resolver which forks a process, then uses gethostbyname.
309  */
310
311 #include <signal.h>
312
313 #define NAMESIZE        64
314
315 int forking_resolver_worker(int fd) {
316     while (1) {
317         struct addr_storage a;
318         struct hostent *he;
319         char buf[NAMESIZE] = {0};
320         if (read(fd, &a, sizeof a) != sizeof a)
321             return -1;
322
323         he = gethostbyaddr((char*)&a.addr, a.len, a.af);
324         if (he)
325             strncpy(buf, he->h_name, NAMESIZE - 1);
326
327         if (write(fd, buf, NAMESIZE) != NAMESIZE)
328             return -1;
329     }
330 }
331
332 char *do_resolve(struct in6_addr *addr) {
333     struct {
334         int fd;
335         pid_t child;
336     } *workerinfo;
337     char name[NAMESIZE];
338     static pthread_mutex_t worker_init_mtx = PTHREAD_MUTEX_INITIALIZER;
339     static pthread_key_t worker_key;
340     static int gotkey;
341
342     /* If no process exists, we need to spawn one. */
343     pthread_mutex_lock(&worker_init_mtx);
344     if (!gotkey) {
345         pthread_key_create(&worker_key, NULL);
346         gotkey = 1;
347     }
348     pthread_mutex_unlock(&worker_init_mtx);
349     
350     workerinfo = pthread_getspecific(worker_key);
351     if (!workerinfo) {
352         int p[2];
353
354         if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
355             return NULL;
356
357         workerinfo = xmalloc(sizeof *workerinfo);
358         pthread_setspecific(worker_key, workerinfo);
359         workerinfo->fd = p[0];
360         
361         switch (workerinfo->child = fork()) {
362             case 0:
363                 close(p[0]);
364                 _exit(forking_resolver_worker(p[1]));
365
366             case -1:
367                 close(p[0]);
368                 close(p[1]);
369                 return NULL;
370
371             default:
372                 close(p[1]);
373         }
374     }
375
376     /* Now have a worker to which we can write requests. */
377     if (write(workerinfo->fd, addr, sizeof *addr) != sizeof *addr
378         || read(workerinfo->fd, name, NAMESIZE) != NAMESIZE) {
379         /* Something went wrong. Just kill the child and get on with it. */
380         kill(workerinfo->child, SIGKILL);
381         wait(NULL);
382         close(workerinfo->fd);
383         xfree(workerinfo);
384         pthread_setspecific(worker_key, NULL);
385         *name = 0;
386     }
387     if (!*name)
388         return NULL;
389     else
390         return xstrdup(name);
391 }
392
393 #else
394
395 #   warning No name resolution method specified; name resolution will not work
396
397 char *do_resolve(struct addr_storage *addr) {
398     return NULL;
399 }
400
401 #endif
402
403 void resolver_worker(void* ptr) {
404 /*    int thread_number = *(int*)ptr;*/
405     pthread_mutex_lock(&resolver_queue_mutex);
406     sethostent(1);
407     while(1) {
408         /* Wait until we are told that an address has been added to the 
409          * queue. */
410         pthread_cond_wait(&resolver_queue_cond, &resolver_queue_mutex);
411
412         /* Keep resolving until the queue is empty */
413         while(head != tail) {
414             char * hostname;
415             struct addr_storage addr = resolve_queue[tail];
416
417             /* mutex always locked at this point */
418
419             tail = (tail + 1) % RESOLVE_QUEUE_LENGTH;
420
421             pthread_mutex_unlock(&resolver_queue_mutex);
422
423             hostname = do_resolve(&addr);
424
425             /*
426              * Store the result in ns_hash
427              */
428             pthread_mutex_lock(&resolver_queue_mutex);
429
430             if(hostname != NULL) {
431                 char* old;
432                 union {
433                     char **ch_pp;
434                     void **void_pp;
435                 } u_old = { &old };
436                 if(hash_find(ns_hash, &addr, u_old.void_pp) == HASH_STATUS_OK) {
437                     hash_delete(ns_hash, &addr);
438                     xfree(old);
439                 }
440                 hash_insert(ns_hash, &addr, (void*)hostname);
441             }
442
443         }
444     }
445 }
446
447 void resolver_initialise() {
448     int* n;
449     int i;
450     pthread_t thread;
451     head = tail = 0;
452
453     ns_hash = ns_hash_create();
454     
455     pthread_mutex_init(&resolver_queue_mutex, NULL);
456     pthread_cond_init(&resolver_queue_cond, NULL);
457
458     for(i = 0; i < 2; i++) {
459         n = (int*)xmalloc(sizeof *n);
460         *n = i;
461         pthread_create(&thread, NULL, (void*)&resolver_worker, (void*)n);
462     }
463
464 }
465
466 void resolve(int af, void* addr, char* result, int buflen) {
467     char* hostname;
468     union {
469         char **ch_pp;
470         void **void_pp;
471     } u_hostname = { &hostname };
472     int added = 0;
473     struct addr_storage *raddr;
474
475     raddr = malloc(sizeof *raddr);
476     memset(raddr, 0, sizeof *raddr);
477     raddr->af = af;
478     raddr->len = (af == AF_INET ? sizeof(struct in_addr)
479                   : sizeof(struct in6_addr));
480     memcpy(&raddr->addr, addr, raddr->len);
481
482     if(options.dnsresolution == 1) {
483
484         pthread_mutex_lock(&resolver_queue_mutex);
485
486         if(hash_find(ns_hash, raddr, u_hostname.void_pp) == HASH_STATUS_OK) {
487             /* Found => already resolved, or on the queue, no need to keep
488              * it around */
489             free(raddr);
490         }
491         else {
492             hostname = xmalloc(INET6_ADDRSTRLEN);
493             inet_ntop(af, &raddr->addr, hostname, INET6_ADDRSTRLEN);
494
495             hash_insert(ns_hash, raddr, hostname);
496
497             if(((head + 1) % RESOLVE_QUEUE_LENGTH) == tail) {
498                 /* queue full */
499             }
500             else if ((af == AF_INET6)
501                      && (IN6_IS_ADDR_LINKLOCAL(&raddr->as_addr6)
502                          || IN6_IS_ADDR_SITELOCAL(&raddr->as_addr6))) {
503                 /* Link-local and site-local stay numerical. */
504             }
505             else {
506                 resolve_queue[head] = *raddr;
507                 head = (head + 1) % RESOLVE_QUEUE_LENGTH;
508                 added = 1;
509             }
510         }
511         pthread_mutex_unlock(&resolver_queue_mutex);
512
513         if(added == 1) {
514             pthread_cond_signal(&resolver_queue_cond);
515         }
516
517         if(result != NULL && buflen > 1) {
518             strncpy(result, hostname, buflen - 1);
519             result[buflen - 1] = '\0';
520         }
521     }
522 }