123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- /*
- * 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>
- */
- /*** MODULEINFO
- <support_level>core</support_level>
- ***/
- #include "asterisk.h"
- ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
- #include "asterisk/module.h"
- #include "asterisk/sched.h"
- #include "asterisk/config.h"
- #include "asterisk/stun.h"
- #include "asterisk/netsock2.h"
- #include "asterisk/lock.h"
- #include "asterisk/acl.h"
- #include "asterisk/cli.h"
- #include "asterisk/json.h"
- #include "asterisk/stasis.h"
- #include "asterisk/stasis_system.h"
- #include "asterisk/astobj2.h"
- #include <fcntl.h>
- #define DEFAULT_MONITOR_REFRESH 30 /*!< Default refresh period in seconds */
- #define DEFAULT_RETRIES 3 /*!< retries shown in stun show status
- matching static retries in stun.c */
- static const char stun_conf_file[] = "res_stun_monitor.conf";
- static struct ast_sched_context *sched;
- static struct {
- /*! 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;
- /*! Monitoring STUN socket. */
- int stun_sock;
- /*! TRUE if the STUN monitor is enabled. */
- unsigned int monitor_enabled: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 void stun_close_sock(void)
- {
- if (0 <= args.stun_sock) {
- close(args.stun_sock);
- args.stun_sock = -1;
- }
- }
- /* \brief called by scheduler to send STUN request */
- static int stun_monitor_request(const void *blarg)
- {
- int res;
- struct sockaddr_in answer;
- static const struct sockaddr_in no_addr = { 0, };
- ast_mutex_lock(&args.lock);
- if (!args.monitor_enabled) {
- goto monitor_request_cleanup;
- }
- if (args.stun_sock < 0) {
- struct ast_sockaddr stun_addr;
- /* STUN socket not open. Refresh the server DNS address resolution. */
- if (!args.server_hostname) {
- /* No STUN hostname? */
- goto monitor_request_cleanup;
- }
- /* 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);
- /* 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_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) {
- RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
- RAII_VAR(struct ast_json_payload *, json_payload, NULL, ao2_cleanup);
- RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
- if (!ast_network_change_type()) {
- goto publish_failure;
- }
- /* This json_object doesn't actually contain anything yet. We have to reference something
- * for stasis, and this is useful for if we want to ever add data for any reason. */
- json_object = ast_json_object_create();
- if (!json_object) {
- goto publish_failure;
- }
- if (!(json_payload = ast_json_payload_create(json_object))) {
- goto publish_failure;
- }
- msg = stasis_message_create(ast_network_change_type(), json_payload);
- publish_failure:
- if (msg) {
- stasis_publish(ast_system_topic(), msg);
- } else {
- ast_log(LOG_ERROR, "Failed to issue network change message.\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 */
- res = args.refresh * 1000;
- ast_mutex_unlock(&args.lock);
- return res;
- }
- /*!
- * \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");
- }
- }
- /*!
- * \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)
- {
- /* if scheduler thread is not started, make sure to start it now */
- if (sched) {
- return 0; /* already started */
- }
- if (!(sched = ast_sched_context_create())) {
- ast_log(LOG_ERROR, "Failed to create stun monitor scheduler context\n");
- return -1;
- }
- if (ast_sched_start_thread(sched)) {
- ast_sched_context_destroy(sched);
- sched = NULL;
- stun_close_sock();
- return -1;
- }
- if (ast_sched_add_variable(sched, (args.refresh * 1000), stun_monitor_request, NULL, 1) < 0) {
- ast_log(LOG_ERROR, "Unable to schedule STUN network monitor \n");
- ast_sched_context_destroy(sched);
- sched = NULL;
- return -1;
- }
- ast_log(LOG_NOTICE, "STUN monitor started\n");
- 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, int reload)
- {
- 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);
- /* Only treat this as fatal if we are reloading */
- if (reload) {
- 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, };
- struct ast_config *cfg;
- struct ast_variable *v;
- if (!startup) {
- ast_set_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
- }
- 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) {
- return 0;
- }
- /* clean up any previous open socket */
- stun_close_sock();
- args.stun_poll_failed_gripe = 0;
- /* set defaults */
- args.monitor_enabled = 0;
- args.refresh = DEFAULT_MONITOR_REFRESH;
- for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
- if (!strcasecmp(v->name, "stunaddr")) {
- if (setup_stunaddr(v->value, !startup)) {
- 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_WARNING, "Invalid config option %s at line %d\n",
- v->value, v->lineno);
- }
- }
- ast_config_destroy(cfg);
- return 0;
- }
- /*! \brief Execute stun show status command */
- static void _stun_show_status(int fd)
- {
- const char *status;
- #define DATALN "%-25s %-5u %-7u %-8d %-7s %-16s %-d\n"
- #define HEADER "%-25s %-5s %-7s %-8s %-7s %-16s %-s\n"
- /*! we only have one stun server, but start to play well with more */
- ast_cli(fd, HEADER, "Hostname", "Port", "Period", "Retries", "Status", "ExternAddr", "ExternPort");
- if (args.stun_poll_failed_gripe) {
- status = "FAIL";
- } else if (args.external_addr_known) {
- status = "OK";
- } else {
- status = "INIT";
- }
- ast_cli( fd, DATALN,
- args.server_hostname,
- args.stun_port,
- args.refresh,
- DEFAULT_RETRIES,
- status,
- ast_inet_ntoa(args.external_addr.sin_addr),
- ntohs(args.external_addr.sin_port)
- );
- #undef HEADER
- #undef DATALN
- }
- static char *handle_cli_stun_show_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
- {
- switch (cmd) {
- case CLI_INIT:
- e->command = "stun show status";
- e->usage =
- "Usage: stun show status\n"
- " List all known STUN servers and statuses.\n";
- return NULL;
- case CLI_GENERATE:
- return NULL;
- }
- if (a->argc != 3) {
- return CLI_SHOWUSAGE;
- }
- _stun_show_status(a->fd);
- return CLI_SUCCESS;
- }
- static struct ast_cli_entry cli_stun[] = {
- AST_CLI_DEFINE(handle_cli_stun_show_status, "Show STUN servers and statuses"),
- };
- 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 < 0 || !args.monitor_enabled) {
- 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);
- /*! Unregister CLI commands */
- ast_cli_unregister_multiple(cli_stun, ARRAY_LEN(cli_stun));
- return 0;
- }
- static int load_module(void)
- {
- ast_mutex_init(&args.lock);
- args.stun_sock = -1;
- if (__reload(1)) {
- ast_mutex_destroy(&args.lock);
- return AST_MODULE_LOAD_DECLINE;
- }
- /*! Register CLI commands */
- ast_cli_register_multiple(cli_stun, sizeof(cli_stun) / sizeof(struct ast_cli_entry));
- return AST_MODULE_LOAD_SUCCESS;
- }
- AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "STUN Network Monitor",
- .support_level = AST_MODULE_SUPPORT_CORE,
- .load = load_module,
- .unload = unload_module,
- .reload = reload,
- .load_pri = AST_MODPRI_CHANNEL_DEPEND
- );
|