tor-android/tor-android-binary/src/main/jni/pdnsd/src/cache.c

2732 lines
72 KiB
C

/* cache.c - Keep the dns caches.
Copyright (C) 2000, 2001 Thomas Moestl
Copyright (C) 2003, 2004, 2005, 2007, 2010, 2011 Paul A. Rombouts
This file is part of the pdnsd package.
pdnsd 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 3 of the License, or
(at your option) any later version.
pdnsd 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 pdnsd; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/time.h>
#include "cache.h"
#include "hash.h"
#include "conff.h"
#include "helpers.h"
#include "dns.h"
#include "error.h"
#include "debug.h"
#include "thread.h"
#include "ipvers.h"
/* A version identifier to prevent reading incompatible cache files */
static const char cachverid[] = {'p','d','1','3'};
/* CACHE STRUCTURE CHANGES IN PDNSD 1.0.0
* Prior to version 1.0.0, the cache was managed at domain granularity (all records of a domain were handled as a unit),
* which was suboptimal after the lean query feature and the additional record management were included.
* From 1.0.0 on, the cache management was switched to act with RR set granularity. The API of the cache handlers was
* slightly modified, in particular the rr_bucket_t was modified and some parameter list were changed. The cache
* file format had to be changed and is incompatible now. This means that post-1.0.0p1 versions will not read the cache
* files of older versions and vice versa. In addition, cache files from 1.0.0p5 on are incompatible to those of 1.0.0p1
* to 1.0.0p4. Better delete them before upgrading.
* The "cent" lists common to old versions have vanished; the only access point to the cent's is the hash.
* However, there are now double linked rrset lists. Thus, rrs can be acces through the hash or through the rrset lists.
* The rrset list entries need some additional entries to manage the deletion from rrs lists as well as from the cents.
*
* Nearly all cache functions had to be changed significantly or even to be rewritten for that. Expect some beta time
* because of that.
* There are bonuses visible to the users resulting from this changes however: more consistent cache handling (under
* some circumstances, rrs could be in the cache more than once) and reduced memory requirements, as no rr needs
* to have stored its oname any more. There are more pointers however, and in some cases (CNAMES) the memory require-
* ments for some records may increase. The total should be lower, however.
*
* RRSET_L LIST STRUCTURE:
* The rrset_l rrset list is a simple double-linked list. The oldest entries are at the first positions, the list is sorted
* by age in descending order. Search is done only on insert.
* The rationale for this form is:
* - the purging operation needs to be fast (this way, the first records are the oldest and can easily be purged)
* - the append operation is common and needs to be fast (in normal operation, an appended record was just retrieved
* and therefore is the newest, so it can be appended at the end of the list without search. Only in the case of
* reading a disk cache file, searches are necessary)
* The rrset list is excusively used for purging purposes.
*
* THE DISK CACHE FILES:
* The disk cache file consists of cent's, i.e. structures for every known hostnames with a header and rrs attached to it.
* Therefore, the rr's are not ordered by their age and a search must be performed to insert the into the rr_l in the
* right positions. This operations has some costs (although not all too much), but the other way (rrs stored in order
* of their age and the cent headers separated from them), the rrs would need to be attached to the cent headers, which
* would be even more costly, also in means of disk space.
*
* CHANGES AFTER 1.0.0p1
* In 1.0.0p5, the cache granularity was changed from rr level to rr set level. This was done because rfc2181 demands
* rr set consistency constraints on rr set level and if we are doing so we can as well save space (and eliminate some
* error-prone algorithms).
*
* CHANGES FOR 1.1.0p1
* In this version, negative caching support was introduced. Following things were changed for that:
* - new members ts, ttl and flags in dns_cent_t and dns_file_t
* - new caching flag CF_NEGATIVE
* - all functions must accept and deal correctly with empty cents with DF_NEGATIVE set.
* - all functions must accept and deal correctly with empty rrsets with CF_NEGATIVE set.
*/
/*
* This is the size the memory cache may exceed the size of the permanent cache.
*/
#define MCSZ 10240
/* Some structs used for storing cache entries in a file. */
typedef struct {
unsigned short rdlen;
/* data (with length rdlen) follows here;*/
} rr_fbucket_t;
typedef struct {
unsigned char tp; /* RR type */
unsigned char num_rr; /* Number of records in RR set. */
unsigned short flags; /* Flags for RR set. */
time_t ttl;
time_t ts;
} __attribute__((packed))
rr_fset_t;
#if NRRTOT>255
#warning "Number of cache-able RR types is greater than 255. This can give problems when saving the cache to file."
#endif
typedef struct {
unsigned char qlen; /* Length of the domain name which follows after the struct. */
unsigned char num_rrs; /* Number of RR-sets. */
unsigned short flags; /* Flags for the whole cent. */
unsigned char c_ns,c_soa; /* Number of trailing name elements in qname to use to find NS or SOA
records to add to the authority section of a response. */
/* ttl and ts follow but only for negatively cached domains. */
/* qname (with length qlen) follows here. */
} __attribute__((packed))
dns_file_t;
/* TTL and timestamp for negatively cached domains. */
typedef struct {
time_t ttl;
time_t ts;
} __attribute__((packed))
dom_fttlts_t;
/*
* This has two modes: Normally, we have rrset, cent and idx filled in;
* for negatively cached cents, we have rrset set to NULL and idx set to -1.
*/
typedef struct rr_lent_s {
struct rr_lent_s *next;
struct rr_lent_s *prev;
rr_set_t *rrset;
dns_cent_t *cent;
int idx; /* This is the array index, not the type of the RR-set. */
} rr_lent_t;
static rr_lent_t *rrset_l=NULL;
static rr_lent_t *rrset_l_tail=NULL;
/*
* We do not count the hash table sizes here. Those are very small compared
* to the cache entries.
*/
static volatile long cache_size=0;
static volatile long ent_num=0;
static volatile int cache_w_lock=0;
static volatile int cache_r_lock=0;
pthread_mutex_t lock_mutex = PTHREAD_MUTEX_INITIALIZER;
/*
* These are condition variables for lock coordination, so that normal lock
* routines do not need to loop. Basically, a process wanting to acquire a lock
* tries first to lock, and if the lock is busy, sleeps on one of the conds.
* If the r lock count has gone to zero one process sleeping on the rw cond
* will be awankened.
* If the rw lock is lifted, either all threads waiting on the r lock or one
* thread waiting on the rw lock is/are awakened. This is determined by policy.
*/
pthread_cond_t rw_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t r_cond = PTHREAD_COND_INITIALIZER;
/* This is to suspend the r lock to avoid lock contention by reading threads */
static volatile int r_pend=0;
static volatile int rw_pend=0;
static volatile int r_susp=0;
/* This threshold is used to temporarily suspend r locking to give rw locking
* a chance. */
#define SUSP_THRESH(r_pend) (r_pend/2+2)
/*
* This is set to 1 once the lock is intialized. This must happen before we get
* multiple threads.
*/
volatile short int use_cache_lock=0;
/*
This is set to 0 while cache is read from disk.
This must be set to 1 before we start adding new entries.
*/
static short int insert_sort=1;
#ifdef ALLOC_DEBUG
#define cache_free(ptr) { if (dbg) pdnsd_free(ptr); else free(ptr); }
#define cache_malloc(sz) ((dbg)?(pdnsd_malloc(sz)):(malloc(sz)))
#define cache_calloc(n,sz) ((dbg)?(pdnsd_calloc(n,sz)):(calloc(n,sz)))
#define cache_realloc(ptr,sz) ((dbg)?(pdnsd_realloc(ptr,sz)):(realloc(ptr,sz)))
#else
#define cache_free(ptr) {free(ptr);}
#define cache_malloc(sz) (malloc(sz))
#define cache_calloc(n,sz) (calloc(n,sz))
#define cache_realloc(ptr,sz) (realloc(ptr,sz))
#endif
/*
* Prototypes for internal use
*/
static void purge_cache(long sz, int lazy);
static void del_cache_ent(dns_cent_t *cent,dns_hash_loc_t *loc);
static void remove_rrl(rr_lent_t *le DBGPARAM);
/*
* Locking functions.
*/
/*
* Lock/unlock cache for reading. Concurrent reads are allowed, while writes are forbidden.
* DO NOT MIX THE LOCK TYPES UP WHEN LOCKING/UNLOCKING!
*
* We use a mutex to lock the access to the locks ;-).
* This is because we do not allow read and write to interfere (for which a normal mutex would be
* fine), but we also want to allow concurrent reads.
* We use condition variables, and readlock contention protection.
*/
static void lock_cache_r(void)
{
if (!use_cache_lock)
return;
pthread_mutex_lock(&lock_mutex);
r_pend++;
while(((rw_pend>SUSP_THRESH(r_pend))?(r_susp=1):r_susp) || cache_w_lock) {
/* This will unlock the mutex while sleeping and relock it before exit */
pthread_cond_wait(&r_cond, &lock_mutex);
}
cache_r_lock++;
r_pend--;
pthread_mutex_unlock(&lock_mutex);
}
static void unlock_cache_r(void)
{
if (!use_cache_lock)
return;
pthread_mutex_lock(&lock_mutex);
if (cache_r_lock>0)
cache_r_lock--;
/* wakeup threads waiting to write */
if (!cache_r_lock)
pthread_cond_signal(&rw_cond);
pthread_mutex_unlock(&lock_mutex);
}
/*
* Lock/unlock cache for reading and writing. Concurrent reads and writes are forbidden.
* Do this only if you actually modify the cache.
* DO NOT MIX THE LOCK TYPES UP WHEN LOCKING/UNLOCKING!
* (cant say it often enough)
*/
static void lock_cache_rw(void)
{
if (!use_cache_lock)
return;
pthread_mutex_lock(&lock_mutex);
rw_pend++;
while(cache_w_lock || cache_r_lock) {
/* This will unlock the mutex while sleeping and relock it before exit */
pthread_cond_wait(&rw_cond, &lock_mutex);
}
cache_w_lock=1;
rw_pend--;
pthread_mutex_unlock(&lock_mutex);
}
/* Lock cache for reading and writing, or time out after tm seconds. */
static int timedlock_cache_rw(int tm)
{
int retval=0;
struct timeval now;
struct timespec timeout;
if (!use_cache_lock)
return 0;
pthread_mutex_lock(&lock_mutex);
gettimeofday(&now,NULL);
timeout.tv_sec = now.tv_sec + tm;
timeout.tv_nsec = now.tv_usec * 1000;
rw_pend++;
while(cache_w_lock || cache_r_lock) {
/* This will unlock the mutex while sleeping and relock it before exit */
if(pthread_cond_timedwait(&rw_cond, &lock_mutex, &timeout) == ETIMEDOUT)
goto cleanup_return;
}
cache_w_lock=1;
retval=1;
cleanup_return:
rw_pend--;
pthread_mutex_unlock(&lock_mutex);
return retval;
}
static void unlock_cache_rw(void)
{
if (!use_cache_lock)
return;
pthread_mutex_lock(&lock_mutex);
cache_w_lock=0;
/* always reset r suspension (r locking code will set it again) */
r_susp=0;
/* wakeup threads waiting to read or write */
if (r_pend==0 || rw_pend>SUSP_THRESH(r_pend))
pthread_cond_signal(&rw_cond); /* schedule another rw proc */
else
pthread_cond_broadcast(&r_cond); /* let 'em all read */
pthread_mutex_unlock(&lock_mutex);
}
/*
If there are other threads waiting to read from or write to
the cache, give up the read/write lock on the cache to give another
thread a chance; then try to get the lock back again.
This can be called regularly during a process that takes
a lot of processor time but has low priority, in order to improve
overall responsiveness.
*/
static void yield_lock_cache_rw()
{
if (!use_cache_lock || (!r_pend && !rw_pend))
return;
/* Give up the lock */
pthread_mutex_lock(&lock_mutex);
cache_w_lock=0;
/* always reset r suspension (r locking code will set it again) */
r_susp=0;
/* wakeup threads waiting to read or write */
if (r_pend==0 || rw_pend>SUSP_THRESH(r_pend))
pthread_cond_signal(&rw_cond); /* schedule another rw proc */
else
pthread_cond_broadcast(&r_cond); /* let 'em all read */
pthread_mutex_unlock(&lock_mutex);
usleep_r(1000);
/* Now try to get the lock back again */
pthread_mutex_lock(&lock_mutex);
rw_pend++;
while(cache_w_lock || cache_r_lock) {
/* This will unlock the mutex while sleeping and relock it before exit */
pthread_cond_wait(&rw_cond, &lock_mutex);
}
cache_w_lock=1;
rw_pend--;
pthread_mutex_unlock(&lock_mutex);
}
/* These are a special version of the ordinary read lock functions. The lock "soft" to avoid deadlocks: they will give up
* after a certain number of bad trials. You have to check the exit status though.
* To avoid blocking mutexes, we cannot use condition variables here. Never mind, these are only used on
* exit. */
static int softlock_cache_r(void)
{
if (!use_cache_lock)
return 0;
{
int lk=0,tr=0;
for(;;) {
if (!softlock_mutex(&lock_mutex))
return 0;
if(!cache_w_lock) {
lk=1;
cache_r_lock++;
}
pthread_mutex_unlock(&lock_mutex);
if (lk) break;
if (++tr>=SOFTLOCK_MAXTRIES)
return 0;
usleep_r(1000); /*give contol back to the scheduler instead of hammering the lock close*/
}
}
return 1;
}
/* On unlocking, we do not wake others. We are about to exit! */
static int softunlock_cache_r(void)
{
if (!use_cache_lock)
return 0;
if (!softlock_mutex(&lock_mutex))
return 0;
if (cache_r_lock>0)
cache_r_lock--;
pthread_mutex_unlock(&lock_mutex);
return 1;
}
static int softlock_cache_rw(void)
{
if (!use_cache_lock)
return 0;
{
int lk=0,tr=0;
for(;;) {
if (!softlock_mutex(&lock_mutex))
return 0;
if (!(cache_w_lock || cache_r_lock)) {
lk=1;
cache_w_lock=1;
}
pthread_mutex_unlock(&lock_mutex);
if(lk) break;
if (++tr>=SOFTLOCK_MAXTRIES)
return 0;
usleep_r(1000); /*give contol back to the scheduler instead of hammering the lock close*/
}
}
return 1;
}
static int softunlock_cache_rw(void)
{
if (!use_cache_lock)
return 0;
if (!softlock_mutex(&lock_mutex))
return 0;
cache_w_lock=0;
pthread_mutex_unlock(&lock_mutex);
return 1;
}
/*
* Serial numbers: Serial numbers are used when additional records are added to the cache: serial numbers are unique to each
* query, so we can determine whether data was added by the query just executed (records can coexist) or not (records must
* be replaced). A serial of 0 is special and will not be used by any query. All records added added authoritatively (as
* chunk) or read from a file can have no query in process and therefore have serial 0, which is != any other serial.
*/
#if 0
unsigned long l_serial=1;
unsigned long get_serial()
{
unsigned long rv;
lock_cache_rw();
rv=l_serial++;
unlock_cache_rw();
return rv;
}
#endif
/*
* Cache/cent handlers
*/
/* Initialize the cache. Call only once. */
#if 0
void init_cache()
{
mk_hash_ctable();
mk_dns_hash();
}
#endif
/* Initialize the cache lock. Call only once. */
/* This is now defined as an inline function in cache.h */
#if 0
void init_cache_lock()
{
use_cache_lock=1;
}
#endif
/* Empty the cache, freeing all entries that match the include/exclude list. */
int empty_cache(slist_array sla)
{
int i;
/* Wait at most 60 seconds to obtain a lock. */
if(!timedlock_cache_rw(60))
return 0;
for(i=0; ; ) {
if(sla)
free_dns_hash_selected(i,sla);
else
free_dns_hash_bucket(i);
if(++i>=HASH_NUM_BUCKETS)
break;
/* Give another thread a chance */
yield_lock_cache_rw();
}
unlock_cache_rw();
return 1;
}
/* Delete the cache. Call only once */
void destroy_cache()
{
/* lock the cache, in case that any thread is still accessing. */
if(!softlock_cache_rw()) {
log_error("Lock failed; could not destroy cache on exit.");
return;
}
free_dns_hash();
#if DEBUG>0
if(ent_num || cache_size) {
DEBUG_MSG("After destroying cache, %ld entries (%ld bytes) remaining.\n",ent_num,cache_size);
}
#endif
#if 0
#if (TARGET!=TARGET_LINUX)
/* under Linux, this frees no resources but may hang on a crash */
pthread_mutex_destroy(&lock_mutex);
pthread_cond_destroy(&rw_cond);
pthread_cond_destroy(&r_cond);
#endif
#endif
}
/* Make a flag value for a dns_cent_t (dns cache entry) from a server record */
/* Now defined as inline function in cache.h */
#if 0
unsigned int mk_flag_val(servparm_t *server)
{
unsigned int fl=0;
if (!server->purge_cache)
fl|=CF_NOPURGE;
if (server->nocache)
fl|=CF_NOCACHE;
if (server->rootserver)
fl|=CF_ROOTSERV;
return fl;
}
#endif
/* Initialize a dns cache record (dns_cent_t) with the query name (in
* transport format), a flag value, a timestamp indicating
* the time the query was done, and a TTL. The timestamp and TTL
* are only used if DF_NEGATIVE is set in the flags. Otherwise,
* the timestamps of the individual records are used. DF_NEGATIVE
* is used for whole-domain negative caching.
* By convention, ttl and ts should be set to 0, unless the
* DF_NEGATIVE bit is set. */
int init_cent(dns_cent_t *cent, const unsigned char *qname, time_t ttl, time_t ts, unsigned flags DBGPARAM)
{
int i;
size_t namesz=rhnlen(qname);
cent->qname=cache_malloc(namesz);
if (cent->qname == NULL)
return 0;
memcpy(cent->qname,qname,namesz);
cent->cs=sizeof(dns_cent_t)+namesz;
cent->num_rrs=0;
cent->flags=flags;
if(flags&DF_NEGATIVE) {
cent->neg.lent=NULL;
cent->neg.ttl=ttl;
cent->neg.ts=ts;
}
else {
for(i=0; i<NRRMU; ++i)
cent->rr.rrmu[i]=NULL;
cent->rr.rrext=NULL;
}
cent->c_ns=cundef;
cent->c_soa=cundef;
return 1;
}
/*
* Create a rr record holder using the given values.
*/
static rr_bucket_t *create_rr(unsigned dlen, void *data DBGPARAM)
{
rr_bucket_t *rrb;
rrb=(rr_bucket_t *)cache_malloc(sizeof(rr_bucket_t)+dlen);
if (rrb == NULL)
return NULL;
rrb->next=NULL;
rrb->rdlen=dlen;
memcpy(rrb->data,data,dlen);
return rrb;
}
/*
* Adds an empty rrset_t with the requested data to a cent. This is exactly what you need to
* do to create a negatively cached cent.
*/
static int add_cent_rrset_by_index(dns_cent_t *cent, unsigned int idx, time_t ttl, time_t ts, unsigned flags DBGPARAM)
{
rr_set_t **rrext, **rrsetpa, *rrset;
/* If we add a rrset, even a negative one, the domain is not negative any more. */
if (cent->flags&DF_NEGATIVE) {
int i;
/* need to remove the cent from the lent list. */
if (cent->neg.lent)
remove_rrl(cent->neg.lent DBGARG);
cent->flags &= ~DF_NEGATIVE;
for(i=0; i<NRRMU; ++i)
cent->rr.rrmu[i]=NULL;
cent->rr.rrext=NULL;
}
if(idx < NRRMU)
rrsetpa = &cent->rr.rrmu[idx];
else {
idx -= NRRMU;
PDNSD_ASSERT(idx < NRREXT, "add_cent_rrset_by_index: rr-set index out of range");
rrext = cent->rr.rrext;
if(!rrext) {
int i;
cent->rr.rrext = rrext = cache_malloc(sizeof(rr_set_t*)*NRREXT);
if(!rrext)
return 0;
for(i=0; i<NRREXT; ++i)
rrext[i]=NULL;
cent->cs += sizeof(rr_set_t*)*NRREXT;
}
rrsetpa = &rrext[idx];
}
#if 0
if(*rrsetpa) del_rrset(*rrsetpa);
#endif
*rrsetpa = rrset = cache_malloc(sizeof(rr_set_t));
if (!rrset)
return 0;
rrset->lent=NULL;
rrset->ttl=ttl;
rrset->ts=ts;
rrset->flags=flags;
rrset->rrs=NULL;
cent->cs += sizeof(rr_set_t);
++cent->num_rrs;
return 1;
}
int add_cent_rrset_by_type(dns_cent_t *cent, int type, time_t ttl, time_t ts, unsigned flags DBGPARAM)
{
int tpi = type - T_MIN;
PDNSD_ASSERT(tpi>=0 && tpi<T_NUM, "add_cent_rrset_by_type: rr type value out of range");
return add_cent_rrset_by_index(cent, rrlkuptab[tpi], ttl, ts, flags DBGARG);
}
/*
* Adds a rr record to a cent. For cache.c internal use.
* idx is the internally used RR-set index, not the RR type!
*/
static int add_cent_rr_int(dns_cent_t *cent, unsigned int idx, time_t ttl, time_t ts, unsigned flags,
unsigned dlen, void *data, rr_bucket_t **rtail DBGPARAM)
{
rr_bucket_t *rr;
rr_set_t *rrset;
if (!(rr=create_rr(dlen,data DBGARG)))
return 0;
if(!(rtail && *rtail)) {
rrset = RRARR_INDEX_TESTEXT(cent,idx);
if (!rrset) {
if (!add_cent_rrset_by_index(cent, idx, ttl, ts, flags DBGARG))
goto cleanup_return;
rrset = RRARR_INDEX(cent,idx);
}
/* do the linking work */
rr->next=rrset->rrs;
rrset->rrs=rr;
}
else {
/* append at the end */
rr->next=(*rtail)->next;
(*rtail)->next=rr;
}
if(rtail) *rtail=rr;
cent->cs += sizeof(rr_bucket_t)+rr->rdlen;
#if DEBUG>0
if(debug_p) {
rrset = RRARR_INDEX(cent,idx);
if (rrset->flags&CF_NEGATIVE) {
char cflagstr[CFLAGSTRLEN];
DEBUG_MSG("Tried to add rr to a rrset with CF_NEGATIVE set! flags=%s\n",cflags2str(rrset->flags,cflagstr));
}
}
#endif
return 1;
cleanup_return:
free_rr(*rr);
free(rr);
return 0;
}
/* Add an rr to a cache entry, giving the ttl, the data length, the rr type
* and a pointer to the data. A record is allocated, and the data is copied into
* it. Do this for all rrs in a cache entry.
* The return value will be 1 in case of success, or 0 in case of a memory allocation
* problem.
*/
int add_cent_rr(dns_cent_t *cent, int type, time_t ttl, time_t ts, unsigned flags,
unsigned dlen, void *data DBGPARAM)
{
int tpi;
unsigned int idx;
rr_set_t *rrset;
rr_bucket_t *rtail, *rrb;
if ((cent->flags&DF_LOCAL) && !(flags&CF_LOCAL))
return 1; /* ignore. Local has precedence. */
tpi = type - T_MIN;
PDNSD_ASSERT(tpi>=0 && tpi<T_NUM, "add_cent_rr: rr type value out of range");
idx= rrlkuptab[tpi];
PDNSD_ASSERT(idx < NRRTOT, "add_cent_rr: illegal rr type value for caching");
rrset= RRARR_INDEX_TESTEXT(cent,idx);
rtail=NULL;
if (rrset) {
if(ttl<rrset->ttl)
/* The ttl timestamps should be identical.
In case they are not, we will use the smallest one. */
rrset->ttl= ttl;
/* OK, some stupid nameservers feel inclined to return the same address twice. Grmbl... */
rrb=rrset->rrs;
while (rrb) {
if (rrb->rdlen==dlen && memcmp(rrb->data,data,dlen)==0)
return 1;
rtail=rrb;
rrb=rrb->next;
}
}
return add_cent_rr_int(cent,idx,ttl,ts,flags,dlen,data,&rtail DBGARG);
}
/* Free a complete rrset including all memory. Returns the size of the memory freed */
int del_rrset(rr_set_t *rrs DBGPARAM)
{
int rv=sizeof(rr_set_t);
rr_bucket_t *rrb,*rrn;
if(rrs->lent) remove_rrl(rrs->lent DBGARG);
rrb=rrs->rrs;
while (rrb) {
rv+=sizeof(rr_bucket_t)+rrb->rdlen;
rrn=rrb->next;
free_rr(*rrb);
cache_free(rrb);
rrb=rrn;
}
cache_free(rrs);
return rv;
}
/* Remove a complete rrset from a cent, freeing the memory.
The second argument should be an RR-set array index, not an RR type!
Returns the size of the memory freed */
static int del_cent_rrset_by_index(dns_cent_t *cent, int i DBGPARAM)
{
int rv=0;
rr_set_t **rrspa = RRARR_INDEX_PA_TESTEXT(cent,i);
if(rrspa) {
rr_set_t *rrs = *rrspa;
if(rrs) {
rv= del_rrset(rrs DBGARG);
*rrspa=NULL;
--cent->num_rrs;
cent->cs -= rv;
cent->flags &= ~DF_AUTH;
}
}
return rv;
}
#if 0
static int del_cent_rrset_by_type(dns_cent_t *cent, int type DBGPARAM)
{
return del_cent_rrset_by_index(cent, rrlkuptab[type-T_MIN] DBGARG);
}
#endif
#if 0
/* Free the pointers contained in an rr record. If the rr record is on the heap,
* don't forget to delete itself. This is done extra mainly for extensibility
* -- This is not here any more. The definition is actually an empty macro in
* cache.h.
*/
void free_rr(rr_bucket_t rr)
{
}
#endif
/* Free all data referred by a cache entry. */
void free_cent(dns_cent_t *cent DBGPARAM)
{
cache_free(cent->qname);
if(cent->flags&DF_NEGATIVE) {
if(cent->neg.lent)
remove_rrl(cent->neg.lent DBGARG);
}
else {
int i;
for (i=0; i<NRRMU; ++i) {
rr_set_t *rrs=cent->rr.rrmu[i];
if (rrs) del_rrset(rrs DBG0);
}
{
rr_set_t **rrext = cent->rr.rrext;
if(rrext) {
for(i=0; i<NRREXT; ++i) {
rr_set_t *rrs=rrext[i];
if (rrs) del_rrset(rrs DBG0);
}
cache_free(rrext);
}
}
}
}
/* Same as free_cent, but is suitable as cleanup handler */
void free_cent0(void *ptr)
{
free_cent(ptr DBG0);
}
/* Negate an existing cache entry and free any existing rr sets. */
void negate_cent(dns_cent_t *cent, time_t ttl, time_t ts)
{
int i;
if(!(cent->flags&DF_NEGATIVE)) {
for (i=0; i<NRRMU; ++i) {
rr_set_t *rrs=cent->rr.rrmu[i];
if (rrs) {
cent->cs -= del_rrset(rrs DBG0);
/* cent->rr.rrmu[i]=NULL; */
}
}
{
rr_set_t **rrext = cent->rr.rrext;
if(rrext) {
for(i=0; i<NRREXT; ++i) {
rr_set_t *rrs=rrext[i];
if (rrs)
cent->cs -= del_rrset(rrs DBG0);
}
cache_free(rrext);
/* cent->rr.rrext=NULL; */
cent->cs -= sizeof(rr_set_t*)*NRREXT;
}
}
cent->num_rrs=0;
cent->flags |= DF_NEGATIVE;
cent->neg.lent=NULL;
}
cent->neg.ttl=ttl;
cent->neg.ts=ts;
}
inline static time_t get_rrlent_ts(rr_lent_t *le)
{
return (le->rrset)?(le->rrset->ts):(le->cent->neg.ts);
}
/* insert a rrset into the rr_l list. This modifies the rr_set_t if rrs is not NULL!
* The rrset address needs to be constant afterwards.
* idx is the internally used RR-set index, not the RR type!
* Call with locks applied. */
static int insert_rrl(rr_set_t *rrs, dns_cent_t *cent, int idx)
{
time_t ts;
rr_lent_t *le,*ne;
/* No need to add local records to the rr_l list, because purge_cache() ignores them anyway. */
if((rrs && (rrs->flags&CF_LOCAL)) || (cent->flags&DF_LOCAL))
return 1;
if (!(ne=malloc(sizeof(rr_lent_t))))
return 0;
ne->rrset=rrs;
ne->cent=cent;
ne->idx=idx;
ne->next=NULL;
ne->prev=NULL;
if(insert_sort) {
/* Since the append at the and is a very common case (and we want this case to be fast), we search back-to-forth.
* Since rr_l is a list and we don't really have fast access to all elements, we do not perform an advanced algorithm
* like binary search.*/
ts=get_rrlent_ts(ne);
le=rrset_l_tail;
while (le) {
if (ts>=get_rrlent_ts(le)) goto found;
le=le->prev;
}
/* not found, so it needs to be inserted at the start of the list. */
ne->next=rrset_l;
if (rrset_l)
rrset_l->prev=ne;
else
rrset_l_tail=ne;
rrset_l=ne;
goto finish;
found:
ne->next=le->next;
ne->prev=le;
if (le->next)
le->next->prev=ne;
else
rrset_l_tail=ne;
le->next=ne;
finish:;
}
else {
/* simply append at the end, sorting will be done later with a more efficient algorithm. */
ne->prev=rrset_l_tail;
if(rrset_l_tail)
rrset_l_tail->next=ne;
else
rrset_l=ne;
rrset_l_tail=ne;
}
if (rrs)
rrs->lent=ne;
else
cent->neg.lent=ne;
return 1;
}
/* Remove a rr from the rr_l list. Call with locks applied. */
static void remove_rrl(rr_lent_t *le DBGPARAM)
{
rr_lent_t *next=le->next,*prev=le->prev;
if (next)
next->prev=prev;
else
rrset_l_tail=prev;
if (prev)
prev->next=next;
else
rrset_l=next;
cache_free(le);
}
/* Merge two sorted rr_l lists to make a larger sorted list.
The lists are sorted according to increasing time-stamp.
The back links are ignored, these must be fixed using a separate pass.
*/
static rr_lent_t *listmerge(rr_lent_t *p, rr_lent_t *q)
{
if(!p)
return q;
else if(!q)
return p;
else {
rr_lent_t *l=NULL, **s= &l;
for(;;) {
if(get_rrlent_ts(p) <= get_rrlent_ts(q)) {
*s= p;
s= &p->next;
p= *s;
if(!p) {
*s= q;
break;
}
}
else { /* get_rrlent_ts(p) > get_rrlent_ts(q) */
*s= q;
s= &q->next;
q= *s;
if(!q) {
*s= p;
break;
}
}
}
return l;
}
}
/* Sort the rr_l list using merge sort, which can be more efficient than insertion sort used by rr_insert().
This algorithm is adapted from the GNU C++ STL implementation for list containers.
Call with locks applied.
Written by Paul Rombouts.
*/
static void sort_rrl()
{
/* Do nothing unless the list has length >= 2. */
if(rrset_l && rrset_l->next) {
/* First sort the list ignoring the back links, these will be fixed later. */
# define NTMPSORT 32
/* Because we use an array of fixed length, the length of the list we can sort
is bounded by pow(2,NTMPSORT)-1. */
rr_lent_t *tmp[NTMPSORT]; /* tmp[i] will either be NULL or point to a sorted list of length pow(2,i). */
rr_lent_t **fill= tmp, **end=tmp+NTMPSORT, **counter;
rr_lent_t *rem= rrset_l, *carry;
do {
carry=rem; rem=rem->next;
carry->next=NULL;
for(counter = tmp; counter!=fill && *counter!=NULL; ++counter) {
carry=listmerge(*counter,carry);
*counter=NULL;
}
PDNSD_ASSERT(counter!=end, "sort_rrl: tmp array overflowed");
*counter=carry;
if(counter==fill) ++fill;
}
while(rem);
/* Merge together all the remaining list fragments contained in array tmp. */
carry= tmp[0];
counter= tmp;
while(++counter!=fill)
carry=listmerge(*counter,carry);
rrset_l= carry;
{
/* Restore the backward links. */
rr_lent_t *p,*q=NULL;
for(p=rrset_l; p; p=p->next) {p->prev=q; q=p;}
rrset_l_tail=q;
}
}
}
/* Copy a rr_bucket_t into newly allocated memory */
inline static rr_bucket_t *copy_rr(rr_bucket_t *rr DBGPARAM)
{
rr_bucket_t *rrn;
rrn=cache_malloc(sizeof(rr_bucket_t)+rr->rdlen);
if (rrn == NULL)
return NULL;
memcpy(rrn,rr,sizeof(rr_bucket_t)+rr->rdlen);
rrn->next=NULL;
return rrn;
}
/* Copy an RR set into newly allocated memory */
static rr_set_t *copy_rrset(rr_set_t *rrset DBGPARAM)
{
rr_set_t *rrsc=cache_malloc(sizeof(rr_set_t));
rr_bucket_t *rr,**rrp;
if (rrsc) {
*rrsc=*rrset;
rrsc->lent=NULL;
rrp=&rrsc->rrs;
rr=rrset->rrs;
while(rr) {
rr_bucket_t *rrc=copy_rr(rr DBGARG);
*rrp=rrc;
if (!rrc) goto cleanup_return;
rrp=&rrc->next;
rr=rr->next;
}
}
return rrsc;
cleanup_return:
del_rrset(rrsc DBG0);
return NULL;
}
/* Copy a cache entry into newly allocated memory */
dns_cent_t *copy_cent(dns_cent_t *cent DBGPARAM)
{
dns_cent_t *copy;
/*
* We do not debug cache internals with it, as mallocs seem to be
* "lost" when they enter the cache for a longer time.
*/
if (!(copy=cache_malloc(sizeof(dns_cent_t))))
return NULL;
{
/* copy the name */
size_t namesz=rhnlen(cent->qname);
if (!(copy->qname=cache_malloc(namesz)))
goto free_return_null;
memcpy(copy->qname,cent->qname,namesz);
}
copy->cs= cent->cs;
copy->num_rrs= cent->num_rrs;
copy->flags= cent->flags;
copy->c_ns = cent->c_ns;
copy->c_soa= cent->c_soa;
if(cent->flags&DF_NEGATIVE) {
copy->neg.lent=NULL;
copy->neg.ttl= cent->neg.ttl;
copy->neg.ts = cent->neg.ts;
}
else {
int i, ilim;
for (i=0; i<NRRMU; ++i)
copy->rr.rrmu[i]=NULL;
copy->rr.rrext=NULL;
ilim = NRRMU;
if(cent->rr.rrext) {
rr_set_t **rrextc;
ilim = NRRTOT;
copy->rr.rrext = rrextc = cache_malloc(sizeof(rr_set_t*)*NRREXT);
if(!rrextc) goto free_cent_return_null;
for (i=0; i<NRREXT; ++i)
rrextc[i]=NULL;
}
for (i=0; i<ilim; ++i) {
rr_set_t *rrset= RRARR_INDEX(cent,i);
if (rrset) {
rr_set_t *rrsc=cache_malloc(sizeof(rr_set_t));
rr_bucket_t *rr,**rrp;
*RRARR_INDEX_PA(copy,i)=rrsc;
if (!rrsc)
goto free_cent_return_null;
*rrsc=*rrset;
rrsc->lent=NULL;
rrp=&rrsc->rrs;
rr=rrset->rrs;
while(rr) {
rr_bucket_t *rrc=copy_rr(rr DBGARG);
*rrp=rrc;
if (!rrc) goto free_cent_return_null;
rrp=&rrc->next;
rr=rr->next;
}
}
}
}
return copy;
free_cent_return_null:
free_cent(copy DBGARG);
free_return_null:
cache_free(copy);
return NULL;
}
/*
* Remove all timed out entries of the RR set with the given index.
* idx is the internally used RR-set index, not the RR type!
* Follow some rules based on flags etc.
* This will either delete the whole rrset, or will leave it as a whole (RFC2181 seems to
* go in that direction)
* This was pretty large once upon a time ;-), but now, since we operate in rrsets, was
* shrunk drastically.
* If test is zero and the record is in the cache, we need rw-locks applied.
* If test is nonzero, nothing will actually be deleted.
* Substracts the size of the freed memory from cache_size (if test is zero).
* Returns 1 if the rrset has been (or would have been) deleted.
*/
static int purge_rrset(dns_cent_t *cent, int idx, int test)
{
rr_set_t *rrs= RRARR_INDEX_TESTEXT(cent,idx);
if (rrs && !(rrs->flags&CF_NOPURGE || rrs->flags&CF_LOCAL) && timedout(rrs)) {
/* well, it must go. */
if(!test)
cache_size -= del_cent_rrset_by_index(cent,idx DBG0);
return 1;
}
return 0;
}
/*
Remove all timed out entries of alls RR sets of a cache entry.
The test flag works the same as in purge_rrset().
Substracts the size of the freed memory from cache_size, just as purge_rrset().
*numrrsrem is set to the number of remaining RR sets (or the number that would have remained).
Returns the number of items (RR sets or RR set arrays) that have been (or would have been) deleted.
*/
static int purge_all_rrsets(dns_cent_t *cent, int test, int *numrrsrem)
{
int rv=0, numrrs=0, numrrext=0;
if(!(cent->flags&DF_NEGATIVE)) {
int i, ilim= RRARR_LEN(cent);
for(i=0; i<ilim; ++i) {
rr_set_t *rrs= RRARR_INDEX(cent,i);
if (rrs) {
if(!(rrs->flags&CF_NOPURGE || rrs->flags&CF_LOCAL) && timedout(rrs)) {
/* well, it must go. */
if(!test)
cache_size -= del_cent_rrset_by_index(cent, i DBG0);
++rv;
}
else {
++numrrs;
if(i>=NRRMU) ++numrrext;
}
}
}
/* If the array of less frequently used RRs has become empty, free it. */
if(cent->rr.rrext && numrrext==0) {
if(!test) {
cache_free(cent->rr.rrext);
cent->rr.rrext=NULL;
cent->cs -= sizeof(rr_set_t*)*NRREXT;
cache_size -= sizeof(rr_set_t*)*NRREXT;
}
++rv;
}
}
if(numrrsrem) *numrrsrem=numrrs;
return rv;
}
/*
* Purge a cent, deleting timed-out rrs (following the constraints noted in "purge_rrset").
* Since the cent may actually become empty and be deleted, you may not use it after this call until
* you refetch its address from the hash (if it is still there).
* If test is zero and the record is in the cache, we need rw-locks applied.
* If test is nonzero, nothing will actually be deleted.
* Substracts the size of the freed memory from cache_size (if test is zero).
* If delete is nonzero and the cent was purged empty and no longer needed, it is removed from the cache.
* Returns -1 if the cent was (or would have been) completely removed,
* otherwise returns the number of items that were (or would have been) deleted.
*/
static int purge_cent(dns_cent_t *cent, int delete, int test)
{
int npurge, numrrs;
npurge = purge_all_rrsets(cent,test, &numrrs);
/* If the cache entry was purged empty, delete it from the cache. */
if (delete && numrrs==0
&& (!(cent->flags&DF_NEGATIVE) ||
(!(cent->flags&DF_LOCAL) && timedout_nxdom(cent))))
{
if(!test)
del_cache_ent(cent,NULL); /* this will subtract the cent's left size from cache_size */
return -1;
}
if(!(cent->flags&DF_LOCAL)) {
/* Set stale references to NS or SOA records back to undefined. */
unsigned scnt=rhnsegcnt(cent->qname);
if(cent->c_ns!=cundef) {
rr_set_t *rrset=NULL;
if(cent->c_ns==scnt)
rrset=getrrset_NS(cent);
else if(cent->c_ns<scnt) {
dns_cent_t *ce=dns_lookup(skipsegs(cent->qname,scnt-cent->c_ns),NULL);
if(ce) rrset=getrrset_NS(ce);
}
if(!rrset || !rrset->rrs || (!(rrset->flags&CF_LOCAL) && timedout(rrset))) {
if(!test)
cent->c_ns=cundef;
++npurge;
}
}
if(cent->c_soa!=cundef) {
rr_set_t *rrset=NULL;
if(cent->c_soa==scnt)
rrset=getrrset_SOA(cent);
else if(cent->c_soa<scnt) {
dns_cent_t *ce=dns_lookup(skipsegs(cent->qname,scnt-cent->c_soa),NULL);
if(ce) rrset=getrrset_SOA(ce);
}
if(!rrset || !rrset->rrs || (!(rrset->flags&CF_LOCAL) && timedout(rrset))) {
if(!test)
cent->c_soa=cundef;
++npurge;
}
}
}
return npurge;
}
/*
* Bring cache to a size below or equal the cache size limit (sz). There are two strategies:
* - for cached sets with CF_NOPURGE not set: delete if timed out
* - additional: delete oldest sets.
*/
static void purge_cache(long sz, int lazy)
{
rr_lent_t *le;
/* Walk the cache list from the oldest entries to the newest, deleting timed-out
* records.
* XXX: We walk the list a second time if this did not free up enough space - this
* should be done better. */
le=rrset_l;
while (le && (!lazy || cache_size>sz)) {
/* Note by Paul Rombouts:
* If data integrity is ensured, at most one node is removed from the rrset_l
* per iteration, and this node is the one referenced by le. */
rr_lent_t *next=le->next;
if (!((le->rrset && (le->rrset->flags&CF_LOCAL)) ||
(le->cent->flags&DF_LOCAL))) {
dns_cent_t *ce = le->cent;
if (le->rrset)
purge_rrset(ce, le->idx,0);
/* Side effect: if purge_rrset called del_cent_rrset then le has been freed.
* ce, however, is still guaranteed to be valid. */
if (ce->num_rrs==0 && (!(ce->flags&DF_NEGATIVE) ||
(!(ce->flags&DF_LOCAL) && timedout_nxdom(ce))))
del_cache_ent(ce,NULL);
}
le=next;
}
if (cache_size<=sz)
return;
/* we are still above the desired cache size. Well, delete records from the oldest to
* the newest. This is the case where nopurge records are deleted anyway. Only local
* records are kept in any case.*/
if(!insert_sort) {
sort_rrl();
insert_sort=1; /* use insertion sort from now on */
}
le=rrset_l;
while (le && cache_size>sz) {
rr_lent_t *next=le->next;
if (!((le->rrset && (le->rrset->flags&CF_LOCAL)) ||
(le->cent->flags&DF_LOCAL))) {
dns_cent_t *ce = le->cent;
if (le->rrset)
cache_size -= del_cent_rrset_by_index(ce, le->idx DBG0);
/* this will also delete negative cache entries */
if (ce->num_rrs==0)
del_cache_ent(ce,NULL);
}
le=next;
}
}
#define log_warn_read_error(f,item) \
log_warn("%s encountered while reading %s from disk cache file.", \
ferror(f)?"Error":feof(f)?"EOF":"Incomplete item",item)
/*
* Load cache from disk and rebuild the hash tables.
*/
void read_disk_cache()
{
/* The locks are done when we add items. */
dns_cent_t ce;
int dtsz=512;
unsigned char *data;
unsigned long cnt;
FILE *f;
char path[strlen(global.cache_dir)+sizeof("/pdnsd.cache")];
stpcpy(stpcpy(path,global.cache_dir),"/pdnsd.cache");
if (!(f=fopen(path,"r"))) {
log_warn("Could not open disk cache file %s: %s",path,strerror(errno));
return;
}
if (!(data = malloc(dtsz))) {
goto fclose_exit;
}
/* Don't use insertion sort while reading caches entries from disk, because this can be
noticeably inefficient with large cache files.
Entries are simply appended at the end of the rr_l list.
The rr_l list is sorted using a more efficient merge sort after we are done reading.
*/
insert_sort=0;
{
unsigned nb;
char buf[sizeof(cachverid)];
/* check cache version identifier */
nb=fread(buf,1,sizeof(cachverid),f);
if (nb!=sizeof(cachverid)) {
/* Don't complain about empty files */
if(nb!=0 || !feof(f)) {
log_warn_read_error(f,"cache version identifier");
}
goto free_data_fclose;
}
if(memcmp(buf,cachverid,sizeof(cachverid))) {
log_warn("Cache file %s ignored because of incompatible version identifier",path);
goto free_data_fclose;
}
}
if (fread(&cnt,sizeof(cnt),1,f)!=1) {
log_warn_read_error(f,"entry count");
goto free_data_fclose;
}
for(;cnt>0;--cnt) {
dns_file_t fe;
dom_fttlts_t fttlts = {0,0};
unsigned char nb[256];
unsigned num_rrs;
unsigned char prevtp;
if (fread(&fe,sizeof(fe),1,f)!=1) {
log_warn_read_error(f,"cache entry header");
goto free_data_fclose;
}
if(fe.flags&DF_NEGATIVE) {
if (fread(&fttlts,sizeof(fttlts),1,f)!=1) {
log_warn_read_error(f,"cache TTL and timestamp");
goto free_data_fclose;
}
}
if (fe.qlen) {
int i;
/* Because of its type qlen should be <=255. */
if (fread(nb,fe.qlen,1,f)!=1) {
log_warn_read_error(f,"domain name");
goto free_data_fclose;
}
for(i=0;i<fe.qlen;) {
unsigned lb=nb[i];
if(!lb || lb>63 || (i += lb+1)>fe.qlen) {
log_warn("Invalid domain name encountered while reading disk cache file.");
goto free_data_fclose;
}
}
}
nb[fe.qlen]='\0';
if (!init_cent(&ce, nb, fttlts.ttl, fttlts.ts, fe.flags DBG0)) {
goto free_data_fclose_exit;
}
ce.c_ns=fe.c_ns; ce.c_soa=fe.c_soa;
/* now, read the rr's */
prevtp=0;
for (num_rrs=fe.num_rrs;num_rrs;--num_rrs) {
rr_fset_t sh;
unsigned num_rr;
if (fread(&sh,sizeof(sh),1,f)!=1) {
log_warn_read_error(f,"rr header");
goto free_cent_data_fclose;
}
if(PDNSD_NOT_CACHED_TYPE(sh.tp)) {
log_warn("Invalid rr type encountered while reading disk cache file.");
goto free_data_fclose;
}
if(sh.tp<=prevtp) {
log_warn("Unexpected rr type encountered (not in strict ascending order) while reading disk cache file.");
goto free_data_fclose;
}
prevtp=sh.tp;
/* Add the rrset header in any case (needed for negative caching) */
if(!add_cent_rrset_by_type(&ce, sh.tp, sh.ttl, sh.ts, sh.flags DBG0)) {
goto free_cent_data_fclose_exit;
}
for (num_rr=sh.num_rr;num_rr;--num_rr) {
rr_fbucket_t rr;
if (fread(&rr,sizeof(rr),1,f)!=1) {
log_warn_read_error(f,"rr data length");
goto free_cent_data_fclose;
}
if (rr.rdlen>dtsz) {
unsigned char *tmp;
dtsz=rr.rdlen;
tmp=realloc(data,dtsz);
if (!tmp) {
goto free_cent_data_fclose_exit;
}
data=tmp;
}
if (rr.rdlen && fread(data,rr.rdlen,1,f)!=1) {
log_warn_read_error(f,"rr data");
goto free_cent_data_fclose;
}
if (!add_cent_rr(&ce,sh.tp,sh.ttl,sh.ts,sh.flags,rr.rdlen,data DBG0)) {
goto free_cent_data_fclose_exit;
}
}
}
add_cache(&ce);
free_cent(&ce DBG0);
}
#ifdef DEBUG_HASH
free(data);
fclose(f);
dumphash();
goto sort_return;
#else
goto free_data_fclose;
#endif
free_cent_data_fclose:
free_cent(&ce DBG0);
free_data_fclose:
free(data);
fclose(f);
#ifdef DEBUG_HASH
sort_return:
#endif
/* Do we need read/write locks to sort the rr_l list?
As long as at most one thread is sorting, it is OK for the other threads
to read the cache, providing they do not add or delete anything.
*/
lock_cache_r();
if(!insert_sort) {
sort_rrl();
insert_sort=1;
}
unlock_cache_r();
return;
free_cent_data_fclose_exit:
free_cent(&ce DBG0);
free_data_fclose_exit:
free(data);
fclose_exit:
fclose(f);
log_error("Out of memory in reading cache file. Exiting.");
pdnsd_exit();
}
/* write an rr to the file f */
static int write_rrset(int tp, rr_set_t *rrs, FILE *f)
{
rr_bucket_t *rr;
rr_fset_t sh;
rr_fbucket_t rf;
unsigned num_rr;
sh.tp=tp;
num_rr=0;
for(rr=rrs->rrs; rr && num_rr<255; rr=rr->next) ++num_rr;
sh.num_rr=num_rr;
sh.flags=rrs->flags;
sh.ttl=rrs->ttl;
sh.ts=rrs->ts;
if (fwrite(&sh,sizeof(sh),1,f)!=1) {
log_error("Error while writing rr header to disk cache: %s", strerror(errno));
return 0;
}
rr=rrs->rrs;
for(; num_rr; --num_rr) {
rf.rdlen=rr->rdlen;
if (fwrite(&rf,sizeof(rf),1,f)!=1 || (rf.rdlen && fwrite((rr->data),rf.rdlen,1,f)!=1)) {
log_error("Error while writing rr data to disk cache: %s", strerror(errno));
return 0;
}
rr=rr->next;
}
return 1;
}
/*
* Write cache to disk on termination. The hash table is lost and needs to be regenerated
* on reload.
*
* The locks are not very fine grained here, but I don't think this needs fixing as this routine
* is only called on exit.
*
*/
void write_disk_cache()
{
int j, jlim;
dns_cent_t *le;
unsigned long en=0;
dns_hash_pos_t pos;
FILE *f;
unsigned long num_rrs_errs=0;
# define MAX_NUM_RRS_ERRS 10
char path[strlen(global.cache_dir)+sizeof("/pdnsd.cache")];
stpcpy(stpcpy(path,global.cache_dir),"/pdnsd.cache");
DEBUG_MSG("Writing cache to %s\n",path);
if (!softlock_cache_rw()) {
goto lock_failed;
}
/* purge cache down to allowed size*/
purge_cache((long)global.perm_cache*1024, 0);
if (!softunlock_cache_rw()) {
goto lock_failed;
}
if (!softlock_cache_r()) {
goto lock_failed;
}
if (!(f=fopen(path,"w"))) {
log_warn("Could not open disk cache file %s: %s",path,strerror(errno));
goto softunlock_return;
}
/* Write the cache version identifier */
if (fwrite(cachverid,sizeof(cachverid),1,f)!=1) {
log_error("Error while writing cache version identifier to disk cache: %s", strerror(errno));
goto fclose_unlock;
}
for (le=fetch_first(&pos); le; le=fetch_next(&pos)) {
/* count the rr's */
if(le->flags&DF_NEGATIVE) {
if(!(le->flags&DF_LOCAL))
++en;
}
else {
jlim= RRARR_LEN(le);
for (j=0; j<jlim; ++j) {
rr_set_t *rrset= RRARR_INDEX(le,j);
if (rrset && !(rrset->flags&CF_LOCAL)) {
++en;
break;
}
}
}
}
if (fwrite(&en,sizeof(en),1,f)!=1) {
log_error("Error while writing entry count to disk cache: %s", strerror(errno));
goto fclose_unlock;
}
for (le=fetch_first(&pos); le; le=fetch_next(&pos)) {
/* now, write the rr's */
if(le->flags&DF_NEGATIVE) {
if(!(le->flags&DF_LOCAL))
goto write_rrs;
}
else {
jlim= RRARR_LEN(le);
for (j=0; j<jlim; ++j) {
rr_set_t *rrset= RRARR_INDEX(le,j);
if (rrset && !(rrset->flags&CF_LOCAL)) {
goto write_rrs;
}
}
}
continue;
write_rrs:
{
dns_file_t df;
int num_rrs;
const unsigned short *iterlist;
df.qlen=rhnlen(le->qname)-1; /* Don't include the null byte at the end */
df.num_rrs=0;
df.flags=le->flags;
df.c_ns=le->c_ns; df.c_soa=le->c_soa;
num_rrs=0;
jlim=RRARR_LEN(le);
for (j=0; j<jlim; ++j) {
rr_set_t *rrset= RRARR_INDEX(le,j);
if(rrset) {
++num_rrs;
if(!(rrset->flags&CF_LOCAL))
++df.num_rrs;
}
}
if(num_rrs!=le->num_rrs && ++num_rrs_errs<=MAX_NUM_RRS_ERRS) {
unsigned char buf[DNSNAMEBUFSIZE];
log_warn("Counted %d rr record types for %s but cached counter=%d",
num_rrs,rhn2str(le->qname,buf,sizeof(buf)),le->num_rrs);
}
if (fwrite(&df,sizeof(df),1,f)!=1) {
log_error("Error while writing cache entry header to disk cache: %s", strerror(errno));
goto fclose_unlock;
}
if(le->flags&DF_NEGATIVE) {
dom_fttlts_t fttlts= {le->neg.ttl,le->neg.ts};
if (fwrite(&fttlts,sizeof(fttlts),1,f)!=1) {
log_error("Error while writing cache TTL and timestamp to disk cache: %s", strerror(errno));
goto fclose_unlock;
}
}
if (df.qlen && fwrite(le->qname,df.qlen,1,f)!=1) {
log_error("Error while writing domain name to disk cache: %s", strerror(errno));
goto fclose_unlock;
}
jlim= NRRITERLIST(le);
iterlist= RRITERLIST(le);
for (j=0; j<jlim; ++j) {
int tp= iterlist[j];
rr_set_t *rrset= getrrset_eff(le,tp);
if(rrset && !(rrset->flags&CF_LOCAL)) {
if(!write_rrset(tp,rrset,f))
goto fclose_unlock;
}
}
}
}
if(fclose(f)) {
log_error("Could not close cache file %s after writing cache: %s", path,strerror(errno));
}
softunlock_cache_r();
DEBUG_MSG("Finished writing cache to disk.\n");
return;
fclose_unlock:
fclose(f);
softunlock_return:
softunlock_cache_r();
return;
lock_failed:
crash_msg("Lock failed; could not write disk cache.");
}
/*
* Conflict Resolution.
* The first function is the actual checker; the latter two are wrappers for the respective
* function for convenience only.
*
* We check for conflicts by checking the new data rrset by rrset against the cent.
* This is not bad when considering that new records are hopefully consistent; if they are not,
* we might end up deleteing too much of the old data, which is probably added back through the
* new query, though.
* Having checked additions rrset by rrset, we are at least sure that the resulting record is OK.
* cr_check_add returns 1 if the addition is OK, 0 otherwise.
* This is for records that are already in the cache!
*
* idx is the internally used RR-set index, not the RR type!
*/
static int cr_check_add(dns_cent_t *cent, int idx, time_t ttl, time_t ts, unsigned flags)
{
time_t nttl;
const struct rr_infos *rri;
if (flags & CF_NEGATIVE)
return 1; /* no constraints here. */
nttl = 0;
rri = &rr_info[idx];
if (!(flags & CF_LOCAL)) {
int i, ilim, ncf;
if(cent->flags & DF_LOCAL)
return 0; /* Local has precedence. */
ncf = 0; ilim = RRARR_LEN(cent);
for (i = 0; i < ilim; ++i) {
rr_set_t *rrs= RRARR_INDEX(cent,i);
/* Should be symmetric; check both ways anyway. */
if (rrs && !(rrs->flags & CF_NEGATIVE) &&
((rri->class & rr_info[i].excludes) ||
(rri->excludes & rr_info[i].class)))
{
time_t rttl;
if (rrs->flags & CF_LOCAL)
return 0; /* old was authoritative. */
++ncf;
rttl = rrs->ttl + rrs->ts - time(NULL);
if(rttl > 0) nttl += rttl;
}
}
if (ncf == 0) /* no conflicts */
return 1;
/* Medium ttl of conflicting records */
nttl /= ncf;
}
if ((flags & CF_LOCAL) || ttl > nttl) {
int i, ilim= RRARR_LEN(cent);
/* Remove the old records, so that the new one can be added. */
for (i = 0; i < ilim; ++i) {
rr_set_t *rrs= RRARR_INDEX(cent,i);
/* Should be symmetric; check both ways anyway. */
if (rrs && !(rrs->flags & CF_NEGATIVE) &&
((rri->class & rr_info[i].excludes) ||
(rri->excludes & rr_info[i].class))) {
del_cent_rrset_by_index(cent, i DBG0);
}
}
return 1;
}
/* old records precede */
return 0;
}
inline static void adjust_ttl(rr_set_t *rrset)
{
if (rrset->flags&CF_NOCACHE) {
rrset->flags &= ~CF_NOCACHE;
rrset->ttl=0;
}
else {
time_t min_ttl= global.min_ttl, neg_ttl=global.neg_ttl;
if((rrset->flags&CF_NEGATIVE) && neg_ttl<min_ttl)
min_ttl=neg_ttl;
if(rrset->ttl<min_ttl)
rrset->ttl=min_ttl;
else {
time_t max_ttl= global.max_ttl;
if(rrset->ttl>max_ttl)
rrset->ttl=max_ttl;
}
}
}
/* Only use for negatively cached domains, thus only
if the DF_NEGATIVE bit is set! */
inline static void adjust_dom_ttl(dns_cent_t *cent)
{
if (cent->flags&DF_NOCACHE) {
cent->flags &= ~DF_NOCACHE;
cent->neg.ttl=0;
}
else {
time_t min_ttl= global.min_ttl, neg_ttl=global.neg_ttl;
if(/* (cent->flags&DF_NEGATIVE) && */ neg_ttl<min_ttl)
min_ttl=neg_ttl;
if(cent->neg.ttl<min_ttl)
cent->neg.ttl=min_ttl;
else {
time_t max_ttl= global.max_ttl;
if(cent->neg.ttl>max_ttl)
cent->neg.ttl=max_ttl;
}
}
}
/*
* Add a ready built dns_cent_t to the hashes, purge if necessary to not exceed cache size
* limits, and add the entries to the hashes.
* As memory is already reserved for the rrs, we only need to wrap up the dns_cent_t and
* alloc memory for it.
* New entries are appended, so we easiliy know the oldest for purging. For fast acces,
* we use hashes instead of ordered storage.
*
* This does not free the argument, and it uses a copy of it, so the caller must do free_cent()
* on it.
*
* The new entries rr sets replace the old ones, i.e. old rr sets with the same key are deleted
* before the new ones are added.
*/
void add_cache(dns_cent_t *cent)
{
dns_cent_t *ce;
dns_hash_loc_t loc;
int i,ilim;
lock_cache_rw();
retry:
if (!(ce=dns_lookup(cent->qname,&loc))) {
/* if the new entry doesn't contain any information,
don't try to add it to the cache because purge_cache() will not
be able to get rid of it.
*/
if(cent->num_rrs==0 && !(cent->flags&DF_NEGATIVE))
goto purge_cache_return;
if(!(ce=copy_cent(cent DBG0)))
goto warn_unlock_cache_return;
if(!(ce->flags&DF_NEGATIVE)) {
ilim= RRARR_LEN(ce);
/* Add the rrs to the rr list */
for (i=0; i<ilim; ++i) {
rr_set_t *rrset= RRARR_INDEX(ce,i);
if (rrset) {
adjust_ttl(rrset);
if (!insert_rrl(rrset,ce,i))
goto free_cent_unlock_cache_return;
}
}
}
else {
/* If this domain is negatively cached, add the cent to the rr_l list. */
adjust_dom_ttl(ce);
if (!insert_rrl(NULL,ce,-1))
goto free_cent_unlock_cache_return;
}
if (!add_dns_hash(ce,&loc))
goto free_cent_unlock_cache_return;
++ent_num;
} else {
if (cent->flags&DF_NEGATIVE) {
/* the new entry is negative. So, we need to delete the whole cent,
* and then generate a new one. */
ilim= RRARR_LEN(ce);
for (i=0; i<ilim; ++i) {
rr_set_t *cerrs= RRARR_INDEX(ce,i);
if (cerrs && cerrs->flags&CF_LOCAL) {
goto unlock_cache_return; /* Do not clobber local records */
}
}
del_cache_ent(ce,&loc);
goto retry;
}
purge_cent(ce, 0,0);
/* We have a record; add the rrsets replacing old ones */
cache_size-=ce->cs;
ilim= RRARR_LEN(cent);
for (i=0; i<ilim; ++i) {
rr_set_t *centrrs= RRARR_INDEX(cent,i);
if(centrrs) {
rr_set_t *cerrs= RRARR_INDEX_TESTEXT(ce,i);
/* Local records have precedence.
Records from answer sections have precedence over additional (off-topic) records.
Answers obtained from root servers have precedence over additional records
from other servers. */
if (!(cerrs &&
((!(centrrs->flags&CF_LOCAL) && (cerrs->flags&CF_LOCAL)) ||
((centrrs->flags&CF_ADDITIONAL) && (!(cerrs->flags&CF_ADDITIONAL) ||
(!(centrrs->flags&CF_ROOTSERV) &&
(cerrs->flags&CF_ROOTSERV))) &&
!timedout(cerrs)))))
{
rr_bucket_t *rr,*rtail;
del_cent_rrset_by_index(ce,i DBG0);
if (!cr_check_add(ce, i, centrrs->ttl, centrrs->ts, centrrs->flags))
continue; /* the new record has been deleted as a conflict resolution measure. */
/* pre-initialize a rrset_t for the case we have a negative cached
* rrset, in which case no further rrs will be added. */
if (!add_cent_rrset_by_index(ce, i, centrrs->ttl, centrrs->ts, centrrs->flags DBG0)) {
goto addsize_unlock_cache_return;
}
rtail=NULL;
for (rr=centrrs->rrs; rr; rr=rr->next) {
if (!add_cent_rr_int(ce,i,centrrs->ttl, centrrs->ts, centrrs->flags,
rr->rdlen, rr->data, &rtail DBG0))
{
/* cleanup this entry */
goto cleanup_cent_unlock_cache_return;
}
}
cerrs= RRARR_INDEX(ce,i);
adjust_ttl(cerrs);
if (!insert_rrl(cerrs,ce,i)) {
goto cleanup_cent_unlock_cache_return;
}
}
}
}
ce->flags |= (cent->flags&(DF_AUTH|DF_WILD));
if(cent->c_ns!=cundef && (ce->c_ns==cundef || ce->c_ns<cent->c_ns))
ce->c_ns=cent->c_ns;
if(cent->c_soa!=cundef && (ce->c_soa==cundef || ce->c_soa<cent->c_soa))
ce->c_soa=cent->c_soa;
}
cache_size += ce->cs;
purge_cache_return:
purge_cache((long)global.perm_cache*1024+MCSZ, 1);
goto unlock_cache_return;
cleanup_cent_unlock_cache_return:
del_cent_rrset_by_index(ce, i DBG0);
addsize_unlock_cache_return:
cache_size += ce->cs;
goto warn_unlock_cache_return;
free_cent_unlock_cache_return:
free_cent(ce DBG0);
pdnsd_free(ce);
warn_unlock_cache_return:
log_warn("Out of cache memory.");
unlock_cache_return:
unlock_cache_rw();
}
/*
Convert A (and AAAA) records in a ready built cache entry to PTR records suitable for reverse resolving
of numeric addresses and add them to the cache.
*/
int add_reverse_cache(dns_cent_t * cent)
{
int tp=T_A;
rr_set_t *rrset= getrrset_A(cent);
for(;;) {
if(rrset) {
rr_bucket_t *rr;
for(rr=rrset->rrs; rr; rr=rr->next) {
dns_cent_t ce;
unsigned char buf[DNSNAMEBUFSIZE],rhn[DNSNAMEBUFSIZE];
if(!a2ptrstr((pdnsd_ca *)(rr->data),tp,buf) || !str2rhn(buf,rhn))
return 0;
if(!init_cent(&ce, rhn, 0, 0, cent->flags DBG0))
return 0;
if(!add_cent_rr(&ce,T_PTR,rrset->ttl,rrset->ts,rrset->flags,rhnlen(cent->qname),cent->qname DBG0)) {
free_cent(&ce DBG0);
return 0;
}
#ifdef RRMUINDEX_NS
ce.rr.rrmu[RRMUINDEX_NS]=cent->rr.rrmu[RRMUINDEX_NS];
#endif
#ifdef RRMUINDEX_SOA
ce.rr.rrmu[RRMUINDEX_SOA]=cent->rr.rrmu[RRMUINDEX_SOA];
#endif
add_cache(&ce);
#ifdef RRMUINDEX_NS
ce.rr.rrmu[RRMUINDEX_NS]=NULL;
#endif
#ifdef RRMUINDEX_SOA
ce.rr.rrmu[RRMUINDEX_SOA]=NULL;
#endif
free_cent(&ce DBG0);
}
}
#if ALLOW_LOCAL_AAAA
if(tp==T_AAAA)
break;
tp=T_AAAA;
rrset= getrrset_AAAA(cent);
#else
break;
#endif
}
return 1;
}
/*
Delete a cent from the cache. Call with write locks applied.
Does not delete corresponding entry in hash table, call del_cache_ent()
or del_cache() for that.
*/
void del_cent(dns_cent_t *cent)
{
cache_size -= cent->cs;
/* free the data referred by the cent and the cent itself */
free_cent(cent DBG0);
free(cent);
--ent_num;
}
/*
* Delete a cent from the cache. Call with write locks applied.
*/
static void del_cache_ent(dns_cent_t *cent,dns_hash_loc_t *loc)
{
dns_cent_t *data;
/* Delete from the hash */
if(loc)
data=del_dns_hash_ent(loc);
else
data=del_dns_hash(cent->qname);
if(!data) {
log_warn("Cache entry not found by del_dns_hash() in %s, line %d",__FILE__,__LINE__);
}
else if(data!=cent) {
log_warn("pointer returned by del_dns_hash() does not match cache entry in %s, line %d",__FILE__,__LINE__);
}
del_cent(cent);
}
/* Delete a cached record. Performs locking. Call this from the outside, NOT del_cache_ent */
void del_cache(const unsigned char *name)
{
dns_cent_t *cent;
lock_cache_rw();
if ((cent=del_dns_hash(name))) {
del_cent(cent);
}
unlock_cache_rw();
}
/* Invalidate a record by resetting the fetch time to 0. This means that it will be refreshed
* if possible (and will only be served when purge_cache=off;) */
void invalidate_record(const unsigned char *name)
{
dns_cent_t *ce;
int i, ilim;
lock_cache_rw();
if ((ce=dns_lookup(name,NULL))) {
if(!(ce->flags&DF_NEGATIVE)) {
ilim= RRARR_LEN(ce);
for (i=0; i<ilim; ++i) {
rr_set_t *rrs= RRARR_INDEX(ce,i);
if (rrs) {
rrs->ts=0;
rrs->flags &= ~CF_AUTH;
}
}
}
else {
/* set the cent time to 0 (for the case that this was negative) */
ce->neg.ts=0;
}
ce->flags &= ~DF_AUTH;
}
unlock_cache_rw();
}
/*
Set flags of the cache entry with the specified name.
Don't use this to set the DF_NEGATIVE flag, or you will
risk leaving the cache in an inconsistent state.
Returns 0 if the cache entry cannot be found, otherwise 1.
*/
int set_cent_flags(const unsigned char *name, unsigned flags)
{
dns_cent_t *ret;
lock_cache_rw();
ret=dns_lookup(name,NULL);
if (ret) {
ret->flags |= flags;
}
unlock_cache_rw();
return ret!=NULL;
}
unsigned char *getlocalowner(unsigned char *name,int tp)
{
unsigned char *ret=NULL;
dns_cent_t *ce;
unsigned lb;
lock_cache_r();
if((lb = *name)) {
while(name += lb+1, lb = *name) {
if((ce=dns_lookup(name,NULL))) {
if(!(ce->flags&DF_LOCAL))
break;
if(have_rr(ce,tp)) {
ret=name;
break;
}
}
}
}
unlock_cache_r();
return ret;
}
/* Lookup an entry in the cache using name (in length byte - string notation).
* For thread safety, a copy must be returned, so delete it after use, by first doing
* free_cent to remove the rrs and then by freeing the returned pointer.
* If wild is nonzero, and name can't be found in the cache, lookup_cache()
* will search up the name hierarchy for a record with the DF_NEGATIVE or DF_WILD flag set.
*/
dns_cent_t *lookup_cache(const unsigned char *name, int *wild)
{
int purge=0;
dns_cent_t *ret;
/* First try with only read access to the cache. */
lock_cache_r();
ret=dns_lookup(name,NULL);
if(wild) {
*wild=0;
if(!ret) {
const unsigned char *nm=name;
unsigned lb=*nm;
if(lb) {
while(nm += lb+1, lb = *nm) {
if ((ret=dns_lookup(nm,NULL))) {
if(ret->flags&DF_NEGATIVE)
/* use this entry */
*wild=w_neg;
else if(ret->flags&DF_WILD) {
unsigned char buf[DNSNAMEBUFSIZE];
buf[0]=1; buf[1]='*';
/* When we get here, at least one element of name
has been removed, so assuming name is not longer
than DNSNAMEBUFSIZE bytes, the remainder is guaranteed to
fit into DNSNAMEBUFSIZE-2 bytes */
rhncpy(&buf[2],nm);
ret=dns_lookup(buf,NULL);
if(ret)
*wild=w_wild;
}
else if(ret->flags&DF_LOCAL)
*wild=w_locnerr;
else
ret=NULL;
break;
}
}
}
}
}
if (ret) {
if(!(purge=purge_cent(ret, 1,1))) /* test only, don't remove anything yet! */
ret=copy_cent(ret DBG1);
}
unlock_cache_r();
if(purge) {
/* we need exclusive read and write access before we delete anything. */
lock_cache_rw();
ret=dns_lookup(name,NULL);
if(wild) {
*wild=0;
if(!ret) {
const unsigned char *nm=name;
unsigned lb=*nm;
if(lb) {
while(nm += lb+1, lb = *nm) {
if ((ret=dns_lookup(nm,NULL))) {
if(ret->flags&DF_NEGATIVE)
/* use this entry */
*wild=w_neg;
else if(ret->flags&DF_WILD) {
unsigned char buf[DNSNAMEBUFSIZE];
buf[0]=1; buf[1]='*';
rhncpy(&buf[2],nm);
ret=dns_lookup(buf,NULL);
if(ret)
*wild=w_wild;
}
else if(ret->flags&DF_LOCAL)
*wild=w_locnerr;
else
ret=NULL;
break;
}
}
}
}
}
if (ret) {
if(purge_cent(ret, 1,0)<0)
ret=NULL;
else
ret=copy_cent(ret DBG1);
}
unlock_cache_rw();
}
return ret;
}
/* lookup_cache_local_rrset() check if there is locally defined RR set of a specific RR type
for name, and if so, returns a copy of the RR set. After use, the copy should be cleaned
up using del_rrset().
This is potentially much more efficient than using lookup_cache(), if the name is likely
to have a cache entry, but unlikely to have locally defined RR sets.
*/
rr_set_t *lookup_cache_local_rrset(const unsigned char *name, int type)
{
rr_set_t *ret=NULL;
dns_cent_t *cent;
lock_cache_r();
cent= dns_lookup(name,NULL);
if(cent) {
rr_set_t *rrset=getrrset(cent,type);
if(rrset && (rrset->flags&CF_LOCAL)) {
ret= copy_rrset(rrset);
}
}
unlock_cache_r();
return ret;
}
#if 0
/* Add an rr to an existing cache entry or create a new entry if necessary.
* The rr is treated with the precedence of an additional or off-topic record, ie. regularly retrieved
* have precedence.
* You cannot add a negative additional record. Makes no sense anyway. */
int add_cache_rr_add(const unsigned char *name, int tp, time_t ttl, time_t ts, unsigned flags, unsigned dlen, void *data, unsigned long serial)
{
dns_hash_loc_t loc;
dns_cent_t *ret;
rr_set_t *rrs;
int rv=0;
lock_cache_rw();
if (!(ret=dns_lookup(name,&loc))) {
if (!(ret=cache_malloc(sizeof(dns_cent_t))))
goto unlock_return;
if(!init_cent(ret, name, 0, 0, 0 DBG0)) {
pdnsd_free(ret);
goto unlock_return;
}
if(!add_dns_hash(ret,&loc)) {
free_cent(ret DBG0);
pdnsd_free(ret);
goto unlock_return;
}
++ent_num;
}
else {
/* purge the record. */
purge_cent(ret,0,0);
cache_size-=ret->cs;
}
rrs=getrrset(ret,tp);
if (rrs &&
((rrs->flags&CF_NEGATIVE && !(rrs->flags&CF_LOCAL)) ||
(rrs->flags&CF_NOPURGE && timedout(rrs)) ||
(rrs->flags&CF_ADDITIONAL && rrs->serial!=serial) ||
(rrs->serial==serial && rrs->ttl!=(ttl<global.min_ttl?global.min_ttl:(ttl>global.max_ttl?global.max_ttl:ttl))))) {
del_cent_rrset_by_type(ret,tp DBG0);
rrs=NULL;
}
if (rrs==NULL || rrs->serial==serial) {
if (cr_check_add(ret,rrlkuptab[tp-T_MIN],ttl,ts,flags)) {
if (add_cent_rr(ret,tp,ttl,ts,flags,dlen,data,serial DBG0)) {
rr_set_t *rrsnew;
if (!rrs && (rrsnew=getrrset(ret,tp)) && !insert_rrl(rrsnew,ret,rrlkuptab[tp-T_MIN])) {
del_cent_rrset_by_type(ret,tp DBG0);
}
else {
cache_size+=ret->cs;
purge_cent(ret,1,0);
rv=1;
goto unlock_return;
}
}
}
} else {
rv=1;
}
cache_size+=ret->cs;
unlock_return:
unlock_cache_rw();
return rv;
}
#endif
/* Report the cache status to the file descriptor f, for the status fifo (see status.c) */
int report_cache_stat(int f)
{
/* Cache size and entry counters are volatile (and even the entries
in the global struct can change), so make copies to get consistent data.
Even better would be to use locks, but that could be rather costly. */
long csz= cache_size, en= ent_num;
long pc= global.perm_cache;
long mc= pc*1024+MCSZ;
fsprintf_or_return(f,"\nCache status:\n=============\n");
fsprintf_or_return(f,"%ld kB maximum disk cache size.\n",pc);
fsprintf_or_return(f,"%ld of %ld bytes (%.3g%%) memory cache used in %ld entries"
" (avg %.5g bytes/entry).\n",
csz, mc, (((double)csz)/mc)*100, en,
((double)csz)/en);
return 0;
}
#define timestamp2str(ts,now,buf) \
{ \
struct tm tstm; \
if(!((ts) && localtime_r(&(ts), &tstm) && \
strftime(buf, sizeof(buf), \
((ts)<=(now) && (now)-(ts)<365*24*60*60/2)?" %m/%d %T":"%Y/%m/%d %T", \
&tstm)>0)) \
strcpy(buf," "); \
}
/* Dump contents of a cache entry to file descriptor fd.
Returns 1 on success, -1 if there is an IO error.
*/
static int dump_cent(int fd, dns_cent_t *cent)
{
time_t now;
char tstr[sizeof "2000/12/31 23:59:59"],dbuf[1024];
fsprintf_or_return(fd,"%s\n",rhn2str(cent->qname,ucharp dbuf,sizeof(dbuf)));
now=time(NULL);
if(cent->flags&DF_NEGATIVE) {
timestamp2str(cent->neg.ts,now,tstr);
fsprintf_or_return(fd,"%s (domain negated)\n",tstr);
}
else {
int i, n= NRRITERLIST(cent);
const unsigned short *iterlist= RRITERLIST(cent);
for(i=0; i<n; ++i) {
int tp= iterlist[i];
rr_set_t *rrset=getrrset_eff(cent,tp);
if (rrset) {
timestamp2str(rrset->ts,now,tstr);
if(rrset->flags&CF_NEGATIVE) {
fsprintf_or_return(fd,"%s %-7s (negated)\n",tstr,rrnames[tp-T_MIN]);
}
else {
rr_bucket_t *rr;
for(rr=rrset->rrs; rr; rr=rr->next) {
switch (tp) {
case T_CNAME:
case T_MB:
case T_MD:
case T_MF:
case T_MG:
case T_MR:
case T_NS:
case T_PTR:
rhn2str((unsigned char *)(rr->data),ucharp dbuf,sizeof(dbuf));
break;
#if IS_CACHED_MINFO || IS_CACHED_RP
#if IS_CACHED_MINFO
case T_MINFO:
#endif
#if IS_CACHED_RP
case T_RP:
#endif
{
unsigned char *p=(unsigned char *)(rr->data);
int n;
rhn2str(p,ucharp dbuf,sizeof(dbuf));
n=strlen(dbuf);
dbuf[n++] = ' ';
if(n>=sizeof(dbuf))
goto hex_dump;
rhn2str(skiprhn(p),ucharp dbuf+n,sizeof(dbuf)-n);
}
break;
#endif
case T_MX:
#if IS_CACHED_AFSDB
case T_AFSDB:
#endif
#if IS_CACHED_RT
case T_RT:
#endif
#if IS_CACHED_KX
case T_KX:
#endif
{
unsigned char *p=(unsigned char *)(rr->data);
unsigned pref;
int n;
GETINT16(pref,p);
n=sprintf(dbuf,"%u ",pref);
if(n<0) goto hex_dump;
rhn2str(p,ucharp dbuf+n,sizeof(dbuf)-n);
}
break;
case T_SOA:
{
unsigned char *p=(unsigned char *)(rr->data);
char *q;
int n,rem;
uint32_t serial,refresh,retry,expire,minimum;
rhn2str(p,ucharp dbuf,sizeof(dbuf));
n=strlen(dbuf);
dbuf[n++] = ' ';
if(n>=sizeof(dbuf))
goto hex_dump;
q=dbuf+n;
rem=sizeof(dbuf)-n;
p=skiprhn(p);
rhn2str(p,ucharp q,rem);
n=strlen(q);
q[n++] = ' ';
if(n>=rem)
goto hex_dump;
q += n;
rem -= n;
p=skiprhn(p);
GETINT32(serial,p);
GETINT32(refresh,p);
GETINT32(retry,p);
GETINT32(expire,p);
GETINT32(minimum,p);
n=snprintf(q,rem,"%lu %lu %lu %lu %lu",
(unsigned long)serial,(unsigned long)refresh,
(unsigned long)retry,(unsigned long)expire,
(unsigned long)minimum);
if(n<0 || n>=rem)
goto hex_dump;
}
break;
#if IS_CACHED_HINFO || IS_CACHED_TXT || IS_CACHED_SPF
#if IS_CACHED_HINFO
case T_HINFO:
#endif
#if IS_CACHED_TXT
case T_TXT:
#endif
#if IS_CACHED_SPF
case T_SPF:
#endif
{
/* TXT records are not necessarily validated
before they are stored in the cache, so
we need to be careful. */
unsigned char *p=(unsigned char *)(rr->data);
char *q=dbuf;
int j=0,n,rem=sizeof(dbuf);
while(j<rr->rdlen) {
unsigned lb;
if(rem<3)
goto hex_dump;
if(j) {
*q++ = ' ';
--rem;
}
*q++ = '"';
--rem;
lb=*p++;
if((j += lb+1)>rr->rdlen)
goto hex_dump;
n=escapestr(charp p,lb,q,rem);
if(n<0 || n+1>=rem)
goto hex_dump;
q += n;
*q++ = '"';
rem -= n+1;
p += lb;
}
*q=0;
}
break;
#endif
#if IS_CACHED_PX
case T_PX:
{
unsigned char *p=(unsigned char *)(rr->data);
char *q;
unsigned pref;
int n,rem;
GETINT16(pref,p);
n=sprintf(dbuf,"%u ",pref);
if(n<0) goto hex_dump;
q=dbuf+n;
rem=sizeof(dbuf)-n;
rhn2str(p,ucharp q,rem);
n=strlen(q);
q[n++] = ' ';
if(n>=rem)
goto hex_dump;
rhn2str(skiprhn(p),ucharp q+n,rem-n);
}
break;
#endif
#if IS_CACHED_SRV
case T_SRV:
{
unsigned char *p=(unsigned char *)(rr->data);
unsigned priority,weight,port;
int n;
GETINT16(priority,p);
GETINT16(weight,p);
GETINT16(port,p);
n=sprintf(dbuf,"%u %u %u ",priority,weight,port);
if(n<0) goto hex_dump;
rhn2str(p,ucharp dbuf+n,sizeof(dbuf)-n);
}
break;
#endif
#if IS_CACHED_NXT
case T_NXT:
{
unsigned char *p=(unsigned char *)(rr->data);
int n,rlen;
rhn2str(p,ucharp dbuf,sizeof(dbuf));
n=strlen(dbuf);
dbuf[n++] = ' ';
if(n>=sizeof(dbuf))
goto hex_dump;
rlen=rhnlen(p);
hexdump(p+rlen,rr->rdlen-rlen,dbuf+n,sizeof(dbuf)-n);
}
break;
#endif
#if IS_CACHED_NAPTR
case T_NAPTR:
{
unsigned char *p=(unsigned char *)(rr->data);
char *q;
unsigned order,pref;
int n,rem,j;
GETINT16(order,p);
GETINT16(pref,p);
n=sprintf(dbuf,"%u %u ",order,pref);
if(n<0) goto hex_dump;
q=dbuf+n;
rem=sizeof(dbuf)-n;
for (j=0;j<3;++j) {
unsigned lb;
if(rem<2)
goto hex_dump;
*q++ = '"';
--rem;
lb=*p++;
n=escapestr(charp p,lb,q,rem);
if(n<0 || n+2>=rem)
goto hex_dump;
q += n;
*q++ = '"';
*q++ = ' ';
rem -= n+2;
p += lb;
}
rhn2str(p,ucharp q,rem);
}
break;
#endif
#if IS_CACHED_LOC
case T_LOC:
/* Binary data length has not necessarily been validated */
if(rr->rdlen!=16)
goto hex_dump;
if(!loc2str(rr->data,dbuf,sizeof(dbuf)))
goto hex_dump;
break;
#endif
case T_A:
if (!inet_ntop(AF_INET,rr->data,dbuf,sizeof(dbuf)))
goto hex_dump;
break;
#if IS_CACHED_AAAA && defined(AF_INET6)
case T_AAAA:
if (!inet_ntop(AF_INET6,rr->data,dbuf,sizeof(dbuf)))
goto hex_dump;
break;
#endif
default:
hex_dump:
hexdump(rr->data,rr->rdlen,dbuf,sizeof(dbuf));
}
fsprintf_or_return(fd,"%s %-7s %s\n",tstr,rrnames[tp-T_MIN],dbuf);
}
}
}
}
}
fsprintf_or_return(fd,"\n");
return 1;
}
/* Dump cache contents to file descriptor fd.
If name is not null, restricts information to that name,
otherwise dumps information about all names found in the cache.
Returns 1 on success, 0 if the name is not found, -1 is there is an IO error.
Mainly for debugging purposes.
*/
int dump_cache(int fd, const unsigned char *name, int exact)
{
int rv=0;
lock_cache_r();
if(name && exact) {
dns_cent_t *cent=dns_lookup(name,NULL);
if(cent)
rv=dump_cent(fd,cent);
}
else {
dns_cent_t *cent;
dns_hash_pos_t pos;
for (cent=fetch_first(&pos); cent; cent=fetch_next(&pos)) {
unsigned int nrem;
if(!name || (domain_match(name,cent->qname,&nrem,NULL),nrem==0))
if((rv=dump_cent(fd,cent))<0)
break;
}
}
unlock_cache_r();
return rv;
}
char *stpcpy(char *dest, char const *src)
{
size_t src_len = strlen(src);
return memcpy(dest, src, src_len) + src_len;
// strcpy(dest, src);
// return dest + strlen(dest);
}
#if DEBUG>0
/* Added by Paul Rombouts: This is only used in debug messages. */
const char cflgnames[NCFLAGS*3]={'N','E','G','L','O','C','A','U','T','N','O','C','A','D','D','N','O','P','R','T','S'};
const char dflgnames[NDFLAGS*3]={'N','E','G','L','O','C','A','U','T','N','O','C','W','L','D'};
char *flags2str(unsigned flags,char *buf,int nflags,const char *flgnames)
{
char *p=buf;
int i,nflgchars=3*nflags;
for(i=0;i<nflgchars;i+=3) {
if(flags&1) {
if(p>buf) *p++='|';
p=mempcpy(p,&flgnames[i],3);
}
flags >>= 1;
}
if(p==buf)
*p++='0';
*p=0;
return buf;
}
#endif