[asterisk-commits] file: branch group/dns r432832 - /team/group/dns/res/res_resolver_unbound.c

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Mar 12 09:00:39 CDT 2015


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,
 	       );




More information about the asterisk-commits mailing list