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