You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/dns_cache.c

4775 lines
131 KiB

/*
* $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
* 2006-10-06 port fix (andrei)
* 2007-06-14 dns iterate through A & AAAA records fix (andrei)
* 2007-06-15 srv rr weight based load balancing support (andrei)
* 2007-06-16 naptr support (andrei)
* 2008-07-18 DNS watchdog support -- can be used to inform the core
* that the DNS servers are down (Miklos)
* 2008-07-25 various rpc commands to manipulate the content
* of the cache (Miklos)
* 2007-07-30 DNS cache measurements added (Gergo)
* 2007-08-17 dns_cache_del_nonexp config option is introduced (Miklos)
* 2008-02-04 DNS cache options are adapted for the configuration
* framework (Miklos)
* 2008-02-11 dns_cache_init cfg parameter is introduced (Miklos)
* 2008-10-17 fixed srv continue with 0 hostname (when falling back to
aaaa) (andrei)
* 2009-03-30 TXT record support, more rpcs (andrei)
* 2009-03-30 EBL record support (andrei)
* 2009-04-01 PTR record support (andrei)
*/
/*!
* \file
* \brief SIP-router core ::
* \ingroup core
* Module: \ref core
*/
#ifdef USE_DNS_CACHE
#ifdef DNS_SRV_LB
#include <stdlib.h> /* FIXME: rand() */
#endif
#include <string.h>
#include "globals.h"
#include "cfg_core.h"
#include "dns_cache.h"
#include "dns_wrappers.h"
#include "compiler_opt.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"
#include "rand/fastrand.h"
#ifdef USE_DNS_CACHE_STATS
#include "pt.h"
#endif
#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_TIMER_INTERVAL 120 /* 2 min. */
#define DNS_HE_MAX_ADDR 10 /* maxium addresses returne in a hostent struct */
#define MAX_CNAME_CHAIN 10
#define SPACE_FORMAT " " /* format of view output */
#define DNS_SRV_ZERO_W_CHANCE 1000 /* one in a 1000*weight_sum chance for
selecting a 0-weight record */
int dns_cache_init=1; /* if 0, the DNS cache is not initialized at startup */
static gen_lock_t* dns_hash_lock=0;
static volatile unsigned int *dns_cache_mem_used=0; /* current mem. use */
unsigned int dns_timer_interval=DEFAULT_DNS_TIMER_INTERVAL; /* in s */
int dns_flags=0; /* default flags used for the dns_*resolvehost
(compatibility wrappers) */
#ifdef USE_DNS_CACHE_STATS
struct t_dns_cache_stats* dns_cache_stats=0;
#endif
#define LOCK_DNS_HASH() lock_get(dns_hash_lock)
#define UNLOCK_DNS_HASH() lock_release(dns_hash_lock)
#define FIX_TTL(t) \
(((t)<cfg_get(core, core_cfg, dns_cache_min_ttl))? \
cfg_get(core, core_cfg, dns_cache_min_ttl): \
(((t)>cfg_get(core, core_cfg, dns_cache_max_ttl))? \
cfg_get(core, core_cfg, 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;
#ifdef DNS_WATCHDOG_SUPPORT
static atomic_t *dns_servers_up = NULL;
#endif
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",
"unresolvable 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 */
"unresolvable NAPTR request",
"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)
{
#ifdef DNS_WATCHDOG_SUPPORT
/* do not clean the hash table if the servers are down */
if (atomic_get(dns_servers_up) == 0)
return (ticks_t)(-1);
#endif
if (*dns_cache_mem_used>12*(cfg_get(core, core_cfg, dns_cache_max_mem)/16)){ /* ~ 75% used */
dns_cache_free_mem(cfg_get(core, core_cfg, 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;
}
#ifdef DNS_WATCHDOG_SUPPORT
if (dns_servers_up){
shm_free(dns_servers_up);
dns_servers_up=0;
}
#endif
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
#ifdef USE_DNS_CACHE_STATS
if (dns_cache_stats)
shm_free(dns_cache_stats);
#endif
if (dns_cache_mem_used){
shm_free((void*)dns_cache_mem_used);
dns_cache_mem_used=0;
}
}
/* set the value of dns_flags */
void fix_dns_flags(str *gname, str *name)
{
/* restore the original value of dns_cache_flags first
* (DNS_IPV4_ONLY may have been set only because dns_try_ipv6
* was disabled, and the flag must be cleared when
* dns_try_ipv6 is enabled) (Miklos)
*/
dns_flags = cfg_get(core, core_cfg, dns_cache_flags) & 7;
if (cfg_get(core, core_cfg, dns_try_ipv6)==0){
dns_flags|=DNS_IPV4_ONLY;
}
if (dns_flags & DNS_IPV4_ONLY){
dns_flags&=~(DNS_IPV6_ONLY|DNS_IPV6_FIRST);
}
if (cfg_get(core, core_cfg, dns_srv_lb)){
#ifdef DNS_SRV_LB
dns_flags|=DNS_SRV_RR_LB;
#else
LOG(L_WARN, "WARNING: fix_dns_flags: SRV loadbalaning is set, but"
" support for it is not compiled -- ignoring\n");
#endif
}
if (cfg_get(core, core_cfg, dns_try_naptr)) {
#ifndef USE_NAPTR
LOG(L_WARN, "WARNING: fix_dns_flags: NAPTR support is enabled, but"
" support for it is not compiled -- ignoring\n");
#endif
dns_flags|=DNS_TRY_NAPTR;
}
}
/* fixup function for use_dns_failover
* verifies that use_dns_cache is set to 1
*/
int use_dns_failover_fixup(void *handle, str *gname, str *name, void **val)
{
if ((int)(long)(*val) && !cfg_get(core, handle, use_dns_cache)) {
LOG(L_ERR, "ERROR: use_dns_failover_fixup(): "
"DNS cache is turned off, failover cannot be enabled. "
"(set use_dns_cache to 1)\n");
return -1;
}
return 0;
}
/* fixup function for use_dns_cache
* verifies that dns_cache_init is set to 1
*/
int use_dns_cache_fixup(void *handle, str *gname, str *name, void **val)
{
if ((int)(long)(*val) && !dns_cache_init) {
LOG(L_ERR, "ERROR: use_dns_cache_fixup(): "
"DNS cache is turned off by dns_cache_init=0, "
"it cannot be enabled runtime.\n");
return -1;
}
if (((int)(long)(*val)==0) && cfg_get(core, handle, use_dns_failover)) {
LOG(L_ERR, "ERROR: use_dns_failover_fixup(): "
"DNS failover depends on use_dns_cache, set use_dns_failover "
"to 0 before disabling the DNS cache\n");
return -1;
}
return 0;
}
/* KByte to Byte conversion */
int dns_cache_max_mem_fixup(void *handle, str *gname, str *name, void **val)
{
unsigned int u;
u = ((unsigned int)(long)(*val))<<10;
(*val) = (void *)(long)u;
return 0;
}
int init_dns_cache()
{
int r;
int ret;
if (dns_cache_init==0) {
/* the DNS cache is turned off */
default_core_cfg.use_dns_cache=0;
default_core_cfg.use_dns_failover=0;
return 0;
}
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;
}
#ifdef DNS_WATCHDOG_SUPPORT
dns_servers_up=shm_malloc(sizeof(atomic_t));
if (dns_servers_up==0){
ret=E_OUT_OF_MEM;
goto error;
}
atomic_set(dns_servers_up, 1);
#endif
/* fix options */
default_core_cfg.dns_cache_max_mem<<=10; /* Kb */ /* TODO: test with 0 */
if (default_core_cfg.use_dns_cache==0)
default_core_cfg.use_dns_failover=0; /* cannot work w/o dns_cache support */
/* fix flags */
fix_dns_flags(NULL, NULL);
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;
}
#ifdef USE_DNS_CACHE_STATS
int init_dns_cache_stats(int iproc_num)
{
/* do not initialize the stats array if the DNS cache will not be used */
if (dns_cache_init==0) return 0;
/* if it is already initialized */
if (dns_cache_stats)
shm_free(dns_cache_stats);
dns_cache_stats=shm_malloc(sizeof(*dns_cache_stats) * iproc_num);
if (dns_cache_stats==0){
return E_OUT_OF_MEM;
}
memset(dns_cache_stats, 0, sizeof(*dns_cache_stats) * iproc_num);
return 0;
}
#endif
/* hash function, type is not used (obsolete)
* params: char* s, int len, int type
* returns the hash value
*/
#define dns_hash_no(s, len, type) \
(get_hash1_case_raw((s),(len)) % DNS_HASH_SIZE)
#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 */
inline static 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;
#ifdef DNS_WATCHDOG_SUPPORT
int servers_up;
servers_up = atomic_get(dns_servers_up);
#endif
cname_chain=0;
ret=0;
now=get_ticks_raw();
*err=0;
again:
*h=dns_hash_no(name->s, name->len, type);
#ifdef DNS_CACHE_DEBUG
DBG("dns_hash_find(%.*s(%d), %d), h=%d\n", name->len, name->s,
name->len, type, *h);
#endif
clist_foreach_safe(&dns_hash[*h], e, tmp, next){
if (
#ifdef DNS_WATCHDOG_SUPPORT
/* remove expired elements only when the dns servers are up */
servers_up &&
#endif
/* automatically remove expired elements */
((e->ent_flags & DNS_FLAG_PERMANENT) == 0) &&
((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->rr_lst==0) || (e->ent_flags & DNS_FLAG_BAD_NAME)) &&
(e->name_len==name->len) &&
(strncasecmp(e->name, name->s, e->name_len)==0)){
/*if CNAME matches and CNAME is entry is not a neg. cache entry
(could be produced by a specific CNAME lookup)*/
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 (((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& (!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 (((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& ((s_ticks_t)(now-e->expire)>=0)
) {
_dns_hash_remove(e);
deleted++;
}
n++;
if (n>=no) goto skip;
}
}
/* 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 ((e->ent_flags & DNS_FLAG_PERMANENT) == 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 (((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& (!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 (((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& ((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 (((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& ((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)>=cfg_get(core, core_cfg, dns_cache_max_mem)){
#ifdef USE_DNS_CACHE_STATS
dns_cache_stats[process_no].dc_lru_cnt++;
#endif
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,
!cfg_get(core, core_cfg, dns_cache_del_nonexp));
if ((*dns_cache_mem_used+e->total_size)>=cfg_get(core, core_cfg, 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);
#ifdef DNS_CACHE_DEBUG
DBG("dns_cache_add: adding %.*s(%d) %d (flags=%0x) at %d\n",
e->name_len, e->name, e->name_len, e->type, e->ent_flags, h);
#endif
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)>=cfg_get(core, core_cfg, dns_cache_max_mem)){
#ifdef USE_DNS_CACHE_STATS
dns_cache_stats[process_no].dc_lru_cnt++;
#endif
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,
!cfg_get(core, core_cfg, dns_cache_del_nonexp));
LOCK_DNS_HASH();
if ((*dns_cache_mem_used+e->total_size)>=cfg_get(core, core_cfg, 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);
#ifdef DNS_CACHE_DEBUG
DBG("dns_cache_add: adding %.*s(%d) %d (flags=%0x) at %d\n",
e->name_len, e->name, e->name_len, e->type, e->ent_flags, h);
#endif
*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;
#ifdef DNS_CACHE_DEBUG
DBG("dns_cache_mk_bad_entry(%.*s, %d, %d, %d)\n", name->len, name->s,
type, ttl, flags);
#endif
size=sizeof(struct dns_hash_entry)+name->len-1+1;
e=shm_malloc(size);
if (e==0){
LOG(L_ERR, "ERROR: dns_cache_mk_bad_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->ent_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;
}
/* creates an srv hash entry from the given parameters
* returns 0 on error */
static struct dns_hash_entry* dns_cache_mk_srv_entry(str* name,
unsigned short priority,
unsigned short weight,
unsigned short port,
str* rr_name,
int ttl)
{
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),
* and similarly, dns_rr must be rounded to sizeof(short).
* multiple.
* Memory image:
* struct dns_hash_entry
* name (name_len+1 bytes)
* padding to multiple of sizeof(long)
* dns_rr
* padding to multiple of sizeof(short)
* rdata
*/
size=ROUND_POINTER(sizeof(struct dns_hash_entry)+name->len-1+1) +
ROUND_SHORT(sizeof(struct dns_rr)) +
sizeof(struct srv_rdata)-1 +
rr_name->len+1;
e=shm_malloc(size);
if (e==0){
LOG(L_ERR, "ERROR: dns_cache_srv_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=T_SRV;
now=get_ticks_raw();
e->last_used=now;
e->expire=now+S_TO_TICKS(ttl);
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+ROUND_SHORT(sizeof(struct dns_rr)));
e->rr_lst->expire=e->expire;
((struct srv_rdata*)e->rr_lst->rdata)->priority = priority;
((struct srv_rdata*)e->rr_lst->rdata)->weight = weight;
((struct srv_rdata*)e->rr_lst->rdata)->port = port;
((struct srv_rdata*)e->rr_lst->rdata)->name_len = rr_name->len;
memcpy(((struct srv_rdata*)e->rr_lst->rdata)->name, rr_name->s, rr_name->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;
int i;
#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;
case T_TXT:
for(; *p;){
if (!rec_matches((*p), type, name)){
/* skip this record */
p=&(*p)->next; /* advance */
continue;
}
/* padding to char* (because of txt[]->cstr*/
size+=ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
TXT_RDATA_SIZE(*(struct txt_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_EBL:
for(; *p;){
if (!rec_matches((*p), type, name)){
/* skip this record */
p=&(*p)->next; /* advance */
continue;
}
/* padding to char* (because of the char* pointers */
size+=ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
EBL_RDATA_SIZE(*(struct ebl_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_PTR:
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)+
PTR_RDATA_SIZE(*(struct ptr_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){
#ifdef DNS_CACHE_DEBUG
DBG("dns_cache_mk_rd_entry: entry %.*s (%d) not found\n",
name->len, name->s, type);
#endif
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_rd_entry: out of memory\n");
return 0;
}
memset(e, 0, size); /* init with 0 */
clist_init(e, next, prev);
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 naptr_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);
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;
case T_TXT:
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)));
memcpy(rr->rdata, l->rdata,
TXT_RDATA_SIZE(*(struct txt_rdata*)l->rdata));
/* adjust the string pointers */
for (i=0; i<((struct txt_rdata*)l->rdata)->cstr_no; i++){
((struct txt_rdata*)rr->rdata)->txt[i].cstr=
translate_pointer((char*)rr->rdata, (char*)l->rdata,
((struct txt_rdata*)l->rdata)->txt[i].cstr);
}
rr->next=(void*)((char*)rr+
ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
TXT_RDATA_SIZE(*(struct txt_rdata*)l->rdata)));
tail_rr=&(rr->next);
rr=rr->next;
}
break;
case T_EBL:
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)));
memcpy(rr->rdata, l->rdata,
EBL_RDATA_SIZE(*(struct ebl_rdata*)l->rdata));
/* adjust the string pointers */
((struct ebl_rdata*)rr->rdata)->separator=
translate_pointer((char*)rr->rdata, (char*)l->rdata,
((struct ebl_rdata*)l->rdata)->separator);
((struct ebl_rdata*)rr->rdata)->separator=
translate_pointer((char*)rr->rdata, (char*)l->rdata,
((struct ebl_rdata*)l->rdata)->separator);
((struct ebl_rdata*)rr->rdata)->apex=
translate_pointer((char*)rr->rdata, (char*)l->rdata,
((struct ebl_rdata*)l->rdata)->apex);
rr->next=(void*)((char*)rr+
ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
EBL_RDATA_SIZE(*(struct ebl_rdata*)l->rdata)));
tail_rr=&(rr->next);
rr=rr->next;
}
break;
case T_PTR:
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,
PTR_RDATA_SIZE(*(struct ptr_rdata*)l->rdata));
rr->next=(void*)((char*)rr+ROUND_POINTER(sizeof(struct dns_rr)+
PTR_RDATA_SIZE(*(struct ptr_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, j;
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;
case T_TXT:
/* padding to char* (because of txt[]->cstr)*/
rec[r].size+=ROUND_POINTER(ROUND_POINTER(
sizeof(struct dns_rr))+
TXT_RDATA_SIZE(*(struct txt_rdata*)l->rdata));
break;
case T_EBL:
/* padding to char* (because of char* pointers)*/
rec[r].size+=ROUND_POINTER(ROUND_POINTER(
sizeof(struct dns_rr))+
EBL_RDATA_SIZE(*(struct ebl_rdata*)l->rdata));
break;
case T_PTR:
/* no padding */
rec[r].size+=ROUND_POINTER(sizeof(struct dns_rr)+
PTR_RDATA_SIZE(*(struct ptr_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_rd_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);
rec[r].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;
case T_TXT:
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)));
memcpy(rec[r].rr->rdata, l->rdata,
TXT_RDATA_SIZE(*(struct txt_rdata*)l->rdata));
/* adjust the string pointers */
for (j=0; j<((struct txt_rdata*)l->rdata)->cstr_no; j++){
((struct txt_rdata*)rec[r].rr->rdata)->txt[j].cstr=
translate_pointer((char*)rec[r].rr->rdata,
(char*)l->rdata,
((struct txt_rdata*)l->rdata)->txt[j].cstr);
}
rec[r].rr->next=(void*)((char*)rec[r].rr+
ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
TXT_RDATA_SIZE(*(struct txt_rdata*)l->rdata)));
rec[r].tail_rr=&(rec[r].rr->next);
rec[r].rr=rec[r].rr->next;
break;
case T_EBL:
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)));
memcpy(rec[r].rr->rdata, l->rdata,
EBL_RDATA_SIZE(*(struct ebl_rdata*)l->rdata));
/* adjust the string pointers */
((struct ebl_rdata*)rec[r].rr->rdata)->separator=
translate_pointer((char*)rec[r].rr->rdata,
(char*)l->rdata,
((struct ebl_rdata*)l->rdata)->separator);
((struct ebl_rdata*)rec[r].rr->rdata)->apex=
translate_pointer((char*)rec[r].rr->rdata,
(char*)l->rdata,
((struct ebl_rdata*)l->rdata)->apex);
rec[r].rr->next=(void*)((char*)rec[r].rr+
ROUND_POINTER(ROUND_POINTER(sizeof(struct dns_rr))+
EBL_RDATA_SIZE(*(struct ebl_rdata*)l->rdata)));
rec[r].tail_rr=&(rec[r].rr->next);
rec[r].rr=rec[r].rr->next;
break;
case T_PTR:
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,
PTR_RDATA_SIZE(*(struct ptr_rdata*)l->rdata));
rec[r].rr->next=(void*)((char*)rec[r].rr+
ROUND_POINTER(sizeof(struct dns_rr)+
PTR_RDATA_SIZE(*(struct ptr_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;
#ifdef DNS_CACHE_DEBUG
DBG("dns_get_related(%p (%.*s, %d), %d, *%p) (%d)\n", e,
e->name_len, e->name, e->type, type, *records, cname_chain_len);
#endif
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);
lst_end=t->prev; /* needed for clist_append*/
clist_append_sublist(l, t, lst_end, 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);
lst_end=t->prev; /* needed for clist_append*/
clist_append_sublist(l, t, lst_end, next, prev);
}
}
}
break;
#ifdef USE_NAPTR
case T_NAPTR:
#ifdef NAPTR_CACHE_ALL_ARS
if (*records)
dns_cache_mk_rd_entry2(*records);
#else
for (rr=e->rr_lst; rr && *records; rr=rr->next){
if (naptr_get_sip_proto((struct naptr_rdata*)rr->rdata)>0){
tmp.s=((struct naptr_rdata*)rr->rdata)->repl;
tmp.len=((struct naptr_rdata*)rr->rdata)->repl_len;
t=dns_cache_mk_rd_entry(&tmp, T_SRV, records);
if (t){
if (*records)
dns_get_related(t, T_SRV, records);
lst_end=t->prev; /* needed for clist_append*/
clist_append_sublist(l, t, lst_end, next, prev);
}
}
}
#endif /* NAPTR_CACHE_ALL_ARS */
#endif /* USE_NAPTR */
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{
/* 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;
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];
struct dns_hash_entry* old;
str rec_name;
int add_record, h, err;
e=0;
l=0;
cname_val.s=0;
old = NULL;
#ifdef USE_DNS_CACHE_STATS
if (dns_cache_stats)
dns_cache_stats[process_no].dns_req_cnt++;
#endif /* USE_DNS_CACHE_STATS */
if (type==T_A){
if (str2ip6(name)!=0)
goto end;
if ((ip=str2ip(name))!=0){
e=dns_cache_mk_ip_entry(name, ip);
if (likely(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 (str2ip(name)!=0)
goto end;
if ((ip=str2ip6(name))!=0){
e=dns_cache_mk_ip_entry(name, ip);
if (likely(e))
atomic_set(&e->refcnt, 1);/* because we ret. a ref. to it*/
goto end;/* we do not cache obvious stuff */
}
}
#ifdef DNS_WATCHDOG_SUPPORT
if (atomic_get(dns_servers_up)==0)
goto end; /* the servers are down, needless to perform the query */
#endif
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 (likely(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 (likely(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;
/* add the new record to the cache by default */
add_record = 1;
if (cfg_get(core, core_cfg, dns_cache_rec_pref) > 0) {
/* check whether there is an old record with the
* same type in the cache */
rec_name.s = r->name;
rec_name.len = r->name_len;
old = _dns_hash_find(&rec_name, r->type, &h, &err);
if (old) {
if (old->type != r->type) {
/* probably CNAME found */
old = NULL;
} else if (old->ent_flags & DNS_FLAG_PERMANENT) {
/* never overwrite permanent entries */
add_record = 0;
} else if ((old->ent_flags & DNS_FLAG_BAD_NAME) == 0) {
/* Non-negative, non-permanent entry found with
* the same type. */
add_record =
/* prefer new records */
((cfg_get(core, core_cfg, dns_cache_rec_pref) == 2)
/* prefer the record with the longer lifetime */
|| ((cfg_get(core, core_cfg, dns_cache_rec_pref) == 3)
&& TICKS_LT(old->expire, r->expire)));
}
}
}
if (add_record) {
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);
}
if (old) {
_dns_hash_remove(old);
old = NULL;
}
} else {
if (old) {
if (r == e) {
/* this entry has to be returned */
e = old;
atomic_inc(&e->refcnt);
}
old = NULL;
}
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 (cfg_get(core, core_cfg, dns_neg_cache_ttl)){
e=dns_cache_mk_bad_entry(name, type,
cfg_get(core, core_cfg, dns_neg_cache_ttl), DNS_FLAG_BAD_NAME);
if (likely(e)) {
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 */
}
}
/* add the new record to the cache by default */
add_record = 1;
if (cfg_get(core, core_cfg, dns_cache_rec_pref) > 0) {
/* check whether there is an old record with the
* same type in the cache */
rec_name.s = r->name;
rec_name.len = r->name_len;
old = _dns_hash_find(&rec_name, r->type, &h, &err);
if (old) {
if (old->type != r->type) {
/* probably CNAME found */
old = NULL;
} else if (old->ent_flags & DNS_FLAG_PERMANENT) {
/* never overwrite permanent entries */
add_record = 0;
} else if ((old->ent_flags & DNS_FLAG_BAD_NAME) == 0) {
/* Non-negative, non-permanent entry found with
* the same type. */
add_record =
/* prefer new records */
((cfg_get(core, core_cfg, dns_cache_rec_pref) == 2)
/* prefer the record with the longer lifetime */
|| ((cfg_get(core, core_cfg, dns_cache_rec_pref) == 3)
&& TICKS_LT(old->expire, r->expire)));
}
}
}
if (add_record) {
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);
}
if (old) {
_dns_hash_remove(old);
old = NULL;
}
} else {
if (old) {
if (r == e) {
/* this entry has to be returned */
e = old;
atomic_inc(&e->refcnt);
}
old = NULL;
}
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);
#ifdef USE_DNS_CACHE_STATS
if (e) {
if ((e->ent_flags & DNS_FLAG_BAD_NAME) && dns_cache_stats)
/* negative DNS cache hit */
dns_cache_stats[process_no].dc_neg_hits_cnt++;
else if (((e->ent_flags & DNS_FLAG_BAD_NAME) == 0)
&& dns_cache_stats
) /* DNS cache hit */
dns_cache_stats[process_no].dc_hits_cnt++;
if (dns_cache_stats)
dns_cache_stats[process_no].dns_req_cnt++;
}
#endif /* USE_DNS_CACHE_STATS */
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->ent_flags & DNS_FLAG_BAD_NAME)){
/* negative cache => not resolvable */
dns_hash_put(e);
e=0;
}
error:
rec_cnt--;
return e;
}
/* gets the first non-expired 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;
#ifdef DNS_WATCHDOG_SUPPORT
int servers_up;
servers_up = atomic_get(dns_servers_up);
#endif
for(rr=e->rr_lst, n=0;rr && (n<*no);rr=rr->next, n++);/* skip *no records*/
for(;rr;rr=rr->next){
if (
#ifdef DNS_WATCHDOG_SUPPORT
/* check the expiration time only when the servers are up */
servers_up &&
#endif
((e->ent_flags & DNS_FLAG_PERMANENT) == 0) &&
((s_ticks_t)(now-rr->expire)>=0) /* expired rr */
)
continue;
/* everything is ok now */
*no=n;
return rr;
}
*no=n;
return 0;
}
#ifdef DNS_SRV_LB
#define srv_reset_tried(p) (*(p)=0)
#define srv_marked(p, i) (*(p)&(1UL<<(i)))
#define srv_mark_tried(p, i) \
do{ \
(*(p)|=(1UL<<(i))); \
}while(0)
#define srv_next_rr(n, f, i) srv_mark_tried(f, i)
/* returns a random number between 0 and max inclusive (0<=r<=max) */
inline static unsigned dns_srv_random(unsigned max)
{
return fastrand_max(max);
}
/* for a SRV record it will return the next entry to be tried according
* to the RFC2782 server selection mechanism
* params:
* e is a dns srv hash entry
* no is the start index of the current group (a group is a set of SRV
* rrs with the same priority)
* tried is a bitmap where the tried srv rrs of the same priority are
* marked
* 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
* WARNING: unlike dns_entry_get_rr() this will always return another
* another rr automatically (*no must not be incremented)
*
* Example usage:
* list all non-expired, non-bad-marked, never tried before srv records
* using the rfc2782 algo:
* e=dns_get_entry(name, T_SRV);
* if (e){
* no=0;
* srv_reset_tried(&tried);
* now=get_ticks_raw();
* while(rr=dns_srv_get_nxt_rr(e, &tried, &no, now){
* DBG("address %d\n", *no);
* }
* }
*
*/
inline static struct dns_rr* dns_srv_get_nxt_rr(struct dns_hash_entry* e,
srv_flags_t* tried,
unsigned char* no, ticks_t now)
{
#define MAX_SRV_GRP_IDX (sizeof(srv_flags_t)*8)
struct dns_rr* rr;
struct dns_rr* start_grp;
int n;
unsigned sum;
unsigned prio;
unsigned rand_w;
int found;
int saved_idx;
int zero_weight; /* number of records with 0 weight */
int i, idx;
struct r_sums_entry{
unsigned r_sum;
struct dns_rr* rr;
}r_sums[MAX_SRV_GRP_IDX];
#ifdef DNS_WATCHDOG_SUPPORT
int servers_up;
servers_up = atomic_get(dns_servers_up);
#endif
memset(r_sums, 0, sizeof(struct r_sums_entry) * MAX_SRV_GRP_IDX);
rand_w=0;
for(rr=e->rr_lst, n=0;rr && (n<*no);rr=rr->next, n++);/* skip *no records*/
retry:
if (unlikely(rr==0))
goto no_more_rrs;
start_grp=rr;
prio=((struct srv_rdata*)start_grp->rdata)->priority;
sum=0;
saved_idx=-1;
zero_weight = 0;
found=0;
for (idx=0;rr && (prio==((struct srv_rdata*)rr->rdata)->priority) &&
(idx < MAX_SRV_GRP_IDX); idx++, rr=rr->next){
if ((
#ifdef DNS_WATCHDOG_SUPPORT
/* check the expiration time only when the servers are up */
servers_up &&
#endif
((e->ent_flags & DNS_FLAG_PERMANENT) == 0) &&
((s_ticks_t)(now-rr->expire)>=0) /* expired entry */) ||
(srv_marked(tried, idx)) ) /* already tried */{
r_sums[idx].r_sum=0; /* 0 sum, to skip over it */
r_sums[idx].rr=0; /* debug: mark it as unused */
continue;
}
/* special case, 0 weight records should be "first":
* remember the first rr int the "virtual" list: A 0 weight must
* come first if present, else get the first one */
if ((saved_idx==-1) || (((struct srv_rdata*)rr->rdata)->weight==0)){
saved_idx=idx;
}
zero_weight += (((struct srv_rdata*)rr->rdata)->weight == 0);
sum+=((struct srv_rdata*)rr->rdata)->weight;
r_sums[idx].r_sum=sum;
r_sums[idx].rr=rr;
found++;
}
if (found==0){
/* try in the next priority group */
n+=idx; /* next group start idx, last rr */
srv_reset_tried(tried);
goto retry;
}else if ((found==1) || (sum==0) ||
(((rand_w=(dns_srv_random(sum-1)+1))==1) && zero_weight &&
(dns_srv_random(DNS_SRV_ZERO_W_CHANCE)==0))){
/* 1. if only one found, avoid a useless random() call
and select it (saved_idx will point to it).
* 2. if the sum of weights is 0 (all have 0 weight) or
* 3. rand_w==1 and there are records with 0 weight and
* random(probab. of selecting a 0-weight)
* immediately select a 0 weight record.
* (this takes care of the 0-weight at the beginning requirement) */
i=saved_idx; /* saved idx contains either first 0 weight or first
valid record */
goto found;
}
/* if we are here => rand_w is not 0 and we have at least 2 valid options
* => we can safely iterate on the whole r_sums[] whithout any other
* extra checks */
for (i=0; (i<idx) && (r_sums[i].r_sum<rand_w); i++);
found:
#ifdef DNS_CACHE_DEBUG
DBG("dns_srv_get_nxt_rr(%p, %lx, %d, %u): selected %d/%d in grp. %d"
" (rand_w=%d, rr=%p rd=%p p=%d w=%d rsum=%d)\n",
e, (unsigned long)*tried, *no, now, i, idx, n, rand_w, r_sums[i].rr,
(r_sums[i].rr)?r_sums[i].rr->rdata:0,
(r_sums[i].rr&&r_sums[i].rr->rdata)?((struct srv_rdata*)r_sums[i].rr->rdata)->priority:0,
(r_sums[i].rr&&r_sums[i].rr->rdata)?((struct srv_rdata*)r_sums[i].rr->rdata)->weight:0,
r_sums[i].r_sum);
#endif
/* i is the winner */
*no=n; /* grp. start */
srv_mark_tried(tried, i); /* mark it */
return r_sums[i].rr;
no_more_rrs:
*no=n;
return 0;
}
#endif /* DNS_SRV_LB */
/* 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
*/
inline static 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();
/* if the entry has already expired use the time at the end of lifetime */
if (unlikely((s_ticks_t)(now-e->expire)>=0)) now=e->expire-1;
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)
*/
inline static struct hostent* dns_a_get_he(str* name)
{
struct dns_hash_entry* e;
struct ip_addr* ip;
struct hostent* he;
e=0;
if (str2ip6(name)!=0)
return 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)
*/
inline static struct hostent* dns_aaaa_get_he(str* name)
{
struct dns_hash_entry* e;
struct ip_addr* ip;
struct hostent* he;
e=0;
if (str2ip(name)!=0)
return 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;
struct hostent* ret;
if ((cfg_get(core, core_cfg, use_dns_cache)==0) || (dns_hash==0)){ /* not init yet */
ret = _resolvehost(name);
if(unlikely(!ret)){
/* increment dns error counter */
if(counters_initialized())
counter_inc(dns_cnts_h.failed_dns_req);
}
return ret;
}
host.s=name;
host.len=strlen(name);
return dns_get_he(&host, dns_flags);
}
#if 0
/* resolves a host name trying NAPTR, SRV, A & AAAA lookups, for details
* see dns_sip_resolve()
* FIXME: this version will return only the first ip
* 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,
char* proto)
{
struct dns_srv_handle h;
struct ip_addr ip;
int ret;
if ((cfg_get(core, core_cfg, use_dns_cache==0)) || (dns_hash==0)){
/* not init or off => use normal, non-cached version */
return _sip_resolvehost(name, port, proto);
}
dns_srv_handle_init(&h);
ret=dns_sip_resolve(&h, name, &ip, port, proto, dns_flags);
dns_srv_handle_put(&h);
if (ret>=0)
return ip_addr2he(name, &ip);
return 0;
}
#endif
/* 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_srv_sip_resolvehost(str* name, unsigned short* port,
char* proto)
{
struct hostent* he;
struct ip_addr* ip;
static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
str srv_name;
char srv_proto;
if ((cfg_get(core, core_cfg, use_dns_cache)==0) || (dns_hash==0)){
/* not init or off => use normal, non-cached version */
return _sip_resolvehost(name, port, proto);
}
if (proto){ /* makes sure we have a protocol set*/
if (*proto==0)
*proto=srv_proto=PROTO_UDP; /* default */
else
srv_proto=*proto;
}else{
srv_proto=PROTO_UDP;
}
/* try SRV if no port specified (draft-ietf-sip-srv-06) */
if ((port)&&(*port==0)){
*port=(srv_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)
|| ((ip=str2ip6(name))!=0)
){
/* we are lucky, this is an ip address */
return ip_addr2he(name,ip);
}
if(srv_proto==PROTO_WS || srv_proto==PROTO_WS) {
/* no srv records for web sockets */
return 0;
}
switch(srv_proto){
case PROTO_UDP:
case PROTO_TCP:
case PROTO_TLS:
case PROTO_SCTP:
create_srv_name(srv_proto, name, tmp);
break;
default:
LOG(L_CRIT, "BUG: sip_resolvehost: unknown proto %d\n",
(int)srv_proto);
return 0;
}
srv_name.s=tmp;
srv_name.len=strlen(tmp);
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;
}
#ifdef USE_NAPTR
/* iterates over a naptr rr list, returning each time a "good" naptr record
* is found.( srv type, no regex and a supported protocol)
* params:
* naptr_head - naptr dns_rr list head
* tried - bitmap used to keep track of the already tried records
* (no more then sizeof(tried)*8 valid records are
* ever walked
* srv_name - if succesfull, it will be set to the selected record
* srv name (naptr repl.)
* proto - if succesfull it will be set to the selected record
* protocol
* returns 0 if no more records found or a pointer to the selected record
* and sets protocol and srv_name
* WARNING: when calling first time make sure you run first
* naptr_iterate_init(&tried)
*/
struct naptr_rdata* dns_naptr_sip_iterate(struct dns_rr* naptr_head,
naptr_bmp_t* tried,
str* srv_name, char* proto)
{
int i, idx;
struct dns_rr* l;
struct naptr_rdata* naptr;
struct naptr_rdata* naptr_saved;
char saved_proto;
char naptr_proto;
idx=0;
naptr_proto=PROTO_NONE;
naptr_saved=0;
saved_proto=0;
i=0;
for(l=naptr_head; l && (i<MAX_NAPTR_RRS); l=l->next){
naptr=(struct naptr_rdata*) l->rdata;
if (naptr==0){
LOG(L_CRIT, "naptr_iterate: BUG: null rdata\n");
goto end;
}
/* check if valid and get proto */
if ((naptr_proto=naptr_get_sip_proto(naptr))<=0) continue;
if (*tried& (1<<i)){
i++;
continue; /* already tried */
}
#ifdef DNS_CACHE_DEBUG
DBG("naptr_iterate: found a valid sip NAPTR rr %.*s,"
" proto %d\n", naptr->repl_len, naptr->repl,
(int)naptr_proto);
#endif
if ((naptr_proto_supported(naptr_proto))){
if (naptr_choose(&naptr_saved, &saved_proto,
naptr, naptr_proto))
idx=i;
}
i++;
}
if (naptr_saved){
/* found something */
#ifdef DNS_CACHE_DEBUG
DBG("naptr_iterate: choosed NAPTR rr %.*s, proto %d"
" tried: 0x%x\n", naptr_saved->repl_len,
naptr_saved->repl, (int)saved_proto, *tried);
#endif
*tried|=1<<idx;
*proto=saved_proto;
srv_name->s=naptr_saved->repl;
srv_name->len=naptr_saved->repl_len;
return naptr_saved;
}
end:
return 0;
}
/* resolves a host name trying NAPTR lookup if *proto==0 and *port==0, 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; if proto==0 => no SRV lookup
* returns: hostent struct & *port filled with the port from the SRV record;
* 0 on error
*/
struct hostent* dns_naptr_sip_resolvehost(str* name, unsigned short* port,
char* proto)
{
struct hostent* he;
struct ip_addr* tmp_ip;
naptr_bmp_t tried_bmp;
struct dns_hash_entry* e;
char n_proto;
char origproto;
str srv_name;
origproto=*proto;
he=0;
if (dns_hash==0){ /* not init => use normal, non-cached version */
LOG(L_WARN, "WARNING: dns_sip_resolvehost: called before dns cache"
" initialization\n");
return _sip_resolvehost(name, port, proto);
}
if (proto && port && (*proto==0) && (*port==0)){
*proto=PROTO_UDP; /* just in case we don't find another */
/* check if it's an ip address */
if ( ((tmp_ip=str2ip(name))!=0)
|| ((tmp_ip=str2ip6(name))!=0)
){
/* we are lucky, this is an ip address */
if (((dns_flags&DNS_IPV4_ONLY) && (tmp_ip->af==AF_INET6))||
((dns_flags&DNS_IPV6_ONLY) && (tmp_ip->af==AF_INET))){
return 0;
}
*port=SIP_PORT;
return ip_addr2he(name, tmp_ip);
}
/* do naptr lookup */
if ((e=dns_get_entry(name, T_NAPTR))==0)
goto naptr_not_found;
naptr_iterate_init(&tried_bmp);
while(dns_naptr_sip_iterate(e->rr_lst, &tried_bmp,
&srv_name, &n_proto)){
if ((he=dns_srv_get_he(&srv_name, port, dns_flags))!=0){
#ifdef DNS_CACHE_DEBUG
DBG("dns_naptr_sip_resolvehost(%.*s, %d, %d) srv, ret=%p\n",
name->len, name->s, (int)*port, (int)*proto, he);
#endif
dns_hash_put(e);
*proto=n_proto;
return he;
}
}
/* no acceptable naptr record found, fallback to srv */
dns_hash_put(e);
}
naptr_not_found:
*proto = origproto;
he = no_naptr_srv_sip_resolvehost(name,port,proto);
/* fallback all the way down to A/AAAA */
if (he==0) {
he=dns_get_he(name,dns_flags);
}
return he;
}
#endif /* USE_NAPTR */
/* resolves a host name trying NAPTR lookup if *proto==0 and *port==0, 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; 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,
char* proto)
{
#ifdef USE_NAPTR
if (dns_flags&DNS_TRY_NAPTR)
return dns_naptr_sip_resolvehost(name, port, proto);
#endif
return dns_srv_sip_resolvehost(name, port, proto);
}
/* 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 used 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)
* Note: either e or name must be different from 0 (name.s !=0 also)
* 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
*/
inline static 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 (str2ip6(name)!=0)
goto error;
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();
/* if the entry has already expired use the time at the end of lifetime */
if (unlikely((s_ticks_t)(now-(*e)->expire)>=0)) now=(*e)->expire-1;
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_resolve(%.*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).
*/
inline static 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 (str2ip(name)!=0)
goto error;
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();
/* if the entry has already expired use the time at the end of lifetime */
if (unlikely((s_ticks_t)(now-(*e)->expire)>=0)) now=(*e)->expire-1;
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
*/
inline static int dns_ip_resolve( struct dns_hash_entry** e,
unsigned char* rr_no,
str* name,
struct ip_addr* ip,
int flags)
{
int ret;
str host;
struct dns_hash_entry* orig;
ret=-E_DNS_NO_IP;
if (*e==0){ /* first call */
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);
}
}else if ((*e)->type==T_A){
/* continue A resolving */
/* retrieve host name from the hash entry (ignore name which might
be null when continuing a srv lookup) */
host.s=(*e)->name;
host.len=(*e)->name_len;
ret=dns_a_resolve(e, rr_no, &host, ip);
if (ret>=0) return ret;
if (!(flags&(DNS_IPV6_ONLY|DNS_IPV6_FIRST|DNS_IPV4_ONLY))){
/* not found, try with AAAA */
orig=*e;
*e=0;
*rr_no=0;
ret=dns_aaaa_resolve(e, rr_no, &host, ip);
/* delay original record release until we're finished with host*/
dns_hash_put(orig);
}
}else if ((*e)->type==T_AAAA){
/* retrieve host name from the hash entry (ignore name which might
be null when continuing a srv lookup) */
host.s=(*e)->name;
host.len=(*e)->name_len;
/* continue AAAA resolving */
ret=dns_aaaa_resolve(e, rr_no, &host, ip);
if (ret>=0) return ret;
if ((flags&DNS_IPV6_FIRST) && !(flags&DNS_IPV6_ONLY)){
/* not found, try with A */
orig=*e;
*e=0;
*rr_no=0;
ret=dns_a_resolve(e, rr_no, &host, ip);
/* delay original record release until we're finished with host*/
dns_hash_put(orig);
}
}else{
LOG(L_CRIT, "BUG: dns_ip_resolve: invalid record type %d\n",
(*e)->type);
}
return ret;
}
/* gets the first srv record starting at rr_no
* Next call will return the next record a.s.o.
* (similar to dns_a_resolve but for srv, sets host, port and automatically
* switches to the next record in the future)
*
* if DNS_SRV_LB and tried!=NULL will do random weight based selection
* for choosing between SRV RRs with the same priority (as described in
* RFC2782).
* If tried==NULL or DNS_SRV_LB is not defined => always returns next
* record in the priority order and for records with the same priority
* the record with the higher weight (from the remaining ones)
*/
inline static int dns_srv_resolve_nxt(struct dns_hash_entry** e,
#ifdef DNS_SRV_LB
srv_flags_t* tried,
#endif
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;
#ifdef DNS_SRV_LB
if (tried)
srv_reset_tried(tried);
#endif
ret=-E_DNS_BAD_SRV_ENTRY;
}
now=get_ticks_raw();
/* if the entry has already expired use the time at the end of lifetime */
if (unlikely((s_ticks_t)(now-(*e)->expire)>=0)) now=(*e)->expire-1;
#ifdef DNS_SRV_LB
if (tried){
rr=dns_srv_get_nxt_rr(*e, tried, rr_no, now);
}else
#endif
{
rr=dns_entry_get_rr(*e, rr_no, now);
(*rr_no)++; /* try next record next time */
}
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
*/
inline static 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){
#ifdef DNS_SRV_LB
if ((ret=dns_srv_resolve_nxt(&h->srv,
(flags & DNS_SRV_RR_LB)?&h->srv_tried_rrs:0,
&h->srv_no,
name, &host, port))<0)
goto error;
#else
if ((ret=dns_srv_resolve_nxt(&h->srv, &h->srv_no,
name, &host, port))<0)
goto error;
#endif
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 */
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 */
}
}while(ret<0);
error:
#ifdef DNS_CACHE_DEBUG
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?ZSW(ip_addr2a(ip)):"");
#endif
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
* 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, *h
*/
inline static int dns_srv_sip_resolve(struct dns_srv_handle* h, str* name,
struct ip_addr* ip, unsigned short* port, char* proto,
int flags)
{
struct dns_srv_proto srv_proto_list[PROTO_LAST];
static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
str srv_name;
struct ip_addr* tmp_ip;
int ret;
struct hostent* he;
size_t i,list_len;
char origproto;
origproto = *proto;
if (dns_hash==0){ /* not init => use normal, non-cached version */
LOG(L_WARN, "WARNING: dns_srv_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;
}
if ((h->srv==0) && (h->a==0)){ /* first call */
if (proto && *proto==0){ /* makes sure we have a protocol set*/
*proto=PROTO_UDP; /* default */
}
h->port=(*proto==PROTO_TLS)?SIPS_PORT:SIP_PORT; /* just in case we
don't find another */
h->proto=*proto; /* store initial protocol */
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_srv_sip_resolve: 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)
|| ((tmp_ip=str2ip6(name))!=0)
){
/* we are lucky, this is an ip address */
if (((flags&DNS_IPV4_ONLY) && (tmp_ip->af==AF_INET6))||
((flags&DNS_IPV6_ONLY) && (tmp_ip->af==AF_INET))){
return -E_DNS_AF_MISMATCH;
}
*ip=*tmp_ip;
*port=h->port;
/* proto already set */
return 0;
}
/* looping on the ordered list until we found a protocol what has srv record */
list_len = create_srv_pref_list(&origproto, srv_proto_list);
for (i=0; i<list_len;i++) {
switch (srv_proto_list[i].proto) {
case PROTO_UDP:
case PROTO_TCP:
case PROTO_TLS:
case PROTO_SCTP:
create_srv_name(srv_proto_list[i].proto, name, tmp);
break;
default:
LOG(L_CRIT, "BUG: dns_srv_sip_resolve: "
"unknown proto %d\n", (int)srv_proto_list[i].proto);
return -E_DNS_CRITICAL;
}
srv_name.s=tmp;
srv_name.len=strlen(tmp);
if ((ret=dns_srv_resolve_ip(h, &srv_name, ip, port, flags))>=0)
{
h->proto = *proto = srv_proto_list[i].proto;
#ifdef DNS_CACHE_DEBUG
DBG("dns_srv_sip_resolve(%.*s, %d, %d), srv0, ret=%d\n",
name->len, name->s, h->srv_no, h->ip_no, ret);
#endif
return ret;
}
}
}
}else{ /* if (*port==0) */
h->port=*port; /* store initial port */
/* proto already set */
}
} /* if (port) */
}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);
if (proto)
*proto=h->proto;
DBG("dns_srv_sip_resolve(%.*s, %d, %d), srv, ret=%d\n",
name->len, name->s, h->srv_no, h->ip_no, ret);
return ret;
}
if (name->len >= MAX_DNS_NAME) {
LOG(L_ERR, "dns_srv_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;
if (proto)
*proto=h->proto;
#ifdef DNS_CACHE_DEBUG
DBG("dns_srv_sip_resolve(%.*s, %d, %d), ip, ret=%d\n",
name->len, name->s, h->srv_no, h->ip_no, ret);
#endif
return ret;
}
#ifdef USE_NAPTR
/* resolves a host name trying:
* - NAPTR lookup if the address is not an ip and proto!=0, port!=0
* *port==0 and *proto=0 and if flags allow NAPTR lookups
* -SRV lookup if port!=0 and *port==0
* - normal A/AAAA lookup if *port!=0, or 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
* 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(h) must be called!
*/
inline static int dns_naptr_sip_resolve(struct dns_srv_handle* h, str* name,
struct ip_addr* ip, unsigned short* port, char* proto,
int flags)
{
struct hostent* he;
struct ip_addr* tmp_ip;
naptr_bmp_t tried_bmp;
struct dns_hash_entry* e;
char n_proto, origproto;
str srv_name;
int ret;
ret=-E_DNS_NO_NAPTR;
origproto=*proto;
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_NAPTR;
}
if (((h->srv==0) && (h->a==0)) && /* first call */
proto && port && (*proto==0) && (*port==0)){
*proto=PROTO_UDP; /* just in case we don't find another */
/* check if it's an ip address */
if ( ((tmp_ip=str2ip(name))!=0)
|| ((tmp_ip=str2ip6(name))!=0)
){
/* we are lucky, this is an ip address */
if (((flags&DNS_IPV4_ONLY) && (tmp_ip->af==AF_INET6))||
((flags&DNS_IPV6_ONLY) && (tmp_ip->af==AF_INET))){
return -E_DNS_AF_MISMATCH;
}
*ip=*tmp_ip;
h->port=SIP_PORT;
h->proto=*proto;
*port=h->port;
return 0;
}
/* do naptr lookup */
if ((e=dns_get_entry(name, T_NAPTR))==0)
goto naptr_not_found;
naptr_iterate_init(&tried_bmp);
while(dns_naptr_sip_iterate(e->rr_lst, &tried_bmp,
&srv_name, &n_proto)){
dns_srv_handle_init(h); /* make sure h does not contain garbage
from previous dns_srv_sip_resolve calls */
if ((ret=dns_srv_resolve_ip(h, &srv_name, ip, port, flags))>=0){
#ifdef DNS_CACHE_DEBUG
DBG("dns_naptr_sip_resolve(%.*s, %d, %d), srv0, ret=%d\n",
name->len, name->s, h->srv_no, h->ip_no, ret);
#endif
dns_hash_put(e);
*proto=n_proto;
h->proto=*proto;
return ret;
}
}
/* no acceptable naptr record found, fallback to srv */
dns_hash_put(e);
dns_srv_handle_init(h); /* make sure h does not contain garbage
from previous dns_srv_sip_resolve calls */
}
naptr_not_found:
*proto=origproto;
return dns_srv_sip_resolve(h, name, ip, port, proto, flags);
}
#endif /* USE_NAPTR */
/* resolves a host name trying:
* - NAPTR lookup if the address is not an ip and proto!=0, port!=0
* *port==0 and *proto=0 and if flags allow NAPTR lookups
* -SRV lookup if port!=0 and *port==0
* - normal A/AAAA lookup if *port!=0, or 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
* 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
*/
int dns_sip_resolve(struct dns_srv_handle* h, str* name,
struct ip_addr* ip, unsigned short* port, char* proto,
int flags)
{
#ifdef USE_NAPTR
if (flags&DNS_TRY_NAPTR)
return dns_naptr_sip_resolve(h, name, ip, port, proto, flags);
#endif
return dns_srv_sip_resolve(h, name, ip, port, proto, flags);
}
/* performs an a lookup and fills ip with the first good ip address
* returns 0 on success, <0 on error (see the error codes)
*/
inline static 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;
}
inline static 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;
}
#ifdef DNS_WATCHDOG_SUPPORT
/* sets the state of the DNS servers:
* 1: at least one server is up
* 0: all the servers are down
*/
void dns_set_server_state(int state)
{
atomic_set(dns_servers_up, state);
}
/* returns the state of the DNS servers */
int dns_get_server_state(void)
{
return atomic_get(dns_servers_up);
}
#endif /* DNS_WATCHDOG_SUPPORT */
/* rpc functions */
void dns_cache_mem_info(rpc_t* rpc, void* ctx)
{
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
rpc->add(ctx, "dd", *dns_cache_mem_used, cfg_get(core, core_cfg, dns_cache_max_mem));
}
void dns_cache_debug(rpc_t* rpc, void* ctx)
{
int h;
struct dns_hash_entry* e;
ticks_t now;
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
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->ent_flags);
}
}
UNLOCK_DNS_HASH();
}
#ifdef USE_DNS_CACHE_STATS
static unsigned long stat_sum(int ivar, int breset)
{
unsigned long isum=0;
int i1=0;
for (; i1 < get_max_procs(); i1++)
switch (ivar) {
case 0:
isum+=dns_cache_stats[i1].dns_req_cnt;
if (breset)
dns_cache_stats[i1].dns_req_cnt=0;
break;
case 1:
isum+=dns_cache_stats[i1].dc_hits_cnt;
if (breset)
dns_cache_stats[i1].dc_hits_cnt=0;
break;
case 2:
isum+=dns_cache_stats[i1].dc_neg_hits_cnt;
if (breset)
dns_cache_stats[i1].dc_neg_hits_cnt=0;
break;
case 3:
isum+=dns_cache_stats[i1].dc_lru_cnt;
if (breset)
dns_cache_stats[i1].dc_lru_cnt=0;
break;
}
return isum;
}
void dns_cache_stats_get(rpc_t* rpc, void* c)
{
char *name=NULL;
void *handle;
int found=0,i=0;
int reset=0;
char* dns_cache_stats_names[] = {
"dns_req_cnt",
"dc_hits_cnt",
"dc_neg_hits_cnt",
"dc_lru_cnt",
NULL
};
if (!cfg_get(core, core_cfg, use_dns_cache)) {
rpc->fault(c, 500, "dns cache support disabled");
return;
}
if (rpc->scan(c, "s", &name) < 0)
return;
if (rpc->scan(c, "d", &reset) < 0)
return;
if (!strcasecmp(name, DNS_CACHE_ALL_STATS)) {
/* dump all the dns cache stat values */
rpc->add(c, "{", &handle);
for (i=0; dns_cache_stats_names[i]; i++)
rpc->struct_add(handle, "d",
dns_cache_stats_names[i],
stat_sum(i, reset));
found=1;
} else {
for (i=0; dns_cache_stats_names[i]; i++)
if (!strcasecmp(dns_cache_stats_names[i], name)) {
rpc->add(c, "{", &handle);
rpc->struct_add(handle, "d",
dns_cache_stats_names[i],
stat_sum(i, reset));
found=1;
break;
}
}
if(!found)
rpc->fault(c, 500, "unknown dns cache stat parameter");
return;
}
#endif /* USE_DNS_CACHE_STATS */
/* 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;
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
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, (int)e->type, i, (int)e->total_size,
(int)e->refcnt.val,
(int)(s_ticks_t)(e->expire-now)<0?-1:
TICKS_TO_S(e->expire-now),
(int)TICKS_TO_S(now-e->last_used),
(int)e->ent_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;
case T_TXT:
rpc->add(ctx, "ss", "txt",
((struct txt_rdata*)(rr->rdata))->cstr_no?
((struct txt_rdata*)(rr->rdata))->txt[0].cstr:
"");
break;
case T_EBL:
rpc->add(ctx, "ss", "ebl",
((struct ebl_rdata*)(rr->rdata))->apex);
break;
case T_PTR:
rpc->add(ctx, "ss", "ptr",
((struct ptr_rdata*)(rr->rdata))->ptrdname);
break;
default:
rpc->add(ctx, "ss", "unknown", "?");
}
rpc->add(ctx, "d",
(int)(s_ticks_t)(rr->expire-now)<0?-1:
TICKS_TO_S(rr->expire-now));
}
}
}
UNLOCK_DNS_HASH();
}
static char *print_type(unsigned short type)
{
switch (type) {
case T_A:
return "A";
case T_AAAA:
return "AAAA";
case T_SRV:
return "SRV";
case T_NAPTR:
return "NAPTR";
case T_CNAME:
return "CNAME";
case T_TXT:
return "TXT";
case T_EBL:
return "EBL";
case T_PTR:
return "PTR";
default:
return "unknown";
}
}
/** convert string type to dns integer T_*.
* used for rpc type translation.
* @return T_* on success, -1 on error.
*/
static int dns_get_type(str* s)
{
char *t;
int len;
t=s->s;
len=s->len;
/* skip over a T_ or t_ prefix */
if ((len>2) && (t[0]=='T' || t[0]=='t') && (t[1]=='_')){
t+=2;
len-=2;
}
switch(len){
case 1:
if (t[0]=='A' || t[0]=='a')
return T_A;
break;
case 4:
if (strncasecmp(t, "AAAA", len)==0)
return T_AAAA;
break;
case 3:
if (strncasecmp(t, "SRV", len)==0)
return T_SRV;
else if (strncasecmp(t, "TXT", len)==0)
return T_TXT;
else if (strncasecmp(t, "EBL", len)==0)
return T_EBL;
else if (strncasecmp(t, "PTR", len)==0)
return T_PTR;
break;
case 5:
if (strncasecmp(t, "NAPTR", len)==0)
return T_NAPTR;
else if (strncasecmp(t, "CNAME", len)==0)
return T_CNAME;
break;
}
return -1;
}
/** rpc-prints a dns cache entry.
*/
void dns_cache_print_entry(rpc_t* rpc, void* ctx, struct dns_hash_entry* e)
{
int expires;
struct dns_rr* rr;
struct ip_addr ip;
ticks_t now;
str s;
int i;
now=get_ticks_raw();
expires = (s_ticks_t)(e->expire-now)<0?-1: TICKS_TO_S(e->expire-now);
rpc->printf(ctx, "%sname: %s", SPACE_FORMAT, e->name);
rpc->printf(ctx, "%stype: %s", SPACE_FORMAT, print_type(e->type));
rpc->printf(ctx, "%ssize (bytes): %d", SPACE_FORMAT,
e->total_size);
rpc->printf(ctx, "%sreference counter: %d", SPACE_FORMAT,
e->refcnt.val);
if (e->ent_flags & DNS_FLAG_PERMANENT) {
rpc->printf(ctx, "%spermanent: yes", SPACE_FORMAT);
} else {
rpc->printf(ctx, "%spermanent: no", SPACE_FORMAT);
rpc->printf(ctx, "%sexpires in (s): %d", SPACE_FORMAT, expires);
}
rpc->printf(ctx, "%slast used (s): %d", SPACE_FORMAT,
TICKS_TO_S(now-e->last_used));
rpc->printf(ctx, "%snegative entry: %s", SPACE_FORMAT,
(e->ent_flags & DNS_FLAG_BAD_NAME) ? "yes" : "no");
for (rr=e->rr_lst; rr; rr=rr->next) {
switch(e->type) {
case T_A:
case T_AAAA:
if (dns_rr2ip(e->type, rr, &ip)==0){
rpc->printf(ctx, "%srr ip: %s", SPACE_FORMAT,
ip_addr2a(&ip) );
}else{
rpc->printf(ctx, "%srr ip: <error: bad rr>",
SPACE_FORMAT);
}
break;
case T_SRV:
rpc->printf(ctx, "%srr name: %s", SPACE_FORMAT,
((struct srv_rdata*)(rr->rdata))->name);
rpc->printf(ctx, "%srr port: %d", SPACE_FORMAT,
((struct srv_rdata*)(rr->rdata))->port);
rpc->printf(ctx, "%srr priority: %d", SPACE_FORMAT,
((struct srv_rdata*)(rr->rdata))->priority);
rpc->printf(ctx, "%srr weight: %d", SPACE_FORMAT,
((struct srv_rdata*)(rr->rdata))->weight);
break;
case T_NAPTR:
rpc->printf(ctx, "%srr order: %d", SPACE_FORMAT,
((struct naptr_rdata*)(rr->rdata))->order);
rpc->printf(ctx, "%srr preference: %d", SPACE_FORMAT,
((struct naptr_rdata*)(rr->rdata))->pref);
s.s = ((struct naptr_rdata*)(rr->rdata))->flags;
s.len = ((struct naptr_rdata*)(rr->rdata))->flags_len;
rpc->printf(ctx, "%srr flags: %.*s", SPACE_FORMAT,
s.len, s.s);
s.s=((struct naptr_rdata*)(rr->rdata))->services;
s.len=((struct naptr_rdata*)(rr->rdata))->services_len;
rpc->printf(ctx, "%srr service: %.*s", SPACE_FORMAT,
s.len, s.s);
s.s = ((struct naptr_rdata*)(rr->rdata))->regexp;
s.len = ((struct naptr_rdata*)(rr->rdata))->regexp_len;
rpc->printf(ctx, "%srr regexp: %.*s", SPACE_FORMAT,
s.len, s.s);
s.s = ((struct naptr_rdata*)(rr->rdata))->repl;
s.len = ((struct naptr_rdata*)(rr->rdata))->repl_len;
rpc->printf(ctx, "%srr replacement: %.*s",
SPACE_FORMAT, s.len, s.s);
break;
case T_CNAME:
rpc->printf(ctx, "%srr name: %s", SPACE_FORMAT,
((struct cname_rdata*)(rr->rdata))->name);
break;
case T_TXT:
for (i=0; i<((struct txt_rdata*)(rr->rdata))->cstr_no;
i++){
rpc->printf(ctx, "%stxt[%d]: %s", SPACE_FORMAT, i,
((struct txt_rdata*)(rr->rdata))->txt[i].cstr);
}
break;
case T_EBL:
rpc->printf(ctx, "%srr position: %d", SPACE_FORMAT,
((struct ebl_rdata*)(rr->rdata))->position);
rpc->printf(ctx, "%srr separator: %s", SPACE_FORMAT,
((struct ebl_rdata*)(rr->rdata))->separator);
rpc->printf(ctx, "%srr apex: %s", SPACE_FORMAT,
((struct ebl_rdata*)(rr->rdata))->apex);
break;
case T_PTR:
rpc->printf(ctx, "%srr name: %s", SPACE_FORMAT,
((struct ptr_rdata*)(rr->rdata))->ptrdname);
break;
default:
rpc->printf(ctx, "%sresource record: unknown",
SPACE_FORMAT);
}
if ((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
rpc->printf(ctx, "%srr expires in (s): %d", SPACE_FORMAT,
(s_ticks_t)(rr->expire-now)<0?-1 :
TICKS_TO_S(rr->expire-now));
}
}
/* dumps the content of the cache in a human-readable format */
void dns_cache_view(rpc_t* rpc, void* ctx)
{
int h;
struct dns_hash_entry* e;
ticks_t now;
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
now=get_ticks_raw();
LOCK_DNS_HASH();
for (h=0; h<DNS_HASH_SIZE; h++){
clist_foreach(&dns_hash[h], e, next){
if (((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& TICKS_LT(e->expire, now)
) {
continue;
}
rpc->printf(ctx, "{\n");
dns_cache_print_entry(rpc, ctx, e);
rpc->printf(ctx, "}");
}
}
UNLOCK_DNS_HASH();
}
/* Delete all the entries from the cache.
* If del_permanent is 0, then only the
* non-permanent entries are deleted.
*/
void dns_cache_flush(int del_permanent)
{
int h;
struct dns_hash_entry* e;
struct dns_hash_entry* tmp;
DBG("dns_cache_flush(): removing elements from the cache\n");
LOCK_DNS_HASH();
for (h=0; h<DNS_HASH_SIZE; h++){
clist_foreach_safe(&dns_hash[h], e, tmp, next){
if (del_permanent || ((e->ent_flags & DNS_FLAG_PERMANENT) == 0))
_dns_hash_remove(e);
}
}
UNLOCK_DNS_HASH();
}
/* deletes all the non-permanent entries from the cache */
void dns_cache_delete_all(rpc_t* rpc, void* ctx)
{
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
dns_cache_flush(0);
rpc->printf(ctx, "OK");
}
/* deletes all the entries from the cache,
* even the permanent ones */
void dns_cache_delete_all_force(rpc_t* rpc, void* ctx)
{
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
dns_cache_flush(1);
rpc->printf(ctx, "OK");
}
/* clones an entry and extends its memory area to hold a new rr.
* if rdata_size>0 the new dns_rr struct is initialized, but the rdata is
* only filled with 0.
*/
static struct dns_hash_entry *dns_cache_clone_entry(struct dns_hash_entry *e,
int rdata_size,
int ttl,
struct dns_rr **_new_rr)
{
struct dns_hash_entry *new;
struct dns_rr *rr, *last_rr, *new_rr;
int size, rounded_size, rr_size;
ticks_t now;
int i;
now=get_ticks_raw();
size = e->total_size;
if (rdata_size) {
/* we have to extend the entry */
rounded_size = ROUND_POINTER(size); /* size may not have been
rounded previously */
switch (e->type) {
case T_A:
case T_AAAA:
case T_CNAME:
rr_size = sizeof(struct dns_rr);
break;
case T_SRV:
rr_size = ROUND_SHORT(sizeof(struct dns_rr));
break;
case T_NAPTR:
rr_size = ROUND_POINTER(sizeof(struct dns_rr));
break;
case T_TXT:
rr_size = ROUND_POINTER(sizeof(struct dns_rr));
break;
case T_EBL:
rr_size = ROUND_POINTER(sizeof(struct dns_rr));
break;
case T_PTR:
rr_size = sizeof(struct dns_rr);
break;
default:
LOG(L_ERR, "ERROR: dns_cache_clone_entry: type %d not "
"supported\n", e->type);
return NULL;
}
} else {
rounded_size = size; /* no need to round the size, we just clone
the entry without extending it */
rr_size = 0;
}
new=shm_malloc(rounded_size+rr_size+rdata_size);
if (!new) {
LOG(L_ERR, "ERROR: dns_cache_clone_entry: out of memory\n");
return NULL;
}
memset(new, 0, rounded_size+rr_size+rdata_size);
/* clone the entry */
memcpy(new, e, size);
/* fix the values and pointers */
new->next = new->prev = NULL;
#ifdef DNS_LU_LST
new->last_used_lst.next = new->last_used_lst.prev = NULL;
#endif
new->rr_lst = (struct dns_rr*)translate_pointer((char*)new, (char*)e,
(char*)new->rr_lst);
atomic_set(&new->refcnt, 0);
new->last_used = now;
/* expire and total_size are fixed later if needed */
/* fix the pointers inside the rr structures */
last_rr = NULL;
for (rr=new->rr_lst; rr; rr=rr->next) {
rr->rdata = (void*)translate_pointer((char*)new, (char*)e,
(char*)rr->rdata);
if (rr->next)
rr->next = (struct dns_rr*)translate_pointer((char*)new, (char*)e,
(char*)rr->next);
else
last_rr = rr;
switch(e->type){
case T_NAPTR:
/* there are pointers inside the NAPTR rdata stucture */
((struct naptr_rdata*)rr->rdata)->flags =
translate_pointer((char*)new, (char*)e,
((struct naptr_rdata*)rr->rdata)->flags);
((struct naptr_rdata*)rr->rdata)->services =
translate_pointer((char*)new, (char*)e,
((struct naptr_rdata*)rr->rdata)->services);
((struct naptr_rdata*)rr->rdata)->regexp =
translate_pointer((char*)new, (char*)e,
((struct naptr_rdata*)rr->rdata)->regexp);
((struct naptr_rdata*)rr->rdata)->repl =
translate_pointer((char*)new, (char*)e,
((struct naptr_rdata*)rr->rdata)->repl);
break;
case T_TXT:
/* there are pointers inside the TXT structure */
for (i=0; i<((struct txt_rdata*)rr->rdata)->cstr_no; i++){
((struct txt_rdata*)rr->rdata)->txt[i].cstr=
translate_pointer((char*) new, (char*) e,
((struct txt_rdata*)rr->rdata)->txt[i].cstr);
}
break;
case T_EBL:
/* there are pointers inside the EBL structure */
((struct ebl_rdata*)rr->rdata)->separator =
translate_pointer((char*)new, (char*)e,
((struct ebl_rdata*)rr->rdata)->separator);
((struct ebl_rdata*)rr->rdata)->apex =
translate_pointer((char*)new, (char*)e,
((struct ebl_rdata*)rr->rdata)->apex);
break;
}
}
if (rdata_size) {
/* set the pointer to the new rr structure */
new_rr = (void*)((char*)new + rounded_size);
new_rr->rdata = (void*)((char*)new_rr+rr_size);
new_rr->expire = now + S_TO_TICKS(ttl);
/* link the rr to the previous one */
last_rr->next = new_rr;
/* fix the total_size and expires values */
new->total_size=rounded_size+rr_size+rdata_size;
new->expire = MAX(new->expire, new_rr->expire);
if (_new_rr)
*_new_rr = new_rr;
} else {
if (_new_rr)
*_new_rr = NULL;
}
return new;
}
/* Adds a new record to the cache.
* If there is an existing record with the same name and value
* (ip address in case of A/AAAA record, name in case of SRV record)
* only the remaining fields are updated.
*
* Note that permanent records cannot be overwritten unless
* the new record is also permanent. A permanent record
* completely replaces a non-permanent one.
*
* Currently only A, AAAA, and SRV records are supported.
*/
int dns_cache_add_record(unsigned short type,
str *name,
int ttl,
str *value,
int priority,
int weight,
int port,
int flags)
{
struct dns_hash_entry *old=NULL, *new=NULL;
struct dns_rr *rr;
str rr_name;
struct ip_addr *ip_addr;
ticks_t expire;
int err, h;
int size;
struct dns_rr *new_rr, **rr_p, **rr_iter;
struct srv_rdata *srv_rd;
/* eliminate gcc warnings */
ip_addr = 0;
size = 0;
rr_name.s = NULL;
rr_name.len = 0;
if (!cfg_get(core, core_cfg, use_dns_cache)){
LOG(L_ERR, "ERROR: dns cache support disabled (see use_dns_cache)\n");
return -1;
}
if ((type != T_A) && (type != T_AAAA) && (type != T_SRV)) {
LOG(L_ERR, "ERROR: rr type %d is not implemented\n",
type);
return -1;
}
if ((flags & DNS_FLAG_BAD_NAME) == 0) {
/* fix-up the values */
switch(type) {
case T_A:
ip_addr = str2ip(value);
if (!ip_addr) {
LOG(L_ERR, "ERROR: Malformed ip address: %.*s\n",
value->len, value->s);
return -1;
}
break;
case T_AAAA:
ip_addr = str2ip6(value);
if (!ip_addr) {
LOG(L_ERR, "ERROR: Malformed ip address: %.*s\n",
value->len, value->s);
return -1;
}
break;
case T_SRV:
rr_name = *value;
break;
}
}
/* check whether there is a matching entry in the cache */
old = dns_hash_get(name, type, &h, &err);
if (old && old->type!=type) {
/* probably we found a CNAME instead of the specified type,
it is not needed */
dns_hash_put(old);
old=NULL;
}
if (old
&& (old->ent_flags & DNS_FLAG_PERMANENT)
&& ((flags & DNS_FLAG_PERMANENT) == 0)
) {
LOG(L_ERR, "ERROR: A non-permanent record cannot overwrite "
"a permanent entry\n");
goto error;
}
/* prepare the entry */
if (flags & DNS_FLAG_BAD_NAME) {
/* negative entry */
new = dns_cache_mk_bad_entry(name, type, ttl, flags);
if (!new) {
LOG(L_ERR, "ERROR: Failed to create a negative "
"DNS cache entry\n");
goto error;
}
} else {
if (!old
|| (old->ent_flags & DNS_FLAG_BAD_NAME)
|| (((old->ent_flags & DNS_FLAG_PERMANENT) == 0)
&& (flags & DNS_FLAG_PERMANENT))
) {
/* There was no matching entry in the hash table,
* the entry is a negative record with inefficient space,
* or a permanent entry overwrites a non-permanent one.
* Let us create a new one.
*/
switch(type) {
case T_A:
case T_AAAA:
new = dns_cache_mk_ip_entry(name, ip_addr);
if (!new) {
LOG(L_ERR, "ERROR: Failed to create an A/AAAA record\n");
goto error;
}
/* fix the expiration time, dns_cache_mk_ip_entry() sets it
* to now-1 */
expire = get_ticks_raw() + S_TO_TICKS(ttl);
new->expire = expire;
new->rr_lst->expire = expire;
break;
case T_SRV:
new = dns_cache_mk_srv_entry(name, priority, weight, port,
&rr_name, ttl);
if (!new) {
LOG(L_ERR, "ERROR: Failed to create an SRV record\n");
goto error;
}
}
new->ent_flags = flags;
} else {
/* we must modify the entry, so better to clone it, modify the new
* one, and replace the old with the new entry in the hash table,
* because the entry might be in use (even if the dns hash is
* locked). The old entry will be removed from the hash and
* automatically destroyed when its refcnt will be 0*/
/* check whether there is an rr with the same value */
for (rr=old->rr_lst; rr; rr=rr->next)
if ((((type == T_A) || (type == T_AAAA)) &&
(memcmp(ip_addr->u.addr, ((struct a_rdata*)rr->rdata)->ip,
ip_addr->len)==0))
|| ((type == T_SRV) &&
(((struct srv_rdata*)rr->rdata)->name_len == rr_name.len)&&
(memcmp(rr_name.s, ((struct srv_rdata*)rr->rdata)->name,
rr_name.len)==0)))
break;
if (rr) {
/* the rr was found in the list */
new = dns_cache_clone_entry(old, 0, 0, 0);
if (!new) {
LOG(L_ERR, "ERROR: Failed to clone an existing "
"DNS cache entry\n");
goto error;
}
/* let the rr point to the new structure */
rr = (struct dns_rr*)translate_pointer((char*)new, (char*)old,
(char*)rr);
new_rr = rr;
if (type == T_SRV) {
/* fix the priority, weight, and port */
((struct srv_rdata*)rr->rdata)->priority = priority;
((struct srv_rdata*)rr->rdata)->weight = weight;
((struct srv_rdata*)rr->rdata)->port = port;
}
/* fix the expire value */
rr->expire = get_ticks_raw() + S_TO_TICKS(ttl);
new->expire = 0;
for (rr=new->rr_lst; rr; rr=rr->next)
new->expire = MAX(new->expire, rr->expire);
} else {
/* there was no matching rr, extend the structure with a new
* one */
switch(type) {
case T_A:
size = sizeof(struct a_rdata);
break;
case T_AAAA:
size = sizeof(struct aaaa_rdata);
break;
case T_SRV:
size = sizeof(struct srv_rdata)-1 +
rr_name.len+1;
break;
}
new = dns_cache_clone_entry(old, size, ttl, &rr);
if (!new) {
LOG(L_ERR, "ERROR: Failed to clone an existing "
"DNS cache entry\n");
goto error;
}
new_rr = rr;
switch(type) {
case T_A:
case T_AAAA:
memcpy(rr->rdata, ip_addr->u.addr, ip_addr->len);
break;
case T_SRV:
((struct srv_rdata*)rr->rdata)->priority = priority;
((struct srv_rdata*)rr->rdata)->weight = weight;
((struct srv_rdata*)rr->rdata)->port = port;
((struct srv_rdata*)rr->rdata)->name_len = rr_name.len;
memcpy(((struct srv_rdata*)rr->rdata)->name, rr_name.s,
rr_name.len);
}
/* maximum expire value has been already fixed by
* dns_cache_clone_entry() */
}
if (type == T_SRV) {
/* SRV records must be ordered by their priority and weight.
* With modifying an exising rr, or adding new rr to the DNS entry,
* the ordered list might got broken which needs to be fixed.
*/
rr_p = NULL;
for ( rr_iter = &new->rr_lst;
*rr_iter;
rr_iter = &((*rr_iter)->next)
) {
if (*rr_iter == new_rr) {
rr_p = rr_iter;
continue;
}
srv_rd = (struct srv_rdata*)(*rr_iter)->rdata;
if ((priority < srv_rd->priority) ||
((priority == srv_rd->priority) && (weight > srv_rd->weight))
)
break; /* insert here */
}
if (!rr_p)
for ( rr_p = rr_iter;
*rr_p && (*rr_p != new_rr);
rr_p = &((*rr_p)->next)
);
if (!rr_p) {
LOG(L_ERR, "ERROR: Failed to correct the orderd list of SRV resource records\n");
goto error;
}
if (*rr_iter != new_rr->next) {
/* unlink rr from the list */
*rr_p = (*rr_p)->next;
/* link it before *rr_iter */
new_rr->next = *rr_iter;
*rr_iter = new_rr;
}
}
}
}
LOCK_DNS_HASH();
if (dns_cache_add_unsafe(new)) {
LOG(L_ERR, "ERROR: Failed to add the entry to the cache\n");
UNLOCK_DNS_HASH();
goto error;
} else {
/* remove the old entry from the list */
if (old)
_dns_hash_remove(old);
}
UNLOCK_DNS_HASH();
if (old)
dns_hash_put(old);
return 0;
error:
/* leave the old entry in the list, and free the new one */
if (old)
dns_hash_put(old);
if (new)
dns_destroy_entry(new);
return -1;
}
/* deletes a record from the cache */
static void dns_cache_delete_record(rpc_t* rpc, void* ctx, unsigned short type)
{
struct dns_hash_entry *e;
str name;
int err, h, found=0, permanent=0;
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
if (rpc->scan(ctx, "S", &name) < 1)
return;
LOCK_DNS_HASH();
e=_dns_hash_find(&name, type, &h, &err);
if (e && (e->type==type)) {
if ((e->ent_flags & DNS_FLAG_PERMANENT) == 0)
_dns_hash_remove(e);
else
permanent = 1;
found = 1;
}
UNLOCK_DNS_HASH();
if (permanent)
rpc->fault(ctx, 400, "Permanent entries cannot be deleted");
else if (!found)
rpc->fault(ctx, 400, "Not found");
}
/* Delete a single record from the cache,
* i.e. the record with the same name and value
* (ip address in case of A/AAAA record, name in case of SRV record).
*
* Currently only A, AAAA, and SRV records are supported.
*/
int dns_cache_delete_single_record(unsigned short type,
str *name,
str *value,
int flags)
{
struct dns_hash_entry *old=NULL, *new=NULL;
struct dns_rr *rr, **next_p;
str rr_name;
struct ip_addr *ip_addr;
int err, h;
/* eliminate gcc warnings */
rr_name.s = NULL;
rr_name.len = 0;
ip_addr = 0;
if (!cfg_get(core, core_cfg, use_dns_cache)){
LOG(L_ERR, "ERROR: dns cache support disabled (see use_dns_cache)\n");
return -1;
}
if ((type != T_A) && (type != T_AAAA) && (type != T_SRV)) {
LOG(L_ERR, "ERROR: rr type %d is not implemented\n",
type);
return -1;
}
if ((flags & DNS_FLAG_BAD_NAME) == 0) {
/* fix-up the values */
switch(type) {
case T_A:
ip_addr = str2ip(value);
if (!ip_addr) {
LOG(L_ERR, "ERROR: Malformed ip address: %.*s\n",
value->len, value->s);
return -1;
}
break;
case T_AAAA:
ip_addr = str2ip6(value);
if (!ip_addr) {
LOG(L_ERR, "ERROR: Malformed ip address: %.*s\n",
value->len, value->s);
return -1;
}
break;
case T_SRV:
rr_name = *value;
break;
}
}
/* check whether there is a matching entry in the cache */
if ((old = dns_hash_get(name, type, &h, &err)) == NULL)
goto not_found;
if ((old->type != type) /* may be CNAME */
|| (old->ent_flags != flags)
)
goto not_found;
if (flags & DNS_FLAG_BAD_NAME) /* negative record, there is no value */
goto delete;
/* check whether there is an rr with the same value */
for (rr=old->rr_lst, next_p=&old->rr_lst;
rr;
next_p=&rr->next, rr=rr->next
)
if ((((type == T_A) || (type == T_AAAA)) &&
(memcmp(ip_addr->u.addr, ((struct a_rdata*)rr->rdata)->ip,
ip_addr->len)==0))
|| ((type == T_SRV) &&
(((struct srv_rdata*)rr->rdata)->name_len == rr_name.len) &&
(memcmp(rr_name.s, ((struct srv_rdata*)rr->rdata)->name,
rr_name.len)==0)))
break;
if (!rr)
goto not_found;
if ((rr == old->rr_lst) && (rr->next == NULL)) {
/* There is a single rr value, hence the whole
* hash entry can be deleted */
goto delete;
} else {
/* we must modify the entry, so better to clone it, modify the new
* one, and replace the old with the new entry in the hash table,
* because the entry might be in use (even if the dns hash is
* locked). The old entry will be removed from the hash and
* automatically destroyed when its refcnt will be 0*/
new = dns_cache_clone_entry(old, 0, 0, 0);
if (!new) {
LOG(L_ERR, "ERROR: Failed to clone an existing "
"DNS cache entry\n");
dns_hash_put(old);
return -1;
}
/* let rr and next_p point to the new structure */
rr = (struct dns_rr*)translate_pointer((char*)new,
(char*)old,
(char*)rr);
next_p = (struct dns_rr**)translate_pointer((char*)new,
(char*)old,
(char*)next_p);
/* unlink rr from the list. The memory will be freed
* when the whole record is freed */
*next_p = rr->next;
}
delete:
LOCK_DNS_HASH();
if (new) {
/* delete the old entry only if the new one can be added */
if (dns_cache_add_unsafe(new)) {
LOG(L_ERR, "ERROR: Failed to add the entry to the cache\n");
UNLOCK_DNS_HASH();
if (old)
dns_hash_put(old);
return -1;
} else {
/* remove the old entry from the list */
if (old)
_dns_hash_remove(old);
}
} else if (old) {
_dns_hash_remove(old);
}
UNLOCK_DNS_HASH();
if (old)
dns_hash_put(old);
return 0;
not_found:
LOG(L_ERR, "ERROR: No matching record found\n");
if (old)
dns_hash_put(old);
return -1;
}
/* performs a dns lookup over rpc */
void dns_cache_rpc_lookup(rpc_t* rpc, void* ctx)
{
struct dns_hash_entry *e;
str name;
str type;
int t;
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
if (rpc->scan(ctx, "SS", &type, &name) < 1)
return;
t=dns_get_type(&type);
if (t<0){
rpc->fault(ctx, 400, "Invalid type");
return;
}
e=dns_get_entry(&name, t);
if (e==0){
rpc->fault(ctx, 400, "Not found");
return;
}
dns_cache_print_entry(rpc, ctx, e);
dns_hash_put(e);
}
/* wrapper functions for adding and deleting records */
void dns_cache_add_a(rpc_t* rpc, void* ctx)
{
str name;
int ttl;
str ip;
int flags;
if (rpc->scan(ctx, "SdSd", &name, &ttl, &ip, &flags) < 4)
return;
if (dns_cache_add_record(T_A,
&name,
ttl,
&ip,
0 /* priority */,
0 /* weight */,
0 /* port */,
flags)
)
rpc->fault(ctx, 400, "Failed to add the entry to the cache");
}
void dns_cache_add_aaaa(rpc_t* rpc, void* ctx)
{
str name;
int ttl;
str ip;
int flags;
if (rpc->scan(ctx, "SdSd", &name, &ttl, &ip, &flags) < 4)
return;
if (dns_cache_add_record(T_AAAA,
&name,
ttl,
&ip,
0 /* priority */,
0 /* weight */,
0 /* port */,
flags)
)
rpc->fault(ctx, 400, "Failed to add the entry to the cache");
}
void dns_cache_add_srv(rpc_t* rpc, void* ctx)
{
str name;
int ttl, priority, weight, port;
str rr_name;
int flags;
if (rpc->scan(ctx, "SddddSd", &name, &ttl, &priority, &weight, &port,
&rr_name, &flags) < 7
)
return;
if (dns_cache_add_record(T_SRV,
&name,
ttl,
&rr_name,
priority,
weight,
port,
flags)
)
rpc->fault(ctx, 400, "Failed to add the entry to the cache");
}
void dns_cache_delete_a(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_A);
}
void dns_cache_delete_aaaa(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_AAAA);
}
void dns_cache_delete_srv(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_SRV);
}
void dns_cache_delete_naptr(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_NAPTR);
}
void dns_cache_delete_cname(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_CNAME);
}
void dns_cache_delete_txt(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_TXT);
}
void dns_cache_delete_ebl(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_EBL);
}
void dns_cache_delete_ptr(rpc_t* rpc, void* ctx)
{
dns_cache_delete_record(rpc, ctx, T_PTR);
}
#ifdef DNS_WATCHDOG_SUPPORT
/* sets the DNS server states */
void dns_set_server_state_rpc(rpc_t* rpc, void* ctx)
{
int state;
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
if (rpc->scan(ctx, "d", &state) < 1)
return;
dns_set_server_state(state);
}
/* prints the DNS server state */
void dns_get_server_state_rpc(rpc_t* rpc, void* ctx)
{
if (!cfg_get(core, core_cfg, use_dns_cache)){
rpc->fault(ctx, 500, "dns cache support disabled (see use_dns_cache)");
return;
}
rpc->add(ctx, "d", dns_get_server_state());
}
#endif /* DNS_WATCHDOG_SUPPORT */
#endif