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