From 9556ab31953b08ec4031e947d2357bde5f848100 Mon Sep 17 00:00:00 2001
From: Iryna Antsyferova <iryna.antsyferova@iopsys.eu>
Date: Tue, 25 Jun 2024 13:08:35 +0200
Subject: [PATCH] Implmenet dns cache support

---
 res/res_pjsip.exports.in              |  1 +
 res/res_pjsip/pjsip_resolver.c        | 68 +++++++++++++++++++++++++++
 res/res_pjsip_outbound_registration.c | 25 ++++++++++
 3 files changed, 94 insertions(+)

diff --git a/res/res_pjsip.exports.in b/res/res_pjsip.exports.in
index 58868e398f..4a0f2181bf 100644
--- a/res/res_pjsip.exports.in
+++ b/res/res_pjsip.exports.in
@@ -6,6 +6,7 @@
 		LINKER_SYMBOL_PREFIXast_copy_pj_str;
 		LINKER_SYMBOL_PREFIXast_copy_pj_str2;
 		LINKER_SYMBOL_PREFIXast_pjsip_rdata_get_endpoint;
+		LINKER_SYMBOL_PREFIXpjsip_resolver_*;
 	local:
 		*;
 };
diff --git a/res/res_pjsip/pjsip_resolver.c b/res/res_pjsip/pjsip_resolver.c
index eb370117e5..792062087c 100644
--- a/res/res_pjsip/pjsip_resolver.c
+++ b/res/res_pjsip/pjsip_resolver.c
@@ -32,9 +32,14 @@
 #include "include/res_pjsip_private.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/threadpool.h"
+#include "asterisk/pjsip_resolver.h"
 
 #ifdef HAVE_PJSIP_EXTERNAL_RESOLVER
 
+/*! DNS cache function pointers */
+dns_cache_get_addr_t get_dns_cache_addr = NULL;
+dns_cache_update_t update_dns_cache = NULL;
+
 /*! \brief Structure which contains transport+port information for an active query */
 struct sip_target {
 	/*! \brief The transport to be used */
@@ -59,8 +64,18 @@ struct host_track_entry {
 	AST_LIST_ENTRY(host_track_entry) next;
 };
 
+/*! \brief Structure which contains cached resolved server addresses */
+struct dns_cache_entry {
+	char *host;
+	pjsip_server_addresses addresses;
+	int ttl;
+	time_t entry_time;
+	AST_LIST_ENTRY(dns_cache_entry) next;
+};
+
 /*! \brief the list of all tracked hosts */
 AST_LIST_HEAD(, host_track_entry) sip_resolve_track;
+AST_LIST_HEAD(, dns_cache_entry) sip_dns_cache;
 
 /*! \brief compare the target host info with track record */
 static int host_track_target_cmp(pjsip_host_info *host, struct host_track_entry *hostc)
@@ -266,6 +281,10 @@ struct sip_resolve {
 	pjsip_resolver_callback *callback;
 	/*! \brief User provided data */
 	void *token;
+	/*! \brief Hostname requested to resolve */
+	char host[NI_MAXHOST];
+	/*! \brief Minimum TTL for the queries resulting in viable server addresses */
+	int ttl;
 };
 
 /*! \brief Our own defined transports, reduces the size of sip_available_transports */
@@ -410,6 +429,18 @@ static int sip_resolve_invoke_user_callback(void *data)
 
 	sip_resolve_target_track(&resolve->target, &resolve->addresses);
 
+	if (update_dns_cache && get_dns_cache_addr && resolve->addresses.count != 0) {
+		ast_debug(2, "Updating DNS cache...\n");
+
+		if (!(*update_dns_cache)(resolve->host, &resolve->addresses, resolve->ttl)) {
+			ast_debug(2, "Getting addr from DNS cache\n");
+
+			if ((*get_dns_cache_addr)(resolve->host, &resolve->addresses)) {
+				ast_log(LOG_ERROR, "Failed to get addr from cache after updating!");
+			}
+		}
+	}
+
 	ast_debug(2, "[%p] Invoking user callback with '%d' addresses\n", resolve, resolve->addresses.count);
 	resolve->callback(resolve->addresses.count ? PJ_SUCCESS : PJLIB_UTIL_EDNSNOANSWERREC, resolve->token, &resolve->addresses);
 
@@ -541,6 +572,12 @@ static void sip_resolve_callback(const struct ast_dns_query_set *query_set)
 						ast_dns_record_get_data_size(record));
 				}
 
