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