]> sjero.net Git - linphone/blob - coreapi/upnp.c
Set external port equal to local port the first time
[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         linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
241
242         if(lupnp->sip_udp != NULL) {
243                 upnp_port_binding_release(lupnp->sip_udp);
244         }
245         if(lupnp->sip_tcp != NULL) {
246                 upnp_port_binding_release(lupnp->sip_tcp);
247         }
248         if(lupnp->sip_tls != NULL) {
249                 upnp_port_binding_release(lupnp->sip_tls);
250         }
251         if(lupnp->upnp_igd_ctxt != NULL) {
252                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
253         }
254         ms_mutex_destroy(&lupnp->mutex);
255
256         ms_message("uPnP IGD: Uninit");
257 }
258
259 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
260         UpnpContext *lupnp = &lc->upnp;
261         upnp_igd_port_mapping mapping;
262         int ret;
263         if(port->state == LinphoneUpnpStateIdle) {
264                 port->retry = 0;
265                 port->state = LinphoneUpnpStateAdding;
266         } else if(port->state != LinphoneUpnpStateAdding) {
267                 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
268                 return -2;
269         }
270
271         if(port->retry >= UPNP_MAX_RETRY) {
272                 ret = -1;
273         } else {
274                 mapping.cookie = upnp_port_binding_retain(port);
275                 mapping.local_port = port->local_port;
276                 mapping.local_host = port->local_addr;
277                 if(port->external_port == -1)
278                         mapping.remote_port = rand()%(0xffff - 1024) + 1024; // TODO: use better method
279                 else
280                         mapping.remote_port = port->external_port;
281                 mapping.remote_host = "";
282                 mapping.description = PACKAGE_NAME;
283                 mapping.protocol = port->protocol;
284
285                 port->retry++;
286                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
287         }
288         if(ret != 0) {
289                 port->state = LinphoneUpnpStateKo;
290         }
291         return ret;
292 }
293
294 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
295         UpnpContext *lupnp = &lc->upnp;
296         upnp_igd_port_mapping mapping;
297         int ret;
298         if(port->state == LinphoneUpnpStateOk) {
299                 port->retry = 0;
300                 port->state = LinphoneUpnpStateRemoving;
301         } else if(port->state != LinphoneUpnpStateRemoving) {
302                 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
303                 return -2;
304         }
305
306         if(port->retry >= UPNP_MAX_RETRY) {
307                 ret = -1;
308         } else {
309                 mapping.cookie = upnp_port_binding_retain(port);
310                 mapping.remote_port = port->external_port;
311                 mapping.remote_host = "";
312                 mapping.protocol = port->protocol;
313                 port->retry++;
314                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
315         }
316         if(ret != 0) {
317                 port->state = LinphoneUpnpStateKo;
318         }
319         return ret;
320 }
321
322 /*
323  * uPnP Core interfaces
324  */
325
326 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
327         LinphoneCore *lc = call->core;
328         UpnpContext *lupnp = &lc->upnp;
329         int ret = -1;
330         const char *local_addr, *external_addr;
331
332         ms_mutex_lock(&lupnp->mutex);
333         // Don't handle when the call
334         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
335                 ret = 0;
336                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
337                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
338
339                 /*
340                  * Audio part
341                  */
342                 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
343                 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
344                 call->upnp_session->audio->rtp->local_port = call->audio_port;
345                 call->upnp_session->audio->rtp->external_port = call->audio_port;
346                 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
347                 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
348                 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
349                 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
350                 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
351                         // Add audio port binding
352                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
353                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
354                         // Remove audio port binding
355                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
356                 }
357                 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
358                         // Add audio port binding
359                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
360                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
361                         // Remove audio port binding
362                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
363                 }
364
365                 /*
366                  * Video part
367                  */
368                 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
369                 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
370                 call->upnp_session->video->rtp->local_port = call->video_port;
371                 call->upnp_session->video->rtp->external_port = call->video_port;
372                 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
373                 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
374                 call->upnp_session->video->rtcp->local_port = call->video_port+1;
375                 call->upnp_session->video->rtcp->external_port = call->video_port+1;
376                 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
377                         // Add video port binding
378                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
379                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
380                         // Remove video port binding
381                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
382                 }
383                 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
384                         // Add video port binding
385                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
386                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
387                         // Remove video port binding
388                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
389                 }
390         }
391
392         ms_mutex_unlock(&lupnp->mutex);
393         return ret;
394 }
395
396
397 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
398         bool_t audio = FALSE;
399         bool_t video = FALSE;
400         int i;
401         const SalStreamDescription *stream;
402
403         for (i = 0; i < md->nstreams; i++) {
404                 stream = &md->streams[i];
405                 if(stream->type == SalAudio) {
406                         audio = TRUE;
407                 } else if(stream->type == SalVideo) {
408                         video = TRUE;
409                 }
410         }
411
412         return linphone_core_update_upnp_audio_video(call, audio, video);
413 }
414
415 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
416         return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
417 }
418
419 int upnp_call_process(LinphoneCall *call) {
420         LinphoneCore *lc = call->core;
421         UpnpContext *lupnp = &lc->upnp;
422         int ret = -1;
423         UpnpState oldState;
424
425         ms_mutex_lock(&lupnp->mutex);
426
427         // Don't handle when the call
428         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
429                 ret = 0;
430
431                 /*
432                  * Update Audio state
433                  */
434                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
435                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
436                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
437                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
438                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
439                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
440                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
441                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
442                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
443                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
444                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
445                 } else {
446                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
447                 }
448
449                 /*
450                  * Update Video state
451                  */
452                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
453                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
454                         call->upnp_session->video->state = LinphoneUpnpStateOk;
455                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
456                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
457                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
458                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
459                         call->upnp_session->video->state = LinphoneUpnpStatePending;
460                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
461                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
462                         call->upnp_session->video->state = LinphoneUpnpStateKo;
463                 } else {
464                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
465                 }
466
467                 /*
468                  * Update session state
469                  */
470                 oldState = call->upnp_session->state;
471                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
472                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
473                         call->upnp_session->state = LinphoneUpnpStateOk;
474                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
475                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
476                         call->upnp_session->state = LinphoneUpnpStatePending;
477                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
478                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
479                         call->upnp_session->state = LinphoneUpnpStateKo;
480                 } else {
481                         call->upnp_session->state = LinphoneUpnpStateIdle;
482                 }
483
484                 /* When change is done proceed update */
485                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
486                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
487                         if(call->upnp_session->state == LinphoneUpnpStateOk)
488                                 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
489                         else
490                                 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
491
492                         switch (call->state) {
493                                 case LinphoneCallUpdating:
494                                         linphone_core_start_update_call(lc, call);
495                                         break;
496                                 case LinphoneCallUpdatedByRemote:
497                                         linphone_core_start_accept_call_update(lc, call);
498                                         break;
499                                 case LinphoneCallOutgoingInit:
500                                         linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
501                                         break;
502                                 case LinphoneCallIdle:
503                                         linphone_core_notify_incoming_call(lc, call);
504                                         break;
505                                 default:
506                                         break;
507                         }
508                 }
509         }
510
511         ms_mutex_unlock(&lupnp->mutex);
512         return ret;
513 }
514
515 bool_t linphone_core_upnp_hook(void *data) {
516         char key[64];
517         MSList *port_bindings = NULL;
518         MSList *port_bindings_item;
519         UpnpPortBinding *port_mapping;
520         LinphoneCore *lc = (LinphoneCore *)data;
521         UpnpContext *lupnp = &lc->upnp;
522         ms_mutex_lock(&lupnp->mutex);
523
524         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
525                 // Remove old mapping
526                 port_bindings = upnp_config_list_port_bindings(lc->config);
527                 if(port_bindings != NULL) {
528                         for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
529                                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
530                                 upnp_context_send_remove_port_binding(lc, port_mapping);
531                         }
532                         ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
533                         port_bindings = ms_list_free(port_bindings);
534                 }
535         }
536
537         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
538                 // Add port bindings
539                 if(lupnp->sip_udp != NULL) {
540                         upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
541                 }
542                 if(lupnp->sip_tcp != NULL) {
543                         upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
544                 }
545                 if(lupnp->sip_tls != NULL) {
546                         upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
547                 }
548         }
549
550         /* Update configs */
551         for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
552                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
553                 if(port_mapping->state == LinphoneUpnpStateAdding) {
554                         snprintf(key, sizeof(key), "%s-%d-%d",
555                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
556                                                                 port_mapping->external_port,
557                                                                 port_mapping->local_port);
558                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
559                         upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
560                 }
561                 if(port_mapping->state == LinphoneUpnpStateRemoving) {
562                         snprintf(key, sizeof(key), "%s-%d-%d",
563                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
564                                                                 port_mapping->external_port,
565                                                                 port_mapping->local_port);
566                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
567                         upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
568                 }
569         }
570         ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
571         lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
572
573         lupnp->old_state = lupnp->state;
574         ms_mutex_unlock(&lupnp->mutex);
575         return TRUE;
576 }
577
578 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
579         int i;
580         SalStreamDescription *stream;
581         UpnpStream *upnpStream;
582
583         for (i = 0; i < desc->nstreams; i++) {
584                 stream = &desc->streams[i];
585                 upnpStream = NULL;
586                 if(stream->type == SalAudio) {
587                         upnpStream = session->audio;
588                 } else if(stream->type == SalVideo) {
589                         upnpStream = session->video;
590                 }
591                 if(upnpStream != NULL) {
592                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
593                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
594                                 stream->rtp_port = upnpStream->rtp->external_port;
595                         }
596                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
597                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
598                                 stream->rtcp_port = upnpStream->rtcp->external_port;
599                         }
600                 }
601         }
602 }
603
604
605 /*
606  * uPnP Port Binding
607  */
608
609 UpnpPortBinding *upnp_port_binding_new() {
610         UpnpPortBinding *port = NULL;
611         port = ms_new0(UpnpPortBinding,1);
612         ms_mutex_init(&port->mutex, NULL);
613         port->state = LinphoneUpnpStateIdle;
614         port->local_addr[0] = '\0';
615         port->local_port = -1;
616         port->external_addr[0] = '\0';
617         port->external_port = -1;
618         port->ref = 1;
619         return port;
620 }
621
622 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
623         UpnpPortBinding *new_port = NULL;
624         new_port = ms_new0(UpnpPortBinding,1);
625         memcpy(new_port, port, sizeof(UpnpPortBinding));
626         ms_mutex_init(&new_port->mutex, NULL);
627         new_port->ref = 1;
628         return new_port;
629 }
630
631 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
632         if(strlen(port->local_addr)) {
633                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
634                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
635                                                                         port->external_port,
636                                                                         port->local_addr,
637                                                                         port->local_port);
638         } else {
639                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
640                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
641                                                                         port->external_port,
642                                                                         port->local_port);
643         }
644 }
645
646 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
647         return port1->protocol == port2->protocol &&
648                         port1->local_port == port2->local_port &&
649                         port1->external_port == port2->external_port;
650 }
651
652 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
653         ms_mutex_lock(&port->mutex);
654         port->ref++;
655         ms_mutex_unlock(&port->mutex);
656         return port;
657 }
658
659 void upnp_port_binding_release(UpnpPortBinding *port) {
660         ms_mutex_lock(&port->mutex);
661         if(--port->ref == 0) {
662                 ms_mutex_unlock(&port->mutex);
663                 ms_mutex_destroy(&port->mutex);
664                 ms_free(port);
665                 return;
666         }
667         ms_mutex_unlock(&port->mutex);
668 }
669
670
671 /*
672  * uPnP Stream
673  */
674
675 UpnpStream* upnp_stream_new() {
676         UpnpStream *stream = ms_new0(UpnpStream,1);
677         stream->state = LinphoneUpnpStateIdle;
678         stream->rtp = upnp_port_binding_new();
679         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
680         stream->rtcp = upnp_port_binding_new();
681         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
682         return stream;
683 }
684
685 void upnp_stream_destroy(UpnpStream* stream) {
686         upnp_port_binding_release(stream->rtp);
687         upnp_port_binding_release(stream->rtcp);
688         ms_free(stream);
689 }
690
691
692 /*
693  * uPnP Session
694  */
695
696 UpnpSession* upnp_session_new() {
697         UpnpSession *session = ms_new0(UpnpSession,1);
698         session->state = LinphoneUpnpStateIdle;
699         session->audio = upnp_stream_new();
700         session->video = upnp_stream_new();
701         return session;
702 }
703
704 void upnp_session_destroy(LinphoneCall* call) {
705         LinphoneCore *lc = call->core;
706
707         /* Remove bindings */
708         if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
709                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
710         }
711         if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
712                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
713         }
714         if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
715                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
716         }
717         if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
718                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
719         }
720
721         upnp_stream_destroy(call->upnp_session->audio);
722         upnp_stream_destroy(call->upnp_session->video);
723         ms_free(call->upnp_session);
724         call->upnp_session = NULL;
725 }
726
727
728 /*
729  * uPnP Config
730  */
731
732 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
733         char protocol_str[4]; // TCP or UDP
734         upnp_igd_ip_protocol protocol;
735         int external_port;
736         int local_port;
737         MSList *retList = NULL;
738         UpnpPortBinding *port;
739         bool_t valid;
740         MSList *elem;
741         MSList *prev_elem;
742         LpItem *item;
743         LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
744         if(sec == NULL)
745                 return retList;
746
747         elem = sec->items;
748         while(elem != NULL) {
749                 item=(LpItem*)elem->data;
750                 valid = TRUE;
751                 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
752                         if(strcasecmp(protocol_str, "TCP") == 0) {
753                                 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
754                         } else if(strcasecmp(protocol_str, "UDP") == 0) {
755                                 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
756                         } else {
757                                 valid = FALSE;
758                         }
759                         if(valid) {
760                                 port = upnp_port_binding_new();
761                                 port->state = LinphoneUpnpStateOk;
762                                 port->protocol = protocol;
763                                 port->external_port = external_port;
764                                 port->local_port = local_port;
765                                 retList = ms_list_append(retList, port);
766                         }
767                 } else {
768                         valid = FALSE;
769                 }
770                 prev_elem = elem;
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 }