📄 resolver.c
字号:
/* $Id: resolver.c 1033 2007-03-02 14:51:03Z bennylp $ */
/*
* Copyright (C) 2003-2007 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/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,
};
/*
* 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. */
char hbufid[PJ_HASH_ENTRY_SIZE]; /**< Hash buffer 1 */
char hbufkey[PJ_HASH_ENTRY_SIZE]; /**< 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);
struct res_key key; /**< Resource key. */
char buf[RES_BUF_SZ];/**< Resource buffer. */
char hbuf[PJ_HASH_ENTRY_SIZE]; /**< Hash buffer */
pj_time_val expiry_time; /**< Expiration time. */
pj_dns_parsed_packet *pkt; /**< The response packet. */
};
/* Resolver entry */
struct pj_dns_resolver
{
pj_str_t name; /**< Resolver instance name for id. */
/* Internals */
pj_pool_t *pool; /**< Internal pool. */
pj_mutex_t *mutex; /**< Mutex protection. */
pj_bool_t own_timer; /**< Do we own timer? */
pj_timer_heap_t *timer; /**< Timer instance. */
pj_bool_t own_ioqueue; /**< Do we own ioqueue? */
pj_ioqueue_t *ioqueue; /**< Ioqueue instance. */
char tmp_pool[TMP_SZ];/**< Temporary pool buffer. */
/* Socket */
pj_sock_t udp_sock; /**< UDP socket. */
pj_ioqueue_key_t *udp_key; /**< UDP socket ioqueue key. */
unsigned char udp_rx_pkt[UDPSZ];/**< UDP receive buffer. */
unsigned char udp_tx_pkt[UDPSZ];/**< UDP receive buffer. */
pj_ssize_t udp_len; /**< Length of received packet. */
pj_ioqueue_op_key_t udp_op_key; /**< UDP read operation key. */
pj_sockaddr_in udp_src_addr; /**< Source address of packet */
int udp_addr_len; /**< Source address length. */
/* Settings */
pj_dns_settings settings; /**< Resolver settings. */
/* Nameservers */
unsigned ns_count; /**< Number of name servers. */
struct nameserver ns[PJ_DNS_RESOLVER_MAX_NS]; /**< Array of NS. */
/* Last DNS transaction ID used. */
pj_uint16_t last_id;
/* Hash table for cached response */
pj_hash_table_t *hrescache; /**< Cached response in hash table */
/* Cached response free list */
struct cached_res res_free_nodes;
/* 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[]);
/*
* Create the resolver.
*/
PJ_DEF(pj_status_t) pj_dns_resolver_create( pj_pool_factory *pf,
const char *name,
unsigned options,
pj_timer_heap_t *timer,
pj_ioqueue_t *ioqueue,
pj_dns_resolver **p_resolver)
{
pj_pool_t *pool;
pj_dns_resolver *resv;
pj_ioqueue_callback socket_cb;
pj_status_t status;
/* Sanity check */
PJ_ASSERT_RETURN(pf && p_resolver, PJ_EINVAL);
if (name == NULL)
name = THIS_FILE;
/* Create and initialize resolver instance */
pool = pj_pool_create(pf, name, 4000, 4000, NULL);
if (!pool)
return PJ_ENOMEM;
/* Create pool and name */
resv = pj_pool_zalloc(pool, sizeof(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;
resv->settings.options = options;
resv->settings.qretr_delay = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_DELAY;
resv->settings.qretr_count = PJ_DNS_RESOLVER_QUERY_RETRANSMIT_COUNT;
resv->settings.cache_max_ttl = PJ_DNS_RESOLVER_MAX_TTL;
/* 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 and item list */
resv->hrescache = pj_hash_create(pool, RES_HASH_TABLE_SIZE);
pj_list_init(&resv->res_free_nodes);
/* Query hash table and free list. */
resv->hquerybyid = pj_hash_create(pool, Q_HASH_TABLE_SIZE);
resv->hquerybyres = pj_hash_create(pool, Q_HASH_TABLE_SIZE);
pj_list_init(&resv->query_free_nodes);
/* Create the UDP socket */
status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &resv->udp_sock);
if (status != PJ_SUCCESS)
goto on_error;
/* Register to ioqueue */
pj_bzero(&socket_cb, sizeof(socket_cb));
socket_cb.on_read_complete = &on_read_complete;
status = pj_ioqueue_register_sock(pool, resv->ioqueue, resv->udp_sock,
resv, &socket_cb, &resv->udp_key);
if (status != PJ_SUCCESS)
goto on_error;
pj_ioqueue_op_key_init(&resv->udp_op_key, sizeof(resv->udp_op_key));
/* Start asynchronous read to the UDP socket */
resv->udp_len = sizeof(resv->udp_rx_pkt);
resv->udp_addr_len = sizeof(resv->udp_src_addr);
status = pj_ioqueue_recvfrom(resv->udp_key, &resv->udp_op_key,
resv->udp_rx_pkt, &resv->udp_len,
PJ_IOQUEUE_ALWAYS_ASYNC,
&resv->udp_src_addr, &resv->udp_addr_len);
if (status != PJ_EPENDING)
goto on_error;
/* Looks like everything is okay */
*p_resolver = resv;
return PJ_SUCCESS;
on_error:
pj_dns_resolver_destroy(resv, PJ_FALSE);
return status;
}
/*
* Destroy DNS resolver instance.
*/
PJ_DEF(pj_status_t) pj_dns_resolver_destroy( pj_dns_resolver *resolver,
pj_bool_t notify)
{
PJ_ASSERT_RETURN(resolver, PJ_EINVAL);
if (notify) {
/*
* Notify pending queries if requested.
*/
pj_hash_iterator_t it_buf, *it;
it = pj_hash_first(resolver->hquerybyid, &it_buf);
while (it) {
pj_dns_async_query *q = 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);
}
}
if (resolver->own_timer && resolver->timer) {
pj_timer_heap_destroy(resolver->timer);
resolver->timer = NULL;
}
if (resolver->own_ioqueue && resolver->ioqueue) {
pj_ioqueue_destroy(resolver->ioqueue);
resolver->ioqueue = NULL;
}
if (resolver->udp_key != NULL) {
pj_ioqueue_unregister(resolver->udp_key);
resolver->udp_key = NULL;
resolver->udp_sock = PJ_INVALID_SOCKET;
} else if (resolver->udp_sock != PJ_INVALID_SOCKET) {
pj_sock_close(resolver->udp_sock);
resolver->udp_sock = PJ_INVALID_SOCKET;
}
if (resolver->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_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)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -