Index: asterisk-22.8.2/main/dns_recurring.c
===================================================================
--- asterisk-22.8.2.orig/main/dns_recurring.c
+++ asterisk-22.8.2/main/dns_recurring.c
@@ -41,9 +41,13 @@
 
 /*! \brief Delay between TTL expiration and the next DNS query, to make sure the
 resolver cache really expired. */
-#define EXTRA_TTL 2
+#define EXTRA_TTL 1
 #define MAX_TTL ((INT_MAX - EXTRA_TTL) / 1000)
 
+/*! \brief Default TTL value to use when received TTL is zero (expired).  A value of 30 seconds or more
+should be chosen to avoid unnecessary spam to the DNS server. */
+#define DEFAULT_TTL 30
+
 /*! \brief Destructor for a DNS query */
 static void dns_query_recurring_destroy(void *data)
 {
@@ -96,14 +100,20 @@ static void dns_query_recurring_resoluti
 	/* So.. if something has not externally cancelled this we can reschedule based on the TTL */
 	if (!recurring->cancelled) {
 		const struct ast_dns_result *result = ast_dns_query_get_result(query);
+
 		int ttl = MIN(ast_dns_result_get_lowest_ttl(result), MAX_TTL);
 
-		if (ttl) {
-			recurring->timer = ast_sched_add(ast_dns_get_sched(), (ttl + EXTRA_TTL) * 1000, dns_query_recurring_scheduled_callback, ao2_bump(recurring));
-			if (recurring->timer < 0) {
-				/* It is impossible for this to be the last reference as the query has a reference to it */
-				ao2_ref(recurring, -1);
-			}
+		/* If TTL is zero, DNS cache has expired. In which case set the next query to a sane
+		 * default value to avoid overloading the DNS server if TTL is always 0
+		 */
+		if (!ttl) {
+			ttl = DEFAULT_TTL;
+			ast_debug(2, "TTL value of 0, waiting %us before next query.\n", ttl);
+		}
+		recurring->timer = ast_sched_add(ast_dns_get_sched(), (ttl + EXTRA_TTL) * 1000, dns_query_recurring_scheduled_callback, ao2_bump(recurring));
+		if (recurring->timer < 0) {
+			/* It is impossible for this to be the last reference as the query has a reference to it */
+			ao2_ref(recurring, -1);
 		}
 	}
 
Index: asterisk-22.8.2/res/res_rtp_asterisk.c
===================================================================
--- asterisk-22.8.2.orig/res/res_rtp_asterisk.c
+++ asterisk-22.8.2/res/res_rtp_asterisk.c
@@ -228,7 +228,7 @@ static int dtls_mtu = DEFAULT_DTLS_MTU;
 #ifdef HAVE_PJPROJECT
 static int icesupport = DEFAULT_ICESUPPORT;
 static int stun_software_attribute = DEFAULT_STUN_SOFTWARE_ATTRIBUTE;
-static struct sockaddr_in stunaddr;
+static struct stun_resolver *stunres = NULL;
 static pj_str_t turnaddr;
 static int turnport = DEFAULT_TURN_PORT;
 static pj_str_t turnusername;
@@ -244,10 +244,6 @@ static ast_rwlock_t ice_acl_lock = AST_R
 static struct ast_acl_list *stun_acl = NULL;
 static ast_rwlock_t stun_acl_lock = AST_RWLOCK_INIT_VALUE;
 
-/*! stunaddr recurring resolution */
-static ast_rwlock_t stunaddr_lock = AST_RWLOCK_INIT_VALUE;
-static struct ast_dns_query_recurring *stunaddr_resolver = NULL;
-
 /*! \brief Pool factory used by pjlib to allocate memory. */
 static pj_caching_pool cachingpool;
 
@@ -292,6 +288,12 @@ struct ast_ice_host_candidate {
 	AST_RWLIST_ENTRY(ast_ice_host_candidate) next;
 };
 
+struct stun_resolver {
+	struct sockaddr_in *address;
+	const char *hostname;
+	struct ast_dns_query_recurring *resolver;
+};
+
 /*! \brief List of ICE host candidate mappings */
 static AST_RWLIST_HEAD_STATIC(host_candidates, ast_ice_host_candidate);
 
@@ -696,8 +698,9 @@ static BIO_METHOD *dtls_bio_methods;
 static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *via_ice, int use_srtp);
 
 #ifdef HAVE_PJPROJECT
-static void stunaddr_resolve_callback(const struct ast_dns_query *query);
-static int store_stunaddr_resolved(const struct ast_dns_query *query);
+static int stun_resolver_get_resolved(struct sockaddr_in *retaddr, struct stun_resolver *stun);
+static struct stun_resolver *stun_resolver_create(const char *hostport);
+static void stun_resolver_stop(struct stun_resolver *stun);
 #endif
 
 #if defined(HAVE_OPENSSL) && (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_SRTP)
@@ -3745,7 +3748,7 @@ static void rtp_add_candidates_to_ice(st
 	pj_sockaddr pjtmp;
 	struct ast_ice_host_candidate *candidate;
 	int af_inet_ok = 0, af_inet6_ok = 0;
-	struct sockaddr_in stunaddr_copy;
+	struct sockaddr_in stunaddr_copy = {0};
 
 	if (ast_sockaddr_is_ipv4(addr)) {
 		af_inet_ok = 1;
@@ -3855,9 +3858,7 @@ static void rtp_add_candidates_to_ice(st
 		freeifaddrs(ifa);
 	}
 
-	ast_rwlock_rdlock(&stunaddr_lock);
-	memcpy(&stunaddr_copy, &stunaddr, sizeof(stunaddr));
-	ast_rwlock_unlock(&stunaddr_lock);
+	stun_resolver_get_resolved(&stunaddr_copy, stunres);
 
 	/* If configured to use a STUN server to get our external mapped address do so */
 	if (stunaddr_copy.sin_addr.s_addr && !stun_address_is_blacklisted(addr) &&
@@ -9614,70 +9615,175 @@ static int ast_rtp_bundle(struct ast_rtp
 }
 
 #ifdef HAVE_PJPROJECT
-static void stunaddr_resolve_callback(const struct ast_dns_query *query)
+static void stun_resolver_destroy(void *obj)
 {
-	const int lowest_ttl = ast_dns_result_get_lowest_ttl(ast_dns_query_get_result(query));
-	const char *stunaddr_name = ast_dns_query_get_name(query);
-	const char *stunaddr_resolved_str;
+	struct stun_resolver *stun = obj;
 
-	if (!store_stunaddr_resolved(query)) {
-		ast_log(LOG_WARNING, "Failed to resolve stunaddr '%s'. Cancelling recurring resolution.\n", stunaddr_name);
-		return;
+	if (stun->address) {
+		memset(stun->address, 0, sizeof(*stun->address));
+		ast_free(stun->address);
+		stun->address = NULL;
 	}
+	if (stun->hostname) {
+		*(char *) stun->hostname = '\0';
+		ast_free((char *) stun->hostname);
+		stun->hostname = NULL;
+	}
+}
 
-	if (DEBUG_ATLEAST(2)) {
-		ast_rwlock_rdlock(&stunaddr_lock);
-		stunaddr_resolved_str = ast_inet_ntoa(stunaddr.sin_addr);
-		ast_rwlock_unlock(&stunaddr_lock);
+static void stun_resolver_callback(const struct ast_dns_query *query)
+{
+	const char *data = NULL;
+	const char *hostname = NULL;
+	struct stun_resolver *stun = ast_dns_query_get_data(query);
+	struct ast_dns_result *result = ast_dns_query_get_result(query);
+	const struct ast_dns_record *record = ast_dns_result_get_records(result);
+	int ttl = ast_dns_result_get_lowest_ttl(result);
 
-		ast_debug_stun(2, "Resolved stunaddr '%s' to '%s'. Lowest TTL = %d.\n",
-			stunaddr_name,
-			stunaddr_resolved_str,
-			lowest_ttl);
-	}
+	for (; record; record = ast_dns_record_get_next(record)) {
+		const size_t datasize = ast_dns_record_get_data_size(record);
+		const int rr_type = ast_dns_record_get_rr_type(record);
+		data = ast_dns_record_get_data(record);
+
+		switch (rr_type) {
+		case T_A:
+			if (datasize != 4) {
+				ast_debug_stun(2,
+							   "Retrieved an A record, but record size is invalid (size: %u, expected: %u)",
+							   (unsigned int) datasize,
+							   (unsigned int) 4);
+				continue;
+			}
+			ao2_wrlock(stun);
+			memcpy(&stun->address->sin_addr, data, datasize);
+			hostname = ast_strdupa(stun->hostname);
+			ao2_unlock(stun);
+			ast_debug_stun(2,
+				   "Resolved STUN from DNS: '%s' -> '%s' (renews in %ds)",
+				   hostname,
+				   ast_inet_ntoa(*(struct in_addr *) data),
+				   ttl);
+			return;
+
+		case T_AAAA:
+			ast_debug_stun(2, "Retrieved an AAAA record, but STUN implementation doesn't support IPv6.");
+			continue;
 
-	if (!lowest_ttl) {
-		ast_log(LOG_WARNING, "Resolution for stunaddr '%s' returned TTL = 0. Recurring resolution was cancelled.\n", ast_dns_query_get_name(query));
+		default:
+			ast_debug_stun(2, "Received invalid STUN address: '%s', moving to next entry...",
+						   ast_inet_ntoa(*(struct in_addr *) data));
+			continue;
+		}
 	}
+
+	/* Looped out without a valid address, emit warning, keep stale record */
+	ast_debug_stun(2, "No valid entries found for STUN from DNS server, keeping previous address.");
 }
 
-static int store_stunaddr_resolved(const struct ast_dns_query *query)
+static int stun_resolver_init(struct stun_resolver *stun, const char *hostport)
 {
-	const struct ast_dns_result *result = ast_dns_query_get_result(query);
-	const struct ast_dns_record *record;
+	const char *hostname = NULL;
+	const char *port = NULL;
+	uint16_t stunport = 0;
+	char *input = ast_strdupa(hostport);
+	struct ast_sockaddr address = {};
 
-	for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
-		const size_t data_size = ast_dns_record_get_data_size(record);
-		const unsigned char *data = (unsigned char *)ast_dns_record_get_data(record);
-		const int rr_type = ast_dns_record_get_rr_type(record);
+	if (!stun || !hostport || !input) {
+		return -1;
+	}
 
-		if (rr_type == ns_t_a && data_size == 4) {
-			ast_rwlock_wrlock(&stunaddr_lock);
-			memcpy(&stunaddr.sin_addr, data, data_size);
-			stunaddr.sin_family = AF_INET;
-			ast_rwlock_unlock(&stunaddr_lock);
+	if (!ast_sockaddr_split_hostport(input, (char **) &hostname, (char **) &port, 0)) {
+		ast_log(LOG_ERROR, "STUN address '%s' is using an invalid format, must use ipv4[:port] or host.name[:port] format", hostport);
+		return -1;
+	}
+	stun->hostname = ast_strdup(hostname);
+	ast_parse_arg(port, PARSE_UINT32 | PARSE_IN_RANGE | PARSE_DEFAULT, &stunport, STANDARD_STUN_PORT, 1, 65535);
 
-			return 1;
-		} else {
-			ast_debug_stun(3, "Unrecognized rr_type '%u' or data_size '%zu' from DNS query for stunaddr '%s'\n",
-										 rr_type, data_size, ast_dns_query_get_name(query));
-			continue;
+	/* Check if it's an IPv4 address */
+	if (!ast_parse_arg(stun->hostname, PARSE_ADDR | PARSE_PORT_IGNORE, &address))
+	{
+		if (!ast_sockaddr_is_ipv4(&address)) {
+			ast_debug_stun(2, "STUN implementation doesn't support IPv6 addresses.");
+			return -1;
+		}
+		ast_sockaddr_set_port(&address, stunport);
+		ast_sockaddr_to_sin(&address, stun->address);
+		ast_debug_stun(2, "Resolving STUN using static address at '%s:%u'", stun->hostname, stunport);
+	}
+	/* Else it's a canonical name that must be resolved */
+	else if (!ast_sockaddr_resolve_first_af(&address, stun->hostname, 0, AF_INET)) {
+		ast_sockaddr_set_port(&address, stunport);
+		ast_sockaddr_to_sin(&address, stun->address);
+		stun->resolver = ast_dns_resolve_recurring(stun->hostname, T_A, C_IN, stun_resolver_callback, stun);
+		if (!stun->resolver) {
+			ast_log(LOG_ERROR, "Failed to start recurring dns query");
+			return -1;
 		}
+		ast_debug_stun(2, "Initialized recurring DNS query to resolve STUN");
+	}
+	/* Not an IPv4 nor a canonical name */
+	else {
+		ast_log(LOG_ERROR, "Unable to parse STUN address: '%s'", hostport);
+		return -1;
 	}
+
 	return 0;
 }
 
-static void clean_stunaddr(void) {
-	if (stunaddr_resolver) {
-		if (ast_dns_resolve_recurring_cancel(stunaddr_resolver)) {
-			ast_log(LOG_ERROR, "Failed to cancel recurring DNS resolution of previous stunaddr.\n");
-		}
-		ao2_ref(stunaddr_resolver, -1);
-		stunaddr_resolver = NULL;
-	}
-	ast_rwlock_wrlock(&stunaddr_lock);
-	memset(&stunaddr, 0, sizeof(stunaddr));
-	ast_rwlock_unlock(&stunaddr_lock);
+static struct stun_resolver *stun_resolver_create(const char *address)
+{
+	struct stun_resolver *stun = NULL;
+
+	if (!(stun = ao2_alloc_options(sizeof(*stun), stun_resolver_destroy, AO2_ALLOC_OPT_LOCK_RWLOCK))) {
+		goto failure;
+	}
+
+	if (!(stun->address = ast_calloc(1, sizeof(*stun->address)))) {
+		goto failure;
+	}
+
+	if (stun_resolver_init(stun, address)) {
+		goto failure;
+	}
+
+	return stun;
+
+failure:
+	ast_log(LOG_ERROR, "Failed to create STUN resolver.\n");
+	ao2_cleanup(stun);
+	return NULL;
+}
+
+static void stun_resolver_stop(struct stun_resolver *stun)
+{
+	if (!stun) {
+		return;
+	}
+
+	if (stun->resolver) {
+		if (ast_dns_resolve_recurring_cancel(stun->resolver)) {
+			ast_log(LOG_ERROR, "Failed to stop STUN recurring resolver.\n");
+		}
+		ao2_cleanup(stun->resolver);
+		stun->resolver = NULL;
+	}
+	ao2_cleanup(stun);
+}
+
+static int stun_resolver_get_resolved(struct sockaddr_in *retsin, struct stun_resolver *stun)
+{
+	if (!stun || !retsin) {
+		return -1;
+	}
+
+	ao2_rdlock(stun);
+	if (!stun->address) {
+		ao2_unlock(stun);
+		return -1;
+	}
+	memcpy(retsin, stun->address, sizeof(*stun->address));
+	ao2_unlock(stun);
+	return 0;
 }
 #endif
 
@@ -9814,10 +9920,9 @@ static char *handle_cli_rtp_settings(str
 #ifdef HAVE_PJPROJECT
 	ast_cli(a->fd, "  ICE support:     %s\n", AST_CLI_YESNO(icesupport));
 
-	ast_rwlock_rdlock(&stunaddr_lock);
-	memcpy(&stunaddr_copy, &stunaddr, sizeof(stunaddr));
-	ast_rwlock_unlock(&stunaddr_lock);
-	ast_cli(a->fd, "  STUN address:    %s:%d\n", ast_inet_ntoa(stunaddr_copy.sin_addr), htons(stunaddr_copy.sin_port));
+	if (!stun_resolver_get_resolved(&stunaddr_copy, stunres)) {
+		ast_cli(a->fd, "  STUN address:    %s:%d\n", ast_inet_ntoa(stunaddr_copy.sin_addr), htons(stunaddr_copy.sin_port));
+	}
 #endif
 	return CLI_SUCCESS;
 }
@@ -10066,7 +10171,8 @@ static int rtp_reload(int reload, int by
 	icesupport = DEFAULT_ICESUPPORT;
 	stun_software_attribute = DEFAULT_STUN_SOFTWARE_ATTRIBUTE;
 	turnport = DEFAULT_TURN_PORT;
-	clean_stunaddr();
+	stun_resolver_stop(stunres);
+	stunres = NULL;
 	turnaddr = pj_str(NULL);
 	turnusername = pj_str(NULL);
 	turnpassword = pj_str(NULL);
@@ -10144,36 +10250,8 @@ static int rtp_reload(int reload, int by
 		stun_software_attribute = ast_true(s);
 	}
 	if ((s = ast_variable_retrieve(cfg, "general", "stunaddr"))) {
-		char *hostport, *host, *port;
-		unsigned int port_parsed = STANDARD_STUN_PORT;
-		struct ast_sockaddr stunaddr_parsed;
-
-		hostport = ast_strdupa(s);
-
-		if (!ast_parse_arg(hostport, PARSE_ADDR, &stunaddr_parsed)) {
-			ast_debug_stun(3, "stunaddr = '%s' does not need name resolution\n",
-				ast_sockaddr_stringify_host(&stunaddr_parsed));
-			if (!ast_sockaddr_port(&stunaddr_parsed)) {
-				ast_sockaddr_set_port(&stunaddr_parsed, STANDARD_STUN_PORT);
-			}
-			ast_rwlock_wrlock(&stunaddr_lock);
-			ast_sockaddr_to_sin(&stunaddr_parsed, &stunaddr);
-			ast_rwlock_unlock(&stunaddr_lock);
-		} else if (ast_sockaddr_split_hostport(hostport, &host, &port, 0)) {
-			if (port) {
-				ast_parse_arg(port, PARSE_UINT32|PARSE_IN_RANGE, &port_parsed, 1, 65535);
-			}
-			stunaddr.sin_port = htons(port_parsed);
-
-			stunaddr_resolver = ast_dns_resolve_recurring(host, T_A, C_IN,
-				&stunaddr_resolve_callback, NULL);
-			if (!stunaddr_resolver) {
-				ast_log(LOG_ERROR, "Failed to setup recurring DNS resolution of stunaddr '%s'",
-					host);
-			}
-		} else {
-			ast_log(LOG_ERROR, "Failed to parse stunaddr '%s'", hostport);
-		}
+		char *hostport = ast_strdupa(s);
+		stunres = stun_resolver_create(hostport);
 	}
 	if ((s = ast_variable_retrieve(cfg, "general", "turnaddr"))) {
 		struct sockaddr_in addr;
@@ -10439,7 +10517,7 @@ static int unload_module(void)
 	acl_change_sub = stasis_unsubscribe_and_join(acl_change_sub);
 	rtp_unload_acl(&ice_acl_lock, &ice_acl);
 	rtp_unload_acl(&stun_acl_lock, &stun_acl);
-	clean_stunaddr();
+	stun_resolver_stop(stunres);
 #endif
 
 	return 0;
