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