sres_cache.c 15.8 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
/*
 * 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.
27
 *
Pekka Pessi's avatar
Pekka Pessi committed
28 29 30 31 32 33 34
 * @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
#else
42 43 44 45 46
#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>
67
#include <sofia-sip/su_string.h>
Pekka Pessi's avatar
Pekka Pessi committed
68
#include <sofia-sip/htable.h>
69
#include <sofia-sip/heap.h>
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

#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;

89
HTABLE_DECLARE_WITH(sres_htable, ht, sres_rr_hash_entry_t, unsigned, size_t);
90

91
typedef HEAP_TYPE sres_heap_t;
92

93
HEAP_DECLARE(static inline, sres_heap_t, sres_heap_, sres_rr_hash_entry_t *);
94

95 96
struct sres_rr_hash_entry_s {
  sres_record_t *rr;
97 98 99
  size_t         rr_heap_index;
  time_t         rr_expires;
  unsigned int   rr_hash_key;
100 101 102 103
};

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

104 105 106
/* ---------------------------------------------------------------------- */
/* Heap */

107
struct sres_cache
108 109 110 111
{
  su_home_t           cache_home[1];
  time_t              cache_cleaned;
  sres_htable_t       cache_hash[1];
112
  sres_heap_t         cache_heap;
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
};

#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

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

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

130
su_inline
131
void _sres_cache_free_one(sres_cache_t *cache, sres_record_t *answer);
132
su_inline
133
void _sres_cache_free_answers(sres_cache_t *cache, sres_record_t **answers);
134 135
su_inline sres_record_t **_sres_cache_copy_answers(
  sres_cache_t *, sres_record_t **);
136 137 138

static unsigned sres_hash_key(const char *string);

139
HTABLE_PROTOS_WITH(sres_htable, ht, sres_rr_hash_entry_t, unsigned, size_t);
140 141 142 143

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

144 145 146 147
/** Create a resolver cache object.
 *
 * @param n initial size of cache
 */
148
sres_cache_t *sres_cache_new(int n)
149 150 151
{
  sres_cache_t *cache = su_home_new(sizeof *cache);

152 153
  if (cache) {
    su_home_threadsafe(cache->cache_home);
154
    if (sres_htable_resize(cache->cache_home, cache->cache_hash, n) < 0 ||
155
	sres_heap_resize(cache->cache_home, &cache->cache_heap, 0) < 0)
156
      su_home_unref(cache->cache_home), cache = NULL;
157
  }
158 159 160 161

  return cache;
}

162
/** Increase reference count on a resolver cache object. */
163 164 165 166 167
sres_cache_t *sres_cache_ref(sres_cache_t *cache)
{
  return su_home_ref(cache->cache_home);
}

168
/** Decrease the reference count on a resolver cache object. */
169 170 171 172 173
void sres_cache_unref(sres_cache_t *cache)
{
  su_home_unref(cache->cache_home);
}

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 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
struct frame {
  struct frame *previous;
  char const *domain;
};

/** Count or get matching records from cache */
static int
sres_cache_get0(sres_htable_t *htable,
		sres_rr_hash_entry_t **iter,
		uint16_t type,
		char const *domain,
		time_t now,
		sres_record_t **cached,
		int len,
		struct frame *previous)
{
  sres_cname_record_t *cname = NULL;
  int dcount = 0, derrorcount = 0, ccount = 0;

  for (; iter && *iter; iter = sres_htable_next(htable, iter)) {
    sres_record_t *rr = (*iter)->rr;

    if (rr == NULL)
      continue;
    if (now > (*iter)->rr_expires)
      continue;
    if (rr->sr_name == NULL)
      continue;
    if (!su_casematch(rr->sr_name, domain))
      continue;

    if (rr->sr_type == type || type == sres_qtype_any) {
      if (rr->sr_status == SRES_RECORD_ERR && type == sres_qtype_any)
	continue;
      if (cached) {
	if (dcount >= len)
	  return -1;
	cached[dcount] = rr, rr->sr_refcount++;
      }
      dcount++;
      if (rr->sr_status)
	derrorcount++;
    }

    if (type != sres_type_cname && rr->sr_type == sres_type_cname) {
      if (rr->sr_status == 0)
	cname = rr->sr_cname;
    }
  }

