mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-18 03:28:07 +00:00
342 lines
10 KiB
C
342 lines
10 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_MAX_RETRY 4
|
|
|
|
UpnpPortBinding *upnp_port_binding_new();
|
|
UpnpPortBinding * upnp_port_binding_retain(UpnpPortBinding *port);
|
|
void upnp_port_binding_release(UpnpPortBinding *port);
|
|
|
|
int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
|
|
int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
|
|
|
|
/* 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_WARNING;
|
|
break;
|
|
case UPNP_IGD_ERROR:
|
|
ortp_level = ORTP_ERROR;
|
|
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) {
|
|
lupnp->state = UPNP_Pending;
|
|
} else if(strcasecmp(connection_status, "Connected") || !nat_enabled) {
|
|
lupnp->state = UPNP_Ko;
|
|
} else {
|
|
// Emit add port binding
|
|
// Emit remove old port binding
|
|
lupnp->state = UPNP_Ok;
|
|
}
|
|
|
|
break;
|
|
|
|
case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
|
|
mapping = (upnp_igd_port_mapping *) arg;
|
|
port_mapping = (UpnpPortBinding*) mapping->cookie;
|
|
port_mapping->remote_port = mapping->remote_port;
|
|
port_mapping->state = UPNP_Ok;
|
|
// TODO: SAVE IN CONFIG THE PORT
|
|
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;
|
|
upnp_context_send_add_port_binding(lc, 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->remote_port = -1;
|
|
port_mapping->state = UPNP_Idle;
|
|
// TODO: REMOVE FROM CONFIG THE PORT
|
|
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;
|
|
upnp_context_send_remove_port_binding(lc, port_mapping);
|
|
// TODO: REMOVE FROM CONFIG THE PORT (DON'T TRY ANYMORE)
|
|
upnp_port_binding_release(port_mapping);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
}
|
|
|
|
int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
upnp_igd_port_mapping mapping;
|
|
char * local_host = NULL;
|
|
int ret;
|
|
if(port->state == UPNP_Idle) {
|
|
port->remote_port = -1;
|
|
port->retry = 0;
|
|
port->state = UPNP_Pending;
|
|
}
|
|
if(port->retry >= UPNP_MAX_RETRY) {
|
|
ret = -1;
|
|
} else {
|
|
local_host = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
|
|
mapping.cookie = upnp_port_binding_retain(port);
|
|
mapping.local_port = port->local_port;
|
|
mapping.local_host = local_host;
|
|
mapping.remote_port = rand()%1024 + 1024;
|
|
mapping.remote_host = "";
|
|
mapping.description = PACKAGE_NAME;
|
|
mapping.protocol = port->protocol;
|
|
|
|
port->retry++;
|
|
ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
|
|
if(local_host != NULL) {
|
|
free(local_host);
|
|
}
|
|
}
|
|
if(ret != 0) {
|
|
port->state = UPNP_Ko;
|
|
}
|
|
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 == UPNP_Idle) {
|
|
port->retry = 0;
|
|
port->state = UPNP_Pending;
|
|
}
|
|
if(port->retry >= UPNP_MAX_RETRY) {
|
|
ret = -1;
|
|
} else {
|
|
mapping.cookie = upnp_port_binding_retain(port);
|
|
mapping.remote_port = port->remote_port;
|
|
mapping.remote_host = "";
|
|
mapping.protocol = port->protocol;
|
|
port->retry++;
|
|
ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
|
|
}
|
|
if(ret != 0) {
|
|
port->state = UPNP_Ko;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int upnp_call_process(LinphoneCall *call) {
|
|
LinphoneCore *lc = call->core;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
int ret = -1;
|
|
|
|
ms_mutex_lock(&lupnp->mutex);
|
|
// Don't handle when the call
|
|
if(lupnp->state != UPNP_Ko && call->upnp_session != NULL) {
|
|
ret = 0;
|
|
|
|
/*
|
|
* Audio part
|
|
*/
|
|
call->upnp_session->audio_rtp->local_port = call->audio_port;
|
|
call->upnp_session->audio_rtcp->local_port = call->audio_port+1;
|
|
if(call->upnp_session->audio_rtp->state == UPNP_Idle && call->audiostream != NULL) {
|
|
// Add audio port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtp);
|
|
} else if(call->upnp_session->audio_rtp->state == UPNP_Ok && call->audiostream == NULL) {
|
|
// Remove audio port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtp);
|
|
}
|
|
if(call->upnp_session->audio_rtcp->state == UPNP_Idle && call->audiostream != NULL) {
|
|
// Add audio port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtcp);
|
|
} else if(call->upnp_session->audio_rtcp->state == UPNP_Ok && call->audiostream == NULL) {
|
|
// Remove audio port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtcp);
|
|
}
|
|
|
|
/*
|
|
* Video part
|
|
*/
|
|
call->upnp_session->video_rtp->local_port = call->video_port;
|
|
call->upnp_session->video_rtcp->local_port = call->video_port+1;
|
|
if(call->upnp_session->video_rtp->state == UPNP_Idle && call->videostream != NULL) {
|
|
// Add video port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtp);
|
|
} else if(call->upnp_session->video_rtp->state == UPNP_Ok && call->videostream == NULL) {
|
|
// Remove video port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtp);
|
|
}
|
|
if(call->upnp_session->video_rtcp->state == UPNP_Idle && call->videostream != NULL) {
|
|
// Add video port binding
|
|
upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtcp);
|
|
} else if(call->upnp_session->video_rtcp->state == UPNP_Ok && call->videostream == NULL) {
|
|
// Remove video port binding
|
|
upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtcp);
|
|
}
|
|
}
|
|
ms_mutex_unlock(&lupnp->mutex);
|
|
return ret;
|
|
}
|
|
|
|
int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
|
|
return upnp_call_process(call);
|
|
}
|
|
|
|
int upnp_context_init(LinphoneCore *lc) {
|
|
LCSipTransports transport;
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
ms_mutex_init(&lupnp->mutex, NULL);
|
|
lupnp->state = UPNP_Idle;
|
|
|
|
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;
|
|
} 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;
|
|
} 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;
|
|
} else {
|
|
lupnp->sip_tls = NULL;
|
|
}
|
|
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 = UPNP_Ko;
|
|
ms_error("Can't create uPnP IGD context");
|
|
return -1;
|
|
}
|
|
lupnp->state = UPNP_Pending;
|
|
return 0;
|
|
}
|
|
|
|
void upnp_context_uninit(LinphoneCore *lc) {
|
|
// Emit remove port (sip & saved)
|
|
UpnpContext *lupnp = &lc->upnp;
|
|
if(lupnp->sip_udp != NULL) {
|
|
upnp_port_binding_release(lupnp->sip_udp);
|
|
}
|
|
if(lupnp->sip_tcp != NULL) {
|
|
upnp_port_binding_release(lupnp->sip_tcp);
|
|
}
|
|
if(lupnp->sip_tls != NULL) {
|
|
upnp_port_binding_release(lupnp->sip_tls);
|
|
}
|
|
if(lupnp->upnp_igd_ctxt != NULL) {
|
|
upnp_igd_destroy(lupnp->upnp_igd_ctxt);
|
|
}
|
|
ms_mutex_destroy(&lupnp->mutex);
|
|
}
|
|
|
|
UpnpPortBinding *upnp_port_binding_new() {
|
|
UpnpPortBinding *port = NULL;
|
|
port = ms_new0(UpnpPortBinding,1);
|
|
ms_mutex_init(&port->mutex, NULL);
|
|
port->state = UPNP_Idle;
|
|
port->local_port = -1;
|
|
port->remote_port = -1;
|
|
port->ref = 1;
|
|
return 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);
|
|
}
|
|
|
|
UpnpSession* upnp_session_new() {
|
|
UpnpSession *session = ms_new0(UpnpSession,1);
|
|
session->state = UPNP_Idle;
|
|
session->audio_rtp = upnp_port_binding_new();
|
|
session->audio_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
session->audio_rtcp = upnp_port_binding_new();
|
|
session->audio_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
session->video_rtp = upnp_port_binding_new();
|
|
session->video_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
session->video_rtcp = upnp_port_binding_new();
|
|
session->video_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
|
|
return NULL;
|
|
}
|
|
|
|
void upnp_session_destroy(UpnpSession* session) {
|
|
upnp_port_binding_release(session->audio_rtp);
|
|
upnp_port_binding_release(session->audio_rtcp);
|
|
upnp_port_binding_release(session->video_rtp);
|
|
upnp_port_binding_release(session->video_rtp);
|
|
// TODO: send remove
|
|
ms_free(session);
|
|
}
|