dns_cache.c
dcb59e67
 /*
  * $Id$
  *
  * resolver related functions
  *
  * Copyright (C) 2006 iptelorg GmbH
  *
  * This file is part of ser, a free SIP server.
  *
  * ser is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version
  *
  * For a license to use the ser software under conditions
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
  * ser 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 General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License 
  * along with this program; if not, write to the Free Software 
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 /* History:
  * --------
  *  2006-07-13  created by andrei
8af0841f
  *  2006-10-06  port fix (andrei)
dcb59e67
  */
 
 #ifdef USE_DNS_CACHE
 
 #include "globals.h"
 #include "dns_cache.h"
 #include "dns_wrappers.h"
 #include "mem/shm_mem.h"
 #include "hashes.h"
 #include "clist.h"
 #include "locking.h"
 #include "atomic_ops.h"
 #include "ut.h"
 #include "timer.h"
 #include "timer_ticks.h"
 #include "error.h"
 #include "rpc.h"
 
 
 
 #define DNS_CACHE_DEBUG /* extra sanity checks and debugging */
 
 
 #ifndef MAX
 	#define MAX(a,b) ( ((a)>(b))?(a):(b))
 #endif
 
 #define MAX_DNS_RECORDS 255  /* maximum dns records number  received in a 
 							   dns answer*/
 
 #define DNS_HASH_SIZE	1024 /* must be <= 65535 */
 #define DEFAULT_DNS_NEG_CACHE_TTL 60 /* 1 min. */
 #define DEFAULT_DNS_CACHE_MIN_TTL 0 /* (disabled) */
 #define DEFAULT_DNS_CACHE_MAX_TTL ((unsigned int)(-1)) /* (maxint) */
 #define DEFAULT_DNS_MAX_MEM 500 /* 500 Kb */
 #define DEFAULT_DNS_TIMER_INTERVAL 120  /* 2 min. */
 #define DNS_HE_MAX_ADDR 10  /* maxium addresses returne in a hostent struct */
 #define MAX_CNAME_CHAIN  10 
 
 
 static gen_lock_t* dns_hash_lock=0;
 static volatile unsigned int *dns_cache_mem_used=0; /* current mem. use */
 unsigned int dns_cache_max_mem=DEFAULT_DNS_MAX_MEM; /* maximum memory used for
 													 the cached entries */
 unsigned int dns_neg_cache_ttl=DEFAULT_DNS_NEG_CACHE_TTL; /* neg. cache ttl */
 unsigned int dns_cache_max_ttl=DEFAULT_DNS_CACHE_MAX_TTL; /* maximum ttl */
 unsigned int dns_cache_min_ttl=DEFAULT_DNS_CACHE_MIN_TTL; /* minimum ttl */
 unsigned int dns_timer_interval=DEFAULT_DNS_TIMER_INTERVAL; /* in s */
 int dns_flags=0; /* default flags used for the  dns_*resolvehost 
                     (compatibility wrappers) */
 
 #define LOCK_DNS_HASH()		lock_get(dns_hash_lock)
 #define UNLOCK_DNS_HASH()	lock_release(dns_hash_lock)
 
 #define FIX_TTL(t)  (((t)<dns_cache_min_ttl)?dns_cache_min_ttl: \
 						(((t)>dns_cache_max_ttl)?dns_cache_max_ttl:(t)))
 
 
 struct dns_hash_head{
 	struct dns_hash_entry* next;
 	struct dns_hash_entry* prev;
 };
 
 #ifdef DNS_LU_LST
 struct dns_lu_lst* dns_last_used_lst=0;
 #endif
 
 static struct dns_hash_head* dns_hash=0;
 
 
 static struct timer_ln* dns_timer_h=0;
 
 
 
 static const char* dns_str_errors[]={
 	"no error",
 	"no more records", /* not an error, but and end condition */
 	"unknown error",
 	"internal error",
 	"bad SRV entry",
 	"unresolvable SRV request",
 	"bad A or AAAA entry",
 	"unresovlable A or AAAA request",
 	"invalid ip in A or AAAA record",
 	"blacklisted ip",
 	"name too long ", /* try again with a shorter name */
 	"ip AF mismatch", /* address family mismatch */
 	"bug - critical error"
 };
 
 
 
 /* param: err (negative error number) */
 const char* dns_strerror(int err)
 {
 	err=-err;
 	if ((err>=0) && (err<sizeof(dns_str_errors)/sizeof(char*)))
 		return dns_str_errors[err];
 	return "bug -- bad error number";
 }
 
 
 
 /* "internal" only, don't use unless you really know waht you're doing */
 inline static void dns_destroy_entry(struct dns_hash_entry* e)
 {
 #ifdef DNS_CACHE_DEBUG
 	memset(e, 0, e->total_size);
 #endif
 	shm_free(e); /* nice having it in one block isn't it? :-) */
 }
 
 
 /* "internal" only, same as above, asumes shm_lock() held (tm optimization) */
 inline static void dns_destroy_entry_shm_unsafe(struct dns_hash_entry* e)
 {
 #ifdef DNS_CACHE_DEBUG
 	memset(e, 0, e->total_size);
 #endif
 	shm_free_unsafe(e); /* nice having it in one block isn't it? :-) */
 }
 
 
 
 /* dec. the internal refcnt and if 0 deletes the entry */
 void dns_hash_put(struct dns_hash_entry* e)
 {
 	if(e && atomic_dec_and_test(&e->refcnt)){
 		/* atomic_sub_long(dns_cache_total_used, e->total_size); */
 		dns_destroy_entry(e);
 	}
 }
 
 
 
 /* same as above but uses dns_destroy_unsafe (assumes shm_lock held -- tm
  *  optimization) */
 void dns_hash_put_shm_unsafe(struct dns_hash_entry* e)
 {
 	if(e && atomic_dec_and_test(&e->refcnt)){
 		/* atomic_sub_long(dns_cache_total_used, e->total_size); */
 		dns_destroy_entry_shm_unsafe(e);
 	}
 }
 
 
 inline static int dns_cache_clean(unsigned int no, int expired_only);
 inline static int dns_cache_free_mem(unsigned int target, int expired_only);
 
 static ticks_t dns_timer(ticks_t ticks, struct timer_ln* tl, void* data)
 {
 	if (*dns_cache_mem_used>12*(dns_cache_max_mem/16)){ /* ~ 75% used */
 		dns_cache_free_mem(dns_cache_max_mem/2, 1); 
 	}else{
 		dns_cache_clean(-1, 1); /* all the table, only expired entries */
 		/* TODO: better strategy? */
 	}
 	return (ticks_t)(-1);
 }
 
 
 
 void destroy_dns_cache()
 {
 	if (dns_timer_h){
 		timer_del(dns_timer_h);
 		timer_free(dns_timer_h);
 		dns_timer_h=0;
 	}
 	if (dns_hash_lock){
 		lock_destroy(dns_hash_lock);
 		lock_dealloc(dns_hash_lock);
 		dns_hash_lock=0;
 	}
 	if (dns_hash){
 		shm_free(dns_hash);
 		dns_hash=0;
 	}
 #ifdef DNS_LU_LST
 	if (dns_last_used_lst){
 		shm_free(dns_last_used_lst);
 		dns_last_used_lst=0;
 	}
 #endif
 	if (dns_cache_mem_used){
 		shm_free((void*)dns_cache_mem_used);
 		dns_cache_mem_used=0;
 	}
 }
 
 
 
 int init_dns_cache()
 {
 	int r;
 	int ret;
 	
 	ret=0;
 	/* sanity check */
 	if (E_DNS_CRITICAL>=sizeof(dns_str_errors)/sizeof(char*)){
 		LOG(L_CRIT, "BUG: dns_cache_init: bad dns error table\n");
 		ret=E_BUG;
 		goto error;
 	}
 	dns_cache_mem_used=shm_malloc(sizeof(*dns_cache_mem_used));
 	if (dns_cache_mem_used==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 #ifdef DNS_LU_LST
 	dns_last_used_lst=shm_malloc(sizeof(*dns_last_used_lst));
 	if (dns_last_used_lst==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 	clist_init(dns_last_used_lst, next, prev);
 #endif
 	dns_hash=shm_malloc(sizeof(struct dns_hash_head)*DNS_HASH_SIZE);
 	if (dns_hash==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 	for (r=0; r<DNS_HASH_SIZE; r++)
 		clist_init(&dns_hash[r], next, prev);
 	
 	dns_hash_lock=lock_alloc();
 	if (dns_hash_lock==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 	if (lock_init(dns_hash_lock)==0){
 		lock_dealloc(dns_hash_lock);
 		dns_hash_lock=0;
 		ret=-1;
 		goto error;
 	}
 	
 	/* fix options */
 	dns_cache_max_mem<<=10; /* Kb */ /* TODO: test with 0 */
 	/* fix flags */
 	if (dns_try_ipv6==0){
 		dns_flags|=DNS_IPV4_ONLY;
 	}
 	if (dns_flags & DNS_IPV4_ONLY){
 		dns_flags&=~(DNS_IPV6_ONLY|DNS_IPV6_FIRST);
 	}
 			;
 	dns_timer_h=timer_alloc();
 	if (dns_timer_h==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 	if (dns_timer_interval){
 		timer_init(dns_timer_h, dns_timer, 0, 0); /* "slow" timer */
 		if (timer_add(dns_timer_h, S_TO_TICKS(dns_timer_interval))<0){
 			LOG(L_CRIT, "BUG: dns_cache_init: failed to add the timer\n");
 			timer_free(dns_timer_h);
 			dns_timer_h=0;
 			goto error;
 		}
 	}
 	
 	return 0;
 error:
 	destroy_dns_cache();
 	return ret;
 }
 
 
57ff54fe
 /* hash function, type is not used (obsolete)
  * params: char* s, int len, int type
dcb59e67
  * returns the hash value
  */
57ff54fe
 #define dns_hash_no(s, len, type) \
 	(get_hash1_case_raw((s),(len)) % DNS_HASH_SIZE)
dcb59e67
 
 
 
 #ifdef DNS_CACHE_DEBUG
 #define DEBUG_LU_LST
 #ifdef DEBUG_LU_LST
 
 #include <stdlib.h> /* abort() */
 #define check_lu_lst(l) ((((l)->next==(l)) || ((l)->prev==(l))) && \
 							((l)!=dns_last_used_lst))
 
 #define dbg_lu_lst(txt, l) \
 		LOG(L_CRIT, "BUG: %s: crt(%p, %p, %p)," \
 					" prev(%p, %p, %p), next(%p, %p, %p)\n", txt, \
 					(l), (l)->next, (l)->prev, \
 					(l)->prev, (l)->prev->next, (l)->prev->prev, \
 					(l)->next, (l)->next->next, (l)->next->prev \
 				) 
 
 #define debug_lu_lst( txt, l) \
 	do{ \
 		if (check_lu_lst((l))){  \
 			dbg_lu_lst(txt  " crt:", (l)); \
 			abort(); \
 		} \
 		if (check_lu_lst((l)->next)){ \
 			dbg_lu_lst(txt  " next:",  (l)); \
 			abort(); \
 		} \
 		if (check_lu_lst((l)->prev)){ \
 			dbg_lu_lst(txt  " prev:", (l)); \
 			abort(); \
 		} \
 	}while(0)
 
 #endif
 #endif /* DNS_CACHE_DEBUG */
 
 
 /* must be called with the DNS_LOCK hold
  * remove and entry from the hash, dec. its refcnt and if not referenced
  * anymore deletes it */
 void _dns_hash_remove(struct dns_hash_entry* e)
 {
 	clist_rm(e, next, prev);
 #ifdef DNS_CACHE_DEBUG
 	e->next=e->prev=0;
 #endif
 #ifdef DNS_LU_LST
 #ifdef DEBUG_LU_LST
 	debug_lu_lst("_dns_hash_remove: pre rm:", &e->last_used_lst);
 #endif
 	clist_rm(&e->last_used_lst, next, prev);
 #ifdef DEBUG_LU_LST
 	debug_lu_lst("_dns_hash_remove: post rm:", &e->last_used_lst);
 #endif
 #ifdef DNS_CACHE_DEBUG
 	e->last_used_lst.next=e->last_used_lst.prev=0;
 #endif
 #endif
 	*dns_cache_mem_used-=e->total_size;
 	dns_hash_put(e);
 }
 
 
 
 /* non locking  version (the dns hash must _be_ locked externally)
  * returns 0 when not found, or the entry on success (an entry with a
  * similar name but with a CNAME type will always match).
  * it doesn't increase the internal refcnt
  * returns the entry when found, 0 when not found and sets *err to !=0
  *  on error (e.g. recursive cnames)
  * WARNING: - internal use only
  *          - always check if the returned entry type is CNAME */
 inline static struct dns_hash_entry* _dns_hash_find(str* name, int type,
 														int* h, int* err)
 {
 	struct dns_hash_entry* e;
 	struct dns_hash_entry* tmp;
 	struct dns_hash_entry* ret;
 	ticks_t now;
 	int cname_chain;
 	str cname;
 	
 	cname_chain=0;
 	ret=0;
 	now=get_ticks_raw();
 	*err=0;
 again:
 	*h=dns_hash_no(name->s, name->len, type);
 	DBG("dns_hash_find(%.*s(%d), %d), h=%d\n", name->len, name->s,
 												name->len, type, *h);
 	clist_foreach_safe(&dns_hash[*h], e, tmp, next){
 		/* automatically remove expired elements */
 		if ((s_ticks_t)(now-e->expire)>=0){
 				_dns_hash_remove(e);
 		}else if ((e->type==type) && (e->name_len==name->len) &&
 			(strncasecmp(e->name, name->s, e->name_len)==0)){
 			e->last_used=now;
 #ifdef DNS_LU_LST
 			/* add it at the end */
 #ifdef DEBUG_LU_LST
 			debug_lu_lst("_dns_hash_find: pre rm:", &e->last_used_lst);
 #endif
 			clist_rm(&e->last_used_lst, next, prev);
 			clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
 #ifdef DEBUG_LU_LST
 			debug_lu_lst("_dns_hash_find: post append:", &e->last_used_lst);
 #endif
 #endif
 			return e;
 		}else if ((e->type==T_CNAME) && (e->name_len==name->len) &&
 			(strncasecmp(e->name, name->s, e->name_len)==0)){
 			e->last_used=now;
 #ifdef DNS_LU_LST
 			/* add it at the end */
 #ifdef DEBUG_LU_LST
 			debug_lu_lst("_dns_hash_find: cname: pre rm:", &e->last_used_lst);
 #endif
 			clist_rm(&e->last_used_lst, next, prev);
 			clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
 #ifdef DEBUG_LU_LST
 			debug_lu_lst("_dns_hash_find: cname: post append:",
 							&e->last_used_lst);
 #endif
 #endif		
 			ret=e; /* if this is an unfinished cname chain, we try to
 					  return the last cname */
 			/* this is a cname => retry using its value */
 			if (cname_chain> MAX_CNAME_CHAIN){
 				LOG(L_ERR, "ERROR: _dns_hash_find: cname chain too long "
 						"or recursive (\"%.*s\")\n", name->len, name->s);
 				ret=0; /* error*/
 				*err=-1;
 				break;
 			}
 			cname_chain++;
 			cname.s=((struct cname_rdata*)e->rr_lst->rdata)->name;
 			cname.len= ((struct cname_rdata*)e->rr_lst->rdata)->name_len;
 			name=&cname;
 			goto again;
 		}
 	}
 	return ret;
 }
 
 
 
 /* frees cache entries, if expired_only=0 only expired entries will be 
  * removed, else all of them
  * it will process maximum no entries (to process all of them use -1)
  * returns the number of deleted entries
  * This should be called from a timer process*/
 inline static int dns_cache_clean(unsigned int no, int expired_only)
 {
 	struct dns_hash_entry* e;
 	ticks_t now;
 	unsigned int n;
 	unsigned int deleted;
 #ifdef DNS_LU_LST
 	struct dns_lu_lst* l;
 	struct dns_lu_lst* tmp;
 #else
 	struct dns_hash_entry* t;
 	unsigned int h;
 	static unsigned int start=0;
 #endif
 	
 	n=0;
 	deleted=0;
 	now=get_ticks_raw();
 	LOCK_DNS_HASH();
 #ifdef DNS_LU_LST
 	clist_foreach_safe(dns_last_used_lst, l, tmp, next){
 		e=(struct dns_hash_entry*)(((char*)l)-
 				(char*)&((struct dns_hash_entry*)(0))->last_used_lst);
 		if (!expired_only || ((s_ticks_t)(now-e->expire)>=0)){
 				_dns_hash_remove(e);
 				deleted++;
 		}
 		n++;
 		if (n>=no) break;
 	}
 #else
 	for(h=start; h!=(start+DNS_HASH_SIZE); h++){
 		clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
 			if  ((s_ticks_t)(now-e->expire)>=0){
 				_dns_hash_remove(e);
 				deleted++;
 			}
 			n++;
 			if (n>=no) break;
 		}
 	}
 	/* not fair, but faster then random() */
 	if (!expired_only){
 		for(h=start; h!=(start+DNS_HASH_SIZE); h++){
 			clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
 				if  ((s_ticks_t)(now-e->expire)>=0){
 					_dns_hash_remove(e);
 					deleted++;
 				}
 				n++;
 				if (n>=no) goto skip;
 			}
 		}
 	}
 skip:
 	start=h;
 #endif
 	UNLOCK_DNS_HASH();
 	return deleted;
 }
 
 
 
 /* frees cache entries, if expired_only=0 only expired entries will be 
  * removed, else all of them
  * it will stop when the dns cache used memory reaches target (to process all 
  * of them use 0)
  * returns the number of deleted entries */
 inline static int dns_cache_free_mem(unsigned int target, int expired_only)
 {
 	struct dns_hash_entry* e;
 	ticks_t now;
 	unsigned int deleted;
 #ifdef DNS_LU_LST
 	struct dns_lu_lst* l;
 	struct dns_lu_lst* tmp;
 #else
 	struct dns_hash_entry* t;
 	unsigned int h;
 	static unsigned int start=0;
 #endif
 	
 	deleted=0;
 	now=get_ticks_raw();
 	LOCK_DNS_HASH();
 #ifdef DNS_LU_LST
 	clist_foreach_safe(dns_last_used_lst, l, tmp, next){
 		if (*dns_cache_mem_used<=target) break;
 		e=(struct dns_hash_entry*)(((char*)l)-
 				(char*)&((struct dns_hash_entry*)(0))->last_used_lst);
 		if (!expired_only || ((s_ticks_t)(now-e->expire)>=0)){
 				_dns_hash_remove(e);
 				deleted++;
 		}
 	}
 #else
 	for(h=start; h!=(start+DNS_HASH_SIZE); h++){
 		clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
 			if (*dns_cache_mem_used<=target) 
 				goto skip;
 			if  ((s_ticks_t)(now-e->expire)>=0){
 				_dns_hash_remove(e);
 				deleted++;
 			}
 		}
 	}
 	/* not fair, but faster then random() */
 	if (!expired_only){
 		for(h=start; h!=(start+DNS_HASH_SIZE); h++){
 			clist_foreach_safe(&dns_hash[h%DNS_HASH_SIZE], e, t, next){
 				if (*dns_cache_mem_used<=target) 
 					goto skip;
 				if  ((s_ticks_t)(now-e->expire)>=0){
 					_dns_hash_remove(e);
 					deleted++;
 				}
 			}
 		}
 	}
 skip:
 	start=h;
 #endif
 	UNLOCK_DNS_HASH();
 	return deleted;
 }
 
 
 
 /* locking  version (the dns hash must _not_be locked externally)
  * returns 0 when not found, the searched entry on success (with CNAMEs
  *  followed) or the last CNAME entry from an unfinished CNAME chain, 
  *  if the search matches a CNAME. On error sets *err (e.g. recursive CNAMEs).
  * it increases the internal refcnt => when finished dns_hash_put() must
  *  be called on the returned entry
  *  WARNING: - the return might be a CNAME even if type!=CNAME, see above */
 inline static struct dns_hash_entry* dns_hash_get(str* name, int type, int* h,
 													int* err)
 {
 	struct dns_hash_entry* e;
 	
 	LOCK_DNS_HASH();
 	e=_dns_hash_find(name, type, h, err);
 	if (e){
 		atomic_inc(&e->refcnt);
 	}
 	UNLOCK_DNS_HASH();
 	return e;
 }
 
 
 
 /* adds a fully created and init. entry (see dns_cache_mk_entry()) to the hash
  * table
  * returns 0 on success, -1 on error */
 inline static int dns_cache_add(struct dns_hash_entry* e)
 {
 	int h;
 	
 	/* check space */
 	/* atomic_add_long(dns_cache_total_used, e->size); */
 	if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
 		LOG(L_WARN, "WARNING: dns_cache_add: cache full, trying to free...\n");
 		/* free ~ 12% of the cache */
 		dns_cache_free_mem(*dns_cache_mem_used/16*14, 1);
 		if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
 			LOG(L_ERR, "ERROR: dns_cache_add: max. cache mem size exceeded\n");
 			return -1;
 		}
 	}
 	atomic_inc(&e->refcnt);
 	h=dns_hash_no(e->name, e->name_len, e->type);
 	DBG("dns_cache_add: adding %.*s(%d) %d (flags=%0x) at %d\n",
 			e->name_len, e->name, e->name_len, e->type, e->err_flags, h);
 	LOCK_DNS_HASH();
 		*dns_cache_mem_used+=e->total_size; /* no need for atomic ops, written
 										 only from within a lock */
 		clist_append(&dns_hash[h], e, next, prev);
 #ifdef DNS_LU_LST
 		clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
 #endif
 	UNLOCK_DNS_HASH();
 	return 0;
 }
 
 
 
 /* same as above, but it must be called with the dns hash lock held
  * returns 0 on success, -1 on error */
 inline static int dns_cache_add_unsafe(struct dns_hash_entry* e)
 {
 	int h;
 	
 	/* check space */
 	/* atomic_add_long(dns_cache_total_used, e->size); */
 	if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
 		LOG(L_WARN, "WARNING: dns_cache_add: cache full, trying to free...\n");
 		/* free ~ 12% of the cache */
 		UNLOCK_DNS_HASH();
 		dns_cache_free_mem(*dns_cache_mem_used/16*14, 1);
 		LOCK_DNS_HASH();
 		if ((*dns_cache_mem_used+e->total_size)>=dns_cache_max_mem){
 			LOG(L_ERR, "ERROR: dns_cache_add: max. cache mem size exceeded\n");
 			return -1;
 		}
 	}
 	atomic_inc(&e->refcnt);
 	h=dns_hash_no(e->name, e->name_len, e->type);
 	DBG("dns_cache_add: adding %.*s(%d) %d (flags=%0x) at %d\n",
 			e->name_len, e->name, e->name_len, e->type, e->err_flags, h);
 	*dns_cache_mem_used+=e->total_size; /* no need for atomic ops, written
 										 only from within a lock */
 	clist_append(&dns_hash[h], e, next, prev);
 #ifdef DNS_LU_LST
 	clist_append(dns_last_used_lst, &e->last_used_lst, next, prev);
 #endif
 	return 0;
 }
 
 
 
 /* creates a "negative" entry which will be valid for ttl seconds */
 inline static struct dns_hash_entry* dns_cache_mk_bad_entry(str* name,
 															int type,
 															int ttl,
 															int flags)
 {
 	struct dns_hash_entry* e;
 	int size;
 	ticks_t now;
 	
 	DBG("dns_cache_mk_bad_entry(%.*s, %d, %d, %d)\n", name->len, name->s,
 									type, ttl, flags);
 	size=sizeof(struct dns_hash_entry)+name->len-1+1;
 	e=shm_malloc(size);
 	if (e==0){
 		LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
 		return 0;
 	}
 	memset(e, 0, size); /* init with 0*/
 	e->total_size=size;
 	e->name_len=name->len;
 	e->type=type;
 	now=get_ticks_raw();
 	e->last_used=now;
 	e->expire=now+S_TO_TICKS(ttl);
 	memcpy(e->name, name->s, name->len);
 	e->err_flags=flags;
 	return e;
 }
 
 
 
 /* create a a/aaaa hash entry from a name and ip address
  * returns 0 on error */
 inline static struct dns_hash_entry* dns_cache_mk_ip_entry(str* name,
 															struct ip_addr* ip)
 {
 	struct dns_hash_entry* e;
 	int size;
 	ticks_t now;
 	
 	/* everything is allocated in one block: dns_hash_entry + name +
 	 * + dns_rr + rdata;  dns_rr must start at an aligned adress,
 	 * hence we need to round dns_hash_entry+name size to a sizeof(long)
 	 * multiple.
 	 * Memory image:
 	 * struct dns_hash_entry
 	 * name (name_len+1 bytes)
 	 * padding to multiple of sizeof(long)
 	 * dns_rr
 	 * rdata  (no padding needed, since for ip is just an array of chars)
 	  */
 	size=ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1)+ 
 			sizeof(struct dns_rr)+ ip->len;
 	e=shm_malloc(size);
 	if (e==0){
 		LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
 		return 0;
 	}
 	memset(e, 0, size); /* init with 0*/
 	e->total_size=size;
 	e->name_len=name->len;
 	e->type=(ip->af==AF_INET)?T_A:T_AAAA;
 	now=get_ticks_raw();
 	e->last_used=now;
 	e->expire=now-1; /* maximum expire */
 	memcpy(e->name, name->s, name->len); /* memset makes sure is 0-term. */
 	e->rr_lst=(void*)((char*)e+
 				ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1));
 	e->rr_lst->rdata=(void*)((char*)e->rr_lst+sizeof(struct dns_rr));
 	e->rr_lst->expire=now-1; /* maximum expire */
 	/* no need to align rr_lst->rdata for a or aaaa records */
 	memcpy(e->rr_lst->rdata, ip->u.addr, ip->len);
 	return e;
 }
 
 
 
 /* create a dns hash entry from a name and a rdata list (pkg_malloc'ed)
  * (it will use only the type records with the name "name" from the
  *  rdata list with one exception: if a matching CNAME with the same
  *  name is found, the search will stop and this will be the record used)
  * returns 0 on error and removes the used elements from the rdata list*/
 inline static struct dns_hash_entry* dns_cache_mk_rd_entry(str* name, int type,
 														struct rdata** rd_lst)
 {
 	struct dns_hash_entry* e;
 	struct dns_rr* rr;
 	struct dns_rr** tail_rr;
 	struct rdata** p;
 	struct rdata* tmp_lst;
 	struct rdata** tail;
 	struct rdata* l;
 	int size;
 	ticks_t now;
 	unsigned int max_ttl;
 	unsigned int ttl;
 	
 #define rec_matches(rec, t, n) /*(struct rdata* record, int type, str* name)*/\
 	(	((rec)->name_len==(n)->len) && ((rec)->type==(t)) && \
 		(strncasecmp((rec)->name, (n)->s, (n)->len)==0))
 	/* init */
 	tmp_lst=0;
 	tail=&tmp_lst;
 	
 	
 	/* everything is allocated in one block: dns_hash_entry + name +
 	 * + dns_rr + rdata_raw+ ....;  dns_rr must start at an aligned adress,
 	 * hence we need to round dns_hash_entry+name size to a sizeof(long)
 	 * multiple. If rdata type requires it, rdata_raw might need to be also
 	 * aligned.
 	 * Memory image:
 	 * struct dns_hash_entry  (e)
 	 * name (name_len+1 bytes)  (&e->name[0])
 	 * padding to multiple of sizeof(char*)
 	 * dns_rr1 (e->rr_lst)
 	 * possible padding: no padding for a_rdata or aaaa_rdata, 
 	 *                   multipe of sizeof(short) for srv_rdata,
 	 *                   multiple of sizeof(long) for naptr_rdata and others
 	 * dns_rr1->rdata  (e->rr_lst->rdata)
 	 * padding to multipe of sizeof long
 	 * dns_rr2 (e->rr_lst->next)
 	 * ....
 	 *
 	 */
 	size=0;
 	if (*rd_lst==0)
 		return 0;
 	/* find the first matching rr, if it's a CNAME use CNAME as type,
 	 * if not continue with the original type */
 	for(p=rd_lst; *p; p=&(*p)->next){
 		if (((*p)->name_len==name->len) &&
 				(((*p)->type==type) || ((*p)->type==T_CNAME)) &&
 				(strncasecmp((*p)->name, name->s, name->len)==0)){
 			type=(*p)->type;
 			break;
 		}
 	}
 	/* continue, we found the type we are looking for */
 	switch(type){
 		case T_A:
 			for(; *p;){
 				if (!rec_matches((*p), type, name)){ 
 					/* skip this record */
 					p=&(*p)->next; /* advance */
 					continue;
 				}
 				size+=ROUND_POINTER(sizeof(struct dns_rr)+
 										sizeof(struct a_rdata));
 				/* add it to our tmp. lst */
 				*tail=*p;
 				tail=&(*p)->next;
 				/* detach it from the rd list */
 				*p=(*p)->next;
 				/* don't advance p, because the crt. elem. has
 				 * just been elimintated */
 			}
 			break;
 		case T_AAAA:
 			for(; *p;){
 				if (!rec_matches((*p), type, name)){ 
 					/* skip this record */
 					p=&(*p)->next; /* advance */
 					continue;
 				}
 				/* no padding */
 				size+=ROUND_POINTER(sizeof(struct dns_rr)+
 											sizeof(struct aaaa_rdata));
 				/* add it to our tmp. lst */
 				*tail=*p;
 				tail=&(*p)->next;
 				/* detach it from the rd list */
 				*p=(*p)->next;
 				/* don't advance p, because the crt. elem. has
 				 * just been elimintated */
 			}
 			break;
 		case T_SRV:
 			for(; *p;){
 				if (!rec_matches((*p), type, name)){ 
 					/* skip this record */
 					p=&(*p)->next; /* advance */
 					continue;
 				}
 				/* padding to short */
 				size+=ROUND_POINTER(ROUND_SHORT(sizeof(struct dns_rr))+
 						SRV_RDATA_SIZE(*(struct srv_rdata*)(*p)->rdata));
 				/* add it to our tmp. lst */
 				*tail=*p;
 				tail=&(*p)->next;
 				/* detach it from the rd list */
 				*p=(*p)->next;
 				/* don't advance p, because the crt. elem. has
 				 * just been elimintated */
 			}
 			break;
 		case T_NAPTR:
 			for(; *p;){
 				if (!rec_matches((*p), type, name)){ 
 					/* skip this record */
 					p=&(*p)->next; /* advance */
 					continue;
 				}
 				/* padding to char* */
 				size+=ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
 						NAPTR_RDATA_SIZE(*(struct naptr_rdata*)(*p)->rdata));
 				/* add it to our tmp. lst */
 				*tail=*p;
 				tail=&(*p)->next;
 				/* detach it from the rd list */
 				*p=(*p)->next;
 				/* don't advance p, because the crt. elem. has
 				 * just been elimintated */
 			}
 			break;
 		case T_CNAME:
 			for(; *p;){
 				if (!rec_matches((*p), type, name)){ 
 					/* skip this record */
 					p=&(*p)->next; /* advance */
 					continue;
 				}
 				/* no padding */
 				size+=ROUND_POINTER(sizeof(struct dns_rr)+
 						CNAME_RDATA_SIZE(*(struct cname_rdata*)(*p)->rdata));
 				/* add it to our tmp. lst */
 				*tail=*p;
 				tail=&(*p)->next;
 				/* detach it from the rd list */
 				*p=(*p)->next;
 				/* don't advance p, because the crt. elem. has
 				 * just been elimintated */
 			}
 			break;
 		default:
 			LOG(L_CRIT, "BUG: dns_cache_mk_rd_entry: type %d not "
 							"supported\n", type);
 			/* we don't know what to do with it, so don't
 			 * add it to the tmp_lst */
 			return 0; /* error */
 	}
 	*tail=0; /* mark the end of our tmp_lst */
 	if (size==0){
 		DBG("dns_cache_mk_rd_entry: entry %.*s (%d) not found\n",
 				name->len, name->s, type);
 		return 0;
 	}
 	/* compute size */
 	size+=ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1);
 	e=shm_malloc(size);
 	if (e==0){
 		LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
 		return 0;
 	}
 	memset(e, 0, size); /* init with 0 */
 	e->total_size=size;
 	e->name_len=name->len;
 	e->type=type;
 	now=get_ticks_raw();
 	e->last_used=now;
 	memcpy(e->name, name->s, name->len); /* memset makes sure is 0-term. */
 	e->rr_lst=(struct dns_rr*)((char*)e+
 				ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1));
 	tail_rr=&(e->rr_lst);
 	rr=e->rr_lst;
 	max_ttl=0;
 	/* copy the actual data */
 	switch(type){
 		case T_A:
 			for(l=tmp_lst; l; l=l->next){
 				ttl=FIX_TTL(l->ttl);
 				rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				max_ttl=MAX(max_ttl, ttl);
 				rr->rdata=(void*)((char*)rr+sizeof(struct dns_rr));
 				memcpy(rr->rdata, l->rdata, sizeof(struct a_rdata));
 				rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
 							sizeof(struct a_rdata)));
 				tail_rr=&(rr->next);
 				rr=rr->next;
 			}
 			break;
 		case T_AAAA:
 			for(l=tmp_lst; l; l=l->next){
 				ttl=FIX_TTL(l->ttl);
 				rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				max_ttl=MAX(max_ttl, ttl);
 				rr->rdata=(void*)((char*)rr+sizeof(struct dns_rr));
 				memcpy(rr->rdata, l->rdata, sizeof(struct aaaa_rdata));
 				rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
 							sizeof(struct aaaa_rdata)));
 				tail_rr=&(rr->next);
 				rr=rr->next;
 			}
 			break;
 		case T_SRV:
 			for(l=tmp_lst; l; l=l->next){
 				ttl=FIX_TTL(l->ttl);
 				rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				max_ttl=MAX(max_ttl, ttl);
 				rr->rdata=(void*)((char*)rr+
 								ROUND_SHORT(sizeof(struct dns_rr)));
 				/* copy the whole srv_rdata block*/
 				memcpy(rr->rdata, l->rdata, 
 						SRV_RDATA_SIZE(*(struct srv_rdata*)l->rdata) );
 				rr->next=(void*)((char*)rr+
 							ROUND_POINTER( ROUND_SHORT(sizeof(struct dns_rr))+
 										SRV_RDATA_SIZE(
 											*(struct srv_rdata*)l->rdata)));
 				tail_rr=&(rr->next);
 				rr=rr->next;
 			}
 			break;
 		case T_NAPTR:
 			for(l=tmp_lst; l; l=l->next){
 				ttl=FIX_TTL(l->ttl);
 				rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				max_ttl=MAX(max_ttl, ttl);
 				rr->rdata=(void*)((char*)rr+
 								ROUND_POINTER(sizeof(struct dns_rr)));
 				/* copy the whole srv_rdata block*/
 				memcpy(rr->rdata, l->rdata, 
 						NAPTR_RDATA_SIZE(*(struct naptr_rdata*)l->rdata) );
 				/* adjust the string pointer */
 				((struct naptr_rdata*)rr->rdata)->flags=
 					translate_pointer((char*)rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->flags));
 				((struct naptr_rdata*)rr->rdata)->services=
 					translate_pointer((char*)rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->services));
 				((struct naptr_rdata*)rr->rdata)->regexp=
 					translate_pointer((char*)rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->regexp));
 				((struct naptr_rdata*)rr->rdata)->repl=
 					translate_pointer((char*)rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->repl));
 				rr->next=(void*)((char*)rr+
 							ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
 										NAPTR_RDATA_SIZE(
 											*(struct naptr_rdata*)l->rdata)));
 				tail_rr=&(rr->next);
 			}
 			break;
 		case T_CNAME:
 			for(l=tmp_lst; l; l=l->next){
 				ttl=FIX_TTL(l->ttl);
 				rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				max_ttl=MAX(max_ttl, ttl);
 				rr->rdata=(void*)((char*)rr+sizeof(struct dns_rr));
 				memcpy(rr->rdata, l->rdata, 
 							CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata));
 				rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
 							CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata)));
 				tail_rr=&(rr->next);
 				rr=rr->next;
 			}
 			break;
 		default:
 			/* do nothing */
 			LOG(L_CRIT, "BUG: dns_cache_mk_rd_entry: create: type %d not "
 							"supported\n", type);
 				;
 	}
 	*tail_rr=0; /* terminate the list */
 	e->expire=now+S_TO_TICKS(max_ttl);
 	free_rdata_list(tmp_lst);
 	return e;
 }
 
 
 
 /* structure used only inside dns_cache_mk_rd_entry2 to break
  *  the list of records into records of the same type */
 struct tmp_rec{
 	struct rdata* rd;
 	struct dns_hash_entry* e;
 	struct dns_rr* rr;
 	struct dns_rr** tail_rr;
 	int max_ttl;
 	int size;
 };
 
 
 
 /* create several dns hash entries from a list of rdata structs
  * returns 0 on error */
 inline static struct dns_hash_entry* dns_cache_mk_rd_entry2(struct rdata* rd)
 {
 	struct rdata* l;
 	ticks_t now;
 	struct tmp_rec rec[MAX_DNS_RECORDS];
 	int rec_idx[MAX_DNS_RECORDS];
 	int r, i;
 	int no_records; /* number of different records */
 	unsigned int ttl;
 	
 	
 	no_records=0; 
 	rec[0].e=0;
 	/* everything is allocated in one block: dns_hash_entry + name +
 	 * + dns_rr + rdata_raw+ ....;  dns_rr must start at an aligned adress,
 	 * hence we need to round dns_hash_entry+name size to a sizeof(long)
 	 * multiple. If rdata type requires it, rdata_raw might need to be also
 	 * aligned.
 	 * Memory image:
 	 * struct dns_hash_entry  (e)
 	 * name (name_len+1 bytes)  (&e->name[0])
 	 * padding to multiple of sizeof(char*)
 	 * dns_rr1 (e->rr_lst)
 	 * possible padding: no padding for a_rdata or aaaa_rdata, 
 	 *                   multipe of sizeof(short) for srv_rdata,
 	 *                   multiple of sizeof(long) for naptr_rdata and others
 	 * dns_rr1->rdata  (e->rr_lst->rdata)
 	 * padding to multipe of sizeof long
 	 * dns_rr2 (e->rr_lst->next)
 	 * ....
 	 *
 	 */
 	/* compute size */
 	for(l=rd, i=0; l && (i<MAX_DNS_RECORDS); l=l->next, i++){
 		for (r=0; r<no_records; r++){
 			if ((l->type==rec[r].rd->type) && 
 					(l->name_len==rec[r].rd->name_len)
 				&& (strncasecmp(l->name, rec[r].rd->name, l->name_len)==0)){
 				/* found */
 				goto found;
 			}
 		}
 		/* not found, create new */
 		if (no_records<MAX_DNS_RECORDS){
 			rec[r].rd=l;
 			rec[r].e=0;
 			rec[r].size=ROUND_POINTER(sizeof(struct dns_hash_entry)+
 							rec[r].rd->name_len-1+1);
 			no_records++;
 		}else{
 			LOG(L_ERR, "ERROR: dns_cache_mk_rd_entry2: too many records: %d\n",
 						no_records);
 			/* skip */
 			continue;
 		}
 found:
 		rec_idx[i]=r;
 		switch(l->type){
 			case T_A:
 				/* no padding */
 				rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
 										sizeof(struct a_rdata));
 				break;
 			case T_AAAA:
 				/* no padding */
 				rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
 												sizeof(struct aaaa_rdata));
 				break;
 			case T_SRV:
 				/* padding to short */
 				rec[r].size+=ROUND_POINTER(ROUND_SHORT(sizeof(struct dns_rr))+
 								SRV_RDATA_SIZE(*(struct srv_rdata*)l->rdata));
 				break;
 			case T_NAPTR:
 					/* padding to char* */
 				rec[r].size+=ROUND_POINTER(ROUND_POINTER(
 												sizeof(struct dns_rr))+
 							NAPTR_RDATA_SIZE(*(struct naptr_rdata*)l->rdata));
 				break;
 			case T_CNAME:
 					/* no padding */
 				rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
 							CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata));
 				break;
 			default:
 				LOG(L_CRIT, "BUG: dns_cache_mk_rd_entry: type %d not "
 							"supported\n", l->type);
 		}
 	}
 	
 	now=get_ticks_raw();
 	/* alloc & init the entries */
 	for (r=0; r<no_records; r++){
 		rec[r].e=shm_malloc(rec[r].size);
 		if (rec[r].e==0){
 			LOG(L_ERR, "ERROR: dns_cache_mk_ip_entry: out of memory\n");
 			goto error;
 		}
 		memset(rec[r].e, 0, rec[r].size); /* init with 0*/
 		rec[r].e->total_size=rec[r].size;
 		rec[r].e->name_len=rec[r].rd->name_len;
 		rec[r].e->type=rec[r].rd->type;
 		rec[r].e->last_used=now;
 		/* memset makes sure is 0-term. */
 		memcpy(rec[r].e->name, rec[r].rd->name, rec[r].rd->name_len); 
 		rec[r].e->rr_lst=(struct dns_rr*)((char*)rec[r].e+
 				ROUND_POINTER(sizeof(struct dns_hash_entry)+rec[r].e->name_len
 								 -1+1));
 		rec[r].tail_rr=&(rec[r].e->rr_lst);
 		rec[r].rr=rec[r].e->rr_lst;
 		rec[r].max_ttl=0;
 		/* link them in a list */
 		if (r==0){
 			clist_init(rec[r].e, next, prev);
 		}else{
 			clist_append(rec[0].e, rec[r].e, next, prev);
 		}
 	}
 	/* copy the actual data */
 	for(l=rd, i=0; l && (i<MAX_DNS_RECORDS); l=l->next, i++){
 		r=rec_idx[i];
 		ttl=FIX_TTL(l->ttl);
 		switch(l->type){
 			case T_A:
 				rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
 				rec[r].rr->rdata=(void*)((char*)rec[r].rr+
 									sizeof(struct dns_rr));
 				memcpy(rec[r].rr->rdata, l->rdata, sizeof(struct a_rdata));
 				rec[r].rr->next=(void*)((char*)rec[r].rr+
 									ROUND_POINTER(sizeof(struct dns_rr)+
 									sizeof(struct a_rdata)));
 				rec[r].tail_rr=&(rec[r].rr->next);
 				rec[r].rr=rec[r].rr->next;
 				break;
 			case T_AAAA:
 				rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
 				rec[r].rr->rdata=(void*)((char*)rec[r].rr+
 									sizeof(struct dns_rr));
 				memcpy(rec[r].rr->rdata, l->rdata, sizeof(struct aaaa_rdata));
 				rec[r].rr->next=(void*)((char*)rec[r].rr+
 									ROUND_POINTER(sizeof(struct dns_rr)+
 									sizeof(struct aaaa_rdata)));
 				rec[r].tail_rr=&(rec[r].rr->next);
 				rec[r].rr=rec[r].rr->next;
 				break;
 			case T_SRV:
 				rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
 				rec[r].rr->rdata=(void*)((char*)rec[r].rr+
 								ROUND_SHORT(sizeof(struct dns_rr)));
 				/* copy the whole srv_rdata block*/
 				memcpy(rec[r].rr->rdata, l->rdata, 
 						SRV_RDATA_SIZE(*(struct srv_rdata*)l->rdata) );
 				rec[r].rr->next=(void*)((char*)rec[r].rr+
 							ROUND_POINTER( ROUND_SHORT(sizeof(struct dns_rr))+
 										SRV_RDATA_SIZE(
 											*(struct srv_rdata*)l->rdata)));
 				rec[r].tail_rr=&(rec[r].rr->next);
 				rec[r].rr=rec[r].rr->next;
 				break;
 			case T_NAPTR:
 				rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
 				rec[r].rr->rdata=(void*)((char*)rec[r].rr+
 								ROUND_POINTER(sizeof(struct dns_rr)));
 				/* copy the whole srv_rdata block*/
 				memcpy(rec[r].rr->rdata, l->rdata, 
 						NAPTR_RDATA_SIZE(*(struct naptr_rdata*)l->rdata) );
 				/* adjust the string pointer */
 				((struct naptr_rdata*)rec[r].rr->rdata)->flags=
 					translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->flags));
 				((struct naptr_rdata*)rec[r].rr->rdata)->services=
 					translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->services));
 				((struct naptr_rdata*)rec[r].rr->rdata)->regexp=
 					translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->regexp));
 				((struct naptr_rdata*)rec[r].rr->rdata)->repl=
 					translate_pointer((char*)rec[r].rr->rdata, (char*)l->rdata,
 							(((struct naptr_rdata*)l->rdata)->repl));
 				rec[r].rr->next=(void*)((char*)rec[r].rr+
 							ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
 										NAPTR_RDATA_SIZE(
 											*(struct naptr_rdata*)l->rdata)));
 				rec[r].tail_rr=&(rec[r].rr->next);
 				break;
 			case T_CNAME:
 				rec[r].rr->expire=now+S_TO_TICKS(ttl); /* maximum expire */
 				rec[r].max_ttl=MAX(rec[r].max_ttl, ttl);
 				rec[r].rr->rdata=(void*)((char*)rec[r].rr
 									+sizeof(struct dns_rr));
 				memcpy(rec[r].rr->rdata, l->rdata,
 							CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata));
 				rec[r].rr->next=(void*)((char*)rec[r].rr+
 							ROUND_POINTER(sizeof(struct dns_rr)+
 							CNAME_RDATA_SIZE(*(struct cname_rdata*)l->rdata)));
 				rec[r].tail_rr=&(rec[r].rr->next);
 				rec[r].rr=rec[r].rr->next;
 				break;
 			default:
 				/* do nothing */
 				;
 		}
 	}
 	for (r=0; r<no_records; r++){
 		*rec[r].tail_rr=0; /* terminate the list */
 		rec[r].e->expire=now+S_TO_TICKS(rec[r].max_ttl);
 	}
 	return rec[0].e;
 error:
 	for (r=0; r<no_records; r++){
 		dns_destroy_entry(rec[r].e);
 	}
 	return 0;
 }
 
 
 
 inline static struct dns_hash_entry* dns_get_entry(str* name, int type);
 
 
 #define CACHE_RELEVANT_RECS_ONLY
 
 #ifdef CACHE_RELEVANT_RECS_ONLY
 /* internal only: gets related entries from a rdata list, appends them
  * to e (list) and returns:
  *  - e if e is of the requested type
  *  -  if e is a CNAME, tries to get to the end of the CNAME chain and returns
  *      the final entry if the types match or 0 if the chain is unfinished
  *  - 0 on error/not found
  * records is modified (the used records are removed from the list and freed)
  *
  * WARNING: - records must be pkg_malloc'ed
  * Notes:   - if the return is 0 and e->type==T_CNAME, the list will contain
  *            the CNAME chain (the last element being the last CNAME)
  *  */
 inline static struct dns_hash_entry* dns_get_related(struct dns_hash_entry* e,
 														int type,
 														struct rdata** records)
 {
 	struct dns_hash_entry* ret;
 	struct dns_hash_entry* l;
 	struct dns_hash_entry* t;
 	struct dns_hash_entry* lst_end;
 	struct dns_rr* rr;
 	static int cname_chain_len=0;
 	str tmp;
 	
 	ret=0;
 	l=e;
 	DBG("dns_get_related(%p (%.*s, %d), %d, *%p) (%d)\n", e,
 			e->name_len, e->name, e->type, type, *records, cname_chain_len);
 	clist_init(l, next, prev);
 	if (type==e->type){
 		ret=e;
 		switch(e->type){
 			case T_SRV:
 				for (rr=e->rr_lst; rr && *records; rr=rr->next){
 					tmp.s=((struct srv_rdata*)rr->rdata)->name;
 					tmp.len=((struct srv_rdata*)rr->rdata)->name_len;
 					if (!(dns_flags&DNS_IPV6_ONLY)){
 						t=dns_cache_mk_rd_entry(&tmp, T_A, records);
 						if (t){
 							if ((t->type==T_CNAME) && *records)
 								dns_get_related(t, T_A, records);
 							clist_append(l, t, next, prev);
 						}
 					}
 					if (!(dns_flags&DNS_IPV4_ONLY)){
 						t=dns_cache_mk_rd_entry(&tmp, T_AAAA, records);
 						if (t){
 							if ((t->type==T_CNAME) && *records)
 								dns_get_related(t, T_AAAA, records);
 							clist_append(l, t, next, prev);
 						}
 					}
 				}
 				break;
 			default:
 				/* nothing extra */
 				break;
 		}
 	}else if ((e->type==T_CNAME) && (cname_chain_len<MAX_CNAME_CHAIN)){
 		/* only one cname is allowed (rfc2181), so we ignore
 		 * the others (we take only the first one) */
 		tmp.s=((struct cname_rdata*)e->rr_lst->rdata)->name;
 		tmp.len=((struct cname_rdata*)e->rr_lst->rdata)->name_len;
 		t=dns_cache_mk_rd_entry(&tmp, type, records);
 		if (t){
 			if (*records){
 				cname_chain_len++;
 				ret=dns_get_related(t, type, records);
 				cname_chain_len--;
 				lst_end=t->prev;
 				clist_append_sublist(l, t, lst_end, next, prev);
 			}else{
859edb6a
 				/* if no more recs, but we found the orig. target anyway,
 				 *  return it (e.g. recs are only CNAME x & x A 1.2.3.4 or 
 				 *  CNAME & SRV) */
 				if (t->type==type)
 					ret=t; 
dcb59e67
 				clist_append(l, t, next, prev);
 			}
 		}
 	}
 	return ret;
 }
 #endif
 
 
 
 /* calls the external resolver and populates the cache with the result
  * returns: 0 on error, pointer to hash entry on success
  * WARNING: make sure you use dns_hash_entry_put() when you're
  *  finished with the result)
  * */
 inline static struct dns_hash_entry* dns_cache_do_request(str* name, int type)
 {
 	struct rdata* records;
 	struct dns_hash_entry* e;
 	struct dns_hash_entry* l;
 	struct dns_hash_entry* r;
 	struct dns_hash_entry* t;
 	struct ip_addr* ip;
 	str cname_val;
 	char name_buf[MAX_DNS_NAME];
 	
 	e=0;
 	l=0;
 	cname_val.s=0;
 	
 	if (type==T_A){
 		if ((ip=str2ip(name))!=0){
 				e=dns_cache_mk_ip_entry(name, ip);
 				if (e)
 					atomic_set(&e->refcnt, 1);/* because we ret. a ref. to it*/
 				goto end; /* we do not cache obvious stuff */
 		}
 	}else if (type==T_AAAA){
 		if ((ip=str2ip6(name))!=0){
 				e=dns_cache_mk_ip_entry(name, ip);
 				if (e)
 					atomic_set(&e->refcnt, 1);/* because we ret. a ref. to it*/
 				goto end;/* we do not cache obvious stuff */
 		}
 	}
 	if (name->len>=MAX_DNS_NAME){
 		LOG(L_ERR, "ERROR: dns_cache_do_request: name too long (%d chars)\n",
 					name->len);
 		goto end;
 	}
 	/* null terminate the string, needed by get_record */
 	memcpy(name_buf, name->s, name->len);
 	name_buf[name->len]=0;
 	records=get_record(name_buf, type, RES_AR);
 	if (records){
 #ifdef CACHE_RELEVANT_RECS_ONLY
 		e=dns_cache_mk_rd_entry(name, type, &records);
 		if (e){
 			l=e;
 			e=dns_get_related(l, type, &records);
 			/* e should contain the searched entry (if found) and l
 			 * all the entries (e and related) */
 			if (e){
 				atomic_set(&e->refcnt, 1); /* 1 because we return a 
 												ref. to it */
 			}else{
 				/* e==0 => l contains a  cname list => we use the last
 				 * cname from the chain for a new resolve attempt (l->prev) */
 				/* only one cname record is allowed (rfc2181), so we ignore 
 				 * the others (we take only the first one) */
 				cname_val.s=
 					((struct cname_rdata*)l->prev->rr_lst->rdata)->name;
 				cname_val.len=
 					((struct cname_rdata*)l->prev->rr_lst->rdata)->name_len;
 				DBG("dns_cache_do_request: cname detected: %.*s (%d)\n",
 						cname_val.len, cname_val.s, cname_val.len);
 			}
 			/* add all the records to the hash */
 			l->prev->next=0; /* we break the double linked list for easier
 								searching */
 			LOCK_DNS_HASH(); /* optimization */
 			for (r=l; r; r=t){
 				t=r->next;
 				dns_cache_add_unsafe(r); /* refcnt++ inside */
 				if (atomic_get(&r->refcnt)==0){
 					/* if cache adding failed and nobody else is interested
 					 * destroy this entry */
 					dns_destroy_entry(r);
 				}
 			}
 			UNLOCK_DNS_HASH();
 			/* if only cnames found => try to resolve the last one */
 			if (cname_val.s){ 
 				DBG("dns_cache_do_request: dns_get_entry(cname: %.*s (%d))\n",
 						cname_val.len, cname_val.s, cname_val.len);
 				e=dns_get_entry(&cname_val, type);
 			}
 		}
 #else
 		l=dns_cache_mk_rd_entry2(records);
 #endif
 		free_rdata_list(records);
 	}else if (dns_neg_cache_ttl){
 		e=dns_cache_mk_bad_entry(name, type, dns_neg_cache_ttl, DNS_BAD_NAME);
 		atomic_set(&e->refcnt, 1); /* 1 because we return a ref. to it */
 		dns_cache_add(e); /* refcnt++ inside*/
 		goto end;
 	}
 #ifndef CACHE_RELEVANT_RECS_ONLY
 	if (l){
 		/* add all the records to the cache, but return only the record
 		 * we are looking for */
 		l->prev->next=0; /* we break the double linked list for easier
 							searching */
 		LOCK_DNS_HASH(); /* optimization */
 		for (r=l; r; r=t){
 			t=r->next;
 			if (e==0){ /* no entry found yet */
 				if (r->type==T_CNAME){
 					if ((r->name_len==name->len) && (r->rr_lst) &&
 							(strncasecmp(r->name, name->s, name->len)==0)){
 						/* update the name with the name from the cname rec. */
 						cname_val.s=
 								((struct cname_rdata*)r->rr_lst->rdata)->name;
 						cname_val.len=
 							((struct cname_rdata*)r->rr_lst->rdata)->name_len;
 						name=&cname_val;
 					}
 				}else if ((r->type==type) && (r->name_len==name->len) &&
 							(strncasecmp(r->name, name->s, name->len)==0)){
 					e=r;
 					atomic_set(&e->refcnt, 1); /* 1 because we return a ref. 
 												  to it */
 				}
 			}
 			dns_cache_add_unsafe(r); /* refcnt++ inside */
 			if (atomic_get(&r->refcnt)==0){
 				/* if cache adding failed and nobody else is interested
 				 * destroy this entry */
 				dns_destroy_entry(r);
 			}
 		}
 		UNLOCK_DNS_HASH();
 		if ((e==0) && (cname_val.s)){ /* not found, but found a cname */
 			/* only one cname is allowed (rfc2181), so we ignore the
 			 * others (we take only the first one) */
 			e=dns_get_entry(&cname_val, type);
 		}
 	}
 #endif
 end:
 	return e;
 }
 
 
 
 /* tries to lookup (name, type) in the hash and if not found tries to make
  *  a dns request
  *  return: 0 on error, pointer to a dns_hash_entry on success
  *  WARNING: when *   not needed anymore dns_hash_put() must be called! */
 inline static struct dns_hash_entry* dns_get_entry(str* name, int type)
 {
 	int h;
 	struct dns_hash_entry* e;
 	str cname_val;
 	int err;
 	static int rec_cnt=0; /* recursion protection */
 	
 	e=0;
 	if (rec_cnt>MAX_CNAME_CHAIN){
 		LOG(L_WARN, "WARNING: dns_get_entry: CNAME chain too long or"
 				" recursive CNAMEs (\"%.*s\")\n", name->len, name->s);
 		goto error;
 	}
 	rec_cnt++;
 	e=dns_hash_get(name, type, &h, &err);
 	if ((e==0) && ((err) || ((e=dns_cache_do_request(name, type))==0))){
 		goto error;
 	}else if ((e->type==T_CNAME) && (type!=T_CNAME)){
 		/* cname found instead which couldn't be resolved with the cached
 		 * info => try a dns request */
 		/* only one cname record is allowed (rfc2181), so we ignore 
 		 * the others (we take only the first one) */
 		cname_val.s= ((struct cname_rdata*)e->rr_lst->rdata)->name;
 		cname_val.len=((struct cname_rdata*)e->rr_lst->rdata)->name_len;
 		dns_hash_put(e); /* not interested in the cname anymore */
 		if ((e=dns_cache_do_request(&cname_val, type))==0)
 			goto error; /* could not resolve cname */
 	}
 	/* found */
 	if ((e->rr_lst==0) || e->err_flags){
 		/* negative cache => not resolvable */
 		dns_hash_put(e);
 		e=0;
 	}
 error:
 	rec_cnt--;
 	return e;
 }
 
 
 
 /* gets the first non-expired, good record starting with record no
  * from the dns_hash_entry struct e
  * params:       e   - dns_hash_entry struct
  *               *no - it must contain the start record number (0 initially);
  *                      it will be filled with the returned record number
  *               now - current time/ticks value
  * returns pointer to the rr on success and sets no to the rr number
  *         0 on error and fills the error flags
  *
  * Example usage:
  * list all non-expired non-bad-marked ips for name:
  * e=dns_get_entry(name, T_A);
  * if (e){
  *    *no=0;
  *    now=get_ticks_raw();
  *    while(rr=dns_entry_get_rr(e, no, now){
  *       DBG("address %d\n", *no);
  *       *no++;  ( get the next address next time )
  *     }
  *  }
  */
 inline static struct dns_rr* dns_entry_get_rr(	struct dns_hash_entry* e,
 											 unsigned char* no, ticks_t now)
 {
 	struct dns_rr* rr;
 	int n;
 	int flags;
 	
 	flags=0;
 	for(rr=e->rr_lst, n=0;rr && (n<*no);rr=rr->next, n++);/* skip *no records*/
 	for(;rr;rr=rr->next){
 		if ((s_ticks_t)(now-e->expire)>=0) /* expired entry */
 			continue;
 		if (rr->err_flags){ /* bad rr */
 			continue;
 		}
 		/* everything is ok now */
 		*no=n;
 		return rr;
 	}
 	*no=n;
 	return 0;
 }
 
 
 
 /* gethostbyname compatibility: converts a dns_hash_entry structure 
  * to a statical internal hostent structure
  * returns a pointer to the internal hostent structure on success or
  *          0 on error 
  */
 struct hostent* dns_entry2he(struct dns_hash_entry* e)
 {
 	static struct hostent he;
 	static char hostname[256];
 	static char* p_aliases[1];
 	static char* p_addr[DNS_HE_MAX_ADDR+1];
 	static char address[16*DNS_HE_MAX_ADDR]; /* max 10 ipv6 addresses */
 	int af, len;
 	struct dns_rr* rr;
 	unsigned char rr_no;
 	ticks_t now;
 	int i;
 	
 	switch(e->type){
 		case T_A:
 			af=AF_INET;
 			len=4;
 			break;
 		case T_AAAA:
 			af=AF_INET6;
 			len=16;
 			break;
 		default:
 			LOG(L_CRIT, "BUG: dns_entry2he: wrong entry type %d for %.*s\n",
 					e->type, e->name_len, e->name);
 			return 0;
 	}
 	
 	
 	rr_no=0;
 	now=get_ticks_raw();
 	rr=dns_entry_get_rr(e, &rr_no, now);
 	for(i=0; rr && (i<DNS_HE_MAX_ADDR); i++, 
 							rr=dns_entry_get_rr(e, &rr_no, now)){
 				p_addr[i]=&address[i*len];
 				memcpy(p_addr[i], ((struct a_rdata*)rr->rdata)->ip, len);
 	}
 	if (i==0){
 		DBG("DEBUG: dns_entry2he: no good records found (%d) for %.*s (%d)\n",
 				rr_no, e->name_len, e->name, e->type);
 		return 0; /* no good record found */
 	}
 	
 	p_addr[i]=0; /* mark the end of the addresses */
 	p_aliases[0]=0; /* no aliases */
 	memcpy(hostname, e->name, e->name_len);
 	hostname[e->name_len]=0;
 	
 	he.h_addrtype=af;
 	he.h_length=len;
 	he.h_addr_list=p_addr;
 	he.h_aliases=p_aliases;
 	he.h_name=hostname;
 	
 	return &he;
 }
 
 
 
 /* gethostbyname compatibility: performs an a_lookup and returns a pointer 
  * to a statical internal hostent structure
  * returns 0 on success, <0 on error (see the error codes)
  */
 struct hostent* dns_a_get_he(str* name)
 {
 	struct dns_hash_entry* e;
 	struct ip_addr* ip;
 	struct hostent* he;
 	
 	e=0;
 	if ((ip=str2ip(name))!=0){
 		return ip_addr2he(name, ip);
 	}
 	if ((e=dns_get_entry(name, T_A))==0)
 		return 0;
 	/* found */
 	he=dns_entry2he(e);
 	dns_hash_put(e);
 	return he;
 }
 
 
 
 /* gethostbyname compatibility: performs an aaaa_lookup and returns a pointer 
  * to a statical internal hostent structure
  * returns 0 on success, <0 on error (see the error codes)
  */
 struct hostent* dns_aaaa_get_he(str* name)
 {
 	struct dns_hash_entry* e;
 	struct ip_addr* ip;
 	struct hostent* he;
 	
 	e=0;
 	if ((ip=str2ip6(name))!=0){
 		return ip_addr2he(name, ip);
 	}
 	if ((e=dns_get_entry(name, T_AAAA))==0)
 			return 0;
 	/* found */
 	he=dns_entry2he(e);
 	dns_hash_put(e);
 	return he;
 }
 
 
 
 /* returns 0 on success, -1 on error (rr type does not contain an ip) */
 inline static int dns_rr2ip(int type, struct dns_rr* rr, struct ip_addr* ip)
 {
 	switch(type){
 		case T_A:
 			ip->af=AF_INET;
 			ip->len=4;
 			memcpy(ip->u.addr, ((struct a_rdata*)rr->rdata)->ip, 4);
 			return 0;
 			break;
 		case T_AAAA:
 			ip->af=AF_INET6;
 			ip->len=16;
 			memcpy(ip->u.addr, ((struct aaaa_rdata*)rr->rdata)->ip6, 16);
 			return 0;
 			break;
 	}
 	return -1;
 }
 
 
 
 /* gethostbyname compatibility:
  * performs an a or aaaa dns lookup, returns 0 on error and a pointer to a
  *          static hostent structure on success
  *  flags:  - none set: tries first an a_lookup and if it fails an aaaa_lookup
  *          - DNS_IPV6_FIRST: tries first an aaaa_lookup and then an a_lookup
  *          - DNS_IPV4_ONLY: tries only an a_lookup
  *          - DNS_IPV6_ONLY: tries only an aaaa_lookup
  */
 struct hostent* dns_get_he(str* name, int flags)
 {
 	struct hostent* he;
 	
 	
 	if ((flags&(DNS_IPV6_FIRST|DNS_IPV6_ONLY))){
 		he=dns_aaaa_get_he(name);
 		if (he) return he;
 	}else{
 		he=dns_a_get_he(name);
 		if (he) return he;
 	}
 	if (flags&DNS_IPV6_FIRST){
 		he=dns_a_get_he(name);
 	}else if (!(flags&(DNS_IPV6_ONLY|DNS_IPV4_ONLY))){
 		he=dns_aaaa_get_he(name);
 	}
 	return he;
 }
 
 
 
 /* sip_resolvehost helper: gets the first good  hostent/port combination
  * returns 0 on error, pointer to static hostent structure on success
  *           (and sets port)*/
 struct hostent* dns_srv_get_he(str* name, unsigned short* port, int flags)
 {
 	struct dns_hash_entry* e;
 	struct dns_rr* rr;
 	str rr_name;
 	struct hostent* he;
 	ticks_t now;
 	unsigned char rr_no;
 	
 	rr=0;
 	he=0;
 	now=get_ticks_raw();
 	if ((e=dns_get_entry(name, T_SRV))==0)
 			goto error;
 	/* look inside the RRs for a good one (not expired or marked bad)  */
 	rr_no=0;
 	while( (rr=dns_entry_get_rr(e, &rr_no, now))!=0){
 		/* everything is ok now, we can try to resolve the ip */
 		rr_name.s=((struct srv_rdata*)rr->rdata)->name;
 		rr_name.len=((struct srv_rdata*)rr->rdata)->name_len;
 		if ((he=dns_get_he(&rr_name, flags))!=0){
 				/* success, at least one good ip found */
 				*port=((struct srv_rdata*)rr->rdata)->port;
 				goto end;
 		}
 		rr_no++; /* try from the next record, the current one was not good */
 	}
 	/* if we reach this point => error, we couldn't find any good rr */
 end:
 	if (e) dns_hash_put(e);
 error:
 	return he;
 }
 
 
 
 struct hostent* dns_resolvehost(char* name)
 {
 	str host;
 	
 	if ((use_dns_cache==0) || (dns_hash==0)){ /* not init yet */
 		return _resolvehost(name);
 	}
 	host.s=name;
 	host.len=strlen(name);
 	return dns_get_he(&host, dns_flags);
 }
 
 
 
 /* resolves a host name trying SRV lookup if *port==0 or normal A/AAAA lookup
  * if *port!=0.
  * when performing SRV lookup (*port==0) it will use proto to look for
  * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
  * returns: hostent struct & *port filled with the port from the SRV record;
  *  0 on error
  */
 struct hostent* dns_sip_resolvehost(str* name, unsigned short* port, int proto)
 {
 	struct hostent* he;
 	struct ip_addr* ip;
 	static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
 	int len;
 	str srv_name;
 
 	if ((use_dns_cache==0) || (dns_hash==0)){ 
 		/* not init or off => use normal, non-cached version */
 		return _sip_resolvehost(name, port, proto);
 	}
 	len=0;
 	/* try SRV if no port specified (draft-ietf-sip-srv-06) */
 	if ((port)&&(*port==0)){
 		*port=(proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; /* just in case we don't
 														find another */
 		if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME){
 			LOG(L_WARN, "WARNING: dns_sip_resolvehost: domain name too long"
 						" (%d), unable to perform SRV lookup\n", name->len);
 		}else{
 			/* check if it's an ip address */
 			if ( ((ip=str2ip(name))!=0)
 #ifdef	USE_IPV6
 				  || ((ip=str2ip6(name))!=0)
 #endif
 				){
 				/* we are lucky, this is an ip address */
 				return ip_addr2he(name,ip);
 			}
 			
 			switch(proto){
 				case PROTO_NONE: /* no proto specified, use udp */
 					goto skip_srv;
 				case PROTO_UDP:
 					memcpy(tmp, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN);
 					memcpy(tmp+SRV_UDP_PREFIX_LEN, name->s, name->len);
 					tmp[SRV_UDP_PREFIX_LEN + name->len] = '\0';
 					len=SRV_UDP_PREFIX_LEN + name->len;
 					break;
 				case PROTO_TCP:
 					memcpy(tmp, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN);
 					memcpy(tmp+SRV_TCP_PREFIX_LEN, name->s, name->len);
 					tmp[SRV_TCP_PREFIX_LEN + name->len] = '\0';
 					len=SRV_TCP_PREFIX_LEN + name->len;
 					break;
 				case PROTO_TLS:
 					memcpy(tmp, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN);
 					memcpy(tmp+SRV_TLS_PREFIX_LEN, name->s, name->len);
 					tmp[SRV_TLS_PREFIX_LEN + name->len] = '\0';
 					len=SRV_TLS_PREFIX_LEN + name->len;
 					break;
 				default:
 					LOG(L_CRIT, "BUG: sip_resolvehost: unknown proto %d\n",
 							proto);
 					return 0;
 			}
 
 			srv_name.s=tmp;
 			srv_name.len=len;
 			if ((he=dns_srv_get_he(&srv_name, port, dns_flags))!=0)
 				return he;
 		}
 	}
 skip_srv:
 	if (name->len >= MAX_DNS_NAME) {
 		LOG(L_ERR, "dns_sip_resolvehost: domain name too long\n");
 		return 0;
 	}
 	he=dns_get_he(name, dns_flags);
 	return he;
 }
 
 
 
 /* performs an a lookup, fills the dns_entry pointer and the ip addr.
  *  (with the first good ip). if *e ==0 does the a lookup, and changes it
  *   to the result, if not it uses the current value and tries to use 
  *   the rr_no record from it.
  * params:  e - must contain the "in-use" dns_hash_entry pointer (from
  *               a previous call) or *e==0 (for the first call)
  *          name - host name for which we do the lookup (required only
  *                  when *e==0)
  *          ip   - will be filled with the first good resolved ip started
  *                 at *rr_no
  *          rr_no - record number to start searching for a good ip from
  *                  (e.g. value from previous call + 1), filled on return
  *                  with the number of the record corresponding to the 
  *                  returned ip
  * returns 0 on success, <0 on error (see the error codes),
  *         fills e, ip and rr_no
  *          On end of records (when use to iterate on all the ips) it
  *          will return E_DNS_EOR (you should not log an error for this
  *          value, is just a signal that the address list end has been reached)
  * WARNING: dns_hash_put(*e) must be called when you don't need
  *          the entry anymore and *e!=0 (failling to do so => mem. leak)
  * Example:
  *  dns_entry=0;
  *  ret=dns_a_get_ip(&dns_entry, name, &ip, &rr_no);  -- get the first rr.
  *  ...
  *  rr_no++;
  *  while((ret>=0) && dns_entry)
  *     dns_a_get_ip(&dns_entry, name, &ip, &rr_no); -- get the next rr
  *   if (ret!=-E_DNS_EOR) ERROR(....);
  *  ...
  *  dns_hash_put(dns_entry); -- finished with the entry
  */
 int dns_a_resolve(struct dns_hash_entry** e, unsigned char* rr_no,
 					str* name, struct ip_addr* ip)
 {
 	struct dns_rr* rr;
 	int ret;
 	ticks_t now;
 	struct ip_addr* tmp;
 	
 	rr=0;
 	ret=-E_DNS_NO_IP; 
 	if (*e==0){ /* do lookup */
 		/* if ip don't set *e */
 		if ((tmp=str2ip(name))!=0){
 			*ip=*tmp;
 			*rr_no=0;
 			return 0;
 		}
 		if ((*e=dns_get_entry(name, T_A))==0)
 			goto error;
 		/* found */
 		*rr_no=0;
 		ret=-E_DNS_BAD_IP_ENTRY;
 	}
 	now=get_ticks_raw();
 	rr=dns_entry_get_rr(*e, rr_no, now);
 	if (rr){
 		/* everything is ok now, we can try to "convert" the ip */
 		dns_rr2ip((*e)->type, rr, ip);
 		ret=0;
 	}else{
 		ret=-E_DNS_EOR;
 	}
 error:
 	DBG("dns_a_resovle(%.*s, %d) returning %d\n",
 			name->len, name->s, *rr_no, ret);
 	return ret;
 }
 
 
 
 /* lookup, fills the dns_entry pointer and the ip addr.
  *  (with the first good ip). if *e ==0 does the a lookup, and changes it
  *   to the result, if not it uses the current value and tries to use 
  * Same as dns_a_resolve but for aaaa records (see above).
  */
 int dns_aaaa_resolve(struct dns_hash_entry** e, unsigned char* rr_no, 
 						str* name, struct ip_addr* ip)
 {
 	struct dns_rr* rr;
 	int ret;
 	ticks_t now;
 	struct ip_addr* tmp;
 	
 	rr=0;
 	ret=-E_DNS_NO_IP; 
 	if (*e==0){ /* do lookup */
 		/* if ip don't set *e */
 		if ((tmp=str2ip6(name))!=0){
 			*ip=*tmp;
 			*rr_no=0;
 			return 0;
 		}
 		if ((*e=dns_get_entry(name, T_AAAA))==0)
 			goto error;
 		/* found */
 		*rr_no=0;
 		ret=-E_DNS_BAD_IP_ENTRY;
 	}
 	now=get_ticks_raw();
 	rr=dns_entry_get_rr(*e, rr_no, now);
 	if (rr){
 		/* everything is ok now, we can try to "convert" the ip */
 		dns_rr2ip((*e)->type, rr, ip);
 		ret=0;
 	}else{
 		ret=-E_DNS_EOR; /* no more records */
 	}
 error:
 	return ret;
 }
 
 
 
 /* performs an a or aaaa dns lookup, returns <0 on error (see the
  *  dns error codes) and 0 on success
  *  flags:  - none set: tries first an a_lookup and if it fails an aaaa_lookup
  *          - DNS_IPV6_FIRST: tries first an aaaa_lookup and then an a_lookup
  *          - DNS_IPV4_ONLY: tries only an a_lookup
  *          - DNS_IPV6_ONLY: tries only an aaaa_lookup
  *  see dns_a_resolve() for the rest of the params., examples a.s.o
  *  WARNING: don't forget dns_hash_put(*e) when e is not needed anymore
  */
 int dns_ip_resolve(struct dns_hash_entry** e, unsigned char* rr_no, 
 					str* name, struct ip_addr* ip, int flags)
 {
 	int ret;
 	
 	if ((flags&(DNS_IPV6_FIRST|DNS_IPV6_ONLY))){
 		ret=dns_aaaa_resolve(e, rr_no, name, ip);
 		if (ret>=0) return ret;
 	}else{
 		ret=dns_a_resolve(e, rr_no, name, ip);
 		if (ret>=0) return ret;
 	}
 	if (flags&DNS_IPV6_FIRST){
 		ret=dns_a_resolve(e, rr_no, name, ip);
 	}else if (!(flags&(DNS_IPV6_ONLY|DNS_IPV4_ONLY))){
 		ret=dns_aaaa_resolve(e, rr_no, name, ip);
 	}
 	return ret;
 }
 
 
 
 /*  gets the first srv record starting at rr_no
  *  (similar to dns_a_resolve but for srv, sets host, port)
  */
 int dns_srv_resolve(struct dns_hash_entry** e, unsigned char* rr_no,
 					str* name, str* host, unsigned short* port)
 {
 	struct dns_rr* rr;
 	int ret;
 	ticks_t now;
 	
 	rr=0;
 	ret=-E_DNS_NO_SRV; 
 	if (*e==0){
 		if ((*e=dns_get_entry(name, T_SRV))==0)
 			goto error;
 		/* found it */
 		*rr_no=0;
 		ret=-E_DNS_BAD_SRV_ENTRY;
 	}
 	now=get_ticks_raw();
 	rr=dns_entry_get_rr(*e, rr_no, now);
 	if (rr){
 		host->s=((struct srv_rdata*)rr->rdata)->name;
 		host->len=((struct srv_rdata*)rr->rdata)->name_len;
 		*port=((struct srv_rdata*)rr->rdata)->port;
 		ret=0;
 	}else{
 		ret=-E_DNS_EOR; /* no more records */
 	}
 error:
 	return ret;
 }
 
 
 
 /*  gets the first srv record starting at h->srv_no, resolve it
  *   and get the first ip address (starting at h->ip_no)
  *  (similar to dns_a_resolve but for srv, sets host, port)
  *  WARNING: don't forget to init h prior to calling this function the first
  *   time and dns_srv_handle_put(h), even if error is returned
  */
 int dns_srv_resolve_ip(struct dns_srv_handle* h,
 					str* name, struct ip_addr* ip, unsigned short* port,
 					int flags)
 {
 	int ret;
 	str host;
 	
 	host.len=0;
 	host.s=0;
 	do{
 		if (h->a==0){ 
 			if ((ret=dns_srv_resolve(&h->srv, &h->srv_no,
 													name, &host, port))<0)
 				goto error;
 			h->port=*port; /* store new port */
 		}else{
 			*port=h->port; /* return the stored port */
 		}
 		if ((ret=dns_ip_resolve(&h->a, &h->ip_no, &host, ip, flags))<0){
 			/* couldn't find any good ip for this record, try the next one */
 			h->srv_no++;
 			if (h->a){
 				dns_hash_put(h->a);
 				h->a=0;
 			}
 		}else if (h->a==0){
 			/* this was an ip, try the next srv record in the future */
 			h->srv_no++;
 		}
 	}while(ret<0);
 error:
 	DBG("dns_srv_resolve_ip(\"%.*s\", %d, %d), ret=%d, ip=%s\n", 
 			name->len, name->s, h->srv_no, h->ip_no, ret, ip_addr2a(ip));
 	return ret;
 }
 
 
 
 /* resolves a host name trying SRV lookup if *port==0 or normal A/AAAA lookup
  * if *port!=0.
  * when performing SRV lookup (*port==0) it will use proto to look for
  * tcp or udp hosts, otherwise proto is unused; if proto==0 => no SRV lookup
  * dns_res_h must be initialized prior to  calling this function and can be
  * used to get the subsequent ips
  * returns:  <0 on error
  *            0 on success and it fills *ip, *port, dns_sip_resolve_h
  * WARNING: when finished, dns_sip_resolve_put(dns_res_h) must be called!
  */
 int dns_sip_resolve(struct dns_srv_handle* h,  str* name,
 						struct ip_addr* ip, unsigned short* port, int proto,
 						int flags)
 {
 	static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
 	int len;
 	str srv_name;
 	struct ip_addr* tmp_ip;
 	int ret;
 	struct hostent* he;
 
 	if (dns_hash==0){ /* not init => use normal, non-cached version */
 		LOG(L_WARN, "WARNING: dns_sip_resolve: called before dns cache"
 					" initialization\n");
 		h->srv=h->a=0;
 		he=_sip_resolvehost(name, port, proto);
 		if (he){
 			hostent2ip_addr(ip, he, 0);
 			return 0;
 		}
 		return -E_DNS_NO_SRV;
 	}
 	len=0;
 	if ((h->srv==0) && (h->a==0)){
 		h->port=(proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; /* just in case we
 														don't find another */
8af0841f
 		if (port){
 			if (*port==0){
 				/* try SRV if initial call & no port specified
 				 * (draft-ietf-sip-srv-06) */
 				if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME){
 					LOG(L_WARN, "WARNING: dns_sip_resolvehost: domain name too"
 								" long (%d), unable to perform SRV lookup\n",
 								name->len);
 				}else{
 					/* check if it's an ip address */
 					if ( ((tmp_ip=str2ip(name))!=0)
dcb59e67
 #ifdef	USE_IPV6
8af0841f
 						  || ((tmp_ip=str2ip6(name))!=0)
dcb59e67
 #endif
8af0841f
 						){
 						/* we are lucky, this is an ip address */
dcb59e67
 #ifdef	USE_IPV6
8af0841f
 						if (((flags&DNS_IPV4_ONLY) && (tmp_ip->af==AF_INET6))||
 							((flags&DNS_IPV6_ONLY) && (tmp_ip->af==AF_INET))){
 							return -E_DNS_AF_MISMATCH;
 						}
dcb59e67
 #endif
8af0841f
 						*ip=*tmp_ip;
10508d1a
 						*port=h->port;
8af0841f
 						return 0;
 					}
 					
 					switch(proto){
 						case PROTO_NONE: /* no proto specified, use udp */
 							goto skip_srv;
 						case PROTO_UDP:
 							memcpy(tmp, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN);
 							memcpy(tmp+SRV_UDP_PREFIX_LEN, name->s, name->len);
 							tmp[SRV_UDP_PREFIX_LEN + name->len] = '\0';
 							len=SRV_UDP_PREFIX_LEN + name->len;
 							break;
 						case PROTO_TCP:
 							memcpy(tmp, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN);
 							memcpy(tmp+SRV_TCP_PREFIX_LEN, name->s, name->len);
 							tmp[SRV_TCP_PREFIX_LEN + name->len] = '\0';
 							len=SRV_TCP_PREFIX_LEN + name->len;
 							break;
 						case PROTO_TLS:
 							memcpy(tmp, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN);
 							memcpy(tmp+SRV_TLS_PREFIX_LEN, name->s, name->len);
 							tmp[SRV_TLS_PREFIX_LEN + name->len] = '\0';
 							len=SRV_TLS_PREFIX_LEN + name->len;
 							break;
 						default:
 							LOG(L_CRIT, "BUG: sip_resolvehost: "
 									"unknown proto %d\n", proto);
 							return -E_DNS_CRITICAL;
 					}
 					srv_name.s=tmp;
 					srv_name.len=len;
 					
 					if ((ret=dns_srv_resolve_ip(h, &srv_name, ip,
 															port, flags))>=0)
 					{
 						DBG("dns_sip_resolve(%.*s, %d, %d), srv0, ret=%d\n", 
 							name->len, name->s, h->srv_no, h->ip_no, ret);
 						return ret;
 					}
dcb59e67
 				}
8af0841f
 			}else{ /* if (*port==0) */
 				h->port=*port; /* store initial port */
dcb59e67
 			}
8af0841f
 		} /* if (port) */
dcb59e67
 	}else if (h->srv){
 			srv_name.s=h->srv->name;
 			srv_name.len=h->srv->name_len;
 			/* continue srv resolving */
 			ret=dns_srv_resolve_ip(h, &srv_name, ip, port, flags);
 			DBG("dns_sip_resolve(%.*s, %d, %d), srv, ret=%d\n", 
 					name->len, name->s, h->srv_no, h->ip_no, ret);
 			return ret;
 	}
 skip_srv:
 	if (name->len >= MAX_DNS_NAME) {
 		LOG(L_ERR, "dns_sip_resolve: domain name too long\n");
 		return -E_DNS_NAME_TOO_LONG;
 	}
 	ret=dns_ip_resolve(&h->a, &h->ip_no, name, ip, flags);
 	if (port)
 		*port=h->port;
 	DBG("dns_sip_resolve(%.*s, %d, %d), ip, ret=%d\n", 
 			name->len, name->s, h->srv_no, h->ip_no, ret);
 	return ret;
 }
 
 
 
 /* performs an a lookup and fills ip with the first good ip address
  * returns 0 on success, <0 on error (see the error codes)
  */
 int dns_a_get_ip(str* name, struct ip_addr* ip)
 {
 	struct dns_hash_entry* e;
 	int ret;
 	unsigned char rr_no;
 	
 	e=0;
 	rr_no=0;
 	ret=dns_a_resolve(&e, &rr_no, name, ip);
 	if (e) dns_hash_put(e);
 	return ret;
 }
 
 
 
 int dns_aaaa_get_ip(str* name, struct ip_addr* ip)
 {
 	struct dns_hash_entry* e;
 	int ret;
 	unsigned char rr_no;
 	
 	e=0;
 	rr_no=0;
 	ret=dns_aaaa_resolve(&e, &rr_no, name, ip);
 	if (e) dns_hash_put(e);
 	return ret;
 }
 
 
 
 /* performs an a or aaaa dns lookup, returns <0 on error (see the
  *  dns error codes) and 0 on success
  *  flags:  - none set: tries first an a_lookup and if it fails an aaaa_lookup
  *          - DNS_IPV6_FIRST: tries first an aaaa_lookup and then an a_lookup
  *          - DNS_IPV4_ONLY: tries only an a_lookup
  *          - DNS_IPV6_ONLY: tries only an aaaa_lookup
  */
 int dns_get_ip(str* name, struct ip_addr* ip, int flags)
 {
 	int ret;
 	struct dns_hash_entry* e;
 	unsigned char rr_no;
 	
 	e=0;
 	rr_no=0;
 	ret=dns_ip_resolve(&e, &rr_no, name, ip, flags);
 	if (e)
 		dns_hash_put(e);
 	return ret;
 }
 
 
 
 /* fast "inline" version, gets the first good ip:port */
 int dns_srv_get_ip(str* name, struct ip_addr* ip, unsigned short* port,
 						int flags)
 {
 	int ret;
 	struct dns_srv_handle h;
 	
 	dns_srv_handle_init(&h);
 	ret=dns_srv_resolve_ip(&h, name, ip, port, flags);
 	dns_srv_handle_put(&h);
 	return ret;
 }
 
 
 
 /* rpc functions */
 void dns_cache_mem_info(rpc_t* rpc, void* ctx)
 {
 	rpc->add(ctx, "dd",  *dns_cache_mem_used, dns_cache_max_mem);
 }
 
 
 void dns_cache_debug(rpc_t* rpc, void* ctx)
 {
 	int h;
 	struct dns_hash_entry* e;
 	ticks_t now;
 	
 	now=get_ticks_raw();
 	LOCK_DNS_HASH();
 		for (h=0; h<DNS_HASH_SIZE; h++){
 			clist_foreach(&dns_hash[h], e, next){
 				rpc->add(ctx, "sdddddd", 
 								e->name, e->type, e->total_size, e->refcnt.val,
 								(s_ticks_t)(e->expire-now)<0?-1:
 									TICKS_TO_S(e->expire-now),
 								TICKS_TO_S(now-e->last_used),
 								e->err_flags);
 			}
 		}
 	UNLOCK_DNS_HASH();
 }
 
 
 
 /* rpc functions */
 void dns_cache_debug_all(rpc_t* rpc, void* ctx)
 {
 	int h;
 	struct dns_hash_entry* e;
 	struct dns_rr* rr;
 	struct ip_addr ip;
 	int i;
 	ticks_t now;
 	
 	now=get_ticks_raw();
 	LOCK_DNS_HASH();
 		for (h=0; h<DNS_HASH_SIZE; h++){
 			clist_foreach(&dns_hash[h], e, next){
 				for (i=0, rr=e->rr_lst; rr; i++, rr=rr->next){
 					rpc->add(ctx, "sddddddd", 
 								e->name, e->type, i, e->total_size,
 								e->refcnt.val,
 								(s_ticks_t)(e->expire-now)<0?-1:
 									TICKS_TO_S(e->expire-now),
 								TICKS_TO_S(now-e->last_used),
 								e->err_flags);
 					switch(e->type){
 						case T_A:
 						case T_AAAA:
 							if (dns_rr2ip(e->type, rr, &ip)==0){
 								rpc->add(ctx, "ss", "ip", ip_addr2a(&ip) );
 							}else{
 								rpc->add(ctx, "ss", "ip", "<error: bad rr>");
 							}
 							break;
 						case T_SRV:
 							rpc->add(ctx, "ss", "srv", 
 									((struct srv_rdata*)(rr->rdata))->name);
 							break;
 						case T_NAPTR:
 							rpc->add(ctx, "ss", "naptr", 
 									((struct naptr_rdata*)(rr->rdata))->flags);
 							break;
 						case T_CNAME:
 							rpc->add(ctx, "ss", "cname", 
 									((struct cname_rdata*)(rr->rdata))->name);
 							break;
 						default:
 							rpc->add(ctx, "ss", "unknown", "?");
 					}
 					rpc->add(ctx, "dd",
 								(s_ticks_t)(rr->expire-now)<0?-1:
 									TICKS_TO_S(rr->expire-now),
 							rr->err_flags);
 				}
 			}
 		}
 	UNLOCK_DNS_HASH();
 }
 
 
 #endif