1 /*
2 linphone
3 Copyright (C) 2012  Belledonne Communications SARL
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 */
19 
20 #include "upnp.h"
21 #include "private.h"
22 #include "linphone/lpconfig.h"
23 #include <ctype.h>
24 
25 #define UPNP_STRINGIFY(x)     #x
26 #define UPNP_TOSTRING(x)      UPNP_STRINGIFY(x)
27 
28 #define UPNP_ADD_MAX_RETRY    4
29 #define UPNP_REMOVE_MAX_RETRY 4
30 #define UPNP_SECTION_NAME     "uPnP"
31 #define UPNP_CORE_READY_CHECK 1
32 #define UPNP_CORE_RETRY_DELAY 10
33 #define UPNP_CALL_RETRY_DELAY 3
34 #define UPNP_UUID_LEN         128
35 #define UPNP_UUID_LEN_STR     UPNP_TOSTRING(UPNP_UUID_LEN)
36 /*
37  * uPnP Definitions
38  */
39 
40 typedef struct _UpnpPortBinding {
41 	ms_mutex_t mutex;
42 	LinphoneUpnpState state;
43 	upnp_igd_ip_protocol protocol;
44 	char *device_id;
45 	char local_addr[LINPHONE_IPADDR_SIZE];
46 	int local_port;
47 	char external_addr[LINPHONE_IPADDR_SIZE];
48 	int external_port;
49 	int retry;
50 	int ref;
51 	bool_t to_remove;
52 	bool_t to_add;
53 	time_t last_update;
54 } UpnpPortBinding;
55 
56 typedef struct _UpnpStream {
57 	UpnpPortBinding *rtp;
58 	UpnpPortBinding *rtcp;
59 	LinphoneUpnpState state;
60 } UpnpStream;
61 
62 struct _UpnpSession {
63 	LinphoneCall *call;
64 	UpnpStream *audio;
65 	UpnpStream *video;
66 	LinphoneUpnpState state;
67 };
68 
69 struct _UpnpContext {
70 	LinphoneCore *lc;
71 	upnp_igd_context *upnp_igd_ctxt;
72 	UpnpPortBinding *sip_tcp;
73 	UpnpPortBinding *sip_tls;
74 	UpnpPortBinding *sip_udp;
75 	LinphoneUpnpState state;
76 	bctbx_list_t *removing_configs;
77 	bctbx_list_t *adding_configs;
78 	bctbx_list_t *pending_bindings;
79 
80 	ms_mutex_t mutex;
81 	ms_cond_t empty_cond;
82 
83 	time_t last_ready_check;
84 	LinphoneUpnpState last_ready_state;
85 };
86 
87 
88 bool_t linphone_core_upnp_hook(void *data);
89 void linphone_upnp_update(UpnpContext *ctx);
90 bool_t linphone_upnp_is_blacklisted(UpnpContext *ctx);
91 
92 UpnpPortBinding *linphone_upnp_port_binding_new(void);
93 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port);
94 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(bctbx_list_t *list, upnp_igd_ip_protocol protocol, int local_port, int external_port);
95 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
96 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char * device_id);
97 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
98 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(bctbx_list_t *list, const UpnpPortBinding *port);
99 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
100 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay);
101 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
102 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
103 void linphone_upnp_update_config(UpnpContext *lupnp);
104 void linphone_upnp_update_proxy(UpnpContext *lupnp, bool_t force);
105 
106 // Configuration
107 bctbx_list_t *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id);
108 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
109 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
110 
111 // uPnP
112 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
113 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry);
114 
linphone_upnp_strncmpi(const char * str1,const char * str2,int len)115 static int linphone_upnp_strncmpi(const char *str1, const char *str2, int len) {
116 	int i = 0;
117 	char char1, char2;
118 	while(i < len) {
119 		char1 = toupper(*str1);
120 		char2 = toupper(*str2);
121 		if(char1 == '\0' || char1 != char2) {
122 			return char1 - char2;
123 		}
124 		str1++;
125 		str2++;
126 		i++;
127 	}
128 	return 0;
129 }
130 
linphone_upnp_str_min(const char * str1,const char * str2)131 static int linphone_upnp_str_min(const char *str1, const char *str2) {
132 	int len1 = strlen(str1);
133 	int len2 = strlen(str2);
134 	if(len1 > len2) {
135 		return len2;
136 	}
137 	return len1;
138 }
139 
linphone_upnp_format_device_id(const char * device_id)140 char * linphone_upnp_format_device_id(const char *device_id) {
141 	char *ret = NULL;
142 	char *tmp;
143 	char tchar;
144 	bool_t copy;
145 	if(device_id == NULL) {
146 		return ret;
147 	}
148 	ret = ms_new0(char, UPNP_UUID_LEN + 1);
149 	tmp = ret;
150 	if(linphone_upnp_strncmpi(device_id, "uuid:", linphone_upnp_str_min(device_id, "uuid:")) == 0) {
151 		device_id += strlen("uuid:");
152 	}
153 	while(*device_id != '\0' && tmp - ret < UPNP_UUID_LEN) {
154 		copy = FALSE;
155 		tchar = *device_id;
156 		if(tchar >= '0' && tchar <= '9')
157 			copy = TRUE;
158 		if(!copy && tchar >= 'A' && tchar <= 'Z')
159 			copy = TRUE;
160 		if(!copy && tchar >= 'a' && tchar <= 'z')
161 			copy = TRUE;
162 		if(copy) {
163 			*tmp = *device_id;
164 			tmp++;
165 		}
166 		device_id++;
167 	}
168 	*tmp = '\0';
169 	return ret;
170 }
171 
172 /**
173  * uPnP Callbacks
174  */
175 
176 /* Convert uPnP IGD logs to ortp logs */
linphone_upnp_igd_print(void * cookie,upnp_igd_print_level level,const char * fmt,va_list list)177 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
178 	int ortp_level = ORTP_DEBUG;
179 	switch(level) {
180 	case UPNP_IGD_MESSAGE:
181 		ortp_level = ORTP_MESSAGE;
182 		break;
183 	case UPNP_IGD_WARNING:
184 		ortp_level = ORTP_DEBUG; // Too verbose otherwise
185 		break;
186 	case UPNP_IGD_ERROR:
187 		ortp_level = ORTP_DEBUG; // Too verbose otherwise
188 		break;
189 	default:
190 		break;
191 	}
192 	ortp_logv(ORTP_LOG_DOMAIN, ortp_level, fmt, list);
193 }
194 
linphone_upnp_igd_callback(void * cookie,upnp_igd_event event,void * arg)195 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
196 	UpnpContext *lupnp = (UpnpContext *)cookie;
197 	upnp_igd_port_mapping *mapping = NULL;
198 	UpnpPortBinding *port_mapping = NULL;
199 	const char *ip_address = NULL;
200 	const char *connection_status = NULL;
201 	bool_t nat_enabled = FALSE;
202 	bool_t blacklisted = FALSE;
203 	LinphoneUpnpState old_state;
204 
205 	if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
206 		ms_error("uPnP IGD: Invalid context in callback");
207 		return;
208 	}
209 
210 	ms_mutex_lock(&lupnp->mutex);
211 	old_state = lupnp->state;
212 
213 	switch(event) {
214 	case UPNP_IGD_DEVICE_ADDED:
215 	case UPNP_IGD_DEVICE_REMOVED:
216 	case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
217 	case UPNP_IGD_NAT_ENABLED_CHANGED:
218 	case UPNP_IGD_CONNECTION_STATUS_CHANGED:
219 		ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
220 		connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
221 		nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
222 		blacklisted = linphone_upnp_is_blacklisted(lupnp);
223 
224 		if(ip_address == NULL || connection_status == NULL) {
225 			ms_message("uPnP IGD: Pending");
226 			lupnp->state = LinphoneUpnpStatePending;
227 		} else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
228 			ms_message("uPnP IGD: Not Available");
229 			lupnp->state = LinphoneUpnpStateNotAvailable;
230 		} else if(blacklisted) {
231 			ms_message("uPnP IGD: Router is blacklisted");
232 			lupnp->state = LinphoneUpnpStateBlacklisted;
233 		} else {
234 			ms_message("uPnP IGD: Connected");
235 			lupnp->state = LinphoneUpnpStateOk;
236 			if(old_state != LinphoneUpnpStateOk) {
237 				linphone_upnp_update(lupnp);
238 			}
239 		}
240 
241 		break;
242 
243 	case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
244 		mapping = (upnp_igd_port_mapping *) arg;
245 		port_mapping = (UpnpPortBinding*) mapping->cookie;
246 		port_mapping->external_port = mapping->remote_port;
247 		port_mapping->state = LinphoneUpnpStateOk;
248 		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
249 		linphone_upnp_config_add_port_binding(lupnp, port_mapping);
250 
251 		break;
252 
253 	case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
254 		mapping = (upnp_igd_port_mapping *) arg;
255 		port_mapping = (UpnpPortBinding*) mapping->cookie;
256 		port_mapping->external_port = -1; //Force random external port
257 		if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE) != 0) {
258 			linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
259 		}
260 
261 		break;
262 
263 	case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
264 		mapping = (upnp_igd_port_mapping *) arg;
265 		port_mapping = (UpnpPortBinding*) mapping->cookie;
266 		port_mapping->state = LinphoneUpnpStateIdle;
267 		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
268 		linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
269 
270 		break;
271 
272 	case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
273 		mapping = (upnp_igd_port_mapping *) arg;
274 		port_mapping = (UpnpPortBinding*) mapping->cookie;
275 		if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE) != 0) {
276 			linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
277 			linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
278 		}
279 
280 		break;
281 
282 	default:
283 		break;
284 	}
285 
286 	if(port_mapping != NULL) {
287 		/*
288 		 * Execute delayed actions
289 		 */
290 		if(port_mapping->to_remove) {
291 			if(port_mapping->state == LinphoneUpnpStateOk) {
292 				port_mapping->to_remove = FALSE;
293 				linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, FALSE);
294 			} else if(port_mapping->state == LinphoneUpnpStateKo) {
295 				port_mapping->to_remove = FALSE;
296 			}
297 		}
298 		if(port_mapping->to_add) {
299 			if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
300 				port_mapping->to_add = FALSE;
301 				linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, FALSE);
302 			}
303 		}
304 
305 		lupnp->pending_bindings = bctbx_list_remove(lupnp->pending_bindings, port_mapping);
306 		linphone_upnp_port_binding_release(port_mapping);
307 	}
308 
309 	/*
310 	 * If there is no pending binding emit a signal
311 	 */
312 	if(lupnp->pending_bindings == NULL) {
313 		ms_cond_signal(&lupnp->empty_cond);
314 	}
315 	ms_mutex_unlock(&lupnp->mutex);
316 }
317 
318 
319 /**
320  * uPnP Context
321  */
322 
linphone_upnp_context_new(LinphoneCore * lc)323 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
324 	UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
325 	char address[LINPHONE_IPADDR_SIZE];
326 	const char*upnp_binding_address=address;
327 	if (linphone_core_get_local_ip_for(lc->sip_conf.ipv6_enabled ? AF_INET6 : AF_INET,NULL,address)) {
328 		ms_warning("Linphone core [%p] cannot guess local address for upnp, let's choice the lib",lc);
329 		upnp_binding_address=NULL;
330 	}
331 	ms_mutex_init(&lupnp->mutex, NULL);
332 	ms_cond_init(&lupnp->empty_cond, NULL);
333 
334 	lupnp->last_ready_check = 0;
335 	lupnp->last_ready_state = LinphoneUpnpStateIdle;
336 
337 	lupnp->lc = lc;
338 	lupnp->pending_bindings = NULL;
339 	lupnp->adding_configs = NULL;
340 	lupnp->removing_configs = NULL;
341 	lupnp->state = LinphoneUpnpStateIdle;
342 	ms_message("uPnP IGD: New %p for core %p bound to %s", lupnp, lc,upnp_binding_address);
343 
344 	// Init ports
345 	lupnp->sip_udp = NULL;
346 	lupnp->sip_tcp = NULL;
347 	lupnp->sip_tls = NULL;
348 
349 	linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
350 
351 	lupnp->upnp_igd_ctxt = NULL;
352 	lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, address, lupnp);
353 	if(lupnp->upnp_igd_ctxt == NULL) {
354 		lupnp->state = LinphoneUpnpStateKo;
355 		ms_error("Can't create uPnP IGD context");
356 		return NULL;
357 	}
358 
359 	lupnp->state = LinphoneUpnpStatePending;
360 	upnp_igd_start(lupnp->upnp_igd_ctxt);
361 
362 	return lupnp;
363 }
364 
linphone_upnp_context_destroy(UpnpContext * lupnp)365 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
366 	linphone_core_remove_iterate_hook(lupnp->lc, linphone_core_upnp_hook, lupnp);
367 
368 	ms_mutex_lock(&lupnp->mutex);
369 
370 	if(lupnp->lc->sip_network_reachable) {
371 		/* Send port binding removes */
372 		if(lupnp->sip_udp != NULL) {
373 			linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp, TRUE);
374 		}
375 		if(lupnp->sip_tcp != NULL) {
376 			linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp, TRUE);
377 		}
378 		if(lupnp->sip_tls != NULL) {
379 			linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls, TRUE);
380 		}
381 	}
382 
383 	/* Wait all pending bindings are done */
384 	if(lupnp->pending_bindings != NULL) {
385 		ms_message("uPnP IGD: Wait all pending port bindings ...");
386 		ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
387 	}
388 	ms_mutex_unlock(&lupnp->mutex);
389 
390 	if(lupnp->upnp_igd_ctxt != NULL) {
391 		upnp_igd_destroy(lupnp->upnp_igd_ctxt);
392 		lupnp->upnp_igd_ctxt = NULL;
393 	}
394 
395 	/* No more multi threading here */
396 
397 	/* Run one more time configuration update and proxy */
398 	linphone_upnp_update_config(lupnp);
399 	linphone_upnp_update_proxy(lupnp, TRUE);
400 
401 	/* Release port bindings */
402 	if(lupnp->sip_udp != NULL) {
403 		linphone_upnp_port_binding_release(lupnp->sip_udp);
404 		lupnp->sip_udp = NULL;
405 	}
406 	if(lupnp->sip_tcp != NULL) {
407 		linphone_upnp_port_binding_release(lupnp->sip_tcp);
408 		lupnp->sip_tcp = NULL;
409 	}
410 	if(lupnp->sip_tls != NULL) {
411 		linphone_upnp_port_binding_release(lupnp->sip_tls);
412 		lupnp->sip_tcp = NULL;
413 	}
414 
415 	/* Release lists */
416 	bctbx_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
417 	lupnp->adding_configs = bctbx_list_free(lupnp->adding_configs);
418 	bctbx_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
419 	lupnp->removing_configs = bctbx_list_free(lupnp->removing_configs);
420 	bctbx_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
421 	lupnp->pending_bindings = bctbx_list_free(lupnp->pending_bindings);
422 
423 	ms_mutex_destroy(&lupnp->mutex);
424 	ms_cond_destroy(&lupnp->empty_cond);
425 
426 	ms_message("uPnP IGD: destroy %p", lupnp);
427 	ms_free(lupnp);
428 }
429 
linphone_upnp_context_get_state(UpnpContext * lupnp)430 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *lupnp) {
431 	LinphoneUpnpState state = LinphoneUpnpStateKo;
432 	if(lupnp != NULL) {
433 		ms_mutex_lock(&lupnp->mutex);
434 		state = lupnp->state;
435 		ms_mutex_unlock(&lupnp->mutex);
436 	}
437 	return state;
438 }
439 
_linphone_upnp_context_is_ready_for_register(UpnpContext * lupnp)440 bool_t _linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
441 	bool_t ready = TRUE;
442 
443 	// 1 Check global uPnP state
444 	ready = (lupnp->state == LinphoneUpnpStateOk);
445 
446 	// 2 Check external ip address
447 	if(ready) {
448 		if (upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt) == NULL) {
449 			ready = FALSE;
450 		}
451 	}
452 
453 	// 3 Check sip ports bindings
454 	if(ready) {
455 		if(lupnp->sip_udp != NULL) {
456 			if(lupnp->sip_udp->state != LinphoneUpnpStateOk) {
457 				ready = FALSE;
458 			}
459 		} else if(lupnp->sip_tcp != NULL) {
460 			if(lupnp->sip_tcp->state != LinphoneUpnpStateOk) {
461 				ready = FALSE;
462 			}
463 		} else if(lupnp->sip_tls != NULL) {
464 			if(lupnp->sip_tls->state != LinphoneUpnpStateOk) {
465 				ready = FALSE;
466 			}
467 		} else {
468 			ready = FALSE;
469 		}
470 	}
471 
472 	return ready;
473 }
474 
linphone_upnp_context_is_ready_for_register(UpnpContext * lupnp)475 bool_t linphone_upnp_context_is_ready_for_register(UpnpContext *lupnp) {
476 	bool_t ready = FALSE;
477 	if(lupnp != NULL) {
478 		ms_mutex_lock(&lupnp->mutex);
479 		ready = _linphone_upnp_context_is_ready_for_register(lupnp);
480 		ms_mutex_unlock(&lupnp->mutex);
481 	}
482 	return ready;
483 }
484 
linphone_upnp_context_get_external_port(UpnpContext * lupnp)485 int linphone_upnp_context_get_external_port(UpnpContext *lupnp) {
486 	int port = -1;
487 	if(lupnp != NULL) {
488 		ms_mutex_lock(&lupnp->mutex);
489 
490 		if(lupnp->sip_udp != NULL) {
491 			if(lupnp->sip_udp->state == LinphoneUpnpStateOk) {
492 				port = lupnp->sip_udp->external_port;
493 			}
494 		} else if(lupnp->sip_tcp != NULL) {
495 			if(lupnp->sip_tcp->state == LinphoneUpnpStateOk) {
496 				port = lupnp->sip_tcp->external_port;
497 			}
498 		} else if(lupnp->sip_tls != NULL) {
499 			if(lupnp->sip_tls->state == LinphoneUpnpStateOk) {
500 				port = lupnp->sip_tls->external_port;
501 			}
502 		}
503 
504 		ms_mutex_unlock(&lupnp->mutex);
505 	}
506 	return port;
507 }
508 
linphone_upnp_is_blacklisted(UpnpContext * lupnp)509 bool_t linphone_upnp_is_blacklisted(UpnpContext *lupnp) {
510 	const char * device_model_name = upnp_igd_get_device_model_name(lupnp->upnp_igd_ctxt);
511 	const char * device_model_number = upnp_igd_get_device_model_number(lupnp->upnp_igd_ctxt);
512 	const char * blacklist = lp_config_get_string(lupnp->lc->config, "net", "upnp_blacklist", NULL);
513 	bool_t blacklisted = FALSE;
514 	char *str;
515 	char *pch;
516 	char *model_name;
517 	char *model_number;
518 
519 	// Sanity checks
520 	if(device_model_name == NULL || device_model_number == NULL || blacklist == NULL) {
521 		return FALSE;
522 	}
523 
524 	// Find in the list
525 	str = strdup(blacklist);
526 	pch = strtok(str, ";");
527 	while (pch != NULL && !blacklisted) {
528 		// Extract model name & number
529 		model_name = pch;
530 		model_number = strstr(pch, ",");
531 		if(model_number != NULL) {
532 			*(model_number++) = '\0';
533 		}
534 
535 		// Compare with current device
536 		if(strcmp(model_name, device_model_name) == 0) {
537 			if(model_number == NULL || strcmp(model_number, device_model_number) == 0) {
538 				blacklisted = TRUE;
539 			}
540 		}
541 		pch = strtok(NULL, ";");
542 	}
543 	free(str);
544 
545 	return blacklisted;
546 }
547 
linphone_upnp_refresh(UpnpContext * lupnp)548 void linphone_upnp_refresh(UpnpContext * lupnp) {
549 	upnp_igd_refresh(lupnp->upnp_igd_ctxt);
550 }
551 
linphone_upnp_context_get_external_ipaddress(UpnpContext * lupnp)552 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *lupnp) {
553 	const char* addr = NULL;
554 	if(lupnp != NULL) {
555 		ms_mutex_lock(&lupnp->mutex);
556 		addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
557 		ms_mutex_unlock(&lupnp->mutex);
558 	}
559 	return addr;
560 }
561 
linphone_upnp_context_send_add_port_binding(UpnpContext * lupnp,UpnpPortBinding * port,bool_t retry)562 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
563 	upnp_igd_port_mapping mapping;
564 	char description[128];
565 	int ret;
566 
567 	if(lupnp->state != LinphoneUpnpStateOk) {
568 		return -2;
569 	}
570 
571 	// Compute port binding state
572 	if(port->state != LinphoneUpnpStateAdding) {
573 		port->to_remove = FALSE;
574 		switch(port->state) {
575 			case LinphoneUpnpStateKo:
576 			case LinphoneUpnpStateIdle: {
577 				port->retry = 0;
578 				port->state = LinphoneUpnpStateAdding;
579 			}
580 			break;
581 			case LinphoneUpnpStateRemoving: {
582 				port->to_add = TRUE;
583 				return 0;
584 			}
585 			break;
586 			default:
587 				return 0;
588 		}
589 	}
590 
591 	// No retry if specified
592 	if(port->retry != 0 && !retry) {
593 		return -1;
594 	}
595 
596 	if(port->retry >= UPNP_ADD_MAX_RETRY) {
597 		ret = -1;
598 	} else {
599 		linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
600 		mapping.cookie = linphone_upnp_port_binding_retain(port);
601 		lupnp->pending_bindings = bctbx_list_append(lupnp->pending_bindings, mapping.cookie);
602 
603 		mapping.local_port = port->local_port;
604 		mapping.local_host = port->local_addr;
605 		if(port->external_port == -1)
606 			port->external_port = rand()%(0xffff - 1024) + 1024;
607 		mapping.remote_port = port->external_port;
608 		mapping.remote_host = "";
609 		snprintf(description, 128, "%s %s at %s:%d",
610 				"Linphone",
611 				(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
612 				port->local_addr, port->local_port);
613 		mapping.description = description;
614 		mapping.protocol = port->protocol;
615 
616 		port->retry++;
617 		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
618 		ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
619 	}
620 	if(ret != 0) {
621 		port->state = LinphoneUpnpStateKo;
622 	}
623 	return ret;
624 }
625 
linphone_upnp_context_send_remove_port_binding(UpnpContext * lupnp,UpnpPortBinding * port,bool_t retry)626 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port, bool_t retry) {
627 	upnp_igd_port_mapping mapping;
628 	int ret;
629 
630 	if(lupnp->state != LinphoneUpnpStateOk) {
631 		return -2;
632 	}
633 
634 	// Compute port binding state
635 	if(port->state != LinphoneUpnpStateRemoving) {
636 		port->to_add = FALSE;
637 		switch(port->state) {
638 			case LinphoneUpnpStateOk: {
639 				port->retry = 0;
640 				port->state = LinphoneUpnpStateRemoving;
641 			}
642 			break;
643 			case LinphoneUpnpStateAdding: {
644 				port->to_remove = TRUE;
645 				return 0;
646 			}
647 			break;
648 			default:
649 				return 0;
650 		}
651 	}
652 
653 	// No retry if specified
654 	if(port->retry != 0 && !retry) {
655 		return 1;
656 	}
657 
658 	if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
659 		ret = -1;
660 	} else {
661 		linphone_upnp_port_binding_set_device_id(port, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
662 		mapping.cookie = linphone_upnp_port_binding_retain(port);
663 		lupnp->pending_bindings = bctbx_list_append(lupnp->pending_bindings, mapping.cookie);
664 
665 		mapping.remote_port = port->external_port;
666 		mapping.remote_host = "";
667 		mapping.protocol = port->protocol;
668 		port->retry++;
669 		linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
670 		ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
671 	}
672 	if(ret != 0) {
673 		port->state = LinphoneUpnpStateKo;
674 	}
675 	return ret;
676 }
677 
678 /*
679  * uPnP Core interfaces
680  */
681 
linphone_call_update_upnp_audio_video(LinphoneCall * call,bool_t audio,bool_t video)682 int linphone_call_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
683 	LinphoneCore *lc = call->core;
684 	UpnpContext *lupnp = lc->upnp;
685 	int ret = -1;
686 
687 	if(lupnp == NULL) {
688 		return ret;
689 	}
690 
691 	ms_mutex_lock(&lupnp->mutex);
692 
693 	// Don't handle when the call
694 	if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
695 		ret = 0;
696 
697 		/*
698 		 * Audio part
699 		 */
700 		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtp,
701 			UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->media_ports[call->main_audio_stream_index].rtp_port:0, UPNP_CALL_RETRY_DELAY);
702 
703 		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->audio->rtcp,
704 			UPNP_IGD_IP_PROTOCOL_UDP, (audio)? call->media_ports[call->main_audio_stream_index].rtcp_port:0, UPNP_CALL_RETRY_DELAY);
705 
706 		/*
707 		 * Video part
708 		 */
709 		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtp,
710 			UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->media_ports[call->main_video_stream_index].rtp_port:0, UPNP_CALL_RETRY_DELAY);
711 
712 		linphone_upnp_update_port_binding(lupnp, &call->upnp_session->video->rtcp,
713 			UPNP_IGD_IP_PROTOCOL_UDP, (video)? call->media_ports[call->main_video_stream_index].rtcp_port:0, UPNP_CALL_RETRY_DELAY);
714 	}
715 
716 	ms_mutex_unlock(&lupnp->mutex);
717 
718 	/*
719 	 * Update uPnP call state
720 	 */
721 	linphone_upnp_call_process(call);
722 
723 	return ret;
724 }
725 
726 
727 
linphone_call_update_upnp_from_remote_media_description(LinphoneCall * call,const SalMediaDescription * md)728 int linphone_call_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
729 	bool_t audio = FALSE;
730 	bool_t video = FALSE;
731 	int i;
732 	const SalStreamDescription *stream;
733 
734 	for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) {
735 		stream = &md->streams[i];
736 		if (!sal_stream_description_active(stream)) continue;
737 		if(stream->type == SalAudio) {
738 			audio = TRUE;
739 		} else if(stream->type == SalVideo) {
740 			video = TRUE;
741 		}
742 	}
743 
744 	return linphone_call_update_upnp_audio_video(call, audio, video);
745 }
746 
linphone_call_update_upnp(LinphoneCall * call)747 int linphone_call_update_upnp(LinphoneCall *call) {
748 	return linphone_call_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
749 }
750 
linphone_call_update_upnp_state_in_call_stats(LinphoneCall * call)751 void linphone_call_update_upnp_state_in_call_stats(LinphoneCall *call) {
752 	call->audio_stats->upnp_state = call->upnp_session->audio->state;
753 	call->video_stats->upnp_state = call->upnp_session->video->state;
754 }
755 
linphone_upnp_update_stream_state(UpnpStream * stream)756 void linphone_upnp_update_stream_state(UpnpStream *stream) {
757 	if((stream->rtp == NULL || stream->rtp->state == LinphoneUpnpStateOk || stream->rtp->state == LinphoneUpnpStateIdle) &&
758 	   (stream->rtcp == NULL || stream->rtcp->state == LinphoneUpnpStateOk || stream->rtcp->state == LinphoneUpnpStateIdle)) {
759 		stream->state = LinphoneUpnpStateOk;
760 	} else if((stream->rtp != NULL &&
761 	   (stream->rtp->state == LinphoneUpnpStateAdding || stream->rtp->state == LinphoneUpnpStateRemoving)) ||
762 		  (stream->rtcp != NULL &&
763 			 (stream->rtcp->state == LinphoneUpnpStateAdding || stream->rtcp->state == LinphoneUpnpStateRemoving))) {
764 		stream->state = LinphoneUpnpStatePending;
765 	} else if((stream->rtp != NULL && stream->rtp->state == LinphoneUpnpStateKo) ||
766 			(stream->rtcp != NULL && stream->rtcp->state == LinphoneUpnpStateKo)) {
767 		stream->state = LinphoneUpnpStateKo;
768 	} else {
769 		ms_error("Invalid stream %p state", stream);
770 	}
771 }
772 
linphone_upnp_call_process(LinphoneCall * call)773 int linphone_upnp_call_process(LinphoneCall *call) {
774 	LinphoneCore *lc = call->core;
775 	UpnpContext *lupnp = lc->upnp;
776 	int ret = -1;
777 	LinphoneUpnpState oldState = 0, newState = 0;
778 
779 	if(lupnp == NULL) {
780 		return ret;
781 	}
782 
783 	ms_mutex_lock(&lupnp->mutex);
784 
785 	// Don't handle when the call
786 	if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
787 		ret = 0;
788 
789 		/*
790 		 * Update Audio state
791 		 */
792 		linphone_upnp_update_stream_state(call->upnp_session->audio);
793 
794 		/*
795 		 * Update Video state
796 		 */
797 		linphone_upnp_update_stream_state(call->upnp_session->video);
798 
799 		/*
800 		 * Update stat
801 		 */
802 		linphone_call_update_upnp_state_in_call_stats(call);
803 
804 		/*
805 		 * Update session state
806 		 */
807 		oldState = call->upnp_session->state;
808 		if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
809 			call->upnp_session->video->state == LinphoneUpnpStateOk) {
810 			call->upnp_session->state = LinphoneUpnpStateOk;
811 		} else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
812 				call->upnp_session->video->state == LinphoneUpnpStatePending) {
813 			call->upnp_session->state = LinphoneUpnpStatePending;
814 		} else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
815 				call->upnp_session->video->state == LinphoneUpnpStateKo) {
816 			call->upnp_session->state = LinphoneUpnpStateKo;
817 		} else {
818 			call->upnp_session->state = LinphoneUpnpStateIdle;
819 		}
820 		newState = call->upnp_session->state;
821 	}
822 
823 	ms_mutex_unlock(&lupnp->mutex);
824 
825 	/* When change is done proceed update */
826 	if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
827 			(newState == LinphoneUpnpStateOk || newState == LinphoneUpnpStateKo)) {
828 		if(call->upnp_session->state == LinphoneUpnpStateOk)
829 			ms_message("uPnP IGD: uPnP for Call %p is ok", call);
830 		else
831 			ms_message("uPnP IGD: uPnP for Call %p is ko", call);
832 
833 		switch (call->state) {
834 			case LinphoneCallUpdating:
835 				linphone_call_start_update(call);
836 				break;
837 			case LinphoneCallUpdatedByRemote:
838 				linphone_call_start_accept_update(call, call->prevstate, linphone_call_state_to_string(call->prevstate));
839 				break;
840 			case LinphoneCallOutgoingInit:
841 				linphone_call_proceed_with_invite_if_ready(call, NULL);
842 				break;
843 			case LinphoneCallIdle:
844 				linphone_call_update_local_media_description_from_ice_or_upnp(call);
845 				sal_call_set_local_media_description(call->op,call->localdesc);
846 				linphone_core_notify_incoming_call(lc, call);
847 				break;
848 			default:
849 				break;
850 		}
851 	}
852 
853 	return ret;
854 }
855 
linphone_core_upnp_get_charptr_null(const char * str)856 static const char *linphone_core_upnp_get_charptr_null(const char *str) {
857 	if(str != NULL) {
858 		return str;
859 	}
860 	return "(Null)";
861 }
862 
linphone_upnp_update(UpnpContext * lupnp)863 void linphone_upnp_update(UpnpContext *lupnp) {
864 	bctbx_list_t *global_list = NULL;
865 	bctbx_list_t *list = NULL;
866 	bctbx_list_t *item;
867 	LinphoneCall *call;
868 	UpnpPortBinding *port_mapping, *port_mapping2;
869 
870 	ms_message("uPnP IGD: Name:%s", linphone_core_upnp_get_charptr_null(upnp_igd_get_device_name(lupnp->upnp_igd_ctxt)));
871 	ms_message("uPnP IGD: Device:%s %s",
872 				   linphone_core_upnp_get_charptr_null(upnp_igd_get_device_model_name(lupnp->upnp_igd_ctxt)),
873 			   linphone_core_upnp_get_charptr_null(upnp_igd_get_device_model_number(lupnp->upnp_igd_ctxt)));
874 	ms_message("uPnP IGD: Refresh mappings");
875 
876 	if(lupnp->sip_udp != NULL) {
877 		global_list = bctbx_list_append(global_list, lupnp->sip_udp);
878 	}
879 	if(lupnp->sip_tcp != NULL) {
880 		global_list = bctbx_list_append(global_list, lupnp->sip_tcp);
881 	}
882 	if(lupnp->sip_tls != NULL) {
883 		global_list = bctbx_list_append(global_list, lupnp->sip_tls);
884 	}
885 
886 	list = lupnp->lc->calls;
887 	while(list != NULL) {
888 		call = (LinphoneCall *)list->data;
889 		if(call->upnp_session != NULL) {
890 			if(call->upnp_session->audio->rtp != NULL) {
891 				global_list = bctbx_list_append(global_list, call->upnp_session->audio->rtp);
892 			}
893 			if(call->upnp_session->audio->rtcp != NULL) {
894 				global_list = bctbx_list_append(global_list, call->upnp_session->audio->rtcp);
895 			}
896 			if(call->upnp_session->video->rtp != NULL) {
897 				global_list = bctbx_list_append(global_list, call->upnp_session->video->rtp);
898 			}
899 			if(call->upnp_session->video->rtcp != NULL) {
900 				global_list = bctbx_list_append(global_list, call->upnp_session->video->rtcp);
901 			}
902 		}
903 		list = list->next;
904 	}
905 
906 	list = linphone_upnp_config_list_port_bindings(lupnp->lc->config, upnp_igd_get_device_id(lupnp->upnp_igd_ctxt));
907 	for(item = list;item != NULL; item = item->next) {
908 			port_mapping = (UpnpPortBinding *)item->data;
909 			port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
910 			if(port_mapping2 == NULL) {
911 				linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
912 			} else if(port_mapping2->state == LinphoneUpnpStateIdle){
913 				/* Force to remove */
914 				port_mapping2->state = LinphoneUpnpStateOk;
915 			}
916 	}
917 	bctbx_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
918 	list = bctbx_list_free(list);
919 
920 
921 	// (Re)Add removed port bindings
922 	list = global_list;
923 	while(list != NULL) {
924 		port_mapping = (UpnpPortBinding *)list->data;
925 		linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping, TRUE);
926 		linphone_upnp_context_send_add_port_binding(lupnp, port_mapping, TRUE);
927 		list = list->next;
928 	}
929 	global_list = bctbx_list_free(global_list);
930 }
931 
linphone_upnp_update_port_binding(UpnpContext * lupnp,UpnpPortBinding ** port_mapping,upnp_igd_ip_protocol protocol,int port,int retry_delay)932 void linphone_upnp_update_port_binding(UpnpContext *lupnp, UpnpPortBinding **port_mapping, upnp_igd_ip_protocol protocol, int port, int retry_delay) {
933 	const char *local_addr, *external_addr;
934 	time_t now = time(NULL);
935 	if(port != 0) {
936 		if(*port_mapping != NULL) {
937 			if(port != (*port_mapping)->local_port) {
938 				linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
939 				*port_mapping = NULL;
940 			}
941 		}
942 		if(*port_mapping == NULL) {
943 			*port_mapping = linphone_upnp_port_binding_new_or_collect(lupnp->pending_bindings, protocol, port, port);
944 		}
945 
946 		// Get addresses
947 		local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
948 		external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
949 
950 		// Force binding update on local address change
951 		if(local_addr != NULL) {
952 			if(strncmp((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr))) {
953 				linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
954 				strncpy((*port_mapping)->local_addr, local_addr, sizeof((*port_mapping)->local_addr));
955 			}
956 		}
957 		if(external_addr != NULL) {
958 			strncpy((*port_mapping)->external_addr, external_addr, sizeof((*port_mapping)->external_addr));
959 		}
960 
961 		// Add (if not already done) the binding
962 		if(now - (*port_mapping)->last_update >= retry_delay) {
963 			(*port_mapping)->last_update = now;
964 			linphone_upnp_context_send_add_port_binding(lupnp, *port_mapping, FALSE);
965 		}
966 	} else {
967 		if(*port_mapping != NULL) {
968 			linphone_upnp_context_send_remove_port_binding(lupnp, *port_mapping, FALSE);
969 			*port_mapping = NULL;
970 		}
971 	}
972 }
973 
linphone_upnp_update_config(UpnpContext * lupnp)974 void linphone_upnp_update_config(UpnpContext* lupnp) {
975 	char key[64];
976 	const bctbx_list_t *item;
977 	UpnpPortBinding *port_mapping;
978 
979 	/* Add configs */
980 	for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
981 		port_mapping = (UpnpPortBinding *)item->data;
982 		snprintf(key, sizeof(key), "%s-%s-%d-%d",
983 					port_mapping->device_id,
984 					(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
985 					port_mapping->external_port,
986 					port_mapping->local_port);
987 		lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
988 		linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
989 	}
990 	bctbx_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
991 	lupnp->adding_configs = bctbx_list_free(lupnp->adding_configs);
992 
993 	/* Remove configs */
994 	for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
995 		port_mapping = (UpnpPortBinding *)item->data;
996 		snprintf(key, sizeof(key), "%s-%s-%d-%d",
997 					port_mapping->device_id,
998 					(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
999 					port_mapping->external_port,
1000 					port_mapping->local_port);
1001 		lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
1002 		linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
1003 	}
1004 	bctbx_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
1005 	lupnp->removing_configs = bctbx_list_free(lupnp->removing_configs);
1006 }
1007 
linphone_upnp_update_proxy(UpnpContext * lupnp,bool_t force)1008 void linphone_upnp_update_proxy(UpnpContext* lupnp, bool_t force) {
1009 	LinphoneUpnpState ready_state;
1010 	const bctbx_list_t *item;
1011 	time_t now = (force)? (lupnp->last_ready_check + UPNP_CORE_READY_CHECK) : time(NULL);
1012 
1013 	/* Refresh registers if we are ready */
1014 	if(now - lupnp->last_ready_check >= UPNP_CORE_READY_CHECK) {
1015 		lupnp->last_ready_check = now;
1016 		ready_state = (_linphone_upnp_context_is_ready_for_register(lupnp))? LinphoneUpnpStateOk: LinphoneUpnpStateKo;
1017 		if(ready_state != lupnp->last_ready_state) {
1018 			for(item=linphone_core_get_proxy_config_list(lupnp->lc);item!=NULL;item=item->next) {
1019 				LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)item->data;
1020 				if (linphone_proxy_config_register_enabled(cfg)) {
1021 					if (ready_state != LinphoneUpnpStateOk) {
1022 						// Only reset ithe registration if we require that upnp should be ok
1023 						if(lupnp->lc->sip_conf.register_only_when_upnp_is_ok) {
1024 							linphone_proxy_config_set_state(cfg, LinphoneRegistrationNone, "Registration impossible (uPnP not ready)");
1025 						} else {
1026 							cfg->commit=TRUE;
1027 						}
1028 					} else {
1029 						cfg->commit=TRUE;
1030 					}
1031 				}
1032 			}
1033 			lupnp->last_ready_state = ready_state;
1034 		}
1035 	}
1036 }
1037 
linphone_core_upnp_hook(void * data)1038 bool_t linphone_core_upnp_hook(void *data) {
1039 	LCSipTransports transport;
1040 	UpnpContext *lupnp = (UpnpContext *)data;
1041 
1042 	ms_mutex_lock(&lupnp->mutex);
1043 
1044 	/* Update ports */
1045 	if(lupnp->state == LinphoneUpnpStateOk) {
1046 		linphone_core_get_sip_transports(lupnp->lc, &transport);
1047 		linphone_upnp_update_port_binding(lupnp, &lupnp->sip_udp, UPNP_IGD_IP_PROTOCOL_UDP, transport.udp_port, UPNP_CORE_RETRY_DELAY);
1048 		linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tcp, UPNP_IGD_IP_PROTOCOL_TCP, transport.tcp_port, UPNP_CORE_RETRY_DELAY);
1049 		linphone_upnp_update_port_binding(lupnp, &lupnp->sip_tls, UPNP_IGD_IP_PROTOCOL_TCP, transport.tls_port, UPNP_CORE_RETRY_DELAY);
1050 	}
1051 
1052 	linphone_upnp_update_proxy(lupnp, FALSE);
1053 	linphone_upnp_update_config(lupnp);
1054 
1055 	ms_mutex_unlock(&lupnp->mutex);
1056 	return TRUE;
1057 }
1058 
linphone_call_update_local_media_description_from_upnp(SalMediaDescription * desc,UpnpSession * session)1059 int linphone_call_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
1060 	int i;
1061 	SalStreamDescription *stream;
1062 	UpnpStream *upnpStream;
1063 
1064 	for (i = 0; i < SAL_MEDIA_DESCRIPTION_MAX_STREAMS; i++) {
1065 		stream = &desc->streams[i];
1066 		if (!sal_stream_description_active(stream)) continue;
1067 		upnpStream = NULL;
1068 		if(stream->type == SalAudio) {
1069 			upnpStream = session->audio;
1070 		} else if(stream->type == SalVideo) {
1071 			upnpStream = session->video;
1072 		}
1073 		if(upnpStream != NULL) {
1074 			if(upnpStream->rtp != NULL && upnpStream->rtp->state == LinphoneUpnpStateOk) {
1075 				strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
1076 				stream->rtp_port = upnpStream->rtp->external_port;
1077 			}
1078 			if(upnpStream->rtcp != NULL && upnpStream->rtcp->state == LinphoneUpnpStateOk) {
1079 				strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
1080 				stream->rtcp_port = upnpStream->rtcp->external_port;
1081 			}
1082 		}
1083 	}
1084 	return 0;
1085 }
1086 
1087 
1088 /*
1089  * uPnP Port Binding
1090  */
1091 
linphone_upnp_port_binding_new(void)1092 UpnpPortBinding *linphone_upnp_port_binding_new(void) {
1093 	UpnpPortBinding *port = NULL;
1094 	port = ms_new0(UpnpPortBinding,1);
1095 	ms_mutex_init(&port->mutex, NULL);
1096 	port->state = LinphoneUpnpStateIdle;
1097 	port->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1098 	port->device_id = NULL;
1099 	port->local_addr[0] = '\0';
1100 	port->local_port = -1;
1101 	port->external_addr[0] = '\0';
1102 	port->external_port = -1;
1103 	port->to_remove = FALSE;
1104 	port->to_add = FALSE;
1105 	port->ref = 1;
1106 	port->last_update = 0;
1107 	return port;
1108 }
1109 
linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol,int local_port,int external_port)1110 UpnpPortBinding *linphone_upnp_port_binding_new_with_parameters(upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1111 	UpnpPortBinding *port_binding = linphone_upnp_port_binding_new();
1112 	port_binding->protocol = protocol;
1113 	port_binding->local_port = local_port;
1114 	port_binding->external_port = external_port;
1115 	return port_binding;
1116 }
1117 
linphone_upnp_port_binding_new_or_collect(bctbx_list_t * list,upnp_igd_ip_protocol protocol,int local_port,int external_port)1118 UpnpPortBinding *linphone_upnp_port_binding_new_or_collect(bctbx_list_t *list, upnp_igd_ip_protocol protocol, int local_port, int external_port) {
1119 	UpnpPortBinding *tmp_binding;
1120 	UpnpPortBinding *end_binding;
1121 
1122 	// Seek an binding with same protocol and local port
1123 	end_binding = linphone_upnp_port_binding_new_with_parameters(protocol, local_port, -1);
1124 	tmp_binding = linphone_upnp_port_binding_equivalent_in_list(list, end_binding);
1125 
1126 	// Must be not attached to any struct
1127 	if(tmp_binding != NULL && tmp_binding->ref == 1) {
1128 		linphone_upnp_port_binding_release(end_binding);
1129 		end_binding = linphone_upnp_port_binding_retain(tmp_binding);
1130 	} else {
1131 		end_binding->external_port = external_port;
1132 	}
1133 	return end_binding;
1134 }
1135 
linphone_upnp_port_binding_copy(const UpnpPortBinding * port)1136 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
1137 	UpnpPortBinding *new_port = NULL;
1138 	new_port = ms_new0(UpnpPortBinding,1);
1139 	memcpy(new_port, port, sizeof(UpnpPortBinding));
1140 	new_port->device_id = NULL;
1141 	linphone_upnp_port_binding_set_device_id(new_port, port->device_id);
1142 	ms_mutex_init(&new_port->mutex, NULL);
1143 	new_port->ref = 1;
1144 	return new_port;
1145 }
1146 
linphone_upnp_port_binding_set_device_id(UpnpPortBinding * port,const char * device_id)1147 void linphone_upnp_port_binding_set_device_id(UpnpPortBinding *port, const char *device_id) {
1148 	char *formated_device_id = linphone_upnp_format_device_id(device_id);
1149 	if(formated_device_id != NULL && port->device_id != NULL) {
1150 		if(strcmp(formated_device_id, port->device_id) == 0) {
1151 			ms_free(formated_device_id);
1152 			return;
1153 		}
1154 	}
1155 	if(port->device_id != NULL) {
1156 		ms_free(port->device_id);
1157 	}
1158 	port->device_id = formated_device_id;
1159 }
1160 
linphone_upnp_port_binding_log(int level,const char * msg,const UpnpPortBinding * port)1161 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
1162 	if(strlen(port->local_addr)) {
1163 		ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d (retry %d)", msg,
1164 							(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1165 									port->external_port,
1166 									port->local_addr,
1167 									port->local_port,
1168 									port->retry - 1);
1169 	} else {
1170 		ortp_log(level, "uPnP IGD: %s %s|%d->%d (retry %d)", msg,
1171 							(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
1172 									port->external_port,
1173 									port->local_port,
1174 									port->retry - 1);
1175 	}
1176 }
1177 
1178 // Return true if the binding are equivalent. (Note external_port == -1 means "don't care")
linphone_upnp_port_binding_equal(const UpnpPortBinding * port1,const UpnpPortBinding * port2)1179 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
1180 	return port1->protocol == port2->protocol &&
1181 		   port1->local_port == port2->local_port &&
1182 		   (port1->external_port == -1 || port2->external_port == -1 || port1->external_port == port2->external_port);
1183 }
1184 
linphone_upnp_port_binding_equivalent_in_list(bctbx_list_t * list,const UpnpPortBinding * port)1185 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(bctbx_list_t *list, const UpnpPortBinding *port) {
1186 	UpnpPortBinding *port_mapping;
1187 	while(list != NULL) {
1188 		port_mapping = (UpnpPortBinding *)list->data;
1189 		if(linphone_upnp_port_binding_equal(port, port_mapping)) {
1190 			return port_mapping;
1191 		}
1192 		list = list->next;
1193 	}
1194 
1195 	return NULL;
1196 }
1197 
linphone_upnp_port_binding_retain(UpnpPortBinding * port)1198 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
1199 	ms_mutex_lock(&port->mutex);
1200 	port->ref++;
1201 	ms_mutex_unlock(&port->mutex);
1202 	return port;
1203 }
1204 
linphone_upnp_port_binding_release(UpnpPortBinding * port)1205 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
1206 	ms_mutex_lock(&port->mutex);
1207 	if(--port->ref == 0) {
1208 		if(port->device_id != NULL) {
1209 			ms_free(port->device_id);
1210 		}
1211 		ms_mutex_unlock(&port->mutex);
1212 		ms_mutex_destroy(&port->mutex);
1213 		ms_free(port);
1214 		return;
1215 	}
1216 	ms_mutex_unlock(&port->mutex);
1217 }
1218 
1219 
1220 /*
1221  * uPnP Stream
1222  */
1223 
linphone_upnp_stream_new(void)1224 UpnpStream* linphone_upnp_stream_new(void) {
1225 	UpnpStream *stream = ms_new0(UpnpStream,1);
1226 	stream->state = LinphoneUpnpStateIdle;
1227 	stream->rtp = NULL;
1228 	stream->rtcp = NULL;
1229 	return stream;
1230 }
1231 
linphone_upnp_stream_destroy(UpnpStream * stream)1232 void linphone_upnp_stream_destroy(UpnpStream* stream) {
1233 	if(stream->rtp != NULL) {
1234 		linphone_upnp_port_binding_release(stream->rtp);
1235 		stream->rtp = NULL;
1236 	}
1237 	if(stream->rtcp != NULL) {
1238 		linphone_upnp_port_binding_release(stream->rtcp);
1239 		stream->rtcp = NULL;
1240 	}
1241 	ms_free(stream);
1242 }
1243 
1244 
1245 /*
1246  * uPnP Session
1247  */
1248 
linphone_upnp_session_new(LinphoneCall * call)1249 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
1250 	UpnpSession *session = ms_new0(UpnpSession,1);
1251 	session->call = call;
1252 	session->state = LinphoneUpnpStateIdle;
1253 	session->audio = linphone_upnp_stream_new();
1254 	session->video = linphone_upnp_stream_new();
1255 	return session;
1256 }
1257 
linphone_upnp_session_destroy(UpnpSession * session)1258 void linphone_upnp_session_destroy(UpnpSession *session) {
1259 	LinphoneCore *lc = session->call->core;
1260 
1261 	if(lc->upnp != NULL) {
1262 		/* Remove bindings */
1263 		if(session->audio->rtp != NULL) {
1264 			linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp, TRUE);
1265 		}
1266 		if(session->audio->rtcp != NULL) {
1267 			linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp, TRUE);
1268 		}
1269 		if(session->video->rtp != NULL) {
1270 			linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp, TRUE);
1271 		}
1272 		if(session->video->rtcp != NULL) {
1273 			linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp, TRUE);
1274 		}
1275 	}
1276 
1277 	session->call->audio_stats->upnp_state = LinphoneUpnpStateKo;
1278 	session->call->video_stats->upnp_state = LinphoneUpnpStateKo;
1279 
1280 	linphone_upnp_stream_destroy(session->audio);
1281 	linphone_upnp_stream_destroy(session->video);
1282 	ms_free(session);
1283 }
1284 
linphone_upnp_session_get_state(UpnpSession * session)1285 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
1286 	return session->state;
1287 }
1288 
1289 
1290 /*
1291  * uPnP Config
1292  */
1293 
1294 struct linphone_upnp_config_list_port_bindings_struct {
1295 	struct _LpConfig *lpc;
1296 	bctbx_list_t *retList;
1297 	const char *device_id;
1298 };
1299 
linphone_upnp_config_list_port_bindings_cb(const char * entry,struct linphone_upnp_config_list_port_bindings_struct * cookie)1300 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
1301 	char device_id[UPNP_UUID_LEN + 1];
1302 	char protocol_str[4]; // TCP or UDP
1303 	upnp_igd_ip_protocol protocol;
1304 	int external_port;
1305 	int local_port;
1306 	int ret;
1307 	bool_t valid = TRUE;
1308 	UpnpPortBinding *port;
1309 
1310 	ret = sscanf(entry, "%"UPNP_UUID_LEN_STR"[^-]-%3s-%i-%i", device_id, protocol_str, &external_port, &local_port);
1311 	if(ret == 4) {
1312 		// Handle only wanted device bindings
1313 		if(device_id != NULL && strcmp(cookie->device_id, device_id) != 0) {
1314 			return;
1315 		}
1316 		if(linphone_upnp_strncmpi(protocol_str, "TCP", 3) == 0) {
1317 			protocol = UPNP_IGD_IP_PROTOCOL_TCP;
1318 		} else if(linphone_upnp_strncmpi(protocol_str, "UDP", 3) == 0) {
1319 			protocol = UPNP_IGD_IP_PROTOCOL_UDP;
1320 		} else {
1321 			valid = FALSE;
1322 		}
1323 		if(valid) {
1324 			port = linphone_upnp_port_binding_new();
1325 			linphone_upnp_port_binding_set_device_id(port, device_id);
1326 			port->state = LinphoneUpnpStateOk;
1327 			port->protocol = protocol;
1328 			port->external_port = external_port;
1329 			port->local_port = local_port;
1330 			cookie->retList = bctbx_list_append(cookie->retList, port);
1331 		}
1332 	} else {
1333 		valid = FALSE;
1334 	}
1335 	if(!valid) {
1336 		ms_warning("uPnP configuration invalid line: %s", entry);
1337 	}
1338 }
1339 
linphone_upnp_config_list_port_bindings(struct _LpConfig * lpc,const char * device_id)1340 bctbx_list_t *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc, const char *device_id) {
1341 	char *formated_device_id = linphone_upnp_format_device_id(device_id);
1342 	struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL, formated_device_id};
1343 	lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1344 	ms_free(formated_device_id);
1345 	return cookie.retList;
1346 }
1347 
linphone_upnp_config_add_port_binding(UpnpContext * lupnp,const UpnpPortBinding * port)1348 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1349 	bctbx_list_t *list;
1350 	UpnpPortBinding *list_port;
1351 
1352 	if(port->device_id == NULL) {
1353 		ms_error("Can't remove port binding without device_id");
1354 		return;
1355 	}
1356 
1357 	list = lupnp->removing_configs;
1358 	while(list != NULL) {
1359 		list_port = (UpnpPortBinding *)list->data;
1360 		if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1361 			lupnp->removing_configs = bctbx_list_remove(lupnp->removing_configs, list_port);
1362 			linphone_upnp_port_binding_release(list_port);
1363 			return;
1364 		}
1365 		list = bctbx_list_next(list);
1366 	}
1367 
1368 	list = lupnp->adding_configs;
1369 	while(list != NULL) {
1370 		list_port = (UpnpPortBinding *)list->data;
1371 		if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1372 			return;
1373 		}
1374 		list = bctbx_list_next(list);
1375 	}
1376 
1377 	list_port = linphone_upnp_port_binding_copy(port);
1378 	lupnp->adding_configs = bctbx_list_append(lupnp->adding_configs, list_port);
1379 }
1380 
linphone_upnp_config_remove_port_binding(UpnpContext * lupnp,const UpnpPortBinding * port)1381 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1382 	bctbx_list_t *list;
1383 	UpnpPortBinding *list_port;
1384 
1385 	if(port->device_id == NULL) {
1386 		ms_error("Can't remove port binding without device_id");
1387 		return;
1388 	}
1389 
1390 	list = lupnp->adding_configs;
1391 	while(list != NULL) {
1392 		list_port = (UpnpPortBinding *)list->data;
1393 		if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1394 			lupnp->adding_configs = bctbx_list_remove(lupnp->adding_configs, list_port);
1395 			linphone_upnp_port_binding_release(list_port);
1396 			return;
1397 		}
1398 		list = bctbx_list_next(list);
1399 	}
1400 
1401 	list = lupnp->removing_configs;
1402 	while(list != NULL) {
1403 		list_port = (UpnpPortBinding *)list->data;
1404 		if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1405 			return;
1406 		}
1407 		list = bctbx_list_next(list);
1408 	}
1409 
1410 	list_port = linphone_upnp_port_binding_copy(port);
1411 	lupnp->removing_configs = bctbx_list_append(lupnp->removing_configs, list_port);
1412 }
1413