]> sjero.net Git - linphone/blob - coreapi/upnp.c
Don't remove hook (done before)
[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
23 #define UPNP_MAX_RETRY 4
24 #define UPNP_SECTION_NAME "uPnP"
25
26 /* Define private types */
27 typedef struct _LpItem{
28         char *key;
29         char *value;
30 } LpItem;
31
32 typedef struct _LpSection{
33         char *name;
34         MSList *items;
35 } LpSection;
36
37 typedef struct _LpConfig{
38         FILE *file;
39         char *filename;
40         MSList *sections;
41         int modified;
42         int readonly;
43 } LpConfig;
44
45 /* Declare private functions */
46 LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
47 void lp_section_remove_item(LpSection *sec, LpItem *item);
48 void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
49
50 bool_t linphone_core_upnp_hook(void *data);
51
52 UpnpPortBinding *upnp_port_binding_new();
53 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
54 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
55 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
56 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
57 void upnp_port_binding_release(UpnpPortBinding *port);
58
59 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
60 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
61 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
62
63 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
64 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
65
66
67 /**
68  * uPnP Callbacks
69  */
70
71 /* Convert uPnP IGD logs to ortp logs */
72 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
73         int ortp_level = ORTP_DEBUG;
74         switch(level) {
75         case UPNP_IGD_MESSAGE:
76                 ortp_level = ORTP_MESSAGE;
77                 break;
78         case UPNP_IGD_WARNING:
79                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
80                 break;
81         case UPNP_IGD_ERROR:
82                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
83                 break;
84         default:
85                 break;
86         }
87         ortp_logv(ortp_level, fmt, list);
88 }
89
90 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
91         LinphoneCore *lc = (LinphoneCore *)cookie;
92         UpnpContext *lupnp = &lc->upnp;
93         upnp_igd_port_mapping *mapping = NULL;
94         UpnpPortBinding *port_mapping = NULL;
95         const char *ip_address = NULL;
96         const char *connection_status = NULL;
97         bool_t nat_enabled = FALSE;
98         ms_mutex_lock(&lupnp->mutex);
99
100         switch(event) {
101         case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
102         case UPNP_IGD_NAT_ENABLED_CHANGED:
103         case UPNP_IGD_CONNECTION_STATUS_CHANGED:
104                 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
105                 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
106                 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
107
108                 if(ip_address == NULL || connection_status == NULL) {
109                         ms_message("uPnP IGD: Pending");
110                         lupnp->state = LinphoneUpnpStatePending;
111                 } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
112                         ms_message("uPnP IGD: Not Available");
113                         lupnp->state = LinphoneUpnpStateNotAvailable;
114                 } else {
115                         ms_message("uPnP IGD: Connected");
116                         lupnp->state = LinphoneUpnpStateOk;
117                 }
118
119                 break;
120
121         case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
122                 mapping = (upnp_igd_port_mapping *) arg;
123                 port_mapping = (UpnpPortBinding*) mapping->cookie;
124                 port_mapping->external_port = mapping->remote_port;
125                 port_mapping->state = LinphoneUpnpStateOk;
126                 upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
127                 upnp_config_add_port_binding(lc, port_mapping);
128
129                 upnp_port_binding_release(port_mapping);
130                 break;
131
132         case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
133                 mapping = (upnp_igd_port_mapping *) arg;
134                 port_mapping = (UpnpPortBinding*) mapping->cookie;
135                 port_mapping->external_port = -1; //Force a new random port
136                 if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
137                         upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
138                 }
139
140                 upnp_port_binding_release(port_mapping);
141                 break;
142
143         case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
144                 mapping = (upnp_igd_port_mapping *) arg;
145                 port_mapping = (UpnpPortBinding*) mapping->cookie;
146                 port_mapping->state = LinphoneUpnpStateIdle;
147                 upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
148                 upnp_config_remove_port_binding(lc, port_mapping);
149
150                 upnp_port_binding_release(port_mapping);
151                 break;
152
153         case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
154                 mapping = (upnp_igd_port_mapping *) arg;
155                 port_mapping = (UpnpPortBinding*) mapping->cookie;
156                 if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
157                         upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
158                         upnp_config_remove_port_binding(lc, port_mapping);
159                 }
160
161                 upnp_port_binding_release(port_mapping);
162                 break;
163
164         default:
165                 break;
166         }
167
168         ms_mutex_unlock(&lupnp->mutex);
169 }
170
171
172 /**
173  * uPnP Context
174  */
175
176 int upnp_context_init(LinphoneCore *lc) {
177         LCSipTransports transport;
178         UpnpContext *lupnp = &lc->upnp;
179         const char *ip_address;
180
181         ms_mutex_init(&lupnp->mutex, NULL);
182         lupnp->pending_configs = NULL;
183         lupnp->state = LinphoneUpnpStateIdle;
184         lupnp->old_state = LinphoneUpnpStateIdle;
185         ms_message("uPnP IGD: Init");
186
187         linphone_core_get_sip_transports(lc, &transport);
188         if(transport.udp_port != 0) {
189                 lupnp->sip_udp = upnp_port_binding_new();
190                 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
191                 lupnp->sip_udp->local_port = transport.udp_port;
192                 lupnp->sip_udp->external_port = transport.udp_port;
193         } else {
194                 lupnp->sip_udp = NULL;
195         }
196         if(transport.tcp_port != 0) {
197                 lupnp->sip_tcp = upnp_port_binding_new();
198                 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
199                 lupnp->sip_tcp->local_port = transport.tcp_port;
200                 lupnp->sip_tcp->external_port = transport.tcp_port;
201         } else {
202                 lupnp->sip_tcp = NULL;
203         }
204         if(transport.tls_port != 0) {
205                 lupnp->sip_tls = upnp_port_binding_new();
206                 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
207                 lupnp->sip_tls->local_port = transport.tls_port;
208                 lupnp->sip_tls->external_port = transport.tls_port;
209         } else {
210                 lupnp->sip_tls = NULL;
211         }
212
213         linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
214
215         lupnp->upnp_igd_ctxt = NULL;
216         lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
217         if(lupnp->upnp_igd_ctxt == NULL) {
218                 lupnp->state = LinphoneUpnpStateKo;
219                 ms_error("Can't create uPnP IGD context");
220                 return -1;
221         }
222
223         ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
224         if(lupnp->sip_udp != NULL) {
225                 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
226         }
227         if(lupnp->sip_tcp != NULL) {
228                 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
229         }
230         if(lupnp->sip_tls != NULL) {
231                 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
232         }
233
234         lupnp->state = LinphoneUpnpStatePending;
235         return 0;
236 }
237
238 void upnp_context_uninit(LinphoneCore *lc) {
239         UpnpContext *lupnp = &lc->upnp;
240
241         // Not need, all hooks are removed before
242         //linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
243
244         if(lupnp->sip_udp != NULL) {
245                 upnp_port_binding_release(lupnp->sip_udp);
246         }
247         if(lupnp->sip_tcp != NULL) {
248                 upnp_port_binding_release(lupnp->sip_tcp);
249         }
250         if(lupnp->sip_tls != NULL) {
251                 upnp_port_binding_release(lupnp->sip_tls);
252         }
253         if(lupnp->upnp_igd_ctxt != NULL) {
254                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
255         }
256         ms_mutex_destroy(&lupnp->mutex);
257
258         ms_message("uPnP IGD: Uninit");
259 }
260
261 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
262         UpnpContext *lupnp = &lc->upnp;
263         upnp_igd_port_mapping mapping;
264         int ret;
265         if(port->state == LinphoneUpnpStateIdle) {
266                 port->retry = 0;
267                 port->state = LinphoneUpnpStateAdding;
268         } else if(port->state != LinphoneUpnpStateAdding) {
269                 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
270                 return -2;
271         }
272
273         if(port->retry >= UPNP_MAX_RETRY) {
274                 ret = -1;
275         } else {
276                 mapping.cookie = upnp_port_binding_retain(port);
277                 mapping.local_port = port->local_port;
278                 mapping.local_host = port->local_addr;
279                 if(port->external_port == -1)
280                         mapping.remote_port = rand()%(0xffff - 1024) + 1024; // TODO: use better method
281                 else
282                         mapping.remote_port = port->external_port;
283                 mapping.remote_host = "";
284                 mapping.description = PACKAGE_NAME;
285                 mapping.protocol = port->protocol;
286
287                 port->retry++;
288                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
289         }
290         if(ret != 0) {
291                 port->state = LinphoneUpnpStateKo;
292         }
293         return ret;
294 }
295
296 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
297         UpnpContext *lupnp = &lc->upnp;
298         upnp_igd_port_mapping mapping;
299         int ret;
300         if(port->state == LinphoneUpnpStateOk) {
301                 port->retry = 0;
302                 port->state = LinphoneUpnpStateRemoving;
303         } else if(port->state != LinphoneUpnpStateRemoving) {
304                 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
305                 return -2;
306         }
307
308         if(port->retry >= UPNP_MAX_RETRY) {
309                 ret = -1;
310         } else {
311                 mapping.cookie = upnp_port_binding_retain(port);
312                 mapping.remote_port = port->external_port;
313                 mapping.remote_host = "";
314                 mapping.protocol = port->protocol;
315                 port->retry++;
316                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
317         }
318         if(ret != 0) {
319                 port->state = LinphoneUpnpStateKo;
320         }
321         return ret;
322 }
323
324 /*
325  * uPnP Core interfaces
326  */
327
328 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
329         LinphoneCore *lc = call->core;
330         UpnpContext *lupnp = &lc->upnp;
331         int ret = -1;
332         const char *local_addr, *external_addr;
333
334         ms_mutex_lock(&lupnp->mutex);
335         // Don't handle when the call
336         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
337                 ret = 0;
338                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
339                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
340
341                 /*
342                  * Audio part
343                  */
344                 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
345                 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
346                 call->upnp_session->audio->rtp->local_port = call->audio_port;
347                 call->upnp_session->audio->rtp->external_port = call->audio_port;
348                 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
349                 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
350                 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
351                 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
352                 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
353                         // Add audio port binding
354                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
355                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
356                         // Remove audio port binding
357                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
358                 }
359                 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
360                         // Add audio port binding
361                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
362                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
363                         // Remove audio port binding
364                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
365                 }
366
367                 /*
368                  * Video part
369                  */
370                 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
371                 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
372                 call->upnp_session->video->rtp->local_port = call->video_port;
373                 call->upnp_session->video->rtp->external_port = call->video_port;
374                 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
375                 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
376                 call->upnp_session->video->rtcp->local_port = call->video_port+1;
377                 call->upnp_session->video->rtcp->external_port = call->video_port+1;
378                 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
379                         // Add video port binding
380                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
381                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
382                         // Remove video port binding
383                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
384                 }
385                 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
386                         // Add video port binding
387                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
388                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
389                         // Remove video port binding
390                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
391                 }
392         }
393
394         ms_mutex_unlock(&lupnp->mutex);
395         return ret;
396 }
397
398
399 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
400         bool_t audio = FALSE;
401         bool_t video = FALSE;
402         int i;
403         const SalStreamDescription *stream;
404
405         for (i = 0; i < md->nstreams; i++) {
406                 stream = &md->streams[i];
407                 if(stream->type == SalAudio) {
408                         audio = TRUE;
409                 } else if(stream->type == SalVideo) {
410                         video = TRUE;
411                 }
412         }
413
414         return linphone_core_update_upnp_audio_video(call, audio, video);
415 }
416
417 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
418         return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
419 }
420
421 int upnp_call_process(LinphoneCall *call) {
422         LinphoneCore *lc = call->core;
423         UpnpContext *lupnp = &lc->upnp;
424         int ret = -1;
425         UpnpState oldState;
426
427         ms_mutex_lock(&lupnp->mutex);
428
429         // Don't handle when the call
430         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
431                 ret = 0;
432
433                 /*
434                  * Update Audio state
435                  */
436                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
437                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
438                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
439                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
440                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
441                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
442                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
443                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
444                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
445                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
446                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
447                 } else {
448                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
449                 }
450
451                 /*
452                  * Update Video state
453                  */
454                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
455                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
456                         call->upnp_session->video->state = LinphoneUpnpStateOk;
457                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
458                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
459                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
460                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
461                         call->upnp_session->video->state = LinphoneUpnpStatePending;
462                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
463                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
464                         call->upnp_session->video->state = LinphoneUpnpStateKo;
465                 } else {
466                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
467                 }
468
469                 /*
470                  * Update session state
471                  */
472                 oldState = call->upnp_session->state;
473                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
474                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
475                         call->upnp_session->state = LinphoneUpnpStateOk;
476                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
477                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
478                         call->upnp_session->state = LinphoneUpnpStatePending;
479                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
480                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
481                         call->upnp_session->state = LinphoneUpnpStateKo;
482                 } else {
483                         call->upnp_session->state = LinphoneUpnpStateIdle;
484                 }
485
486                 /* When change is done proceed update */
487                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
488                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
489                         if(call->upnp_session->state == LinphoneUpnpStateOk)
490                                 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
491                         else
492                                 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
493
494                         switch (call->state) {
495                                 case LinphoneCallUpdating:
496                                         linphone_core_start_update_call(lc, call);
497                                         break;
498                                 case LinphoneCallUpdatedByRemote:
499                                         linphone_core_start_accept_call_update(lc, call);
500                                         break;
501                                 case LinphoneCallOutgoingInit:
502                                         linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
503                                         break;
504                                 case LinphoneCallIdle:
505                                         linphone_core_notify_incoming_call(lc, call);
506                                         break;
507                                 default:
508                                         break;
509                         }
510                 }
511         }
512
513         ms_mutex_unlock(&lupnp->mutex);
514         return ret;
515 }
516
517 bool_t linphone_core_upnp_hook(void *data) {
518         char key[64];
519         MSList *port_bindings = NULL;
520         MSList *port_bindings_item;
521         UpnpPortBinding *port_mapping;
522         LinphoneCore *lc = (LinphoneCore *)data;
523         UpnpContext *lupnp = &lc->upnp;
524         ms_mutex_lock(&lupnp->mutex);
525
526         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
527                 // Remove old mapping
528                 port_bindings = upnp_config_list_port_bindings(lc->config);
529                 if(port_bindings != NULL) {
530                         for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
531                                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
532                                 upnp_context_send_remove_port_binding(lc, port_mapping);
533                         }
534                         ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
535                         port_bindings = ms_list_free(port_bindings);
536                 }
537         }
538
539         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
540                 // Add port bindings
541                 if(lupnp->sip_udp != NULL) {
542                         upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
543                 }
544                 if(lupnp->sip_tcp != NULL) {
545                         upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
546                 }
547                 if(lupnp->sip_tls != NULL) {
548                         upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
549                 }
550         }
551
552         /* Update configs */
553         for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
554                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
555                 if(port_mapping->state == LinphoneUpnpStateAdding) {
556                         snprintf(key, sizeof(key), "%s-%d-%d",
557                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
558                                                                 port_mapping->external_port,
559                                                                 port_mapping->local_port);
560                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
561                         upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
562                 }
563                 if(port_mapping->state == LinphoneUpnpStateRemoving) {
564                         snprintf(key, sizeof(key), "%s-%d-%d",
565                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
566                                                                 port_mapping->external_port,
567                                                                 port_mapping->local_port);
568                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
569                         upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
570                 }
571         }
572         ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
573         lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
574
575         lupnp->old_state = lupnp->state;
576         ms_mutex_unlock(&lupnp->mutex);
577         return TRUE;
578 }
579
580 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
581         int i;
582         SalStreamDescription *stream;
583         UpnpStream *upnpStream;
584
585         for (i = 0; i < desc->nstreams; i++) {
586                 stream = &desc->streams[i];
587                 upnpStream = NULL;
588                 if(stream->type == SalAudio) {
589                         upnpStream = session->audio;
590                 } else if(stream->type == SalVideo) {
591                         upnpStream = session->video;
592                 }
593                 if(upnpStream != NULL) {
594                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
595                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
596                                 stream->rtp_port = upnpStream->rtp->external_port;
597                         }
598                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
599                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
600                                 stream->rtcp_port = upnpStream->rtcp->external_port;
601                         }
602                 }
603         }
604 }
605
606
607 /*
608  * uPnP Port Binding
609  */
610
611 UpnpPortBinding *upnp_port_binding_new() {
612         UpnpPortBinding *port = NULL;
613         port = ms_new0(UpnpPortBinding,1);
614         ms_mutex_init(&port->mutex, NULL);
615         port->state = LinphoneUpnpStateIdle;
616         port->local_addr[0] = '\0';
617         port->local_port = -1;
618         port->external_addr[0] = '\0';
619         port->external_port = -1;
620         port->ref = 1;
621         return port;
622 }
623
624 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
625         UpnpPortBinding *new_port = NULL;
626         new_port = ms_new0(UpnpPortBinding,1);
627         memcpy(new_port, port, sizeof(UpnpPortBinding));
628         ms_mutex_init(&new_port->mutex, NULL);
629         new_port->ref = 1;
630         return new_port;
631 }
632
633 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
634         if(strlen(port->local_addr)) {
635                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
636                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
637                                                                         port->external_port,
638                                                                         port->local_addr,
639                                                                         port->local_port);
640         } else {
641                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
642                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
643                                                                         port->external_port,
644                                                                         port->local_port);
645         }
646 }
647
648 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
649         return port1->protocol == port2->protocol &&
650                         port1->local_port == port2->local_port &&
651                         port1->external_port == port2->external_port;
652 }
653
654 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
655         ms_mutex_lock(&port->mutex);
656         port->ref++;
657         ms_mutex_unlock(&port->mutex);
658         return port;
659 }
660
661 void upnp_port_binding_release(UpnpPortBinding *port) {
662         ms_mutex_lock(&port->mutex);
663         if(--port->ref == 0) {
664                 ms_mutex_unlock(&port->mutex);
665                 ms_mutex_destroy(&port->mutex);
666                 ms_free(port);
667                 return;
668         }
669         ms_mutex_unlock(&port->mutex);
670 }
671
672
673 /*
674  * uPnP Stream
675  */
676
677 UpnpStream* upnp_stream_new() {
678         UpnpStream *stream = ms_new0(UpnpStream,1);
679         stream->state = LinphoneUpnpStateIdle;
680         stream->rtp = upnp_port_binding_new();
681         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
682         stream->rtcp = upnp_port_binding_new();
683         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
684         return stream;
685 }
686
687 void upnp_stream_destroy(UpnpStream* stream) {
688         upnp_port_binding_release(stream->rtp);
689         upnp_port_binding_release(stream->rtcp);
690         ms_free(stream);
691 }
692
693
694 /*
695  * uPnP Session
696  */
697
698 UpnpSession* upnp_session_new() {
699         UpnpSession *session = ms_new0(UpnpSession,1);
700         session->state = LinphoneUpnpStateIdle;
701         session->audio = upnp_stream_new();
702         session->video = upnp_stream_new();
703         return session;
704 }
705
706 void upnp_session_destroy(LinphoneCall* call) {
707         LinphoneCore *lc = call->core;
708
709         /* Remove bindings */
710         if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
711                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
712         }
713         if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
714                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
715         }
716         if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
717                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
718         }
719         if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
720                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
721         }
722
723         upnp_stream_destroy(call->upnp_session->audio);
724         upnp_stream_destroy(call->upnp_session->video);
725         ms_free(call->upnp_session);
726         call->upnp_session = NULL;
727 }
728
729
730 /*
731  * uPnP Config
732  */
733
734 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
735         char protocol_str[4]; // TCP or UDP
736         upnp_igd_ip_protocol protocol;
737         int external_port;
738         int local_port;
739         MSList *retList = NULL;
740         UpnpPortBinding *port;
741         bool_t valid;
742         MSList *elem;
743         LpItem *item;
744         LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
745         if(sec == NULL)
746                 return retList;
747
748         elem = sec->items;
749         while(elem != NULL) {
750                 item=(LpItem*)elem->data;
751                 valid = TRUE;
752                 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
753                         if(strcasecmp(protocol_str, "TCP") == 0) {
754                                 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
755                         } else if(strcasecmp(protocol_str, "UDP") == 0) {
756                                 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
757                         } else {
758                                 valid = FALSE;
759                         }
760                         if(valid) {
761                                 port = upnp_port_binding_new();
762                                 port->state = LinphoneUpnpStateOk;
763                                 port->protocol = protocol;
764                                 port->external_port = external_port;
765                                 port->local_port = local_port;
766                                 retList = ms_list_append(retList, port);
767                         }
768                 } else {
769                         valid = FALSE;
770                 }
771                 elem = ms_list_next(elem);
772                 if(!valid) {
773                         ms_warning("uPnP configuration invalid line: %s", item->key);
774                         lp_section_remove_item(sec, item);
775                 }
776         }
777
778         return retList;
779 }
780
781 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
782         UpnpContext *lupnp = &lc->upnp;
783         MSList *list = lupnp->pending_configs;
784         UpnpPortBinding *list_port;
785         bool_t remove = FALSE;
786         bool_t add = TRUE;
787         while(list != NULL) {
788                 list_port = (UpnpPortBinding *)list->data;
789                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
790                         if(list_port->state == LinphoneUpnpStateAdding) {
791                                 add = FALSE;
792                                 break;
793                         }
794                         if(list_port->state == LinphoneUpnpStateRemoving) {
795                                 remove = TRUE;
796                                 break;
797                         }
798                 }
799                 list = ms_list_next(list);
800         }
801
802         if(remove) {
803                 lupnp->pending_configs = ms_list_remove(list, list_port);
804         } else if(add) {
805                 list_port = upnp_port_binding_copy(port);
806                 list_port->state = LinphoneUpnpStateAdding;
807                 lupnp->pending_configs = ms_list_append(list, list_port);
808         }
809
810         return 0;
811 }
812
813 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
814         UpnpContext *lupnp = &lc->upnp;
815         MSList *list = lupnp->pending_configs;
816         UpnpPortBinding *list_port;
817         bool_t remove = FALSE;
818         bool_t add = TRUE;
819         while(list != NULL) {
820                 list_port = (UpnpPortBinding *)list->data;
821                 if(upnp_port_binding_equal(list_port, port)) {
822                         if(list_port->state == LinphoneUpnpStateRemoving) {
823                                 add = FALSE;
824                                 break;
825                         }
826                         if(list_port->state == LinphoneUpnpStateAdding) {
827                                 remove = TRUE;
828                                 break;
829                         }
830                 }
831                 list = ms_list_next(list);
832         }
833
834         if(remove) {
835                 lupnp->pending_configs = ms_list_remove(list, list_port);
836         } else if(add) {
837                 list_port = upnp_port_binding_copy(port);
838                 list_port->state = LinphoneUpnpStateRemoving;
839                 lupnp->pending_configs = ms_list_append(list, list_port);
840         }
841
842         return 0;
843 }