sres_cache.c 10.6 KB
Newer Older
Pekka Pessi's avatar
Pekka Pessi committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2006 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**@CFILE sres_cache.c
 * @brief Cache for Sofia DNS Resolver.
 * 
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 * @author Teemu Jalava <Teemu.Jalava@nokia.com>
 * @author Mikko Haataja
 *
 * @todo The resolver should allow handling arbitrary records, too.
 */

35 36 37 38 39 40
#include "config.h"

#if HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
41 42 43 44 45 46
#else 
#if defined(_WIN32)
typedef unsigned _int8 uint8_t;
typedef unsigned _int16 uint16_t;
typedef unsigned _int32 uint32_t;
#endif
47
#endif
Pekka Pessi's avatar
Pekka Pessi committed
48

49
#if HAVE_NETINET_IN_H
Pekka Pessi's avatar
Pekka Pessi committed
50 51
#include <sys/types.h>
#include <sys/socket.h>
52 53
#include <netinet/in.h>
#endif
54

Pekka Pessi's avatar
Pekka Pessi committed
55 56 57 58
#if HAVE_WINSOCK2_H
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
59

60 61
#include <time.h>

62 63 64 65 66
#include "sofia-resolv/sres_cache.h"
#include "sofia-resolv/sres_record.h"

#include <sofia-sip/su_alloc.h>
#include <sofia-sip/su_strlst.h>
Pekka Pessi's avatar
Pekka Pessi committed
67
#include <sofia-sip/htable.h>
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#include <limits.h>

#include <assert.h>

#define SU_LOG sresolv_log

#include <sofia-sip/su_debug.h>

typedef struct sres_rr_hash_entry_s sres_rr_hash_entry_t;

87
HTABLE_DECLARE_WITH(sres_htable, ht, sres_rr_hash_entry_t, unsigned, size_t);
88 89 90 91 92 93 94 95 96

struct sres_rr_hash_entry_s {
  unsigned int   rr_hash_key;
  time_t         rr_received;
  sres_record_t *rr;
};

#define SRES_HENTRY_HASH(e) ((e)->rr_hash_key)

97
struct sres_cache
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
{
  su_home_t           cache_home[1];
  time_t              cache_cleaned;
  sres_htable_t       cache_hash[1];
};

#define sr_refcount sr_record->r_refcount
#define sr_name     sr_record->r_name
#define sr_status   sr_record->r_status
#define sr_size     sr_record->r_size
#define sr_type     sr_record->r_type
#define sr_class    sr_record->r_class
#define sr_ttl      sr_record->r_ttl
#define sr_rdlen    sr_record->r_rdlen
#define sr_rdata    sr_generic->g_data

/* ---------------------------------------------------------------------- */
/* Internal prototypes */

#define LOCK(cache) (su_home_mutex_lock((cache)->cache_home) == 0)
#define UNLOCK(cache) (su_home_mutex_unlock((cache)->cache_home))

static inline
void _sres_cache_free_one(sres_cache_t *cache, sres_record_t *answer);
static inline
void _sres_cache_free_answers(sres_cache_t *cache, sres_record_t **answers);

static unsigned sres_hash_key(const char *string);

127
HTABLE_PROTOS_WITH(sres_htable, ht, sres_rr_hash_entry_t, unsigned, size_t);
128 129 130 131 132


/* ---------------------------------------------------------------------- */
/* Public functions */

133 134 135 136
/** Create a resolver cache object.
 *
 * @param n initial size of cache
 */
137
sres_cache_t *sres_cache_new(int n)
138 139 140
{
  sres_cache_t *cache = su_home_new(sizeof *cache);

141 142
  if (cache) {
    su_home_threadsafe(cache->cache_home);
143 144
    if (sres_htable_resize(cache->cache_home, cache->cache_hash, n) < 0)
      su_home_unref(cache->cache_home), cache = NULL;
145
  }
146 147 148 149

  return cache;
}

150
/** Increase reference count on a resolver cache object. */
151 152 153 154 155
sres_cache_t *sres_cache_ref(sres_cache_t *cache)
{
  return su_home_ref(cache->cache_home);
}

156
/** Decrease the reference count on a resolver cache object. */
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
void sres_cache_unref(sres_cache_t *cache)
{
  su_home_unref(cache->cache_home);
}

