summaryrefslogtreecommitdiff
path: root/res/res_stun_monitor.c
diff options
context:
space:
mode:
Diffstat (limited to 'res/res_stun_monitor.c')
-rw-r--r--res/res_stun_monitor.c302
1 files changed, 188 insertions, 114 deletions
diff --git a/res/res_stun_monitor.c b/res/res_stun_monitor.c
index 64ca73eda..b9854e60f 100644
--- a/res/res_stun_monitor.c
+++ b/res/res_stun_monitor.c
@@ -38,107 +38,137 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/stun.h"
#include "asterisk/netsock2.h"
#include "asterisk/lock.h"
+#include "asterisk/acl.h"
#include <fcntl.h>
-static const int DEFAULT_MONITOR_REFRESH = 30;
+#define DEFAULT_MONITOR_REFRESH 30 /*!< Default refresh period in seconds */
static const char stun_conf_file[] = "res_stun_monitor.conf";
static struct ast_sched_context *sched;
static struct {
- struct sockaddr_in stunaddr; /*!< The stun address we send requests to*/
- struct sockaddr_in externaladdr; /*!< current perceived external address. */
+ /*! STUN monitor protection lock. */
ast_mutex_t lock;
+ /*! Current perceived external address. */
+ struct sockaddr_in external_addr;
+ /*! STUN server host name. */
+ const char *server_hostname;
+ /*! Port of STUN server to use */
+ unsigned int stun_port;
+ /*! Number of seconds between polls to the STUN server for the external address. */
unsigned int refresh;
- int stunsock;
+ /*! Monitoring STUN socket. */
+ int stun_sock;
+ /*! TRUE if the STUN monitor is enabled. */
unsigned int monitor_enabled:1;
- unsigned int externaladdr_known:1;
+ /*! TRUE if the perceived external address is valid/known. */
+ unsigned int external_addr_known:1;
+ /*! TRUE if we have already griped about a STUN poll failing. */
+ unsigned int stun_poll_failed_gripe:1;
} args;
-static inline void stun_close_sock(void)
+static void stun_close_sock(void)
{
- if (args.stunsock != -1) {
- close(args.stunsock);
- args.stunsock = -1;
- memset(&args.externaladdr, 0, sizeof(args.externaladdr));
- args.externaladdr_known = 0;
+ if (0 <= args.stun_sock) {
+ close(args.stun_sock);
+ args.stun_sock = -1;
}
}
-/* \brief purge the stun socket's receive buffer before issuing a new request
- *
- * XXX Note that this is somewhat of a hack. This function is essentially doing
- * a cleanup on the socket rec buffer to handle removing any STUN responses we have not
- * handled. This is called before sending out a new STUN request so we don't read
- * a latent previous response thinking it is new.
- */
-static void stun_purge_socket(void)
-{
- int flags = fcntl(args.stunsock, F_GETFL);
- int res = 0;
- unsigned char reply_buf[1024];
-
- fcntl(args.stunsock, F_SETFL, flags | O_NONBLOCK);
- while (res != -1) {
- /* throw away everything in the buffer until we reach the end. */
- res = recv(args.stunsock, reply_buf, sizeof(reply_buf), 0);
- }
- fcntl(args.stunsock, F_SETFL, flags & ~O_NONBLOCK);
-}
-
/* \brief called by scheduler to send STUN request */
static int stun_monitor_request(const void *blarg)
{
int res;
- int generate_event = 0;
- struct sockaddr_in answer = { 0, };
-
+ struct sockaddr_in answer;
+ static const struct sockaddr_in no_addr = { 0, };
- /* once the stun socket goes away, this scheduler item will go away as well */
ast_mutex_lock(&args.lock);
- if (args.stunsock == -1) {
- ast_log(LOG_ERROR, "STUN monitor: can not send STUN request, socket is not open\n");
+ if (!args.monitor_enabled) {
goto monitor_request_cleanup;
}
- stun_purge_socket();
-
- if (!(ast_stun_request(args.stunsock, &args.stunaddr, NULL, &answer)) &&
- (memcmp(&args.externaladdr, &answer, sizeof(args.externaladdr)))) {
- const char *newaddr = ast_strdupa(ast_inet_ntoa(answer.sin_addr));
- int newport = ntohs(answer.sin_port);
-
- ast_log(LOG_NOTICE, "STUN MONITOR: Old external address/port %s:%d now seen as %s:%d \n",
- ast_inet_ntoa(args.externaladdr.sin_addr), ntohs(args.externaladdr.sin_port),
- newaddr, newport);
-
- memcpy(&args.externaladdr, &answer, sizeof(args.externaladdr));
+ if (args.stun_sock < 0) {
+ struct ast_sockaddr stun_addr;
- if (args.externaladdr_known) {
- /* the external address was already known, and has changed... generate event. */
- generate_event = 1;
+ /* STUN socket not open. Refresh the server DNS address resolution. */
+ if (!args.server_hostname) {
+ /* No STUN hostname? */
+ goto monitor_request_cleanup;
+ }
- } else {
- /* this was the first external address we found, do not alert listeners
- * until this address changes to something else. */
- args.externaladdr_known = 1;
+ /* Lookup STUN address. */
+ memset(&stun_addr, 0, sizeof(stun_addr));
+ stun_addr.ss.ss_family = AF_INET;
+ if (ast_get_ip(&stun_addr, args.server_hostname)) {
+ /* Lookup failed. */
+ ast_log(LOG_WARNING, "Unable to lookup STUN server '%s'\n",
+ args.server_hostname);
+ goto monitor_request_cleanup;
}
- }
+ ast_sockaddr_set_port(&stun_addr, args.stun_port);
- if (generate_event) {
- struct ast_event *event = ast_event_new(AST_EVENT_NETWORK_CHANGE, AST_EVENT_IE_END);
- if (!event) {
- ast_log(LOG_ERROR, "STUN monitor: could not create AST_EVENT_NETWORK_CHANGE event.\n");
+ /* open socket binding */
+ args.stun_sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (args.stun_sock < 0) {
+ ast_log(LOG_WARNING, "Unable to create STUN socket: %s\n", strerror(errno));
goto monitor_request_cleanup;
}
- if (ast_event_queue(event)) {
- ast_event_destroy(event);
- event = NULL;
- ast_log(LOG_ERROR, "STUN monitor: could not queue AST_EVENT_NETWORK_CHANGE event.\n");
+ if (ast_connect(args.stun_sock, &stun_addr)) {
+ ast_log(LOG_WARNING, "STUN Failed to connect to %s: %s\n",
+ ast_sockaddr_stringify(&stun_addr), strerror(errno));
+ stun_close_sock();
goto monitor_request_cleanup;
}
}
+ res = ast_stun_request(args.stun_sock, NULL, NULL, &answer);
+ if (res) {
+ /*
+ * STUN request timed out or errored.
+ *
+ * Refresh the server DNS address resolution next time around.
+ */
+ if (!args.stun_poll_failed_gripe) {
+ args.stun_poll_failed_gripe = 1;
+ ast_log(LOG_WARNING, "STUN poll %s. Re-evaluating STUN server address.\n",
+ res < 0 ? "failed" : "got no response");
+ }
+ stun_close_sock();
+ } else {
+ args.stun_poll_failed_gripe = 0;
+ if (memcmp(&no_addr, &answer, sizeof(no_addr))
+ && memcmp(&args.external_addr, &answer, sizeof(args.external_addr))) {
+ const char *newaddr = ast_strdupa(ast_inet_ntoa(answer.sin_addr));
+ int newport = ntohs(answer.sin_port);
+
+ ast_log(LOG_NOTICE, "Old external address/port %s:%d now seen as %s:%d.\n",
+ ast_inet_ntoa(args.external_addr.sin_addr),
+ ntohs(args.external_addr.sin_port), newaddr, newport);
+
+ args.external_addr = answer;
+
+ if (args.external_addr_known) {
+ struct ast_event *event;
+
+ /*
+ * The external address was already known, and has changed...
+ * generate event.
+ */
+ event = ast_event_new(AST_EVENT_NETWORK_CHANGE, AST_EVENT_IE_END);
+ if (!event) {
+ ast_log(LOG_ERROR, "Could not create AST_EVENT_NETWORK_CHANGE event.\n");
+ } else if (ast_event_queue(event)) {
+ ast_event_destroy(event);
+ ast_log(LOG_ERROR, "Could not queue AST_EVENT_NETWORK_CHANGE event.\n");
+ }
+ } else {
+ /* this was the first external address we found, do not alert listeners
+ * until this address changes to something else. */
+ args.external_addr_known = 1;
+ }
+ }
+ }
+
monitor_request_cleanup:
/* always refresh this scheduler item. It will be removed elsewhere when
* it is supposed to go away */
@@ -148,46 +178,40 @@ monitor_request_cleanup:
return res;
}
-/* \brief stops the stun monitor thread
+/*!
+ * \internal
+ * \brief Stops the STUN monitor thread.
+ *
* \note do not hold the args->lock while calling this
+ *
+ * \return Nothing
*/
static void stun_stop_monitor(void)
{
+ ast_mutex_lock(&args.lock);
+ args.monitor_enabled = 0;
+ ast_free((char *) args.server_hostname);
+ args.server_hostname = NULL;
+ stun_close_sock();
+ ast_mutex_unlock(&args.lock);
+
if (sched) {
ast_sched_context_destroy(sched);
sched = NULL;
ast_log(LOG_NOTICE, "STUN monitor stopped\n");
}
- /* it is only safe to destroy the socket without holding arg->lock
- * after the sched thread is destroyed */
- stun_close_sock();
}
-/* \brief starts the stun monitor thread
+/*!
+ * \internal
+ * \brief Starts the STUN monitor thread.
+ *
* \note The args->lock MUST be held when calling this function
+ *
+ * \return Nothing
*/
static int stun_start_monitor(void)
{
- struct ast_sockaddr dst;
- /* clean up any previous open socket */
- stun_close_sock();
-
- /* create destination ast_sockaddr */
- ast_sockaddr_from_sin(&dst, &args.stunaddr);
-
- /* open new socket binding */
- args.stunsock = socket(AF_INET, SOCK_DGRAM, 0);
- if (args.stunsock < 0) {
- ast_log(LOG_WARNING, "Unable to create STUN socket: %s\n", strerror(errno));
- return -1;
- }
-
- if (ast_connect(args.stunsock, &dst) != 0) {
- ast_log(LOG_WARNING, "SIP STUN Failed to connect to %s\n", ast_sockaddr_stringify(&dst));
- stun_close_sock();
- return -1;
- }
-
/* if scheduler thread is not started, make sure to start it now */
if (sched) {
return 0; /* already started */
@@ -195,7 +219,6 @@ static int stun_start_monitor(void)
if (!(sched = ast_sched_context_create())) {
ast_log(LOG_ERROR, "Failed to create stun monitor scheduler context\n");
- stun_close_sock();
return -1;
}
@@ -210,7 +233,6 @@ static int stun_start_monitor(void)
ast_log(LOG_ERROR, "Unable to schedule STUN network monitor \n");
ast_sched_context_destroy(sched);
sched = NULL;
- stun_close_sock();
return -1;
}
@@ -219,6 +241,65 @@ static int stun_start_monitor(void)
return 0;
}
+/*!
+ * \internal
+ * \brief Parse and setup the stunaddr parameter.
+ *
+ * \param value Configuration parameter variable value.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int setup_stunaddr(const char *value)
+{
+ char *val;
+ char *host_str;
+ char *port_str;
+ unsigned int port;
+ struct ast_sockaddr stun_addr;
+
+ if (ast_strlen_zero(value)) {
+ /* Setting to an empty value disables STUN monitoring. */
+ args.monitor_enabled = 0;
+ return 0;
+ }
+
+ val = ast_strdupa(value);
+ if (!ast_sockaddr_split_hostport(val, &host_str, &port_str, 0)
+ || ast_strlen_zero(host_str)) {
+ return -1;
+ }
+
+ /* Determine STUN port */
+ if (ast_strlen_zero(port_str)
+ || 1 != sscanf(port_str, "%30u", &port)) {
+ port = STANDARD_STUN_PORT;
+ }
+
+ host_str = ast_strdup(host_str);
+ if (!host_str) {
+ return -1;
+ }
+
+ /* Lookup STUN address. */
+ memset(&stun_addr, 0, sizeof(stun_addr));
+ stun_addr.ss.ss_family = AF_INET;
+ if (ast_get_ip(&stun_addr, host_str)) {
+ ast_log(LOG_WARNING, "Unable to lookup STUN server '%s'\n", host_str);
+ ast_free(host_str);
+ return -1;
+ }
+
+ /* Save STUN server information. */
+ ast_free((char *) args.server_hostname);
+ args.server_hostname = host_str;
+ args.stun_port = port;
+
+ /* Enable STUN monitor */
+ args.monitor_enabled = 1;
+ return 0;
+}
+
static int load_config(int startup)
{
struct ast_flags config_flags = { 0, };
@@ -229,39 +310,37 @@ static int load_config(int startup)
ast_set_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
}
- if (!(cfg = ast_config_load2(stun_conf_file, "res_stun_monitor", config_flags)) ||
- cfg == CONFIG_STATUS_FILEINVALID) {
+ cfg = ast_config_load2(stun_conf_file, "res_stun_monitor", config_flags);
+ if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_WARNING, "Unable to load config %s\n", stun_conf_file);
return -1;
}
-
- if (cfg == CONFIG_STATUS_FILEUNCHANGED && !startup) {
+ if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
return 0;
}
+ /* clean up any previous open socket */
+ stun_close_sock();
+ args.stun_poll_failed_gripe = 0;
+
/* set defaults */
args.monitor_enabled = 0;
- memset(&args.stunaddr, 0, sizeof(args.stunaddr));
args.refresh = DEFAULT_MONITOR_REFRESH;
for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
if (!strcasecmp(v->name, "stunaddr")) {
- args.stunaddr.sin_port = htons(STANDARD_STUN_PORT);
- if (ast_parse_arg(v->value, PARSE_INADDR, &args.stunaddr)) {
- ast_log(LOG_WARNING, "Invalid STUN server address: %s\n", v->value);
- } else {
- ast_log(LOG_NOTICE, "STUN monitor enabled: %s\n", v->value);
- args.monitor_enabled = 1;
+ if (setup_stunaddr(v->value)) {
+ ast_log(LOG_WARNING, "Invalid STUN server address: %s at line %d\n",
+ v->value, v->lineno);
}
} else if (!strcasecmp(v->name, "stunrefresh")) {
if ((sscanf(v->value, "%30u", &args.refresh) != 1) || !args.refresh) {
ast_log(LOG_WARNING, "Invalid stunrefresh value '%s', must be an integer > 0 at line %d\n", v->value, v->lineno);
args.refresh = DEFAULT_MONITOR_REFRESH;
- } else {
- ast_log(LOG_NOTICE, "STUN Monitor set to refresh every %d seconds\n", args.refresh);
}
} else {
- ast_log(LOG_WARNING, "SIP STUN: invalid config option %s at line %d\n", v->value, v->lineno);
+ ast_log(LOG_WARNING, "Invalid config option %s at line %d\n",
+ v->value, v->lineno);
}
}
@@ -280,8 +359,7 @@ static int __reload(int startup)
}
ast_mutex_unlock(&args.lock);
- if ((res == -1) || !args.monitor_enabled) {
- args.monitor_enabled = 0;
+ if (res < 0 || !args.monitor_enabled) {
stun_stop_monitor();
}
@@ -303,12 +381,8 @@ static int unload_module(void)
static int load_module(void)
{
ast_mutex_init(&args.lock);
- args.stunsock = -1;
- memset(&args.externaladdr, 0, sizeof(args.externaladdr));
- args.externaladdr_known = 0;
- sched = NULL;
+ args.stun_sock = -1;
if (__reload(1)) {
- stun_stop_monitor();
ast_mutex_destroy(&args.lock);
return AST_MODULE_LOAD_DECLINE;
}