]> sjero.net Git - linphone/blob - coreapi/upnp.c
Working sip 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_MAX_RETRY 4
24 #define UPNP_SECTION_NAME "uPnP"
25
26 /* Define private types */
27 typedef struct _LpItem{
28         char *key;
29         char *value;
30 } LpItem;
31
32 typedef struct _LpSection{
33         char *name;
34         MSList *items;
35 } LpSection;
36
37 typedef struct _LpConfig{
38         FILE *file;
39         char *filename;
40         MSList *sections;
41         int modified;
42         int readonly;
43 } LpConfig;
44
45 /* Declare private functions */
46 LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
47 void lp_section_remove_item(LpSection *sec, LpItem *item);
48 void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
49
50 bool_t linphone_core_upnp_hook(void *data);
51
52 UpnpPortBinding *upnp_port_binding_new();
53 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
54 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
55 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
56 void upnp_port_binding_release(UpnpPortBinding *port);
57
58 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
59 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
60 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
61
62 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
63 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
64
65 /**
66  * uPnP Callbacks
67  */
68
69 /* Convert uPnP IGD logs to ortp logs */
70 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
71         int ortp_level = ORTP_DEBUG;
72         switch(level) {
73         case UPNP_IGD_MESSAGE:
74                 ortp_level = ORTP_MESSAGE;
75                 break;
76         case UPNP_IGD_WARNING:
77                 ortp_level = ORTP_WARNING;
78                 break;
79         case UPNP_IGD_ERROR:
80                 ortp_level = ORTP_ERROR;
81                 break;
82         default:
83                 break;
84         }
85         ortp_logv(ortp_level, fmt, list);
86 }
87
88 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
89         LinphoneCore *lc = (LinphoneCore *)cookie;
90         UpnpContext *lupnp = &lc->upnp;
91         upnp_igd_port_mapping *mapping = NULL;
92         UpnpPortBinding *port_mapping = NULL;
93         const char *ip_address = NULL;
94         const char *connection_status = NULL;
95         bool_t nat_enabled = FALSE;
96         ms_mutex_lock(&lupnp->mutex);
97
98         switch(event) {
99         case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
100         case UPNP_IGD_NAT_ENABLED_CHANGED:
101         case UPNP_IGD_CONNECTION_STATUS_CHANGED:
102                 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
103                 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
104                 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
105
106                 if(ip_address == NULL || connection_status == NULL) {
107                         ms_message("uPnP IGD: Pending");
108                         lupnp->state = LinphoneUpnpStatePending;
109                 } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
110                         ms_message("uPnP IGD: Not Available");
111                         lupnp->state = LinphoneUpnpStateNotAvailable;
112                 } else {
113                         ms_message("uPnP IGD: Connected");
114                         lupnp->state = LinphoneUpnpStateOk;
115                 }
116
117                 break;
118
119         case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
120                 mapping = (upnp_igd_port_mapping *) arg;
121                 port_mapping = (UpnpPortBinding*) mapping->cookie;
122                 port_mapping->external_port = mapping->remote_port;
123                 port_mapping->state = LinphoneUpnpStateOk;
124                 ms_message("uPnP IGD: Added port binding %s|%d->%s:%d",
125                                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
126                                                         port_mapping->external_port,
127                                                         port_mapping->local_addr,
128                                                         port_mapping->local_port);
129                 upnp_config_add_port_binding(lc, port_mapping);
130
131                 upnp_port_binding_release(port_mapping);
132                 break;
133
134         case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
135                 mapping = (upnp_igd_port_mapping *) arg;
136                 port_mapping = (UpnpPortBinding*) mapping->cookie;
137                 if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
138                         ms_error("uPnP IGD: Can't add port binding %s|%d->%s:%d",
139                                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
140                                                                 port_mapping->external_port,
141                                                                 port_mapping->local_addr,
142                                                                 port_mapping->local_port);
143                 }
144
145                 upnp_port_binding_release(port_mapping);
146                 break;
147
148         case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
149                 mapping = (upnp_igd_port_mapping *) arg;
150                 port_mapping = (UpnpPortBinding*) mapping->cookie;
151                 port_mapping->state = LinphoneUpnpStateIdle;
152                 ms_message("uPnP IGD: Removed port binding %s|%d->%d",
153                                                         (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
154                                                         port_mapping->external_port,
155                                                         port_mapping->local_port);
156                 upnp_config_remove_port_binding(lc, port_mapping);
157
158                 upnp_port_binding_release(port_mapping);
159                 break;
160
161         case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
162                 mapping = (upnp_igd_port_mapping *) arg;
163                 port_mapping = (UpnpPortBinding*) mapping->cookie;
164                 if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
165                         ms_error("uPnP IGD: Can't remove port binding %s|%d->%d",
166                                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
167                                                                 port_mapping->external_port,
168                                                                 port_mapping->local_port);
169                         upnp_config_remove_port_binding(lc, port_mapping);
170                 }
171
172                 upnp_port_binding_release(port_mapping);
173                 break;
174
175         default:
176                 break;
177         }
178
179         ms_mutex_unlock(&lupnp->mutex);
180 }
181
182
183 /**
184  * uPnP Context
185  */
186
187 int upnp_context_init(LinphoneCore *lc) {
188         LCSipTransports transport;
189         UpnpContext *lupnp = &lc->upnp;
190         const char *ip_address;
191
192         ms_mutex_init(&lupnp->mutex, NULL);
193         lupnp->pending_configs = NULL;
194         lupnp->state = LinphoneUpnpStateIdle;
195         lupnp->old_state = LinphoneUpnpStateIdle;
196         ms_message("uPnP IGD: Init");
197
198         linphone_core_get_sip_transports(lc, &transport);
199         if(transport.udp_port != 0) {
200                 lupnp->sip_udp = upnp_port_binding_new();
201                 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
202                 lupnp->sip_udp->local_port = transport.udp_port;
203         } else {
204                 lupnp->sip_udp = NULL;
205         }
206         if(transport.tcp_port != 0) {
207                 lupnp->sip_tcp = upnp_port_binding_new();
208                 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
209                 lupnp->sip_tcp->local_port = transport.tcp_port;
210         } else {
211                 lupnp->sip_tcp = NULL;
212         }
213         if(transport.tls_port != 0) {
214                 lupnp->sip_tls = upnp_port_binding_new();
215                 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
216                 lupnp->sip_tls->local_port = transport.tls_port;
217         } else {
218                 lupnp->sip_tls = NULL;
219         }
220
221         linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
222
223         lupnp->upnp_igd_ctxt = NULL;
224         lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
225         if(lupnp->upnp_igd_ctxt == NULL) {
226                 lupnp->state = LinphoneUpnpStateKo;
227                 ms_error("Can't create uPnP IGD context");
228                 return -1;
229         }
230
231         ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
232         if(lupnp->sip_udp != NULL) {
233                 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
234         }
235         if(lupnp->sip_tcp != NULL) {
236                 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
237         }
238         if(lupnp->sip_tls != NULL) {
239                 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
240         }
241
242         lupnp->state = LinphoneUpnpStatePending;
243         return 0;
244 }
245
246 void upnp_context_uninit(LinphoneCore *lc) {
247         UpnpContext *lupnp = &lc->upnp;
248         linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
249
250         if(lupnp->sip_udp != NULL) {
251                 upnp_port_binding_release(lupnp->sip_udp);
252         }
253         if(lupnp->sip_tcp != NULL) {
254                 upnp_port_binding_release(lupnp->sip_tcp);
255         }
256         if(lupnp->sip_tls != NULL) {
257                 upnp_port_binding_release(lupnp->sip_tls);
258         }
259         if(lupnp->upnp_igd_ctxt != NULL) {
260                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
261         }
262         ms_mutex_destroy(&lupnp->mutex);
263
264         ms_message("uPnP IGD: Uninit");
265 }
266
267 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
268         UpnpContext *lupnp = &lc->upnp;
269         upnp_igd_port_mapping mapping;
270         int ret;
271         if(port->state == LinphoneUpnpStateIdle) {
272                 port->external_port = -1;
273                 port->retry = 0;
274                 port->state = LinphoneUpnpStateAdding;
275         } else if(port->state != LinphoneUpnpStateAdding) {
276                 ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
277                 return -2;
278         }
279
280         if(port->retry >= UPNP_MAX_RETRY) {
281                 ret = -1;
282         } else {
283                 mapping.cookie = upnp_port_binding_retain(port);
284                 mapping.local_port = port->local_port;
285                 mapping.local_host = port->local_addr;
286                 if(port->external_port == -1)
287                         mapping.remote_port = rand()%1024 + 1024; // TODO: use better method
288                 else
289                         mapping.remote_port = port->external_port;
290                 mapping.remote_host = "";
291                 mapping.description = PACKAGE_NAME;
292                 mapping.protocol = port->protocol;
293
294                 port->retry++;
295                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
296         }
297         if(ret != 0) {
298                 port->state = LinphoneUpnpStateKo;
299         }
300         return ret;
301 }
302
303 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
304         UpnpContext *lupnp = &lc->upnp;
305         upnp_igd_port_mapping mapping;
306         int ret;
307         if(port->state == LinphoneUpnpStateIdle) {
308                 port->retry = 0;
309                 port->state = LinphoneUpnpStateRemoving;
310         } else if(port->state != LinphoneUpnpStateRemoving) {
311                 ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
312                 return -2;
313         }
314
315         if(port->retry >= UPNP_MAX_RETRY) {
316                 ret = -1;
317         } else {
318                 mapping.cookie = upnp_port_binding_retain(port);
319                 mapping.remote_port = port->external_port;
320                 mapping.remote_host = "";
321                 mapping.protocol = port->protocol;
322                 port->retry++;
323                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
324         }
325         if(ret != 0) {
326                 port->state = LinphoneUpnpStateKo;
327         }
328         return ret;
329 }
330
331 /*
332  * uPnP Core interfaces
333  */
334
335 int upnp_call_process(LinphoneCall *call) {
336         LinphoneCore *lc = call->core;
337         UpnpContext *lupnp = &lc->upnp;
338         int ret = -1;
339         UpnpState oldState;
340         const char *local_addr, *external_addr;
341
342         ms_mutex_lock(&lupnp->mutex);
343         // Don't handle when the call
344         if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
345                 ret = 0;
346                 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
347                 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
348
349                 /*
350                  * Audio part
351                  */
352                 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
353                 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
354                 call->upnp_session->audio->rtp->local_port = call->audio_port;
355                 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
356                 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
357                 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
358                 if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
359                         // Add audio port binding
360                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
361                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
362                         // Remove audio port binding
363                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
364                 }
365                 if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && call->audiostream != NULL) {
366                         // Add audio port binding
367                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
368                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && call->audiostream == NULL) {
369                         // Remove audio port binding
370                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
371                 }
372                 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
373                                 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
374                         call->upnp_session->audio->state = LinphoneUpnpStateOk;
375                 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
376                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
377                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
378                                 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
379                         call->upnp_session->audio->state = LinphoneUpnpStatePending;
380                 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
381                                 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
382                         call->upnp_session->audio->state = LinphoneUpnpStateKo;
383                 } else {
384                         call->upnp_session->audio->state = LinphoneUpnpStateIdle;
385                 }
386
387                 /*
388                  * Video part
389                  */
390                 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
391                 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
392                 call->upnp_session->video->rtp->local_port = call->video_port;
393                 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
394                 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
395                 call->upnp_session->video->rtcp->local_port = call->video_port+1;
396                 if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
397                         // Add video port binding
398                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
399                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
400                         // Remove video port binding
401                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
402                 }
403                 if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && call->videostream != NULL) {
404                         // Add video port binding
405                         upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
406                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && call->videostream == NULL) {
407                         // Remove video port binding
408                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
409                 }
410                 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
411                                 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
412                         call->upnp_session->video->state = LinphoneUpnpStateOk;
413                 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
414                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
415                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
416                                 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
417                         call->upnp_session->video->state = LinphoneUpnpStatePending;
418                 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
419                                 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
420                         call->upnp_session->video->state = LinphoneUpnpStateKo;
421                 } else {
422                         call->upnp_session->video->state = LinphoneUpnpStateIdle;
423                 }
424
425                 /*
426                  * Update session state
427                  */
428                 oldState = call->upnp_session->state;
429                 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
430                         call->upnp_session->video->state == LinphoneUpnpStateOk) {
431                         call->upnp_session->state = LinphoneUpnpStateOk;
432                 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
433                                 call->upnp_session->video->state == LinphoneUpnpStatePending) {
434                         call->upnp_session->state = LinphoneUpnpStatePending;
435                 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
436                                 call->upnp_session->video->state == LinphoneUpnpStateKo) {
437                         call->upnp_session->state = LinphoneUpnpStateKo;
438                 } else {
439                         call->upnp_session->state = LinphoneUpnpStateIdle;
440                 }
441
442                 /* When change is done proceed update */
443                 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
444                                 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
445                         switch (call->state) {
446                                 case LinphoneCallUpdating:
447                                         linphone_core_start_update_call(call->core, call);
448                                         break;
449                                 case LinphoneCallUpdatedByRemote:
450                                         linphone_core_start_accept_call_update(call->core, call);
451                                         break;
452                                 case LinphoneCallOutgoingInit:
453                                         linphone_core_proceed_with_invite_if_ready(call->core, call, NULL);
454                                         break;
455                                 case LinphoneCallIdle:
456                                         linphone_core_notify_incoming_call(call->core, call);
457                                         break;
458                                 default:
459                                         break;
460                         }
461                 }
462         }
463
464         ms_mutex_unlock(&lupnp->mutex);
465         return ret;
466 }
467
468 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
469         return upnp_call_process(call);
470 }
471
472 bool_t linphone_core_upnp_hook(void *data) {
473         char key[64];
474         MSList *port_bindings = NULL;
475         MSList *port_bindings_item;
476         UpnpPortBinding *port_mapping;
477         LinphoneCore *lc = (LinphoneCore *)data;
478         UpnpContext *lupnp = &lc->upnp;
479         ms_mutex_lock(&lupnp->mutex);
480
481         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
482                 // Remove old mapping
483                 port_bindings = upnp_config_list_port_bindings(lc->config);
484                 if(port_bindings != NULL) {
485                         for(port_bindings_item = port_bindings;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
486                                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
487                                 upnp_context_send_remove_port_binding(lc, port_mapping);
488                         }
489                         ms_list_for_each(port_bindings,(void (*)(void*))upnp_port_binding_release);
490                         port_bindings = ms_list_free(port_bindings);
491                 }
492         }
493
494         if(lupnp->state == LinphoneUpnpStateOk && lupnp->old_state != LinphoneUpnpStateOk) {
495                 // Add port bindings
496                 if(lupnp->sip_udp != NULL) {
497                         upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
498                 }
499                 if(lupnp->sip_tcp != NULL) {
500                         upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
501                 }
502                 if(lupnp->sip_tls != NULL) {
503                         upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
504                 }
505         }
506
507         /* Update configs */
508         for(port_bindings_item = lupnp->pending_configs;port_bindings_item!=NULL;port_bindings_item=port_bindings_item->next) {
509                 port_mapping = (UpnpPortBinding *)port_bindings_item->data;
510                 if(port_mapping->state == LinphoneUpnpStateAdding) {
511                         snprintf(key, sizeof(key), "%s-%d-%d",
512                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
513                                                                 port_mapping->external_port,
514                                                                 port_mapping->local_port);
515                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "");
516                 }
517                 if(port_mapping->state == LinphoneUpnpStateRemoving) {
518                         snprintf(key, sizeof(key), "%s-%d-%d",
519                                                 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
520                                                                 port_mapping->external_port,
521                                                                 port_mapping->local_port);
522                         lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
523                 }
524         }
525         ms_list_for_each(lupnp->pending_configs,(void (*)(void*))upnp_port_binding_release);
526         lupnp->pending_configs = ms_list_free(lupnp->pending_configs);
527
528         lupnp->old_state = lupnp->state;
529         ms_mutex_unlock(&lupnp->mutex);
530         return TRUE;
531 }
532
533 void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
534         int i;
535         SalStreamDescription *stream;
536         UpnpStream *upnpStream;
537
538         for (i = 0; i < desc->nstreams; i++) {
539                 stream = &desc->streams[i];
540                 upnpStream = NULL;
541                 if(stream->type == SalAudio) {
542                         upnpStream = session->audio;
543                 } else if(stream->type == SalVideo) {
544                         upnpStream = session->video;
545                 }
546                 if(upnpStream != NULL) {
547                         if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
548                                 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
549                                 stream->rtp_port = upnpStream->rtp->external_port;
550                         }
551                         if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
552                                 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
553                                 stream->rtcp_port = upnpStream->rtcp->external_port;
554                         }
555                 }
556         }
557 }
558
559
560 /*
561  * uPnP Port Binding
562  */
563
564 UpnpPortBinding *upnp_port_binding_new() {
565         UpnpPortBinding *port = NULL;
566         port = ms_new0(UpnpPortBinding,1);
567         ms_mutex_init(&port->mutex, NULL);
568         port->state = LinphoneUpnpStateIdle;
569         port->local_addr[0] = '\0';
570         port->local_port = -1;
571         port->external_addr[0] = '\0';
572         port->external_port = -1;
573         port->ref = 1;
574         return port;
575 }
576
577 UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
578         UpnpPortBinding *new_port = NULL;
579         new_port = ms_new0(UpnpPortBinding,1);
580         memcpy(new_port, port, sizeof(UpnpPortBinding));
581         ms_mutex_init(&new_port->mutex, NULL);
582         new_port->ref = 1;
583         return new_port;
584 }
585
586 bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
587         return port1->protocol == port2->protocol && port1->local_port == port2->local_port &&
588                         port1->external_port && port2->external_port;
589 }
590
591 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
592         ms_mutex_lock(&port->mutex);
593         port->ref++;
594         ms_mutex_unlock(&port->mutex);
595         return port;
596 }
597
598 void upnp_port_binding_release(UpnpPortBinding *port) {
599         ms_mutex_lock(&port->mutex);
600         if(--port->ref == 0) {
601                 ms_mutex_unlock(&port->mutex);
602                 ms_mutex_destroy(&port->mutex);
603                 ms_free(port);
604                 return;
605         }
606         ms_mutex_unlock(&port->mutex);
607 }
608
609 /*
610  * uPnP Stream
611  */
612
613 UpnpStream* upnp_stream_new() {
614         UpnpStream *stream = ms_new0(UpnpStream,1);
615         stream->state = LinphoneUpnpStateIdle;
616         stream->rtp = upnp_port_binding_new();
617         stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
618         stream->rtcp = upnp_port_binding_new();
619         stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
620         return NULL;
621 }
622
623 void upnp_stream_destroy(UpnpStream* stream) {
624         upnp_port_binding_release(stream->rtp);
625         upnp_port_binding_release(stream->rtcp);
626         ms_free(stream);
627 }
628
629
630 /*
631  * uPnP Session
632  */
633
634 UpnpSession* upnp_session_new() {
635         UpnpSession *session = ms_new0(UpnpSession,1);
636         session->state = LinphoneUpnpStateIdle;
637         session->audio = upnp_stream_new();
638         session->video = upnp_stream_new();
639         return NULL;
640 }
641
642 void upnp_session_destroy(LinphoneCall* call) {
643         LinphoneCore *lc = call->core;
644
645         /* Remove bindings */
646         if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
647                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
648         }
649         if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
650                 upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
651         }
652         if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
653                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
654         }
655         if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
656                 upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
657         }
658
659         upnp_stream_destroy(call->upnp_session->audio);
660         upnp_stream_destroy(call->upnp_session->video);
661         ms_free(call->upnp_session);
662         call->upnp_session = NULL;
663 }
664
665
666 /*
667  * uPnP Config
668  */
669
670 MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
671         char protocol_str[4]; // TCP or UDP
672         upnp_igd_ip_protocol protocol;
673         int external_port;
674         int local_port;
675         MSList *retList = NULL;
676         UpnpPortBinding *port;
677         bool_t valid;
678         MSList *elem;
679         MSList *prev_elem;
680         LpItem *item;
681         LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
682         if(sec == NULL)
683                 return retList;
684
685         elem = sec->items;
686         while(elem != NULL) {
687                 item=(LpItem*)elem->data;
688                 valid = TRUE;
689                 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
690                         if(strcasecmp(protocol_str, "TCP") == 0) {
691                                 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
692                         } else if(strcasecmp(protocol_str, "UDP") == 0) {
693                                 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
694                         } else {
695                                 valid = FALSE;
696                         }
697                         if(valid) {
698                                 port = upnp_port_binding_new();
699                                 port->protocol = protocol;
700                                 port->external_port = external_port;
701                                 port->local_port = local_port;
702                                 retList = ms_list_append(retList, port);
703                         }
704                 } else {
705                         valid = FALSE;
706                 }
707                 prev_elem = elem;
708                 elem = ms_list_next(elem);
709                 if(!valid) {
710                         ms_warning("uPnP configuration invalid line: %s", item->key);
711                         lp_section_remove_item(sec, item);
712                 }
713         }
714
715         return retList;
716 }
717
718 int upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
719         UpnpContext *lupnp = &lc->upnp;
720         MSList *list = lupnp->pending_configs;
721         UpnpPortBinding *list_port;
722         bool_t remove;
723         bool_t add = TRUE;
724         while(list != NULL) {
725                 remove = FALSE;
726                 list_port = (UpnpPortBinding *)list->data;
727                 if(upnp_port_binding_equal(list_port, port) == TRUE) {
728                         if(list_port->state == LinphoneUpnpStateAdding) {
729                                 add = FALSE;
730                                 break;
731                         }
732                         if(list_port->state == LinphoneUpnpStateRemoving) {
733                                 remove = TRUE;
734                                 break;
735                         }
736                 }
737                 list = ms_list_next(list);
738         }
739
740         if(remove) {
741                 lupnp->pending_configs = ms_list_remove(list, list_port);
742         } else if(add) {
743                 list_port = upnp_port_binding_copy(port);
744                 list_port->state = LinphoneUpnpStateAdding;
745                 lupnp->pending_configs = ms_list_append(list, list_port);
746         }
747
748         return 0;
749 }
750
751 int upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
752         UpnpContext *lupnp = &lc->upnp;
753         MSList *list = lupnp->pending_configs;
754         UpnpPortBinding *list_port;
755         bool_t remove;
756         bool_t add = TRUE;
757         while(list != NULL) {
758                 remove = FALSE;
759                 list_port = (UpnpPortBinding *)list->data;
760                 if(upnp_port_binding_equal(list_port, port)) {
761                         if(list_port->state == LinphoneUpnpStateRemoving) {
762                                 add = FALSE;
763                                 break;
764                         }
765                         if(list_port->state == LinphoneUpnpStateAdding) {
766                                 remove = TRUE;
767                                 break;
768                         }
769                 }
770                 list = ms_list_next(list);
771         }
772
773         if(remove) {
774                 lupnp->pending_configs = ms_list_remove(list, list_port);
775         } else if(add) {
776                 list_port = upnp_port_binding_copy(port);
777                 list_port->state = LinphoneUpnpStateRemoving;
778                 lupnp->pending_configs = ms_list_append(list, list_port);
779         }
780
781         return 0;
782 }