]> sjero.net Git - linphone/blob - coreapi/upnp.c
Improve uPnP
[linphone] / coreapi / upnp.c
1 /*
2 linphone
3 Copyright (C) 2012  Belledonne Communications SARL
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19
20 #include "upnp.h"
21 #include "private.h"
22
23 #define UPNP_ADD_MAX_RETRY 4
24 #define UPNP_REMOVE_MAX_RETRY 4
25 #define UPNP_SECTION_NAME "uPnP"
26
27 /* Define private types */
28 typedef struct _LpItem{
29         char *key;
30         char *value;
31 } LpItem;
32
33 typedef struct _LpSection{
34         char *name;
35         MSList *items;
36 } LpSection;
37
38 typedef struct _LpConfig{
39         FILE *file;
40         char *filename;
41         MSList *sections;
42         int modified;
43         int readonly;
44 } LpConfig;
45
46 /* Declare private functions */
47 LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
48 void lp_section_remove_item(LpSection *sec, LpItem *item);
49 void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
50
51 bool_t linphone_core_upnp_hook(void *data);
52
53 UpnpPortBinding *upnp_port_binding_new();
54 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
55 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
56 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
57 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
58 void upnp_port_binding_release(UpnpPortBinding *port);
59
60 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
61 void upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
62 void upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
63
64 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
65 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
66
67
68 /**
69  * uPnP Callbacks
70  */
71
72 /* Convert uPnP IGD logs to ortp logs */
73 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
74         int ortp_level = ORTP_DEBUG;
75         switch(level) {
76         case UPNP_IGD_MESSAGE:
77                 ortp_level = ORTP_MESSAGE;
78                 break;
79         case UPNP_IGD_WARNING:
80                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
81                 break;
82         case UPNP_IGD_ERROR:
83                 ortp_level = ORTP_DEBUG; // Too verbose otherwise
84                 break;
85         default:
86                 break;
87         }
88         ortp_logv(ortp_level, fmt, list);
89 }
90
91 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
92         LinphoneCore *lc = (LinphoneCore *)cookie;
93         UpnpContext *lupnp = &lc->upnp;
94         upnp_igd_port_mapping *mapping = NULL;
95         UpnpPortBinding *port_mapping = NULL;
96         const char *ip_address = NULL;
97         const char *connection_status = NULL;
98         bool_t nat_enabled = FALSE;
99         ms_mutex_lock(&lupnp->mutex);
100
101         switch(event) {
102         case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
103         case UPNP_IGD_NAT_ENABLED_CHANGED:
104         case UPNP_IGD_CONNECTION_STATUS_CHANGED:
105                 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
106                 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
107                 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
108
109                 if(ip_address == NULL || connection_status == NULL) {
110                         ms_message("uPnP IGD: Pending");
111                         lupnp->state = LinphoneUpnpStatePending;
112                 } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
113                         ms_message("uPnP IGD: Not Available");
114                         lupnp->state = LinphoneUpnpStateNotAvailable;
115                 } else {
116                         ms_message("uPnP IGD: Connected");
117                         if(lupnp->state != LinphoneUpnpStateOk) {
118                                 lupnp->clean = TRUE; // Remove saved port mapping configurations
119                         }
120                         lupnp->state = LinphoneUpnpStateOk;
121                 }
122
123                 break;
124
125         case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
126                 mapping = (upnp_igd_port_mapping *) arg;
127                 port_mapping = (UpnpPortBinding*) mapping->cookie;
128                 port_mapping->external_port = mapping->remote_port;
129                 port_mapping->state = LinphoneUpnpStateOk;
130                 upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
131                 upnp_config_add_port_binding(lc, port_mapping);
132
133                 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
134                 upnp_port_binding_release(port_mapping);
135                 break;
136
137         case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
138                 mapping = (upnp_igd_port_mapping *) arg;
139                 port_mapping = (UpnpPortBinding*) mapping->cookie;
140                 if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
141                         upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
142                 }
143
144                 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
145                 upnp_port_binding_release(port_mapping);
146                 break;
147
148         case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
149                 mapping = (upnp_igd_port_mapping *) arg;
150                 port_mapping = (UpnpPortBinding*) mapping->cookie;
151                 port_mapping->state = LinphoneUpnpStateIdle;
152                 upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
153                 upnp_config_remove_port_binding(lc, port_mapping);
154
155                 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
156                 upnp_port_binding_release(port_mapping);
157                 break;
158
159         case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
160                 mapping = (upnp_igd_port_mapping *) arg;
161                 port_mapping = (UpnpPortBinding*) mapping->cookie;
162                 if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
163                         upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
164                         upnp_config_remove_port_binding(lc, port_mapping);
165                 }
166
167                 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
168                 upnp_port_binding_release(port_mapping);
169                 break;
170
171         default:
172                 break;
173         }
174
175         if(lupnp->pending_bindings == NULL) {
176                 if(lupnp->cleaning == TRUE) {
177                         lupnp->emit = TRUE; // Emit port bindings
178                         lupnp->cleaning = FALSE;
179                 }
180                 pthread_cond_signal(&lupnp->cond);
181         }
182         ms_mutex_unlock(&lupnp->mutex);
183 }
184
185
186 /**
187  * uPnP Context
188  */
189
190 int upnp_context_init(LinphoneCore *lc) {
191         LCSipTransports transport;
192         UpnpContext *lupnp = &lc->upnp;
193         const char *ip_address;
194
195         ms_mutex_init(&lupnp->mutex, NULL);
196         ms_cond_init(&lupnp->cond, NULL);
197
198         lupnp->pending_bindings = NULL;
199         lupnp->adding_configs = NULL;
200         lupnp->removing_configs = NULL;
201         lupnp->clean = FALSE;
202         lupnp->cleaning = FALSE;
203         lupnp->emit = FALSE;
204         lupnp->state = LinphoneUpnpStateIdle;
205         ms_message("uPnP IGD: Init");
206
207         linphone_core_get_sip_transports(lc, &transport);
208         if(transport.udp_port != 0) {
209                 lupnp->sip_udp = upnp_port_binding_new();
210                 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
211                 lupnp->sip_udp->local_port = transport.udp_port;
212                 lupnp->sip_udp->external_port = transport.udp_port;
213         } else {
214                 lupnp->sip_udp = NULL;
215         }
216         if(transport.tcp_port != 0) {
217                 lupnp->sip_tcp = upnp_port_binding_new();
218                 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
219                 lupnp->sip_tcp->local_port = transport.tcp_port;
220                 lupnp->sip_tcp->external_port = transport.tcp_port;
221         } else {
222                 lupnp->sip_tcp = NULL;
223         }
224         if(transport.tls_port != 0) {
225                 lupnp->sip_tls = upnp_port_binding_new();
226                 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
227                 lupnp->sip_tls->local_port = transport.tls_port;
228                 lupnp->sip_tls->external_port = transport.tls_port;
229         } else {
230                 lupnp->sip_tls = NULL;
231         }
232
233         linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
234
235         lupnp->upnp_igd_ctxt = NULL;
236         lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
237         if(lupnp->upnp_igd_ctxt == NULL) {
238                 lupnp->state = LinphoneUpnpStateKo;
239                 ms_error("Can't create uPnP IGD context");
240                 return -1;
241         }
242
243         ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
244         if(lupnp->sip_udp != NULL) {
245                 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
246         }
247         if(lupnp->sip_tcp != NULL) {
248                 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
249         }
250         if(lupnp->sip_tls != NULL) {
251                 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
252         }
253
254         lupnp->state = LinphoneUpnpStatePending;
255         return 0;
256 }
257
258 void upnp_context_uninit(LinphoneCore *lc) {
259         UpnpContext *lupnp = &lc->upnp;
260
261         /*
262          * Not need, all hooks are removed before
263          * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
264      */
265
266         /* Send port binding removes */
267         if(lupnp->sip_udp != NULL) {
268                 upnp_context_send_remove_port_binding(lc, lupnp->sip_udp);
269                 lupnp->sip_udp = NULL;
270         }
271         if(lupnp->sip_tcp != NULL) {
272                 upnp_context_send_remove_port_binding(lc, lupnp->sip_tcp);
273                 lupnp->sip_tcp = NULL;
274         }
275         if(lupnp->sip_tls != NULL) {
276                 upnp_context_send_remove_port_binding(lc, lupnp->sip_tls);
277                 lupnp->sip_tcp = NULL;
278         }
279
280         /* Wait all pending bindings are done */
281         ms_message("uPnP IGD: Wait all pending port bindings ...");
282         ms_mutex_lock(&lupnp->mutex);
283         ms_cond_wait(&lupnp->cond, &lupnp->mutex);
284         ms_mutex_unlock(&lupnp->mutex);
285
286         if(lupnp->upnp_igd_ctxt != NULL) {
287                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
288         }
289
290         /* Run one time the hook for configuration update */
291         linphone_core_upnp_hook(lc);
292
293         /* Release port bindings */
294         if(lupnp->sip_udp != NULL) {
295                 upnp_port_binding_release(lupnp->sip_udp);
296                 lupnp->sip_udp = NULL;
297         }
298         if(lupnp->sip_tcp != NULL) {
299                 upnp_port_binding_release(lupnp->sip_tcp);
300                 lupnp->sip_tcp = NULL;
301         }
302         if(lupnp->sip_tls != NULL) {
303                 upnp_port_binding_release(lupnp->sip_tls);
304                 lupnp->sip_tcp = NULL;
305         }
306
307         /* Release lists */
308         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))upnp_port_binding_release);
309         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
310         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))upnp_port_binding_release);
311         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
312         ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))upnp_port_binding_release);
313         lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
314
315         ms_mutex_destroy(&lupnp->mutex);
316         ms_cond_destroy(&lupnp->cond);
317
318         ms_message("uPnP IGD: Uninit");
319 }
320
321 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
322         UpnpContext *lupnp = &lc->upnp;
323         upnp_igd_port_mapping mapping;
324         int ret;
325         if(port->state == LinphoneUpnpStateIdle) {
326                 port->retry = 0;
327                 port->state = LinphoneUpnpStateAdding;
328         } else if(port->state != LinphoneUpnpStateAdding) {
329                 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
330                 return -2;
331         }
332
333         if(port->retry >= UPNP_ADD_MAX_RETRY) {
334                 ret = -1;
335         } else {
336                 mapping.cookie = upnp_port_binding_retain(port);
337                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
338
339                 mapping.local_port = port->local_port;
340                 mapping.local_host = port->local_addr;
341                 if(port->external_port == -1)
342                         mapping.remote_port = rand()%(0xffff - 1024) + 1024; // TODO: use better method
343                 else
344                         mapping.remote_port = port->external_port;
345                 mapping.remote_host = "";
346                 mapping.description = PACKAGE_NAME;
347                 mapping.protocol = port->protocol;
348
349                 port->retry++;
350                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
351         }
352         if(ret != 0) {
353                 port->state = LinphoneUpnpStateKo;
354         }
355         return ret;
356 }
357
358 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
359         UpnpContext *lupnp = &lc->upnp;
360         upnp_igd_port_mapping mapping;
361         int ret;
362         if(port->state == LinphoneUpnpStateOk) {
363                 port->retry = 0;
364                 port->state = LinphoneUpnpStateRemoving;
365         } else if(port->state != LinphoneUpnpStateRemoving) {
366                 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
367                 return -2;
368         }
369
370         if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
371                 ret = -1;
372         } else {
373                 mapping.cookie = upnp_port_binding_retain(port);
374                 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
375
376                 mapping.remote_port = port->external_port;
377                 mapping.remote_host = "";
378                 mapping.protocol = port->protocol;
379                 port->retry++;
380                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
381         }
382         if(ret != 0) {
383                 port->state = LinphoneUpnpStateKo;
384         }
385         return ret;
386 }
387
388 /*
389  * uPnP Core interfaces
390  */
391
392 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
393         LinphoneCore *lc = call->core;
394         UpnpContext *lupnp = &lc->upnp;
395         int ret = -1;
396         const char *local_addr, *external_addr;
397
398         ms_mutex_lock(&lupnp->mutex);
399         // Don't handle when the call
400         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
401                 ret = 0;
402                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
403                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
404
405                 /*
406                  * Audio part
407                  */
408                 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
409                 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
410                 call->upnp_session->audio->rtp->local_port = call->audio_port;
411                 if(call->upnp_session->audio->rtp->external_port == -1) {
412                         call->upnp_session->audio->rtp->external_port = call->audio_port;
413                 }
414                 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
415                 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
416                 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
417                 if(call->upnp_session->audio->rtcp->external_port == -1) {
418                         call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
419                 }
420                 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
421                         // Add audio port binding
422                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
423                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
424                         // Remove audio port binding
425                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
426                 }
427                 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
428                         // Add audio port binding
429                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
430                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
431                         // Remove audio port binding
432                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
433                 }
434
435                 /*
436                  * Video part
437                  */
438                 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
439                 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
440                 call->upnp_session->video->rtp->local_port = call->video_port;
441                 if(call->upnp_session->video->rtp->external_port == -1) {
442                         call->upnp_session->video->rtp->external_port = call->video_port;
443                 }
444                 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
445                 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
446                 call->upnp_session->video->rtcp->local_port = call->video_port+1;
447                 if(call->upnp_session->video->rtcp->external_port == -1) {
448                         call->upnp_session->video->rtcp->external_port = call->video_port+1;
449                 }
450                 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
451                         // Add video port binding
452                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
453                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
454                         // Remove video port binding
455                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
456                 }
457                 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
458                         // Add video port binding
459                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
460                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
461                         // Remove video port binding
462                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
463                 }
464         }
465
466         ms_mutex_unlock(&lupnp->mutex);
467
468         /*
469          * Update uPnP call state
470          */
471         upnp_call_process(call);
472
473         return ret;
474 }
475
476
477 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
478         bool_t audio = FALSE;
479         bool_t video = FALSE;
480         int i;
481         const SalStreamDescription *stream;
482
483         for (i = 0; i < md->nstreams; i++) {
484                 stream = &md->streams[i];
485                 if(stream->type == SalAudio) {
486                         audio = TRUE;
487                 } else if(stream->type == SalVideo) {
488                         video = TRUE;
489                 }
490         }
491
492         return linphone_core_update_upnp_audio_video(call, audio, video);
493 }
494
495 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
496         return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
497 }
498
499 int upnp_call_process(LinphoneCall *call) {
500         LinphoneCore *lc = call->core;
501         UpnpContext *lupnp = &lc->upnp;
502         int ret = -1;
503         UpnpState oldState;
504
505         ms_mutex_lock(&lupnp->mutex);
506
507         // Don't handle when the call
508         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
509                 ret = 0;
510
511                 /*
512                  * Update Audio state
513                  */
514                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
515                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
516                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
517                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
518                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
519                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
520                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
521                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
522                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
523                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
524                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
525                 } else {
526                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
527                 }
528
529                 /*
530                  * Update Video state
531                  */
532                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
533                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
534                         call->upnp_session->video->state = LinphoneUpnpStateOk;
535                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
536                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
537                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
538                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
539                         call->upnp_session->video->state = LinphoneUpnpStatePending;
540                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
541                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
542                         call->upnp_session->video->state = LinphoneUpnpStateKo;
543                 } else {
544                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
545                 }
546
547                 /*
548                  * Update session state
549                  */
550                 oldState = call->upnp_session->state;
551                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
552                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
553                         call->upnp_session->state = LinphoneUpnpStateOk;
554                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
555                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
556                         call->upnp_session->state = LinphoneUpnpStatePending;
557                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
558                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
559                         call->upnp_session->state = LinphoneUpnpStateKo;
560                 } else {
561                         call->upnp_session->state = LinphoneUpnpStateIdle;
562                 }
563
564                 /* When change is done proceed update */
565                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
566                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
567                         if(call->upnp_session->state == LinphoneUpnpStateOk)
568                                 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
569                         else
570                                 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
571
572                         switch (call->state) {
573                                 case LinphoneCallUpdating:
574                                         linphone_core_start_update_call(lc, call);
575                                         break;
576                                 case LinphoneCallUpdatedByRemote:
577                                         linphone_core_start_accept_call_update(lc, call);
578                                         break;
579                                 case LinphoneCallOutgoingInit:
580                                         linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
581                                         break;
582                                 case LinphoneCallIdle:
583                                         linphone_core_notify_incoming_call(lc, call);
584                                         break;
585                                 default:
586                                         break;
587                         }
588                 }
589         }
590
591         ms_mutex_unlock(&lupnp->mutex);
592         return ret;
593 }
594
595 bool_t linphone_core_upnp_hook(void *data) {
596         char key[64];
597         MSList *list = NULL;
598         MSList *item;
599         UpnpPortBinding *port_mapping;
600         LinphoneCore *lc = (LinphoneCore *)data;
601         LinphoneCall *call;
602         UpnpContext *lupnp = &lc->upnp;
603         ms_mutex_lock(&lupnp->mutex);
604
605         if(lupnp->clean && !lupnp->cleaning) {
606                 lupnp->clean = FALSE;
607                 // Remove old mapping
608                 list = upnp_config_list_port_bindings(lc->config);
609                 if(list == NULL) {
610                         lupnp->emit = TRUE;
611                 } else {
612                         lupnp->cleaning = TRUE;
613                         for(item = list;item != NULL; item = item->next) {
614                                 port_mapping = (UpnpPortBinding *)item->data;
615                                 upnp_context_send_remove_port_binding(lc, port_mapping);
616                         }
617                         ms_list_for_each(list,(void (*)(void*))upnp_port_binding_release);
618                         list = ms_list_free(list);
619                 }
620         }
621
622         if(lupnp->emit) {
623                 lupnp->emit = FALSE;
624
625                 /* Force port bindings */
626                 if(lupnp->sip_udp != NULL) {
627                         lupnp->sip_udp->state = LinphoneUpnpStateIdle;
628                         upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
629                 }
630                 if(lupnp->sip_tcp != NULL) {
631                         lupnp->sip_udp->state = LinphoneUpnpStateIdle;
632                         upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
633                 }
634                 if(lupnp->sip_tls != NULL) {
635                         lupnp->sip_udp->state = LinphoneUpnpStateIdle;
636                         upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
637                 }
638                 list = lc->calls;
639                 while(list != NULL) {
640                         call = (LinphoneCall *)list->data;
641                         call->upnp_session->audio->rtp->state = LinphoneUpnpStateIdle;
642                         call->upnp_session->audio->rtcp->state = LinphoneUpnpStateIdle;
643                         call->upnp_session->video->rtp->state = LinphoneUpnpStateIdle;
644                         call->upnp_session->video->rtcp->state = LinphoneUpnpStateIdle;
645                         linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
646                         list = list->next;
647                 }
648         }
649
650         /* Add configs */
651         for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
652                 port_mapping = (UpnpPortBinding *)item->data;
653                 snprintf(key, sizeof(key), "%s-%d-%d",
654                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
655                                                         port_mapping->external_port,
656                                                         port_mapping->local_port);
657                 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
658                 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
659         }
660         ms_list_for_each(lupnp->adding_configs,(void (*)(void*))upnp_port_binding_release);
661         lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
662
663         /* Remove configs */
664         for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
665                 port_mapping = (UpnpPortBinding *)item->data;
666                 snprintf(key, sizeof(key), "%s-%d-%d",
667                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
668                                                         port_mapping->external_port,
669                                                         port_mapping->local_port);
670                 lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
671                 upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
672         }
673         ms_list_for_each(lupnp->removing_configs,(void (*)(void*))upnp_port_binding_release);
674         lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
675
676         ms_mutex_unlock(&lupnp->mutex);
677         return TRUE;
678 }
679
680 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
681         int i;
682         SalStreamDescription *stream;
683         UpnpStream *upnpStream;
684
685         for (i = 0; i < desc->nstreams; i++) {
686                 stream = &desc->streams[i];
687                 upnpStream = NULL;
688                 if(stream->type == SalAudio) {
689                         upnpStream = session->audio;
690                 } else if(stream->type == SalVideo) {
691                         upnpStream = session->video;
692                 }
693                 if(upnpStream != NULL) {
694                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
695                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
696                                 stream->rtp_port = upnpStream->rtp->external_port;
697                         }
698                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
699                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
700                                 stream->rtcp_port = upnpStream->rtcp->external_port;
701                         }
702                 }
703         }
704 }
705
706
707 /*
708  * uPnP Port Binding
709  */
710
711 UpnpPortBinding *upnp_port_binding_new() {
712         UpnpPortBinding *port = NULL;
713         port = ms_new0(UpnpPortBinding,1);
714         ms_mutex_init(&port->mutex, NULL);
715         port->state = LinphoneUpnpStateIdle;
716         port->local_addr[0] = '\0';
717         port->local_port = -1;
718         port->external_addr[0] = '\0';
719         port->external_port = -1;
720         port->ref = 1;
721         return port;
722 }
723
724 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
725         UpnpPortBinding *new_port = NULL;
726         new_port = ms_new0(UpnpPortBinding,1);
727         memcpy(new_port, port, sizeof(UpnpPortBinding));
728         ms_mutex_init(&new_port->mutex, NULL);
729         new_port->ref = 1;
730         return new_port;
731 }
732
733 void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
734         if(strlen(port->local_addr)) {
735                 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
736                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
737                                                                         port->external_port,
738                                                                         port->local_addr,
739                                                                         port->local_port);
740         } else {
741                 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
742                                                         (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
743                                                                         port->external_port,
744                                                                         port->local_port);
745         }
746 }
747
748 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
749         return port1->protocol == port2->protocol &&
750                         port1->local_port == port2->local_port &&
751                         port1->external_port == port2->external_port;
752 }
753
754 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
755         ms_mutex_lock(&port->mutex);
756         port->ref++;
757         ms_mutex_unlock(&port->mutex);
758         return port;
759 }
760
761 void upnp_port_binding_release(UpnpPortBinding *port) {
762         ms_mutex_lock(&port->mutex);
763         if(--port->ref == 0) {
764                 ms_mutex_unlock(&port->mutex);
765                 ms_mutex_destroy(&port->mutex);
766                 ms_free(port);
767                 return;
768         }
769         ms_mutex_unlock(&port->mutex);
770 }
771
772
773 /*
774  * uPnP Stream
775  */
776
777 UpnpStream* upnp_stream_new() {
778         UpnpStream *stream = ms_new0(UpnpStream,1);
779         stream->state = LinphoneUpnpStateIdle;
780         stream->rtp = upnp_port_binding_new();
781         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
782         stream->rtcp = upnp_port_binding_new();
783         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
784         return stream;
785 }
786
787 void upnp_stream_destroy(UpnpStream* stream) {
788         upnp_port_binding_release(stream->rtp);
789         stream->rtp = NULL;
790         upnp_port_binding_release(stream->rtcp);
791         stream->rtcp = NULL;
792         ms_free(stream);
793 }
794
795
796 /*
797  * uPnP Session
798  */
799
800 UpnpSession* upnp_session_new() {
801         UpnpSession *session = ms_new0(UpnpSession,1);
802         session->state = LinphoneUpnpStateIdle;
803         session->audio = upnp_stream_new();
804         session->video = upnp_stream_new();
805         return session;
806 }
807
808 void upnp_session_destroy(LinphoneCall* call) {
809         LinphoneCore *lc = call->core;
810
811         /* Remove bindings */
812         if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
813                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
814         }
815         if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
816                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
817         }
818         if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
819                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
820         }
821         if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
822                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
823         }
824
825         upnp_stream_destroy(call->upnp_session->audio);
826         upnp_stream_destroy(call->upnp_session->video);
827         ms_free(call->upnp_session);
828         call->upnp_session = NULL;
829 }
830
831
832 /*
833  * uPnP Config
834  */
835
836 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
837         char protocol_str[4]; // TCP or UDP
838         upnp_igd_ip_protocol protocol;
839         int external_port;
840         int local_port;
841         MSList *retList = NULL;
842         UpnpPortBinding *port;
843         bool_t valid;
844         MSList *elem;
845         LpItem *item;
846         LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
847         if(sec == NULL)
848                 return retList;
849
850         elem = sec->items;
851         while(elem != NULL) {
852                 item=(LpItem*)elem->data;
853                 valid = TRUE;
854                 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
855                         if(strcasecmp(protocol_str, "TCP") == 0) {
856                                 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
857                         } else if(strcasecmp(protocol_str, "UDP") == 0) {
858                                 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
859                         } else {
860                                 valid = FALSE;
861                         }
862                         if(valid) {
863                                 port = upnp_port_binding_new();
864                                 port->state = LinphoneUpnpStateOk;
865                                 port->protocol = protocol;
866                                 port->external_port = external_port;
867                                 port->local_port = local_port;
868                                 retList = ms_list_append(retList, port);
869                         }
870                 } else {
871                         valid = FALSE;
872                 }
873                 elem = ms_list_next(elem);
874                 if(!valid) {
875                         ms_warning("uPnP configuration invalid line: %s", item->key);
876                         lp_section_remove_item(sec, item);
877                 }
878         }
879
880         return retList;
881 }
882
883 void upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
884         UpnpContext *lupnp = &lc->upnp;
885         MSList *list;
886         UpnpPortBinding *list_port;
887
888         list = lupnp->removing_configs;
889         while(list != NULL) {
890                 list_port = (UpnpPortBinding *)list->data;
891                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
892                         lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
893                         upnp_port_binding_release(list_port);
894                         return;
895                 }
896                 list = ms_list_next(list);
897         }
898
899         list = lupnp->adding_configs;
900         while(list != NULL) {
901                 list_port = (UpnpPortBinding *)list->data;
902                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
903                         return;
904                 }
905                 list = ms_list_next(list);
906         }
907
908         list_port = upnp_port_binding_copy(port);
909         lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
910 }
911
912 void upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
913         UpnpContext *lupnp = &lc->upnp;
914         MSList *list;
915         UpnpPortBinding *list_port;
916
917         list = lupnp->adding_configs;
918         while(list != NULL) {
919                 list_port = (UpnpPortBinding *)list->data;
920                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
921                         lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
922                         upnp_port_binding_release(list_port);
923                         return;
924                 }
925                 list = ms_list_next(list);
926         }
927
928         list = lupnp->removing_configs;
929         while(list != NULL) {
930                 list_port = (UpnpPortBinding *)list->data;
931                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
932                         return;
933                 }
934                 list = ms_list_next(list);
935         }
936
937         list_port = upnp_port_binding_copy(port);
938         lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);
939 }