/** Get a list of matching records from cache. */
int sres_cache_get(sres_cache_t *cache,
		   uint16_t type,
		   char const *domain,
		   sres_record_t ***return_cached)
{
  sres_record_t **result = NULL, *rr = NULL;
  sres_rr_hash_entry_t **rr_iter, **rr_iter2;
  int result_size, rr_count = 0;
  unsigned hash;
  time_t now;
Pekka Pessi's avatar
Pekka Pessi committed
173
  char b[8];
174 175 176 177 178 179

  if (!domain || !return_cached)
    return -1;

  *return_cached = NULL;

Pekka Pessi's avatar
Pekka Pessi committed
180 181
  SU_DEBUG_9(("%s(%p, %s, \"%s\") called\n", "sres_cache_get",
	      cache, sres_record_type(type, b), domain));
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

  hash = sres_hash_key(domain);

  if (!LOCK(cache))
    return -1;

  time(&now);

  /* First pass: just count the number of rr:s for array allocation */
  rr_iter2 = sres_htable_hash(cache->cache_hash, hash);

  /* Find the domain records from the hash table */
  for (rr_iter = rr_iter2; 
       rr_iter && *rr_iter; 
       rr_iter = sres_htable_next(cache->cache_hash, rr_iter)) {
    rr = (*rr_iter)->rr;

    if (rr != NULL &&
200
	(uint32_t)(now - (*rr_iter)->rr_received) <= rr->sr_ttl &&
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
        (type == sres_qtype_any || rr->sr_type == type) &&
        rr->sr_name != NULL &&
        strcasecmp(rr->sr_name, domain) == 0) 
      rr_count++;
  }

  if (rr_count == 0) {
    UNLOCK(cache);
    return 0;
  }

  result_size = (sizeof *result) * (rr_count + 1);
  result = su_zalloc(cache->cache_home, result_size);
  if (result == NULL) {
    UNLOCK(cache);
    return -1;
  }

  /* Second pass: add the rr pointers to the allocated array */

  for (rr_iter = rr_iter2, rr_count = 0; 
       rr_iter && *rr_iter; 
       rr_iter = sres_htable_next(cache->cache_hash, rr_iter)) {
    rr = (*rr_iter)->rr;

    if (rr != NULL &&
227
	(uint32_t)(now - (*rr_iter)->rr_received) <= rr->sr_ttl &&
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
        (type == sres_qtype_any || rr->sr_type == type) &&
        rr->sr_name != NULL &&
        strcasecmp(rr->sr_name, domain) == 0) {
      SU_DEBUG_9(("rr found in cache: %s %02d\n", 
		  rr->sr_name, rr->sr_type));

      result[rr_count++] = rr;
      rr->sr_refcount++;
    }
  }

  result[rr_count] = NULL;

  UNLOCK(cache);

Pekka Pessi's avatar
Pekka Pessi committed
243 244
  SU_DEBUG_9(("%s(%p, %s, \"%s\") returned %d entries\n", "sres_cache_get", 
	      cache, sres_record_type(type, b), domain, rr_count));
245 246 247 248 249 250 251

  *return_cached = result;

  return rr_count;
}

sres_record_t *
252 253 254
sres_cache_alloc_record(sres_cache_t *cache,
			sres_record_t const *template,
			size_t extra)
255 256
{
  sres_record_t *sr;
257
  size_t size, name_length;
258

259 260 261 262 263 264
  size = template->sr_size;

  assert(size >= sizeof(sres_common_t));
  assert(template->sr_name != NULL);

  name_length = strlen(template->sr_name);
265

266
  sr = su_alloc(cache->cache_home, size + extra + name_length + 1);
267 268

  if (sr) {
269 270 271
    char *s = (char *)sr + size + extra;
    sr->sr_refcount = 0;
    sr->sr_name = memcpy(s, template->sr_name, name_length);
272
    sr->sr_name[name_length] = '\0';
273
    memcpy(&sr->sr_status, &template->sr_status,
274
	   size - offsetof(sres_common_t, r_status));
275 276 277 278 279
  }
    
  return sr;
}

280 281 282 283 284 285 286 287 288 289 290
/** Free a record that has not been stored. */
void sres_cache_free_record(sres_cache_t *cache, void *rr)
{
  sres_record_t *sr = rr;

  if (sr) {
    assert(sr->sr_refcount == 0);
    su_free(cache->cache_home, rr);
  }
}

