📄 resolver.c
字号:
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(resolver->pool, sizeof(*q));
}
/* Init query */
q->resolver = resolver;
q->options = options;
q->user_data = user_data;
q->cb = cb;
pj_list_init(&q->child_head);
return q;
}
/*
* Transmit query.
*/
static pj_status_t transmit_query(pj_dns_resolver *resolver,
pj_dns_async_query *q)
{
unsigned pkt_size;
unsigned i, server_cnt;
unsigned servers[PJ_DNS_RESOLVER_MAX_NS];
pj_time_val now;
pj_str_t name;
pj_time_val delay;
pj_status_t status;
/* Create DNS query packet */
pkt_size = sizeof(resolver->udp_tx_pkt);
name = pj_str(q->key.name);
status = pj_dns_make_query(resolver->udp_tx_pkt, &pkt_size,
q->id, q->key.qtype, &name);
if (status != PJ_SUCCESS) {
return status;
}
/* Select which nameserver(s) to send requests to. */
server_cnt = PJ_ARRAY_SIZE(servers);
status = select_nameservers(resolver, &server_cnt, servers);
if (status != PJ_SUCCESS) {
return status;
}
if (server_cnt == 0) {
return PJLIB_UTIL_EDNSNOWORKINGNS;
}
/* Start retransmit/timeout timer for the query */
pj_assert(q->timer_entry.id == 0);
q->timer_entry.id = 1;
q->timer_entry.user_data = q;
q->timer_entry.cb = &on_timeout;
delay.sec = 0;
delay.msec = resolver->settings.qretr_delay;
pj_time_val_normalize(&delay);
status = pj_timer_heap_schedule(resolver->timer, &q->timer_entry, &delay);
if (status != PJ_SUCCESS) {
return status;
}
/* Get current time. */
pj_gettimeofday(&now);
/* Send the packet to name servers */
for (i=0; i<server_cnt; ++i) {
pj_ssize_t sent = (pj_ssize_t) pkt_size;
struct nameserver *ns = &resolver->ns[servers[i]];
pj_sock_sendto(resolver->udp_sock, resolver->udp_tx_pkt, &sent, 0,
&resolver->ns[servers[i]].addr, sizeof(pj_sockaddr_in));
PJ_LOG(4,(resolver->name.ptr,
"%s %d bytes to NS %d (%s:%d): DNS %s query for %s",
(q->transmit_cnt==0? "Transmitting":"Re-transmitting"),
(int)sent, servers[i],
pj_inet_ntoa(ns->addr.sin_addr),
(int)pj_ntohs(ns->addr.sin_port),
pj_dns_get_type_name(q->key.qtype),
q->key.name));
if (ns->q_id == 0) {
ns->q_id = q->id;
ns->sent_time = now;
}
}
++q->transmit_cnt;
return PJ_SUCCESS;
}
/*
* Initialize resource key for hash table lookup.
*/
static void init_res_key(struct res_key *key, int type, const pj_str_t *name)
{
unsigned i, len;
char *dst = key->name;
const char *src = name->ptr;
pj_bzero(key, sizeof(struct res_key));
key->qtype = (pj_uint16_t)type;
len = name->slen;
if (len > PJ_MAX_HOSTNAME) len = PJ_MAX_HOSTNAME;
/* Copy key, in lowercase */
for (i=0; i<len; ++i) {
*dst++ = (char)pj_tolower(*src++);
}
}
/*
* 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 = pj_hash_get(resolver->hrescache, &key, sizeof(key), &hval);
if (cache) {
/* We've found a cached entry. */
/* Check for expiration */
if (PJ_TIME_VAL_GT(cache->expiry_time, now)) {
/* Log */
PJ_LOG(5,(resolver->name.ptr,
"Picked up DNS %s record for %.*s from cache, ttl=%d",
pj_dns_get_type_name(type),
(int)name->slen, name->ptr,
(int)(cache->expiry_time.sec - now.sec)));
/* Map DNS Rcode in the response into PJLIB status name space */
status = PJ_DNS_GET_RCODE(cache->pkt->hdr.flags);
status = PJ_STATUS_FROM_DNS_RCODE(status);
/* This cached response is still valid. Just return this
* response to caller.
*/
if (cb) {
(*cb)(user_data, status, cache->pkt);
}
/* Done. No host resolution is necessary */
/* Must return PJ_SUCCESS */
status = PJ_SUCCESS;
goto on_return;
}
/* At this point, we have a cached entry, but this entry has expired.
* Remove this entry from the cached list.
*/
pj_hash_set(NULL, resolver->hrescache, &key, sizeof(key), 0, NULL);
/* Store the entry into free nodes */
pj_list_push_back(&resolver->res_free_nodes, cache);
/* Must continue with creating a query now */
}
/* Next, check if we have pending query on the same resource */
q = 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 */
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;
}
/* 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];
ns->state = state;
ns->state_expiry = *now;
if (state == STATE_PROBING)
ns->state_expiry.sec += ((PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT + 2) *
PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY) / 1000;
else if (state == STATE_ACTIVE)
ns->state_expiry.sec += PJ_DNS_RESOLVER_GOOD_NS_TTL;
else
ns->state_expiry.sec += PJ_DNS_RESOLVER_BAD_NS_TTL;
}
/* 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_pool_t *res_pool;
pj_uint32_t hval=0, ttl;
PJ_USE_EXCEPTION;
/* If status is unsuccessful, clear the same entry from the cache */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -