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