]> gitweb.fperrin.net Git - iftop.git/blob - resolver.c
Import de iftop-1.0pre1
[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 in6_addr resolve_queue[RESOLVE_QUEUE_LENGTH];
29
30 pthread_cond_t resolver_queue_cond;
31 pthread_mutex_t resolver_queue_mutex;
32
33 hash_type* ns_hash;
34
35 int head;
36 int tail;
37
38 extern options_t options;
39
40
41 /* 
42  * We have a choice of resolver methods. Real computers have getnameinfo or
43  * gethostbyaddr_r, which are reentrant and therefore thread safe. Other
44  * machines don't, and so we can use non-reentrant gethostbyaddr and have only
45  * one resolver thread.  Alternatively, we can use the MIT ares asynchronous
46  * DNS library to do this.
47  */
48
49 #if defined(USE_GETNAMEINFO)
50 /**
51  * Implementation of do_resolve for platforms with getaddrinfo.
52  *
53  * This is a fairly sane function with a uniform interface which is even --
54  * shock! -- standardised by POSIX and in RFC 2553. Unfortunately systems such
55  * as NetBSD break the RFC and implement it in a non-thread-safe fashion, so
56  * for the moment, the configure script won't try to use it.
57  */
58 char *do_resolve(struct in6_addr *addr) {
59     struct sockaddr_in sin;
60     struct sockaddr_in6 sin6;
61     char buf[NI_MAXHOST]; /* 1025 */
62     int res, af;
63     uint32_t* probe;
64
65     memset(&sin, '\0', sizeof(sin));
66     memset(&sin6, '\0', sizeof(sin6));
67
68     /* If the upper three (network byte order) uint32-parts
69      * are null, then there ought to be an IPv4 address here.
70      * Any such IPv6 would have to be 'xxxx::'. Neglectable? */
71     probe = (uint32_t *) addr;
72     af = (probe[1] || probe[2] || probe[3]) ? AF_INET6 : AF_INET;
73
74     switch (af) {
75         case AF_INET:
76             sin.sin_family = af;
77             sin.sin_port = 0;
78             memcpy(&sin.sin_addr, addr, sizeof(sin.sin_addr));
79
80             if (getnameinfo((struct sockaddr*)&sin, sizeof sin,
81                             buf, sizeof buf, NULL, 0, NI_NAMEREQD) == 0)
82                 return xstrdup(buf);
83             else
84                 return NULL;
85             break;
86         case AF_INET6:
87             sin6.sin6_family = af;
88             sin6.sin6_port = 0;
89             memcpy(&sin6.sin6_addr, addr, sizeof(sin6.sin6_addr));
90
91             if (getnameinfo((struct sockaddr*)&sin6, sizeof sin6,
92                             buf, sizeof buf, NULL, 0, NI_NAMEREQD) == 0)
93                 return xstrdup(buf);
94             else
95                 return NULL;
96             break;
97         default:
98             return NULL;
99     }
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 in_addr * 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, sizeof(struct in_addr), AF_INET,
124                                   &hostbuf, tmphstbuf, hstbuflen,
125                                   &hp, &herr)) == ERANGE)
126 #else
127     /* ... also assume one fewer argument.... */
128     while ((hp = gethostbyaddr_r((char*)addr, sizeof(struct in_addr), AF_INET,
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 in_addr *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, sizeof *addr, AF_INET);
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 in_addr * addr) {
181   char msg[PACKETSZ];
182   char s[35];
183   int l;
184   unsigned char* a;
185   char * ret = NULL;
186
187   a = (unsigned char*)addr;
188
189   snprintf(s, 35, "%d.%d.%d.%d.in-addr.arpa.",a[3], a[2], a[1], a[0]);
190
191   l = res_search(s, C_IN, T_PTR, msg, PACKETSZ);
192   if(l != -1) {
193     ns_msg nsmsg;
194     ns_rr rr;
195     if(ns_initparse(msg, l, &nsmsg) != -1) {
196       int c;
197       int i;
198       c = ns_msg_count(nsmsg, ns_s_an);
199       for(i = 0; i < c; i++) {
200         if(ns_parserr(&nsmsg, ns_s_an, i, &rr) == 0){
201           if(ns_rr_type(rr) == T_PTR) {
202             char buf[256];
203             ns_name_uncompress(msg, msg + l, ns_rr_rdata(rr), buf, 256);
204             ret = xstrdup(buf);
205           }
206         }
207       }
208     }
209   }
210   return ret;
211 }
212
213 #elif defined(USE_ARES)
214
215 /**
216  * ares implementation
217  */
218
219 #include <sys/time.h>
220 #include <ares.h>
221 #include <arpa/nameser.h>
222
223 /* callback function for ares */
224 struct ares_callback_comm {
225     struct in_addr *addr;
226     int result;
227     char *name;
228 };
229
230 static void do_resolve_ares_callback(void *arg, int status, unsigned char *abuf, int alen) {
231     struct hostent *he;
232     struct ares_callback_comm *C;
233     C = (struct ares_callback_comm*)arg;
234
235     if (status == ARES_SUCCESS) {
236         C->result = 1;
237         ares_parse_ptr_reply(abuf, alen, C->addr, sizeof *C->addr, AF_INET, &he);
238         C->name = xstrdup(he->h_name);;
239         ares_free_hostent(he);
240     } else {
241         C->result = -1;
242     }
243 }
244
245 char *do_resolve(struct in_addr * addr) {
246     struct ares_callback_comm C;
247     char s[35];
248     unsigned char *a;
249     ares_channel *chan;
250     static pthread_mutex_t ares_init_mtx = PTHREAD_MUTEX_INITIALIZER;
251     static pthread_key_t ares_key;
252     static int gotkey;
253
254     /* Make sure we have an ARES channel for this thread. */
255     pthread_mutex_lock(&ares_init_mtx);
256     if (!gotkey) {
257         pthread_key_create(&ares_key, NULL);
258         gotkey = 1;
259         
260     }
261     pthread_mutex_unlock(&ares_init_mtx);
262     
263     chan = pthread_getspecific(ares_key);
264     if (!chan) {
265         chan = xmalloc(sizeof *chan);
266         pthread_setspecific(ares_key, chan);
267         if (ares_init(chan) != ARES_SUCCESS) return NULL;
268     }
269     
270     a = (unsigned char*)addr;
271     sprintf(s, "%d.%d.%d.%d.in-addr.arpa.", a[3], a[2], a[1], a[0]);
272     
273     C.result = 0;
274     C.addr = addr;
275     ares_query(*chan, s, C_IN, T_PTR, do_resolve_ares_callback, &C);
276     while (C.result == 0) {
277         int n;
278         fd_set readfds, writefds;
279         struct timeval tv;
280         FD_ZERO(&readfds);
281         FD_ZERO(&writefds);
282         n = ares_fds(*chan, &readfds, &writefds);
283         ares_timeout(*chan, NULL, &tv);
284         select(n, &readfds, &writefds, NULL, &tv);
285         ares_process(*chan, &readfds, &writefds);
286     }
287
288     /* At this stage, the query should be complete. */
289     switch (C.result) {
290         case -1:
291         case 0:     /* shouldn't happen */
292             return NULL;
293
294         default:
295             return C.name;
296     }
297 }
298
299 #elif defined(USE_FORKING_RESOLVER)
300
301 /**
302  * Resolver which forks a process, then uses gethostbyname.
303  */
304
305 #include <signal.h>
306
307 #define NAMESIZE        64
308
309 int forking_resolver_worker(int fd) {
310     while (1) {
311         struct in_addr a;
312         struct hostent *he;
313         char buf[NAMESIZE] = {0};
314         if (read(fd, &a, sizeof a) != sizeof a)
315             return -1;
316
317         he = gethostbyaddr((char*)&a, sizeof a, AF_INET);
318         if (he)
319             strncpy(buf, he->h_name, NAMESIZE - 1);
320
321         if (write(fd, buf, NAMESIZE) != NAMESIZE)
322             return -1;
323     }
324 }
325
326 char *do_resolve(struct in_addr *addr) {
327     struct {
328         int fd;
329         pid_t child;
330     } *workerinfo;
331     char name[NAMESIZE];
332     static pthread_mutex_t worker_init_mtx = PTHREAD_MUTEX_INITIALIZER;
333     static pthread_key_t worker_key;
334     static int gotkey;
335
336     /* If no process exists, we need to spawn one. */
337     pthread_mutex_lock(&worker_init_mtx);
338     if (!gotkey) {
339         pthread_key_create(&worker_key, NULL);
340         gotkey = 1;
341     }
342     pthread_mutex_unlock(&worker_init_mtx);
343     
344     workerinfo = pthread_getspecific(worker_key);
345     if (!workerinfo) {
346         int p[2];
347
348         if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, p) == -1)
349             return NULL;
350
351         workerinfo = xmalloc(sizeof *workerinfo);
352         pthread_setspecific(worker_key, workerinfo);
353         workerinfo->fd = p[0];
354         
355         switch (workerinfo->child = fork()) {
356             case 0:
357                 close(p[0]);
358                 _exit(forking_resolver_worker(p[1]));
359
360             case -1:
361                 close(p[0]);
362                 close(p[1]);
363                 return NULL;
364
365             default:
366                 close(p[1]);
367         }
368     }
369
370     /* Now have a worker to which we can write requests. */
371     if (write(workerinfo->fd, addr, sizeof *addr) != sizeof *addr
372         || read(workerinfo->fd, name, NAMESIZE) != NAMESIZE) {
373         /* Something went wrong. Just kill the child and get on with it. */
374         kill(workerinfo->child, SIGKILL);
375         wait(NULL);
376         close(workerinfo->fd);
377         xfree(workerinfo);
378         pthread_setspecific(worker_key, NULL);
379         *name = 0;
380     }
381     if (!*name)
382         return NULL;
383     else
384         return xstrdup(name);
385 }
386
387 #else
388
389 #   warning No name resolution method specified; name resolution will not work
390
391 char *do_resolve(struct in_addr *addr) {
392     return NULL;
393 }
394
395 #endif
396
397 void resolver_worker(void* ptr) {
398 /*    int thread_number = *(int*)ptr;*/
399     pthread_mutex_lock(&resolver_queue_mutex);
400     sethostent(1);
401     while(1) {
402         /* Wait until we are told that an address has been added to the 
403          * queue. */
404         pthread_cond_wait(&resolver_queue_cond, &resolver_queue_mutex);
405
406         /* Keep resolving until the queue is empty */
407         while(head != tail) {
408             char * hostname;
409             struct in6_addr addr = resolve_queue[tail];
410
411             /* mutex always locked at this point */
412
413             tail = (tail + 1) % RESOLVE_QUEUE_LENGTH;
414
415             pthread_mutex_unlock(&resolver_queue_mutex);
416
417             hostname = do_resolve(&addr);
418
419             /*
420              * Store the result in ns_hash
421              */
422             pthread_mutex_lock(&resolver_queue_mutex);
423
424             if(hostname != NULL) {
425                 char* old;
426                 union {
427                     char **ch_pp;
428                     void **void_pp;
429                 } u_old = { &old };
430                 if(hash_find(ns_hash, &addr, u_old.void_pp) == HASH_STATUS_OK) {
431                     hash_delete(ns_hash, &addr);
432                     xfree(old);
433                 }
434                 hash_insert(ns_hash, &addr, (void*)hostname);
435             }
436
437         }
438     }
439 }
440
441 void resolver_initialise() {
442     int* n;
443     int i;
444     pthread_t thread;
445     head = tail = 0;
446
447     ns_hash = ns_hash_create();
448     
449     pthread_mutex_init(&resolver_queue_mutex, NULL);
450     pthread_cond_init(&resolver_queue_cond, NULL);
451
452     for(i = 0; i < 2; i++) {
453         n = (int*)xmalloc(sizeof *n);
454         *n = i;
455         pthread_create(&thread, NULL, (void*)&resolver_worker, (void*)n);
456     }
457
458 }
459
460 void resolve(int af, struct in6_addr* addr, char* result, int buflen) {
461     char* hostname;
462     union {
463         char **ch_pp;
464         void **void_pp;
465     } u_hostname = { &hostname };
466     int added = 0;
467
468     if(options.dnsresolution == 1) {
469
470         pthread_mutex_lock(&resolver_queue_mutex);
471
472         if(hash_find(ns_hash, addr, u_hostname.void_pp) == HASH_STATUS_OK) {
473             /* Found => already resolved, or on the queue */
474         }
475         else {
476             hostname = xmalloc(INET6_ADDRSTRLEN);
477             inet_ntop(af, addr, hostname, INET6_ADDRSTRLEN);
478             hash_insert(ns_hash, addr, hostname);
479
480             if(((head + 1) % RESOLVE_QUEUE_LENGTH) == tail) {
481                 /* queue full */
482             }
483             else if((af == AF_INET6)
484                         && (IN6_IS_ADDR_LINKLOCAL(addr)
485                             || IN6_IS_ADDR_SITELOCAL(addr))) {
486                 /* Link-local and site-local stay numerical. */
487             }
488             else {
489                 resolve_queue[head] = *addr;
490                 head = (head + 1) % RESOLVE_QUEUE_LENGTH;
491                 added = 1;
492             }
493         }
494         pthread_mutex_unlock(&resolver_queue_mutex);
495
496         if(added == 1) {
497             pthread_cond_signal(&resolver_queue_cond);
498         }
499
500         if(result != NULL && buflen > 1) {
501             strncpy(result, hostname, buflen - 1);
502             result[buflen - 1] = '\0';
503         }
504     }
505 }