]> sjero.net Git - linphone/blob - coreapi/upnp.c
Add another early port binding release
[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                                 //TODO: Don't send id it's udp/tcp/tls port binding
533                                 upnp_context_send_remove_port_binding(lc, port_mapping);
534                         }
535                         ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
536                         port_bindings = ms_list_free(port_bindings);
537                 }
538         }
539
540         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
541                 // Add port bindings
542                 if(lupnp->sip_udp != NULL) {
543                         upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
544                 }
545                 if(lupnp->sip_tcp != NULL) {
546                         upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
547                 }
548                 if(lupnp->sip_tls != NULL) {
549                         upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
550                 }
551         }
552
553         /* Update configs */
554         for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
555                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
556                 if(port_mapping->state == LinphoneUpnpStateAdding) {
557                         snprintf(key, sizeof(key), "%s-%d-%d",
558                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
559                                                                 port_mapping->external_port,
560                                                                 port_mapping->local_port);
561                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
562                         upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
563                 }
564                 if(port_mapping->state == LinphoneUpnpStateRemoving) {
565                         snprintf(key, sizeof(key), "%s-%d-%d",
566                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
567                                                                 port_mapping->external_port,
568                                                                 port_mapping->local_port);
569                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
570                         upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
571                 }
572         }
573         ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
574         lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
575
576         lupnp->old_state = lupnp->state;
577         ms_mutex_unlock(&lupnp->mutex);
578         return TRUE;
579 }
580
581 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
582         int i;
583         SalStreamDescription *stream;
584         UpnpStream *upnpStream;
585
586         for (i = 0; i < desc->nstreams; i++) {
587                 stream = &desc->streams[i];
588                 upnpStream = NULL;
589                 if(stream->type == SalAudio) {
590                         upnpStream = session->audio;
591                 } else if(stream->type == SalVideo) {
592                         upnpStream = session->video;
593                 }
594                 if(upnpStream != NULL) {
595                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
596                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
597                                 stream->rtp_port = upnpStream->rtp->external_port;
598                         }
599                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
600                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
601                                 stream->rtcp_port = upnpStream->rtcp->external_port;
602                         }
603                 }
604         }
605 }
606
607
608 /*
609  * uPnP Port Binding
610  */
611
612 UpnpPortBinding *upnp_port_binding_new() {
613         UpnpPortBinding *port = NULL;
614         port = ms_new0(UpnpPortBinding,1);
615         ms_mutex_init(&port->mutex, NULL);
616         port->state = LinphoneUpnpStateIdle;
617         port->local_addr[0] = '\0';
618         port->local_port = -1;
619         port->external_addr[0] = '\0';
620         port->external_port = -1;
621         port->ref = 1;
622         return port;
623 }
624
625 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
626         UpnpPortBinding *new_port = NULL;
627         new_port = ms_new0(UpnpPortBinding,1);
628         memcpy(new_port, port, sizeof(UpnpPortBinding));
629         ms_mutex_init(&new_port->mutex, NULL);
630         new_port->ref = 1;
631         return new_port;
632 }
633
634 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
635         if(strlen(port->local_addr)) {
636                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
637                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
638                                                                         port->external_port,
639                                                                         port->local_addr,
640                                                                         port->local_port);
641         } else {
642                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
643                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
644                                                                         port->external_port,
645                                                                         port->local_port);
646         }
647 }
648
649 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
650         return port1->protocol == port2->protocol &&
651                         port1->local_port == port2->local_port &&
652                         port1->external_port == port2->external_port;
653 }
654
655 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
656         ms_mutex_lock(&port->mutex);
657         port->ref++;
658         ms_mutex_unlock(&port->mutex);
659         return port;
660 }
661
662 void upnp_port_binding_release(UpnpPortBinding *port) {
663         ms_mutex_lock(&port->mutex);
664         if(--port->ref == 0) {
665                 ms_mutex_unlock(&port->mutex);
666                 ms_mutex_destroy(&port->mutex);
667                 ms_free(port);
668                 return;
669         }
670         ms_mutex_unlock(&port->mutex);
671 }
672
673
674 /*
675  * uPnP Stream
676  */
677
678 UpnpStream* upnp_stream_new() {
679         UpnpStream *stream = ms_new0(UpnpStream,1);
680         stream->state = LinphoneUpnpStateIdle;
681         stream->rtp = upnp_port_binding_new();
682         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
683         stream->rtcp = upnp_port_binding_new();
684         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
685         return stream;
686 }
687
688 void upnp_stream_destroy(UpnpStream* stream) {
689         upnp_port_binding_release(stream->rtp);
690         upnp_port_binding_release(stream->rtcp);
691         ms_free(stream);
692 }
693
694
695 /*
696  * uPnP Session
697  */
698
699 UpnpSession* upnp_session_new() {
700         UpnpSession *session = ms_new0(UpnpSession,1);
701         session->state = LinphoneUpnpStateIdle;
702         session->audio = upnp_stream_new();
703         session->video = upnp_stream_new();
704         return session;
705 }
706
707 void upnp_session_destroy(LinphoneCall* call) {
708         LinphoneCore *lc = call->core;
709
710         /* Remove bindings */
711         if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
712                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
713         }
714         if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
715                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
716         }
717         if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
718                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
719         }
720         if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
721                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
722         }
723
724         upnp_stream_destroy(call->upnp_session->audio);
725         upnp_stream_destroy(call->upnp_session->video);
726         ms_free(call->upnp_session);
727         call->upnp_session = NULL;
728 }
729
730
731 /*
732  * uPnP Config
733  */
734
735 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
736         char protocol_str[4]; // TCP or UDP
737         upnp_igd_ip_protocol protocol;
738         int external_port;
739         int local_port;
740         MSList *retList = NULL;
741         UpnpPortBinding *port;
742         bool_t valid;
743         MSList *elem;
744         LpItem *item;
745         LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
746         if(sec == NULL)
747                 return retList;
748
749         elem = sec->items;
750         while(elem != NULL) {
751                 item=(LpItem*)elem->data;
752                 valid = TRUE;
753                 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
754                         if(strcasecmp(protocol_str, "TCP") == 0) {
755                                 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
756                         } else if(strcasecmp(protocol_str, "UDP") == 0) {
757                                 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
758                         } else {
759                                 valid = FALSE;
760                         }
761                         if(valid) {
762                                 port = upnp_port_binding_new();
763                                 port->state = LinphoneUpnpStateOk;
764                                 port->protocol = protocol;
765                                 port->external_port = external_port;
766                                 port->local_port = local_port;
767                                 retList = ms_list_append(retList, port);
768                         }
769                 } else {
770                         valid = FALSE;
771                 }
772                 elem = ms_list_next(elem);
773                 if(!valid) {
774                         ms_warning("uPnP configuration invalid line: %s", item->key);
775                         lp_section_remove_item(sec, item);
776                 }
777         }
778
779         return retList;
780 }
781
782 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
783         UpnpContext *lupnp = &lc->upnp;
784         MSList *list = lupnp->pending_configs;
785         UpnpPortBinding *list_port;
786         bool_t remove = FALSE;
787         bool_t add = TRUE;
788         while(list != NULL) {
789                 list_port = (UpnpPortBinding *)list->data;
790                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
791                         if(list_port->state == LinphoneUpnpStateAdding) {
792                                 add = FALSE;
793                                 break;
794                         }
795                         if(list_port->state == LinphoneUpnpStateRemoving) {
796                                 remove = TRUE;
797                                 break;
798                         }
799                 }
800                 list = ms_list_next(list);
801         }
802
803         if(remove) {
804                 lupnp->pending_configs = ms_list_remove(list, list_port);
805         } else if(add) {
806                 list_port = upnp_port_binding_copy(port);
807                 list_port->state = LinphoneUpnpStateAdding;
808                 lupnp->pending_configs = ms_list_append(list, list_port);
809         }
810
811         return 0;
812 }
813
814 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
815         UpnpContext *lupnp = &lc->upnp;
816         MSList *list = lupnp->pending_configs;
817         UpnpPortBinding *list_port;
818         bool_t remove = FALSE;
819         bool_t add = TRUE;
820         while(list != NULL) {
821                 list_port = (UpnpPortBinding *)list->data;
822                 if(upnp_port_binding_equal(list_port, port)) {
823                         if(list_port->state == LinphoneUpnpStateRemoving) {
824                                 add = FALSE;
825                                 break;
826                         }
827                         if(list_port->state == LinphoneUpnpStateAdding) {
828                                 remove = TRUE;
829                                 break;
830                         }
831                 }
832                 list = ms_list_next(list);
833         }
834
835         if(remove) {
836                 lupnp->pending_configs = ms_list_remove(list, list_port);
837         } else if(add) {
838                 list_port = upnp_port_binding_copy(port);
839                 list_port->state = LinphoneUpnpStateRemoving;
840                 lupnp->pending_configs = ms_list_append(list, list_port);
841         }
842
843         return 0;
844 }