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