/* ARISA - Asynchronous DNS hooks and caching * Copyright (C) 2003, 2004 Carl Ritson * * This program 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. * * This program 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 */ #include "arisa.h" #include #include #include #include #include #include /* Defines */ #define TABLE_INIT_SIZE 16 #define TABLE_OPTIMAL_DEPTH 4 #define MAX_MEMORY_USAGE 65536 #define RESOLV_CONF "/etc/resolv.conf" #define HOSTS_FILE "/etc/hosts" #define DNS_PORT 53 #define LOWEST_TTL (2*24*60*60) #define ERROR_TTL (5*60) #define MAX_ATTEMPTS 5 #define RETRY_INTERVAL 3 /* Enums */ enum { NS_T_A = 1, NS_T_CNAME = 5, NS_T_PTR = 12, NS_T_AAAA = 28, NS_C_IN = 1 }; /* Internal Structures */ typedef struct rr_t { char *name; long ttl; int type; int class; union { char *name; struct in_addr addr; } data; } rr_t; typedef struct dnsq_t { int id; int err; rr_t **questions; int no_questions; rr_t **answers; int no_answers; } dnsq_t; /* NOTE: Locking convention is that resolv_lock and lookup_lock may only be held together if resolv_lock is obtained first. */ /** hash_host * Convert a hostname to unsigned long hash. */ static unsigned long hash_host(const char *host) { unsigned long hash = 1; int i; assert(host != NULL); for(i = 0; host[i] != '\0'; ++i) hash *= tolower(host[i]) * 37UL; return hash; } /** hash_addr * Convert a in_addr structure to an unsigned long hash. */ static unsigned long hash_addr(const struct in_addr *addr) { unsigned char *caddr = (unsigned char *)addr; unsigned long hash = 1; int i; assert(addr != NULL); //D("hash_addr: %p",caddr); for(i = 0; i < sizeof(struct in_addr); ++i) hash = hash * 37UL + (unsigned long) caddr[i]; return hash; } /** rehash_servers * Loads the servers from the file specified by RESOLV_CONF. */ static void rehash_servers(aresolv_t *ar) { struct sockaddr_in addr, *paddr; struct stat sbuf; char buffer[256]; char **servers = NULL;; int no_servers = 0; time_t tmp; FILE *fh; int i, ret; LOCKR(&ar->resolv_lock); tmp = ar->last_s_rehash; UNLOCKR(&ar->resolv_lock); ret = stat(RESOLV_CONF,&sbuf); //D("ret: %d, stat: %ld, tmp: %ld",ret,sbuf.st_mtime,tmp); if(ret == -1 || sbuf.st_mtime <= tmp) return; fh = fopen(RESOLV_CONF,"r"); if(fh == NULL) return; while(readtline_file(fh,buffer,sizeof(buffer)) != NULL) { if(strncasecmp(buffer,"nameserver",10) == 0) { i = nextchar(buffer,10); PTRARR_ADD(&servers,&no_servers,xstrdup(buffer+i)); } } fclose(fh); LOCKR(&ar->resolv_lock); for(i = 0; i < ar->no_servers; ++i) xfree(ar->servers[i]); if(ar->servers != NULL) xfree(ar->servers); ar->servers = NULL; ar->no_servers = 0; addr.sin_family = AF_INET; addr.sin_port = htons(DNS_PORT); for(i = 0; i < no_servers; ++i) { ret = inet_pton(AF_INET,servers[i],&(addr.sin_addr)); if(ret > 0) { paddr = xalloc(sizeof(struct sockaddr_in)); memcpy(paddr,&addr,sizeof(addr)); PTRARR_ADD(&(ar->servers),&(ar->no_servers),paddr); //D("nameserver: %s",servers[i]); } xfree(servers[i]); } if(servers != NULL) xfree(servers); if(ar->next_server >= ar->no_servers) ar->next_server = 0; ar->last_s_rehash = sbuf.st_mtime; UNLOCKR(&ar->resolv_lock); } /** rehash_hosts * Reads the file specified in HOSTS_FILE and installs it the hosts it * contains staticly into the dns cache. Removing any static hosts not * in the file. */ /* Pre-declarations needed for rehash_hosts. */ static arb_t *alloc_bucket(unsigned long hash, arbtype_t type); static int compar_host(const arb_t *bkt, const void *host); static int compar_addr(const arb_t *bkt, const void *addr); static arb_t *find_bucket(arb_t **table, int table_size, int (*compar)(const arb_t *, const void *), unsigned long hash, const void *data); static void place_bucket(arb_t **table, int table_size, arb_t *bkt); static void do_expires(aresolv_t *ar, arb_t **table, int table_size, int *table_bkts, time_t now, int inverse); static void rehash_hosts(aresolv_t *ar) { struct { struct in_addr addr; char *name; } _host_t, *h = NULL, **hosts = NULL; struct in_addr addr; struct stat sbuf; char buffer[256], addrbuf[64]; arb_t *bkt; int no_hosts = 0; time_t tmp; FILE *fh; int i, j, ret; LOCKR(&ar->resolv_lock); tmp = ar->last_h_rehash; UNLOCKR(&ar->resolv_lock); ret = stat(HOSTS_FILE,&sbuf); //D("ret: %d, stat: %ld, tmp: %ld",ret,sbuf.st_mtime,tmp); if(ret == -1 || sbuf.st_mtime <= tmp) return; fh = fopen(HOSTS_FILE,"r"); if(fh == NULL) return; while(readtline_file(fh,buffer,sizeof(buffer)) != NULL) { xstrncpy(addrbuf,buffer,17); i = nextspace(addrbuf,0); addrbuf[i] = '\0'; ret = inet_pton(AF_INET,addrbuf,&addr); //D("ret: %d, addrbuf: %s",ret,addrbuf); if(ret > 0) { h = xalloc(sizeof(_host_t)); memcpy(&(h->addr),&addr,sizeof(struct in_addr)); i = nextchar(buffer,strlen(addrbuf)); j = nextspace(buffer,i); h->name = xalloc((j - i) + 1); memcpy(h->name,buffer + i,(j - i)); h->name[(j - i)] = '\0'; /* scan for duplicates */ for(i = 0, ret = 0; i < no_hosts && !ret; ++i) { if(strcasecmp(hosts[i]->name,h->name) == 0 && memcmp(&(hosts[i]->addr),&(h->addr),sizeof(struct in_addr)) == 0) ret = 1; } if(!ret) { //D("h->name = %s",h->name); PTRARR_ADD(&hosts,&no_hosts,h); } else { xfree(h->name); xfree(h); } } } fclose(fh); LOCKR(&ar->lookup_lock); /* Find all buckets matching those we just read in from the hosts file and wipe their contents. */ for(i = 0; i < no_hosts; ++i) { h = hosts[i]; bkt = find_bucket(ar->fwd_table,ar->fwd_table_size, compar_host,hash_host(h->name),h->name); if(bkt != NULL) { if(bkt->addr != NULL) { ar->memory_usage -= sizeof(struct in_addr) * bkt->no_addr; xfree(bkt->addr); bkt->addr = NULL; bkt->no_addr = 0; } bkt->type = ARESOLV_FWD; bkt->expires = 1; } bkt = find_bucket(ar->rev_table,ar->rev_table_size, compar_addr,hash_addr(&(h->addr)),&(h->addr)); if(bkt != NULL) { if(bkt->host != NULL) { for(j = 0; j < bkt->no_host; ++j) { ar->memory_usage -= strlen(bkt->host[j]) + 1 + sizeof(char *); xfree(bkt->host[j]); } xfree(bkt->host); bkt->host = 0; bkt->no_host = 0; } bkt->type = ARESOLV_REV; bkt->expires = 1; } } /* Mark any other static buckets for immediate expiry. */ for(i = 0; i < ar->fwd_table_size; ++i) { bkt = ar->fwd_table[i]; while(bkt != NULL) { if(bkt->expires == 0 && (bkt->type & ARESOLV_INPROGRESS) == 0) bkt->expires = 1; bkt = bkt->next; } } for(i = 0; i < ar->rev_table_size; ++i) { bkt = ar->rev_table[i]; while(bkt != NULL) { if(bkt->expires == 0 && (bkt->type & ARESOLV_INPROGRESS) == 0) bkt->expires = 1; bkt = bkt->next; } } /* Now afix the data from the hosts file to the existing buckets, or if they don't exist create new ones. */ for(i = 0; i < no_hosts; ++i) { h = hosts[i]; /* Forward Table */ bkt = find_bucket(ar->fwd_table,ar->fwd_table_size, compar_host,hash_host(h->name),h->name); if(bkt == NULL) { bkt = alloc_bucket(hash_host(h->name),ARESOLV_FWD); bkt->host = xalloc(sizeof(char *)); bkt->host[0] = xstrdup(h->name); bkt->no_host = 1; ar->memory_usage += sizeof(arb_t) + strlen(h->name) + 1 + sizeof(char *); ar->fwd_table_bkts++; place_bucket(ar->fwd_table,ar->fwd_table_size,bkt); } bkt->no_addr++; bkt->addr = xreloc(bkt->addr,sizeof(struct in_addr) * bkt->no_addr); memcpy(bkt->addr + (bkt->no_addr - 1),&(h->addr), sizeof(struct in_addr)); ar->memory_usage += sizeof(struct in_addr); bkt->expires = 0; /* Reverse Table */ bkt = find_bucket(ar->rev_table,ar->rev_table_size, compar_addr,hash_addr(&(h->addr)),&(h->addr)); if(bkt == NULL) { bkt = alloc_bucket(hash_addr(&(h->addr)),ARESOLV_REV); bkt->addr = xalloc(sizeof(struct in_addr)); memcpy(bkt->addr,&(h->addr),sizeof(struct in_addr)); bkt->no_addr = 1; ar->memory_usage += sizeof(arb_t) + sizeof(struct in_addr); ar->rev_table_bkts++; place_bucket(ar->rev_table,ar->rev_table_size,bkt); } //D("host:%s addr hash: %lu",h->name,bkt->hash); PTRARR_ADD(&(bkt->host),&(bkt->no_host),xstrdup(h->name)); ar->memory_usage += strlen(h->name) + 1 + sizeof(char *); bkt->expires = 0; xfree(h->name); xfree(h); } if(hosts != NULL) xfree(hosts); /* expire all the old static buckets */ tmp = xtime(); do_expires(ar,ar->fwd_table,ar->fwd_table_size,&(ar->fwd_table_bkts), tmp,0); do_expires(ar,ar->rev_table,ar->rev_table_size,&(ar->rev_table_bkts), tmp,0); UNLOCKR(&ar->lookup_lock); LOCKR(&ar->resolv_lock); ar->last_h_rehash = sbuf.st_mtime; UNLOCKR(&ar->resolv_lock); } /** alloc_bucket * allocate and initialise a aresolv hash bucket. */ static arb_t *alloc_bucket(unsigned long hash, arbtype_t type) { arb_t *bkt = xalloc(sizeof(arb_t)); bkt->hash = hash; bkt->type = type; bkt->next = NULL; bkt->expires = 0; bkt->host = NULL; bkt->no_host = 0; bkt->addr = NULL; bkt->no_addr = 0; return bkt; } /** free_bucket * de-allocates an aresolv hash bucket. */ static size_t free_bucket(arb_t *bkt) { size_t bytes = 0; int i; assert(bkt != NULL); if((bkt->type & ARESOLV_FWD) == ARESOLV_FWD) { for(i = 0; i < bkt->no_host; ++i) { bytes += strlen(bkt->host[i]) + sizeof(char *) + 1; xfree(bkt->host[i]); } if(bkt->host != NULL) xfree(bkt->host); } else if((bkt->type & ARESOLV_REV) == ARESOLV_REV) { if(bkt->no_addr > 0) { bytes += bkt->no_addr * sizeof(struct in_addr); xfree(bkt->addr); } } bytes += sizeof(arb_t); xfree(bkt); return bytes; } /** alloc_lookup * allocates a structure to hold a lookup */ static arl_t *alloc_lookup(unsigned long hash, int type, arb_t *bkt) { arl_t *l = xalloc(sizeof(arl_t)); l->hash = hash; l->type = type; l->bkt = bkt; l->last_attempt = 0; l->attempts = 0; l->ns_type = 0; l->present = NULL; l->lowest_ttl = LOWEST_TTL; return l; } /** free_lookup * deallocates a lookup structure */ static void free_lookup(arl_t *l) { if(l->present != NULL) xfree(l->present); xfree(l); } static rr_t *alloc_rr(void) { rr_t *rr = xalloc(sizeof(rr_t)); rr->name = NULL; rr->ttl = 0; rr->type = 0; rr->class = 0; rr->data.name = NULL; return rr; } static void free_rr(rr_t *rr) { if(rr->name != NULL) xfree(rr->name); if(rr->type == NS_T_CNAME || rr->type == NS_T_PTR) { if(rr->data.name != NULL) xfree(rr->data.name); } xfree(rr); } static dnsq_t *alloc_dnsq(void) { dnsq_t *q = xalloc(sizeof(dnsq_t)); q->id = 0; q->err = 0; q->questions = NULL; q->no_questions = 0; q->answers = NULL; q->no_answers = 0; return q; } static void free_dnsq(dnsq_t *q) { int i; for(i = 0; i < q->no_questions; ++i) free_rr(q->questions[i]); if(q->questions != NULL) xfree(q->questions); for(i = 0; i < q->no_answers; ++i) free_rr(q->answers[i]); if(q->answers != NULL) xfree(q->answers); xfree(q); } /** aresolv_init * Allocates and initialises an aresolv'er. * It contains a forward and reverse caches and adns structure. */ aresolv_t *aresolv_init(void) { aresolv_t *ar = xalloc(sizeof(aresolv_t)); int i; assert(ar != NULL); LOCK_INIT(&ar->lookup_lock); ar->fwd_table = xalloc(sizeof(arb_t *)*TABLE_INIT_SIZE); ar->rev_table = xalloc(sizeof(arb_t *)*TABLE_INIT_SIZE); ar->fwd_table_size = TABLE_INIT_SIZE; ar->fwd_table_bkts = 0; ar->rev_table_size = TABLE_INIT_SIZE; ar->rev_table_bkts = 0; for(i = 0; i < TABLE_INIT_SIZE; ++i) { ar->fwd_table[i] = NULL; ar->rev_table[i] = NULL; } ar->memory_usage = sizeof(arb_t *) * TABLE_INIT_SIZE * 2; LOCK_INIT(&ar->resolv_lock); ar->socket = socket(PF_INET,SOCK_DGRAM,17); if(ar->socket == -1) E(errno); i = fcntl(ar->socket,F_SETFL,O_NONBLOCK); if(i == -1) E(errno); ar->next_server = 0; ar->servers = NULL; ar->no_servers = 0; ar->lookups = NULL; ar->no_lookups = 0; ar->last_s_rehash = 0; ar->last_h_rehash = 0; rehash_servers(ar); rehash_hosts(ar); return ar; } /** aresolv_free * De-allocates a aresolv'er. */ void aresolv_free(aresolv_t *ar) { arb_t *pbkt,*nbkt; int i; assert(ar != NULL); LOCKR(&ar->resolv_lock); LOCKR(&ar->lookup_lock); for(i = 0; i < ar->fwd_table_size; ++i) { nbkt = ar->fwd_table[i]; while(nbkt != NULL) { pbkt = nbkt; nbkt = pbkt->next; free_bucket(pbkt); } } xfree(ar->fwd_table); for(i = 0; i < ar->rev_table_size; ++i) { nbkt = ar->rev_table[i]; while(nbkt != NULL) { pbkt = nbkt; nbkt = pbkt->next; free_bucket(pbkt); } } xfree(ar->rev_table); close(ar->socket); for(i = 0; i < ar->no_servers; ++i) xfree(ar->servers[i]); if(ar->servers != NULL) xfree(ar->servers); for(i = 0; i < ar->no_lookups; ++i) free_lookup(ar->lookups[i]); if(ar->lookups != NULL) xfree(ar->lookups); UNLOCKR(&ar->lookup_lock); UNLOCKR(&ar->resolv_lock); LOCK_FREE(&ar->lookup_lock); LOCK_FREE(&ar->resolv_lock); xfree(ar); } /** place_bucket * Places a aresolv hash bucket into an aresolv hash table. */ static void place_bucket(arb_t **table, int table_size, arb_t *bkt) { unsigned long place; arb_t *pbkt; assert(table != NULL); assert(bkt != NULL); place = bkt->hash % table_size; if(table[place] == NULL) table[place] = bkt; else { pbkt = table[place]; while(pbkt->next != NULL) pbkt = pbkt->next; pbkt->next = bkt; } bkt->next = NULL; } /** has_expired * Tests an expiry time against the present time, returns 1 if it has * expired, 0 if not. Inverse invertes the return value. * This function implements pinning of entries in the caches. */ static int has_expired(time_t expires, time_t now, int inverse) { if(expires == 0) return 0; /* pin entries */ else if(!inverse && expires <= now) return 1; else if(inverse && expires >= now) return 1; else return 0; } /** do_expires * Walks an aresolv hash table and tests each entry with has_expired, * if the entry/bucket has expired it is removed. * The number of bytes of memory freed are returned. */ static void do_expires(aresolv_t *ar, arb_t **table, int table_size, int *table_bkts, time_t now, int inverse) { size_t freed = 0; arb_t *pbkt,*nbkt,*tmp; int i; assert(table != NULL); assert(table_bkts != NULL); for(i = 0; i < table_size; ++i) { pbkt = NULL; nbkt = table[i]; while(nbkt != NULL) { if(has_expired(nbkt->expires,now,inverse)) { if(pbkt == NULL) table[i] = nbkt->next; else pbkt->next = nbkt->next; tmp = nbkt; nbkt = nbkt->next; freed += free_bucket(tmp); (*table_bkts)--; } else { pbkt = nbkt; nbkt = nbkt->next; } } } ar->memory_usage -= freed; } /** compar_host * Compares the host in an aresolv bucket against a host in a NULL and * type safe way. Returns 1 if they match (case insensitively). */ static int compar_host(const arb_t *bkt, const void *host) { assert(bkt != NULL); if(host == NULL || bkt->host == NULL || (bkt->type & ARESOLV_FWD) != ARESOLV_FWD) return 0; if(strcasecmp(bkt->host[0],(char *)host) == 0) return 1; else return 0; } /** compar_addr * Compares the addr in an aresolv bucket against another address in a * NULL and type safe way. Returns 1 if they match. */ static int compar_addr(const arb_t *bkt, const void *addr) { assert(bkt != NULL); if(addr == NULL || bkt->addr == NULL || (bkt->type & ARESOLV_REV) != ARESOLV_REV) return 0; if(memcmp(bkt->addr,addr,sizeof(struct in_addr)) == 0) return 1; else return 0; } /** find_bucket * Locates a bucket within a aresolv hash, using the comparison * function passed to it. Returns the bucket if found, else NULL. */ static arb_t *find_bucket(arb_t **table, int table_size, int (*compar)(const arb_t *, const void *), unsigned long hash, const void *data) { unsigned long place = hash % table_size; arb_t *bkt; assert(table != NULL); assert(compar != NULL); bkt = table[place]; while(bkt != NULL) { if(compar(bkt,data)) break; else bkt = bkt->next; } return bkt; } static int encode_dn(unsigned char *buffer, char *name) { int spos = 0, dpos = 0; while(name[spos] != '\0') { buffer[dpos] = 0; while( name[spos+buffer[dpos]] != '.' && name[spos+buffer[dpos]] != '\0') buffer[dpos]++; if(buffer[dpos] > 0) { memcpy(buffer + dpos + 1,name + spos,buffer[dpos]); spos += buffer[dpos]; if(name[spos] == '.') spos++; } dpos += buffer[dpos] + 1; } buffer[dpos] = 0; return dpos + 1; } static int build_query(arl_t *lu, unsigned char *buffer, size_t bufsize) { uint16_t *sp; int pos = 0; if((strlen(lu->present) + 1 + (sizeof(uint16_t) * 6)) > bufsize) return -1; sp = (uint16_t *)buffer; sp[0] = htons(lu->hash); // id field sp[1] = htons(0x0100); // recursion requested sp[2] = htons(1); // one question sp[3] = htons(0); // no answers sp[4] = htons(0); // no name servers sp[5] = htons(0); // no additional resources pos += sizeof(uint16_t) * 6; pos += encode_dn(buffer + pos, lu->present); sp = (uint16_t *)(buffer + pos); sp[0] = htons((uint16_t)lu->ns_type); sp[1] = htons((uint16_t)NS_C_IN); pos += sizeof(uint16_t) * 2; return pos; } static int send_query(aresolv_t *ar, unsigned char *query, size_t query_len) { int ret; if(ar->no_servers == 0) return -1; ret = sendto(ar->socket, query, query_len, 0, (struct sockaddr *)ar->servers[ar->next_server], sizeof(struct sockaddr_in)); ar->next_server++; if(ar->next_server >= ar->no_servers) ar->next_server = 0; //D("ret: %d",ret); return ret; } static int make_query(aresolv_t *ar, arl_t *lu) { unsigned char buffer[512]; int ret; assert(ar != NULL); assert(lu != NULL); assert(lu->ns_type == NS_T_A || lu->ns_type == NS_T_PTR); assert(lu->present != NULL); ret = build_query(lu,buffer,sizeof(buffer)); //D("ret: %d",ret); if(ret != -1) ret = send_query(ar,buffer,ret); lu->last_attempt = xtime(); lu->attempts++; return ret; } static void start_query(aresolv_t *ar, arl_t *lu) { assert(ar != NULL); assert(lu != NULL); assert(lu->ns_type == NS_T_A || lu->ns_type == NS_T_PTR); assert(lu->present != NULL); //D(""); LOCKR(&(ar->resolv_lock)); PTRARR_ADD(&(ar->lookups),&(ar->no_lookups),lu); make_query(ar,lu); UNLOCKR(&(ar->resolv_lock)); } static void start_query_fwd(aresolv_t *ar, unsigned long hash, arb_t *bkt, const char *host) { arl_t *l = alloc_lookup(hash,ARESOLV_FWD,bkt); l->present = xstrdup(host); l->ns_type = NS_T_A; start_query(ar,l); } static void start_query_rev(aresolv_t *ar, unsigned long hash, arb_t *bkt, const struct in_addr *addr) { char buffer[32]; arl_t *l = alloc_lookup(hash,ARESOLV_REV,bkt); snprintf(buffer,sizeof(buffer),"%u.%u.%u.%u.in-addr.arpa", ((unsigned char *)addr)[3], ((unsigned char *)addr)[2], ((unsigned char *)addr)[1], ((unsigned char *)addr)[0] ); l->present = xstrdup(buffer); l->ns_type = NS_T_PTR; start_query(ar,l); } static int decode_dn(unsigned char *query, size_t len, int off, char *buffer, size_t bufsize) { int i, pl, spos = off, dpos = 0; int ret = 0, jumped = 0; do { if(spos >= len) return -1; pl = query[spos]; if((pl & 0xc0) == 0) { if(spos + pl > len) return -1; if(dpos + pl > (bufsize-2)) return -1; if(pl != 0 && dpos != 0) buffer[dpos++] = '.'; spos++; for(i = 0; i < pl; ++i) buffer[dpos++] = (char) query[spos++]; if(!jumped) ret += pl + 1; } else if((pl & 0xc0) == 0xc0) { if(spos + 2 > len) return -1; if(!jumped) ret += 2; jumped++; spos = (ntohs(*(uint16_t *)(query + spos))) & 0x3fff; } else return -1; if(jumped > (len/2) || dpos > len) // anti-loop return -1; } while(pl != 0); buffer[dpos] = '\0'; return ret; } static int load_question(unsigned char *query, size_t query_len, int pos, rr_t **ret_ptr) { uint16_t *sp; char name[256]; int ret, start = pos; ret = decode_dn(query, query_len, pos, name, sizeof(name)); if(ret == -1) return -1; pos += ret; if((pos + (sizeof(uint16_t) * 2)) > query_len) return -1; (*ret_ptr) = alloc_rr(); sp = (uint16_t *)(query + pos); pos += sizeof(uint16_t) * 2; (*ret_ptr)->name = xstrdup(name); (*ret_ptr)->type = ntohs(sp[0]); (*ret_ptr)->class = ntohs(sp[1]); return (pos - start); } static int load_answer(unsigned char *query, size_t query_len, int pos, rr_t **ret_ptr) { uint16_t *sp, rdlength; int32_t *lp; char name[256]; int ret, start = pos; ret = decode_dn(query, query_len, pos, name, sizeof(name)); if(ret == -1) return -1; pos += ret; if((pos + (sizeof(uint16_t) * 3) + sizeof(int32_t)) > query_len) return -1; sp = (uint16_t *)(query + pos); pos += sizeof(uint16_t) * 2; lp = (int32_t *)(query + pos); pos += sizeof(int32_t) + sizeof(uint16_t); (*ret_ptr) = alloc_rr(); (*ret_ptr)->name = xstrdup(name); (*ret_ptr)->type = ntohs(sp[0]); (*ret_ptr)->class = ntohs(sp[1]); (*ret_ptr)->ttl = ntohl(lp[0]); rdlength = ntohs(sp[4]); if((pos + rdlength) > query_len) { free_rr((*ret_ptr)); return -1; } switch((*ret_ptr)->type) { case NS_T_A: memcpy(&((*ret_ptr)->data.addr),query+pos, IMIN(rdlength,sizeof(struct in_addr))); break; case NS_T_CNAME: case NS_T_PTR: ret = decode_dn(query, query_len, pos, name, sizeof(name)); if(ret == -1) { free_rr((*ret_ptr)); return -1; } //D("name: %s",name); (*ret_ptr)->data.name = xstrdup(name); break; default: free_rr((*ret_ptr)); return -1; } return (pos - start) + rdlength; } static int recv_query(aresolv_t *ar, dnsq_t **ret_ptr) { unsigned char buffer[512]; struct sockaddr_in addr; uint16_t *sp; socklen_t addrlen = sizeof(addr); dnsq_t *q = NULL; size_t len; rr_t *rr; int i,ret,pos; ret = recvfrom(ar->socket,buffer,sizeof(buffer),0, (struct sockaddr *)&addr,&addrlen); //D("ret: %d",ret); if(ret <= 0) return 0; len = ret; /* check that it came from one of the servers we know */ for(i = 0, ret = -1; i < ar->no_servers && ret == -1; ++i) { if(memcmp(&(addr.sin_addr),&((struct sockaddr_in *)ar->servers[i])->sin_addr, sizeof(struct in_addr)) == 0) ret = 0; } if(ret == -1) return -1; //D(""); /* check it is at least the size of a header */ if(len < (sizeof(uint16_t) * 6)) return -1; //D(""); sp = (uint16_t *)buffer; for(i = 0; i < 6; ++i) sp[i] = ntohs(sp[i]); pos = (sizeof(uint16_t) * 6); q = alloc_dnsq(); q->id = sp[0]; q->err = sp[1] & 0x000f; /* load questions */ for(i = 0; i < sp[2] && pos < len; ++i, pos += ret) { ret = load_question(buffer,len,pos,&rr); //D("ret: %d",ret); if(ret == -1) { free_dnsq(q); return -1; } else PTRARR_ADD(&q->questions,&q->no_questions,rr); } /* load answers */ for(i = 0; i < sp[3] && pos < len; ++i, pos += ret) { ret = load_answer(buffer,len,pos,&rr); //D("ret: %d",ret); if(ret == -1) { free_dnsq(q); return -1; } else PTRARR_ADD(&q->answers,&q->no_answers,rr); } (*ret_ptr) = q; return 1; } static int contains_answer(arl_t *lu, dnsq_t *query) { char *present = lu->present; int i; //D("present: %s",present); for(i = 0; i < query->no_answers; ++i) { rr_t *ans = query->answers[i]; //D("ans->type = %d, ans->name = %s",ans->type,ans->name); if(ans->type == NS_T_CNAME && strcasecmp(ans->name,present) == 0) present = ans->data.name; else if((ans->type == NS_T_A && lu->type == ARESOLV_FWD) && strcasecmp(ans->name,present) == 0) return 1; else if((ans->type == NS_T_PTR && lu->type == ARESOLV_REV) && strcasecmp(ans->name,present) == 0) return 1; } return 0; } static int contains_cname(arl_t *lu, dnsq_t *query) { char *present = lu->present; int i; for(i = 0; i < query->no_answers; ++i) { rr_t *ans = query->answers[i]; if(ans->type == NS_T_CNAME && strcasecmp(ans->name,present) == 0) return 1; } return 0; } static inline void lowest_ttl(arl_t *lu, time_t ttl) { if(ttl < lu->lowest_ttl) lu->lowest_ttl = ttl; } static void follow_cname(aresolv_t *ar, arl_t *lu, dnsq_t *query) { char *present = lu->present; int i; for(i = 0; i < query->no_answers; ++i) { rr_t *ans = query->answers[i]; if(ans->type == NS_T_CNAME && strcasecmp(ans->name,present) == 0) { lowest_ttl(lu,ans->ttl); present = ans->data.name; } } xfree(lu->present); lu->present = xstrdup(present); make_query(ar,lu); } static void copy_answers(aresolv_t *ar, arl_t *lu, dnsq_t *query) { arb_t *bkt = lu->bkt; char *present; int i,count; /* resolve all the contained CNAMEs */ for(i = 0, present = lu->present; i < query->no_answers; ++i) { if(query->answers[i]->type == NS_T_CNAME && strcasecmp(query->answers[i]->name,present) == 0) { lowest_ttl(lu,query->answers[i]->ttl); present = query->answers[i]->data.name; } } if(lu->type == ARESOLV_FWD) { for(i = 0, count = 0; i < query->no_answers; ++i) { if(query->answers[i]->type == NS_T_A && strcasecmp(query->answers[i]->name,present) == 0) count++; } bkt->no_addr = count; bkt->addr = xalloc( sizeof(struct in_addr) * bkt->no_addr ); ar->memory_usage += sizeof(struct in_addr) * bkt->no_addr; for(i = 0, count = 0; i < query->no_answers && count < bkt->no_addr; ++i) { if(query->answers[i]->type == NS_T_A && strcasecmp(query->answers[i]->name,present) == 0) { lowest_ttl(lu,query->answers[i]->ttl); memcpy( &(bkt->addr[count++]), &(query->answers[i]->data.addr), sizeof(struct in_addr) ); } } } else if(lu->type == ARESOLV_REV) { for(i = 0, count = 0; i < query->no_answers; ++i) { if(query->answers[i]->type == NS_T_PTR && strcasecmp(query->answers[i]->name,present) == 0) count++; } //D("count: %d",count); bkt->no_host = count; bkt->host = xalloc(sizeof(char *)*bkt->no_host); ar->memory_usage += sizeof(char *)*bkt->no_host; for(i = 0, count = 0; i < query->no_answers && count < bkt->no_host; ++i) { if(query->answers[i]->type == NS_T_PTR && strcasecmp(query->answers[i]->name,present) == 0) { lowest_ttl(lu,query->answers[i]->ttl); bkt->host[i] = xstrdup(query->answers[i]->data.name); ar->memory_usage += strlen(query->answers[i]->data.name) + 1; } } } else { /* we shouldn't be here */ assert(0); } } static arl_t *find_lookup(aresolv_t *ar, dnsq_t *query) { arl_t *lu; int i,j; for(i = 0, lu = NULL; i < ar->no_lookups && lu == NULL; ++i) { if(query->id == (unsigned short)ar->lookups[i]->hash) { if(query->no_questions > 0) { for(j = 0; j < query->no_questions && lu == NULL; ++j) { if(query->questions[j]->type == ar->lookups[i]->ns_type) { if(strcasecmp(query->questions[j]->name, ar->lookups[i]->present) == 0) lu = ar->lookups[i]; } } } else lu = ar->lookups[i]; } } //D("lu: %p",lu); return lu; } /** check_lookups * Walks the list of pending queries on the aresolver, and * assigns the results of any completed queries to the appropriate * bucket in the resolution cache. */ static void check_lookups(aresolv_t *ar) { dnsq_t *query; arl_t *lu; int i,ret; time_t now = xtime(); assert(ar != NULL); //D("entry"); while((ret = recv_query(ar,&query)) != 0) { if(ret == -1) continue; //D(""); LOCKR(&ar->resolv_lock); lu = find_lookup(ar,query); if(lu != NULL) { LOCKR(&ar->lookup_lock); if(query->err != 0) { //D("query->err = %d",query->err); lu->lowest_ttl = ERROR_TTL; } else { if(contains_answer(lu,query)) { copy_answers(ar,lu,query); } else if(contains_cname(lu,query) && lu->attempts < MAX_ATTEMPTS) { follow_cname(ar,lu,query); lu = NULL; } } if(lu != NULL) { lu->bkt->type &= ~ARESOLV_INPROGRESS; lu->bkt->expires = now + lu->lowest_ttl; free_lookup(lu); PTRARR_DEL(&ar->lookups,&ar->no_lookups,lu); } UNLOCKR(&ar->lookup_lock); } UNLOCKR(&ar->resolv_lock); free_dnsq(query); } LOCKR(&ar->resolv_lock); for(i = 0; i < ar->no_lookups; ++i) { lu = ar->lookups[i]; if((lu->last_attempt + RETRY_INTERVAL) <= now) { if(lu->attempts < MAX_ATTEMPTS) make_query(ar,lu); else { LOCKR(&ar->lookup_lock); lu->bkt->type &= ~ARESOLV_INPROGRESS; lu->bkt->expires = now + ERROR_TTL; UNLOCKR(&ar->lookup_lock); free_lookup(lu); ar->lookups[i] = NULL; } } } PTRARR_DEL(&ar->lookups,&ar->no_lookups,NULL); UNLOCKR(&ar->resolv_lock); } /** aresolv_lookup * Initiates and or returns the results of an Asynchronous forward * DNS query. Returns -1 on error, 0 if the query is pending else * the number of addresses resolved for a host. * On successful queries the pointer addrs is updated to point to an * array of struct in_addrs, this must be xfree'd after usage. */ int aresolv_lookup(aresolv_t *ar, const char *host, struct in_addr **addrs) { unsigned long hash; struct in_addr addr; time_t now = time(NULL); // intentionally not xtime arb_t *bkt; int ret = -1; //D("entry"); assert(ar != NULL); assert(host != NULL); assert(addrs != NULL); //D("host:%s",host); if(inet_pton(AF_INET,host,&addr) > 0) { /* host is already an ip address */ *addrs = xalloc(sizeof(struct in_addr)); memcpy(*addrs,&addr,sizeof(struct in_addr)); //D("shortcut, as ip address"); return 1; } hash = hash_host(host); find: LOCKR(&ar->lookup_lock); bkt = find_bucket(ar->fwd_table,ar->fwd_table_size, compar_host,hash,host); if(bkt != NULL) { if((bkt->type & ARESOLV_INPROGRESS) == ARESOLV_INPROGRESS) { UNLOCKR(&ar->lookup_lock); if(ret == -1) { check_lookups(ar); ret = 0; goto find; } else return ret; } else if(has_expired(bkt->expires,now,0)) { do_expires(ar,ar->fwd_table, ar->fwd_table_size,&ar->fwd_table_bkts,now,0); //UNLOCKR(&ar->lockup_lock); } else { if(bkt->addr != NULL) { /* This code randomise the order of the * addresses returned. */ int i,j, shift = lrand(bkt->no_addr - 1); //D(""); *addrs = xalloc(sizeof(struct in_addr) * bkt->no_addr); j = 0; for(i = shift; i < bkt->no_addr; ++i) memcpy((*addrs)+(j++),&(bkt->addr[i]), sizeof(struct in_addr)); for(i = 0; i < shift; ++i) memcpy((*addrs)+(j++),&(bkt->addr[i]), sizeof(struct in_addr)); ret = bkt->no_addr; } else ret = -1; UNLOCKR(&ar->lookup_lock); return ret; } } //D("submit new request"); /* submission of new lookup */ bkt = alloc_bucket(hash, ARESOLV_FWD | ARESOLV_INPROGRESS); bkt->no_host = 1; bkt->host = xalloc(sizeof(char *)); bkt->host[0] = xstrdup(host); place_bucket(ar->fwd_table,ar->fwd_table_size,bkt); ar->fwd_table_bkts++; ar->memory_usage += sizeof(arb_t) + sizeof(char *) + strlen(host) + 1; UNLOCKR(&ar->lookup_lock); start_query_fwd(ar,hash,bkt,host); return 0; } /** aresolv_lookup_reverse * Initiates and or returns the results of an Asynchronous reverse * DNS query. Returns -1 on error, 0 if the query is pending else * the number of hosts resolved for the address. * On successful queries hosts pointer is updated to point to an * array of string pointers, the whole chuck is a single memory * allocation and thus must be xfree'd only once with the address * of the array, not the seperately freeing each string. */ int aresolv_lookup_reverse(aresolv_t *ar, const struct in_addr *addr, char ***hosts) { unsigned long hash; time_t now = xtime(); size_t tmpsize; arb_t *bkt; int i,ret = -1; assert(ar != NULL); assert(addr != NULL); assert(hosts != NULL); hash = hash_addr(addr); //D("lookup: %lu",hash); find: LOCKR(&ar->lookup_lock); bkt = find_bucket(ar->rev_table,ar->rev_table_size, compar_addr,hash,addr); if(bkt != NULL) { if((bkt->type & ARESOLV_INPROGRESS) == ARESOLV_INPROGRESS) { UNLOCKR(&ar->lookup_lock); if(ret == -1) { //D("looking to check lookups"); check_lookups(ar); ret = 0; goto find; } else return ret; } else if(has_expired(bkt->expires,now,0)) { //D("bucket expired"); do_expires(ar,ar->rev_table, ar->rev_table_size,&ar->rev_table_bkts,now,0); //UNLOCKR(&ar->lockup_lock); } else { //D("found"); if(bkt->host != NULL) { /* What his nasty looking code does is place all the hosts and their associated pointer array in the same memory allocation, so that the whole lot can be freed as one. */ for(i = 0, tmpsize = 0; i < bkt->no_host; ++i) tmpsize += sizeof(char *) + strlen(bkt->host[i]) + 1; *hosts = xalloc(tmpsize); (*hosts)[0] = ((char *)*hosts) + (sizeof(char *) * bkt->no_host); for(i = 0; i < bkt->no_host; ++i) { strcpy((*hosts)[i],bkt->host[i]); if(i < (bkt->no_host - 1)) (*hosts)[i+1] = (*hosts)[i] + strlen((*hosts)[i]) + 1; } ret = bkt->no_host; } else ret = -1; UNLOCKR(&ar->lookup_lock); //D("returning: %d",ret); return ret; } } //D("submit new lookup"); /* submission of new lookup */ bkt = alloc_bucket(hash,ARESOLV_REV | ARESOLV_INPROGRESS); bkt->no_addr = 1; bkt->addr = xalloc(sizeof(struct in_addr)); memcpy(bkt->addr,addr,sizeof(struct in_addr)); place_bucket(ar->rev_table,ar->rev_table_size,bkt); ar->rev_table_bkts++; ar->memory_usage += sizeof(arb_t) + sizeof(struct in_addr) + 1; UNLOCKR(&ar->lookup_lock); start_query_rev(ar,hash,bkt,addr); return 0; } /** resize_table * Resizes the a aresolv hash table. */ static void resize_table(aresolv_t *ar, arb_t ***table_ptr, int *table_size, int new_size) { arb_t **old_table, *pbkt, *nbkt; long ret = 0; int i; assert(table_ptr != NULL); assert(table_size != NULL); assert(new_size > 0); ret = sizeof(arb_t *) * (new_size - (*table_size)); old_table = *table_ptr; *table_ptr = xalloc(sizeof(arb_t *) * new_size); for(i = 0; i < new_size; ++i) (*table_ptr)[i] = NULL; for(i = 0; i < *table_size; ++i) { nbkt = old_table[i]; while(nbkt != NULL) { pbkt = nbkt; nbkt = nbkt->next; place_bucket(*table_ptr,new_size,pbkt); } } *table_size = new_size; xfree(old_table); ar->memory_usage += ret; } static void manage_memory_usage(aresolv_t *ar) { time_t tmp, now = xtime(); LOCKR(&ar->lookup_lock); do_expires(ar,ar->fwd_table, ar->fwd_table_size,&ar->fwd_table_bkts,now,0); do_expires(ar,ar->rev_table, ar->rev_table_size,&ar->rev_table_bkts,now,0); if(ar->memory_usage >= MAX_MEMORY_USAGE) { /* Attempt to reduce memory usage by expiring records starting with those with a high ttl. */ tmp = 24 * 60 * 60; do { do_expires(ar,ar->fwd_table, ar->fwd_table_size,&ar->fwd_table_bkts, now+tmp,1); do_expires(ar,ar->rev_table, ar->rev_table_size,&ar->rev_table_bkts, now+tmp,1); tmp /= 2; } while(ar->memory_usage >= MAX_MEMORY_USAGE && tmp > 60); } if(ar->fwd_table_bkts > (ar->fwd_table_size * TABLE_OPTIMAL_DEPTH)) resize_table(ar,&ar->fwd_table, &ar->fwd_table_size,ar->fwd_table_size*2); else if(ar->fwd_table_bkts < ar->fwd_table_size && ar->fwd_table_size > TABLE_INIT_SIZE) resize_table(ar,&ar->fwd_table, &ar->fwd_table_size,ar->fwd_table_size/2); if(ar->rev_table_bkts > (ar->rev_table_size * TABLE_OPTIMAL_DEPTH)) resize_table(ar,&ar->rev_table, &ar->rev_table_size,ar->rev_table_size*2); else if(ar->rev_table_bkts < ar->rev_table_size && ar->rev_table_size > TABLE_INIT_SIZE) resize_table(ar,&ar->rev_table, &ar->rev_table_size,ar->rev_table_size/2); UNLOCKR(&ar->lookup_lock); } /** aresolve_manage * Performs lookup expiry and hash table size optimisation. * Hence the manage function should be called periodically. */ void aresolv_manage(aresolv_t *ar) { check_lookups(ar); rehash_servers(ar); rehash_hosts(ar); manage_memory_usage(ar); } int aresolv_nametoaddrs(aresolv_t *ar, int af, const char *host, void *addrs) { int ret; assert(addrs != NULL); if(af != AF_INET) return -1; while((ret = aresolv_lookup(ar,host,(struct in_addr **)addrs)) == 0) { usleep(250000); if(thread_should_end()) return -1; } return ret; } int aresolv_nametoaddr(aresolv_t *ar, int af, const char *host, void *addr) { struct in_addr *addrs = NULL; int ret; ret = aresolv_nametoaddrs(ar,af,host,&addrs); if(ret == -1) return -1; else { memcpy(addr,addrs,sizeof(struct in_addr)); xfree(addrs); return 0; } } int aresolv_addrtonames(aresolv_t *ar, int af, const void *addr, char ***hosts) { int ret; if(af != AF_INET) return -1; while((ret = aresolv_lookup_reverse(ar,(struct in_addr *)addr,hosts)) == 0) { usleep(250000); if(thread_should_end()) return -1; } return ret; } int aresolv_addrtoname(aresolv_t *ar, int af, const void *addr, char *host, size_t hostlen) { char **hosts; int ret; ret = aresolv_addrtonames(ar,af,addr,&hosts); if(ret == -1) return -1; else { xstrncpy(host,hosts[0],hostlen); xfree(hosts); return 0; } }