From 3cdfd39af7c632403bff34fdbf2c4945cd02bb79 Mon Sep 17 00:00:00 2001 From: Ashley Sanders Date: Tue, 7 Jul 2015 15:03:34 -0500 Subject: DNS: Create a system-level DNS resolver Prior to this patch, the DNS core present in master had no default system-level resolver implementation. Therefore, it was not possible for the DNS core to perform resolutions unless the libunbound library was installed and the res_resolver_unbound module was loaded. This patch introduces a system-level DNS resolver implementation that will register itself with the lowest consideration priority available (to ensure that it is to be used only as a last resort). The resolver relies on low-level DNS search functions to perform a rudimentary DNS search based on a provided query and then supplies the search results to the DNS core. ASTERISK-25146 #close Reported By: Joshua Colp Change-Id: I3b36ea17b889a98df4f8d80d50bb7ee175afa077 --- main/asterisk.c | 5 + main/dns.c | 312 +++++++++++++++++++++++++++++++++++++++++++-- main/dns_system_resolver.c | 267 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 571 insertions(+), 13 deletions(-) create mode 100755 main/dns_system_resolver.c (limited to 'main') diff --git a/main/asterisk.c b/main/asterisk.c index 53bceadfd..0478f6c5d 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -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); diff --git a/main/dns.c b/main/dns.c index fd8742026..409ed44ad 100644 --- a/main/dns.c +++ b/main/dns.c @@ -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; + + 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; + } + } -/*! \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. + /* 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 diff --git a/main/dns_system_resolver.c b/main/dns_system_resolver.c new file mode 100755 index 000000000..a5ac77127 --- /dev/null +++ b/main/dns_system_resolver.c @@ -0,0 +1,267 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, Digium, Inc. + * + * Ashley Sanders + * + * 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 + */ + +#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; +} -- cgit v1.2.3