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