| /* $Id$ */ |
| /* |
| * 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. */ |
| unsigned ref_cnt; /**< Reference counter. */ |
| }; |
| |
| |
| /* 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_rx_key; /**< UDP read operation key. */ |
| pj_ioqueue_op_key_t udp_op_tx_key; /**< UDP write 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[]); |
| |
| |
| /* Close UDP socket */ |
| static void close_sock(pj_dns_resolver *resv) |
| { |
| /* Close existing socket */ |
| if (resv->udp_key != NULL) { |
| pj_ioqueue_unregister(resv->udp_key); |
| resv->udp_key = NULL; |
| resv->udp_sock = PJ_INVALID_SOCKET; |
| } else if (resv->udp_sock != PJ_INVALID_SOCKET) { |
| pj_sock_close(resv->udp_sock); |
| resv->udp_sock = PJ_INVALID_SOCKET; |
| } |
| } |
| |
| |
| /* Initialize UDP socket */ |
| static pj_status_t init_sock(pj_dns_resolver *resv) |
| { |
| pj_ioqueue_callback socket_cb; |
| pj_status_t status; |
| |
| /* Create the UDP socket */ |
| status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &resv->udp_sock); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Bind to any address/port */ |
| status = pj_sock_bind_in(resv->udp_sock, 0, 0); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Register to ioqueue */ |
| pj_bzero(&socket_cb, sizeof(socket_cb)); |
| socket_cb.on_read_complete = &on_read_complete; |
| status = pj_ioqueue_register_sock(resv->pool, resv->ioqueue, |
| resv->udp_sock, resv, &socket_cb, |
| &resv->udp_key); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| pj_ioqueue_op_key_init(&resv->udp_op_rx_key, sizeof(resv->udp_op_rx_key)); |
| pj_ioqueue_op_key_init(&resv->udp_op_tx_key, sizeof(resv->udp_op_tx_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_rx_key, |
| resv->udp_rx_pkt, &resv->udp_len, |
| PJ_IOQUEUE_ALWAYS_ASYNC, |
| &resv->udp_src_addr, &resv->udp_addr_len); |
| if (status != PJ_EPENDING) |
| return status; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* 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_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); |
| |
| /* Initialize the UDP socket */ |
| status = init_sock(resv); |
| if (status != PJ_SUCCESS) |
| 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; |
| } |
| |
| close_sock(resolver); |
| |
| 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; |
| |
| /* 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; |
| } |
| |
| /* Check if the socket is available for sending */ |
| if (pj_ioqueue_is_pending(resolver->udp_key, &resolver->udp_op_tx_key)) { |
| ++q->transmit_cnt; |
| PJ_LOG(4,(resolver->name.ptr, |
| "Socket busy in transmitting DNS %s query for %s%s", |
| pj_dns_get_type_name(q->key.qtype), |
| q->key.name, |
| (q->transmit_cnt < resolver->settings.qretr_count? |
| ", will try again later":""))); |
| return PJ_SUCCESS; |
| } |
| |
| /* 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) { |
| pj_timer_heap_cancel(resolver->timer, &q->timer_entry); |
| 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]]; |
| |
| status = pj_ioqueue_sendto(resolver->udp_key, |
| &resolver->udp_op_tx_key, |
| resolver->udp_tx_pkt, &sent, 0, |
| &resolver->ns[servers[i]].addr, |
| sizeof(pj_sockaddr_in)); |
| |
| PJ_PERROR(4,(resolver->name.ptr, status, |
| "%s %d bytes to NS %d (%s:%d): DNS %s query for %s", |
| (q->transmit_cnt==0? "Transmitting":"Re-transmitting"), |
| (int)pkt_size, 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; |
| pj_size_t 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; |
| cache->ref_cnt = 1; |
| |
| return cache; |
| } |
| |
| /* Re-allocate cache entry, to free cached packet */ |
| static void reset_entry(struct cached_res **p_cached) |
| { |
| pj_pool_t *pool; |
| struct cached_res *cache = *p_cached; |
| unsigned ref_cnt; |
| |
| pool = cache->pool; |
| ref_cnt = cache->ref_cnt; |
| |
| pj_pool_reset(pool); |
| |
| cache = PJ_POOL_ZALLOC_T(pool, struct cached_res); |
| cache->pool = pool; |
| cache->ref_cnt = ref_cnt; |
| *p_cached = 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); |
| |
| /* Workaround for deadlock problem. Need to increment the cache's |
| * ref counter first before releasing mutex, so the cache won't be |
| * destroyed by other thread while in callback. |
| */ |
| cache->ref_cnt++; |
| pj_mutex_unlock(resolver->mutex); |
| |
| /* 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 */ |
| pj_mutex_lock(resolver->mutex); |
| |
| /* Decrement the ref counter. Also check if it is time to free |
| * the cache (as it has been expired). |
| */ |
| cache->ref_cnt--; |
| if (cache->ref_cnt <= 0) |
| free_entry(resolver, cache); |
| |
| /* 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); |
| |
| /* Also free the cache, if it is not being used (by callback). */ |
| cache->ref_cnt--; |
| if (cache->ref_cnt <= 0) |
| 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; |
| pj_size_t bufstart = 0; |
| pj_size_t 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); |
| /* Remove the entry before releasing its pool (see ticket #1710) */ |
| pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); |
| |
| /* Free the entry */ |
| if (cache && --cache->ref_cnt <= 0) |
| free_entry(resolver, cache); |
| } |
| |
| |
| /* 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); |
| /* Remove the entry before releasing its pool (see ticket #1710) */ |
| pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); |
| |
| /* Free the entry */ |
| if (cache && --cache->ref_cnt <= 0) |
| free_entry(resolver, cache); |
| 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); |
| } else if (cache->ref_cnt > 1) { |
| /* When cache entry is being used by callback (to app), just decrement |
| * ref_cnt so it will be freed after the callback returns and allocate |
| * new entry. |
| */ |
| cache->ref_cnt--; |
| cache = alloc_entry(resolver); |
| } else { |
| /* Remove the entry before resetting its pool (see ticket #1710) */ |
| pj_hash_set(NULL, resolver->hrescache, key, sizeof(*key), hval, NULL); |
| |
| /* Reset cache to avoid bloated cache pool */ |
| reset_entry(&cache); |
| } |
| |
| /* 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. |
| */ |
| 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); |
| |
| /* Workaround for deadlock problem in #1565 (similar to #1108) */ |
| pj_mutex_unlock(resolver->mutex); |
| |
| /* 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; |
| } |
| |
| /* Workaround for deadlock problem in #1565 (similar to #1108) */ |
| pj_mutex_lock(resolver->mutex); |
| |
| /* 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 = (pj_status_t)-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 |
| } |
| |