  if (cname && dcount == derrorcount) {
    /* Nothing found, trace CNAMEs */
Kai Samposalo's avatar
Kai Samposalo committed
226 227 228 229 230 231
    unsigned hash;
    struct frame *f, frame;
    frame.previous = previous;
    frame.domain = domain;

    hash = sres_hash_key(domain = cname->cn_cname);
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

    /* Check for cname loops */
    for (f = previous; f; f = f->previous) {
      if (su_casematch(domain, f->domain))
	break;
    }

    if (f == NULL) {
      ccount = sres_cache_get0(htable, sres_htable_hash(htable, hash),
			       type, domain, now,
			       cached ? cached + dcount : NULL,
			       cached ? len - dcount : 0,
			       &frame);
    }
    if (ccount < 0)
      return ccount;
  }

  return dcount + ccount;
}

253 254 255 256 257 258
/** 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)
{
259 260 261
  sres_record_t **result = NULL;
  sres_rr_hash_entry_t **slot;
  int result_size, i, j;
262 263
  unsigned hash;
  time_t now;
Pekka Pessi's avatar
Pekka Pessi committed
264
  char b[8];
265 266 267 268 269 270

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

  *return_cached = NULL;

Pekka Pessi's avatar
Pekka Pessi committed
271
  SU_DEBUG_9(("%s(%p, %s, \"%s\") called\n", "sres_cache_get",
272
	      (void *)cache, sres_record_type(type, b), domain));
273 274 275 276 277 278 279 280 281

  hash = sres_hash_key(domain);

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

  time(&now);

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

284 285 286
  i = sres_cache_get0(cache->cache_hash, slot, type, domain, now,
		      NULL, 0, NULL);
  if (i <= 0) {
287 288 289 290
    UNLOCK(cache);
    return 0;
  }

291
  result_size = (sizeof *result) * (i + 1);
292 293 294 295 296 297 298
  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 */
299 300 301 302 303 304 305 306 307
  j = sres_cache_get0(cache->cache_hash, slot, type, domain, now,
		      result, i, NULL);
  if (i != j) {
    /* Uh-oh. */
    SU_DEBUG_9(("%s(%p, %s, \"%s\") got %d != %d\n", "sres_cache_get",
		(void *)cache, sres_record_type(type, b), domain, i, j));
    for (i = 0; i < result_size; i++) {
      if (result[i])
	result[i]->sr_refcount--;
308
    }
309 310
    su_free(cache->cache_home, result);
    return 0;
311 312
  }

313
  result[i] = NULL;
314 315 316

  UNLOCK(cache);

317
  SU_DEBUG_9(("%s(%p, %s, \"%s\") returned %d entries\n", "sres_cache_get",
318
	      (void *)cache, sres_record_type(type, b), domain, i));
319 320 321

  *return_cached = result;

322
  return i;
323 324 325
}

sres_record_t *
326 327 328
sres_cache_alloc_record(sres_cache_t *cache,
			sres_record_t const *template,
			size_t extra)
329 330
{
  sres_record_t *sr;
331
  size_t size, name_length;
332

333 334 335 336 337 338
  size = template->sr_size;

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

  name_length = strlen(template->sr_name);
339

340
  sr = su_alloc(cache->cache_home, size + extra + name_length + 1);
341 342

  if (sr) {
343 344 345
    char *s = (char *)sr + size + extra;
    sr->sr_refcount = 0;
    sr->sr_name = memcpy(s, template->sr_name, name_length);
346
    sr->sr_name[name_length] = '\0';
347
    memcpy(&sr->sr_status, &template->sr_status,
348
	   size - offsetof(sres_common_t, r_status));
349
  }
350

351 352 353
  return sr;
}

354
/** Free a record that has not been stored. */
355
void sres_cache_free_record(sres_cache_t *cache, void *_sr)
356
{
357
  sres_record_t *sr = _sr;
358 359 360

  if (sr) {
    assert(sr->sr_refcount == 0);
361
    su_free(cache->cache_home, sr);
362 363 364
  }
}

365
/** Store record to cache */
366
void
367 368 369 370 371 372 373 374
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;