291 292 293 294 295 296 297 298 299 300
/** Store record to cache */
void 
sres_cache_store(sres_cache_t *cache, sres_record_t *rr, time_t now)
{
  sres_rr_hash_entry_t **rr_iter, *rr_hash_entry;
  unsigned hash;

  if (rr == NULL)
    return;

301 302 303 304 305
  hash = sres_hash_key(rr->sr_name);

  if (!LOCK(cache))
    return;

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
  if (sres_htable_is_full(cache->cache_hash))
    sres_htable_resize(cache->cache_home, cache->cache_hash, 0);

  for (rr_iter = sres_htable_hash(cache->cache_hash, hash);
       (rr_hash_entry = *rr_iter); 
       rr_iter = sres_htable_next(cache->cache_hash, rr_iter)) {
    sres_record_t *or = rr_hash_entry->rr;

    if (or == NULL)
      continue;
    if (rr_hash_entry->rr_hash_key != hash)
      continue;
    if (or->sr_type != rr->sr_type)
      continue;
    if (!!or->sr_name != !!rr->sr_name)
      continue;
    if (or->sr_name != rr->sr_name && 
	strcasecmp(or->sr_name, rr->sr_name) != 0)
      continue;
    if (rr->sr_type != sres_type_soa /* There can be only one */
	&& sres_record_compare(or, rr))
      continue;
    
    /* There was an old entry in the cache.. Zap it, replace this with it */
    rr_hash_entry->rr_received = now;
    rr_hash_entry->rr = rr;
    rr->sr_refcount++;
    
    _sres_cache_free_one(cache, or);
335 336 337

    UNLOCK(cache);

338 339 340 341 342 343 344 345 346 347 348 349 350 351
    return;
  }
  
  rr_hash_entry = su_zalloc(cache->cache_home, sizeof(*rr_hash_entry));
  if (rr_hash_entry) {
    rr_hash_entry->rr_hash_key = hash;
    rr_hash_entry->rr_received = now;
    rr_hash_entry->rr = rr;
    rr->sr_refcount++;

    cache->cache_hash->ht_used++;
  }
  
  *rr_iter = rr_hash_entry;
352 353

  UNLOCK(cache);
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
}

/** Free the list records. */
void sres_cache_free_answers(sres_cache_t *cache, sres_record_t **answers)
{
  if (answers && LOCK(cache)) {
      _sres_cache_free_answers(cache, answers);
    UNLOCK(cache);
  }
}

/** Free and zero one record. */
void sres_cache_free_one(sres_cache_t *cache, sres_record_t *answer)
{
  if (LOCK(cache)) {
    _sres_cache_free_one(cache, answer);
    UNLOCK(cache);
  }
}

/* ---------------------------------------------------------------------- */
/* Private functions */

static inline
void _sres_cache_free_answers(sres_cache_t *cache, sres_record_t **answers)
{
  int i;

  for (i = 0; answers[i] != NULL; i++) {
    if (answers[i]->sr_refcount <= 1)
      su_free(cache->cache_home, answers[i]);
    else 
      answers[i]->sr_refcount--;
    answers[i] = NULL;
  }

  su_free(cache->cache_home, answers);
}

static inline
void _sres_cache_free_one(sres_cache_t *cache, sres_record_t *answer)
{
  if (answer) {
    if (answer->sr_refcount <= 1)
      su_free(cache->cache_home, answer);
    else 
      answer->sr_refcount--;
  }
}

/** Calculate a hash key for a string */
static
unsigned
sres_hash_key(const char *string)
{
  unsigned int result = 0;
  
  while (string && *string)
    result = result * 797 + (unsigned char) * (string++);

  if (result == 0)
    result--;

  return result;
}

void sres_cache_clean(sres_cache_t *cache, time_t now)
{
422
  size_t i;
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448

  if (now < cache->cache_cleaned + SRES_CACHE_TIMER_INTERVAL)
    return;

  if (!LOCK(cache))
    return;

  /* Clean cache from old entries */
  cache->cache_cleaned = now;

  for (i = 0; i < cache->cache_hash->ht_size; i++) {
    sres_rr_hash_entry_t *e;
      
    while ((e = cache->cache_hash->ht_table[i]) != NULL) {
      if (now - e->rr_received <= e->rr->sr_ttl)
	break;
	
      sres_htable_remove(cache->cache_hash, e);
      
      _sres_cache_free_one(cache, e->rr);
    }
  }

  UNLOCK(cache);
}

449 450
HTABLE_BODIES_WITH(sres_htable, ht, sres_rr_hash_entry_t, SRES_HENTRY_HASH,
		   unsigned, size_t);