summaryrefslogtreecommitdiff
path: root/res/res_stun_monitor.c
diff options
context:
space:
mode:
authorDavid Vossel <dvossel@digium.com>2010-08-13 20:05:44 +0000
committerDavid Vossel <dvossel@digium.com>2010-08-13 20:05:44 +0000
commit0f8eaa62998c697ee72c6c3c98bf0c9478de5fb7 (patch)
tree595c0ede600ce5c602d97f7592666d96f3c7e3c4 /res/res_stun_monitor.c
parent86142d711f40bb582e0bfea311ec10b93ef7d60a (diff)
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
Diffstat (limited to 'res/res_stun_monitor.c')
-rw-r--r--res/res_stun_monitor.c311
1 files changed, 311 insertions, 0 deletions
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 <dvossel@digium.com>
+ *
+ * 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 <dvossel@digium.com>
+ */
+
+#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 <fcntl.h>
+
+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
+ );