Index: main/stun.c =================================================================== --- main/stun.c (revision 345978) +++ main/stun.c (working copy) @@ -380,7 +380,7 @@ unsigned char reqdata[1024]; int reqlen, reqleft; struct stun_attr *attr; - int res = 0; + int res = -1; int retry; req = (struct stun_header *)reqdata; @@ -407,11 +407,21 @@ retry, res); continue; } - if (answer == NULL) + if (answer == NULL) { + /* Successful send since we don't care about any response. */ + res = 0; break; + } res = ast_poll(&pfds, 1, 3000); - if (res <= 0) /* timeout or error */ + if (res < 0) { + /* Error */ continue; + } + if (!res) { + /* No response, timeout */ + res = 1; + continue; + } memset(&src, 0, sizeof(src)); srclen = sizeof(src); /* XXX pass -1 in the size, because stun_handle_packet might @@ -427,7 +437,8 @@ memset(answer, 0, sizeof(struct sockaddr_in)); ast_stun_handle_packet(s, &src, reply_buf, res, stun_get_mapped, answer); - res = 0; /* signal regular exit */ + /* Success. answer contains the external address if available. */ + res = 0; break; } return res; Index: configs/res_stun_monitor.conf.sample =================================================================== --- configs/res_stun_monitor.conf.sample (revision 345978) +++ configs/res_stun_monitor.conf.sample (working copy) @@ -6,7 +6,7 @@ ; provided by the STUN server an event is sent out internally within Asterisk ; to alert all listeners to that event of the change. -; The current default listeners for the netork change event include chan_sip +; The current default listeners for the network change event include chan_sip ; and chan_iax. Both of these channel drivers by default react to this event ; by renewing all outbound registrations. This allows the endpoints Asterisk ; is registering with to become aware of the address change and know the new @@ -15,8 +15,10 @@ [general] ; ; ---- STUN Server configuration --- -; Setting the 'stunaddr' option to a valid address enables the stun monitor. +; Setting the 'stunaddr' option to a valid address enables the STUN monitor. ; -; stunaddr = mystunserver.com ; address of the stun server to query. -; stunrefresh = 30 ; number of seconds between stun refreshes. default is 30 -; +;stunaddr = mystunserver.com ; Address of the STUN server to query. +;stunrefresh = 30 ; Number of seconds between STUN refreshes. + ; Default is 30. +;stunservermonitor = no ; Monitor DNS for STUN server address changes. + ; Default is no. Index: res/res_stun_monitor.c =================================================================== --- res/res_stun_monitor.c (revision 345978) +++ res/res_stun_monitor.c (working copy) @@ -38,108 +38,103 @@ #include "asterisk/stun.h" #include "asterisk/netsock2.h" #include "asterisk/lock.h" +#include "asterisk/dnsmgr.h" #include -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_thread *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; + /*! Address of STUN server to use (Updated by DNS manager as needed) */ + struct ast_sockaddr stun_addr; + /*! DNS manager update control for the STUN server address */ + struct ast_dnsmgr_entry *dnsmgr; + /*! Port of STUN server to use */ + unsigned int stun_port; + /*! Number of seconds to poll the STUN server for the external address. */ unsigned int refresh; - int stunsock; + /*! TRUE if the STUN monitor is enabled. */ unsigned int monitor_enabled:1; - unsigned int externaladdr_known:1; + /*! TRUE if monitor DNS for STUN server address changes. */ + unsigned int monitor_stun_address:1; + /*! TRUE if the perceived external address is valid/known. */ + unsigned int external_addr_known:1; } args; -static inline 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; - } -} - -/* \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; + int skt = -1; struct sockaddr_in answer = { 0, }; + struct sockaddr_in no_addr = { 0, }; + struct sockaddr_in stunaddr;/*!< The address where we send STUN requests. */ + struct ast_sockaddr dst; - - /* 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(); + /* open socket binding */ + ast_sockaddr_copy(&dst, &args.stun_addr); + ast_sockaddr_set_port(&dst, args.stun_port); + skt = socket(AF_INET, SOCK_DGRAM, 0); + if (skt < 0) { + ast_log(LOG_WARNING, "Unable to create STUN socket: %s\n", strerror(errno)); + goto monitor_request_cleanup; + } + if (ast_connect(skt, &dst) != 0) { + ast_log(LOG_WARNING, "STUN Failed to connect to %s\n", + ast_sockaddr_stringify(&dst)); + goto monitor_request_cleanup; + } - if (!(ast_stun_request(args.stunsock, &args.stunaddr, NULL, &answer)) && - (memcmp(&args.externaladdr, &answer, sizeof(args.externaladdr)))) { + if (!ast_sockaddr_to_sin(&dst, &stunaddr)) { + goto monitor_request_cleanup; + } + if (!ast_stun_request(skt, &stunaddr, NULL, &answer) + && 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, "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), + ast_log(LOG_NOTICE, + "STUN MONITOR: 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); - memcpy(&args.externaladdr, &answer, sizeof(args.externaladdr)); + memcpy(&args.external_addr, &answer, sizeof(args.external_addr)); - if (args.externaladdr_known) { + if (args.external_addr_known) { + struct ast_event *event; + /* the external address was already known, and has changed... generate event. */ - generate_event = 1; - + 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.externaladdr_known = 1; + args.external_addr_known = 1; } } - 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"); - 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"); - goto monitor_request_cleanup; - } +monitor_request_cleanup: + if (0 <= skt) { + close(skt); } -monitor_request_cleanup: /* always refresh this scheduler item. It will be removed elsewhere when * it is supposed to go away */ res = args.refresh * 1000; @@ -148,18 +143,34 @@ return res; } +/*! + * \internal + * \brief Disable the STUN monitor. + * + * \return Nothing + */ +static void stun_disable_monitor(void) +{ + args.monitor_enabled = 0; + + /* Stop any DNS manager monitoring */ + ast_dnsmgr_release(args.dnsmgr); + args.dnsmgr = NULL; +} + /* \brief stops the stun monitor thread * \note do not hold the args->lock while calling this */ static void stun_stop_monitor(void) { + ast_mutex_lock(&args.lock); + stun_disable_monitor(); + ast_mutex_unlock(&args.lock); + if (sched) { sched = ast_sched_thread_destroy(sched); 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 @@ -167,26 +178,12 @@ */ 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 (!args.monitor_stun_address) { + /* Stop any DNS manager monitoring */ + ast_dnsmgr_release(args.dnsmgr); + args.dnsmgr = NULL; } - 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 */ @@ -194,14 +191,12 @@ if (!(sched = ast_sched_thread_create())) { ast_log(LOG_ERROR, "Failed to create stun monitor scheduler thread\n"); - stun_close_sock(); return -1; } if (ast_sched_thread_add_variable(sched, (args.refresh * 1000), stun_monitor_request, NULL, 1) < 0) { ast_log(LOG_ERROR, "Unable to schedule STUN network monitor \n"); sched = ast_sched_thread_destroy(sched); - stun_close_sock(); return -1; } @@ -209,6 +204,55 @@ 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 parse_stunaddr(const char *value) +{ + char *val; + char *host_str; + char *port_str; + unsigned int port; + + if (ast_strlen_zero(value)) { + /* Setting to an empty value disables STUN monitoring. */ + stun_disable_monitor(); + return 0; + } + + val = ast_strdupa(value); + if (!ast_sockaddr_split_hostport(val, &host_str, &port_str, 0) + || ast_strlen_zero(host_str)) { + return -1; + } + + /* Stop any previous DNS manager monitoring */ + ast_dnsmgr_release(args.dnsmgr); + args.dnsmgr = NULL; + + /* Determine STUN port */ + if (ast_strlen_zero(port_str) + || 1 != sscanf(port_str, "%30u", &port)) { + port = STANDARD_STUN_PORT; + } + args.stun_port = port; + + /* Lookup STUN address. */ + memset(&args.stun_addr, 0, sizeof(args.stun_addr)); + args.stun_addr.ss.ss_family = AF_INET; + ast_dnsmgr_lookup(host_str, &args.stun_addr, &args.dnsmgr, NULL); + + /* Enable STUN monitor */ + args.monitor_enabled = 1; + return 0; +} + static int load_config(int startup) { struct ast_flags config_flags = { 0, }; @@ -221,7 +265,7 @@ if (!(cfg = ast_config_load2(stun_conf_file, "res_stun_monitor", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) { - ast_log(LOG_ERROR, "Unable to load config %s\n", stun_conf_file); + ast_log(LOG_WARNING, "Unable to load config %s\n", stun_conf_file); return -1; } @@ -230,26 +274,23 @@ } /* set defaults */ - args.monitor_enabled = 0; - memset(&args.stunaddr, 0, sizeof(args.stunaddr)); + stun_disable_monitor(); + args.monitor_stun_address = 0; 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 (parse_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 if (!strcasecmp(v->name, "stunservermonitor")) { + args.monitor_stun_address = ast_true(v->value) ? 1 : 0; } else { ast_log(LOG_WARNING, "SIP STUN: invalid config option %s at line %d\n", v->value, v->lineno); } @@ -271,7 +312,6 @@ ast_mutex_unlock(&args.lock); if ((res == -1) || !args.monitor_enabled) { - args.monitor_enabled = 0; stun_stop_monitor(); } @@ -293,12 +333,7 @@ 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; if (__reload(1)) { - stun_stop_monitor(); ast_mutex_destroy(&args.lock); return AST_MODULE_LOAD_DECLINE; }