diff options
Diffstat (limited to 'pjlib-util/src/pjlib-util/resolver.c')
-rw-r--r-- | pjlib-util/src/pjlib-util/resolver.c | 1588 |
1 files changed, 1588 insertions, 0 deletions
diff --git a/pjlib-util/src/pjlib-util/resolver.c b/pjlib-util/src/pjlib-util/resolver.c new file mode 100644 index 0000000..4be5350 --- /dev/null +++ b/pjlib-util/src/pjlib-util/resolver.c @@ -0,0 +1,1588 @@ +/* $Id: resolver.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * 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 <pjlib-util/resolver.h> +#include <pjlib-util/errno.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/except.h> +#include <pj/hash.h> +#include <pj/ioqueue.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/pool_buf.h> +#include <pj/rand.h> +#include <pj/string.h> +#include <pj/sock.h> +#include <pj/timer.h> + + +#define THIS_FILE "resolver.c" + + +/* Check that maximum DNS nameservers is not too large. + * This has got todo with the datatype to index the nameserver in the query. + */ +#if PJ_DNS_RESOLVER_MAX_NS > 256 +# error "PJ_DNS_RESOLVER_MAX_NS is too large (max=256)" +#endif + + +#define RES_HASH_TABLE_SIZE 127 /**< Hash table size (must be 2^n-1 */ +#define PORT 53 /**< Default NS port. */ +#define Q_HASH_TABLE_SIZE 127 /**< Query hash table size */ +#define TIMER_SIZE 127 /**< Initial number of timers. */ +#define MAX_FD 3 /**< Maximum internal sockets. */ + +#define RES_BUF_SZ PJ_DNS_RESOLVER_RES_BUF_SIZE +#define UDPSZ PJ_DNS_RESOLVER_MAX_UDP_SIZE +#define TMP_SZ PJ_DNS_RESOLVER_TMP_BUF_SIZE + + +/* Nameserver state */ +enum ns_state +{ + STATE_PROBING, + STATE_ACTIVE, + STATE_BAD, +}; + +static const char *state_names[3] = +{ + "Probing", + "Active", + "Bad" +}; + + +/* + * Each nameserver entry. + * A name server is identified by its socket address (IP and port). + * Each NS will have a flag to indicate whether it's properly functioning. + */ +struct nameserver +{ + pj_sockaddr_in addr; /**< Server address. */ + + enum ns_state state; /**< Nameserver state. */ + pj_time_val state_expiry; /**< Time set next state. */ + pj_time_val rt_delay; /**< Response time. */ + + + /* For calculating rt_delay: */ + pj_uint16_t q_id; /**< Query ID. */ + pj_time_val sent_time; /**< Time this query is sent. */ +}; + + +/* Child query list head + * See comments on pj_dns_async_query below. + */ +struct query_head +{ + PJ_DECL_LIST_MEMBER(pj_dns_async_query); +}; + + +/* Key to look for outstanding query and/or cached response */ +struct res_key +{ + pj_uint16_t qtype; /**< Query type. */ + char name[PJ_MAX_HOSTNAME]; /**< Name being queried */ +}; + + +/* + * This represents each asynchronous query entry. + * This entry will be put in two hash tables, the first one keyed on the DNS + * transaction ID to match response with the query, and the second one keyed + * on "res_key" structure above to match a new request against outstanding + * requests. + * + * An asynchronous entry may have child entries; child entries are subsequent + * queries to the same resource while there is pending query on the same + * DNS resource name and type. When a query has child entries, once the + * response is received (or error occurs), the response will trigger callback + * invocations for all childs entries. + * + * Note: when application cancels the query, the callback member will be + * set to NULL, but for simplicity, the query will be let running. + */ +struct pj_dns_async_query +{ + PJ_DECL_LIST_MEMBER(pj_dns_async_query); /**< List member. */ + + pj_dns_resolver *resolver; /**< The resolver instance. */ + pj_uint16_t id; /**< Transaction ID. */ + + unsigned transmit_cnt; /**< Number of transmissions. */ + + struct res_key key; /**< Key to index this query. */ + pj_hash_entry_buf hbufid; /**< Hash buffer 1 */ + pj_hash_entry_buf hbufkey; /**< Hash buffer 2 */ + pj_timer_entry timer_entry; /**< Timer to manage timeouts */ + unsigned options; /**< Query options. */ + void *user_data; /**< Application data. */ + pj_dns_callback *cb; /**< Callback to be called. */ + struct query_head child_head; /**< Child queries list head. */ +}; + + +/* This structure is used to keep cached response entry. + * The cache is a hash table keyed on "res_key" structure above. + */ +struct cached_res +{ + PJ_DECL_LIST_MEMBER(struct cached_res); + + pj_pool_t *pool; /**< Cache's pool. */ + struct res_key key; /**< Resource key. */ + pj_hash_entry_buf hbuf; /**< Hash buffer */ + pj_time_val expiry_time; /**< Expiration time. */ + pj_dns_parsed_packet *pkt; /**< The response packet. */ +}; + + +/* Resolver entry */ +struct pj_dns_resolver +{ + pj_str_t name; /**< Resolver instance name for id. */ + + /* Internals */ + pj_pool_t *pool; /**< Internal pool. */ + pj_mutex_t *mutex; /**< Mutex protection. */ + pj_bool_t own_timer; /**< Do we own timer? */ + pj_timer_heap_t *timer; /**< Timer instance. */ + pj_bool_t own_ioqueue; /**< Do we own ioqueue? */ + pj_ioqueue_t *ioqueue; /**< Ioqueue instance. */ + char tmp_pool[TMP_SZ];/**< Temporary pool buffer. */ + + /* Socket */ + pj_sock_t udp_sock; /**< UDP socket. */ + pj_ioqueue_key_t *udp_key; /**< UDP socket ioqueue key. */ + unsigned char udp_rx_pkt[UDPSZ];/**< UDP receive buffer. */ + unsigned char udp_tx_pkt[UDPSZ];/**< UDP receive buffer. */ + pj_ssize_t udp_len; /**< Length of received packet. */ + pj_ioqueue_op_key_t udp_op_key; /**< UDP read operation key. */ + pj_sockaddr_in udp_src_addr; /**< Source address of packet */ + int udp_addr_len; /**< Source address length. */ + + /* Settings */ + pj_dns_settings settings; /**< Resolver settings. */ + + /* Nameservers */ + unsigned ns_count; /**< Number of name servers. */ + struct nameserver ns[PJ_DNS_RESOLVER_MAX_NS]; /**< Array of NS. */ + + /* Last DNS transaction ID used. */ + pj_uint16_t last_id; + + /* Hash table for cached response */ + pj_hash_table_t *hrescache; /**< Cached response in hash table */ + + /* Pending asynchronous query, hashed by transaction ID. */ + pj_hash_table_t *hquerybyid; + + /* Pending asynchronous query, hashed by "res_key" */ + pj_hash_table_t *hquerybyres; + + /* Query entries free list */ + struct query_head query_free_nodes; +}; + + +/* Callback from ioqueue when packet is received */ +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read); + +/* Callback to be called when query has timed out */ +static void on_timeout( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry); + +/* Select which nameserver to use */ +static pj_status_t select_nameservers(pj_dns_resolver *resolver, + unsigned *count, + unsigned servers[]); + + +/* Initialize DNS settings with default values */ +PJ_DEF(void) pj_dns_settings_default(pj_dns_settings *s) +{ + pj_bzero(s, sizeof(pj_dns_settings)); + s->qretr_delay = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY; + s->qretr_count = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT; + s->cache_max_ttl = PJ_DNS_RESOLVER_MAX_TTL; + s->good_ns_ttl = PJ_DNS_RESOLVER_GOOD_NS_TTL; + s->bad_ns_ttl = PJ_DNS_RESOLVER_BAD_NS_TTL; +} + + +/* + * Create the resolver. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_create( pj_pool_factory *pf, + const char *name, + unsigned options, + pj_timer_heap_t *timer, + pj_ioqueue_t *ioqueue, + pj_dns_resolver **p_resolver) +{ + pj_pool_t *pool; + pj_dns_resolver *resv; + pj_ioqueue_callback socket_cb; + pj_status_t status; + + /* Sanity check */ + PJ_ASSERT_RETURN(pf && p_resolver, PJ_EINVAL); + + if (name == NULL) + name = THIS_FILE; + + /* Create and initialize resolver instance */ + pool = pj_pool_create(pf, name, 4000, 4000, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Create pool and name */ + resv = PJ_POOL_ZALLOC_T(pool, struct pj_dns_resolver); + resv->pool = pool; + resv->udp_sock = PJ_INVALID_SOCKET; + pj_strdup2_with_null(pool, &resv->name, name); + + /* Create the mutex */ + status = pj_mutex_create_recursive(pool, name, &resv->mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Timer, ioqueue, and settings */ + resv->timer = timer; + resv->ioqueue = ioqueue; + resv->last_id = 1; + + pj_dns_settings_default(&resv->settings); + resv->settings.options = options; + + /* Create the timer heap if one is not specified */ + if (resv->timer == NULL) { + status = pj_timer_heap_create(pool, TIMER_SIZE, &resv->timer); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Create the ioqueue if one is not specified */ + if (resv->ioqueue == NULL) { + status = pj_ioqueue_create(pool, MAX_FD, &resv->ioqueue); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Response cache hash table */ + resv->hrescache = pj_hash_create(pool, RES_HASH_TABLE_SIZE); + + /* Query hash table and free list. */ + resv->hquerybyid = pj_hash_create(pool, Q_HASH_TABLE_SIZE); + resv->hquerybyres = pj_hash_create(pool, Q_HASH_TABLE_SIZE); + pj_list_init(&resv->query_free_nodes); + + /* Create the UDP socket */ + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &resv->udp_sock); + if (status != PJ_SUCCESS) + goto on_error; + + /* Bind to any address/port */ + status = pj_sock_bind_in(resv->udp_sock, 0, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register to ioqueue */ + pj_bzero(&socket_cb, sizeof(socket_cb)); + socket_cb.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock(pool, resv->ioqueue, resv->udp_sock, + resv, &socket_cb, &resv->udp_key); + if (status != PJ_SUCCESS) + goto on_error; + + pj_ioqueue_op_key_init(&resv->udp_op_key, sizeof(resv->udp_op_key)); + + /* Start asynchronous read to the UDP socket */ + resv->udp_len = sizeof(resv->udp_rx_pkt); + resv->udp_addr_len = sizeof(resv->udp_src_addr); + status = pj_ioqueue_recvfrom(resv->udp_key, &resv->udp_op_key, + resv->udp_rx_pkt, &resv->udp_len, + PJ_IOQUEUE_ALWAYS_ASYNC, + &resv->udp_src_addr, &resv->udp_addr_len); + if (status != PJ_EPENDING) + goto on_error; + + + /* Looks like everything is okay */ + *p_resolver = resv; + return PJ_SUCCESS; + +on_error: + pj_dns_resolver_destroy(resv, PJ_FALSE); + return status; +} + + +/* + * Destroy DNS resolver instance. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_destroy( pj_dns_resolver *resolver, + pj_bool_t notify) +{ + pj_hash_iterator_t it_buf, *it; + PJ_ASSERT_RETURN(resolver, PJ_EINVAL); + + if (notify) { + /* + * Notify pending queries if requested. + */ + it = pj_hash_first(resolver->hquerybyid, &it_buf); + while (it) { + pj_dns_async_query *q = (pj_dns_async_query *) + pj_hash_this(resolver->hquerybyid, it); + pj_dns_async_query *cq; + if (q->cb) + (*q->cb)(q->user_data, PJ_ECANCELLED, NULL); + + cq = q->child_head.next; + while (cq != (pj_dns_async_query*)&q->child_head) { + if (cq->cb) + (*cq->cb)(cq->user_data, PJ_ECANCELLED, NULL); + cq = cq->next; + } + it = pj_hash_next(resolver->hquerybyid, it); + } + } + + /* Destroy cached entries */ + it = pj_hash_first(resolver->hrescache, &it_buf); + while (it) { + struct cached_res *cache; + + cache = (struct cached_res*) pj_hash_this(resolver->hrescache, it); + pj_hash_set(NULL, resolver->hrescache, &cache->key, + sizeof(cache->key), 0, NULL); + pj_pool_release(cache->pool); + + it = pj_hash_first(resolver->hrescache, &it_buf); + } + + if (resolver->own_timer && resolver->timer) { + pj_timer_heap_destroy(resolver->timer); + resolver->timer = NULL; + } + + if (resolver->udp_key != NULL) { + pj_ioqueue_unregister(resolver->udp_key); + resolver->udp_key = NULL; + resolver->udp_sock = PJ_INVALID_SOCKET; + } else if (resolver->udp_sock != PJ_INVALID_SOCKET) { + pj_sock_close(resolver->udp_sock); + resolver->udp_sock = PJ_INVALID_SOCKET; + } + + if (resolver->own_ioqueue && resolver->ioqueue) { + pj_ioqueue_destroy(resolver->ioqueue); + resolver->ioqueue = NULL; + } + + if (resolver->mutex) { + pj_mutex_destroy(resolver->mutex); + resolver->mutex = NULL; + } + + if (resolver->pool) { + pj_pool_t *pool = resolver->pool; + resolver->pool = NULL; + pj_pool_release(pool); + } + return PJ_SUCCESS; +} + + + +/* + * Configure name servers for the DNS resolver. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_set_ns( pj_dns_resolver *resolver, + unsigned count, + const pj_str_t servers[], + const pj_uint16_t ports[]) +{ + unsigned i; + pj_time_val now; + pj_status_t status; + + PJ_ASSERT_RETURN(resolver && count && servers, PJ_EINVAL); + PJ_ASSERT_RETURN(count < PJ_DNS_RESOLVER_MAX_NS, PJ_EINVAL); + + pj_mutex_lock(resolver->mutex); + + if (count > PJ_DNS_RESOLVER_MAX_NS) + count = PJ_DNS_RESOLVER_MAX_NS; + + resolver->ns_count = 0; + pj_bzero(resolver->ns, sizeof(resolver->ns)); + + pj_gettimeofday(&now); + + for (i=0; i<count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + status = pj_sockaddr_in_init(&ns->addr, &servers[i], + (pj_uint16_t)(ports ? ports[i] : PORT)); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(resolver->mutex); + return PJLIB_UTIL_EDNSINNSADDR; + } + + ns->state = STATE_ACTIVE; + ns->state_expiry = now; + ns->rt_delay.sec = 10; + } + + resolver->ns_count = count; + + pj_mutex_unlock(resolver->mutex); + return PJ_SUCCESS; +} + + + +/* + * Modify the resolver settings. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_set_settings(pj_dns_resolver *resolver, + const pj_dns_settings *st) +{ + PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL); + + pj_mutex_lock(resolver->mutex); + pj_memcpy(&resolver->settings, st, sizeof(*st)); + pj_mutex_unlock(resolver->mutex); + return PJ_SUCCESS; +} + + +/* + * Get the resolver current settings. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_get_settings( pj_dns_resolver *resolver, + pj_dns_settings *st) +{ + PJ_ASSERT_RETURN(resolver && st, PJ_EINVAL); + + pj_mutex_lock(resolver->mutex); + pj_memcpy(st, &resolver->settings, sizeof(*st)); + pj_mutex_unlock(resolver->mutex); + return PJ_SUCCESS; +} + + +/* + * Poll for events from the resolver. + */ +PJ_DEF(void) pj_dns_resolver_handle_events(pj_dns_resolver *resolver, + const pj_time_val *timeout) +{ + PJ_ASSERT_ON_FAIL(resolver, return); + + pj_mutex_lock(resolver->mutex); + pj_timer_heap_poll(resolver->timer, NULL); + pj_mutex_unlock(resolver->mutex); + + pj_ioqueue_poll(resolver->ioqueue, timeout); +} + + +/* Get one query node from the free node, if any, or allocate + * a new one. + */ +static pj_dns_async_query *alloc_qnode(pj_dns_resolver *resolver, + unsigned options, + void *user_data, + pj_dns_callback *cb) +{ + pj_dns_async_query *q; + + /* Merge query options with resolver options */ + options |= resolver->settings.options; + + if (!pj_list_empty(&resolver->query_free_nodes)) { + q = resolver->query_free_nodes.next; + pj_list_erase(q); + pj_bzero(q, sizeof(*q)); + } else { + q = PJ_POOL_ZALLOC_T(resolver->pool, pj_dns_async_query); + } + + /* Init query */ + q->resolver = resolver; + q->options = options; + q->user_data = user_data; + q->cb = cb; + pj_list_init(&q->child_head); + + return q; +} + + +/* + * Transmit query. + */ +static pj_status_t transmit_query(pj_dns_resolver *resolver, + pj_dns_async_query *q) +{ + unsigned pkt_size; + unsigned i, server_cnt; + unsigned servers[PJ_DNS_RESOLVER_MAX_NS]; + pj_time_val now; + pj_str_t name; + pj_time_val delay; + pj_status_t status; + + /* Create DNS query packet */ + pkt_size = sizeof(resolver->udp_tx_pkt); + name = pj_str(q->key.name); + status = pj_dns_make_query(resolver->udp_tx_pkt, &pkt_size, + q->id, q->key.qtype, &name); + if (status != PJ_SUCCESS) { + return status; + } + + /* Select which nameserver(s) to send requests to. */ + server_cnt = PJ_ARRAY_SIZE(servers); + status = select_nameservers(resolver, &server_cnt, servers); + if (status != PJ_SUCCESS) { + return status; + } + + if (server_cnt == 0) { + return PJLIB_UTIL_EDNSNOWORKINGNS; + } + + /* Start retransmit/timeout timer for the query */ + pj_assert(q->timer_entry.id == 0); + q->timer_entry.id = 1; + q->timer_entry.user_data = q; + q->timer_entry.cb = &on_timeout; + + delay.sec = 0; + delay.msec = resolver->settings.qretr_delay; + pj_time_val_normalize(&delay); + status = pj_timer_heap_schedule(resolver->timer, &q->timer_entry, &delay); + if (status != PJ_SUCCESS) { + return status; + } + + /* Get current time. */ + pj_gettimeofday(&now); + + /* Send the packet to name servers */ + for (i=0; i<server_cnt; ++i) { + pj_ssize_t sent = (pj_ssize_t) pkt_size; + struct nameserver *ns = &resolver->ns[servers[i]]; + + pj_sock_sendto(resolver->udp_sock, resolver->udp_tx_pkt, &sent, 0, + &resolver->ns[servers[i]].addr, sizeof(pj_sockaddr_in)); + + PJ_LOG(4,(resolver->name.ptr, + "%s %d bytes to NS %d (%s:%d): DNS %s query for %s", + (q->transmit_cnt==0? "Transmitting":"Re-transmitting"), + (int)sent, servers[i], + pj_inet_ntoa(ns->addr.sin_addr), + (int)pj_ntohs(ns->addr.sin_port), + pj_dns_get_type_name(q->key.qtype), + q->key.name)); + + if (ns->q_id == 0) { + ns->q_id = q->id; + ns->sent_time = now; + } + } + + ++q->transmit_cnt; + + return PJ_SUCCESS; +} + + +/* + * Initialize resource key for hash table lookup. + */ +static void init_res_key(struct res_key *key, int type, const pj_str_t *name) +{ + unsigned i, len; + char *dst = key->name; + const char *src = name->ptr; + + pj_bzero(key, sizeof(struct res_key)); + key->qtype = (pj_uint16_t)type; + + len = name->slen; + if (len > PJ_MAX_HOSTNAME) len = PJ_MAX_HOSTNAME; + + /* Copy key, in lowercase */ + for (i=0; i<len; ++i) { + *dst++ = (char)pj_tolower(*src++); + } +} + + +/* Allocate new cache entry */ +static struct cached_res *alloc_entry(pj_dns_resolver *resolver) +{ + pj_pool_t *pool; + struct cached_res *cache; + + pool = pj_pool_create(resolver->pool->factory, "dnscache", + RES_BUF_SZ, 256, NULL); + cache = PJ_POOL_ZALLOC_T(pool, struct cached_res); + cache->pool = pool; + + return cache; +} + +/* Put unused/expired cached entry to the free list */ +static void free_entry(pj_dns_resolver *resolver, struct cached_res *cache) +{ + PJ_UNUSED_ARG(resolver); + pj_pool_release(cache->pool); +} + + +/* + * Create and start asynchronous DNS query for a single resource. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_start_query( pj_dns_resolver *resolver, + const pj_str_t *name, + int type, + unsigned options, + pj_dns_callback *cb, + void *user_data, + pj_dns_async_query **p_query) +{ + pj_time_val now; + struct res_key key; + struct cached_res *cache; + pj_dns_async_query *q; + pj_uint32_t hval; + pj_status_t status = PJ_SUCCESS; + + /* Validate arguments */ + PJ_ASSERT_RETURN(resolver && name && type, PJ_EINVAL); + + /* Check name is not too long. */ + PJ_ASSERT_RETURN(name->slen>0 && name->slen < PJ_MAX_HOSTNAME, + PJ_ENAMETOOLONG); + + /* Check type */ + PJ_ASSERT_RETURN(type > 0 && type < 0xFFFF, PJ_EINVAL); + + if (p_query) + *p_query = NULL; + + /* Build resource key for looking up hash tables */ + init_res_key(&key, type, name); + + /* Start working with the resolver */ + pj_mutex_lock(resolver->mutex); + + /* Get current time. */ + pj_gettimeofday(&now); + + /* First, check if we have cached response for the specified name/type, + * and the cached entry has not expired. + */ + hval = 0; + cache = (struct cached_res *) pj_hash_get(resolver->hrescache, &key, + sizeof(key), &hval); + if (cache) { + /* We've found a cached entry. */ + + /* Check for expiration */ + if (PJ_TIME_VAL_GT(cache->expiry_time, now)) { + + /* Log */ + PJ_LOG(5,(resolver->name.ptr, + "Picked up DNS %s record for %.*s from cache, ttl=%d", + pj_dns_get_type_name(type), + (int)name->slen, name->ptr, + (int)(cache->expiry_time.sec - now.sec))); + + /* Map DNS Rcode in the response into PJLIB status name space */ + status = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags); + status = PJ_STATUS_FROM_DNS_RCODE(status); + + /* This cached response is still valid. Just return this + * response to caller. + */ + if (cb) { + (*cb)(user_data, status, cache->pkt); + } + + /* Done. No host resolution is necessary */ + + /* Must return PJ_SUCCESS */ + status = PJ_SUCCESS; + + goto on_return; + } + + /* At this point, we have a cached entry, but this entry has expired. + * Remove this entry from the cached list. + */ + pj_hash_set(NULL, resolver->hrescache, &key, sizeof(key), 0, NULL); + + /* Store the entry into free nodes */ + free_entry(resolver, cache); + + /* Must continue with creating a query now */ + } + + /* Next, check if we have pending query on the same resource */ + q = (pj_dns_async_query *) pj_hash_get(resolver->hquerybyres, &key, + sizeof(key), NULL); + if (q) { + /* Yes, there's another pending query to the same key. + * Just create a new child query and add this query to + * pending query's child queries. + */ + pj_dns_async_query *nq; + + nq = alloc_qnode(resolver, options, user_data, cb); + pj_list_push_back(&q->child_head, nq); + + /* Done. This child query will be notified once the "parent" + * query completes. + */ + status = PJ_SUCCESS; + goto on_return; + } + + /* There's no pending query to the same key, initiate a new one. */ + q = alloc_qnode(resolver, options, user_data, cb); + + /* Save the ID and key */ + /* TODO: dnsext-forgery-resilient: randomize id for security */ + q->id = resolver->last_id++; + if (resolver->last_id == 0) + resolver->last_id = 1; + pj_memcpy(&q->key, &key, sizeof(struct res_key)); + + /* Send the query */ + status = transmit_query(resolver, q); + if (status != PJ_SUCCESS) { + pj_list_push_back(&resolver->query_free_nodes, q); + goto on_return; + } + + /* Add query entry to the hash tables */ + pj_hash_set_np(resolver->hquerybyid, &q->id, sizeof(q->id), + 0, q->hbufid, q); + pj_hash_set_np(resolver->hquerybyres, &q->key, sizeof(q->key), + 0, q->hbufkey, q); + + if (p_query) + *p_query = q; + +on_return: + pj_mutex_unlock(resolver->mutex); + return status; +} + + +/* + * Cancel a pending query. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_cancel_query(pj_dns_async_query *query, + pj_bool_t notify) +{ + pj_dns_callback *cb; + + PJ_ASSERT_RETURN(query, PJ_EINVAL); + + pj_mutex_lock(query->resolver->mutex); + + cb = query->cb; + query->cb = NULL; + + if (notify) + (*cb)(query->user_data, PJ_ECANCELLED, NULL); + + pj_mutex_unlock(query->resolver->mutex); + return PJ_SUCCESS; +} + + +/* + * DNS response containing A packet. + */ +PJ_DEF(pj_status_t) pj_dns_parse_a_response(const pj_dns_parsed_packet *pkt, + pj_dns_a_record *rec) +{ + enum { MAX_SEARCH = 20 }; + pj_str_t hostname, alias = {NULL, 0}, *resname; + unsigned bufstart = 0; + unsigned bufleft = sizeof(rec->buf_); + unsigned i, ansidx, search_cnt=0; + + PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); + + /* Init the record */ + pj_bzero(rec, sizeof(pj_dns_a_record)); + + /* Return error if there's error in the packet. */ + if (PJ_DNS_GET_RCODE(pkt->hdr.flags)) + return PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(pkt->hdr.flags)); + + /* Return error if there's no query section */ + if (pkt->hdr.qdcount == 0) + return PJLIB_UTIL_EDNSINANSWER; + + /* Return error if there's no answer */ + if (pkt->hdr.anscount == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + /* Get the hostname from the query. */ + hostname = pkt->q[0].name; + + /* Copy hostname to the record */ + if (hostname.slen > (int)bufleft) { + return PJ_ENAMETOOLONG; + } + + pj_memcpy(&rec->buf_[bufstart], hostname.ptr, hostname.slen); + rec->name.ptr = &rec->buf_[bufstart]; + rec->name.slen = hostname.slen; + + bufstart += hostname.slen; + bufleft -= hostname.slen; + + /* Find the first RR which name matches the hostname */ + for (ansidx=0; ansidx < pkt->hdr.anscount; ++ansidx) { + if (pj_stricmp(&pkt->ans[ansidx].name, &hostname)==0) + break; + } + + if (ansidx == pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + resname = &hostname; + + /* Keep following CNAME records. */ + while (pkt->ans[ansidx].type == PJ_DNS_TYPE_CNAME && + search_cnt++ < MAX_SEARCH) + { + resname = &pkt->ans[ansidx].rdata.cname.name; + + if (!alias.slen) + alias = *resname; + + for (i=0; i < pkt->hdr.anscount; ++i) { + if (pj_stricmp(resname, &pkt->ans[i].name)==0) { + break; + } + } + + if (i==pkt->hdr.anscount) + return PJLIB_UTIL_EDNSNOANSWERREC; + + ansidx = i; + } + + if (search_cnt >= MAX_SEARCH) + return PJLIB_UTIL_EDNSINANSWER; + + if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A) + return PJLIB_UTIL_EDNSINANSWER; + + /* Copy alias to the record, if present. */ + if (alias.slen) { + if (alias.slen > (int)bufleft) + return PJ_ENAMETOOLONG; + + pj_memcpy(&rec->buf_[bufstart], alias.ptr, alias.slen); + rec->alias.ptr = &rec->buf_[bufstart]; + rec->alias.slen = alias.slen; + + bufstart += alias.slen; + bufleft -= alias.slen; + } + + /* Get the IP addresses. */ + for (i=0; i < pkt->hdr.anscount; ++i) { + if (pkt->ans[i].type == PJ_DNS_TYPE_A && + pj_stricmp(&pkt->ans[i].name, resname)==0 && + rec->addr_count < PJ_DNS_MAX_IP_IN_A_REC) + { + rec->addr[rec->addr_count++].s_addr = + pkt->ans[i].rdata.a.ip_addr.s_addr; + } + } + + if (rec->addr_count == 0) + return PJLIB_UTIL_EDNSNOANSWERREC; + + return PJ_SUCCESS; +} + + +/* Set nameserver state */ +static void set_nameserver_state(pj_dns_resolver *resolver, + unsigned index, + enum ns_state state, + const pj_time_val *now) +{ + struct nameserver *ns = &resolver->ns[index]; + enum ns_state old_state = ns->state; + + ns->state = state; + ns->state_expiry = *now; + + if (state == STATE_PROBING) + ns->state_expiry.sec += ((resolver->settings.qretr_count + 2) * + resolver->settings.qretr_delay) / 1000; + else if (state == STATE_ACTIVE) + ns->state_expiry.sec += resolver->settings.good_ns_ttl; + else + ns->state_expiry.sec += resolver->settings.bad_ns_ttl; + + PJ_LOG(5, (resolver->name.ptr, "Nameserver %s:%d state changed %s --> %s", + pj_inet_ntoa(ns->addr.sin_addr), + (int)pj_ntohs(ns->addr.sin_port), + state_names[old_state], state_names[state])); +} + + +/* Select which nameserver(s) to use. Note this may return multiple + * name servers. The algorithm to select which nameservers to be + * sent the request to is as follows: + * - select the first nameserver that is known to be good for the + * last PJ_DNS_RESOLVER_GOOD_NS_TTL interval. + * - for all NSes, if last_known_good >= PJ_DNS_RESOLVER_GOOD_NS_TTL, + * include the NS to re-check again that the server is still good, + * unless the NS is known to be bad in the last PJ_DNS_RESOLVER_BAD_NS_TTL + * interval. + * - for all NSes, if last_known_bad >= PJ_DNS_RESOLVER_BAD_NS_TTL, + * also include the NS to re-check again that the server is still bad. + */ +static pj_status_t select_nameservers(pj_dns_resolver *resolver, + unsigned *count, + unsigned servers[]) +{ + unsigned i, max_count=*count; + int min; + pj_time_val now; + + pj_assert(max_count > 0); + + *count = 0; + servers[0] = 0xFFFF; + + /* Check that nameservers are configured. */ + if (resolver->ns_count == 0) + return PJLIB_UTIL_EDNSNONS; + + pj_gettimeofday(&now); + + /* Select one Active nameserver with best response time. */ + for (min=-1, i=0; i<resolver->ns_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (ns->state != STATE_ACTIVE) + continue; + + if (min == -1) + min = i; + else if (PJ_TIME_VAL_LT(ns->rt_delay, resolver->ns[min].rt_delay)) + min = i; + } + if (min != -1) { + servers[0] = min; + ++(*count); + } + + /* Scan nameservers. */ + for (i=0; i<resolver->ns_count && *count < max_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (PJ_TIME_VAL_LTE(ns->state_expiry, now)) { + if (ns->state == STATE_PROBING) { + set_nameserver_state(resolver, i, STATE_BAD, &now); + } else { + set_nameserver_state(resolver, i, STATE_PROBING, &now); + if ((int)i != min) { + servers[*count] = i; + ++(*count); + } + } + } else if (ns->state == STATE_PROBING && (int)i != min) { + servers[*count] = i; + ++(*count); + } + } + + return PJ_SUCCESS; +} + + +/* Update name server status */ +static void report_nameserver_status(pj_dns_resolver *resolver, + const pj_sockaddr_in *ns_addr, + const pj_dns_parsed_packet *pkt) +{ + unsigned i; + int rcode; + pj_uint32_t q_id; + pj_time_val now; + pj_bool_t is_good; + + /* Only mark nameserver as "bad" if it returned non-parseable response or + * it returned the following status codes + */ + if (pkt) { + rcode = PJ_DNS_GET_RCODE(pkt->hdr.flags); + q_id = pkt->hdr.id; + } else { + rcode = 0; + q_id = (pj_uint32_t)-1; + } + + if (!pkt || rcode == PJ_DNS_RCODE_SERVFAIL || + rcode == PJ_DNS_RCODE_REFUSED || + rcode == PJ_DNS_RCODE_NOTAUTH) + { + is_good = PJ_FALSE; + } else { + is_good = PJ_TRUE; + } + + + /* Mark time */ + pj_gettimeofday(&now); + + /* Recheck all nameservers. */ + for (i=0; i<resolver->ns_count; ++i) { + struct nameserver *ns = &resolver->ns[i]; + + if (ns->addr.sin_addr.s_addr == ns_addr->sin_addr.s_addr && + ns->addr.sin_port == ns_addr->sin_port && + ns->addr.sin_family == ns_addr->sin_family) + { + if (q_id == ns->q_id) { + /* Calculate response time */ + pj_time_val rt = now; + PJ_TIME_VAL_SUB(rt, ns->sent_time); + ns->rt_delay = rt; + ns->q_id = 0; + } + set_nameserver_state(resolver, i, + (is_good ? STATE_ACTIVE : STATE_BAD), &now); + break; + } + } +} + + +/* Update response cache */ +static void update_res_cache(pj_dns_resolver *resolver, + const struct res_key *key, + pj_status_t status, + pj_bool_t set_expiry, + const pj_dns_parsed_packet *pkt) +{ + struct cached_res *cache; + pj_uint32_t hval=0, ttl; + + /* If status is unsuccessful, clear the same entry from the cache */ + if (status != PJ_SUCCESS) { + cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key, + sizeof(*key), &hval); + if (cache) + free_entry(resolver, cache); + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + } + + + /* Calculate expiration time. */ + if (set_expiry) { + if (pkt->hdr.anscount == 0 || status != PJ_SUCCESS) { + /* If we don't have answers for the name, then give a different + * ttl value (note: PJ_DNS_RESOLVER_INVALID_TTL may be zero, + * which means that invalid names won't be kept in the cache) + */ + ttl = PJ_DNS_RESOLVER_INVALID_TTL; + + } else { + /* Otherwise get the minimum TTL from the answers */ + unsigned i; + ttl = 0xFFFFFFFF; + for (i=0; i<pkt->hdr.anscount; ++i) { + if (pkt->ans[i].ttl < ttl) + ttl = pkt->ans[i].ttl; + } + } + } else { + ttl = 0xFFFFFFFF; + } + + /* Apply maximum TTL */ + if (ttl > resolver->settings.cache_max_ttl) + ttl = resolver->settings.cache_max_ttl; + + /* If TTL is zero, clear the same entry in the hash table */ + if (ttl == 0) { + cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key, + sizeof(*key), &hval); + if (cache) + free_entry(resolver, cache); + pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); + return; + } + + /* Get a cache response entry */ + cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key, + sizeof(*key), &hval); + if (cache == NULL) { + cache = alloc_entry(resolver); + } + + /* Duplicate the packet. + * We don't need to keep the NS and AR sections from the packet, + * so exclude from duplication. We do need to keep the Query + * section since DNS A parser needs the query section to know + * the name being requested. + */ + cache->pkt = NULL; + pj_dns_packet_dup(cache->pool, pkt, + PJ_DNS_NO_NS | PJ_DNS_NO_AR, + &cache->pkt); + + /* Calculate expiration time */ + if (set_expiry) { + pj_gettimeofday(&cache->expiry_time); + cache->expiry_time.sec += ttl; + } else { + cache->expiry_time.sec = 0x7FFFFFFFL; + cache->expiry_time.msec = 0; + } + + /* Copy key to the cached response */ + pj_memcpy(&cache->key, key, sizeof(*key)); + + /* Update the hash table */ + pj_hash_set_np(resolver->hrescache, &cache->key, sizeof(*key), hval, + cache->hbuf, cache); + +} + + +/* Callback to be called when query has timed out */ +static void on_timeout( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pj_dns_resolver *resolver; + pj_dns_async_query *q, *cq; + pj_status_t status; + + PJ_UNUSED_ARG(timer_heap); + + q = (pj_dns_async_query *) entry->user_data; + resolver = q->resolver; + + pj_mutex_lock(resolver->mutex); + + /* Recheck that this query is still pending, since there is a slight + * possibility of race condition (timer elapsed while at the same time + * response arrives) + */ + if (pj_hash_get(resolver->hquerybyid, &q->id, sizeof(q->id), NULL)==NULL) { + /* Yeah, this query is done. */ + pj_mutex_unlock(resolver->mutex); + return; + } + + /* Invalidate id. */ + q->timer_entry.id = 0; + + /* Check to see if we should retransmit instead of time out */ + if (q->transmit_cnt < resolver->settings.qretr_count) { + status = transmit_query(resolver, q); + if (status == PJ_SUCCESS) { + pj_mutex_unlock(resolver->mutex); + return; + } else { + /* Error occurs */ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(resolver->name.ptr, + "Error transmitting request: %s", errmsg)); + + /* Let it fallback to timeout section below */ + } + } + + /* Clear hash table entries */ + pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + + /* Call application callback, if any. */ + if (q->cb) + (*q->cb)(q->user_data, PJ_ETIMEDOUT, NULL); + + /* Call application callback for child queries. */ + cq = q->child_head.next; + while (cq != (void*)&q->child_head) { + if (cq->cb) + (*cq->cb)(cq->user_data, PJ_ETIMEDOUT, NULL); + cq = cq->next; + } + + /* Clear data */ + q->timer_entry.id = 0; + q->user_data = NULL; + + /* Put child entries into recycle list */ + cq = q->child_head.next; + while (cq != (void*)&q->child_head) { + pj_dns_async_query *next = cq->next; + pj_list_push_back(&resolver->query_free_nodes, cq); + cq = next; + } + + /* Put query entry into recycle list */ + pj_list_push_back(&resolver->query_free_nodes, q); + + pj_mutex_unlock(resolver->mutex); +} + + +/* Callback from ioqueue when packet is received */ +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + pj_dns_resolver *resolver; + pj_pool_t *pool = NULL; + pj_dns_parsed_packet *dns_pkt; + pj_dns_async_query *q; + pj_status_t status; + PJ_USE_EXCEPTION; + + + resolver = (pj_dns_resolver *) pj_ioqueue_get_user_data(key); + pj_mutex_lock(resolver->mutex); + + + /* Check for errors */ + if (bytes_read < 0) { + char errmsg[PJ_ERR_MSG_SIZE]; + + status = -bytes_read; + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(resolver->name.ptr, + "DNS resolver read error from %s:%d: %s", + pj_inet_ntoa(resolver->udp_src_addr.sin_addr), + pj_ntohs(resolver->udp_src_addr.sin_port), + errmsg)); + + goto read_next_packet; + } + + PJ_LOG(5,(resolver->name.ptr, + "Received %d bytes DNS response from %s:%d", + (int)bytes_read, + pj_inet_ntoa(resolver->udp_src_addr.sin_addr), + pj_ntohs(resolver->udp_src_addr.sin_port))); + + + /* Check for zero packet */ + if (bytes_read == 0) + goto read_next_packet; + + /* Create temporary pool from a fixed buffer */ + pool = pj_pool_create_on_buf("restmp", resolver->tmp_pool, + sizeof(resolver->tmp_pool)); + + /* Parse DNS response */ + status = -1; + dns_pkt = NULL; + PJ_TRY { + status = pj_dns_parse_packet(pool, resolver->udp_rx_pkt, + (unsigned)bytes_read, &dns_pkt); + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + } + PJ_END; + + /* Update nameserver status */ + report_nameserver_status(resolver, &resolver->udp_src_addr, dns_pkt); + + /* Handle parse error */ + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(3,(resolver->name.ptr, + "Error parsing DNS response from %s:%d: %s", + pj_inet_ntoa(resolver->udp_src_addr.sin_addr), + pj_ntohs(resolver->udp_src_addr.sin_port), + errmsg)); + goto read_next_packet; + } + + /* Find the query based on the transaction ID */ + q = (pj_dns_async_query*) + pj_hash_get(resolver->hquerybyid, &dns_pkt->hdr.id, + sizeof(dns_pkt->hdr.id), NULL); + if (!q) { + PJ_LOG(5,(resolver->name.ptr, + "DNS response from %s:%d id=%d discarded", + pj_inet_ntoa(resolver->udp_src_addr.sin_addr), + pj_ntohs(resolver->udp_src_addr.sin_port), + (unsigned)dns_pkt->hdr.id)); + goto read_next_packet; + } + + /* Map DNS Rcode in the response into PJLIB status name space */ + status = PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_GET_RCODE(dns_pkt->hdr.flags)); + + /* Cancel query timeout timer. */ + pj_assert(q->timer_entry.id != 0); + pj_timer_heap_cancel(resolver->timer, &q->timer_entry); + q->timer_entry.id = 0; + + /* Clear hash table entries */ + pj_hash_set(NULL, resolver->hquerybyid, &q->id, sizeof(q->id), 0, NULL); + pj_hash_set(NULL, resolver->hquerybyres, &q->key, sizeof(q->key), 0, NULL); + + /* Workaround for deadlock problem in #1108 */ + pj_mutex_unlock(resolver->mutex); + + /* Notify applications first, to allow application to modify the + * record before it is saved to the hash table. + */ + if (q->cb) + (*q->cb)(q->user_data, status, dns_pkt); + + /* If query has subqueries, notify subqueries's application callback */ + if (!pj_list_empty(&q->child_head)) { + pj_dns_async_query *child_q; + + child_q = q->child_head.next; + while (child_q != (pj_dns_async_query*)&q->child_head) { + if (child_q->cb) + (*child_q->cb)(child_q->user_data, status, dns_pkt); + child_q = child_q->next; + } + } + + /* Workaround for deadlock problem in #1108 */ + pj_mutex_lock(resolver->mutex); + + /* Save/update response cache. */ + update_res_cache(resolver, &q->key, status, PJ_TRUE, dns_pkt); + + /* Recycle query objects, starting with the child queries */ + if (!pj_list_empty(&q->child_head)) { + pj_dns_async_query *child_q; + + child_q = q->child_head.next; + while (child_q != (pj_dns_async_query*)&q->child_head) { + pj_dns_async_query *next = child_q->next; + pj_list_erase(child_q); + pj_list_push_back(&resolver->query_free_nodes, child_q); + child_q = next; + } + } + pj_list_push_back(&resolver->query_free_nodes, q); + +read_next_packet: + if (pool) { + /* needed just in case PJ_HAS_POOL_ALT_API is set */ + pj_pool_release(pool); + } + bytes_read = sizeof(resolver->udp_rx_pkt); + resolver->udp_addr_len = sizeof(resolver->udp_src_addr); + status = pj_ioqueue_recvfrom(resolver->udp_key, op_key, + resolver->udp_rx_pkt, + &bytes_read, PJ_IOQUEUE_ALWAYS_ASYNC, + &resolver->udp_src_addr, + &resolver->udp_addr_len); + if (status != PJ_EPENDING) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(resolver->name.ptr, "DNS resolver ioqueue read error: %s", + errmsg)); + + pj_assert(!"Unhandled error"); + } + + pj_mutex_unlock(resolver->mutex); +} + + +/* + * Put the specified DNS packet into DNS cache. This function is mainly used + * for testing the resolver, however it can also be used to inject entries + * into the resolver. + */ +PJ_DEF(pj_status_t) pj_dns_resolver_add_entry( pj_dns_resolver *resolver, + const pj_dns_parsed_packet *pkt, + pj_bool_t set_ttl) +{ + struct res_key key; + + /* Sanity check */ + PJ_ASSERT_RETURN(resolver && pkt, PJ_EINVAL); + + /* Packet must be a DNS response */ + PJ_ASSERT_RETURN(PJ_DNS_GET_QR(pkt->hdr.flags) & 1, PJ_EINVAL); + + /* Make sure there are answers in the packet */ + PJ_ASSERT_RETURN((pkt->hdr.anscount && pkt->ans) || + (pkt->hdr.qdcount && pkt->q), + PJLIB_UTIL_EDNSNOANSWERREC); + + pj_mutex_lock(resolver->mutex); + + /* Build resource key for looking up hash tables */ + pj_bzero(&key, sizeof(struct res_key)); + if (pkt->hdr.anscount) { + /* Make sure name is not too long. */ + PJ_ASSERT_RETURN(pkt->ans[0].name.slen < PJ_MAX_HOSTNAME, + PJ_ENAMETOOLONG); + + init_res_key(&key, pkt->ans[0].type, &pkt->ans[0].name); + + } else { + /* Make sure name is not too long. */ + PJ_ASSERT_RETURN(pkt->q[0].name.slen < PJ_MAX_HOSTNAME, + PJ_ENAMETOOLONG); + + init_res_key(&key, pkt->q[0].type, &pkt->q[0].name); + } + + /* Insert entry. */ + update_res_cache(resolver, &key, PJ_SUCCESS, set_ttl, pkt); + + pj_mutex_unlock(resolver->mutex); + + return PJ_SUCCESS; +} + + +/* + * Get the total number of response in the response cache. + */ +PJ_DEF(unsigned) pj_dns_resolver_get_cached_count(pj_dns_resolver *resolver) +{ + unsigned count; + + PJ_ASSERT_RETURN(resolver, 0); + + pj_mutex_lock(resolver->mutex); + count = pj_hash_count(resolver->hrescache); + pj_mutex_unlock(resolver->mutex); + + return count; +} + + +/* + * Dump resolver state to the log. + */ +PJ_DEF(void) pj_dns_resolver_dump(pj_dns_resolver *resolver, + pj_bool_t detail) +{ +#if PJ_LOG_MAX_LEVEL >= 3 + unsigned i; + pj_time_val now; + + pj_mutex_lock(resolver->mutex); + + pj_gettimeofday(&now); + + PJ_LOG(3,(resolver->name.ptr, " Dumping resolver state:")); + + PJ_LOG(3,(resolver->name.ptr, " Name servers:")); + for (i=0; i<resolver->ns_count; ++i) { + const char *state_names[] = { "probing", "active", "bad"}; + struct nameserver *ns = &resolver->ns[i]; + + PJ_LOG(3,(resolver->name.ptr, + " NS %d: %s:%d (state=%s until %ds, rtt=%d ms)", + i, pj_inet_ntoa(ns->addr.sin_addr), + pj_ntohs(ns->addr.sin_port), + state_names[ns->state], + ns->state_expiry.sec - now.sec, + PJ_TIME_VAL_MSEC(ns->rt_delay))); + } + + PJ_LOG(3,(resolver->name.ptr, " Nb. of cached responses: %u", + pj_hash_count(resolver->hrescache))); + if (detail) { + pj_hash_iterator_t itbuf, *it; + it = pj_hash_first(resolver->hrescache, &itbuf); + while (it) { + struct cached_res *cache; + cache = (struct cached_res*)pj_hash_this(resolver->hrescache, it); + PJ_LOG(3,(resolver->name.ptr, + " Type %s: %s", + pj_dns_get_type_name(cache->key.qtype), + cache->key.name)); + it = pj_hash_next(resolver->hrescache, it); + } + } + PJ_LOG(3,(resolver->name.ptr, " Nb. of pending queries: %u (%u)", + pj_hash_count(resolver->hquerybyid), + pj_hash_count(resolver->hquerybyres))); + if (detail) { + pj_hash_iterator_t itbuf, *it; + it = pj_hash_first(resolver->hquerybyid, &itbuf); + while (it) { + struct pj_dns_async_query *q; + q = (pj_dns_async_query*) pj_hash_this(resolver->hquerybyid, it); + PJ_LOG(3,(resolver->name.ptr, + " Type %s: %s", + pj_dns_get_type_name(q->key.qtype), + q->key.name)); + it = pj_hash_next(resolver->hquerybyid, it); + } + } + PJ_LOG(3,(resolver->name.ptr, " Nb. of pending query free nodes: %u", + pj_list_size(&resolver->query_free_nodes))); + PJ_LOG(3,(resolver->name.ptr, " Nb. of timer entries: %u", + pj_timer_heap_count(resolver->timer))); + PJ_LOG(3,(resolver->name.ptr, " Pool capacity: %d, used size: %d", + pj_pool_get_capacity(resolver->pool), + pj_pool_get_used_size(resolver->pool))); + + pj_mutex_unlock(resolver->mutex); +#endif +} + |