1bda4b9ff9ff657de077e1b5f29400ee4d54c1d8
[asterisk/asterisk.git] / res / res_stun_monitor.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2010, Digium, Inc.
5  *
6  * David Vossel <dvossel@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*!
20  * \file
21  * \brief STUN Network Monitor
22  *
23  * \author David Vossel <dvossel@digium.com>
24  */
25
26 /*** MODULEINFO
27         <support_level>core</support_level>
28  ***/
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include "asterisk/module.h"
35 #include "asterisk/sched.h"
36 #include "asterisk/config.h"
37 #include "asterisk/stun.h"
38 #include "asterisk/netsock2.h"
39 #include "asterisk/lock.h"
40 #include "asterisk/acl.h"
41 #include "asterisk/cli.h"
42 #include "asterisk/json.h"
43 #include "asterisk/stasis.h"
44 #include "asterisk/stasis_system.h"
45 #include "asterisk/astobj2.h"
46
47 #include <fcntl.h>
48
49 #define DEFAULT_MONITOR_REFRESH 30      /*!< Default refresh period in seconds */
50 #define DEFAULT_RETRIES 3               /*!< retries shown in stun show status
51                                              matching static retries in stun.c */
52
53 static const char stun_conf_file[] = "res_stun_monitor.conf";
54 static struct ast_sched_context *sched;
55
56 static struct {
57         /*! STUN monitor protection lock. */
58         ast_mutex_t lock;
59         /*! Current perceived external address. */
60         struct sockaddr_in external_addr;
61         /*! STUN server host name. */
62         const char *server_hostname;
63         /*! Port of STUN server to use */
64         unsigned int stun_port;
65         /*! Number of seconds between polls to the STUN server for the external address. */
66         unsigned int refresh;
67         /*! Monitoring STUN socket. */
68         int stun_sock;
69         /*! TRUE if the STUN monitor is enabled. */
70         unsigned int monitor_enabled:1;
71         /*! TRUE if the perceived external address is valid/known. */
72         unsigned int external_addr_known:1;
73         /*! TRUE if we have already griped about a STUN poll failing. */
74         unsigned int stun_poll_failed_gripe:1;
75 } args;
76
77 static void stun_close_sock(void)
78 {
79         if (0 <= args.stun_sock) {
80                 close(args.stun_sock);
81                 args.stun_sock = -1;
82         }
83 }
84
85 /* \brief called by scheduler to send STUN request */
86 static int stun_monitor_request(const void *blarg)
87 {
88         int res;
89         struct sockaddr_in answer;
90         static const struct sockaddr_in no_addr = { 0, };
91
92         ast_mutex_lock(&args.lock);
93         if (!args.monitor_enabled) {
94                 goto monitor_request_cleanup;
95         }
96
97         if (args.stun_sock < 0) {
98                 struct ast_sockaddr stun_addr;
99
100                 /* STUN socket not open.  Refresh the server DNS address resolution. */
101                 if (!args.server_hostname) {
102                         /* No STUN hostname? */
103                         goto monitor_request_cleanup;
104                 }
105
106                 /* Lookup STUN address. */
107                 memset(&stun_addr, 0, sizeof(stun_addr));
108                 stun_addr.ss.ss_family = AF_INET;
109                 if (ast_get_ip(&stun_addr, args.server_hostname)) {
110                         /* Lookup failed. */
111                         ast_log(LOG_WARNING, "Unable to lookup STUN server '%s'\n",
112                                 args.server_hostname);
113                         goto monitor_request_cleanup;
114                 }
115                 ast_sockaddr_set_port(&stun_addr, args.stun_port);
116
117                 /* open socket binding */
118                 args.stun_sock = socket(AF_INET, SOCK_DGRAM, 0);
119                 if (args.stun_sock < 0) {
120                         ast_log(LOG_WARNING, "Unable to create STUN socket: %s\n", strerror(errno));
121                         goto monitor_request_cleanup;
122                 }
123                 if (ast_connect(args.stun_sock, &stun_addr)) {
124                         ast_log(LOG_WARNING, "STUN Failed to connect to %s: %s\n",
125                                 ast_sockaddr_stringify(&stun_addr), strerror(errno));
126                         stun_close_sock();
127                         goto monitor_request_cleanup;
128                 }
129         }
130
131         res = ast_stun_request(args.stun_sock, NULL, NULL, &answer);
132         if (res) {
133                 /*
134                  * STUN request timed out or errored.
135                  *
136                  * Refresh the server DNS address resolution next time around.
137                  */
138                 if (!args.stun_poll_failed_gripe) {
139                         args.stun_poll_failed_gripe = 1;
140                         ast_log(LOG_WARNING, "STUN poll %s. Re-evaluating STUN server address.\n",
141                                 res < 0 ? "failed" : "got no response");
142                 }
143                 stun_close_sock();
144         } else {
145                 args.stun_poll_failed_gripe = 0;
146                 if (memcmp(&no_addr, &answer, sizeof(no_addr))
147                         && memcmp(&args.external_addr, &answer, sizeof(args.external_addr))) {
148                         const char *newaddr = ast_strdupa(ast_inet_ntoa(answer.sin_addr));
149                         int newport = ntohs(answer.sin_port);
150
151                         ast_log(LOG_NOTICE, "Old external address/port %s:%d now seen as %s:%d.\n",
152                                 ast_inet_ntoa(args.external_addr.sin_addr),
153                                 ntohs(args.external_addr.sin_port), newaddr, newport);
154
155                         args.external_addr = answer;
156
157                         if (args.external_addr_known) {
158                                 RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
159                                 RAII_VAR(struct ast_json_payload *, json_payload, NULL, ao2_cleanup);
160                                 RAII_VAR(struct ast_json *, json_object, ast_json_object_create(), ast_json_unref);
161
162                                 /* This json_object doesn't actually contain anything yet. We have to reference something
163                                  * for stasis, and this is useful for if we want to ever add data for any reason. */
164                                 if (!json_object) {
165                                         goto publish_failure;
166                                 }
167
168                                 if (!(json_payload = ast_json_payload_create(json_object))) {
169                                         goto publish_failure;
170                                 }
171
172                                 msg = stasis_message_create(ast_network_change_type(), json_payload);
173
174 publish_failure:
175                                 if (msg) {
176                                         stasis_publish(ast_system_topic(), msg);
177                                 } else {
178                                         ast_log(LOG_ERROR, "Failed to issue network change message.\n");
179                                 }
180                         } else {
181                                 /* this was the first external address we found, do not alert listeners
182                                  * until this address changes to something else. */
183                                 args.external_addr_known = 1;
184                         }
185                 }
186         }
187
188 monitor_request_cleanup:
189         /* always refresh this scheduler item.  It will be removed elsewhere when
190          * it is supposed to go away */
191         res = args.refresh * 1000;
192         ast_mutex_unlock(&args.lock);
193
194         return res;
195 }
196
197 /*!
198  * \internal
199  * \brief Stops the STUN monitor thread.
200  *
201  * \note do not hold the args->lock while calling this
202  *
203  * \return Nothing
204  */
205 static void stun_stop_monitor(void)
206 {
207         ast_mutex_lock(&args.lock);
208         args.monitor_enabled = 0;
209         ast_free((char *) args.server_hostname);
210         args.server_hostname = NULL;
211         stun_close_sock();
212         ast_mutex_unlock(&args.lock);
213
214         if (sched) {
215                 ast_sched_context_destroy(sched);
216                 sched = NULL;
217                 ast_log(LOG_NOTICE, "STUN monitor stopped\n");
218         }
219 }
220
221 /*!
222  * \internal
223  * \brief Starts the STUN monitor thread.
224  *
225  * \note The args->lock MUST be held when calling this function
226  *
227  * \return Nothing
228  */
229 static int stun_start_monitor(void)
230 {
231         /* if scheduler thread is not started, make sure to start it now */
232         if (sched) {
233                 return 0; /* already started */
234         }
235
236         if (!(sched = ast_sched_context_create())) {
237                 ast_log(LOG_ERROR, "Failed to create stun monitor scheduler context\n");
238                 return -1;
239         }
240
241         if (ast_sched_start_thread(sched)) {
242                 ast_sched_context_destroy(sched);
243                 sched = NULL;
244                 stun_close_sock();
245                 return -1;
246         }
247
248         if (ast_sched_add_variable(sched, (args.refresh * 1000), stun_monitor_request, NULL, 1) < 0) {
249                 ast_log(LOG_ERROR, "Unable to schedule STUN network monitor \n");
250                 ast_sched_context_destroy(sched);
251                 sched = NULL;
252                 return -1;
253         }
254
255         ast_log(LOG_NOTICE, "STUN monitor started\n");
256
257         return 0;
258 }
259
260 /*!
261  * \internal
262  * \brief Parse and setup the stunaddr parameter.
263  *
264  * \param value Configuration parameter variable value.
265  *
266  * \retval 0 on success.
267  * \retval -1 on error.
268  */
269 static int setup_stunaddr(const char *value)
270 {
271         char *val;
272         char *host_str;
273         char *port_str;
274         unsigned int port;
275         struct ast_sockaddr stun_addr;
276
277         if (ast_strlen_zero(value)) {
278                 /* Setting to an empty value disables STUN monitoring. */
279                 args.monitor_enabled = 0;
280                 return 0;
281         }
282
283         val = ast_strdupa(value);
284         if (!ast_sockaddr_split_hostport(val, &host_str, &port_str, 0)
285                 || ast_strlen_zero(host_str)) {
286                 return -1;
287         }
288
289         /* Determine STUN port */
290         if (ast_strlen_zero(port_str)
291                 || 1 != sscanf(port_str, "%30u", &port)) {
292                 port = STANDARD_STUN_PORT;
293         }
294
295         host_str = ast_strdup(host_str);
296         if (!host_str) {
297                 return -1;
298         }
299
300         /* Lookup STUN address. */
301         memset(&stun_addr, 0, sizeof(stun_addr));
302         stun_addr.ss.ss_family = AF_INET;
303         if (ast_get_ip(&stun_addr, host_str)) {
304                 ast_log(LOG_WARNING, "Unable to lookup STUN server '%s'\n", host_str);
305                 ast_free(host_str);
306                 return -1;
307         }
308
309         /* Save STUN server information. */
310         ast_free((char *) args.server_hostname);
311         args.server_hostname = host_str;
312         args.stun_port = port;
313
314         /* Enable STUN monitor */
315         args.monitor_enabled = 1;
316         return 0;
317 }
318
319 static int load_config(int startup)
320 {
321         struct ast_flags config_flags = { 0, };
322         struct ast_config *cfg;
323         struct ast_variable *v;
324
325         if (!startup) {
326                 ast_set_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
327         }
328
329         cfg = ast_config_load2(stun_conf_file, "res_stun_monitor", config_flags);
330         if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
331                 ast_log(LOG_WARNING, "Unable to load config %s\n", stun_conf_file);
332                 return -1;
333         }
334         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
335                 return 0;
336         }
337
338         /* clean up any previous open socket */
339         stun_close_sock();
340         args.stun_poll_failed_gripe = 0;
341
342         /* set defaults */
343         args.monitor_enabled = 0;
344         args.refresh = DEFAULT_MONITOR_REFRESH;
345
346         for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
347                 if (!strcasecmp(v->name, "stunaddr")) {
348                         if (setup_stunaddr(v->value)) {
349                                 ast_log(LOG_WARNING, "Invalid STUN server address: %s at line %d\n",
350                                         v->value, v->lineno);
351                         }
352                 } else if (!strcasecmp(v->name, "stunrefresh")) {
353                         if ((sscanf(v->value, "%30u", &args.refresh) != 1) || !args.refresh) {
354                                 ast_log(LOG_WARNING, "Invalid stunrefresh value '%s', must be an integer > 0 at line %d\n", v->value, v->lineno);
355                                 args.refresh = DEFAULT_MONITOR_REFRESH;
356                         }
357                 } else {
358                         ast_log(LOG_WARNING, "Invalid config option %s at line %d\n",
359                                 v->value, v->lineno);
360                 }
361         }
362
363         ast_config_destroy(cfg);
364
365         return 0;
366 }
367
368 /*! \brief Execute stun show status command */
369 static void _stun_show_status(int fd)
370 {
371         const char *status;
372
373 #define DATALN "%-25s %-5u %-7u %-8d %-7s %-16s %-d\n"
374 #define HEADER "%-25s %-5s %-7s %-8s %-7s %-16s %-s\n"
375
376         /*! we only have one stun server, but start to play well with more */
377         ast_cli(fd, HEADER, "Hostname", "Port", "Period", "Retries", "Status", "ExternAddr", "ExternPort");
378
379         if (args.stun_poll_failed_gripe) {
380                 status = "FAIL";
381         } else if (args.external_addr_known) {
382                 status = "OK";
383         } else {
384                 status = "INIT";
385         }
386         ast_cli( fd, DATALN,
387                      args.server_hostname,
388                      args.stun_port,
389                      args.refresh,
390                      DEFAULT_RETRIES,
391                      status,
392                      ast_inet_ntoa(args.external_addr.sin_addr),
393                      ntohs(args.external_addr.sin_port)
394                    );
395
396 #undef HEADER
397 #undef DATALN
398 }
399
400 static char *handle_cli_stun_show_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
401 {
402         switch (cmd) {
403         case CLI_INIT:
404                 e->command = "stun show status";
405                 e->usage =
406                     "Usage: stun show status\n"
407                     "       List all known STUN servers and statuses.\n";
408                 return NULL;
409         case CLI_GENERATE:
410                 return NULL;
411         }
412
413         if (a->argc != 3) {
414                 return CLI_SHOWUSAGE;
415         }
416
417         _stun_show_status(a->fd);
418         return CLI_SUCCESS;
419 }
420
421 static struct ast_cli_entry cli_stun[] = {
422         AST_CLI_DEFINE(handle_cli_stun_show_status, "Show STUN servers and statuses"),
423 };
424
425 static int __reload(int startup)
426 {
427         int res;
428
429         ast_mutex_lock(&args.lock);
430         if (!(res = load_config(startup)) && args.monitor_enabled) {
431                 res = stun_start_monitor();
432         }
433         ast_mutex_unlock(&args.lock);
434
435         if (res < 0 || !args.monitor_enabled) {
436                 stun_stop_monitor();
437         }
438
439         return res;
440 }
441
442 static int reload(void)
443 {
444         return __reload(0);
445 }
446
447 static int unload_module(void)
448 {
449         stun_stop_monitor();
450         ast_mutex_destroy(&args.lock);
451
452         /*! Unregister CLI commands */
453         ast_cli_unregister_multiple(cli_stun, ARRAY_LEN(cli_stun));
454
455         return 0;
456 }
457
458 static int load_module(void)
459 {
460         ast_mutex_init(&args.lock);
461         args.stun_sock = -1;
462         if (__reload(1)) {
463                 ast_mutex_destroy(&args.lock);
464                 return AST_MODULE_LOAD_DECLINE;
465         }
466
467         /*! Register CLI commands */
468         ast_cli_register_multiple(cli_stun, sizeof(cli_stun) / sizeof(struct ast_cli_entry));
469
470         return AST_MODULE_LOAD_SUCCESS;
471 }
472
473 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "STUN Network Monitor",
474                 .support_level = AST_MODULE_SUPPORT_CORE,
475                 .load = load_module,
476                 .unload = unload_module,
477                 .reload = reload,
478                 .load_pri = AST_MODPRI_CHANNEL_DEPEND
479         );