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