]> gitweb.fperrin.net Git - iftop.git/blob - resolver.c
Import iftop-1.0pre4
[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 = NULL;
111     size_t hstbuflen = 1024;
112     char *tmphstbuf;
113     int res = 0;
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     /* nss-myhostname's gethostbyaddr_r() causes an assertion failure if an
121      * "invalid" (as in outside of IPv4 or IPv6) address family is passed */
122     if (addr->af == AF_INET || addr->af == AF_INET6) {
123
124     /* Some machines have gethostbyaddr_r returning an integer error code; on
125      * others, it returns a struct hostent*. */
126 #ifdef GETHOSTBYADDR_R_RETURNS_INT
127     while ((res = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
128                                   &hostbuf, tmphstbuf, hstbuflen,
129                                   &hp, &herr)) == ERANGE)
130 #else
131     /* ... also assume one fewer argument.... */
132     while ((hp = gethostbyaddr_r((char*)&addr->addr, addr->len, addr->af,
133                                  &hostbuf, tmphstbuf, hstbuflen, &herr)) == NULL
134             && errno == ERANGE)
135 #endif
136             {
137         
138         /* Enlarge the buffer.  */
139         hstbuflen *= 2;
140         tmphstbuf = realloc (tmphstbuf, hstbuflen);
141       }
142     }
143
144     /*  Check for errors.  */
145     if (res || hp == NULL) {
146         /* failed */
147         /* Leave the unresolved IP in the hash */
148     }
149     else {
150         ret = xstrdup(hp->h_name);
151
152     }
153     xfree(tmphstbuf);
154     return ret;
155 }
156
157 #elif defined(USE_GETHOSTBYADDR)
158
159 /**
160  * Implementation using gethostbyname. Since this is nonreentrant, we have to
161  * wrap it in a mutex, losing all benefit of multithreaded resolution.
162  */
163 char *do_resolve(struct addr_storage *addr) {
164     static pthread_mutex_t ghba_mtx = PTHREAD_MUTEX_INITIALIZER;
165     char *s = NULL;
166     struct hostent *he;
167     pthread_mutex_lock(&ghba_mtx);
168     he = gethostbyaddr((char*)&addr->addr, addr->len, addr->af);
169     if (he)
170         s = xstrdup(he->h_name);
171     pthread_mutex_unlock(&ghba_mtx);
172     return s;
173 }
174
175
176 #elif defined(USE_LIBRESOLV)
177
178 #include <arpa/nameser.h>
179 #include <resolv.h>
180
181 /**
182  * libresolv implementation 
183  * resolver functions may not be thread safe
184  */
185 char* do_resolve(struct addr_storage *addr) {
186   char msg[PACKETSZ];
187   char s[35];
188   int l;
189   unsigned char* a;
190   char * ret = NULL;
191
192   if (addr->af != AF_INET)
193     return NULL;
194
195   a = (unsigned char*)&addr->addr;
196
197   snprintf(s, 35, "%d.%d.%d.%d.in-addr.arpa.",a[3], a[2], a[1], a[0]);
198
199   l = res_search(s, C_IN, T_PTR, msg, PACKETSZ);
200   if(l != -1) {
201     ns_msg nsmsg;
202     ns_rr rr;
203     if(ns_initparse(msg, l, &nsmsg) != -1) {
204       int c;
205       int i;
206       c = ns_msg_count(nsmsg, ns_s_an);
207       for(i = 0; i < c; i++) {
208         if(ns_parserr(&nsmsg, ns_s_an, i, &rr) == 0){
209           if(ns_rr_type(rr) == T_PTR) {
210             char buf[256];
211             ns_name_uncompress(msg, msg + l, ns_rr_rdata(rr), buf, 256);
212             ret = xstrdup(buf);
213           }
214         }
215       }
216     }
217   }
218   return ret;
219 }
220
221 #elif defined(USE_ARES)
222
223 /**
224  * ares implementation
225  */
226
227 #include <sys/time.h>
228 #include <ares.h>
229 #include <arpa/nameser.h>
230
231 /* callback function for ares */
232 struct ares_callback_comm {
233     struct in_addr *addr;
234     int result;
235     char *name;
236 };
237
238 static void do_resolve_ares_callback(void *arg, int status, unsigned char *abuf, int alen) {
239     struct hostent *he;
240     struct ares_callback_comm *C;
241     C = (struct ares_callback_comm*)arg;
242
243     if (status == ARES_SUCCESS) {
244         C->result = 1;
245         ares_parse_ptr_reply(abuf, alen, C->addr, sizeof *C->addr, AF_INET, &he);
246         C->name = xstrdup(he->h_name);;
247         ares_free_hostent(he);
248     } else {
249         C->result = -1;
250     }
251 }
252
253 char *do_resolve(struct addr_storage * addr) {
254     struct ares_callback_comm C;
255     char s[35];
256     unsigned char *a;
257     ares_channel *chan;
258     static pthread_mutex_t ares_init_mtx = PTHREAD_MUTEX_INITIALIZER;
259     static pthread_key_t ares_key;
260     static int gotkey;
261
262     if (addr->af != AF_INET)
263         return NULL;
264
265     /* Make sure we have an ARES channel for this thread. */
266     pthread_mutex_lock(&ares_init_mtx);
267     if (!gotkey) {
268         pthread_key_create(&ares_key, NULL);
269         gotkey = 1;
270         
271     }
272     pthread_mutex_unlock(&ares_init_mtx);
273     
274     chan = pthread_getspecific(ares_key);
275     if (!chan) {
276         chan = xmalloc(sizeof *chan);
277         pthread_setspecific(ares_key, chan);
278         if (ares_init(chan) != ARES_SUCCESS) return NULL;
279     }
280     
281     a = (unsigned char*)&addr->as_addr4;
282     sprintf(s, "%d.%d.%d.%d.in-addr.arpa.", a[3], a[2], a[1], a[0]);
283     
284     C.result = 0;
285     C.addr = &addr->as_addr4;
286     ares_query(*chan, s, C_IN, T_PTR, do_resolve_ares_callback, &C);
287     while (C.result == 0) {
288         int n;
289         fd_set readfds, writefds;
290         struct timeval tv;
291         FD_ZERO(&readfds);
292         FD_ZERO(&writefds);
293         n = ares_fds(*chan, &readfds, &writefds);
294         ares_timeout(*chan, NULL, &tv);
295         select(n, &readfds, &writefds, NULL, &tv);
296         ares_process(*chan, &readfds, &writefds);
297     }
298
299     /* At this stage, the query should be complete. */
300     switch (C.result) {
301         case -1:
302         case 0:     /* shouldn't happen */
303             return NULL;
304
305         default:
306             return C.name;
307     }
308 }
309
310 #elif defined(USE_FORKING_RESOLVER)
311
312 /**
313  * Resolver which forks a process, then uses gethostbyname.
314  */
315
316 #include <signal.h>
317
318 #define NAMESIZE        64
319
320 int forking_resolver_worker(int fd) {
321     while (1) {
322         struct addr_storage a;
323         struct hostent *he;
324         char buf[NAMESIZE] = {0};
325         if (read(fd, &a, sizeof a) != sizeof a)
326             return -1;
327
328         he = gethostbyaddr((char*)&a.addr, a.len, a.af);
329         if (he)
330             strncpy(buf, he->h_name, NAMESIZE - 1);
331
332         if (write(fd, buf, NAMESIZE) != NAMESIZE)
333             return -1;
334     }
335 }
336
337 char *do_resolve(struct in6_addr *addr) {
338     struct {
339         int fd;
340         pid_t child;
341     } *workerinfo;
342     char name[NAMESIZE];
343     static pthread_mutex_t worker_init_mtx = PTHREAD_MUTEX_INITIALIZER;
344     static pthread_key_t worker_key;
345     static int gotkey;
346
347     /* If no process exists, we need to spawn one. */
348     pthread_mutex_lock(&worker_init_mtx);
349     if (!gotkey) {
350         pthread_key_create(&worker_key, NULL);
351         gotkey = 1;
352     }
353     pthread_mutex_unlock(&worker_init_mtx);
354     
355     workerinfo = pthread_getspecific(worker_key);
356     if (!workerinfo) {
357         int p[2];
358
359         if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
360             return NULL;
361
362         workerinfo = xmalloc(sizeof *workerinfo);
363         pthread_setspecific(worker_key, workerinfo);
364         workerinfo->fd = p[0];
365         
366         switch (workerinfo->child = fork()) {
367             case 0:
368                 close(p[0]);
369                 _exit(forking_resolver_worker(p[1]));
370
371             case -1:
372                 close(p[0]);
373                 close(p[1]);
374                 return NULL;
375
376             default:
377                 close(p[1]);
378         }
379     }
380
381     /* Now have a worker to which we can write requests. */
382     if (write(workerinfo->fd, addr, sizeof *addr) != sizeof *addr
383         || read(workerinfo->fd, name, NAMESIZE) != NAMESIZE) {
384         /* Something went wrong. Just kill the child and get on with it. */
385         kill(workerinfo->child, SIGKILL);
386         wait(NULL);
387         close(workerinfo->fd);
388         xfree(workerinfo);
389         pthread_setspecific(worker_key, NULL);
390         *name = 0;
391     }
392     if (!*name)
393         return NULL;
394     else
395         return xstrdup(name);
396 }
397
398 #else
399
400 #   warning No name resolution method specified; name resolution will not work
401
402 char *do_resolve(struct addr_storage *addr) {
403     return NULL;
404 }
405
406 #endif
407
408 void resolver_worker(void* ptr) {
409 /*    int thread_number = *(int*)ptr;*/
410     pthread_mutex_lock(&resolver_queue_mutex);
411     sethostent(1);
412     while(1) {
413         /* Wait until we are told that an address has been added to the 
414          * queue. */
415         pthread_cond_wait(&resolver_queue_cond, &resolver_queue_mutex);
416
417         /* Keep resolving until the queue is empty */
418         while(head != tail) {
419             char * hostname;
420             struct addr_storage addr = resolve_queue[tail];
421
422             /* mutex always locked at this point */
423
424             tail = (tail + 1) % RESOLVE_QUEUE_LENGTH;
425
426             pthread_mutex_unlock(&resolver_queue_mutex);
427
428             hostname = do_resolve(&addr);
429
430             /*
431              * Store the result in ns_hash
432              */
433             pthread_mutex_lock(&resolver_queue_mutex);
434
435             if(hostname != NULL) {
436                 char* old;
437                 union {
438                     char **ch_pp;
439                     void **void_pp;
440                 } u_old = { &old };
441                 if(hash_find(ns_hash, &addr, u_old.void_pp) == HASH_STATUS_OK) {
442                     hash_delete(ns_hash, &addr);
443                     xfree(old);
444                 }
445                 hash_insert(ns_hash, &addr, (void*)hostname);
446             }
447
448         }
449     }
450 }
451
452 void resolver_initialise() {
453     int* n;
454     int i;
455     pthread_t thread;
456     head = tail = 0;
457
458     ns_hash = ns_hash_create();
459     
460     pthread_mutex_init(&resolver_queue_mutex, NULL);
461     pthread_cond_init(&resolver_queue_cond, NULL);
462
463     for(i = 0; i < 2; i++) {
464         n = (int*)xmalloc(sizeof *n);
465         *n = i;
466         pthread_create(&thread, NULL, (void*)&resolver_worker, (void*)n);
467     }
468
469 }
470
471 void resolve(int af, void* addr, char* result, int buflen) {
472     char* hostname;
473     union {
474         char **ch_pp;
475         void **void_pp;
476     } u_hostname = { &hostname };
477     int added = 0;
478     struct addr_storage *raddr;
479
480     if(options.dnsresolution == 1) {
481
482         raddr = malloc(sizeof *raddr);
483         memset(raddr, 0, sizeof *raddr);
484         raddr->af = af;
485         raddr->len = (af == AF_INET ? sizeof(struct in_addr)
486                       : sizeof(struct in6_addr));
487         memcpy(&raddr->addr, addr, raddr->len);
488
489         pthread_mutex_lock(&resolver_queue_mutex);
490
491         if(hash_find(ns_hash, raddr, u_hostname.void_pp) == HASH_STATUS_OK) {
492             /* Found => already resolved, or on the queue, no need to keep
493              * it around */
494             free(raddr);
495         }
496         else {
497             hostname = xmalloc(INET6_ADDRSTRLEN);
498             inet_ntop(af, &raddr->addr, hostname, INET6_ADDRSTRLEN);
499
500             hash_insert(ns_hash, raddr, hostname);
501
502             if(((head + 1) % RESOLVE_QUEUE_LENGTH) == tail) {
503                 /* queue full */
504             }
505             else if ((af == AF_INET6)
506                      && (IN6_IS_ADDR_LINKLOCAL(&raddr->as_addr6)
507                          || IN6_IS_ADDR_SITELOCAL(&raddr->as_addr6))) {
508                 /* Link-local and site-local stay numerical. */
509             }
510             else {
511                 resolve_queue[head] = *raddr;
512                 head = (head + 1) % RESOLVE_QUEUE_LENGTH;
513                 added = 1;
514             }
515         }
516         pthread_mutex_unlock(&resolver_queue_mutex);
517
518         if(added == 1) {
519             pthread_cond_signal(&resolver_queue_cond);
520         }
521
522         if(result != NULL && buflen > 1) {
523             strncpy(result, hostname, buflen - 1);
524             result[buflen - 1] = '\0';
525         }
526     }
527 }