375 376 377 378 379
  hash = sres_hash_key(rr->sr_name);

  if (!LOCK(cache))
    return;

380 381 382
  if (sres_htable_is_full(cache->cache_hash))
    sres_htable_resize(cache->cache_home, cache->cache_hash, 0);

383
  if (sres_heap_is_full(cache->cache_heap))
384
    if (sres_heap_resize(cache->cache_home, &cache->cache_heap, 0) < 0) {
385 386 387 388
      UNLOCK(cache);
      return;
    }

389
  for (rr_iter = sres_htable_hash(cache->cache_hash, hash);
390
       (rr_hash_entry = *rr_iter);
391 392 393 394 395 396 397 398 399 400 401
       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;
402
    if (or->sr_name != rr->sr_name &&
403
	!su_casematch(or->sr_name, rr->sr_name))
404 405 406 407
      continue;
    if (rr->sr_type != sres_type_soa /* There can be only one */
	&& sres_record_compare(or, rr))
      continue;
408

409
    /* There was an old entry in the cache.. Zap it, replace this with it */
410 411
    sres_heap_remove(cache->cache_heap, rr_hash_entry->rr_heap_index);
    rr_hash_entry->rr_expires = now + rr->sr_ttl;
412 413
    rr_hash_entry->rr = rr;
    rr->sr_refcount++;
414 415
    sres_heap_add(cache->cache_heap, rr_hash_entry);

416
    _sres_cache_free_one(cache, or);
417 418 419

    UNLOCK(cache);

420 421
    return;
  }
422

423
  rr_hash_entry = su_zalloc(cache->cache_home, sizeof(*rr_hash_entry));
424

425 426
  if (rr_hash_entry) {
    rr_hash_entry->rr_hash_key = hash;
427
    rr_hash_entry->rr_expires = now + rr->sr_ttl;
428 429 430
    rr_hash_entry->rr = rr;
    rr->sr_refcount++;

431 432
    sres_heap_add(cache->cache_heap, rr_hash_entry);

433
    cache->cache_hash->ht_used++;
434 435

    *rr_iter = rr_hash_entry;
436
  }
437 438

  UNLOCK(cache);
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
}

/** 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);
  }
}

459 460 461 462 463 464 465 466 467 468 469 470 471 472
/** Copy the list of records. */
sres_record_t **
sres_cache_copy_answers(sres_cache_t *cache, sres_record_t **answers)
{
  sres_record_t **copy = NULL;

  if (answers && LOCK(cache)) {
    copy = _sres_cache_copy_answers(cache, answers);
    UNLOCK(cache);
  }

  return copy;
}

473 474 475
/* ---------------------------------------------------------------------- */
/* Private functions */

476
su_inline
477 478 479 480 481 482 483
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]);
484
    else
485 486 487 488 489 490 491
      answers[i]->sr_refcount--;
    answers[i] = NULL;
  }

  su_free(cache->cache_home, answers);
}

492
su_inline
493 494 495 496 497
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);
498
    else
499 500 501 502
      answer->sr_refcount--;
  }
}

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
su_inline sres_record_t **
_sres_cache_copy_answers(sres_cache_t *cache, sres_record_t **answers)
{
  int i, n;
  sres_record_t **copy;

  for (n = 0; answers[n] != NULL; n++)
    ;

  copy = su_alloc(cache->cache_home, (n + 1) * (sizeof *copy));
  if (copy == NULL)
    return NULL;

  for (i = 0; i < n; i++) {
    copy[i] = answers[i];
    copy[i]->sr_refcount++;
  }

  copy[i] = NULL;

  return copy;
}

526 527 528 529 530 531
/** Calculate a hash key for a string */
static
unsigned
sres_hash_key(const char *string)
{
  unsigned int result = 0;
532

533 534 535 536 537 538 539 540 541
  while (string && *string)
    result = result * 797 + (unsigned char) * (string++);

  if (result == 0)
    result--;

  return result;
}

542 543 544 545 546 547 548
/** Remove old records from cache.
 *
 * Remove entries older than @a now from the cache.
 *
 * @param cache    pointer to DNS cache object
 * @param now      remove older than this time
 */
