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