Merge "DNS: Create a system-level DNS resolver"

This commit is contained in:
Mark Michelson 2015-07-08 09:00:41 -05:00 committed by Gerrit Code Review
commit af3f4b342b
5 changed files with 628 additions and 23 deletions

View file

@ -29,6 +29,7 @@ int ast_cli_perms_init(int reload); /*!< Provided by cli.c */
int dnsmgr_init(void); /*!< Provided by dnsmgr.c */
void dnsmgr_start_refresh(void); /*!< Provided by dnsmgr.c */
int dnsmgr_reload(void); /*!< Provided by dnsmgr.c */
int ast_dns_system_resolver_init(void); /*!< Provided by dns_system_resolver.c */
void threadstorage_init(void); /*!< Provided by threadstorage.c */
int ast_device_state_engine_init(void); /*!< Provided by devicestate.c */
int astobj2_init(void); /*!< Provided by astobj2.c */

View file

@ -24,17 +24,63 @@
#ifndef _ASTERISK_DNS_H
#define _ASTERISK_DNS_H
/*! \brief Perform DNS lookup (used by DNS, enum and SRV lookups)
\param context
\param dname Domain name to lookup (host, SRV domain, TXT record name)
\param class Record Class (see "man res_search")
\param type Record type (see "man res_search")
\param callback Callback function for handling DNS result
\note Asterisk DNS is synchronus at this time. This means that if your DNS
services does not work, Asterisk may lock while waiting for response.
*/
/*! \brief DNS search return values */
enum ast_dns_search_result {
AST_DNS_SEARCH_FAILURE = -1, /*!< DNS search resulted in failure */
AST_DNS_SEARCH_NO_RECORDS = 0, /*!< DNS search yielded no results */
AST_DNS_SEARCH_SUCCESS = 1 /*!< DNS search yielded at least one discovered record */
};
/*!
* \brief Perform DNS lookup (used by DNS, enum and SRV lookups)
*
* \param context Void pointer containing data to use in the callback function.
* \param dname Domain name to lookup (host, SRV domain, TXT record name).
* \param class Record Class (see "man res_search").
* \param type Record type (see "man res_search").
* \param answer The full DNS response.
* \param len The length of the full DNS response.
* \param callback Callback function for handling the discovered resource records from
* the DNS search.
*
* \retval -1 on search failure
* \retval 0 on no records found
* \retval 1 on success
*
* \note Asterisk DNS is synchronus at this time. This means that if your DNS
* service does not work, Asterisk may lock while waiting for a response.
*/
int ast_search_dns(void *context, const char *dname, int class, int type,
int (*callback)(void *context, unsigned char *answer, int len, unsigned char *fullanswer));
int (*callback)(void *context, unsigned char *answer, int len, unsigned char *fullanswer));
/*!
* \brief Extended version of the DNS search function.
*
* \details Performs a DNS lookup, (used by DNS, enum and SRV lookups), parses the
* results and notifies the observer with the response and discovered records
* via invoking the provided callbacks (used by ast_dns_system_resolver).
*
* \param context Void pointer containing data to use in the handler functions.
* \param dname Domain name to lookup (host, SRV domain, TXT record name).
* \param rr_class Record Class (see "man res_search").
* \param rr_type Record type (see "man res_search").
* \param response_handler Callback function for handling the DNS response. Invoked upon
* completion of the DNS search.
* \param record_handler Callback function for handling the discovered resource
* records from the DNS search. Invoked n times, where n is the
* number of records discovered while parsing the DNS
* response.
*
* \retval AST_DNS_SEARCH_FAILURE on search failure
* \retval AST_DNS_SEARCH_NO_RECORDS on no records found
* \retval AST_DNS_SEARCH_SUCCESS on success
*
* \note Asterisk DNS is synchronus at this time. This means that if your DNS
* service does not work, Asterisk may lock while waiting for a response.
*/
enum ast_dns_search_result ast_search_dns_ex(void *context, const char *dname, int rr_class, int rr_type,
int (*response_handler)(void *context, unsigned char *dns_response, int dns_response_len, int rcode),
int (*record_handler)(void *context, unsigned char *record, int record_len, int ttl));
/*! \brief Retrieve the configured nameservers of the system */
struct ao2_container *ast_dns_get_nameservers(void);