+				int min_ttl = ast_dns_result_get_lowest_ttl(result);
+				if (min_ttl > 0 && resolve->ttl < min_ttl) {
+					ast_debug(2, "Updating DNS TTL for query set from %d to %d\n", resolve->ttl, min_ttl);
+					resolve->ttl = min_ttl;
+				}
+
 				address_count++;
 			} else if (ast_dns_record_get_rr_type(record) == T_SRV) {
 				if (have_naptr) {
@@ -735,6 +772,19 @@ static void sip_resolve(pjsip_resolver_t *resolver, pj_pool_t *pool, const pjsip
 		return;
 	}
 
+	if (get_dns_cache_addr) {
+		ast_debug(2, "Checking DNS cache...(%s)\n", host);
+		pjsip_server_addresses addresses = { .count = 0, };
+
+		if (!(*get_dns_cache_addr)(host, &addresses)) {
+			ast_debug(2, "Addr found in DNS cache\n");
+
+			cb(PJ_SUCCESS, token, &addresses);
+			return;
+		}
+		ast_debug(2, "No addr found in DNS cache\n");
+	}
+
 	resolve = ao2_alloc_options(sizeof(*resolve), sip_resolve_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
 	if (!resolve) {
 		cb(PJ_ENOMEM, token, NULL);
@@ -744,6 +794,8 @@ static void sip_resolve(pjsip_resolver_t *resolver, pj_pool_t *pool, const pjsip
 	pj_memcpy(&resolve->target, target, sizeof(*target));
 	resolve->callback = cb;
 	resolve->token = token;
+	strncpy(resolve->host, host, NI_MAXHOST);
+	resolve->ttl = 0;
 
 	if (AST_VECTOR_INIT(&resolve->resolving, 4)) {
 		ao2_ref(resolve, -1);
@@ -869,6 +921,22 @@ static void sip_check_transport(pj_pool_t *pool, pjsip_transport_type_e transpor
 	}
 }
 
+/* External API to set DNS cache function pointers */
+void pjsip_resolver_set_dns_cache(dns_cache_get_addr_t get_addr, dns_cache_update_t update_cache)
+{
+	ast_debug(3, "Setting DNS cache control function pointers\n");
+	get_dns_cache_addr = get_addr;
+	update_dns_cache = update_cache;
+}
+
+/* External API to clear DNS cache function pointers */
+void pjsip_resolver_clear_dns_cache(void)
+{
+	ast_debug(3, "Clearing DNS cache control function pointers\n");
+	get_dns_cache_addr = NULL;
+	update_dns_cache = NULL;
+}
+
 /*! \brief External resolver implementation for PJSIP */
 static pjsip_ext_resolver ext_resolver = {
 	.resolve = sip_resolve,
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index 6ed2141d69..9f91bc7eb9 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -286,6 +286,10 @@ static int set_outbound_initial_authentication_credentials(pjsip_regc *regc,
 /*! \brief Some thread local storage used to determine if the running thread invoked the callback */
 AST_THREADSTORAGE(register_callback_invoked);
 
+/*! DNS cache function pointer typedefs */
+typedef void (*dns_cache_update_addr_t)(void);
+typedef void (*dns_cache_clear_t)(void);
+
 /*! \brief Amount of buffer time (in seconds) before expiration that we re-register at */
 #define REREGISTER_BUFFER_TIME 10
 
@@ -505,6 +509,10 @@ static AO2_GLOBAL_OBJ_STATIC(current_states);
 /*! subscription id for network change events */
 static struct stasis_subscription *network_change_sub;
 
+/*! DNS cache function pointers */
+dns_cache_update_addr_t update_dns_cache_addr = NULL;
+dns_cache_clear_t clear_dns_cache = NULL;
+
 /*! \brief hashing function for state objects */
 static int registration_state_hash(const void *obj, const int flags)
 {
@@ -3142,6 +3150,23 @@ static void network_change_stasis_cb(void *data, struct stasis_subscription *sub
 	reregister_all();
 }
 
+/* External API to set DNS cache control function pointers */
+void pjsip_outbound_registration_set_dns_cache_control(dns_cache_update_addr_t update_addr,
+	       	dns_cache_clear_t clear_cache)
+{
+	ast_debug(3, "Setting DNS cache control function pointers\n");
+	update_dns_cache_addr = update_addr;
+	clear_dns_cache = clear_cache;
+}
+
+/* External API to clear DNS cache control function pointers */
+void pjsip_outbound_registration_clear_dns_cache_control(void)
+{
+	ast_debug(3, "Clearing DNS cache control function pointers\n");
+	update_dns_cache_addr = NULL;
+	clear_dns_cache = NULL;
+}
+
 static int unload_module(void)
 {
 	int remaining;
-- 
GitLab