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