Author: file Date: Thu Mar 12 09:00:37 2015 New Revision: 432832 URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=432832 Log: res_resolver_unbound: Add configuration support.
Modified: team/group/dns/res/res_resolver_unbound.c Modified: team/group/dns/res/res_resolver_unbound.c URL: http://svnview.digium.com/svn/asterisk/team/group/dns/res/res_resolver_unbound.c?view=diff&rev=432832&r1=432831&r2=432832 ============================================================================== --- team/group/dns/res/res_resolver_unbound.c (original) +++ team/group/dns/res/res_resolver_unbound.c Thu Mar 12 09:00:37 2015 @@ -31,6 +31,44 @@ #include "asterisk/linkedlists.h" #include "asterisk/dns_core.h" #include "asterisk/dns_resolver.h" +#include "asterisk/config.h" +#include "asterisk/config_options.h" + +/*** DOCUMENTATION + <configInfo name="res_resolver_unbound" language="en_US"> + <configFile name="resolver_unbound.conf"> + <configObject name="globals"> + <synopsis>Options that apply globally to res_resolver_unbound</synopsis> + <configOption name="hosts"> + <synopsis>Full path to an optional hosts file</synopsis> + <description><para>Hosts specified in a hosts file will be resolved within the resolver itself. If a value + of system is provided the system-specific file will be used.</para></description> + </configOption> + <configOption name="resolv"> + <synopsis>Full path to an optional resolv.conf file</synopsis> + <description><para>The resolv.conf file specifies the nameservers to contact when resolving queries. If a + value of system is provided the system-specific file will be used.</para></description> + </configOption> + <configOption name="nameserver"> + <synopsis>Nameserver to use for queries</synopsis> + <description><para>An explicit nameserver can be specified which is used for resolving queries. If multiple + nameserver lines are specified the first will be the primary with failover occurring, in order, to the other + nameservers as backups.</para></description> + </configOption> + <configOption name="debug"> + <synopsis>Unbound debug level</synopsis> + <description><para>The debugging level for the unbound resolver. While there is no explicit range generally + the higher the number the more debug is output.</para></description> + </configOption> + <configOption name="ta_file"> + <synopsis>Trust anchor file</synopsis> + <description><para>Full path to a file with DS and DNSKEY records in zone file format. This file is provided + to unbound and is used as a source for trust anchors.</para></description> + </configOption> + </configObject> + </configFile> + </configInfo> + ***/ /*! \brief Structure for an unbound resolver */ struct unbound_resolver { @@ -44,10 +82,73 @@ struct unbound_resolver_data { /*! \brief ID for the specific query */ int id; -}; - -/*! \brief Unbound resolver */ -static struct unbound_resolver *resolver; + /*! \brief The resolver in use for the query */ + struct unbound_resolver *resolver; +}; + +/*! \brief Unbound configuration state information */ +struct unbound_config_state { + /*! \brief The configured resolver */ + struct unbound_resolver *resolver; +}; + +/*! \brief A structure to hold global configuration-related options */ +struct unbound_global_config { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(hosts); /*!< Optional hosts file */ + AST_STRING_FIELD(resolv); /*!< Optional resolv.conf file */ + AST_STRING_FIELD(ta_file); /*!< Optional trust anchor file */ + ); + /*! \brief List of nameservers (in order) to use for queries */ + struct ao2_container *nameservers; + /*! \brief Debug level for the resolver */ + unsigned int debug; + /*! \brief State information */ + struct unbound_config_state *state; +}; + +/*! \brief A container for config related information */ +struct unbound_config { + struct unbound_global_config *global; +}; + +/*! + * \brief Allocate a unbound_config to hold a snapshot of the complete results of parsing a config + * \internal + * \returns A void pointer to a newly allocated unbound_config + */ +static void *unbound_config_alloc(void); + +/*! \brief An aco_type structure to link the "general" category to the unbound_global_config type */ +static struct aco_type global_option = { + .type = ACO_GLOBAL, + .name = "globals", + .item_offset = offsetof(struct unbound_config, global), + .category_match = ACO_WHITELIST, + .category = "^general$", +}; + +static struct aco_type *global_options[] = ACO_TYPES(&global_option); + +static struct aco_file resolver_unbound_conf = { + .filename = "resolver_unbound.conf", + .types = ACO_TYPES(&global_option), +}; + +/*! \brief A global object container that will contain the global_config that gets swapped out on reloads */ +static AO2_GLOBAL_OBJ_STATIC(globals); + +/*! + * \brief Finish initializing new configuration + * \internal + */ +static int unbound_config_preapply_callback(void); + +/*! \brief Register information about the configs being processed by this module */ +CONFIG_INFO_STANDARD(cfg_info, globals, unbound_config_alloc, + .files = ACO_FILES(&resolver_unbound_conf), + .pre_apply_config = unbound_config_preapply_callback, +); /*! \brief Destructor for unbound resolver */ static void unbound_resolver_destroy(void *obj) @@ -79,9 +180,6 @@ /* Each async result should be invoked in a separate thread so others are not blocked */ ub_ctx_async(resolver->context, 1); - - ub_ctx_resolvconf(resolver->context, NULL); - ub_ctx_hosts(resolver->context, NULL); return resolver; } @@ -171,6 +269,7 @@ static int unbound_resolver_resolve(struct ast_dns_query *query) { + struct unbound_config *cfg = ao2_global_obj_ref(globals); struct unbound_resolver_data *data; int res; @@ -180,9 +279,10 @@ ast_dns_query_get_name(query)); return -1; } + data->resolver = ao2_bump(cfg->global->state->resolver); ast_dns_resolver_set_data(query, data); - res = ub_resolve_async(resolver->context, ast_dns_query_get_name(query), + res = ub_resolve_async(data->resolver->context, ast_dns_query_get_name(query), ast_dns_query_get_rr_type(query), ast_dns_query_get_rr_class(query), ao2_bump(query), unbound_resolver_callback, &data->id); @@ -193,6 +293,7 @@ } ao2_ref(data, -1); + ao2_ref(cfg, -1); return res; } @@ -202,7 +303,7 @@ struct unbound_resolver_data *data = ast_dns_resolver_get_data(query); int res; - res = ub_cancel(resolver->context, data->id); + res = ub_cancel(data->resolver->context, data->id); if (!res) { /* When this query was started we bumped the ref, now that it has been cancelled we have ownership and * need to drop it @@ -220,25 +321,222 @@ .cancel = unbound_resolver_cancel, }; +static void unbound_config_destructor(void *obj) +{ + struct unbound_config *cfg = obj; + + ao2_cleanup(cfg->global); +} + +static void unbound_global_config_destructor(void *obj) +{ + struct unbound_global_config *global = obj; + + ast_string_field_free_memory(global); + ao2_cleanup(global->nameservers); + ao2_cleanup(global->state); +} + +static void unbound_config_state_destructor(void *obj) +{ + struct unbound_config_state *state = obj; + + if (state->resolver) { + unbound_resolver_stop(state->resolver); + ao2_ref(state->resolver, -1); + } +} + +static void *unbound_config_alloc(void) +{ + struct unbound_config *cfg; + + cfg = ao2_alloc_options(sizeof(*cfg), unbound_config_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!cfg) { + return NULL; + } + + /* Allocate/initialize memory */ + cfg->global = ao2_alloc_options(sizeof(*cfg->global), unbound_global_config_destructor, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!cfg->global) { + goto error; + } + + if (ast_string_field_init(cfg->global, 128)) { + goto error; + } + + cfg->global->nameservers = ast_str_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1); + if (!cfg->global->nameservers) { + goto error; + } + + return cfg; +error: + ao2_ref(cfg, -1); + return NULL; +} + +static int unbound_config_preapply(struct unbound_config *cfg) +{ + int res = 0; + struct ao2_iterator it_nameservers; + const char *nameserver; + + cfg->global->state = ao2_alloc_options(sizeof(*cfg->global->state), unbound_config_state_destructor, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!cfg->global->state) { + ast_log(LOG_ERROR, "Could not allocate unbound resolver state structure\n"); + return -1; + } + + cfg->global->state->resolver = unbound_resolver_alloc(); + if (!cfg->global->state->resolver) { + ast_log(LOG_ERROR, "Could not create an unbound resolver\n"); + return -1; + } + + if (!strcmp(cfg->global->hosts, "system")) { + res = ub_ctx_hosts(cfg->global->state->resolver->context, NULL); + } else if (!ast_strlen_zero(cfg->global->hosts)) { + res = ub_ctx_hosts(cfg->global->state->resolver->context, cfg->global->hosts); + } + + if (res) { + ast_log(LOG_ERROR, "Failed to set hosts file to '%s' in unbound resolver: %s\n", + cfg->global->hosts, ub_strerror(res)); + return -1; + } + + if (!strcmp(cfg->global->resolv, "system")) { + res = ub_ctx_resolvconf(cfg->global->state->resolver->context, NULL); + } else if (!ast_strlen_zero(cfg->global->resolv)) { + res = ub_ctx_resolvconf(cfg->global->state->resolver->context, cfg->global->resolv); + } + + if (res) { + ast_log(LOG_ERROR, "Failed to set resolv.conf file to '%s' in unbound resolver: %s\n", + cfg->global->resolv, ub_strerror(res)); + return -1; + } + + it_nameservers = ao2_iterator_init(cfg->global->nameservers, 0); + while ((nameserver = ao2_iterator_next(&it_nameservers))) { + res = ub_ctx_set_fwd(cfg->global->state->resolver->context, nameserver); + + if (res) { + ast_log(LOG_ERROR, "Failed to add nameserver '%s' to unbound resolver: %s\n", + nameserver, ub_strerror(res)); + ao2_iterator_destroy(&it_nameservers); + return -1; + } + } + ao2_iterator_destroy(&it_nameservers); + + ub_ctx_debuglevel(cfg->global->state->resolver->context, cfg->global->debug); + + if (!ast_strlen_zero(cfg->global->ta_file)) { + res = ub_ctx_add_ta_file(cfg->global->state->resolver->context, cfg->global->ta_file); + + if (res) { + ast_log(LOG_ERROR, "Failed to set trusted anchor file to '%s' in unbound resolver: %s\n", + cfg->global->ta_file, ub_strerror(res)); + return -1; + } + } + + if (unbound_resolver_start(cfg->global->state->resolver)) { + ast_log(LOG_ERROR, "Could not start unbound resolver thread\n"); + return -1; + } + + return 0; +} + +static int unbound_config_apply_default(void) +{ + struct unbound_config *cfg; + + cfg = unbound_config_alloc(); + if (!cfg) { + ast_log(LOG_ERROR, "Could not create default configuration for unbound resolver\n"); + return -1; + } + + aco_set_defaults(&global_option, "general", cfg->global); + + if (unbound_config_preapply(cfg)) { + return -1; + } + + ast_verb(1, "Starting unbound resolver using default configuration\n"); + + ao2_global_obj_replace_unref(globals, cfg); + ao2_ref(cfg, -1); + + return 0; +} + +static int unbound_config_preapply_callback(void) +{ + return unbound_config_preapply(aco_pending_config(&cfg_info)); +} + +static int reload_module(void) +{ + if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) { + return AST_MODULE_RELOAD_ERROR; + } + + return 0; +} + static int unload_module(void) { - unbound_resolver_stop(resolver); - ao2_replace(resolver, NULL); + aco_info_destroy(&cfg_info); + ao2_global_obj_release(globals); return 0; } +static int custom_nameserver_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct unbound_global_config *global = obj; + + return ast_str_container_add(global->nameservers, var->value); +} + static int load_module(void) { - resolver = unbound_resolver_alloc(); - if (!resolver) { + struct ast_config *cfg; + struct ast_flags cfg_flags = { 0, }; + + if (aco_info_init(&cfg_info)) { return AST_MODULE_LOAD_DECLINE; } - if (unbound_resolver_start(resolver) || - ast_dns_resolver_register(&unbound_resolver)) { - unload_module(); - return AST_MODULE_LOAD_DECLINE; - } + aco_option_register(&cfg_info, "hosts", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, hosts)); + aco_option_register(&cfg_info, "resolv", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, resolv)); + aco_option_register_custom(&cfg_info, "nameserver", ACO_EXACT, global_options, "", custom_nameserver_handler, 0); + aco_option_register(&cfg_info, "debug", ACO_EXACT, global_options, "0", OPT_UINT_T, 0, FLDSET(struct unbound_global_config, debug)); + aco_option_register(&cfg_info, "ta_file", ACO_EXACT, global_options, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct unbound_global_config, ta_file)); + + /* This purposely checks for a configuration file so we don't output an error message in ACO if one is not present */ + cfg = ast_config_load(resolver_unbound_conf.filename, cfg_flags); + if (!cfg) { + if (unbound_config_apply_default()) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + } else { + ast_config_destroy(cfg); + if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + } + + ast_dns_resolver_register(&unbound_resolver); ast_module_shutdown_ref(ast_module_info->self); @@ -249,5 +547,6 @@ .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, + .reload = reload_module, .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4, ); -- _____________________________________________________________________ -- Bandwidth and Colocation Provided by http://www.api-digital.com -- svn-commits mailing list To UNSUBSCRIBE or update options visit: http://lists.digium.com/mailman/listinfo/svn-commits