#ifdef HAVE_PJPROJECT
static int icesupport = DEFAULT_ICESUPPORT;
static int stun_software_attribute = DEFAULT_STUN_SOFTWARE_ATTRIBUTE;
-static struct sockaddr_in stunaddr;
static pj_str_t turnaddr;
static int turnport = DEFAULT_TURN_PORT;
static pj_str_t turnusername;
static struct ast_acl_list *stun_acl = NULL;
static ast_rwlock_t stun_acl_lock = AST_RWLOCK_INIT_VALUE;
+static struct sockaddr_in stunaddr;
/*! stunaddr recurring resolution */
static ast_rwlock_t stunaddr_lock = AST_RWLOCK_INIT_VALUE;
static struct ast_dns_query_recurring *stunaddr_resolver = NULL;
+/*! TTL from last successful query */
+static int stunaddr_ttl = 0;
+/*! The current hostname if stunaddr isn't an IP address */
+static char *stun_hostname = NULL;
+/*! Re-resolve hostname if TTL = 0? */
+static int stunaddr_reresolve_ttl_0 = 0;
/*! \brief Pool factory used by pjlib to allocate memory. */
static pj_caching_pool cachingpool;
#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 store_stunaddr_resolved(const char *name, const struct ast_dns_result *result, int lock);
#endif
#if defined(HAVE_OPENSSL) && (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_SRTP)
struct ast_ice_host_candidate *candidate;
int af_inet_ok = 0, af_inet6_ok = 0;
struct sockaddr_in stunaddr_copy;
+ int stunaddr_ttl_copy = 0;
+ char *stun_hostname_copy = NULL;
if (ast_sockaddr_is_ipv4(addr)) {
af_inet_ok = 1;
freeifaddrs(ifa);
}
+ /*
+ * Snap copies of the stun info with the stunaddr_lock held because
+ * recurring DNS lookup could be happening in another thread.
+ */
ast_rwlock_rdlock(&stunaddr_lock);
memcpy(&stunaddr_copy, &stunaddr, sizeof(stunaddr));
+ stunaddr_ttl_copy = stunaddr_ttl;
+ stun_hostname_copy = ast_strdupa(S_OR(stun_hostname, ""));
ast_rwlock_unlock(&stunaddr_lock);
/* 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) &&
+ if ( (!ast_strlen_zero(stun_hostname_copy) || stunaddr_copy.sin_addr.s_addr)
+ && !stun_address_is_blacklisted(addr) &&
(ast_sockaddr_is_ipv4(addr) || ast_sockaddr_is_any(addr)) &&
count < PJ_ICE_MAX_CAND) {
struct sockaddr_in answer;
int rsp;
- ast_debug_category(3, AST_DEBUG_CATEGORY_ICE | AST_DEBUG_CATEGORY_STUN,
+ ast_debug_category(2, AST_DEBUG_CATEGORY_ICE | AST_DEBUG_CATEGORY_STUN,
"(%p) ICE request STUN %s %s candidate\n", instance,
transport == AST_TRANSPORT_UDP ? "UDP" : "TCP",
component == AST_RTP_ICE_COMPONENT_RTP ? "RTP" : "RTCP");
/*
* The instance should not be locked because we can block
- * waiting for a STUN respone.
+ * waiting for stunaddr_lock and/or a STUN respone.
*/
ao2_unlock(instance);
+
+ /*
+ * If stunaddr_ttl is 0 and stun_hostname is set, then "stunaddr" was set to a
+ * hostname in rtp.conf but the last attempt at resolution returned a TTL = 0
+ * (don't cache) and periodic resolution was cancelled. Theoretically, as long
+ * as TTL = 0, we should do a synchronous lookup before every use of the address
+ * but historically (and incorrectly), we just kept on using the cached result
+ * forever.
+ *
+ * Now, if the "stunaddr_reresolve_ttl_0" parameter in rtp.conf set set to yes,
+ * we'll use the cached value for the current call setup (because we don't
+ * want to hold up the call for a synchronous DNS lookup) but restart periodic
+ * resolution. It's not obvious but restarting periodic resolution actually
+ * just triggers an asynchronous lookup which calls our stunaddr_resolve_callback
+ * then reschedules itself if the result has a TTL > 0.
+ *
+ * The bottom line is that it's not really an issue if THIS call setup attempt
+ * uses a stale cached entry if TTL = 0 as long as we trigger a re-resolution
+ * fairly quickly and keep doing it as long as TTL = 0.
+ *
+ */
+ ast_debug_stun(2, "Checking stunaddr_reresolve_ttl_0: %s TTL: %d Host: %s resolver: %p\n",
+ AST_CLI_YESNO(stunaddr_reresolve_ttl_0), stunaddr_ttl_copy, stun_hostname_copy,
+ stunaddr_resolver);
+
+ if (stunaddr_reresolve_ttl_0 && stunaddr_ttl_copy == 0
+ && !ast_strlen_zero(stun_hostname_copy) && !stunaddr_resolver) {
+
+ /*
+ * We need the write lock becausae we might be setting stunaddr_resolver.
+ */
+ ast_rwlock_wrlock(&stunaddr_lock);
+
+ /*
+ * Now that we have the write lock, check whether we still need to resolve.
+ * It's possible that another thread got here first. It's also possible that
+ * a reload changed the "stunaddr" parameter to an IP address (which clears
+ * stun_hostname) so we don't need resolution at all any more.
+ */
+ if (stunaddr_ttl == 0 && !ast_strlen_zero(stun_hostname) && !stunaddr_resolver) {
+ ast_debug_stun(2, "Restarting recurring resolution for stun server '%s'\n",
+ stun_hostname);
+ /*
+ * This will return immediately after triggering an async lookup
+ * and scheduling the next lookup. stunaddr_resolve_callback will be
+ * called from another thread.
+ */
+ stunaddr_resolver = ast_dns_resolve_recurring(stun_hostname, T_A, C_IN,
+ &stunaddr_resolve_callback, NULL);
+ if (!stunaddr_resolver) {
+ ast_log(LOG_ERROR, "Failed to setup recurring DNS resolution of stunaddr '%s'",
+ stun_hostname);
+ }
+ } else {
+ ast_debug_stun(2, "stun TTL: %d H: %s. Re-resolution skipped because another thread took care of it.\n",
+ stunaddr_ttl, stun_hostname);
+ }
+
+ ast_rwlock_unlock(&stunaddr_lock);
+ } else {
+ ast_debug_stun(2, "stun TTL: %d H: %s. Re-resolution not needed or disabled.\n", stunaddr_ttl, stun_hostname);
+ }
+
rsp = ast_stun_request(component == AST_RTP_ICE_COMPONENT_RTCP
? rtp->rtcp->s : rtp->s, &stunaddr_copy, NULL, &answer);
ao2_lock(instance);
format = frame->subclass.format;
if (ast_format_cmp(rtp->lasttxformat, format) == AST_FORMAT_CMP_NOT_EQUAL) {
/* Oh dear, if the format changed we will have to set up a new smoother */
- ast_debug_rtp(1, "(%s) RTP ooh, format changed from %s to %s\n",
+ ast_debug_rtp(3, "(%s) RTP ooh, format changed from %s to %s\n",
ast_rtp_instance_get_channel_id(instance),
ast_format_get_name(rtp->lasttxformat),
ast_format_get_name(frame->subclass.format));
#ifdef HAVE_PJPROJECT
static void stunaddr_resolve_callback(const struct ast_dns_query *query)
{
- 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;
-
- if (!store_stunaddr_resolved(query)) {
- ast_log(LOG_WARNING, "Failed to resolve stunaddr '%s'. Cancelling recurring resolution.\n", stunaddr_name);
- return;
- }
- if (DEBUG_ATLEAST(2)) {
- ast_rwlock_rdlock(&stunaddr_lock);
- stunaddr_resolved_str = ast_inet_ntoa(stunaddr.sin_addr);
- ast_rwlock_unlock(&stunaddr_lock);
-
- ast_debug_stun(2, "Resolved stunaddr '%s' to '%s'. Lowest TTL = %d.\n",
- stunaddr_name,
- stunaddr_resolved_str,
- lowest_ttl);
- }
-
- 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));
- }
+ /* Call store_stunaddr_resolved with locking enabled. */
+ store_stunaddr_resolved(stunaddr_name, ast_dns_query_get_result(query), 1);
}
-static int store_stunaddr_resolved(const struct ast_dns_query *query)
+static int store_stunaddr_resolved(const char *name, const struct ast_dns_result *result, int lock)
{
- const struct ast_dns_result *result = ast_dns_query_get_result(query);
const struct ast_dns_record *record;
+ struct ast_dns_query_recurring *last_resolver = stunaddr_resolver;
+ /*
+ * According to https://datatracker.ietf.org/doc/html/rfc2181#section-5.2,
+ * It is an error if the TTLs in an RRset differ but if they do, we should
+ * use the lowest one.
+ */
+ const int ttl = ast_dns_result_get_lowest_ttl(result);
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);
+ ast_debug_stun(2, "Record rr_type '%u' ttl: %d data_size '%zu' from DNS query for stunaddr '%s'\n",
+ rr_type, ttl, data_size, name);
+
if (rr_type == ns_t_a && data_size == 4) {
- ast_rwlock_wrlock(&stunaddr_lock);
+ if (lock) {
+ ast_rwlock_wrlock(&stunaddr_lock);
+ }
memcpy(&stunaddr.sin_addr, data, data_size);
stunaddr.sin_family = AF_INET;
- ast_rwlock_unlock(&stunaddr_lock);
+ stunaddr_ttl = ttl;
+ ast_debug_stun(2, "Resolved stunaddr '%s' to '%s'. TTL = %d.\n", name,
+ ast_inet_ntoa(stunaddr.sin_addr), stunaddr_ttl);
+ if (stunaddr_ttl == 0) {
+ ast_log(LOG_WARNING, "Resolution for stunaddr '%s' returned TTL = 0. Recurring resolution disabled.\n", name);
+ ao2_cleanup(stunaddr_resolver);
+ stunaddr_resolver = NULL;
+ }
+ if (lock) {
+ ast_rwlock_unlock(&stunaddr_lock);
+ }
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));
+ ast_debug_stun(2, "Unrecognized rr_type '%u' or data_size '%zu' from DNS query for stunaddr '%s'\n",
+ rr_type, data_size, name);
continue;
}
}
+
+ ao2_cleanup(stunaddr_resolver);
+ stunaddr_resolver = NULL;
+ stunaddr_ttl = 0;
+
+ if (stunaddr.sin_addr.s_addr) {
+ ast_log(LOG_WARNING, "Lookup of stunaddr '%s' failed.%s STUN continuing with server %s:%d\n",
+ name, last_resolver ? " Periodic resolution cancelled." : "",
+ ast_inet_ntoa(stunaddr.sin_addr), htons(stunaddr.sin_port));
+ } else {
+ ast_log(LOG_WARNING, "Lookup of stunaddr '%s' failed. STUN disabled.\n", name);
+ }
+
return 0;
}
static void clean_stunaddr(void) {
+ ast_rwlock_wrlock(&stunaddr_lock);
+ ast_debug_stun(2, "Cleanup\n");
if (stunaddr_resolver) {
+ ast_debug_stun(2, "Cancelling recurring resolution for '%s'\n", stun_hostname);
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));
+ stunaddr_ttl = 0;
+ ast_free(stun_hostname);
+ stun_hostname = NULL;
ast_rwlock_unlock(&stunaddr_lock);
}
#endif
{
#ifdef HAVE_PJPROJECT
struct sockaddr_in stunaddr_copy;
+ const char *stun_hostname_copy = NULL;
+ int stunaddr_ttl_copy = 0;
#endif
switch (cmd) {
case CLI_INIT:
ast_rwlock_rdlock(&stunaddr_lock);
memcpy(&stunaddr_copy, &stunaddr, sizeof(stunaddr));
+ stun_hostname_copy = ast_strdupa(S_OR(stun_hostname, ""));
+ stunaddr_ttl_copy = stunaddr_ttl;
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));
+
+ ast_cli(a->fd, " STUN: %s\n", stunaddr_copy.sin_addr.s_addr ? "enbabled" : "disabled");
+ if (ast_strlen_zero(stun_hostname_copy)) {
+ ast_cli(a->fd, " Address: %s:%d\n", ast_inet_ntoa(stunaddr_copy.sin_addr),
+ htons(stunaddr_copy.sin_port));
+ } else {
+ ast_cli(a->fd, " Hostname: %s:%d\n", stun_hostname_copy, htons(stunaddr_copy.sin_port));
+ ast_cli(a->fd, " Resolved Addr: %s:%d%s\n", ast_inet_ntoa(stunaddr_copy.sin_addr),
+ htons(stunaddr_copy.sin_port),
+ stunaddr_copy.sin_addr.s_addr ? stunaddr_resolver ? "" : " (possibly stale)" : " (lookup failed)");
+ ast_cli(a->fd, " Last TTL: %d (periodic resolution %s)\n", stunaddr_ttl_copy,
+ stunaddr_resolver ? "enabled" : "disabled");
+ ast_cli(a->fd, " Reresove TTL 0: %s\n", AST_CLI_YESNO(stunaddr_reresolve_ttl_0));
+ }
+ if (stun_acl) {
+ ast_acl_output(a->fd, stun_acl, " ");
+ }
#endif
return CLI_SUCCESS;
}
}
#endif
+#ifdef HAVE_PJPROJECT
+static char *handle_cli_rtp_refresh_stun(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "rtp resolve stun hostname";
+ e->usage =
+ "Usage: rtp resolve stun hostname\n"
+ " Force a resolution of the STUN hostname (if set).\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != e->args) {
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_rwlock_wrlock(&stunaddr_lock);
+ if (ast_strlen_zero(stun_hostname)) {
+ if (stunaddr.sin_addr.s_addr) {
+ ast_cli(a->fd, "RTP STUN server specified as IP address '%s'. Resolution not required./n",
+ ast_inet_ntoa(stunaddr.sin_addr));
+ } else {
+ ast_cli(a->fd, "RTP STUN disabled./n");
+ }
+ } else {
+ if (stunaddr_resolver) {
+ ast_debug_stun(2, "Cancelling recurring resolution for '%s'\n", stun_hostname);
+ 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;
+ }
+ stunaddr_resolver = ast_dns_resolve_recurring(stun_hostname, T_A, C_IN, &stunaddr_resolve_callback, NULL);
+ if (!stunaddr_resolver) {
+ ast_cli(a->fd, "Failed to setup recurring DNS resolution of stunaddr '%s'",
+ stun_hostname);
+ } else {
+ ast_cli(a->fd, "Triggered background stun hostname resolution for '%s'. Run 'rtp show settings' to check results.\n", stun_hostname);
+ }
+ }
+ ast_rwlock_unlock(&stunaddr_lock);
+
+ return CLI_SUCCESS;
+}
+#endif
+
static struct ast_cli_entry cli_rtp[] = {
AST_CLI_DEFINE(handle_cli_rtp_set_debug, "Enable/Disable RTP debugging"),
AST_CLI_DEFINE(handle_cli_rtp_settings, "Display RTP settings"),
#ifdef AST_DEVMODE
AST_CLI_DEFINE(handle_cli_rtp_drop_incoming_packets, "Drop RTP incoming packets"),
#endif
+#ifdef HAVE_PJPROJECT
+ AST_CLI_DEFINE(handle_cli_rtp_refresh_stun, "Force a resolution of the STUN hostname"),
+#endif
};
static int rtp_reload(int reload, int by_external_config)
if ((s = ast_variable_retrieve(cfg, "general", "stun_software_attribute"))) {
stun_software_attribute = ast_true(s);
}
+ if ((s = ast_variable_retrieve(cfg, "general", "stunaddr_reresolve_ttl_0"))) {
+ stunaddr_reresolve_ttl_0 = ast_true(s);
+ }
if ((s = ast_variable_retrieve(cfg, "general", "stunaddr"))) {
char *hostport, *host, *port;
unsigned int port_parsed = STANDARD_STUN_PORT;
}
ast_rwlock_wrlock(&stunaddr_lock);
ast_sockaddr_to_sin(&stunaddr_parsed, &stunaddr);
+ /* Set stunaddr_ttl = -1 to indicate no resolution required in the future */
+ stunaddr_ttl = -1;
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);
+ ast_rwlock_wrlock(&stunaddr_lock);
+
+ stunaddr.sin_port = htons(port_parsed);
+ ast_free(stun_hostname);
+ stun_hostname = ast_strdup(host);
+ if (!stun_hostname) {
+ ast_log(LOG_ERROR, "Failed to set stun_hostname from '%s'", host);
+ } else {
+ 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_debug_stun(2, "Attemping to start recurring stun hostname resolution for '%s'\n", stun_hostname);
+ }
+ /*
+ * Set stunaddr_ttl = 0 to indicate resolution is required.
+ * If a later query returns a positive ttl, great. We'll use the results
+ * of the last query until it expires. If it returns 0, we'll resolve
+ * every time we need it.
+ */
+ stunaddr_ttl = 0;
}
+ ast_rwlock_unlock(&stunaddr_lock);
+
+
} else {
ast_log(LOG_ERROR, "Failed to parse stunaddr '%s'", hostport);
}