549 550
void sres_cache_clean(sres_cache_t *cache, time_t now)
{
551
  size_t i;
552 553 554 555 556 557

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

  /* Clean cache from old entries */

558 559 560 561 562 563
  for (;;) {
    if (!LOCK(cache))
      return;

    cache->cache_cleaned = now;

564
    for (i = 0; i < 100; i++) {
565
      sres_rr_hash_entry_t *e = sres_heap_get(cache->cache_heap, 1);
566

567
      if (e == NULL || e->rr_expires >= now) {
568 569 570
	UNLOCK(cache);
	return;
      }
571

572
      sres_heap_remove(cache->cache_heap, 1);
573 574
      sres_htable_remove(cache->cache_hash, e);
      _sres_cache_free_one(cache, e->rr);
575
      su_free(cache->cache_home, e);
576
    }
577 578

    UNLOCK(cache);
579 580 581
  }
}

582 583 584 585 586 587 588
/** Set the priority of the matching cached SRV record.
 *
 * The SRV records with the domain name, target and port are matched and
 * their priority value is adjusted. This function is used to implement
 * greylisting of SIP servers.
 *
 * @param cache    pointer to DNS cache object
589
 * @param domain   domain name of the SRV record(s) to modify
590 591
 *                 (including final dot)
 * @param target   SRV target of the SRV record(s) to modify
592 593
 * @param port     port number of SRV record(s) to modify
 *                 (in host byte order)
594
 * @param ttl      new ttl
595 596 597
 * @param priority new priority value (0=highest, 65535=lowest)
 *
 * @sa sres_set_cached_srv_priority()
598
 *
599 600
 * @NEW_1_12_8
 */
601 602 603 604
int sres_cache_set_srv_priority(sres_cache_t *cache,
				char const *domain,
				char const *target,
				uint16_t port,
605
				uint32_t ttl,
606
				uint16_t priority)
607 608 609 610
{
  int ret = 0;
  unsigned hash;
  sres_rr_hash_entry_t **iter;
611
  time_t expires;
612

613 614 615 616 617 618 619 620
  if (cache == NULL || domain == NULL || target == NULL)
    return -1;

  hash = sres_hash_key(domain);

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

621 622 623
  time(&expires);
  expires += ttl;

624 625 626 627
  for (iter = sres_htable_hash(cache->cache_hash, hash);
       iter && *iter;
       iter = sres_htable_next(cache->cache_hash, iter)) {
    sres_record_t *rr = (*iter)->rr;
628

629 630
    if (rr && rr->sr_name &&
	sres_type_srv == rr->sr_type &&
631
	su_casematch(rr->sr_name, domain)) {
632 633

      (*iter)->rr_expires = expires;
634

635 636
      if ((port == 0 || rr->sr_srv->srv_port == port) &&
	  rr->sr_srv->srv_target &&
637
	  su_casematch(rr->sr_srv->srv_target, target)) {
638 639 640 641
	/* record found --> change priority of server */
	rr->sr_srv->srv_priority = priority;
	ret++;
      }
642 643 644 645 646 647 648 649 650
    }
  }

  UNLOCK(cache);

  /** @return number of modified entries or -1 upon an error. */
  return ret;
}

651 652
HTABLE_BODIES_WITH(sres_htable, ht, sres_rr_hash_entry_t, SRES_HENTRY_HASH,
		   unsigned, size_t);
653 654

static inline
655 656
int sres_heap_earlier_entry(sres_rr_hash_entry_t const *a,
			    sres_rr_hash_entry_t const *b)
657
{
658
  return a->rr_expires < b->rr_expires;
659 660 661 662
}

static inline
void sres_heap_set_entry(sres_rr_hash_entry_t **heap,
663
			 size_t index,
664 665 666 667
			 sres_rr_hash_entry_t *entry)
{
  entry->rr_heap_index = index;
  heap[index] = entry;
668
}
669

670 671 672
HEAP_BODIES(static inline,
	    sres_heap_t,
	    sres_heap_,
673
	    sres_rr_hash_entry_t *,
674
	    sres_heap_earlier_entry,
675
	    sres_heap_set_entry,
676 677
	    su_realloc,
	    NULL);