]> sjero.net Git - linphone/blob - coreapi/upnp.c
Fix uPnP: Better handling of protocol
[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         /* Send port binding removes */
314         if(lupnp->sip_udp != NULL) {
315                 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp);
316                 lupnp->sip_udp = NULL;
317         }
318         if(lupnp->sip_tcp != NULL) {
319                 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp);
320                 lupnp->sip_tcp = NULL;
321         }
322         if(lupnp->sip_tls != NULL) {
323                 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls);
324                 lupnp->sip_tcp = NULL;
325         }
326
327         /* Wait all pending bindings are done */
328         ms_message("uPnP IGD: Wait all pending port bindings ...");
329         ms_mutex_lock(&lupnp->mutex);
330         ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
331         ms_mutex_unlock(&lupnp->mutex);
332
333         if(lupnp->upnp_igd_ctxt != NULL) {
334                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
335         }
336
337         /* Run one time the hook for configuration update */
338         linphone_core_upnp_hook(lupnp);
339
340         /* Release port bindings */
341         if(lupnp->sip_udp != NULL) {
342                 linphone_upnp_port_binding_release(lupnp->sip_udp);
343                 lupnp->sip_udp = NULL;
344         }
345         if(lupnp->sip_tcp != NULL) {
346                 linphone_upnp_port_binding_release(lupnp->sip_tcp);
347                 lupnp->sip_tcp = NULL;
348         }
349         if(lupnp->sip_tls != NULL) {
350                 linphone_upnp_port_binding_release(lupnp->sip_tls);
351                 lupnp->sip_tcp = NULL;
352         }
353
354         /* Release lists */
355         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
356         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
357         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
358         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
359         ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
360         lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
361
362         ms_mutex_destroy(&lupnp->mutex);
363         ms_cond_destroy(&lupnp->empty_cond);
364
365         ms_message("uPnP IGD: destroy %p", lupnp);
366         ms_free(lupnp);
367 }
368
369 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) {
370         return ctx->state;
371 }
372
373 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) {
374         return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
375 }
376
377 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
378         upnp_igd_port_mapping mapping;
379         char description[128];
380         int ret;
381
382         // Compute port binding state
383         if(port->state != LinphoneUpnpStateAdding) {
384                 port->to_remove = FALSE;
385                 switch(port->state) {
386                         case LinphoneUpnpStateKo:
387                         case LinphoneUpnpStateIdle: {
388                                 port->retry = 0;
389                                 port->state = LinphoneUpnpStateAdding;
390                         }
391                         break;
392                         case LinphoneUpnpStateRemoving: {
393                                 port->to_add = TRUE;
394                                 return 0;
395                         }
396                         break;
397                         default:
398                                 return 0;
399                 }
400         }
401
402         if(port->retry >= UPNP_ADD_MAX_RETRY) {
403                 ret = -1;
404         } else {
405                 mapping.cookie = linphone_upnp_port_binding_retain(port);
406                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
407
408                 mapping.local_port = port->local_port;
409                 mapping.local_host = port->local_addr;
410                 if(port->external_port == -1)
411                         mapping.remote_port = rand()%(0xffff - 1024) + 1024;
412                 else
413                         mapping.remote_port = port->external_port;
414                 mapping.remote_host = "";
415                 snprintf(description, 128, "%s %s at %s:%d",
416                                 PACKAGE_NAME,
417                                 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
418                                 port->local_addr, port->local_port);
419                 mapping.description = description;
420                 mapping.protocol = port->protocol;
421
422                 port->retry++;
423                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
424                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
425         }
426         if(ret != 0) {
427                 port->state = LinphoneUpnpStateKo;
428         }
429         return ret;
430 }
431
432 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
433         upnp_igd_port_mapping mapping;
434         int ret;
435
436         // Compute port binding state
437         if(port->state != LinphoneUpnpStateRemoving) {
438                 port->to_add = FALSE;
439                 switch(port->state) {
440                         case LinphoneUpnpStateOk: {
441                                 port->retry = 0;
442                                 port->state = LinphoneUpnpStateRemoving;
443                         }
444                         break;
445                         case LinphoneUpnpStateAdding: {
446                                 port->to_remove = TRUE;
447                                 return 0;
448                         }
449                         break;
450                         default:
451                                 return 0;
452                 }
453         }
454
455         if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
456                 ret = -1;
457         } else {
458                 mapping.cookie = linphone_upnp_port_binding_retain(port);
459                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
460
461                 mapping.remote_port = port->external_port;
462                 mapping.remote_host = "";
463                 mapping.protocol = port->protocol;
464                 port->retry++;
465                 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
466                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
467         }
468         if(ret != 0) {
469                 port->state = LinphoneUpnpStateKo;
470         }
471         return ret;
472 }
473
474 /*
475  * uPnP Core interfaces
476  */
477
478 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
479         LinphoneCore *lc = call->core;
480         UpnpContext *lupnp = lc->upnp;
481         int ret = -1;
482         const char *local_addr, *external_addr;
483
484         if(lupnp == NULL) {
485                 return ret;
486         }
487
488         ms_mutex_lock(&lupnp->mutex);
489         // Don't handle when the call
490         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
491                 ret = 0;
492                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
493                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
494
495                 /*
496                  * Audio part
497                  */
498                 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
499                 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
500                 call->upnp_session->audio->rtp->local_port = call->audio_port;
501                 if(call->upnp_session->audio->rtp->external_port == -1) {
502                         call->upnp_session->audio->rtp->external_port = call->audio_port;
503                 }
504                 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
505                 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
506                 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
507                 if(call->upnp_session->audio->rtcp->external_port == -1) {
508                         call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
509                 }
510                 if(audio) {
511                         // Add audio port binding
512                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
513                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
514                 } else {
515                         // Remove audio port binding
516                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
517                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
518                 }
519
520                 /*
521                  * Video part
522                  */
523                 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
524                 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
525                 call->upnp_session->video->rtp->local_port = call->video_port;
526                 if(call->upnp_session->video->rtp->external_port == -1) {
527                         call->upnp_session->video->rtp->external_port = call->video_port;
528                 }
529                 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
530                 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
531                 call->upnp_session->video->rtcp->local_port = call->video_port+1;
532                 if(call->upnp_session->video->rtcp->external_port == -1) {
533                         call->upnp_session->video->rtcp->external_port = call->video_port+1;
534                 }
535                 if(video) {
536                         // Add video port binding
537                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
538                         linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
539                 } else {
540                         // Remove video port binding
541                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
542                         linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
543                 }
544         }
545
546         ms_mutex_unlock(&lupnp->mutex);
547
548         /*
549          * Update uPnP call state
550          */
551         linphone_upnp_call_process(call);
552
553         return ret;
554 }
555
556
557 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
558         bool_t audio = FALSE;
559         bool_t video = FALSE;
560         int i;
561         const SalStreamDescription *stream;
562
563         for (i = 0; i < md->nstreams; i++) {
564                 stream = &md->streams[i];
565                 if(stream->type == SalAudio) {
566                         audio = TRUE;
567                 } else if(stream->type == SalVideo) {
568                         video = TRUE;
569                 }
570         }
571
572         return linphone_core_update_upnp_audio_video(call, audio, video);
573 }
574
575 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
576         return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
577 }
578
579 int linphone_upnp_call_process(LinphoneCall *call) {
580         LinphoneCore *lc = call->core;
581         UpnpContext *lupnp = lc->upnp;
582         int ret = -1;
583         LinphoneUpnpState oldState;
584
585         if(lupnp == NULL) {
586                 return ret;
587         }
588
589         ms_mutex_lock(&lupnp->mutex);
590
591         // Don't handle when the call
592         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
593                 ret = 0;
594
595                 /*
596                  * Update Audio state
597                  */
598                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
599                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
600                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
601                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
602                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
603                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
604                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
605                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
606                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
607                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
608                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
609                 } else {
610                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
611                 }
612
613                 /*
614                  * Update Video state
615                  */
616                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
617                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
618                         call->upnp_session->video->state = LinphoneUpnpStateOk;
619                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
620                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
621                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
622                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
623                         call->upnp_session->video->state = LinphoneUpnpStatePending;
624                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
625                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
626                         call->upnp_session->video->state = LinphoneUpnpStateKo;
627                 } else {
628                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
629                 }
630
631                 /*
632                  * Update session state
633                  */
634                 oldState = call->upnp_session->state;
635                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
636                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
637                         call->upnp_session->state = LinphoneUpnpStateOk;
638                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
639                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
640                         call->upnp_session->state = LinphoneUpnpStatePending;
641                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
642                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
643                         call->upnp_session->state = LinphoneUpnpStateKo;
644                 } else {
645                         call->upnp_session->state = LinphoneUpnpStateIdle;
646                 }
647
648                 /* When change is done proceed update */
649                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
650                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
651                         if(call->upnp_session->state == LinphoneUpnpStateOk)
652                                 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
653                         else
654                                 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
655
656                         switch (call->state) {
657                                 case LinphoneCallUpdating:
658                                         linphone_core_start_update_call(lc, call);
659                                         break;
660                                 case LinphoneCallUpdatedByRemote:
661                                         linphone_core_start_accept_call_update(lc, call);
662                                         break;
663                                 case LinphoneCallOutgoingInit:
664                                         linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
665                                         break;
666                                 case LinphoneCallIdle:
667                                         linphone_core_notify_incoming_call(lc, call);
668                                         break;
669                                 default:
670                                         break;
671                         }
672                 }
673         }
674
675         ms_mutex_unlock(&lupnp->mutex);
676         return ret;
677 }
678
679 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
680         MSList *global_list = NULL;
681         MSList *list = NULL;
682         MSList *item;
683         LinphoneCall *call;
684         UpnpPortBinding *port_mapping, *port_mapping2;
685
686         ms_message("uPnP IGD: Refresh mappings");
687
688         /* Remove context port bindings */
689         if(lupnp->sip_udp != NULL) {
690                 global_list = ms_list_append(global_list, lupnp->sip_udp);
691         }
692         if(lupnp->sip_tcp != NULL) {
693                 global_list = ms_list_append(global_list, lupnp->sip_tcp);
694         }
695         if(lupnp->sip_tls != NULL) {
696                 global_list = ms_list_append(global_list, lupnp->sip_tls);
697         }
698
699         /* Remove call port bindings */
700         list = lupnp->lc->calls;
701         while(list != NULL) {
702                 call = (LinphoneCall *)list->data;
703                 if(call->upnp_session != NULL) {
704                         global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
705                         global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
706                         global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
707                         global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
708                 }
709                 list = list->next;
710         }
711
712         // Remove port binding configurations
713         list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
714         for(item = list;item != NULL; item = item->next) {
715                         port_mapping = (UpnpPortBinding *)item->data;
716                         port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
717                         if(port_mapping2 == NULL) {
718                                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
719                         } else if(port_mapping2->state == LinphoneUpnpStateIdle){
720                                 /* Force to remove */
721                                 port_mapping2->state = LinphoneUpnpStateOk;
722                         }
723         }
724         ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
725         list = ms_list_free(list);
726
727
728         // (Re)Add removed port bindings
729         list = global_list;
730         while(list != NULL) {
731                 port_mapping = (UpnpPortBinding *)list->data;
732                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
733                 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
734                 list = list->next;
735         }
736         global_list = ms_list_free(global_list);
737 }
738
739 bool_t linphone_core_upnp_hook(void *data) {
740         char key[64];
741         MSList *item;
742         UpnpPortBinding *port_mapping;
743         UpnpContext *lupnp = (UpnpContext *)data;
744         ms_mutex_lock(&lupnp->mutex);
745
746         /* Add configs */
747         for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
748                 port_mapping = (UpnpPortBinding *)item->data;
749                 snprintf(key, sizeof(key), "%s-%d-%d",
750                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
751                                                         port_mapping->external_port,
752                                                         port_mapping->local_port);
753                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
754                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
755         }
756         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
757         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
758
759         /* Remove configs */
760         for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
761                 port_mapping = (UpnpPortBinding *)item->data;
762                 snprintf(key, sizeof(key), "%s-%d-%d",
763                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
764                                                         port_mapping->external_port,
765                                                         port_mapping->local_port);
766                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
767                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
768         }
769         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
770         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
771
772         ms_mutex_unlock(&lupnp->mutex);
773         return TRUE;
774 }
775
776 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
777         int i;
778         SalStreamDescription *stream;
779         UpnpStream *upnpStream;
780
781         for (i = 0; i < desc->nstreams; i++) {
782                 stream = &desc->streams[i];
783                 upnpStream = NULL;
784                 if(stream->type == SalAudio) {
785                         upnpStream = session->audio;
786                 } else if(stream->type == SalVideo) {
787                         upnpStream = session->video;
788                 }
789                 if(upnpStream != NULL) {
790                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
791                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
792                                 stream->rtp_port = upnpStream->rtp->external_port;
793                         }
794                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
795                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
796                                 stream->rtcp_port = upnpStream->rtcp->external_port;
797                         }
798                 }
799         }
800         return 0;
801 }
802
803
804 /*
805  * uPnP Port Binding
806  */
807
808 UpnpPortBinding *linphone_upnp_port_binding_new() {
809         UpnpPortBinding *port = NULL;
810         port = ms_new0(UpnpPortBinding,1);
811         ms_mutex_init(&port->mutex, NULL);
812         port->state = LinphoneUpnpStateIdle;
813         port->local_addr[0] = '\0';
814         port->local_port = -1;
815         port->external_addr[0] = '\0';
816         port->external_port = -1;
817         port->to_remove = FALSE;
818         port->to_add = FALSE;
819         port->ref = 1;
820         return port;
821 }
822
823 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
824         UpnpPortBinding *new_port = NULL;
825         new_port = ms_new0(UpnpPortBinding,1);
826         memcpy(new_port, port, sizeof(UpnpPortBinding));
827         ms_mutex_init(&new_port->mutex, NULL);
828         new_port->ref = 1;
829         return new_port;
830 }
831
832 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
833         if(strlen(port->local_addr)) {
834                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
835                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
836                                                                         port->external_port,
837                                                                         port->local_addr,
838                                                                         port->local_port);
839         } else {
840                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
841                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
842                                                                         port->external_port,
843                                                                         port->local_port);
844         }
845 }
846
847 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
848         return port1->protocol == port2->protocol &&
849                         port1->local_port == port2->local_port &&
850                         port1->external_port == port2->external_port;
851 }
852
853 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
854         UpnpPortBinding *port_mapping;
855         while(list != NULL) {
856                 port_mapping = (UpnpPortBinding *)list->data;
857                 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
858                         return port_mapping;
859                 }
860                 list = list->next;
861         }
862
863         return NULL;
864 }
865
866 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
867         ms_mutex_lock(&port->mutex);
868         port->ref++;
869         ms_mutex_unlock(&port->mutex);
870         return port;
871 }
872
873 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
874         ms_mutex_lock(&port->mutex);
875         if(--port->ref == 0) {
876                 ms_mutex_unlock(&port->mutex);
877                 ms_mutex_destroy(&port->mutex);
878                 ms_free(port);
879                 return;
880         }
881         ms_mutex_unlock(&port->mutex);
882 }
883
884
885 /*
886  * uPnP Stream
887  */
888
889 UpnpStream* linphone_upnp_stream_new() {
890         UpnpStream *stream = ms_new0(UpnpStream,1);
891         stream->state = LinphoneUpnpStateIdle;
892         stream->rtp = linphone_upnp_port_binding_new();
893         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
894         stream->rtcp = linphone_upnp_port_binding_new();
895         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
896         return stream;
897 }
898
899 void linphone_upnp_stream_destroy(UpnpStream* stream) {
900         linphone_upnp_port_binding_release(stream->rtp);
901         stream->rtp = NULL;
902         linphone_upnp_port_binding_release(stream->rtcp);
903         stream->rtcp = NULL;
904         ms_free(stream);
905 }
906
907
908 /*
909  * uPnP Session
910  */
911
912 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
913         UpnpSession *session = ms_new0(UpnpSession,1);
914         session->call = call;
915         session->state = LinphoneUpnpStateIdle;
916         session->audio = linphone_upnp_stream_new();
917         session->video = linphone_upnp_stream_new();
918         return session;
919 }
920
921 void linphone_upnp_session_destroy(UpnpSession *session) {
922         LinphoneCore *lc = session->call->core;
923
924         if(lc->upnp != NULL) {
925                 /* Remove bindings */
926                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
927                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
928                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
929                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
930         }
931
932         linphone_upnp_stream_destroy(session->audio);
933         linphone_upnp_stream_destroy(session->video);
934         ms_free(session);
935 }
936
937 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
938         return session->state;
939 }
940
941 /*
942  * uPnP Config
943  */
944
945 struct linphone_upnp_config_list_port_bindings_struct {
946         struct _LpConfig *lpc;
947         MSList *retList;
948 };
949
950 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
951         char protocol_str[4]; // TCP or UDP
952         upnp_igd_ip_protocol protocol;
953         int external_port;
954         int local_port;
955         bool_t valid = TRUE;
956         UpnpPortBinding *port;
957         if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
958                 if(strcasecmp(protocol_str, "TCP") == 0) {
959                         protocol = UPNP_IGD_IP_PROTOCOL_TCP;
960                 } else if(strcasecmp(protocol_str, "UDP") == 0) {
961                         protocol = UPNP_IGD_IP_PROTOCOL_UDP;
962                 } else {
963                         valid = FALSE;
964                 }
965                 if(valid) {
966                         port = linphone_upnp_port_binding_new();
967                         port->state = LinphoneUpnpStateOk;
968                         port->protocol = protocol;
969                         port->external_port = external_port;
970                         port->local_port = local_port;
971                         cookie->retList = ms_list_append(cookie->retList, port);
972                 }
973         } else {
974                 valid = FALSE;
975         }
976         if(!valid) {
977                 ms_warning("uPnP configuration invalid line: %s", entry);
978         }
979 }
980
981 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
982         struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
983         lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
984         return cookie.retList;
985 }
986
987 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
988         MSList *list;
989         UpnpPortBinding *list_port;
990
991         list = lupnp->removing_configs;
992         while(list != NULL) {
993                 list_port = (UpnpPortBinding *)list->data;
994                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
995                         lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
996                         linphone_upnp_port_binding_release(list_port);
997                         return;
998                 }
999                 list = ms_list_next(list);
1000         }
1001
1002         list = lupnp->adding_configs;
1003         while(list != NULL) {
1004                 list_port = (UpnpPortBinding *)list->data;
1005                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1006                         return;
1007                 }
1008                 list = ms_list_next(list);
1009         }
1010
1011         list_port = linphone_upnp_port_binding_copy(port);
1012         lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1013 }
1014
1015 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1016         MSList *list;
1017         UpnpPortBinding *list_port;
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                         lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1024                         linphone_upnp_port_binding_release(list_port);
1025                         return;
1026                 }
1027                 list = ms_list_next(list);
1028         }
1029
1030         list = lupnp->removing_configs;
1031         while(list != NULL) {
1032                 list_port = (UpnpPortBinding *)list->data;
1033                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1034                         return;
1035                 }
1036                 list = ms_list_next(list);
1037         }
1038
1039         list_port = linphone_upnp_port_binding_copy(port);
1040         lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);
1041 }