mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-18 11:38:08 +00:00
946 lines
31 KiB
C
946 lines
31 KiB
C
/*
|
|
linphone
|
|
Copyright (C) 2012 Belledonne Communications SARL
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "upnp.h"
|
|
#include "private.h"
|
|
|
|
#define UPNP_ADD_MAX_RETRY 4
|
|
#define UPNP_REMOVE_MAX_RETRY 4
|
|
#define UPNP_SECTION_NAME "uPnP"
|
|
|
|
/* Define private types */
|
|
typedef struct _LpItem{
|
|
char *key;
|
|
char *value;
|
|
} LpItem;
|
|
|
|
typedef struct _LpSection{
|
|
char *name;
|
|
MSList *items;
|
|
} LpSection;
|
|
|
|
typedef struct _LpConfig{
|
|
FILE *file;
|
|
char *filename;
|
|
MSList *sections;
|
|
int modified;
|
|
int readonly;
|
|
} LpConfig;
|
|
|
|
/* Declare private functions */
|
|
LpSection *lp_config_find_section(LpConfig *lpconfig, const char *name);
|
|
void lp_section_remove_item(LpSection *sec, LpItem *item);
|
|
void lp_config_set_string(LpConfig *lpconfig,const char *section, const char *key, const char *value);
|
|
|
|
bool_t linphone_core_upnp_hook(void *data);
|
|
|
|
UpnpPortBinding *upnp_port_binding_new();
|
|
UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port);
|
|
bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2);
|
|
UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port);
|
|
void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port);
|
|
void upnp_port_binding_release(UpnpPortBinding *port);
|
|
|
|
MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc);
|
|
void upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
|
|
void upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port);
|
|
|
|
int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
|
|
int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
|
|
|
|
|
|
/**
|
|
* uPnP Callbacks
|
|
*/
|
|
|
|
/* Convert uPnP IGD logs to ortp logs */
|
|
void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
|
|
int ortp_level = ORTP_DEBUG;
|
|
switch(level) {
|
|
case UPNP_IGD_MESSAGE:
|
|
ortp_level = ORTP_MESSAGE;
|
|
break;
|
|
case UPNP_IGD_WARNING:
|
|
ortp_level = ORTP_DEBUG; // Too verbose otherwise
|
|
break;
|
|
case UPNP_IGD_ERROR:
|
|
ortp_level = ORTP_DEBUG; // Too verbose otherwise
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
ortp_logv(ortp_level, fmt, list);
|
|
}
|
|
|
|
void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
|
|
LinphoneCore *lc = (LinphoneCore *)cookie;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
upnp_igd_port_mapping *mapping = NULL;
|
|
UpnpPortBinding *port_mapping = NULL;
|
|
const char *ip_address = NULL;
|
|
const char *connection_status = NULL;
|
|
bool_t nat_enabled = FALSE;
|
|
ms_mutex_lock(&lupnp->mutex);
|
|
|
|
switch(event) {
|
|
case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
|
|
case UPNP_IGD_NAT_ENABLED_CHANGED:
|
|
case UPNP_IGD_CONNECTION_STATUS_CHANGED:
|
|
ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
|
|
connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
|
|
nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
|
|
|
|
if(ip_address == NULL || connection_status == NULL) {
|
|
ms_message("uPnP IGD: Pending");
|
|
lupnp->state = LinphoneUpnpStatePending;
|
|
} else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
|
|
ms_message("uPnP IGD: Not Available");
|
|
lupnp->state = LinphoneUpnpStateNotAvailable;
|
|
} else {
|
|
ms_message("uPnP IGD: Connected");
|
|
if(lupnp->state != LinphoneUpnpStateOk) {
|
|
lupnp->clean = TRUE; // Remove saved port mapping configurations
|
|
}
|
|
lupnp->state = LinphoneUpnpStateOk;
|
|
}
|
|
|
|
break;
|
|
|
|
case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
|
|
mapping = (upnp_igd_port_mapping *) arg;
|
|
port_mapping = (UpnpPortBinding*) mapping->cookie;
|
|
port_mapping->external_port = mapping->remote_port;
|
|
port_mapping->state = LinphoneUpnpStateOk;
|
|
upnp_port_binding_log(ORTP_MESSAGE, "Added port binding", port_mapping);
|
|
upnp_config_add_port_binding(lc, port_mapping);
|
|
|
|
lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
|
|
upnp_port_binding_release(port_mapping);
|
|
break;
|
|
|
|
case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
|
|
mapping = (upnp_igd_port_mapping *) arg;
|
|
port_mapping = (UpnpPortBinding*) mapping->cookie;
|
|
if(upnp_context_send_add_port_binding(lc, port_mapping) != 0) {
|
|
upnp_port_binding_log(ORTP_ERROR, "Can't add port binding", port_mapping);
|
|
}
|
|
|
|
lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
|
|
upnp_port_binding_release(port_mapping);
|
|
break;
|
|
|
|
case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
|
|
mapping = (upnp_igd_port_mapping *) arg;
|
|
port_mapping = (UpnpPortBinding*) mapping->cookie;
|
|
port_mapping->state = LinphoneUpnpStateIdle;
|
|
upnp_port_binding_log(ORTP_MESSAGE, "Removed port binding", port_mapping);
|
|
upnp_config_remove_port_binding(lc, port_mapping);
|
|
|
|
lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
|
|
upnp_port_binding_release(port_mapping);
|
|
break;
|
|
|
|
case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
|
|
mapping = (upnp_igd_port_mapping *) arg;
|
|
port_mapping = (UpnpPortBinding*) mapping->cookie;
|
|
if(upnp_context_send_remove_port_binding(lc, port_mapping) != 0) {
|
|
upnp_port_binding_log(ORTP_ERROR, "Can't remove port binding", port_mapping);
|
|
upnp_config_remove_port_binding(lc, port_mapping);
|
|
}
|
|
|
|
lupnp->pending_bindings = ms_list_remove(lupnp->pending_bindings, port_mapping);
|
|
upnp_port_binding_release(port_mapping);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(lupnp->pending_bindings == NULL) {
|
|
if(lupnp->cleaning == TRUE) {
|
|
lupnp->emit = TRUE; // Emit port bindings
|
|
lupnp->cleaning = FALSE;
|
|
}
|
|
pthread_cond_signal(&lupnp->cond);
|
|
}
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
}
|
|
|
|
|
|
/**
|
|
* uPnP Context
|
|
*/
|
|
|
|
int upnp_context_init(LinphoneCore *lc) {
|
|
LCSipTransports transport;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
const char *ip_address;
|
|
|
|
ms_mutex_init(&lupnp->mutex, NULL);
|
|
ms_cond_init(&lupnp->cond, NULL);
|
|
|
|
lupnp->pending_bindings = NULL;
|
|
lupnp->adding_configs = NULL;
|
|
lupnp->removing_configs = NULL;
|
|
lupnp->clean = FALSE;
|
|
lupnp->cleaning = FALSE;
|
|
lupnp->emit = FALSE;
|
|
lupnp->state = LinphoneUpnpStateIdle;
|
|
ms_message("uPnP IGD: Init");
|
|
|
|
linphone_core_get_sip_transports(lc, &transport);
|
|
if(transport.udp_port != 0) {
|
|
lupnp->sip_udp = upnp_port_binding_new();
|
|
lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
lupnp->sip_udp->local_port = transport.udp_port;
|
|
lupnp->sip_udp->external_port = transport.udp_port;
|
|
} else {
|
|
lupnp->sip_udp = NULL;
|
|
}
|
|
if(transport.tcp_port != 0) {
|
|
lupnp->sip_tcp = upnp_port_binding_new();
|
|
lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
|
|
lupnp->sip_tcp->local_port = transport.tcp_port;
|
|
lupnp->sip_tcp->external_port = transport.tcp_port;
|
|
} else {
|
|
lupnp->sip_tcp = NULL;
|
|
}
|
|
if(transport.tls_port != 0) {
|
|
lupnp->sip_tls = upnp_port_binding_new();
|
|
lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
|
|
lupnp->sip_tls->local_port = transport.tls_port;
|
|
lupnp->sip_tls->external_port = transport.tls_port;
|
|
} else {
|
|
lupnp->sip_tls = NULL;
|
|
}
|
|
|
|
linphone_core_add_iterate_hook(lc, linphone_core_upnp_hook, lc);
|
|
|
|
lupnp->upnp_igd_ctxt = NULL;
|
|
lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
|
|
if(lupnp->upnp_igd_ctxt == NULL) {
|
|
lupnp->state = LinphoneUpnpStateKo;
|
|
ms_error("Can't create uPnP IGD context");
|
|
return -1;
|
|
}
|
|
|
|
ip_address = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
|
|
if(lupnp->sip_udp != NULL) {
|
|
strncpy(lupnp->sip_udp->local_addr, ip_address, sizeof(lupnp->sip_udp->local_addr));
|
|
}
|
|
if(lupnp->sip_tcp != NULL) {
|
|
strncpy(lupnp->sip_tcp->local_addr, ip_address, sizeof(lupnp->sip_tcp->local_addr));
|
|
}
|
|
if(lupnp->sip_tls != NULL) {
|
|
strncpy(lupnp->sip_tls->local_addr, ip_address, sizeof(lupnp->sip_tls->local_addr));
|
|
}
|
|
|
|
lupnp->state = LinphoneUpnpStatePending;
|
|
return 0;
|
|
}
|
|
|
|
void upnp_context_uninit(LinphoneCore *lc) {
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
|
|
/*
|
|
* Not need, all hooks are removed before
|
|
* linphone_core_remove_iterate_hook(lc, linphone_core_upnp_hook, lc);
|
|
*/
|
|
|
|
/* Send port binding removes */
|
|
if(lupnp->sip_udp != NULL) {
|
|
upnp_context_send_remove_port_binding(lc, lupnp->sip_udp);
|
|
lupnp->sip_udp = NULL;
|
|
}
|
|
if(lupnp->sip_tcp != NULL) {
|
|
upnp_context_send_remove_port_binding(lc, lupnp->sip_tcp);
|
|
lupnp->sip_tcp = NULL;
|
|
}
|
|
if(lupnp->sip_tls != NULL) {
|
|
upnp_context_send_remove_port_binding(lc, lupnp->sip_tls);
|
|
lupnp->sip_tcp = NULL;
|
|
}
|
|
|
|
/* Wait all pending bindings are done */
|
|
ms_message("uPnP IGD: Wait all pending port bindings ...");
|
|
ms_mutex_lock(&lupnp->mutex);
|
|
ms_cond_wait(&lupnp->cond, &lupnp->mutex);
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
|
|
if(lupnp->upnp_igd_ctxt != NULL) {
|
|
upnp_igd_destroy(lupnp->upnp_igd_ctxt);
|
|
}
|
|
|
|
/* Run one time the hook for configuration update */
|
|
linphone_core_upnp_hook(lc);
|
|
|
|
/* Release port bindings */
|
|
if(lupnp->sip_udp != NULL) {
|
|
upnp_port_binding_release(lupnp->sip_udp);
|
|
lupnp->sip_udp = NULL;
|
|
}
|
|
if(lupnp->sip_tcp != NULL) {
|
|
upnp_port_binding_release(lupnp->sip_tcp);
|
|
lupnp->sip_tcp = NULL;
|
|
}
|
|
if(lupnp->sip_tls != NULL) {
|
|
upnp_port_binding_release(lupnp->sip_tls);
|
|
lupnp->sip_tcp = NULL;
|
|
}
|
|
|
|
/* Release lists */
|
|
ms_list_for_each(lupnp->adding_configs,(void (*)(void*))upnp_port_binding_release);
|
|
lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
|
|
ms_list_for_each(lupnp->removing_configs,(void (*)(void*))upnp_port_binding_release);
|
|
lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
|
|
ms_list_for_each(lupnp->pending_bindings,(void (*)(void*))upnp_port_binding_release);
|
|
lupnp->pending_bindings = ms_list_free(lupnp->pending_bindings);
|
|
|
|
ms_mutex_destroy(&lupnp->mutex);
|
|
ms_cond_destroy(&lupnp->cond);
|
|
|
|
ms_message("uPnP IGD: Uninit");
|
|
}
|
|
|
|
int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
upnp_igd_port_mapping mapping;
|
|
char description[128];
|
|
int ret;
|
|
if(port->state == LinphoneUpnpStateIdle) {
|
|
port->retry = 0;
|
|
port->state = LinphoneUpnpStateAdding;
|
|
} else if(port->state != LinphoneUpnpStateAdding) {
|
|
ms_error("uPnP: try to add a port binding in wrong state: %d", port->state);
|
|
return -2;
|
|
}
|
|
|
|
if(port->retry >= UPNP_ADD_MAX_RETRY) {
|
|
ret = -1;
|
|
} else {
|
|
mapping.cookie = upnp_port_binding_retain(port);
|
|
lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
|
|
|
|
mapping.local_port = port->local_port;
|
|
mapping.local_host = port->local_addr;
|
|
if(port->external_port == -1)
|
|
mapping.remote_port = rand()%(0xffff - 1024) + 1024;
|
|
else
|
|
mapping.remote_port = port->external_port;
|
|
mapping.remote_host = "";
|
|
snprintf(description, 128, "%s %s at %s:%d",
|
|
PACKAGE_NAME,
|
|
(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP": "UDP",
|
|
port->local_addr, port->local_port);
|
|
mapping.description = description;
|
|
mapping.protocol = port->protocol;
|
|
|
|
port->retry++;
|
|
upnp_port_binding_log(ORTP_DEBUG, "Adding port binding...", port);
|
|
ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
|
|
}
|
|
if(ret != 0) {
|
|
port->state = LinphoneUpnpStateKo;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
upnp_igd_port_mapping mapping;
|
|
int ret;
|
|
if(port->state == LinphoneUpnpStateOk) {
|
|
port->retry = 0;
|
|
port->state = LinphoneUpnpStateRemoving;
|
|
} else if(port->state != LinphoneUpnpStateRemoving) {
|
|
ms_error("uPnP: try to remove a port binding in wrong state: %d", port->state);
|
|
return -2;
|
|
}
|
|
|
|
if(port->retry >= UPNP_REMOVE_MAX_RETRY) {
|
|
ret = -1;
|
|
} else {
|
|
mapping.cookie = upnp_port_binding_retain(port);
|
|
lupnp->pending_bindings = ms_list_append(lupnp->pending_bindings, mapping.cookie);
|
|
|
|
mapping.remote_port = port->external_port;
|
|
mapping.remote_host = "";
|
|
mapping.protocol = port->protocol;
|
|
port->retry++;
|
|
upnp_port_binding_log(ORTP_DEBUG, "Removing port binding...", port);
|
|
ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
|
|
}
|
|
if(ret != 0) {
|
|
port->state = LinphoneUpnpStateKo;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* uPnP Core interfaces
|
|
*/
|
|
|
|
int linphone_core_update_upnp_audio_video(LinphoneCall *call, bool_t audio, bool_t video) {
|
|
LinphoneCore *lc = call->core;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
int ret = -1;
|
|
const char *local_addr, *external_addr;
|
|
|
|
ms_mutex_lock(&lupnp->mutex);
|
|
// Don't handle when the call
|
|
if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
|
|
ret = 0;
|
|
local_addr = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
|
|
external_addr = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
|
|
|
|
/*
|
|
* Audio part
|
|
*/
|
|
strncpy(call->upnp_session->audio->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
|
|
strncpy(call->upnp_session->audio->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
|
|
call->upnp_session->audio->rtp->local_port = call->audio_port;
|
|
if(call->upnp_session->audio->rtp->external_port == -1) {
|
|
call->upnp_session->audio->rtp->external_port = call->audio_port;
|
|
}
|
|
strncpy(call->upnp_session->audio->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
|
|
strncpy(call->upnp_session->audio->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
|
|
call->upnp_session->audio->rtcp->local_port = call->audio_port+1;
|
|
if(call->upnp_session->audio->rtcp->external_port == -1) {
|
|
call->upnp_session->audio->rtcp->external_port = call->audio_port+1;
|
|
}
|
|
if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle && audio) {
|
|
// Add audio port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtp);
|
|
} else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk && !audio) {
|
|
// Remove audio port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
|
|
}
|
|
if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle && audio) {
|
|
// Add audio port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->audio->rtcp);
|
|
} else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk && !audio) {
|
|
// Remove audio port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
|
|
}
|
|
|
|
/*
|
|
* Video part
|
|
*/
|
|
strncpy(call->upnp_session->video->rtp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
|
|
strncpy(call->upnp_session->video->rtp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
|
|
call->upnp_session->video->rtp->local_port = call->video_port;
|
|
if(call->upnp_session->video->rtp->external_port == -1) {
|
|
call->upnp_session->video->rtp->external_port = call->video_port;
|
|
}
|
|
strncpy(call->upnp_session->video->rtcp->local_addr, local_addr, LINPHONE_IPADDR_SIZE);
|
|
strncpy(call->upnp_session->video->rtcp->external_addr, external_addr, LINPHONE_IPADDR_SIZE);
|
|
call->upnp_session->video->rtcp->local_port = call->video_port+1;
|
|
if(call->upnp_session->video->rtcp->external_port == -1) {
|
|
call->upnp_session->video->rtcp->external_port = call->video_port+1;
|
|
}
|
|
if(call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle && video) {
|
|
// Add video port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtp);
|
|
} else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateOk && !video) {
|
|
// Remove video port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
|
|
}
|
|
if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle && video) {
|
|
// Add video port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->video->rtcp);
|
|
} else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk && !video) {
|
|
// Remove video port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
|
|
}
|
|
}
|
|
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
|
|
/*
|
|
* Update uPnP call state
|
|
*/
|
|
upnp_call_process(call);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int linphone_core_update_upnp_from_remote_media_description(LinphoneCall *call, const SalMediaDescription *md) {
|
|
bool_t audio = FALSE;
|
|
bool_t video = FALSE;
|
|
int i;
|
|
const SalStreamDescription *stream;
|
|
|
|
for (i = 0; i < md->nstreams; i++) {
|
|
stream = &md->streams[i];
|
|
if(stream->type == SalAudio) {
|
|
audio = TRUE;
|
|
} else if(stream->type == SalVideo) {
|
|
video = TRUE;
|
|
}
|
|
}
|
|
|
|
return linphone_core_update_upnp_audio_video(call, audio, video);
|
|
}
|
|
|
|
int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
|
|
return linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
|
|
}
|
|
|
|
int upnp_call_process(LinphoneCall *call) {
|
|
LinphoneCore *lc = call->core;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
int ret = -1;
|
|
UpnpState oldState;
|
|
|
|
ms_mutex_lock(&lupnp->mutex);
|
|
|
|
// Don't handle when the call
|
|
if(lupnp->state == LinphoneUpnpStateOk && call->upnp_session != NULL) {
|
|
ret = 0;
|
|
|
|
/*
|
|
* Update Audio state
|
|
*/
|
|
if((call->upnp_session->audio->rtp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtp->state == LinphoneUpnpStateIdle) &&
|
|
(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->audio->rtcp->state == LinphoneUpnpStateIdle)) {
|
|
call->upnp_session->audio->state = LinphoneUpnpStateOk;
|
|
} else if(call->upnp_session->audio->rtp->state == LinphoneUpnpStateAdding ||
|
|
call->upnp_session->audio->rtp->state == LinphoneUpnpStateRemoving ||
|
|
call->upnp_session->audio->rtcp->state == LinphoneUpnpStateAdding ||
|
|
call->upnp_session->audio->rtcp->state == LinphoneUpnpStateRemoving) {
|
|
call->upnp_session->audio->state = LinphoneUpnpStatePending;
|
|
} else if(call->upnp_session->audio->rtcp->state == LinphoneUpnpStateKo ||
|
|
call->upnp_session->audio->rtp->state == LinphoneUpnpStateKo) {
|
|
call->upnp_session->audio->state = LinphoneUpnpStateKo;
|
|
} else {
|
|
call->upnp_session->audio->state = LinphoneUpnpStateIdle;
|
|
}
|
|
|
|
/*
|
|
* Update Video state
|
|
*/
|
|
if((call->upnp_session->video->rtp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtp->state == LinphoneUpnpStateIdle) &&
|
|
(call->upnp_session->video->rtcp->state == LinphoneUpnpStateOk || call->upnp_session->video->rtcp->state == LinphoneUpnpStateIdle)) {
|
|
call->upnp_session->video->state = LinphoneUpnpStateOk;
|
|
} else if(call->upnp_session->video->rtp->state == LinphoneUpnpStateAdding ||
|
|
call->upnp_session->video->rtp->state == LinphoneUpnpStateRemoving ||
|
|
call->upnp_session->video->rtcp->state == LinphoneUpnpStateAdding ||
|
|
call->upnp_session->video->rtcp->state == LinphoneUpnpStateRemoving) {
|
|
call->upnp_session->video->state = LinphoneUpnpStatePending;
|
|
} else if(call->upnp_session->video->rtcp->state == LinphoneUpnpStateKo ||
|
|
call->upnp_session->video->rtp->state == LinphoneUpnpStateKo) {
|
|
call->upnp_session->video->state = LinphoneUpnpStateKo;
|
|
} else {
|
|
call->upnp_session->video->state = LinphoneUpnpStateIdle;
|
|
}
|
|
|
|
/*
|
|
* Update session state
|
|
*/
|
|
oldState = call->upnp_session->state;
|
|
if(call->upnp_session->audio->state == LinphoneUpnpStateOk &&
|
|
call->upnp_session->video->state == LinphoneUpnpStateOk) {
|
|
call->upnp_session->state = LinphoneUpnpStateOk;
|
|
} else if(call->upnp_session->audio->state == LinphoneUpnpStatePending ||
|
|
call->upnp_session->video->state == LinphoneUpnpStatePending) {
|
|
call->upnp_session->state = LinphoneUpnpStatePending;
|
|
} else if(call->upnp_session->audio->state == LinphoneUpnpStateKo ||
|
|
call->upnp_session->video->state == LinphoneUpnpStateKo) {
|
|
call->upnp_session->state = LinphoneUpnpStateKo;
|
|
} else {
|
|
call->upnp_session->state = LinphoneUpnpStateIdle;
|
|
}
|
|
|
|
/* When change is done proceed update */
|
|
if(oldState != LinphoneUpnpStateOk && oldState != LinphoneUpnpStateKo &&
|
|
(call->upnp_session->state == LinphoneUpnpStateOk || call->upnp_session->state == LinphoneUpnpStateKo)) {
|
|
if(call->upnp_session->state == LinphoneUpnpStateOk)
|
|
ms_message("uPnP IGD: uPnP for Call %p is ok", call);
|
|
else
|
|
ms_message("uPnP IGD: uPnP for Call %p is ko", call);
|
|
|
|
switch (call->state) {
|
|
case LinphoneCallUpdating:
|
|
linphone_core_start_update_call(lc, call);
|
|
break;
|
|
case LinphoneCallUpdatedByRemote:
|
|
linphone_core_start_accept_call_update(lc, call);
|
|
break;
|
|
case LinphoneCallOutgoingInit:
|
|
linphone_core_proceed_with_invite_if_ready(lc, call, NULL);
|
|
break;
|
|
case LinphoneCallIdle:
|
|
linphone_core_notify_incoming_call(lc, call);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
return ret;
|
|
}
|
|
|
|
bool_t linphone_core_upnp_hook(void *data) {
|
|
char key[64];
|
|
MSList *list = NULL;
|
|
MSList *item;
|
|
UpnpPortBinding *port_mapping;
|
|
LinphoneCore *lc = (LinphoneCore *)data;
|
|
LinphoneCall *call;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
ms_mutex_lock(&lupnp->mutex);
|
|
|
|
if(lupnp->clean && !lupnp->cleaning) {
|
|
lupnp->clean = FALSE;
|
|
// Remove old mapping
|
|
list = upnp_config_list_port_bindings(lc->config);
|
|
if(list == NULL) {
|
|
lupnp->emit = TRUE;
|
|
} else {
|
|
lupnp->cleaning = TRUE;
|
|
for(item = list;item != NULL; item = item->next) {
|
|
port_mapping = (UpnpPortBinding *)item->data;
|
|
upnp_context_send_remove_port_binding(lc, port_mapping);
|
|
}
|
|
ms_list_for_each(list,(void (*)(void*))upnp_port_binding_release);
|
|
list = ms_list_free(list);
|
|
}
|
|
}
|
|
|
|
if(lupnp->emit) {
|
|
lupnp->emit = FALSE;
|
|
|
|
/* Force port bindings */
|
|
if(lupnp->sip_udp != NULL) {
|
|
lupnp->sip_udp->state = LinphoneUpnpStateIdle;
|
|
upnp_context_send_add_port_binding(lc, lupnp->sip_udp);
|
|
}
|
|
if(lupnp->sip_tcp != NULL) {
|
|
lupnp->sip_udp->state = LinphoneUpnpStateIdle;
|
|
upnp_context_send_add_port_binding(lc, lupnp->sip_tcp);
|
|
}
|
|
if(lupnp->sip_tls != NULL) {
|
|
lupnp->sip_udp->state = LinphoneUpnpStateIdle;
|
|
upnp_context_send_add_port_binding(lc, lupnp->sip_tls);
|
|
}
|
|
list = lc->calls;
|
|
while(list != NULL) {
|
|
call = (LinphoneCall *)list->data;
|
|
call->upnp_session->audio->rtp->state = LinphoneUpnpStateIdle;
|
|
call->upnp_session->audio->rtcp->state = LinphoneUpnpStateIdle;
|
|
call->upnp_session->video->rtp->state = LinphoneUpnpStateIdle;
|
|
call->upnp_session->video->rtcp->state = LinphoneUpnpStateIdle;
|
|
linphone_core_update_upnp_audio_video(call, call->audiostream!=NULL, call->videostream!=NULL);
|
|
list = list->next;
|
|
}
|
|
}
|
|
|
|
/* Add configs */
|
|
for(item = lupnp->adding_configs;item!=NULL;item=item->next) {
|
|
port_mapping = (UpnpPortBinding *)item->data;
|
|
snprintf(key, sizeof(key), "%s-%d-%d",
|
|
(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
|
|
port_mapping->external_port,
|
|
port_mapping->local_port);
|
|
lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, "uPnP");
|
|
upnp_port_binding_log(ORTP_DEBUG, "Configuration: Added port binding", port_mapping);
|
|
}
|
|
ms_list_for_each(lupnp->adding_configs,(void (*)(void*))upnp_port_binding_release);
|
|
lupnp->adding_configs = ms_list_free(lupnp->adding_configs);
|
|
|
|
/* Remove configs */
|
|
for(item = lupnp->removing_configs;item!=NULL;item=item->next) {
|
|
port_mapping = (UpnpPortBinding *)item->data;
|
|
snprintf(key, sizeof(key), "%s-%d-%d",
|
|
(port_mapping->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
|
|
port_mapping->external_port,
|
|
port_mapping->local_port);
|
|
lp_config_set_string(lc->config, UPNP_SECTION_NAME, key, NULL);
|
|
upnp_port_binding_log(ORTP_DEBUG, "Configuration: Removed port binding", port_mapping);
|
|
}
|
|
ms_list_for_each(lupnp->removing_configs,(void (*)(void*))upnp_port_binding_release);
|
|
lupnp->removing_configs = ms_list_free(lupnp->removing_configs);
|
|
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
return TRUE;
|
|
}
|
|
|
|
void linphone_core_update_local_media_description_from_upnp(SalMediaDescription *desc, UpnpSession *session) {
|
|
int i;
|
|
SalStreamDescription *stream;
|
|
UpnpStream *upnpStream;
|
|
|
|
for (i = 0; i < desc->nstreams; i++) {
|
|
stream = &desc->streams[i];
|
|
upnpStream = NULL;
|
|
if(stream->type == SalAudio) {
|
|
upnpStream = session->audio;
|
|
} else if(stream->type == SalVideo) {
|
|
upnpStream = session->video;
|
|
}
|
|
if(upnpStream != NULL) {
|
|
if(upnpStream->rtp->state == LinphoneUpnpStateOk) {
|
|
strncpy(stream->rtp_addr, upnpStream->rtp->external_addr, LINPHONE_IPADDR_SIZE);
|
|
stream->rtp_port = upnpStream->rtp->external_port;
|
|
}
|
|
if(upnpStream->rtcp->state == LinphoneUpnpStateOk) {
|
|
strncpy(stream->rtcp_addr, upnpStream->rtcp->external_addr, LINPHONE_IPADDR_SIZE);
|
|
stream->rtcp_port = upnpStream->rtcp->external_port;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* uPnP Port Binding
|
|
*/
|
|
|
|
UpnpPortBinding *upnp_port_binding_new() {
|
|
UpnpPortBinding *port = NULL;
|
|
port = ms_new0(UpnpPortBinding,1);
|
|
ms_mutex_init(&port->mutex, NULL);
|
|
port->state = LinphoneUpnpStateIdle;
|
|
port->local_addr[0] = '\0';
|
|
port->local_port = -1;
|
|
port->external_addr[0] = '\0';
|
|
port->external_port = -1;
|
|
port->ref = 1;
|
|
return port;
|
|
}
|
|
|
|
UpnpPortBinding *upnp_port_binding_copy(const UpnpPortBinding *port) {
|
|
UpnpPortBinding *new_port = NULL;
|
|
new_port = ms_new0(UpnpPortBinding,1);
|
|
memcpy(new_port, port, sizeof(UpnpPortBinding));
|
|
ms_mutex_init(&new_port->mutex, NULL);
|
|
new_port->ref = 1;
|
|
return new_port;
|
|
}
|
|
|
|
void upnp_port_binding_log(int level, const char *msg, const UpnpPortBinding *port) {
|
|
if(strlen(port->local_addr)) {
|
|
ortp_log(level, "uPnP IGD: %s %s|%d->%s:%d", msg,
|
|
(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
|
|
port->external_port,
|
|
port->local_addr,
|
|
port->local_port);
|
|
} else {
|
|
ortp_log(level, "uPnP IGD: %s %s|%d->%d", msg,
|
|
(port->protocol == UPNP_IGD_IP_PROTOCOL_TCP)? "TCP":"UDP",
|
|
port->external_port,
|
|
port->local_port);
|
|
}
|
|
}
|
|
|
|
bool_t upnp_port_binding_equal(const UpnpPortBinding *port1, const UpnpPortBinding *port2) {
|
|
return port1->protocol == port2->protocol &&
|
|
port1->local_port == port2->local_port &&
|
|
port1->external_port == port2->external_port;
|
|
}
|
|
|
|
UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
|
|
ms_mutex_lock(&port->mutex);
|
|
port->ref++;
|
|
ms_mutex_unlock(&port->mutex);
|
|
return port;
|
|
}
|
|
|
|
void upnp_port_binding_release(UpnpPortBinding *port) {
|
|
ms_mutex_lock(&port->mutex);
|
|
if(--port->ref == 0) {
|
|
ms_mutex_unlock(&port->mutex);
|
|
ms_mutex_destroy(&port->mutex);
|
|
ms_free(port);
|
|
return;
|
|
}
|
|
ms_mutex_unlock(&port->mutex);
|
|
}
|
|
|
|
|
|
/*
|
|
* uPnP Stream
|
|
*/
|
|
|
|
UpnpStream* upnp_stream_new() {
|
|
UpnpStream *stream = ms_new0(UpnpStream,1);
|
|
stream->state = LinphoneUpnpStateIdle;
|
|
stream->rtp = upnp_port_binding_new();
|
|
stream->rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
stream->rtcp = upnp_port_binding_new();
|
|
stream->rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
return stream;
|
|
}
|
|
|
|
void upnp_stream_destroy(UpnpStream* stream) {
|
|
upnp_port_binding_release(stream->rtp);
|
|
stream->rtp = NULL;
|
|
upnp_port_binding_release(stream->rtcp);
|
|
stream->rtcp = NULL;
|
|
ms_free(stream);
|
|
}
|
|
|
|
|
|
/*
|
|
* uPnP Session
|
|
*/
|
|
|
|
UpnpSession* upnp_session_new() {
|
|
UpnpSession *session = ms_new0(UpnpSession,1);
|
|
session->state = LinphoneUpnpStateIdle;
|
|
session->audio = upnp_stream_new();
|
|
session->video = upnp_stream_new();
|
|
return session;
|
|
}
|
|
|
|
void upnp_session_destroy(LinphoneCall* call) {
|
|
LinphoneCore *lc = call->core;
|
|
|
|
/* Remove bindings */
|
|
if(call->upnp_session->audio->rtp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtp->state != LinphoneUpnpStateIdle) {
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtp);
|
|
}
|
|
if(call->upnp_session->audio->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->audio->rtcp->state != LinphoneUpnpStateIdle) {
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->audio->rtcp);
|
|
}
|
|
if(call->upnp_session->video->rtp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtp->state != LinphoneUpnpStateIdle) {
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtp);
|
|
}
|
|
if(call->upnp_session->video->rtcp->state != LinphoneUpnpStateKo && call->upnp_session->video->rtcp->state != LinphoneUpnpStateIdle) {
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->video->rtcp);
|
|
}
|
|
|
|
upnp_stream_destroy(call->upnp_session->audio);
|
|
upnp_stream_destroy(call->upnp_session->video);
|
|
ms_free(call->upnp_session);
|
|
call->upnp_session = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* uPnP Config
|
|
*/
|
|
|
|
MSList *upnp_config_list_port_bindings(struct _LpConfig *lpc) {
|
|
char protocol_str[4]; // TCP or UDP
|
|
upnp_igd_ip_protocol protocol;
|
|
int external_port;
|
|
int local_port;
|
|
MSList *retList = NULL;
|
|
UpnpPortBinding *port;
|
|
bool_t valid;
|
|
MSList *elem;
|
|
LpItem *item;
|
|
LpSection *sec=lp_config_find_section(lpc, UPNP_SECTION_NAME);
|
|
if(sec == NULL)
|
|
return retList;
|
|
|
|
elem = sec->items;
|
|
while(elem != NULL) {
|
|
item=(LpItem*)elem->data;
|
|
valid = TRUE;
|
|
if(sscanf(item->key, "%3s-%i-%i", protocol_str, &external_port, &local_port) == 3) {
|
|
if(strcasecmp(protocol_str, "TCP") == 0) {
|
|
protocol = UPNP_IGD_IP_PROTOCOL_TCP;
|
|
} else if(strcasecmp(protocol_str, "UDP") == 0) {
|
|
protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
} else {
|
|
valid = FALSE;
|
|
}
|
|
if(valid) {
|
|
port = upnp_port_binding_new();
|
|
port->state = LinphoneUpnpStateOk;
|
|
port->protocol = protocol;
|
|
port->external_port = external_port;
|
|
port->local_port = local_port;
|
|
retList = ms_list_append(retList, port);
|
|
}
|
|
} else {
|
|
valid = FALSE;
|
|
}
|
|
elem = ms_list_next(elem);
|
|
if(!valid) {
|
|
ms_warning("uPnP configuration invalid line: %s", item->key);
|
|
lp_section_remove_item(sec, item);
|
|
}
|
|
}
|
|
|
|
return retList;
|
|
}
|
|
|
|
void upnp_config_add_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
MSList *list;
|
|
UpnpPortBinding *list_port;
|
|
|
|
list = lupnp->removing_configs;
|
|
while(list != NULL) {
|
|
list_port = (UpnpPortBinding *)list->data;
|
|
if(upnp_port_binding_equal(list_port, port) == TRUE) {
|
|
lupnp->removing_configs = ms_list_remove(lupnp->removing_configs, list_port);
|
|
upnp_port_binding_release(list_port);
|
|
return;
|
|
}
|
|
list = ms_list_next(list);
|
|
}
|
|
|
|
list = lupnp->adding_configs;
|
|
while(list != NULL) {
|
|
list_port = (UpnpPortBinding *)list->data;
|
|
if(upnp_port_binding_equal(list_port, port) == TRUE) {
|
|
return;
|
|
}
|
|
list = ms_list_next(list);
|
|
}
|
|
|
|
list_port = upnp_port_binding_copy(port);
|
|
lupnp->adding_configs = ms_list_append(lupnp->adding_configs, list_port);
|
|
}
|
|
|
|
void upnp_config_remove_port_binding(LinphoneCore *lc, const UpnpPortBinding *port) {
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
MSList *list;
|
|
UpnpPortBinding *list_port;
|
|
|
|
list = lupnp->adding_configs;
|
|
while(list != NULL) {
|
|
list_port = (UpnpPortBinding *)list->data;
|
|
if(upnp_port_binding_equal(list_port, port) == TRUE) {
|
|
lupnp->adding_configs = ms_list_remove(lupnp->adding_configs, list_port);
|
|
upnp_port_binding_release(list_port);
|
|
return;
|
|
}
|
|
list = ms_list_next(list);
|
|
}
|
|
|
|
list = lupnp->removing_configs;
|
|
while(list != NULL) {
|
|
list_port = (UpnpPortBinding *)list->data;
|
|
if(upnp_port_binding_equal(list_port, port) == TRUE) {
|
|
return;
|
|
}
|
|
list = ms_list_next(list);
|
|
}
|
|
|
|
list_port = upnp_port_binding_copy(port);
|
|
lupnp->removing_configs = ms_list_append(lupnp->removing_configs, list_port);
|
|
}
|