View file

@ -4511,6 +4511,11 @@ int main(int argc, char *argv[])
exit(1);
}
if (ast_dns_system_resolver_init()) { /* Initialize the default DNS resolver */
printf("Failed: ast_dns_system_resolver_init\n%s", term_quit());
exit(1);
}
if ((moduleresult = load_modules(1))) { /* Load modules, pre-load only */
printf("Failed: load_modules\n%s", term_quit());
exit(moduleresult == -2 ? 2 : 1);

View file

@ -45,6 +45,7 @@ ASTERISK_REGISTER_FILE()
#include "asterisk/dns.h"
#include "asterisk/endian.h"
/*! \brief The maximum size permitted for the answer from the DNS server */
#define MAX_SIZE 4096
#ifdef __PDP_ENDIAN
@ -59,6 +60,10 @@ ASTERISK_REGISTER_FILE()
#define DETERMINED_BYTE_ORDER __LITTLE_ENDIAN
#endif
#ifndef HAVE_RES_NINIT
AST_MUTEX_DEFINE_STATIC(res_lock);
#endif
/* The dns_HEADER structure definition below originated
in the arpa/nameser.h header file distributed with ISC
BIND, which contains the following copyright and license
@ -156,12 +161,23 @@ typedef struct {
} dns_HEADER;
struct dn_answer {
unsigned short rtype;
unsigned short class;
unsigned int ttl;
unsigned short size;
unsigned short rtype; /*!< The resource record type. */
unsigned short class; /*!< The resource record class. */
unsigned int ttl; /*!< The resource record time to live. */
unsigned short size; /*!< The resource record size. */
} __attribute__((__packed__));
/*!
* \brief Tries to find the position of the next field in the DNS response.
*
* \internal
*
* \param s A char pointer to the current frame in the DNS response.
* \param len The remaining available length of the DNS response.
*
* \retval The position of the next field
* \retval -1 if there are no remaining fields
*/
static int skip_name(unsigned char *s, int len)
{
int x = 0;
@ -172,20 +188,149 @@ static int skip_name(unsigned char *s, int len)
x++;
break;
}
if ((*s & 0xc0) == 0xc0) {
s += 2;
x += 2;
break;
}
x += *s + 1;
s += *s + 1;
}
if (x >= len)
return -1;
/* If we are out of room to search, return failure. */
if (x >= len) {
return AST_DNS_SEARCH_FAILURE;
}
/* Return the value for the current position in the DNS response. This is the start
position of the next field. */
return x;
}
/*! \brief Parse DNS lookup result, call callback */
/*!
* \brief Advances the position of the DNS response pointer by the size of the current field.
*
* \internal
*
* \param dns_response A pointer to a char pointer to the current field in the DNS response.
* \param remaining_len The remaining available length in the DNS response to search.
* \param field_size A positive value representing the size of the current field
pointed to by the dns_response parameter.
*
* \retval The remaining length in the DNS response
* \retval -1 there are no frames remaining in the DNS response
*/
static int dns_advance_field(unsigned char **dns_response, int remaining_len, int field_size)
{
if (dns_response == NULL || field_size < 0 || remaining_len < field_size) {
return AST_DNS_SEARCH_FAILURE;
}
*dns_response += field_size;
remaining_len -= field_size;
return remaining_len;
}
#ifndef HAVE_RES_NINIT
/*!
* \brief Handles the DNS search if the system has RES_INIT.
*
* \internal
*
* \param dname Domain name to lookup (host, SRV domain, TXT record name).
* \param rr_class Record Class (see "man res_search").
* \param rr_type Record type (see "man res_search").
* \param dns_response The full DNS response.
* \param dns_response_len The length of the full DNS response.
*
* \retval The length of the DNS response
* \retval -1 on search failure
*/
static int dns_search_res(const char *dname, int rr_class, int rr_type,
unsigned char *dns_response, int dns_response_len)
{
int ret = AST_DNS_SEARCH_FAILURE;
struct __res_state dns_state;
ast_mutex_lock(&res_lock);
res_init();
ret = res_search(&dns_state,
dname,
rr_class,
rr_type,
dns_response,
dns_response_len);
#ifdef HAVE_RES_CLOSE
res_close();
#endif
ast_mutex_unlock(&res_lock);
return ret;
}
#else
/*!
* \brief Handles the DNS search if the system has RES_NINIT.
*
* \internal
*
* \param dname Domain name to lookup (host, SRV domain, TXT record name).
* \param rr_class Record Class (see "man res_search").
* \param rr_type Record type (see "man res_search").
* \param dns_response The full DNS response.
* \param dns_response_len The length of the full DNS response.
*
* \retval The length of the DNS response
* \retval -1 on search failure
*/
static int dns_search_res(const char *dname, int rr_class, int rr_type,
unsigned char *dns_response, int dns_response_len)
{
int ret = AST_DNS_SEARCH_FAILURE;
struct __res_state dns_state;
memset(&dns_state, 0, sizeof(dns_state));
res_ninit(&dns_state);
ret = res_nsearch(&dns_state,
dname,
rr_class,
rr_type,
dns_response,
dns_response_len);
#ifdef HAVE_RES_NDESTROY
res_ndestroy(&dns_state);
#else
res_nclose(&dns_state);
#endif
return ret;
}
#endif
/*!
* \brief Parse DNS lookup result, call callback
*
* \internal
*
* \param context Void pointer containing data to use in the callback functions.
* \param dname Domain name to lookup (host, SRV domain, TXT record name).
* \param class Record Class (see "man res_search").
* \param type Record type (see "man res_search").
* \param answer The full DNS response.
* \param len The length of the full DNS response.
* \param callback Callback function for handling the discovered resource records from the DNS search.
*
* \retval -1 on search failure
* \retval 0 on no records found
* \retval 1 on success
*/
static int dns_parse_answer(void *context,
int class, int type, unsigned char *answer, int len,
int (*callback)(void *context, unsigned char *answer, int len, unsigned char *fullanswer))
@ -244,13 +389,110 @@ static int dns_parse_answer(void *context,
return ret;
}
#ifndef HAVE_RES_NINIT
AST_MUTEX_DEFINE_STATIC(res_lock);
#endif
/*!
* \brief Extended version of the DNS Parsing function.
*
* \details Parses the DNS lookup result and notifies the observer of each discovered
* resource record with the provided callback.
*
* \internal
*
* \param context Void pointer containing data to use in the callback functions.
* \param dname Domain name to lookup (host, SRV domain, TXT record name).
* \param rr_class Record Class (see "man res_search").
* \param rr_type Record type (see "man res_search").
* \param answer The full DNS response.
* \param answer_len The length of the full DNS response.
* \param response_handler Callback function for handling the DNS response.
* \param record_handler Callback function for handling the discovered resource records from the DNS search.
*
* \retval -1 on search failure
* \retval 0 on no records found
* \retval 1 on success
*/
static int dns_parse_answer_ex(void *context, int rr_class, int rr_type, unsigned char *answer, int answer_len,
int (*response_handler)(void *context, unsigned char *dns_response, int dns_response_len, int rcode),
int (*record_handler)(void *context, unsigned char *record, int record_len, int ttl))
{
unsigned char *dns_response = answer;
dns_HEADER *dns_header = (dns_HEADER *)answer;
/*! \brief Lookup record in DNS
\note Asterisk DNS is synchronus at this time. This means that if your DNS does
not work properly, Asterisk might not start properly or a channel may lock.
struct dn_answer *ans;
int res, x, pos, dns_response_len, ret;
dns_response_len = answer_len;
ret = AST_DNS_SEARCH_NO_RECORDS;
/* Invoke the response_handler callback to notify the observer of the raw DNS response */
response_handler(context, dns_response, dns_response_len, ntohs(dns_header->rcode));
/* Verify there is something to parse */
if (answer_len == 0) {
return ret;
}
/* Try advancing the cursor for the dns header */
if ((pos = dns_advance_field(&answer, answer_len, sizeof(dns_HEADER))) < 0) {
ast_log(LOG_WARNING, "Length of DNS answer exceeds available search frames\n");
return AST_DNS_SEARCH_FAILURE;
}
/* Skip domain name and QCODE / QCLASS */
for (x = 0; x < ntohs(dns_header->qdcount); x++) {
if ((res = skip_name(answer, pos)) < 0) {
ast_log(LOG_WARNING, "Failed skipping name\n");
return AST_DNS_SEARCH_FAILURE;
}
/* Try advancing the cursor for the name and QCODE / QCLASS fields */
if ((pos = dns_advance_field(&answer, pos, res + 4)) < 0) {
return AST_DNS_SEARCH_FAILURE;
}
}
/* Extract the individual records */
for (x = 0; x < ntohs(dns_header->ancount); x++) {
if ((res = skip_name(answer, pos)) < 0) {
ast_log(LOG_WARNING, "Failed skipping name\n");
return AST_DNS_SEARCH_FAILURE;
}
/* Try advancing the cursor to the current record */
if ((pos = dns_advance_field(&answer, pos, res)) < 0) {
ast_log(LOG_WARNING, "Length of DNS answer exceeds available search frames\n");
return AST_DNS_SEARCH_FAILURE;
}
/* Cast the current value for the answer pointer as a dn_answer struct */
ans = (struct dn_answer *) answer;
/* Try advancing the cursor to the end of the current record */
if ((pos = dns_advance_field(&answer, pos, sizeof(struct dn_answer))) < 0) {
ast_log(LOG_WARNING, "Length of DNS answer exceeds available search frames\n");
return AST_DNS_SEARCH_FAILURE;
}
/* Skip over the records that do not have the same resource record class and type we care about */
if (ntohs(ans->class) == rr_class && ntohs(ans->rtype) == rr_type) {
/* Invoke the record handler callback to deliver the discovered record */
record_handler(context, answer, ntohs(ans->size), ans->ttl);
/*At least one record was found */
ret = AST_DNS_SEARCH_SUCCESS;
}
/* Try and update the field to the next record, but ignore any errors that come
* back because this may be the end of the line. */
pos = dns_advance_field(&answer, pos, res + ntohs(ans->size));
}
return ret;
}
/*!
* \brief Lookup record in DNS
*
* \note Asterisk DNS is synchronus at this time. This means that if your DNS does not
* work properly, Asterisk might not start properly or a channel may lock.
*/
int ast_search_dns(void *context,
const char *dname, int class, int type,
@ -297,6 +539,50 @@ int ast_search_dns(void *context,
return ret;
}
enum ast_dns_search_result ast_search_dns_ex(void *context, const char *dname, int rr_class, int rr_type,
int (*response_handler)(void *context, unsigned char *dns_response, int dns_response_len, int rcode),
int (*record_handler)(void *context, unsigned char *record, int record_len, int ttl))
{
int ret, dns_response_len;
unsigned char dns_response[MAX_SIZE];
/* Assert that the callbacks are not NULL */
ast_assert(response_handler != NULL);
ast_assert(record_handler != NULL);
/* Try the DNS search. */
dns_response_len = dns_search_res(dname,
rr_class,
rr_type,
dns_response,
sizeof(dns_response));
if (dns_response_len < 0) {
ast_log(LOG_ERROR, "DNS search failed for %s\n", dname);
return AST_DNS_SEARCH_FAILURE;
}
/* Parse records from DNS response */
ret = dns_parse_answer_ex(context,
rr_class,
rr_type,
dns_response,
dns_response_len,
response_handler,
record_handler);
/* Handle the return code from parsing the DNS response */
if (ret == AST_DNS_SEARCH_FAILURE) {
/* Parsing Error */
ast_log(LOG_WARNING, "DNS Parse error for %s\n", dname);
} else if (ret == AST_DNS_SEARCH_NO_RECORDS) {
/* No results found */
ast_debug(1, "DNS search yielded no results for %s\n", dname);
}
return ret;
}
struct ao2_container *ast_dns_get_nameservers(void)
{
#ifdef HAVE_RES_NINIT

267
main/dns_system_resolver.c Executable file
View file

@ -0,0 +1,267 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Digium, Inc.
*
* Ashley Sanders <asanders@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief The default DNS resolver for Asterisk.
*
* \arg See also \ref res_resolver_unbound
*
* \author Ashley Sanders <asanders@digium.com>
*/
#include "asterisk.h"
ASTERISK_REGISTER_FILE()
#include "asterisk/_private.h"
#include "asterisk/astobj2.h"
#include "asterisk/dns.h"
#include "asterisk/dns_core.h"
#include "asterisk/dns_resolver.h"
#include "asterisk/linkedlists.h"
#include "asterisk/taskprocessor.h"
/*! \brief The consideration priority for this resolver implementation. */
#define DNS_SYSTEM_RESOLVER_PRIORITY INT_MAX
/*! \brief Resolver return code upon success. */
#define DNS_SYSTEM_RESOLVER_SUCCESS 0
/*! \brief Resolver return code upon failure. */
#define DNS_SYSTEM_RESOLVER_FAILURE -1
static int dns_system_resolver_add_record(void *context, unsigned char *record, int record_len, int ttl);
static int dns_system_resolver_cancel(struct ast_dns_query *query);
static void dns_system_resolver_destroy(void);
static int dns_system_resolver_process_query(void *data);
static int dns_system_resolver_resolve(struct ast_dns_query *query);
static int dns_system_resolver_set_response(void *context, unsigned char *dns_response, int dns_response_len, int rcode);
/*! \brief The task processor to use for making DNS searches asynchronous. */
static struct ast_taskprocessor *dns_system_resolver_tp;
/*! \brief The base definition for the dns_system_resolver */
struct ast_dns_resolver dns_system_resolver_base = {
.name = "system",
.priority = DNS_SYSTEM_RESOLVER_PRIORITY,
.resolve = dns_system_resolver_resolve,
.cancel = dns_system_resolver_cancel,
};
/*!
* \brief Callback to handle processing resource records.
*
* \details Adds an individual resource record discovered with ast_search_dns_ex to the
* ast_dns_query currently being resolved.
*
* \internal
*
* \param context A void pointer to the ast_dns_query being processed.
* \param record An individual resource record discovered during the DNS search.
* \param record_len The length of the resource record.
* \param ttl The resource record's expiration time limit (time to live).
*
* \retval 0 on success
* \retval -1 on failure
*/
static int dns_system_resolver_add_record(void *context, unsigned char *record, int record_len, int ttl)
{
struct ast_dns_query *query = context;
/* Add the record to the query.*/
return ast_dns_resolver_add_record(query,
ast_dns_query_get_rr_type(query),
ast_dns_query_get_rr_class(query),
ttl,
(const char*) record,
record_len);
}
/*!
* \brief Cancels processing resolution for a given query.
*
* \note The system API calls block so there is no way to cancel them. Therefore, this function always
* returns failure when invoked.
*
* \internal
*
* \param query The ast_dns_query to cancel.
*
* \retval 0 on success
* \retval -1 on failure
*/
static int dns_system_resolver_cancel(struct ast_dns_query *query)
{
return DNS_SYSTEM_RESOLVER_FAILURE;
}
/*!
* \brief Destructor.
*
* \internal
*/
static void dns_system_resolver_destroy(void)
{
/* Unreference the task processor */
dns_system_resolver_tp = ast_taskprocessor_unreference(dns_system_resolver_tp);
/* Unregister the base resolver */
ast_dns_resolver_unregister(&dns_system_resolver_base);
}
/*!
* \brief Callback to handle processing the query from the ast_taskprocessor instance.
*
* \internal
*
* \param data A void pointer to the ast_dns_query being processed.
*
* \retval -1 on search failure
* \retval 0 on no records found
* \retval 1 on success
*/
static int dns_system_resolver_process_query(void *data)
{
struct ast_dns_query *query = data;
/* Perform the DNS search */
enum ast_dns_search_result res = ast_search_dns_ex(query,
ast_dns_query_get_name(query),
ast_dns_query_get_rr_class(query),
ast_dns_query_get_rr_type(query),
dns_system_resolver_set_response,
dns_system_resolver_add_record);
/* Handle the possible return values from the DNS search */
if (res == AST_DNS_SEARCH_FAILURE) {
ast_log(LOG_ERROR, "DNS search failed for query: '%s'\n",
ast_dns_query_get_name(query));
} else if (res == AST_DNS_SEARCH_NO_RECORDS) {
ast_log(LOG_WARNING, "DNS search failed to yield any results for query: '%s'\n",
ast_dns_query_get_name(query));
}
/* Mark the query as complete */
ast_dns_resolver_completed(query);
/* Reduce the reference count on the query object */
ao2_ref(query, -1);
return res;
}
/*!
* \brief Resolves a DNS query.
*
* \internal
*
* \param query The ast_dns_query to resolve.
*
* \retval 0 on successful load of query handler to the ast_taskprocessor instance
* \retval -1 on failure to load the query handler to the ast_taskprocessor instance
*/
static int dns_system_resolver_resolve(struct ast_dns_query *query)
{
/* Add query processing handler to the task processor */
int res = ast_taskprocessor_push(dns_system_resolver_tp,
dns_system_resolver_process_query,
ao2_bump(query));
/* The query processing handler was not added to the task processor */
if (res < 0) {
ast_log(LOG_ERROR, "Failed to perform async DNS resolution of '%s'\n",
ast_dns_query_get_name(query));
ao2_ref(query, -1);
}
/* Return the result of adding the query processing handler to the task processor */
return res;
}
/*!
* \brief Callback to handle initializing the results field.
*
* \internal
*
* \param dns_response The full DNS response.
* \param dns_response The length of the full DNS response.
* \param rcode The DNS response code.
*
* \retval 0 on success
* \retval -1 on failure
*/
static int dns_system_resolver_set_response(void *context, unsigned char *dns_response, int dns_response_len, int rcode)
{
struct ast_dns_query *query = context;
int res;
/* Instantiate the query's result field (if necessary). */
if (!ast_dns_query_get_result(query)) {
res = ast_dns_resolver_set_result(query,
0,
0,
rcode,
ast_dns_query_get_name(query),
(const char*) dns_response,
dns_response_len);
if (res) {
/* There was a problem instantiating the results field. */
ast_log(LOG_ERROR, "Could not instantiate the results field for query: '%s'\n",
ast_dns_query_get_name(query));
}
} else {
res = DNS_SYSTEM_RESOLVER_SUCCESS;
}
return res;
}
/*!
* \brief Initializes the resolver.
*
* \retval 0 on success
* \retval -1 on failure
*/
int ast_dns_system_resolver_init(void)
{
/* Register the base resolver */
int res = ast_dns_resolver_register(&dns_system_resolver_base);
if (res) {
return DNS_SYSTEM_RESOLVER_FAILURE;
}
/* Instantiate the task processor */
dns_system_resolver_tp = ast_taskprocessor_get("dns_system_resolver_tp",
TPS_REF_DEFAULT);
/* Return error if the task processor failed to instantiate */
if (!dns_system_resolver_tp) {
dns_system_resolver_destroy();
return DNS_SYSTEM_RESOLVER_FAILURE;
}
/* Register the cleanup function */
ast_register_cleanup(dns_system_resolver_destroy);
return DNS_SYSTEM_RESOLVER_SUCCESS;
}