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