⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 resolver.c

📁 一个开源SIP协议栈
💻 C
📖 第 1 页 / 共 3 页
字号:
    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 + -