/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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_grp_lock_t *grp_lock; /**< Group lock 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 transmit buffer. */ 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 udp_src_addr; /**< Source address of packet */ int udp_addr_len; /**< Source address length. */ #if PJ_HAS_IPV6 /* IPv6 socket */ pj_sock_t udp6_sock; /**< UDP socket. */ pj_ioqueue_key_t *udp6_key; /**< UDP socket ioqueue key. */ unsigned char udp6_rx_pkt[UDPSZ];/**< UDP receive buffer. */ //unsigned char udp6_tx_pkt[UDPSZ];/**< UDP transmit buffer. */ pj_ioqueue_op_key_t udp6_op_rx_key;/**< UDP read operation key. */ pj_ioqueue_op_key_t udp6_op_tx_key;/**< UDP write operation key. */ pj_sockaddr udp6_src_addr; /**< Source address of packet */ int udp6_addr_len; /**< Source address length. */ #endif /* 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[]); /* Destructor */ static void dns_resolver_on_destroy(void *member); /* 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; } #if PJ_HAS_IPV6 if (resv->udp6_key != NULL) { pj_ioqueue_unregister(resv->udp6_key); resv->udp6_key = NULL; resv->udp6_sock = PJ_INVALID_SOCKET; } else if (resv->udp6_sock != PJ_INVALID_SOCKET) { pj_sock_close(resv->udp6_sock); resv->udp6_sock = PJ_INVALID_SOCKET; } #endif } /* Initialize UDP socket */ static pj_status_t init_sock(pj_dns_resolver *resv) { pj_ioqueue_callback socket_cb; pj_sockaddr bound_addr; pj_ssize_t rx_pkt_size; 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_sock2(resv->pool, resv->ioqueue, resv->udp_sock, resv->grp_lock, 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 */ rx_pkt_size = 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, &rx_pkt_size, PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp_src_addr, &resv->udp_addr_len); if (status != PJ_EPENDING) return status; #if PJ_HAS_IPV6 /* Also setup IPv6 socket */ /* Create the UDP socket */ status = pj_sock_socket(pj_AF_INET6(), pj_SOCK_DGRAM(), 0, &resv->udp6_sock); if (status != PJ_SUCCESS) { /* Skip IPv6 socket on system without IPv6 (see ticket #1953) */ if (status == PJ_STATUS_FROM_OS(OSERR_EAFNOSUPPORT)) { PJ_LOG(3,(resv->name.ptr, "System does not support IPv6, resolver will " "ignore any IPv6 nameservers")); return PJ_SUCCESS; } return status; } /* Bind to any address/port */ pj_sockaddr_init(pj_AF_INET6(), &bound_addr, NULL, 0); status = pj_sock_bind(resv->udp6_sock, &bound_addr, pj_sockaddr_get_len(&bound_addr)); 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_sock2(resv->pool, resv->ioqueue, resv->udp6_sock, resv->grp_lock, resv, &socket_cb, &resv->udp6_key); if (status != PJ_SUCCESS) return status; pj_ioqueue_op_key_init(&resv->udp6_op_rx_key, sizeof(resv->udp6_op_rx_key)); pj_ioqueue_op_key_init(&resv->udp6_op_tx_key, sizeof(resv->udp6_op_tx_key)); /* Start asynchronous read to the UDP socket */ rx_pkt_size = sizeof(resv->udp6_rx_pkt); resv->udp6_addr_len = sizeof(resv->udp6_src_addr); status = pj_ioqueue_recvfrom(resv->udp6_key, &resv->udp6_op_rx_key, resv->udp6_rx_pkt, &rx_pkt_size, PJ_IOQUEUE_ALWAYS_ASYNC, &resv->udp6_src_addr, &resv->udp6_addr_len); if (status != PJ_EPENDING) return status; #else PJ_UNUSED_ARG(bound_addr); #endif 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 group lock */ status = pj_grp_lock_create_w_handler(pool, NULL, resv, &dns_resolver_on_destroy, &resv->grp_lock); if (status != PJ_SUCCESS) goto on_error; pj_grp_lock_add_ref(resv->grp_lock); /* 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) { resv->own_timer = PJ_TRUE; 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) { resv->own_ioqueue = PJ_TRUE; 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; } void dns_resolver_on_destroy(void *member) { pj_dns_resolver *resolver = (pj_dns_resolver*)member; pj_pool_safe_release(&resolver->pool); } /* * 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; } pj_grp_lock_dec_ref(resolver->grp_lock); 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_grp_lock_acquire(resolver->grp_lock); 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; ins[i]; status = pj_sockaddr_init(pj_AF_INET(), &ns->addr, &servers[i], (pj_uint16_t)(ports ? ports[i] : PORT)); if (status != PJ_SUCCESS) status = pj_sockaddr_init(pj_AF_INET6(), &ns->addr, &servers[i], (pj_uint16_t)(ports ? ports[i] : PORT)); if (status != PJ_SUCCESS) { pj_grp_lock_release(resolver->grp_lock); return PJLIB_UTIL_EDNSINNSADDR; } ns->state = STATE_ACTIVE; ns->state_expiry = now; ns->rt_delay.sec = 10; } resolver->ns_count = count; pj_grp_lock_release(resolver->grp_lock); 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_grp_lock_acquire(resolver->grp_lock); pj_memcpy(&resolver->settings, st, sizeof(*st)); pj_grp_lock_release(resolver->grp_lock); 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_grp_lock_acquire(resolver->grp_lock); pj_memcpy(st, &resolver->settings, sizeof(*st)); pj_grp_lock_release(resolver->grp_lock); 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_grp_lock_acquire(resolver->grp_lock); pj_timer_heap_poll(resolver->timer, NULL); pj_grp_lock_release(resolver->grp_lock); 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, send_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_w_grp_lock(resolver->timer, &q->timer_entry, &delay, 1, resolver->grp_lock); 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) #if PJ_HAS_IPV6 || (resolver->udp6_key && pj_ioqueue_is_pending(resolver->udp6_key, &resolver->udp6_op_tx_key)) #endif ) { ++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 */ send_cnt = 0; for (i=0; ins[servers[i]]; if (ns->addr.addr.sa_family == pj_AF_INET()) { status = pj_ioqueue_sendto(resolver->udp_key, &resolver->udp_op_tx_key, resolver->udp_tx_pkt, &sent, 0, &ns->addr, pj_sockaddr_get_len(&ns->addr)); if (status == PJ_SUCCESS || status == PJ_EPENDING) send_cnt++; } #if PJ_HAS_IPV6 else if (resolver->udp6_key) { status = pj_ioqueue_sendto(resolver->udp6_key, &resolver->udp6_op_tx_key, resolver->udp_tx_pkt, &sent, 0, &ns->addr, pj_sockaddr_get_len(&ns->addr)); if (status == PJ_SUCCESS || status == PJ_EPENDING) send_cnt++; } #endif else { continue; } 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_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), 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; } } if (send_cnt == 0) { pj_timer_heap_cancel(resolver->timer, &q->timer_entry); return PJLIB_UTIL_EDNSNOWORKINGNS; } ++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; ipool->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, *p_q = NULL; 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); /* Build resource key for looking up hash tables */ init_res_key(&key, type, name); /* Start working with the resolver */ pj_grp_lock_acquire(resolver->grp_lock); /* 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_grp_lock_release(resolver->grp_lock); /* 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_grp_lock_acquire(resolver->grp_lock); /* 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; /* * We cannot write to *p_query after calling cb because what * p_query points to may have been freed by cb. * Refer to ticket #1974. */ pj_grp_lock_release(resolver->grp_lock); return status; } /* 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. */ p_q = nq; 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); p_q = q; on_return: if (p_query) *p_query = p_q; pj_grp_lock_release(resolver->grp_lock); 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_grp_lock_acquire(query->resolver->grp_lock); if (query->timer_entry.id == 1) { pj_timer_heap_cancel_if_active(query->resolver->timer, &query->timer_entry, 0); } cb = query->cb; query->cb = NULL; if (notify) (*cb)(query->user_data, PJ_ECANCELLED, NULL); pj_grp_lock_release(query->resolver->grp_lock); 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; } /* * DNS response containing A and/or AAAA packet. */ PJ_DEF(pj_status_t) pj_dns_parse_addr_response( const pj_dns_parsed_packet *pkt, pj_dns_addr_record *rec) { enum { MAX_SEARCH = 20 }; pj_str_t hostname, alias = {NULL, 0}, *resname; pj_size_t bufstart = 0; pj_size_t bufleft; unsigned i, ansidx, cnt=0; PJ_ASSERT_RETURN(pkt && rec, PJ_EINVAL); /* Init the record */ pj_bzero(rec, sizeof(*rec)); bufleft = sizeof(rec->buf_); /* 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 && 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 (cnt >= MAX_SEARCH) return PJLIB_UTIL_EDNSINANSWER; if (pkt->ans[ansidx].type != PJ_DNS_TYPE_A && pkt->ans[ansidx].type != PJ_DNS_TYPE_AAAA) { 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. */ cnt = 0; for (i=0; i < pkt->hdr.anscount && cnt < PJ_DNS_MAX_IP_IN_A_REC ; ++i) { if ((pkt->ans[i].type == PJ_DNS_TYPE_A || pkt->ans[i].type == PJ_DNS_TYPE_AAAA) && pj_stricmp(&pkt->ans[i].name, resname)==0) { if (pkt->ans[i].type == PJ_DNS_TYPE_A) { rec->addr[cnt].af = pj_AF_INET(); rec->addr[cnt].ip.v4 = pkt->ans[i].rdata.a.ip_addr; } else { rec->addr[cnt].af = pj_AF_INET6(); rec->addr[cnt].ip.v6 = pkt->ans[i].rdata.aaaa.ip_addr; } ++cnt; } } rec->addr_count = cnt; if (cnt == 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; char addr[PJ_INET6_ADDRSTRLEN]; 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_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), 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; ins_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; ins_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 *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; } /* Some nameserver is reported to respond with PJ_DNS_RCODE_SERVFAIL for * missing AAAA record, and the standard doesn't seem to specify that * SERVFAIL should prevent the server to be contacted again for other * queries. So let's not mark nameserver as bad for SERVFAIL response. */ 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; ins_count; ++i) { struct nameserver *ns = &resolver->ns[i]; if (pj_sockaddr_cmp(&ns->addr, ns_addr) == 0) { 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; ihdr.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; /* Get a cache response entry */ cache = (struct cached_res *) pj_hash_get(resolver->hrescache, key, sizeof(*key), &hval); /* If TTL is zero, clear the same entry in the hash table */ if (ttl == 0) { /* 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; } if (cache == NULL) { 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); 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 { /* 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_grp_lock_acquire(resolver->grp_lock); /* 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_grp_lock_release(resolver->grp_lock); 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_grp_lock_release(resolver->grp_lock); return; } else { /* Error occurs */ PJ_PERROR(4,(resolver->name.ptr, status, "Error transmitting request")); /* 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_grp_lock_release(resolver->grp_lock); /* 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_grp_lock_acquire(resolver->grp_lock); /* 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_grp_lock_release(resolver->grp_lock); } /* 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; char addr[PJ_INET6_ADDRSTRLEN]; pj_sockaddr *src_addr; int *src_addr_len; unsigned char *rx_pkt; pj_ssize_t rx_pkt_size; pj_status_t status; PJ_USE_EXCEPTION; resolver = (pj_dns_resolver *) pj_ioqueue_get_user_data(key); pj_assert(resolver); #if PJ_HAS_IPV6 if (key == resolver->udp6_key) { src_addr = &resolver->udp6_src_addr; src_addr_len = &resolver->udp6_addr_len; rx_pkt = resolver->udp6_rx_pkt; rx_pkt_size = sizeof(resolver->udp6_rx_pkt); } else #endif { src_addr = &resolver->udp_src_addr; src_addr_len = &resolver->udp_addr_len; rx_pkt = resolver->udp_rx_pkt; rx_pkt_size = sizeof(resolver->udp_rx_pkt); } pj_grp_lock_acquire(resolver->grp_lock); /* Check for errors */ if (bytes_read < 0) { status = (pj_status_t)-bytes_read; PJ_PERROR(4,(resolver->name.ptr, status, "DNS resolver read error")); goto read_next_packet; } PJ_LOG(5,(resolver->name.ptr, "Received %d bytes DNS response from %s:%d", (int)bytes_read, pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr))); /* 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, rx_pkt, (unsigned)bytes_read, &dns_pkt); } PJ_CATCH_ANY { status = PJ_ENOMEM; } PJ_END; /* Update nameserver status */ report_nameserver_status(resolver, src_addr, dns_pkt); /* Handle parse error */ if (status != PJ_SUCCESS) { PJ_PERROR(3,(resolver->name.ptr, status, "Error parsing DNS response from %s:%d", pj_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr))); 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_sockaddr_print(src_addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(src_addr), (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_grp_lock_release(resolver->grp_lock); /* 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_grp_lock_acquire(resolver->grp_lock); /* Truncated responses MUST NOT be saved (cached). */ if (PJ_DNS_GET_TC(dns_pkt->hdr.flags) == 0) { /* 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); } status = pj_ioqueue_recvfrom(key, op_key, rx_pkt, &rx_pkt_size, PJ_IOQUEUE_ALWAYS_ASYNC, src_addr, src_addr_len); if (status != PJ_EPENDING && status != PJ_ECANCELLED) { PJ_PERROR(4,(resolver->name.ptr, status, "DNS resolver ioqueue read error")); pj_assert(!"Unhandled error"); } pj_grp_lock_release(resolver->grp_lock); } /* * 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_grp_lock_acquire(resolver->grp_lock); /* 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_grp_lock_release(resolver->grp_lock); 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_grp_lock_acquire(resolver->grp_lock); count = pj_hash_count(resolver->hrescache); pj_grp_lock_release(resolver->grp_lock); 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_grp_lock_acquire(resolver->grp_lock); pj_gettimeofday(&now); PJ_LOG(3,(resolver->name.ptr, " Dumping resolver state:")); PJ_LOG(3,(resolver->name.ptr, " Name servers:")); for (i=0; ins_count; ++i) { char addr[PJ_INET6_ADDRSTRLEN]; 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_sockaddr_print(&ns->addr, addr, sizeof(addr), 2), pj_sockaddr_get_port(&ns->addr), 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_grp_lock_release(resolver->grp_lock); #endif }