]> sjero.net Git - linphone/blob - coreapi/upnp.c
Update ms2 for upnp improvement
[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 void linphone_core_update_upnp_state_in_call_stats(LinphoneCall *call) {
580         call->stats[LINPHONE_CALL_STATS_AUDIO].upnp_state = call->upnp_session->audio->state;
581         call->stats[LINPHONE_CALL_STATS_VIDEO].upnp_state = call->upnp_session->video->state;
582 }
583
584 int linphone_upnp_call_process(LinphoneCall *call) {
585         LinphoneCore *lc = call->core;
586         UpnpContext *lupnp = lc->upnp;
587         int ret = -1;
588         LinphoneUpnpState oldState = 0, newState = 0;
589
590         if(lupnp == NULL) {
591                 return ret;
592         }
593
594         ms_mutex_lock(&lupnp->mutex);
595
596         // Don't handle when the call
597         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
598                 ret = 0;
599
600                 /*
601                  * Update Audio state
602                  */
603                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
604                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
605                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
606                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
607                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
608                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
609                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
610                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
611                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
612                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
613                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
614                 } else {
615                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
616                 }
617
618                 /*
619                  * Update Video state
620                  */
621                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
622                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
623                         call->upnp_session->video->state = LinphoneUpnpStateOk;
624                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
625                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
626                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
627                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
628                         call->upnp_session->video->state = LinphoneUpnpStatePending;
629                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
630                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
631                         call->upnp_session->video->state = LinphoneUpnpStateKo;
632                 } else {
633                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
634                 }
635
636                 /*
637                  * Update session state
638                  */
639                 oldState = call->upnp_session->state;
640                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
641                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
642                         call->upnp_session->state = LinphoneUpnpStateOk;
643                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
644                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
645                         call->upnp_session->state = LinphoneUpnpStatePending;
646                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
647                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
648                         call->upnp_session->state = LinphoneUpnpStateKo;
649                 } else {
650                         call->upnp_session->state = LinphoneUpnpStateIdle;
651                 }
652                 newState = call->upnp_session->state;
653
654                 /* When change is done proceed update */
655                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
656                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
657                         if(call->upnp_session->state == LinphoneUpnpStateOk)
658                                 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
659                         else
660                                 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
661
662                         switch (call->state) {
663                                 case LinphoneCallUpdating:
664                                         linphone_core_start_update_call(lc, call);
665                                         break;
666                                 case LinphoneCallUpdatedByRemote:
667                                         linphone_core_start_accept_call_update(lc, call);
668                                         break;
669                                 case LinphoneCallOutgoingInit:
670                                         linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
671                                         break;
672                                 case LinphoneCallIdle:
673                                         linphone_core_notify_incoming_call(lc, call);
674                                         break;
675                                 default:
676                                         break;
677                         }
678                 }
679         }
680
681         ms_mutex_unlock(&lupnp->mutex);
682
683         /*
684          * Update uPnP call stats
685          */
686         if(oldState != newState) {
687                 linphone_core_update_upnp_state_in_call_stats(call);
688         }
689
690         return ret;
691 }
692
693 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
694         MSList *global_list = NULL;
695         MSList *list = NULL;
696         MSList *item;
697         LinphoneCall *call;
698         UpnpPortBinding *port_mapping, *port_mapping2;
699
700         ms_message("uPnP IGD: Refresh mappings");
701
702         /* Remove context port bindings */
703         if(lupnp->sip_udp != NULL) {
704                 global_list = ms_list_append(global_list, lupnp->sip_udp);
705         }
706         if(lupnp->sip_tcp != NULL) {
707                 global_list = ms_list_append(global_list, lupnp->sip_tcp);
708         }
709         if(lupnp->sip_tls != NULL) {
710                 global_list = ms_list_append(global_list, lupnp->sip_tls);
711         }
712
713         /* Remove call port bindings */
714         list = lupnp->lc->calls;
715         while(list != NULL) {
716                 call = (LinphoneCall *)list->data;
717                 if(call->upnp_session != NULL) {
718                         global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
719                         global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
720                         global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
721                         global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
722                 }
723                 list = list->next;
724         }
725
726         // Remove port binding configurations
727         list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
728         for(item = list;item != NULL; item = item->next) {
729                         port_mapping = (UpnpPortBinding *)item->data;
730                         port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
731                         if(port_mapping2 == NULL) {
732                                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
733                         } else if(port_mapping2->state == LinphoneUpnpStateIdle){
734                                 /* Force to remove */
735                                 port_mapping2->state = LinphoneUpnpStateOk;
736                         }
737         }
738         ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
739         list = ms_list_free(list);
740
741
742         // (Re)Add removed port bindings
743         list = global_list;
744         while(list != NULL) {
745                 port_mapping = (UpnpPortBinding *)list->data;
746                 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
747                 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
748                 list = list->next;
749         }
750         global_list = ms_list_free(global_list);
751 }
752
753 bool_t linphone_core_upnp_hook(void *data) {
754         char key[64];
755         MSList *item;
756         UpnpPortBinding *port_mapping;
757         UpnpContext *lupnp = (UpnpContext *)data;
758         ms_mutex_lock(&lupnp->mutex);
759
760         /* Add configs */
761         for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
762                 port_mapping = (UpnpPortBinding *)item->data;
763                 snprintf(key, sizeof(key), "%s-%d-%d",
764                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
765                                                         port_mapping->external_port,
766                                                         port_mapping->local_port);
767                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
768                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
769         }
770         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
771         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
772
773         /* Remove configs */
774         for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
775                 port_mapping = (UpnpPortBinding *)item->data;
776                 snprintf(key, sizeof(key), "%s-%d-%d",
777                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
778                                                         port_mapping->external_port,
779                                                         port_mapping->local_port);
780                 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
781                 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
782         }
783         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
784         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
785
786         ms_mutex_unlock(&lupnp->mutex);
787         return TRUE;
788 }
789
790 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
791         int i;
792         SalStreamDescription *stream;
793         UpnpStream *upnpStream;
794
795         for (i = 0; i < desc->nstreams; i++) {
796                 stream = &desc->streams[i];
797                 upnpStream = NULL;
798                 if(stream->type == SalAudio) {
799                         upnpStream = session->audio;
800                 } else if(stream->type == SalVideo) {
801                         upnpStream = session->video;
802                 }
803                 if(upnpStream != NULL) {
804                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
805                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
806                                 stream->rtp_port = upnpStream->rtp->external_port;
807                         }
808                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
809                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
810                                 stream->rtcp_port = upnpStream->rtcp->external_port;
811                         }
812                 }
813         }
814         return 0;
815 }
816
817
818 /*
819  * uPnP Port Binding
820  */
821
822 UpnpPortBinding *linphone_upnp_port_binding_new() {
823         UpnpPortBinding *port = NULL;
824         port = ms_new0(UpnpPortBinding,1);
825         ms_mutex_init(&port->mutex, NULL);
826         port->state = LinphoneUpnpStateIdle;
827         port->local_addr[0] = '\0';
828         port->local_port = -1;
829         port->external_addr[0] = '\0';
830         port->external_port = -1;
831         port->to_remove = FALSE;
832         port->to_add = FALSE;
833         port->ref = 1;
834         return port;
835 }
836
837 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
838         UpnpPortBinding *new_port = NULL;
839         new_port = ms_new0(UpnpPortBinding,1);
840         memcpy(new_port, port, sizeof(UpnpPortBinding));
841         ms_mutex_init(&new_port->mutex, NULL);
842         new_port->ref = 1;
843         return new_port;
844 }
845
846 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
847         if(strlen(port->local_addr)) {
848                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
849                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
850                                                                         port->external_port,
851                                                                         port->local_addr,
852                                                                         port->local_port);
853         } else {
854                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
855                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
856                                                                         port->external_port,
857                                                                         port->local_port);
858         }
859 }
860
861 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
862         return port1->protocol == port2->protocol &&
863                         port1->local_port == port2->local_port &&
864                         port1->external_port == port2->external_port;
865 }
866
867 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
868         UpnpPortBinding *port_mapping;
869         while(list != NULL) {
870                 port_mapping = (UpnpPortBinding *)list->data;
871                 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
872                         return port_mapping;
873                 }
874                 list = list->next;
875         }
876
877         return NULL;
878 }
879
880 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
881         ms_mutex_lock(&port->mutex);
882         port->ref++;
883         ms_mutex_unlock(&port->mutex);
884         return port;
885 }
886
887 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
888         ms_mutex_lock(&port->mutex);
889         if(--port->ref == 0) {
890                 ms_mutex_unlock(&port->mutex);
891                 ms_mutex_destroy(&port->mutex);
892                 ms_free(port);
893                 return;
894         }
895         ms_mutex_unlock(&port->mutex);
896 }
897
898
899 /*
900  * uPnP Stream
901  */
902
903 UpnpStream* linphone_upnp_stream_new() {
904         UpnpStream *stream = ms_new0(UpnpStream,1);
905         stream->state = LinphoneUpnpStateIdle;
906         stream->rtp = linphone_upnp_port_binding_new();
907         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
908         stream->rtcp = linphone_upnp_port_binding_new();
909         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
910         return stream;
911 }
912
913 void linphone_upnp_stream_destroy(UpnpStream* stream) {
914         linphone_upnp_port_binding_release(stream->rtp);
915         stream->rtp = NULL;
916         linphone_upnp_port_binding_release(stream->rtcp);
917         stream->rtcp = NULL;
918         ms_free(stream);
919 }
920
921
922 /*
923  * uPnP Session
924  */
925
926 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
927         UpnpSession *session = ms_new0(UpnpSession,1);
928         session->call = call;
929         session->state = LinphoneUpnpStateIdle;
930         session->audio = linphone_upnp_stream_new();
931         session->video = linphone_upnp_stream_new();
932         return session;
933 }
934
935 void linphone_upnp_session_destroy(UpnpSession *session) {
936         LinphoneCore *lc = session->call->core;
937
938         if(lc->upnp != NULL) {
939                 /* Remove bindings */
940                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
941                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
942                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
943                 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
944         }
945
946         linphone_upnp_stream_destroy(session->audio);
947         linphone_upnp_stream_destroy(session->video);
948         ms_free(session);
949 }
950
951 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
952         return session->state;
953 }
954
955 /*
956  * uPnP Config
957  */
958
959 struct linphone_upnp_config_list_port_bindings_struct {
960         struct _LpConfig *lpc;
961         MSList *retList;
962 };
963
964 static void linphone_upnp_config_list_port_bindings_cb(const char *entry, struct linphone_upnp_config_list_port_bindings_struct *cookie) {
965         char protocol_str[4]; // TCP or UDP
966         upnp_igd_ip_protocol protocol;
967         int external_port;
968         int local_port;
969         bool_t valid = TRUE;
970         UpnpPortBinding *port;
971         if(sscanf(entry, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
972                 if(strcasecmp(protocol_str, "TCP") == 0) {
973                         protocol = UPNP_IGD_IP_PROTOCOL_TCP;
974                 } else if(strcasecmp(protocol_str, "UDP") == 0) {
975                         protocol = UPNP_IGD_IP_PROTOCOL_UDP;
976                 } else {
977                         valid = FALSE;
978                 }
979                 if(valid) {
980                         port = linphone_upnp_port_binding_new();
981                         port->state = LinphoneUpnpStateOk;
982                         port->protocol = protocol;
983                         port->external_port = external_port;
984                         port->local_port = local_port;
985                         cookie->retList = ms_list_append(cookie->retList, port);
986                 }
987         } else {
988                 valid = FALSE;
989         }
990         if(!valid) {
991                 ms_warning("uPnP configuration invalid line: %s", entry);
992         }
993 }
994
995 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
996         struct linphone_upnp_config_list_port_bindings_struct cookie = {lpc, NULL};
997         lp_config_for_each_entry(lpc, UPNP_SECTION_NAME, (void(*)(const char *, void*))linphone_upnp_config_list_port_bindings_cb, &cookie);
998         return cookie.retList;
999 }
1000
1001 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1002         MSList *list;
1003         UpnpPortBinding *list_port;
1004
1005         list = lupnp->removing_configs;
1006         while(list != NULL) {
1007                 list_port = (UpnpPortBinding *)list->data;
1008                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1009                         lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1010                         linphone_upnp_port_binding_release(list_port);
1011                         return;
1012                 }
1013                 list = ms_list_next(list);
1014         }
1015
1016         list = lupnp->adding_configs;
1017         while(list != NULL) {
1018                 list_port = (UpnpPortBinding *)list->data;
1019                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1020                         return;
1021                 }
1022                 list = ms_list_next(list);
1023         }
1024
1025         list_port = linphone_upnp_port_binding_copy(port);
1026         lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1027 }
1028
1029 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1030         MSList *list;
1031         UpnpPortBinding *list_port;
1032
1033         list = lupnp->adding_configs;
1034         while(list != NULL) {
1035                 list_port = (UpnpPortBinding *)list->data;
1036                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1037                         lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1038                         linphone_upnp_port_binding_release(list_port);
1039                         return;
1040                 }
1041                 list = ms_list_next(list);
1042         }
1043
1044         list = lupnp->removing_configs;
1045         while(list != NULL) {
1046                 list_port = (UpnpPortBinding *)list->data;
1047                 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1048                         return;
1049                 }
1050                 list = ms_list_next(list);
1051         }
1052
1053         list_port = linphone_upnp_port_binding_copy(port);
1054         lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);
1055 }