From 0f8eaa62998c697ee72c6c3c98bf0c9478de5fb7 Mon Sep 17 00:00:00 2001 From: David Vossel Date: Fri, 13 Aug 2010 20:05:44 +0000 Subject: Merged revisions 282269 via svnmerge from https://origsvn.digium.com/svn/asterisk/branches/1.8 ........ r282269 | dvossel | 2010-08-13 15:03:56 -0500 (Fri, 13 Aug 2010) | 4 lines res_stun_monitor for monitoring network changes behind a NAT device Review: https://reviewboard.asterisk.org/r/854 ........ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@282270 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- res/res_stun_monitor.c | 311 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 res/res_stun_monitor.c (limited to 'res') diff --git a/res/res_stun_monitor.c b/res/res_stun_monitor.c new file mode 100644 index 000000000..807f885db --- /dev/null +++ b/res/res_stun_monitor.c @@ -0,0 +1,311 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2010, Digium, Inc. + * + * David Vossel + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief STUN Network Monitor + * + * \author David Vossel + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/event.h" +#include "asterisk/sched.h" +#include "asterisk/config.h" +#include "asterisk/stun.h" +#include "asterisk/netsock2.h" +#include "asterisk/lock.h" +#include + +static const int STANDARD_STUN_PORT = 3478; +static const int DEFAULT_MONITOR_REFRESH = 30; + +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. */ + ast_mutex_t lock; + unsigned int refresh; + int stunsock; + unsigned int monitor_enabled:1; + unsigned int externaladdr_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; + struct sockaddr_in answer = { 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"); + 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.externaladdr_known) { + /* the external address was already known, and has changed... generate event. */ + generate_event = 1; + + } else { + /* this was the first external address we found, do not alert listeners + * until this address changes to something else. */ + args.externaladdr_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: + /* always refresh this scheduler item. It will be removed elsewhere when + * it is supposed to go away */ + res = args.refresh * 1000; + ast_mutex_unlock(&args.lock); + + return res; +} + +/* \brief stops the stun monitor thread + * \note do not hold the args->lock while calling this + */ +static void stun_stop_monitor(void) +{ + 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 + * \note The args->lock MUST be held when calling this function + */ +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 */ + } + + 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; + } + + ast_log(LOG_NOTICE, "STUN monitor started\n"); + return 0; +} + +static int load_config(int startup) +{ + struct ast_flags config_flags = { 0, }; + struct ast_config *cfg; + struct ast_variable *v; + + if (!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) { + ast_log(LOG_ERROR, "Unable to load config %s\n", stun_conf_file); + return -1; + } + + if (cfg == CONFIG_STATUS_FILEUNCHANGED && !startup) { + return 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; + } + } 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_config_destroy(cfg); + + return 0; +} + +static int __reload(int startup) +{ + int res; + + ast_mutex_lock(&args.lock); + if (!(res = load_config(startup)) && args.monitor_enabled) { + res = stun_start_monitor(); + } + ast_mutex_unlock(&args.lock); + + if ((res == -1) || !args.monitor_enabled) { + args.monitor_enabled = 0; + stun_stop_monitor(); + } + + return res; +} + +static int reload(void) +{ + return __reload(0); +} + +static int unload_module(void) +{ + stun_stop_monitor(); + ast_mutex_destroy(&args.lock); + return 0; +} + +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_FAILURE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "STUN Network Monitor", + .load = load_module, + .unload = unload_module, + .reload = reload, + .load_pri = AST_MODPRI_CHANNEL_DEPEND + ); -- cgit v1.2.3