]> sjero.net Git - linphone/blob - coreapi/upnp.c
Fix previous merge
[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 #include "lpconfig.h"
23
24 #define UPNP_ADD_MAX_RETRY 4
25 #define UPNP_REMOVE_MAX_RETRY 4
26 #define UPNP_SECTION_NAME "uPnP"
27
28 /*
29  * uPnP Definitions
30  */
31
32 typedef struct _UpnpPortBinding {
33         ms_mutex_t mutex;
34         LinphoneUpnpState state;
35         upnp_igd_ip_protocol protocol;
36         char local_addr[LINPHONE_IPADDR_SIZE];
37         int local_port;
38         char external_addr[LINPHONE_IPADDR_SIZE];
39         int external_port;
40         int retry;
41         int ref;
42         bool_t to_remove;
43         bool_t to_add;
44 } UpnpPortBinding;
45
46 typedef struct _UpnpStream {
47         UpnpPortBinding *rtp;
48         UpnpPortBinding *rtcp;
49         LinphoneUpnpState state;
50 } UpnpStream;
51
52 struct _UpnpSession {
53         LinphoneCall *call;
54         UpnpStream *audio;
55         UpnpStream *video;
56         LinphoneUpnpState state;
57 };
58
59 struct _UpnpContext {
60         LinphoneCore *lc;
61         upnp_igd_context *upnp_igd_ctxt;
62         UpnpPortBinding *sip_tcp;
63         UpnpPortBinding *sip_tls;
64         UpnpPortBinding *sip_udp;
65         LinphoneUpnpState state;
66         MSList *removing_configs;
67         MSList *adding_configs;
68         MSList *pending_bindings;
69
70         ms_mutex_t mutex;
71         ms_cond_t empty_cond;
72
73 };
74
75
76 bool_t linphone_core_upnp_hook(void *data);
77 void linphone_core_upnp_refresh(UpnpContext *ctx);
78
79 UpnpPortBinding *linphone_upnp_port_binding_new();
80 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
81 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
82 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
83 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
84 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
85 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
86
87 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
88 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
89 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
90
91 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
92 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
93
94
95 /**
96  * uPnP Callbacks
97  */
98
99 /* Convert uPnP IGD logs to ortp logs */
100 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
101         int ortp_level = ORTP_DEBUG;
102         switch(level) {
103         case UPNP_IGD_MESSAGE:
104                 ortp_level = ORTP_MESSAGE;
105                 break;
106         case UPNP_IGD_WARNING:
107                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
108                 break;
109         case UPNP_IGD_ERROR:
110                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
111                 break;
112         default:
113                 break;
114         }
115         ortp_logv(ortp_level, fmt, list);
116 }
117
118 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
119         UpnpContext *lupnp = (UpnpContext *)cookie;
120         upnp_igd_port_mapping *mapping = NULL;
121         UpnpPortBinding *port_mapping = NULL;
122         const char *ip_address = NULL;
123         const char *connection_status = NULL;
124         bool_t nat_enabled = FALSE;
125         LinphoneUpnpState old_state;
126
127         if(lupnp == NULL || lupnp->upnp_igd_ctxt == NULL) {
128                 ms_error("uPnP IGD: Invalid context in callback");
129                 return;
130         }
131
132         ms_mutex_lock(&lupnp->mutex);
133         old_state = lupnp->state;
134
135         switch(event) {
136         case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
137         case UPNP_IGD_NAT_ENABLED_CHANGED:
138         case UPNP_IGD_CONNECTION_STATUS_CHANGED:
139                 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
140                 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
141                 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
142
143                 if(ip_address == NULL || connection_status == NULL) {
144                         ms_message("uPnP IGD: Pending");
145                         lupnp->state = LinphoneUpnpStatePending;
146                 } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
147                         ms_message("uPnP IGD: Not Available");
148                         lupnp->state = LinphoneUpnpStateNotAvailable;
149                 } else {
150                         ms_message("uPnP IGD: Connected");
151                         lupnp->state = LinphoneUpnpStateOk;
152                         if(old_state != LinphoneUpnpStateOk) {
153                                 linphone_core_upnp_refresh(lupnp);
154                         }
155                 }
156
157                 break;
158
159         case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
160                 mapping = (upnp_igd_port_mapping *) arg;
161                 port_mapping = (UpnpPortBinding*) mapping->cookie;
162                 port_mapping->external_port = mapping->remote_port;
163                 port_mapping->state = LinphoneUpnpStateOk;
164                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
165                 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
166
167                 break;
168
169         case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
170                 mapping = (upnp_igd_port_mapping *) arg;
171                 port_mapping = (UpnpPortBinding*) mapping->cookie;
172                 port_mapping->external_port = -1; //Force random external port
173                 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) {
174                         linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
175                 }
176
177                 break;
178
179         case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
180                 mapping = (upnp_igd_port_mapping *) arg;
181                 port_mapping = (UpnpPortBinding*) mapping->cookie;
182                 port_mapping->state = LinphoneUpnpStateIdle;
183                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
184                 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
185
186                 break;
187
188         case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
189                 mapping = (upnp_igd_port_mapping *) arg;
190                 port_mapping = (UpnpPortBinding*) mapping->cookie;
191                 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) {
192                         linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
193                         linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
194                 }
195
196                 break;
197
198         default:
199                 break;
200         }
201
202         if(port_mapping != NULL) {
203                 /*
204                  * Execute delayed actions
205                  */
206                 if(port_mapping->to_remove) {
207                         if(port_mapping->state == LinphoneUpnpStateOk) {
208                                 port_mapping->to_remove = FALSE;
209                                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
210                         } else if(port_mapping->state == LinphoneUpnpStateKo) {
211                                 port_mapping->to_remove = FALSE;
212                         }
213                 }
214                 if(port_mapping->to_add) {
215                         if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
216                                 port_mapping->to_add = FALSE;
217                                 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
218                         }
219                 }
220
221                 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
222                 linphone_upnp_port_binding_release(port_mapping);
223         }
224
225         /*
226          * If there is no pending binding emit a signal
227          */
228         if(lupnp->pending_bindings == NULL) {
229                 pthread_cond_signal(&lupnp->empty_cond);
230         }
231         ms_mutex_unlock(&lupnp->mutex);
232 }
233
234
235 /**
236  * uPnP Context
237  */
238
239 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
240         LCSipTransports transport;
241         UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
242         const char *ip_address;
243
244         ms_mutex_init(&lupnp->mutex, NULL);
245         ms_cond_init(&lupnp->empty_cond, NULL);
246
247         lupnp->lc = lc;
248         lupnp->pending_bindings = NULL;
249         lupnp->adding_configs = NULL;
250         lupnp->removing_configs = NULL;
251         lupnp->state = LinphoneUpnpStateIdle;
252         ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
253
254         linphone_core_get_sip_transports(lc, &transport);
255         if(transport.udp_port != 0) {
256                 lupnp->sip_udp = linphone_upnp_port_binding_new();
257                 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
258                 lupnp->sip_udp->local_port = transport.udp_port;
259                 lupnp->sip_udp->external_port = transport.udp_port;
260         } else {
261                 lupnp->sip_udp = NULL;
262         }
263         if(transport.tcp_port != 0) {
264                 lupnp->sip_tcp = linphone_upnp_port_binding_new();
265                 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
266                 lupnp->sip_tcp->local_port = transport.tcp_port;
267                 lupnp->sip_tcp->external_port = transport.tcp_port;
268         } else {
269                 lupnp->sip_tcp = NULL;
270         }
271         if(transport.tls_port != 0) {
272                 lupnp->sip_tls = linphone_upnp_port_binding_new();
273                 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
274                 lupnp->sip_tls->local_port = transport.tls_port;
275                 lupnp->sip_tls->external_port = transport.tls_port;
276         } else {
277                 lupnp->sip_tls = NULL;
278         }
279
280         linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
281
282         lupnp->upnp_igd_ctxt = NULL;
283         lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
284         if(lupnp->upnp_igd_ctxt == NULL) {
285                 lupnp->state = LinphoneUpnpStateKo;
286                 ms_error("Can't create uPnP IGD context");
287                 return NULL;
288         }
289
290         ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
291         if(lupnp->sip_udp != NULL) {
292                 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
293         }
294         if(lupnp->sip_tcp != NULL) {
295                 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
296         }
297         if(lupnp->sip_tls != NULL) {
298                 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
299         }
300
301         lupnp->state = LinphoneUpnpStatePending;
302         upnp_igd_start(lupnp->upnp_igd_ctxt);
303
304         return lupnp;
305 }
306
307 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
308         /*
309          * Not need, all hooks are removed before
310          * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
311          */
312
313         ms_mutex_lock(&lupnp->mutex);
314         
315         /* Send port binding removes */
316         if(lupnp->sip_udp != NULL) {
317                 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp);
318                 lupnp->sip_udp = NULL;
319         }
320         if(lupnp->sip_tcp != NULL) {
321                 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp);
322                 lupnp->sip_tcp = NULL;
323         }
324         if(lupnp->sip_tls != NULL) {
325                 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls);
326                 lupnp->sip_tcp = NULL;
327         }
328
329         /* Wait all pending bindings are done */
330         if(lupnp->pending_bindings != NULL) {
331                 ms_message("uPnP IGD: Wait all pending port bindings ...");
332                 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
333         }
334         ms_mutex_unlock(&lupnp->mutex);
335
336         if(lupnp->upnp_igd_ctxt != NULL) {
337                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
338         }
339
340         /* Run one time the hook for configuration update */
341         linphone_core_upnp_hook(lupnp);
342
343         /* Release port bindings */
344         if(lupnp->sip_udp != NULL) {
345                 linphone_upnp_port_binding_release(lupnp->sip_udp);
346                 lupnp->sip_udp = NULL;
347         }
348         if(lupnp->sip_tcp != NULL) {
349                 linphone_upnp_port_binding_release(lupnp->sip_tcp);
350                 lupnp->sip_tcp = NULL;
351         }
352         if(lupnp->sip_tls != NULL) {
353                 linphone_upnp_port_binding_release(lupnp->sip_tls);
354                 lupnp->sip_tcp = NULL;
355         }
356
357         /* Release lists */
358         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
359         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
360         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
361         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
362         ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
363         lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
364
365         ms_mutex_destroy(&lupnp->mutex);
366         ms_cond_destroy(&lupnp->empty_cond);
367
368         ms_message("uPnP IGD: destroy %p", lupnp);
369         ms_free(lupnp);
370 }
371
372 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) {
373         return ctx->state;
374 }
375
376 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) {
377         return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
378 }
379
380 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
381         upnp_igd_port_mapping mapping;
382         char description[128];
383         int ret;
384
385         // Compute port binding state
386         if(port->state != LinphoneUpnpStateAdding) {
387                 port->to_remove = FALSE;
388                 switch(port->state) {
389                         case LinphoneUpnpStateKo:
390                         case LinphoneUpnpStateIdle: {
391                                 port->retry = 0;
392                                 port->state = LinphoneUpnpStateAdding;
393                         }
394                         break;
395                         case LinphoneUpnpStateRemoving: {
396                                 port->to_add = TRUE;
397                                 return 0;
398                         }
399                         break;
400                         default:
401                                 return 0;
402                 }
403         }
404
405         if(port->retry >= UPNP_ADD_MAX_RETRY) {
406                 ret = -1;
407         } else {
408                 mapping.cookie = linphone_upnp_port_binding_retain(port);
409                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
410
411                 mapping.local_port = port->local_port;
412                 mapping.local_host = port->local_addr;
413                 if(port->external_port == -1)
414                         mapping.remote_port = rand()%(0xffff - 1024) + 1024;
415                 else
416                         mapping.remote_port = port->external_port;
417                 mapping.remote_host = "";
418                 snprintf(description, 128, "%s %s at %s:%d",
419                                 PACKAGE_NAME,
420                                 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
421                                 port->local_addr, port->local_port);
422                 mapping.description = description;
423                 mapping.protocol = port->protocol;
424
425                 port->retry++;
426                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
427                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
428         }
429         if(ret != 0) {
430                 port->state = LinphoneUpnpStateKo;
431         }
432         return ret;
433 }
434
435 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
436         upnp_igd_port_mapping mapping;
437         int ret;
438
439         // Compute port binding state
440         if(port->state != LinphoneUpnpStateRemoving) {
441                 port->to_add = FALSE;
442                 switch(port->state) {
443                         case LinphoneUpnpStateOk: {
444                                 port->retry = 0;
445                                 port->state = LinphoneUpnpStateRemoving;
446                         }
447                         break;
448                         case LinphoneUpnpStateAdding: {
449                                 port->to_remove = TRUE;
450                                 return 0;
451                         }
452                         break;
453                         default:
454                                 return 0;
455                 }
456         }
457
458         if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
459                 ret = -1;
460         } else {
461                 mapping.cookie = linphone_upnp_port_binding_retain(port);
462                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
463
464                 mapping.remote_port = port->external_port;
465                 mapping.remote_host = "";
466                 mapping.protocol = port->protocol;
467                 port->retry++;
468                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
469                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
470         }
471         if(ret != 0) {
472                 port->state = LinphoneUpnpStateKo;
473         }
474         return ret;
475 }
476
477 /*
478  * uPnP Core interfaces
479  */
480
481 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
482         LinphoneCore *lc = call->core;
483         UpnpContext *lupnp = lc->upnp;
484         int ret = -1;
485         const char *local_addr, *external_addr;
486
487         if(lupnp == NULL) {
488                 return ret;
489         }
490
491         ms_mutex_lock(&lupnp->mutex);
492         // Don't handle when the call
493         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
494                 ret = 0;
495                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
496                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
497
498                 /*
499                  * Audio part
500                  */
501                 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
502                 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
503                 call->upnp_session->audio->rtp->local_port = call->audio_port;
504                 if(call->upnp_session->audio->rtp->external_port == -1) {
505                         call->upnp_session->audio->rtp->external_port = call->audio_port;
506                 }
507                 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
508                 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
509                 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
510                 if(call->upnp_session->audio->rtcp->external_port == -1) {
511                         call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
512                 }
513                 if(audio) {
514                         // Add audio port binding
515                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
516                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
517                 } else {
518                         // Remove audio port binding
519                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
520                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
521                 }
522
523                 /*
524                  * Video part
525                  */
526                 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
527                 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
528                 call->upnp_session->video->rtp->local_port = call->video_port;
529                 if(call->upnp_session->video->rtp->external_port == -1) {
530                         call->upnp_session->video->rtp->external_port = call->video_port;
531                 }
532                 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
533                 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
534                 call->upnp_session->video->rtcp->local_port = call->video_port+1;
535                 if(call->upnp_session->video->rtcp->external_port == -1) {
536                         call->upnp_session->video->rtcp->external_port = call->video_port+1;
537                 }
538                 if(video) {
539                         // Add video port binding
540                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
541                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
542                 } else {
543                         // Remove video port binding
544                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
545                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
546                 }
547         }
548
549         ms_mutex_unlock(&lupnp->mutex);
550
551         /*
552          * Update uPnP call state
553          */
554         linphone_upnp_call_process(call);
555
556         return ret;
557 }
558
559
560 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
561         bool_t audio = FALSE;
562         bool_t video = FALSE;
563         int i;
564         const SalStreamDescription *stream;
565
566         for (i = 0; i < md->n_total_streams; i++) {
567                 stream = &md->streams[i];
568                 if(stream->type == SalAudio) {
569                         audio = TRUE;
570                 } else if(stream->type == SalVideo) {
571                         video = TRUE;
572                 }
573         }
574
575         return linphone_core_update_upnp_audio_video(call, audio, video);
576 }
577
578 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
579         return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
580 }
581
582 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
583         call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
584         call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
585 }
586
587 int linphone_upnp_call_process(LinphoneCall *call) {
588         LinphoneCore *lc = call->core;
589         UpnpContext *lupnp = lc->upnp;
590         int ret = -1;
591         LinphoneUpnpState oldState = 0, newState = 0;
592
593         if(lupnp == NULL) {
594                 return ret;
595         }
596
597         ms_mutex_lock(&lupnp->mutex);
598
599         // Don't handle when the call
600         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
601                 ret = 0;
602
603                 /*
604                  * Update Audio state
605                  */
606                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
607                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
608                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
609                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
610                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
611                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
612                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
613                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
614                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
615                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
616                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
617                 } else {
618                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
619                 }
620
621                 /*
622                  * Update Video state
623                  */
624                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
625                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
626                         call->upnp_session->video->state = LinphoneUpnpStateOk;
627                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
628                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
629                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
630                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
631                         call->upnp_session->video->state = LinphoneUpnpStatePending;
632                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
633                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
634                         call->upnp_session->video->state = LinphoneUpnpStateKo;
635                 } else {
636                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
637                 }
638
639                 /*
640                  * Update session state
641                  */
642                 oldState = call->upnp_session->state;
643                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
644                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
645                         call->upnp_session->state = LinphoneUpnpStateOk;
646                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
647                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
648                         call->upnp_session->state = LinphoneUpnpStatePending;
649                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
650                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
651                         call->upnp_session->state = LinphoneUpnpStateKo;
652                 } else {
653                         call->upnp_session->state = LinphoneUpnpStateIdle;
654                 }
655                 newState = call->upnp_session->state;
656
657                 /* When change is done proceed update */
658                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
659                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
660                         if(call->upnp_session->state == LinphoneUpnpStateOk)
661                                 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
662                         else
663                                 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
664
665                         switch (call->state) {
666                                 case LinphoneCallUpdating:
667                                         linphone_core_start_update_call(lc, call);
668                                         break;
669                                 case LinphoneCallUpdatedByRemote:
670                                         linphone_core_start_accept_call_update(lc, call);
671                                         break;
672                                 case LinphoneCallOutgoingInit:
673                                         linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
674                                         break;
675                                 case LinphoneCallIdle:
676                                         linphone_core_notify_incoming_call(lc, call);
677                                         break;
678                                 default:
679                                         break;
680                         }
681                 }
682         }
683
684         ms_mutex_unlock(&lupnp->mutex);
685
686         /*
687          * Update uPnP call stats
688          */
689         if(oldState != newState) {
690                 linphone_core_update_upnp_state_in_call_stats(call);
691         }
692
693         return ret;
694 }
695
696 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
697         MSList *global_list = NULL;
698         MSList *list = NULL;
699         MSList *item;
700         LinphoneCall *call;
701         UpnpPortBinding *port_mapping, *port_mapping2;
702
703         ms_message("uPnP IGD: Refresh mappings");
704
705         /* Remove context port bindings */
706         if(lupnp->sip_udp != NULL) {
707                 global_list = ms_list_append(global_list, lupnp->sip_udp);
708         }
709         if(lupnp->sip_tcp != NULL) {
710                 global_list = ms_list_append(global_list, lupnp->sip_tcp);
711         }
712         if(lupnp->sip_tls != NULL) {
713                 global_list = ms_list_append(global_list, lupnp->sip_tls);
714         }
715
716         /* Remove call port bindings */
717         list = lupnp->lc->calls;
718         while(list != NULL) {
719                 call = (LinphoneCall *)list->data;
720                 if(call->upnp_session != NULL) {
721                         global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
722                         global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
723                         global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
724                         global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
725                 }
726                 list = list->next;
727         }
728
729         // Remove port binding configurations
730         list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
731         for(item = list;item != NULL; item = item->next) {
732                         port_mapping = (UpnpPortBinding *)item->data;
733                         port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
734                         if(port_mapping2 == NULL) {
735                                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
736                         } else if(port_mapping2->state == LinphoneUpnpStateIdle){
737                                 /* Force to remove */
738                                 port_mapping2->state = LinphoneUpnpStateOk;
739                         }
740         }
741         ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
742         list = ms_list_free(list);
743
744
745         // (Re)Add removed port bindings
746         list = global_list;
747         while(list != NULL) {
748                 port_mapping = (UpnpPortBinding *)list->data;
749                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
750                 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
751                 list = list->next;
752         }
753         global_list = ms_list_free(global_list);
754 }
755
756 bool_t linphone_core_upnp_hook(void *data) {
757         char key[64];
758         MSList *item;
759         UpnpPortBinding *port_mapping;
760         UpnpContext *lupnp = (UpnpContext *)data;
761         ms_mutex_lock(&lupnp->mutex);
762
763         /* Add configs */
764         for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
765                 port_mapping = (UpnpPortBinding *)item->data;
766                 snprintf(key, sizeof(key), "%s-%d-%d",
767                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
768                                                         port_mapping->external_port,
769                                                         port_mapping->local_port);
770                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
771                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
772         }
773         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
774         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
775
776         /* Remove configs */
777         for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
778                 port_mapping = (UpnpPortBinding *)item->data;
779                 snprintf(key, sizeof(key), "%s-%d-%d",
780                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
781                                                         port_mapping->external_port,
782                                                         port_mapping->local_port);
783                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
784                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
785         }
786         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
787         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
788
789         ms_mutex_unlock(&lupnp->mutex);
790         return TRUE;
791 }
792
793 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
794         int i;
795         SalStreamDescription *stream;
796         UpnpStream *upnpStream;
797
798         for (i = 0; i < desc->n_active_streams; i++) {
799                 stream = &desc->streams[i];
800                 upnpStream = NULL;
801                 if(stream->type == SalAudio) {
802                         upnpStream = session->audio;
803                 } else if(stream->type == SalVideo) {
804                         upnpStream = session->video;
805                 }
806                 if(upnpStream != NULL) {
807                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
808                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
809                                 stream->rtp_port = upnpStream->rtp->external_port;
810                         }
811                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
812                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
813                                 stream->rtcp_port = upnpStream->rtcp->external_port;
814                         }
815                 }
816         }
817         return 0;
818 }
819
820
821 /*
822  * uPnP Port Binding
823  */
824
825 UpnpPortBinding *linphone_upnp_port_binding_new() {
826         UpnpPortBinding *port = NULL;
827         port = ms_new0(UpnpPortBinding,1);
828         ms_mutex_init(&port->mutex, NULL);
829         port->state = LinphoneUpnpStateIdle;
830         port->local_addr[0] = '\0';
831         port->local_port = -1;
832         port->external_addr[0] = '\0';
833         port->external_port = -1;
834         port->to_remove = FALSE;
835         port->to_add = FALSE;
836         port->ref = 1;
837         return port;
838 }
839
840 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
841         UpnpPortBinding *new_port = NULL;
842         new_port = ms_new0(UpnpPortBinding,1);
843         memcpy(new_port, port, sizeof(UpnpPortBinding));
844         ms_mutex_init(&new_port->mutex, NULL);
845         new_port->ref = 1;
846         return new_port;
847 }
848
849 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
850         if(strlen(port->local_addr)) {
851                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
852                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
853                                                                         port->external_port,
854                                                                         port->local_addr,
855                                                                         port->local_port);
856         } else {
857                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
858                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
859                                                                         port->external_port,
860                                                                         port->local_port);
861         }
862 }
863
864 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
865         return port1->protocol == port2->protocol &&
866                         port1->local_port == port2->local_port &&
867                         port1->external_port == port2->external_port;
868 }
869
870 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
871         UpnpPortBinding *port_mapping;
872         while(list != NULL) {
873                 port_mapping = (UpnpPortBinding *)list->data;
874                 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
875                         return port_mapping;
876                 }
877                 list = list->next;
878         }
879
880         return NULL;
881 }
882
883 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
884         ms_mutex_lock(&port->mutex);
885         port->ref++;
886         ms_mutex_unlock(&port->mutex);
887         return port;
888 }
889
890 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
891         ms_mutex_lock(&port->mutex);
892         if(--port->ref == 0) {
893                 ms_mutex_unlock(&port->mutex);
894                 ms_mutex_destroy(&port->mutex);
895                 ms_free(port);
896                 return;
897         }
898         ms_mutex_unlock(&port->mutex);
899 }
900
901
902 /*
903  * uPnP Stream
904  */
905
906 UpnpStream* linphone_upnp_stream_new() {
907         UpnpStream *stream = ms_new0(UpnpStream,1);
908         stream->state = LinphoneUpnpStateIdle;
909         stream->rtp = linphone_upnp_port_binding_new();
910         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
911         stream->rtcp = linphone_upnp_port_binding_new();
912         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
913         return stream;
914 }
915
916 void linphone_upnp_stream_destroy(UpnpStream* stream) {
917         linphone_upnp_port_binding_release(stream->rtp);
918         stream->rtp = NULL;
919         linphone_upnp_port_binding_release(stream->rtcp);
920         stream->rtcp = NULL;
921         ms_free(stream);
922 }
923
924
925 /*
926  * uPnP Session
927  */
928
929 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
930         UpnpSession *session = ms_new0(UpnpSession,1);
931         session->call = call;
932         session->state = LinphoneUpnpStateIdle;
933         session->audio = linphone_upnp_stream_new();
934         session->video = linphone_upnp_stream_new();
935         return session;
936 }
937
938 void linphone_upnp_session_destroy(UpnpSession *session) {
939         LinphoneCore *lc = session->call->core;
940
941         if(lc->upnp != NULL) {
942                 /* Remove bindings */
943                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
944                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
945                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
946                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
947         }
948
949         linphone_upnp_stream_destroy(session->audio);
950         linphone_upnp_stream_destroy(session->video);
951         ms_free(session);
952 }
953
954 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
955         return session->state;
956 }
957
958 /*
959  * uPnP Config
960  */
961
962 struct linphone_upnp_config_list_port_bindings_struct {
963         struct _LpConfig *lpc;
964         MSList *retList;
965 };
966
967 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
968         char protocol_str[4]; // TCP or UDP
969         upnp_igd_ip_protocol protocol;
970         int external_port;
971         int local_port;
972         bool_t valid = TRUE;
973         UpnpPortBinding *port;
974         if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
975                 if(strcasecmp(protocol_str, "TCP") == 0) {
976                         protocol = UPNP_IGD_IP_PROTOCOL_TCP;
977                 } else if(strcasecmp(protocol_str, "UDP") == 0) {
978                         protocol = UPNP_IGD_IP_PROTOCOL_UDP;
979                 } else {
980                         valid = FALSE;
981                 }
982                 if(valid) {
983                         port = linphone_upnp_port_binding_new();
984                         port->state = LinphoneUpnpStateOk;
985                         port->protocol = protocol;
986                         port->external_port = external_port;
987                         port->local_port = local_port;
988                         cookie->retList = ms_list_append(cookie->retList, port);
989                 }
990         } else {
991                 valid = FALSE;
992         }
993         if(!valid) {
994                 ms_warning("uPnP configuration invalid line: %s", entry);
995         }
996 }
997
998 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
999         struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
1000         lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
1001         return cookie.retList;
1002 }
1003
1004 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1005         MSList *list;
1006         UpnpPortBinding *list_port;
1007
1008         list = lupnp->removing_configs;
1009         while(list != NULL) {
1010                 list_port = (UpnpPortBinding *)list->data;
1011                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1012                         lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1013                         linphone_upnp_port_binding_release(list_port);
1014                         return;
1015                 }
1016                 list = ms_list_next(list);
1017         }
1018
1019         list = lupnp->adding_configs;
1020         while(list != NULL) {
1021                 list_port = (UpnpPortBinding *)list->data;
1022                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1023                         return;
1024                 }
1025                 list = ms_list_next(list);
1026         }
1027
1028         list_port = linphone_upnp_port_binding_copy(port);
1029         lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1030 }
1031
1032 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1033         MSList *list;
1034         UpnpPortBinding *list_port;
1035
1036         list = lupnp->adding_configs;
1037         while(list != NULL) {
1038                 list_port = (UpnpPortBinding *)list->data;
1039                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1040                         lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1041                         linphone_upnp_port_binding_release(list_port);
1042                         return;
1043                 }
1044                 list = ms_list_next(list);
1045         }
1046
1047         list = lupnp->removing_configs;
1048         while(list != NULL) {
1049                 list_port = (UpnpPortBinding *)list->data;
1050                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1051                         return;
1052                 }
1053                 list = ms_list_next(list);
1054         }
1055
1056         list_port = linphone_upnp_port_binding_copy(port);
1057         lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);
1058 }