64ca73eda60bf6eed41d6bde07039e3fb0b35cb7
[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/event.h"
36 #include "asterisk/sched.h"
37 #include "asterisk/config.h"
38 #include "asterisk/stun.h"
39 #include "asterisk/netsock2.h"
40 #include "asterisk/lock.h"
41 #include <fcntl.h>
42
43 static const int DEFAULT_MONITOR_REFRESH = 30;
44
45 static const char stun_conf_file[] = "res_stun_monitor.conf";
46 static struct ast_sched_context *sched;
47
48 static struct {
49         struct sockaddr_in stunaddr;      /*!< The stun address we send requests to*/
50         struct sockaddr_in externaladdr;  /*!< current perceived external address. */
51         ast_mutex_t lock;
52         unsigned int refresh;
53         int stunsock;
54         unsigned int monitor_enabled:1;
55         unsigned int externaladdr_known:1;
56 } args;
57
58 static inline void stun_close_sock(void)
59 {
60         if (args.stunsock != -1) {
61                 close(args.stunsock);
62                 args.stunsock = -1;
63                 memset(&args.externaladdr, 0, sizeof(args.externaladdr));
64                 args.externaladdr_known = 0;
65         }
66 }
67
68 /* \brief purge the stun socket's receive buffer before issuing a new request
69  *
70  * XXX Note that this is somewhat of a hack.  This function is essentially doing
71  * a cleanup on the socket rec buffer to handle removing any STUN responses we have not
72  * handled.  This is called before sending out a new STUN request so we don't read
73  * a latent previous response thinking it is new.
74  */
75 static void stun_purge_socket(void)
76 {
77         int flags = fcntl(args.stunsock, F_GETFL);
78         int res = 0;
79         unsigned char reply_buf[1024];
80
81         fcntl(args.stunsock, F_SETFL, flags | O_NONBLOCK);
82         while (res != -1) {
83                 /* throw away everything in the buffer until we reach the end. */
84                 res = recv(args.stunsock, reply_buf, sizeof(reply_buf), 0);
85         }
86         fcntl(args.stunsock, F_SETFL, flags & ~O_NONBLOCK);
87 }
88
89 /* \brief called by scheduler to send STUN request */
90 static int stun_monitor_request(const void *blarg)
91 {
92         int res;
93         int generate_event = 0;
94         struct sockaddr_in answer = { 0, };
95
96
97         /* once the stun socket goes away, this scheduler item will go away as well */
98         ast_mutex_lock(&args.lock);
99         if (args.stunsock == -1) {
100                 ast_log(LOG_ERROR, "STUN monitor: can not send STUN request, socket is not open\n");
101                 goto monitor_request_cleanup;
102         }
103
104         stun_purge_socket();
105
106         if (!(ast_stun_request(args.stunsock, &args.stunaddr, NULL, &answer)) &&
107                 (memcmp(&args.externaladdr, &answer, sizeof(args.externaladdr)))) {
108                 const char *newaddr = ast_strdupa(ast_inet_ntoa(answer.sin_addr));
109                 int newport = ntohs(answer.sin_port);
110
111                 ast_log(LOG_NOTICE, "STUN MONITOR: Old external address/port %s:%d now seen as %s:%d \n",
112                         ast_inet_ntoa(args.externaladdr.sin_addr), ntohs(args.externaladdr.sin_port),
113                         newaddr, newport);
114
115                 memcpy(&args.externaladdr, &answer, sizeof(args.externaladdr));
116
117                 if (args.externaladdr_known) {
118                         /* the external address was already known, and has changed... generate event. */
119                         generate_event = 1;
120
121                 } else {
122                         /* this was the first external address we found, do not alert listeners
123                          * until this address changes to something else. */
124                         args.externaladdr_known = 1;
125                 }
126         }
127
128         if (generate_event) {
129                 struct ast_event *event = ast_event_new(AST_EVENT_NETWORK_CHANGE, AST_EVENT_IE_END);
130                 if (!event) {
131                         ast_log(LOG_ERROR, "STUN monitor: could not create AST_EVENT_NETWORK_CHANGE event.\n");
132                         goto monitor_request_cleanup;
133                 }
134                 if (ast_event_queue(event)) {
135                         ast_event_destroy(event);
136                         event = NULL;
137                         ast_log(LOG_ERROR, "STUN monitor: could not queue AST_EVENT_NETWORK_CHANGE event.\n");
138                         goto monitor_request_cleanup;
139                 }
140         }
141
142 monitor_request_cleanup:
143         /* always refresh this scheduler item.  It will be removed elsewhere when
144          * it is supposed to go away */
145         res = args.refresh * 1000;
146         ast_mutex_unlock(&args.lock);
147
148         return res;
149 }
150
151 /* \brief stops the stun monitor thread
152  * \note do not hold the args->lock while calling this
153  */
154 static void stun_stop_monitor(void)
155 {
156         if (sched) {
157                 ast_sched_context_destroy(sched);
158                 sched = NULL;
159                 ast_log(LOG_NOTICE, "STUN monitor stopped\n");
160         }
161         /* it is only safe to destroy the socket without holding arg->lock
162          * after the sched thread is destroyed */
163         stun_close_sock();
164 }
165
166 /* \brief starts the stun monitor thread
167  * \note The args->lock MUST be held when calling this function
168  */
169 static int stun_start_monitor(void)
170 {
171         struct ast_sockaddr dst;
172         /* clean up any previous open socket */
173         stun_close_sock();
174
175         /* create destination ast_sockaddr */
176         ast_sockaddr_from_sin(&dst, &args.stunaddr);
177
178         /* open new socket binding */
179         args.stunsock = socket(AF_INET, SOCK_DGRAM, 0);
180         if (args.stunsock < 0) {
181                 ast_log(LOG_WARNING, "Unable to create STUN socket: %s\n", strerror(errno));
182                 return -1;
183         }
184
185         if (ast_connect(args.stunsock, &dst) != 0) {
186                 ast_log(LOG_WARNING, "SIP STUN Failed to connect to %s\n", ast_sockaddr_stringify(&dst));
187                 stun_close_sock();
188                 return -1;
189         }
190
191         /* if scheduler thread is not started, make sure to start it now */
192         if (sched) {
193                 return 0; /* already started */
194         }
195
196         if (!(sched = ast_sched_context_create())) {
197                 ast_log(LOG_ERROR, "Failed to create stun monitor scheduler context\n");
198                 stun_close_sock();
199                 return -1;
200         }
201
202         if (ast_sched_start_thread(sched)) {
203                 ast_sched_context_destroy(sched);
204                 sched = NULL;
205                 stun_close_sock();
206                 return -1;
207         }
208
209         if (ast_sched_add_variable(sched, (args.refresh * 1000), stun_monitor_request, NULL, 1) < 0) {
210                 ast_log(LOG_ERROR, "Unable to schedule STUN network monitor \n");
211                 ast_sched_context_destroy(sched);
212                 sched = NULL;
213                 stun_close_sock();
214                 return -1;
215         }
216
217         ast_log(LOG_NOTICE, "STUN monitor started\n");
218
219         return 0;
220 }
221
222 static int load_config(int startup)
223 {
224         struct ast_flags config_flags = { 0, };
225         struct ast_config *cfg;
226         struct ast_variable *v;
227
228         if (!startup) {
229                 ast_set_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
230         }
231
232         if (!(cfg = ast_config_load2(stun_conf_file, "res_stun_monitor", config_flags)) ||
233                 cfg == CONFIG_STATUS_FILEINVALID) {
234                 ast_log(LOG_WARNING, "Unable to load config %s\n", stun_conf_file);
235                 return -1;
236         }
237
238         if (cfg == CONFIG_STATUS_FILEUNCHANGED && !startup) {
239                 return 0;
240         }
241
242         /* set defaults */
243         args.monitor_enabled = 0;
244         memset(&args.stunaddr, 0, sizeof(args.stunaddr));
245         args.refresh = DEFAULT_MONITOR_REFRESH;
246
247         for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
248                 if (!strcasecmp(v->name, "stunaddr")) {
249                         args.stunaddr.sin_port = htons(STANDARD_STUN_PORT);
250                         if (ast_parse_arg(v->value, PARSE_INADDR, &args.stunaddr)) {
251                                 ast_log(LOG_WARNING, "Invalid STUN server address: %s\n", v->value);
252                         } else {
253                                 ast_log(LOG_NOTICE, "STUN monitor enabled: %s\n", v->value);
254                                 args.monitor_enabled = 1;
255                         }
256                 } else if (!strcasecmp(v->name, "stunrefresh")) {
257                         if ((sscanf(v->value, "%30u", &args.refresh) != 1) || !args.refresh) {
258                                 ast_log(LOG_WARNING, "Invalid stunrefresh value '%s', must be an integer > 0 at line %d\n", v->value, v->lineno);
259                                 args.refresh = DEFAULT_MONITOR_REFRESH;
260                         } else {
261                                 ast_log(LOG_NOTICE, "STUN Monitor set to refresh every %d seconds\n", args.refresh);
262                         }
263                 } else {
264                         ast_log(LOG_WARNING, "SIP STUN: invalid config option %s at line %d\n", v->value, v->lineno);
265                 }
266         }
267
268         ast_config_destroy(cfg);
269
270         return 0;
271 }
272
273 static int __reload(int startup)
274 {
275         int res;
276
277         ast_mutex_lock(&args.lock);
278         if (!(res = load_config(startup)) && args.monitor_enabled) {
279                 res = stun_start_monitor();
280         }
281         ast_mutex_unlock(&args.lock);
282
283         if ((res == -1) || !args.monitor_enabled) {
284                 args.monitor_enabled = 0;
285                 stun_stop_monitor();
286         }
287
288         return res;
289 }
290
291 static int reload(void)
292 {
293         return __reload(0);
294 }
295
296 static int unload_module(void)
297 {
298         stun_stop_monitor();
299         ast_mutex_destroy(&args.lock);
300         return 0;
301 }
302
303 static int load_module(void)
304 {
305         ast_mutex_init(&args.lock);
306         args.stunsock = -1;
307         memset(&args.externaladdr, 0, sizeof(args.externaladdr));
308         args.externaladdr_known = 0;
309         sched = NULL;
310         if (__reload(1)) {
311                 stun_stop_monitor();
312                 ast_mutex_destroy(&args.lock);
313                 return AST_MODULE_LOAD_DECLINE;
314         }
315
316         return AST_MODULE_LOAD_SUCCESS;
317 }
318
319 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "STUN Network Monitor",
320                 .load = load_module,
321                 .unload = unload_module,
322                 .reload = reload,
323                 .load_pri = AST_MODPRI_CHANNEL_DEPEND
324         );