blob: 210dfdd7240773e1430c6a930740b2395bf0166d [file] [log] [blame]
The Android Open Source Projecta27d2ba2008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in
12 * the documentation and/or other materials provided with the
13 * distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
19 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
22 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include "resolv_cache.h"
30#include <stdlib.h>
31#include <time.h>
32#include "pthread.h"
33
34/* this code implements a small DNS resolver cache for all gethostbyname* functions
35 *
36 * the cache is shared between all threads of the current process, but the result of
37 * a succesful lookup is always copied to a thread-local variable to honor the persistence
38 * rules of the gethostbyname*() APIs.
39 */
40
41/* the name of an environment variable that will be checked the first time this code is called
42 * if its value is "0", then the resolver cache is disabled.
43 */
44#define CONFIG_ENV "BIONIC_DNSCACHE"
45
46/* entries older than CONFIG_SECONDS seconds are always discarded. otherwise we could keep
47 * stale addresses in the cache and be oblivious to DNS server changes
48 */
49#define CONFIG_SECONDS (60*10) /* 10 minutes */
50
51/* maximum number of entries kept in the cache. be frugal, this is the C library
52 * this MUST BE A POWER OF 2
53 */
54#define CONFIG_MAX_ENTRIES 128
55
56/****************************************************************************/
57/****************************************************************************/
58/***** *****/
59/***** *****/
60/***** *****/
61/****************************************************************************/
62/****************************************************************************/
63
64#define DEBUG 0
65
66#if DEBUG
67#include <fcntl.h>
68#include <errno.h>
69#include <stdarg.h>
70#include <stdio.h>
71static void
72xlog( const char* fmt, ... )
73{
74 static int fd = -2;
75 int ret;
76
77 if (fd == -2) {
78 do {
79 fd = open( "/data/dns.log", O_CREAT | O_APPEND | O_WRONLY, 0666 );
80 } while (fd < 0 && errno == EINTR);
81 }
82
83 if (fd >= 0) {
84 char temp[128];
85 va_list args;
86 va_start(args, fmt);
87 vsnprintf( temp, sizeof(temp), fmt, args);
88 va_end(args);
89
90 do {
91 ret = write( fd, temp, strlen(temp) );
92 } while (ret == -1 && errno == EINTR);
93 }
94}
95#define XLOG(...) xlog(__VA_ARGS__)
96#else
97#define XLOG(...) ((void)0)
98#endif
99
100
101static time_t
102_time_now( void )
103{
104 struct timeval tv;
105
106 gettimeofday( &tv, NULL );
107 return tv.tv_sec;
108}
109
110/****************************************************************************/
111/****************************************************************************/
112/***** *****/
113/***** *****/
114/***** *****/
115/****************************************************************************/
116/****************************************************************************/
117
118/* used to define the content of _RESOLV_HOSTENT_NONE
119 */
120const struct hostent _resolv_hostent_none_cst = {
121 NULL,
122 NULL,
123 AF_INET,
124 4,
125 NULL
126};
127
128struct hostent*
129_resolv_hostent_copy( struct hostent* hp )
130{
131 struct hostent* dst;
132 int nn, len;
133 char* p;
134 int num_aliases = 0, num_addresses = 0;
135
136 if (hp == NULL)
137 return NULL;
138
139 if (hp == _RESOLV_HOSTENT_NONE)
140 return hp;
141
142 len = sizeof(*hp);
143 len += strlen(hp->h_name) + 1;
144
145 if (hp->h_aliases != NULL) {
146 for (nn = 0; hp->h_aliases[nn] != NULL; nn++)
147 len += sizeof(char*) + strlen(hp->h_aliases[nn]) + 1;
148 num_aliases = nn;
149 }
150 len += sizeof(char*);
151
152 for (nn = 0; hp->h_addr_list[nn] != NULL; nn++) {
153 len += sizeof(char*) + hp->h_length;
154 }
155 num_addresses = nn;
156 len += sizeof(char*);
157
158 dst = malloc( len );
159 if (dst == NULL)
160 return NULL;
161
162 dst->h_aliases = (char**)(dst + 1);
163 dst->h_addr_list = dst->h_aliases + num_aliases + 1;
164 dst->h_length = hp->h_length;
165 dst->h_addrtype = hp->h_addrtype;
166
167 p = (char*)(dst->h_addr_list + num_addresses + 1);
168
169 /* write the addresses first, to help with alignment issues */
170 for (nn = 0; nn < num_addresses; nn++) {
171 dst->h_addr_list[nn] = p;
172 len = hp->h_length;
173 memcpy( p, hp->h_addr_list[nn], len );
174 p += len;
175 }
176 dst->h_addr_list[nn] = NULL;
177
178 for (nn = 0; nn < num_aliases; nn++) {
179 dst->h_aliases[nn] = p;
180 len = strlen(hp->h_aliases[nn]) + 1;
181 memcpy( p, hp->h_aliases[nn], len );
182 p += len;
183 }
184 dst->h_aliases[nn] = NULL;
185
186 dst->h_name = p;
187 len = strlen(hp->h_name) + 1;
188 memcpy(p, hp->h_name, len);
189 p += len;
190
191 return dst;
192}
193
194
195void
196_resolv_hostent_free( struct hostent* hp )
197{
198 if (hp && hp != _RESOLV_HOSTENT_NONE)
199 free(hp);
200}
201
202/****************************************************************************/
203/****************************************************************************/
204/***** *****/
205/***** *****/
206/***** *****/
207/****************************************************************************/
208/****************************************************************************/
209
210typedef struct Entry {
211 unsigned int hash;
212 const char* name;
213 short af;
214 short index;
215 struct Entry* mru_prev;
216 struct Entry* mru_next;
217 time_t when;
218 struct hostent* hp;
219} Entry;
220
221
222static void
223entry_free( Entry* e )
224{
225 /* everything is allocated in a single memory block */
226 if (e) {
227 _resolv_hostent_free(e->hp);
228 free(e);
229 }
230}
231
232static void
233entry_init_key( Entry* e, const char* name, int af )
234{
235 unsigned h = 0;
236 const char* p = name;
237
238 /* compute hash */
239 p = name;
240 while (*p) {
241 h = h*33 + *p++;
242 }
243 h += af*17;
244
245 e->hash = h;
246 e->name = name;
247 e->af = (short) af;
248}
249
250
251static __inline__ void
252entry_mru_remove( Entry* e )
253{
254 e->mru_prev->mru_next = e->mru_next;
255 e->mru_next->mru_prev = e->mru_prev;
256}
257
258static __inline__ void
259entry_mru_add( Entry* e, Entry* list )
260{
261 Entry* first = list->mru_next;
262
263 e->mru_next = first;
264 e->mru_prev = list;
265
266 list->mru_next = e;
267 first->mru_prev = e;
268}
269
270
271static Entry*
272entry_alloc( const char* name, int af, int index, struct hostent* hp )
273{
274 Entry* e;
275 int num_aliases = 0;
276 int num_addresses = 0;
277 char** aliases;
278 char** addresses;
279
280 /* compute the length of the memory block that will contain everything */
281 int len = sizeof(*e) + strlen(name)+1;
282
283 e = malloc(len);
284 if (e == NULL)
285 return e;
286
287 entry_init_key(e, name, af);
288
289 e->mru_next = e->mru_prev = e;
290 e->index = (short) index;
291 e->when = _time_now();
292 e->hp = _resolv_hostent_copy(hp);
293
294 if (e->hp == NULL) {
295 free(e);
296 return NULL;
297 }
298
299 e->name = (char*)(e+1);
300 len = strlen(name)+1;
301 memcpy( (char*)e->name, name, len );
302 return e;
303}
304
305
306static __inline__ int
307entry_equals( const Entry* e1, const Entry* e2 )
308{
309 return e1->hash == e2->hash && e1->af == e2->af && !strcmp( e1->name, e2->name );
310}
311
312/****************************************************************************/
313/****************************************************************************/
314/***** *****/
315/***** *****/
316/***** *****/
317/****************************************************************************/
318/****************************************************************************/
319
320#define MAX_HASH_ENTRIES (2*CONFIG_MAX_ENTRIES)
321
322typedef struct resolv_cache {
323 int num_entries;
324 Entry mru_list;
325 pthread_mutex_t lock;
326 int disabled;
327 Entry* entries[ MAX_HASH_ENTRIES ]; /* hash-table of pointers to entries */
328} Cache;
329
330
331void
332_resolv_cache_destroy( struct resolv_cache* cache )
333{
334 if (cache != NULL) {
335 int nn;
336 for (nn = 0; nn < MAX_HASH_ENTRIES; nn++) {
337 entry_free(cache->entries[nn]);
338 }
339 pthread_mutex_destroy(&cache->lock);
340 free(cache);
341 }
342}
343
344
345struct resolv_cache*
346_resolv_cache_create( void )
347{
348 struct resolv_cache* cache;
349
350 cache = calloc(sizeof(*cache), 1);
351 if (cache) {
352 const char* env = getenv(CONFIG_ENV);
353
354 if (env && atoi(env) == 0)
355 cache->disabled = 1;
356
357 pthread_mutex_init( &cache->lock, NULL );
358 cache->mru_list.mru_prev = cache->mru_list.mru_next = &cache->mru_list;
359 XLOG("%s: cache=%p %s\n", __FUNCTION__, cache, cache->disabled ? "disabled" : "enabled" );
360 }
361 return cache;
362}
363
364
365static int
366_resolv_cache_find_index( Cache* cache,
367 const char* name,
368 int af )
369{
370 Entry key;
371 int nn, step, tries;
372
373 entry_init_key( &key, name, af );
374
375 tries = MAX_HASH_ENTRIES;
376 nn = key.hash % MAX_HASH_ENTRIES;
377 step = 5;
378
379 while (tries > 0) {
380 Entry* key2 = cache->entries[nn];
381
382 if (key2 == NULL) {
383 return -(nn + 1);
384 }
385
386 if (entry_equals( &key, key2 ) ) {
387 return nn;
388 }
389
390 nn = (nn + step) % MAX_HASH_ENTRIES;
391 tries -= 1;
392 }
393 return -(MAX_HASH_ENTRIES+1);
394}
395
396
397static void
398_resolv_cache_remove( struct resolv_cache* cache,
399 Entry* e )
400{
401 XLOG("%s: name='%s' af=%d\n", __FUNCTION__, e->name, e->af);
402 cache->entries[ e->index ] = NULL; /* remove from hash table */
403 entry_mru_remove( e );
404 entry_free( e );
405 cache->num_entries -= 1;
406}
407
408
409struct hostent*
410_resolv_cache_lookup( struct resolv_cache* cache,
411 const char* name,
412 int af )
413{
414 int index;
415 struct hostent* result = NULL;
416
417 if (cache->disabled)
418 return NULL;
419
420 pthread_mutex_lock( &cache->lock );
421
422 XLOG( "%s: cache=%p name='%s' af=%d ", __FUNCTION__, cache, name, af );
423 index = _resolv_cache_find_index( cache, name, af );
424 if (index >= 0) {
425 Entry* e = cache->entries[index];
426 time_t now = _time_now();
427 struct hostent** pht;
428
429 /* ignore stale entries, they will be discarded in _resolv_cache_add */
430 if ( (unsigned)(now - e->when) >= CONFIG_SECONDS ) {
431 XLOG( " OLD\n" );
432 goto Exit;
433 }
434
435 /* bump up this entry to the top of the MRU list */
436 if (e != cache->mru_list.mru_next) {
437 entry_mru_remove( e );
438 entry_mru_add( e, &cache->mru_list );
439 }
440
441 /* now copy the result into a thread-local variable */
442 pht = __get_res_cache_hostent_p();
443 if (pht == NULL) {
444 XLOG( " NOTLS\n" ); /* shouldn't happen */
445 goto Exit;
446 }
447
448 if (pht[0]) {
449 _resolv_hostent_free( pht[0] ); /* clear previous value */
450 pht[0] = NULL;
451 }
452 result = _resolv_hostent_copy( e->hp );
453 if (result == NULL) {
454 XLOG( " NOMEM\n" ); /* bummer */
455 goto Exit;
456 }
457 XLOG( " OK\n" );
458 pht[0] = result;
459 goto Exit;
460 }
461 XLOG( " KO\n" );
462Exit:
463 pthread_mutex_unlock( &cache->lock );
464 return result;
465}
466
467
468void
469_resolv_cache_add( struct resolv_cache* cache,
470 const char* name,
471 int af,
472 struct hostent* hp )
473{
474 Entry* e;
475 int index;
476
477 if (cache->disabled)
478 return;
479
480 pthread_mutex_lock( &cache->lock );
481
482 XLOG( "%s: cache=%p name='%s' af=%d\n", __FUNCTION__, cache, name, af);
483
484 /* get rid of the oldest entry if needed */
485 if (cache->num_entries >= CONFIG_MAX_ENTRIES) {
486 Entry* oldest = cache->mru_list.mru_prev;
487 _resolv_cache_remove( cache, oldest );
488 }
489
490 index = _resolv_cache_find_index( cache, name, af );
491 if (index >= 0) {
492 /* discard stale entry */
493 _resolv_cache_remove( cache, cache->entries[index] );
494 } else {
495 index = -(index+1);
496 if (index >= MAX_HASH_ENTRIES)
497 goto Exit; /* should not happen */
498 }
499
500 e = entry_alloc( name, af, index, hp );
501 if (e != NULL) {
502 entry_mru_add( e, &cache->mru_list );
503 cache->entries[index] = e;
504 cache->num_entries += 1;
505 }
506Exit:
507 pthread_mutex_unlock( &cache->lock );
508}
509
510/****************************************************************************/
511/****************************************************************************/
512/***** *****/
513/***** *****/
514/***** *****/
515/****************************************************************************/
516/****************************************************************************/
517
518static struct resolv_cache* _res_cache;
519static pthread_once_t _res_cache_once;
520
521static void
522_res_cache_init( void )
523{
524 _res_cache = _resolv_cache_create();
525}
526
527
528struct resolv_cache*
529__get_res_cache( void )
530{
531 pthread_once( &_res_cache_once, _res_cache_init );
532 return _res_cache;
533}