3 Copyright (C) 2012 Belledonne Communications SARL
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.
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.
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.
23 #define UPNP_ADD_MAX_RETRY 4
24 #define UPNP_REMOVE_MAX_RETRY 4
25 #define UPNP_SECTION_NAME "uPnP"
27 /* Define private types */
28 typedef struct _LpItem{
33 typedef struct _LpSection{
38 typedef struct _LpConfig{
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);
55 typedef struct _UpnpPortBinding {
57 LinphoneUpnpState state;
58 upnp_igd_ip_protocol protocol;
59 char local_addr[LINPHONE_IPADDR_SIZE];
61 char external_addr[LINPHONE_IPADDR_SIZE];
69 typedef struct _UpnpStream {
71 UpnpPortBinding *rtcp;
72 LinphoneUpnpState state;
79 LinphoneUpnpState state;
84 upnp_igd_context *upnp_igd_ctxt;
85 UpnpPortBinding *sip_tcp;
86 UpnpPortBinding *sip_tls;
87 UpnpPortBinding *sip_udp;
88 LinphoneUpnpState state;
89 MSList *removing_configs;
90 MSList *adding_configs;
91 MSList *pending_bindings;
99 bool_t linphone_core_upnp_hook(void *data);
100 void linphone_core_upnp_refresh(UpnpContext *ctx);
102 UpnpPortBinding *linphone_upnp_port_binding_new();
103 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port);
104 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
105 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port);
106 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port);
107 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
108 void linphone_upnp_port_binding_release(UpnpPortBinding *port);
110 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc);
111 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
112 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port);
114 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
115 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port);
122 /* Convert uPnP IGD logs to ortp logs */
123 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
124 int ortp_level = ORTP_DEBUG;
126 case UPNP_IGD_MESSAGE:
127 ortp_level = ORTP_MESSAGE;
129 case UPNP_IGD_WARNING:
130 ortp_level = ORTP_DEBUG; // Too verbose otherwise
133 ortp_level = ORTP_DEBUG; // Too verbose otherwise
138 ortp_logv(ortp_level, fmt, list);
141 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
142 UpnpContext *lupnp = (UpnpContext *)cookie;
143 upnp_igd_port_mapping *mapping = NULL;
144 UpnpPortBinding *port_mapping = NULL;
145 const char *ip_address = NULL;
146 const char *connection_status = NULL;
147 bool_t nat_enabled = FALSE;
148 ms_mutex_lock(&lupnp->mutex);
149 LinphoneUpnpState old_state = lupnp->state;
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);
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;
166 ms_message("uPnP IGD: Connected");
167 lupnp->state = LinphoneUpnpStateOk;
168 if(old_state != LinphoneUpnpStateOk) {
169 linphone_core_upnp_refresh(lupnp);
175 case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
176 mapping = (upnp_igd_port_mapping *) arg;
177 port_mapping = (UpnpPortBinding*) mapping->cookie;
178 port_mapping->external_port = mapping->remote_port;
179 port_mapping->state = LinphoneUpnpStateOk;
180 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
181 linphone_upnp_config_add_port_binding(lupnp, port_mapping);
185 case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
186 mapping = (upnp_igd_port_mapping *) arg;
187 port_mapping = (UpnpPortBinding*) mapping->cookie;
188 port_mapping->external_port = -1; //Force random external port
189 if(linphone_upnp_context_send_add_port_binding(lupnp, port_mapping) != 0) {
190 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
195 case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
196 mapping = (upnp_igd_port_mapping *) arg;
197 port_mapping = (UpnpPortBinding*) mapping->cookie;
198 port_mapping->state = LinphoneUpnpStateIdle;
199 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
200 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
204 case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
205 mapping = (upnp_igd_port_mapping *) arg;
206 port_mapping = (UpnpPortBinding*) mapping->cookie;
207 if(linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping) != 0) {
208 linphone_upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
209 linphone_upnp_config_remove_port_binding(lupnp, port_mapping);
218 if(port_mapping != NULL) {
220 * Execute delayed actions
222 if(port_mapping->to_remove) {
223 if(port_mapping->state == LinphoneUpnpStateOk) {
224 port_mapping->to_remove = FALSE;
225 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
226 } else if(port_mapping->state == LinphoneUpnpStateKo) {
227 port_mapping->to_remove = FALSE;
230 if(port_mapping->to_add) {
231 if(port_mapping->state == LinphoneUpnpStateIdle || port_mapping->state == LinphoneUpnpStateKo) {
232 port_mapping->to_add = FALSE;
233 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
237 lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
238 linphone_upnp_port_binding_release(port_mapping);
242 * If there is no pending binding emit a signal
244 if(lupnp->pending_bindings == NULL) {
245 pthread_cond_signal(&lupnp->empty_cond);
247 ms_mutex_unlock(&lupnp->mutex);
255 UpnpContext* linphone_upnp_context_new(LinphoneCore *lc) {
256 LCSipTransports transport;
257 UpnpContext *lupnp = (UpnpContext *)ms_new0(UpnpContext,1);
258 const char *ip_address;
260 ms_mutex_init(&lupnp->mutex, NULL);
261 ms_cond_init(&lupnp->empty_cond, NULL);
264 lupnp->pending_bindings = NULL;
265 lupnp->adding_configs = NULL;
266 lupnp->removing_configs = NULL;
267 lupnp->state = LinphoneUpnpStateIdle;
268 ms_message("uPnP IGD: New %p for core %p", lupnp, lc);
270 linphone_core_get_sip_transports(lc, &transport);
271 if(transport.udp_port != 0) {
272 lupnp->sip_udp = linphone_upnp_port_binding_new();
273 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
274 lupnp->sip_udp->local_port = transport.udp_port;
275 lupnp->sip_udp->external_port = transport.udp_port;
277 lupnp->sip_udp = NULL;
279 if(transport.tcp_port != 0) {
280 lupnp->sip_tcp = linphone_upnp_port_binding_new();
281 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
282 lupnp->sip_tcp->local_port = transport.tcp_port;
283 lupnp->sip_tcp->external_port = transport.tcp_port;
285 lupnp->sip_tcp = NULL;
287 if(transport.tls_port != 0) {
288 lupnp->sip_tls = linphone_upnp_port_binding_new();
289 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
290 lupnp->sip_tls->local_port = transport.tls_port;
291 lupnp->sip_tls->external_port = transport.tls_port;
293 lupnp->sip_tls = NULL;
296 linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lupnp);
298 lupnp->upnp_igd_ctxt = NULL;
299 lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lupnp);
300 if(lupnp->upnp_igd_ctxt == NULL) {
301 lupnp->state = LinphoneUpnpStateKo;
302 ms_error("Can't create uPnP IGD context");
306 ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
307 if(lupnp->sip_udp != NULL) {
308 strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
310 if(lupnp->sip_tcp != NULL) {
311 strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
313 if(lupnp->sip_tls != NULL) {
314 strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
317 lupnp->state = LinphoneUpnpStatePending;
321 void linphone_upnp_context_destroy(UpnpContext *lupnp) {
323 * Not need, all hooks are removed before
324 * linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
327 /* Send port binding removes */
328 if(lupnp->sip_udp != NULL) {
329 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_udp);
330 lupnp->sip_udp = NULL;
332 if(lupnp->sip_tcp != NULL) {
333 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tcp);
334 lupnp->sip_tcp = NULL;
336 if(lupnp->sip_tls != NULL) {
337 linphone_upnp_context_send_remove_port_binding(lupnp, lupnp->sip_tls);
338 lupnp->sip_tcp = NULL;
341 /* Wait all pending bindings are done */
342 ms_message("uPnP IGD: Wait all pending port bindings ...");
343 ms_mutex_lock(&lupnp->mutex);
344 ms_cond_wait(&lupnp->empty_cond, &lupnp->mutex);
345 ms_mutex_unlock(&lupnp->mutex);
347 if(lupnp->upnp_igd_ctxt != NULL) {
348 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
351 /* Run one time the hook for configuration update */
352 linphone_core_upnp_hook(lupnp);
354 /* Release port bindings */
355 if(lupnp->sip_udp != NULL) {
356 linphone_upnp_port_binding_release(lupnp->sip_udp);
357 lupnp->sip_udp = NULL;
359 if(lupnp->sip_tcp != NULL) {
360 linphone_upnp_port_binding_release(lupnp->sip_tcp);
361 lupnp->sip_tcp = NULL;
363 if(lupnp->sip_tls != NULL) {
364 linphone_upnp_port_binding_release(lupnp->sip_tls);
365 lupnp->sip_tcp = NULL;
369 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
370 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
371 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
372 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
373 ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))linphone_upnp_port_binding_release);
374 lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
376 ms_mutex_destroy(&lupnp->mutex);
377 ms_cond_destroy(&lupnp->empty_cond);
379 ms_message("uPnP IGD: destroy %p", lupnp);
383 LinphoneUpnpState linphone_upnp_context_get_state(UpnpContext *ctx) {
387 const char* linphone_upnp_context_get_external_ipaddress(UpnpContext *ctx) {
388 return upnp_igd_get_external_ipaddress(ctx->upnp_igd_ctxt);
391 int linphone_upnp_context_send_add_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
392 upnp_igd_port_mapping mapping;
393 char description[128];
396 // Compute port binding state
397 if(port->state != LinphoneUpnpStateAdding) {
398 port->to_remove = FALSE;
399 switch(port->state) {
400 case LinphoneUpnpStateKo:
401 case LinphoneUpnpStateIdle: {
403 port->state = LinphoneUpnpStateAdding;
406 case LinphoneUpnpStateRemoving: {
416 if(port->retry >= UPNP_ADD_MAX_RETRY) {
419 mapping.cookie = linphone_upnp_port_binding_retain(port);
420 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
422 mapping.local_port = port->local_port;
423 mapping.local_host = port->local_addr;
424 if(port->external_port == -1)
425 mapping.remote_port = rand()%(0xffff - 1024) + 1024;
427 mapping.remote_port = port->external_port;
428 mapping.remote_host = "";
429 snprintf(description, 128, "%s %s at %s:%d",
431 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
432 port->local_addr, port->local_port);
433 mapping.description = description;
434 mapping.protocol = port->protocol;
437 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to add port binding", port);
438 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
441 port->state = LinphoneUpnpStateKo;
446 int linphone_upnp_context_send_remove_port_binding(UpnpContext *lupnp, UpnpPortBinding *port) {
447 upnp_igd_port_mapping mapping;
450 // Compute port binding state
451 if(port->state != LinphoneUpnpStateRemoving) {
452 port->to_add = FALSE;
453 switch(port->state) {
454 case LinphoneUpnpStateOk: {
456 port->state = LinphoneUpnpStateRemoving;
459 case LinphoneUpnpStateAdding: {
460 port->to_remove = TRUE;
469 if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
472 mapping.cookie = linphone_upnp_port_binding_retain(port);
473 lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
475 mapping.remote_port = port->external_port;
476 mapping.remote_host = "";
477 mapping.protocol = port->protocol;
479 linphone_upnp_port_binding_log(ORTP_MESSAGE, "Try to remove port binding", port);
480 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
483 port->state = LinphoneUpnpStateKo;
489 * uPnP Core interfaces
492 int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
493 LinphoneCore *lc = call->core;
494 UpnpContext *lupnp = lc->upnp;
496 const char *local_addr, *external_addr;
502 ms_mutex_lock(&lupnp->mutex);
503 // Don't handle when the call
504 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
506 local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
507 external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
512 strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
513 strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
514 call->upnp_session->audio->rtp->local_port = call->audio_port;
515 if(call->upnp_session->audio->rtp->external_port == -1) {
516 call->upnp_session->audio->rtp->external_port = call->audio_port;
518 strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
519 strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
520 call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
521 if(call->upnp_session->audio->rtcp->external_port == -1) {
522 call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
525 // Add audio port binding
526 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtp);
527 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->audio->rtcp);
529 // Remove audio port binding
530 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtp);
531 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->audio->rtcp);
537 strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
538 strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
539 call->upnp_session->video->rtp->local_port = call->video_port;
540 if(call->upnp_session->video->rtp->external_port == -1) {
541 call->upnp_session->video->rtp->external_port = call->video_port;
543 strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
544 strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
545 call->upnp_session->video->rtcp->local_port = call->video_port+1;
546 if(call->upnp_session->video->rtcp->external_port == -1) {
547 call->upnp_session->video->rtcp->external_port = call->video_port+1;
550 // Add video port binding
551 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtp);
552 linphone_upnp_context_send_add_port_binding(lupnp, call->upnp_session->video->rtcp);
554 // Remove video port binding
555 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtp);
556 linphone_upnp_context_send_remove_port_binding(lupnp, call->upnp_session->video->rtcp);
560 ms_mutex_unlock(&lupnp->mutex);
563 * Update uPnP call state
565 linphone_upnp_call_process(call);
571 int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
572 bool_t audio = FALSE;
573 bool_t video = FALSE;
575 const SalStreamDescription *stream;
577 for (i = 0; i < md->nstreams; i++) {
578 stream = &md->streams[i];
579 if(stream->type == SalAudio) {
581 } else if(stream->type == SalVideo) {
586 return linphone_core_update_upnp_audio_video(call, audio, video);
589 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
590 return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
593 int linphone_upnp_call_process(LinphoneCall *call) {
594 LinphoneCore *lc = call->core;
595 UpnpContext *lupnp = lc->upnp;
597 LinphoneUpnpState oldState;
603 ms_mutex_lock(&lupnp->mutex);
605 // Don't handle when the call
606 if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
612 if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
613 (call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
614 call->upnp_session->audio->state = LinphoneUpnpStateOk;
615 } else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
616 call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
617 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
618 call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
619 call->upnp_session->audio->state = LinphoneUpnpStatePending;
620 } else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
621 call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
622 call->upnp_session->audio->state = LinphoneUpnpStateKo;
624 call->upnp_session->audio->state = LinphoneUpnpStateIdle;
630 if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
631 (call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
632 call->upnp_session->video->state = LinphoneUpnpStateOk;
633 } else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
634 call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
635 call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
636 call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
637 call->upnp_session->video->state = LinphoneUpnpStatePending;
638 } else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
639 call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
640 call->upnp_session->video->state = LinphoneUpnpStateKo;
642 call->upnp_session->video->state = LinphoneUpnpStateIdle;
646 * Update session state
648 oldState = call->upnp_session->state;
649 if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
650 call->upnp_session->video->state == LinphoneUpnpStateOk) {
651 call->upnp_session->state = LinphoneUpnpStateOk;
652 } else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
653 call->upnp_session->video->state == LinphoneUpnpStatePending) {
654 call->upnp_session->state = LinphoneUpnpStatePending;
655 } else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
656 call->upnp_session->video->state == LinphoneUpnpStateKo) {
657 call->upnp_session->state = LinphoneUpnpStateKo;
659 call->upnp_session->state = LinphoneUpnpStateIdle;
662 /* When change is done proceed update */
663 if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
664 (call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
665 if(call->upnp_session->state == LinphoneUpnpStateOk)
666 ms_message("uPnP IGD: uPnP for Call %p is ok", call);
668 ms_message("uPnP IGD: uPnP for Call %p is ko", call);
670 switch (call->state) {
671 case LinphoneCallUpdating:
672 linphone_core_start_update_call(lc, call);
674 case LinphoneCallUpdatedByRemote:
675 linphone_core_start_accept_call_update(lc, call);
677 case LinphoneCallOutgoingInit:
678 linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
680 case LinphoneCallIdle:
681 linphone_core_notify_incoming_call(lc, call);
689 ms_mutex_unlock(&lupnp->mutex);
693 void linphone_core_upnp_refresh(UpnpContext *lupnp) {
694 MSList *global_list = NULL;
698 UpnpPortBinding *port_mapping, *port_mapping2;
700 ms_message("uPnP IGD: Refresh mappings");
702 /* Remove context port bindings */
703 if(lupnp->sip_udp != NULL) {
704 global_list = ms_list_append(global_list, lupnp->sip_udp);
706 if(lupnp->sip_tcp != NULL) {
707 global_list = ms_list_append(global_list, lupnp->sip_tcp);
709 if(lupnp->sip_tls != NULL) {
710 global_list = ms_list_append(global_list, lupnp->sip_tls);
713 /* Remove call port bindings */
714 list = lupnp->lc->calls;
715 while(list != NULL) {
716 call = (LinphoneCall *)list->data;
717 if(call->upnp_session != NULL) {
718 global_list = ms_list_append(global_list, call->upnp_session->audio->rtp);
719 global_list = ms_list_append(global_list, call->upnp_session->audio->rtcp);
720 global_list = ms_list_append(global_list, call->upnp_session->video->rtp);
721 global_list = ms_list_append(global_list, call->upnp_session->video->rtcp);
726 // Remove port binding configurations
727 list = linphone_upnp_config_list_port_bindings(lupnp->lc->config);
728 for(item = list;item != NULL; item = item->next) {
729 port_mapping = (UpnpPortBinding *)item->data;
730 port_mapping2 = linphone_upnp_port_binding_equivalent_in_list(global_list, port_mapping);
731 if(port_mapping2 == NULL) {
732 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
733 } else if(port_mapping2->state == LinphoneUpnpStateIdle){
734 /* Force to remove */
735 port_mapping2->state = LinphoneUpnpStateOk;
738 ms_list_for_each(list, (void (*)(void*))linphone_upnp_port_binding_release);
739 list = ms_list_free(list);
742 // (Re)Add removed port bindings
744 while(list != NULL) {
745 port_mapping = (UpnpPortBinding *)list->data;
746 linphone_upnp_context_send_remove_port_binding(lupnp, port_mapping);
747 linphone_upnp_context_send_add_port_binding(lupnp, port_mapping);
750 global_list = ms_list_free(global_list);
753 bool_t linphone_core_upnp_hook(void *data) {
756 UpnpPortBinding *port_mapping;
757 UpnpContext *lupnp = (UpnpContext *)data;
758 ms_mutex_lock(&lupnp->mutex);
761 for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
762 port_mapping = (UpnpPortBinding *)item->data;
763 snprintf(key, sizeof(key), "%s-%d-%d",
764 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
765 port_mapping->external_port,
766 port_mapping->local_port);
767 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, "uPnP");
768 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
770 ms_list_for_each(lupnp->adding_configs,(void (*)(void*))linphone_upnp_port_binding_release);
771 lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
774 for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
775 port_mapping = (UpnpPortBinding *)item->data;
776 snprintf(key, sizeof(key), "%s-%d-%d",
777 (port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
778 port_mapping->external_port,
779 port_mapping->local_port);
780 lp_config_set_string(lupnp->lc->config, UPNP_SECTION_NAME, key, NULL);
781 linphone_upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
783 ms_list_for_each(lupnp->removing_configs,(void (*)(void*))linphone_upnp_port_binding_release);
784 lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
786 ms_mutex_unlock(&lupnp->mutex);
790 int linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
792 SalStreamDescription *stream;
793 UpnpStream *upnpStream;
795 for (i = 0; i < desc->nstreams; i++) {
796 stream = &desc->streams[i];
798 if(stream->type == SalAudio) {
799 upnpStream = session->audio;
800 } else if(stream->type == SalVideo) {
801 upnpStream = session->video;
803 if(upnpStream != NULL) {
804 if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
805 strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
806 stream->rtp_port = upnpStream->rtp->external_port;
808 if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
809 strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
810 stream->rtcp_port = upnpStream->rtcp->external_port;
822 UpnpPortBinding *linphone_upnp_port_binding_new() {
823 UpnpPortBinding *port = NULL;
824 port = ms_new0(UpnpPortBinding,1);
825 ms_mutex_init(&port->mutex, NULL);
826 port->state = LinphoneUpnpStateIdle;
827 port->local_addr[0] = '\0';
828 port->local_port = -1;
829 port->external_addr[0] = '\0';
830 port->external_port = -1;
831 port->to_remove = FALSE;
832 port->to_add = FALSE;
837 UpnpPortBinding *linphone_upnp_port_binding_copy(const UpnpPortBinding *port) {
838 UpnpPortBinding *new_port = NULL;
839 new_port = ms_new0(UpnpPortBinding,1);
840 memcpy(new_port, port, sizeof(UpnpPortBinding));
841 ms_mutex_init(&new_port->mutex, NULL);
846 void linphone_upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
847 if(strlen(port->local_addr)) {
848 ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
849 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
854 ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
855 (port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
861 bool_t linphone_upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
862 return port1->protocol == port2->protocol &&
863 port1->local_port == port2->local_port &&
864 port1->external_port == port2->external_port;
867 UpnpPortBinding *linphone_upnp_port_binding_equivalent_in_list(MSList *list, const UpnpPortBinding *port) {
868 UpnpPortBinding *port_mapping;
869 while(list != NULL) {
870 port_mapping = (UpnpPortBinding *)list->data;
871 if(linphone_upnp_port_binding_equal(port, port_mapping)) {
880 UpnpPortBinding *linphone_upnp_port_binding_retain(UpnpPortBinding *port) {
881 ms_mutex_lock(&port->mutex);
883 ms_mutex_unlock(&port->mutex);
887 void linphone_upnp_port_binding_release(UpnpPortBinding *port) {
888 ms_mutex_lock(&port->mutex);
889 if(--port->ref == 0) {
890 ms_mutex_unlock(&port->mutex);
891 ms_mutex_destroy(&port->mutex);
895 ms_mutex_unlock(&port->mutex);
903 UpnpStream* linphone_upnp_stream_new() {
904 UpnpStream *stream = ms_new0(UpnpStream,1);
905 stream->state = LinphoneUpnpStateIdle;
906 stream->rtp = linphone_upnp_port_binding_new();
907 stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
908 stream->rtcp = linphone_upnp_port_binding_new();
909 stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
913 void linphone_upnp_stream_destroy(UpnpStream* stream) {
914 linphone_upnp_port_binding_release(stream->rtp);
916 linphone_upnp_port_binding_release(stream->rtcp);
926 UpnpSession* linphone_upnp_session_new(LinphoneCall* call) {
927 UpnpSession *session = ms_new0(UpnpSession,1);
928 session->call = call;
929 session->state = LinphoneUpnpStateIdle;
930 session->audio = linphone_upnp_stream_new();
931 session->video = linphone_upnp_stream_new();
935 void linphone_upnp_session_destroy(UpnpSession *session) {
936 LinphoneCore *lc = session->call->core;
938 if(lc->upnp != NULL) {
939 /* Remove bindings */
940 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtp);
941 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->audio->rtcp);
942 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtp);
943 linphone_upnp_context_send_remove_port_binding(lc->upnp, session->video->rtcp);
946 linphone_upnp_stream_destroy(session->audio);
947 linphone_upnp_stream_destroy(session->video);
951 LinphoneUpnpState linphone_upnp_session_get_state(UpnpSession *session) {
952 return session->state;
959 MSList *linphone_upnp_config_list_port_bindings(struct _LpConfig *lpc) {
960 char protocol_str[4]; // TCP or UDP
961 upnp_igd_ip_protocol protocol;
964 MSList *retList = NULL;
965 UpnpPortBinding *port;
969 LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
974 while(elem != NULL) {
975 item=(LpItem*)elem->data;
977 if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
978 if(strcasecmp(protocol_str, "TCP") == 0) {
979 protocol = UPNP_IGD_IP_PROTOCOL_TCP;
980 } else if(strcasecmp(protocol_str, "UDP") == 0) {
981 protocol = UPNP_IGD_IP_PROTOCOL_UDP;
986 port = linphone_upnp_port_binding_new();
987 port->state = LinphoneUpnpStateOk;
988 port->protocol = protocol;
989 port->external_port = external_port;
990 port->local_port = local_port;
991 retList = ms_list_append(retList, port);
996 elem = ms_list_next(elem);
998 ms_warning("uPnP configuration invalid line: %s", item->key);
999 lp_section_remove_item(sec, item);
1006 void linphone_upnp_config_add_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1008 UpnpPortBinding *list_port;
1010 list = lupnp->removing_configs;
1011 while(list != NULL) {
1012 list_port = (UpnpPortBinding *)list->data;
1013 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1014 lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
1015 linphone_upnp_port_binding_release(list_port);
1018 list = ms_list_next(list);
1021 list = lupnp->adding_configs;
1022 while(list != NULL) {
1023 list_port = (UpnpPortBinding *)list->data;
1024 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1027 list = ms_list_next(list);
1030 list_port = linphone_upnp_port_binding_copy(port);
1031 lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
1034 void linphone_upnp_config_remove_port_binding(UpnpContext *lupnp, const UpnpPortBinding *port) {
1036 UpnpPortBinding *list_port;
1038 list = lupnp->adding_configs;
1039 while(list != NULL) {
1040 list_port = (UpnpPortBinding *)list->data;
1041 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1042 lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
1043 linphone_upnp_port_binding_release(list_port);
1046 list = ms_list_next(list);
1049 list = lupnp->removing_configs;
1050 while(list != NULL) {
1051 list_port = (UpnpPortBinding *)list->data;
1052 if(linphone_upnp_port_binding_equal(list_port, port) == TRUE) {
1055 list = ms_list_next(list);
1058 list_port = linphone_upnp_port_binding_copy(port);
1059 lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);