Commit 2e0ef7e3 authored by Yann Diorcet's avatar Yann Diorcet
Browse files

Merge branch 'upnp'

parents 3a231efb b21f3042
......@@ -531,6 +531,26 @@ AC_SUBST(SPANDSP_LIBS)
fi
dnl check for installed version of libupnp
AC_ARG_ENABLE(upnp,
[AS_HELP_STRING([--disable-upnp], [Disable uPnP support])],
[case "${enableval}" in
yes) build_upnp=true ;;
no) build_upnp=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --disable-upnp) ;;
esac],[build_upnp=auto])
if test "$build_upnp" != "false" ; then
PKG_CHECK_MODULES([LIBUPNP], [libupnp], [build_upnp=true],
[
if test "$build_upnp" == "true" ; then
AC_MSG_ERROR([libupnp not found.])
else
build_upnp=false
fi
])
fi
AM_CONDITIONAL(BUILD_GSM, test x$build_gsm = xyes )
AM_CONDITIONAL(BUILD_G726, test "$have_spandsp" = "true" )
......@@ -545,7 +565,7 @@ AM_CONDITIONAL(BUILD_FFMPEG, test "$ffmpeg" = "true")
AM_CONDITIONAL(BUILD_SDL,test "$sdl_found" = "true" )
AM_CONDITIONAL(BUILD_X11_XV, test "$enable_xv" = "true" )
AM_CONDITIONAL(BUILD_X11_GL, test "$enable_gl" = "true" )
AM_CONDITIONAL(BUILD_UPNP, test "$build_upnp" = "true" )
dnl *********************************************
dnl Enable/disable oRTP dependency
......
......@@ -2,6 +2,7 @@
mediastreamer2_includedir=$(includedir)/mediastreamer2
mediastreamer2_include_HEADERS= ice.h \
upnp_igd.h \
mscodecutils.h \
msfilter.h \
msqueue.h \
......
#ifndef _UPNP_IGD_H__
#define _UPNP_IGD_H__
#include <stdarg.h>
typedef enum _upnp_igd_print_level {
UPNP_IGD_DEBUG = 0,
UPNP_IGD_MESSAGE,
UPNP_IGD_WARNING,
UPNP_IGD_ERROR
} upnp_igd_print_level;
typedef enum _upnp_igd_ip_protocol {
UPNP_IGD_IP_PROTOCOL_UDP = 0,
UPNP_IGD_IP_PROTOCOL_TCP
} upnp_igd_ip_protocol;
typedef enum _upnp_igd_event {
UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED = 0,
UPNP_IGD_NAT_ENABLED_CHANGED,
UPNP_IGD_CONNECTION_STATUS_CHANGED,
UPNP_IGD_PORT_MAPPING_ADD_SUCCESS,
UPNP_IGD_PORT_MAPPING_ADD_FAILURE,
UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS,
UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE,
UPNP_IGD_DEVICE_ADDED = 100,
UPNP_IGD_DEVICE_REMOVED,
} upnp_igd_event;
typedef struct _upnp_igd_port_mapping {
upnp_igd_ip_protocol protocol;
const char* local_host;
int local_port;
const char* remote_host;
int remote_port;
const char* description;
void *cookie;
int retvalue;
} upnp_igd_port_mapping;
typedef void (*upnp_igd_callback_function)(void *cookie, upnp_igd_event event, void *arg);
typedef void (*upnp_igd_print_function)(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list);
typedef struct _upnp_igd_context upnp_igd_context;
upnp_igd_context* upnp_igd_create(upnp_igd_callback_function cb_fct, upnp_igd_print_function print_fct, void *cookie);
int upnp_igd_start(upnp_igd_context*igd_ctxt);
int upnp_igd_is_started(upnp_igd_context *igd_ctxt);
int upnp_igd_stop(upnp_igd_context*igd_ctxt);
void upnp_igd_destroy(upnp_igd_context *igd_ctxt);
char *upnp_igd_get_local_ipaddress(upnp_igd_context *igd_ctxt);
const char *upnp_igd_get_external_ipaddress(upnp_igd_context *igd_ctxt);
const char *upnp_igd_get_connection_status(upnp_igd_context *igd_ctxt);
int upnp_igd_get_nat_enabled(upnp_igd_context *igd_ctxt);
int upnp_igd_add_port_mapping(upnp_igd_context *igd_ctxt, const upnp_igd_port_mapping *mapping);
int upnp_igd_delete_port_mapping(upnp_igd_context *igd_ctxt, const upnp_igd_port_mapping *mapping);
int upnp_refresh(upnp_igd_context *igd_ctxt);
#endif //_UPNP_IGD_H__
......@@ -239,7 +239,13 @@ endif BUILD_VIDEO
endif MS2_FILTERS
if BUILD_UPNP
libmediastreamer_voip_la_SOURCES+= upnp/upnp_igd.c \
upnp/upnp_igd_private.h \
upnp/upnp_igd_cmd.c \
upnp/upnp_igd_utils.c \
upnp/upnp_igd_utils.h
endif
basedescs.h: Makefile $(libmediastreamer_base_la_SOURCES)
builddir=`pwd` && cd $(srcdir) && \
......@@ -333,6 +339,11 @@ endif
libmediastreamer_voip_la_LDFLAGS= $(libmediastreamer_base_la_LDFLAGS)
if BUILD_UPNP
AM_CFLAGS+=$(LIBUPNP_CFLAGS) -D_GNU_SOURCE
libmediastreamer_voip_la_LIBADD+= $(LIBUPNP_LIBS)
endif
if BUILD_IOS
libmediastreamer_voip_la_LDFLAGS+= -framework CoreGraphics
endif
......
#include "mediastreamer2/upnp_igd.h"
#include "upnp_igd_utils.h"
#include "upnp_igd_private.h"
#include <string.h>
#include <stdlib.h>
#include <upnp.h>
#include <upnptools.h>
#include <ixml.h>
#include <ithread.h>
#include <errno.h>
#include <sys/time.h>
const char *UPNPDeviceType = "urn:schemas-upnp-org:event-1-0";
const char *IGDDeviceType = "urn:schemas-upnp-org:device:InternetGatewayDevice:1";
const char *IGDServiceType[] = {
"urn:schemas-upnp-org:service:WANIPConnection:1",
};
const char *IGDServiceName[] = {
"WANIPConnection"
};
const char *IGDVarName[IGD_SERVICE_SERVCOUNT][IGD_MAXVARS] = {
{
"ExternalIPAddress",
"NATEnabled",
"ConnectionStatus"
}
};
char IGDVarCount[IGD_SERVICE_SERVCOUNT] = {
IGD_SERVICE_WANIPCONNECTION_VARCOUNT
};
int IGDTimeOut[IGD_SERVICE_SERVCOUNT] = {
1801
};
/********************************************************************************
* upnp_igd_delete_node
*
* Description:
* Delete a device node from the context device list. Note that this
* function is NOT thread safe, and should be called from another
* function that has already locked the global device list.
*
* Parameters:
* igd_ctxt -- The upnp igd context
* node -- The device node
*
********************************************************************************/
int upnp_igd_delete_node(upnp_igd_context *igd_ctxt, upnp_igd_device_node *node) {
int rc, service, var;
if (NULL == node) {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "upnp_igd_delete_node: Node is empty");
return 0;
}
upnp_igd_print(igd_ctxt, UPNP_IGD_MESSAGE, "Remove IGD device: %s[%s]", node->device.friendly_name, node->device.udn);
for (service = 0; service < IGD_SERVICE_SERVCOUNT; service++) {
/*
If we have a valid control SID, then unsubscribe
*/
if (strcmp(node->device.services[service].sid, "") != 0) {
rc = UpnpUnSubscribe(igd_ctxt->upnp_handle, node->device.services[service].sid);
if (UPNP_E_SUCCESS == rc) {
upnp_igd_print(igd_ctxt, UPNP_IGD_DEBUG, "Unsubscribed from IGD %s EventURL with SID=%s", IGDServiceName[service], node->device.services[service].sid);
} else {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Error unsubscribing to IGD %s EventURL -- %d", IGDServiceName[service], rc);
}
}
for (var = 0; var < IGDVarCount[service]; var++) {
if (node->device.services[service].variables[var]) {
free(node->device.services[service].variables[var]);
}
}
}
free(node);
node = NULL;
if(igd_ctxt->callback_fct != NULL) {
igd_ctxt->callback_fct(igd_ctxt->cookie, UPNP_IGD_DEVICE_REMOVED, NULL);
}
return 0;
}
/********************************************************************************
* upnp_igd_remove_device
*
* Description:
* Remove a device from the context device list.
*
* Parameters:
* igd_ctxt -- The upnp igd context
* udn -- The Unique Device Name for the device to remove
*
********************************************************************************/
int upnp_igd_remove_device(upnp_igd_context *igd_ctxt, const char *udn) {
upnp_igd_device_node *curdevnode, *prevdevnode;
ithread_mutex_lock(&igd_ctxt->devices_mutex);
curdevnode = igd_ctxt->devices;
if (!curdevnode) {
upnp_igd_print(igd_ctxt, UPNP_IGD_WARNING, "upnp_igd_remove_device: Device list empty");
} else {
if (0 == strcmp(curdevnode->device.udn, udn)) {
igd_ctxt->devices = curdevnode->next;
upnp_igd_delete_node(igd_ctxt, curdevnode);
} else {
prevdevnode = curdevnode;
curdevnode = curdevnode->next;
while (curdevnode) {
if (strcmp(curdevnode->device.udn, udn) == 0) {
prevdevnode->next = curdevnode->next;
upnp_igd_delete_node(igd_ctxt, curdevnode);
break;
}
prevdevnode = curdevnode;
curdevnode = curdevnode->next;
}
}
}
ithread_mutex_unlock(&igd_ctxt->devices_mutex);
return 0;
}
/********************************************************************************
* upnp_igd_remove_all
*
* Description:
* Remove all devices from the context device list.
*
* Parameters:
* igd_ctxt -- The upnp igd context
*
********************************************************************************/
int upnp_igd_remove_all(upnp_igd_context *igd_ctxt) {
upnp_igd_device_node *curdevnode, *next;
ithread_mutex_lock(&igd_ctxt->devices_mutex);
curdevnode = igd_ctxt->devices;
igd_ctxt->devices = NULL;
while (curdevnode) {
next = curdevnode->next;
upnp_igd_delete_node(igd_ctxt, curdevnode);
curdevnode = next;
}
ithread_mutex_unlock(&igd_ctxt->devices_mutex);
return 0;
}
/********************************************************************************
* upnp_igd_verify_timeouts
*
* Description:
* Check all the device for refreshing which ones are close to timeout.
*
* Parameters:
* igd_ctxt -- The upnp igd context
* incr -- Number of second before next check
*
********************************************************************************/
void upnp_igd_verify_timeouts(upnp_igd_context *igd_ctxt, int incr) {
upnp_igd_device_node *prevdevnode, *curdevnode;
int ret;
ithread_mutex_lock(&igd_ctxt->devices_mutex);
prevdevnode = NULL;
curdevnode = igd_ctxt->devices;
while (curdevnode) {
curdevnode->device.advr_time_out -= incr;
upnp_igd_print(igd_ctxt, UPNP_IGD_DEBUG, "IGD device: %s[%s] | Advertisement Timeout: %d",
curdevnode->device.friendly_name,
curdevnode->device.udn,
curdevnode->device.advr_time_out);
if (curdevnode->device.advr_time_out <= 0) {
/* This advertisement has expired, so we should remove the device
* from the list */
if (igd_ctxt->devices == curdevnode)
igd_ctxt->devices = curdevnode->next;
else
prevdevnode->next = curdevnode->next;
upnp_igd_delete_node(igd_ctxt, curdevnode);
if (prevdevnode)
curdevnode = prevdevnode->next;
else
curdevnode = igd_ctxt->devices;
} else {
if (curdevnode->device.advr_time_out < 2 * incr) {
/* This advertisement is about to expire, so
* send out a search request for this device
* UDN to try to renew */
ret = UpnpSearchAsync(igd_ctxt->upnp_handle, incr, curdevnode->device.udn, igd_ctxt);
if (ret != UPNP_E_SUCCESS)
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Error sending search request for Device UDN: %s -- err = %d",
curdevnode->device.udn, ret);
}
prevdevnode = curdevnode;
curdevnode = curdevnode->next;
}
}
ithread_mutex_unlock(&igd_ctxt->devices_mutex);
}
/********************************************************************************
* upnp_igd_timer_loop
*
* Description:
* Thread function which check the timeouts.
*
* Parameters:
* args -- The upnp igd context
*
********************************************************************************/
void *upnp_igd_timer_loop(void *args) {
upnp_igd_context *igd_ctxt = (upnp_igd_context*)args;
struct timespec ts;
struct timeval tp;
/* how often to verify the timeouts, in seconds */
static int incr = 30;
// Update timeout
gettimeofday(&tp, NULL);
ts.tv_sec = tp.tv_sec;
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec += incr;
ithread_mutex_lock(&igd_ctxt->timer_mutex);
while(ithread_cond_timedwait(&igd_ctxt->timer_cond, &igd_ctxt->timer_mutex, &ts) == ETIMEDOUT) {
upnp_igd_verify_timeouts(igd_ctxt, incr);
// Update timeout
gettimeofday(&tp, NULL);
ts.tv_sec = tp.tv_sec;
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec += incr;
}
ithread_mutex_unlock(&igd_ctxt->timer_mutex);
return NULL;
}
/********************************************************************************
* upnp_igd_get_var
*
* Description:
* Send a GetVar request to the specified service of a device.
*
* Parameters:
* igd_ctxt -- The upnp igd context
* device_node -- The device
* service -- The service
* variable -- The variable to request.
* fun -- Callback function
* cookie -- Callback cookie
*
********************************************************************************/
int upnp_igd_get_var(upnp_igd_context* igd_ctxt, upnp_igd_device_node *device_node, int service, int variable,
Upnp_FunPtr fun, const void *cookie) {
int ret;
upnp_igd_print(igd_ctxt, UPNP_IGD_DEBUG, "Get %s.%s from IGD device %s[%s]",
IGDServiceName[service],
IGDVarName[service][variable],
device_node->device.friendly_name,
device_node->device.udn);
ret = UpnpGetServiceVarStatusAsync(igd_ctxt->upnp_handle,
device_node->device.services[service].control_url,
IGDVarName[service][variable],
fun,
cookie);
if (ret != UPNP_E_SUCCESS) {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Error in UpnpGetServiceVarStatusAsync -- %d", ret);
ret = -1;
}
return 0;
}
/********************************************************************************
* upnp_igd_send_action
*
* Description:
* Send an Action request to the specified service of a device.
*
* Parameters:
* igd_ctxt -- The upnp igd context
* device_node -- The device
* service -- The service
* actionname -- The name of the action.
* param_name -- An array of parameter names
* param_val -- The corresponding parameter values
* param_count -- The number of parameters
* fun -- Callback function
* cookie -- Callback cookie
*
********************************************************************************/
int upnp_igd_send_action(upnp_igd_context* igd_ctxt, upnp_igd_device_node *device_node, int service,
const char *actionname, const char **param_name, const char **param_val, int param_count,
Upnp_FunPtr fun, const void *cookie) {
IXML_Document *actionNode = NULL;
int ret = 0;
int param;
if (0 == param_count) {
actionNode = UpnpMakeAction(actionname, IGDServiceType[service], 0, NULL);
} else {
for (param = 0; param < param_count; param++) {
if (UpnpAddToAction(&actionNode, actionname, IGDServiceType[service], param_name[param], param_val[param]) != UPNP_E_SUCCESS) {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "ERROR: upnp_igd_send_action: Trying to add action param");
}
}
}
ret = UpnpSendActionAsync(igd_ctxt->upnp_handle, device_node->device.services[service].control_url,
IGDServiceType[service], NULL, actionNode, fun, cookie);
if (ret != UPNP_E_SUCCESS) {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Error in UpnpSendActionAsync -- %d", ret);
ret = -1;
}
if (actionNode)
ixmlDocument_free(actionNode);
return ret;
}
/********************************************************************************
* upnp_igd_add_device
*
* Description:
* If the device is not already included in the global device list,
* add it. Otherwise, update its advertisement expiration timeout.
*
* Parameters:
* igd_ctxt -- The upnp igd context
* desc_doc -- The description document for the device
* d_event -- event associated with the new device
*
********************************************************************************/
void upnp_igd_add_device(upnp_igd_context *igd_ctxt, IXML_Document *desc_doc, struct Upnp_Discovery *d_event) {
upnp_igd_device_node *deviceNode, *tmpdevnode;
int found = 0;
int ret;
int service, var;
char presURL[200];
char *serviceId;
char *event_url;
char *controlURL;
Upnp_SID eventSID;
char *deviceType = NULL;
char *friendlyName = NULL;
char *baseURL = NULL;
char *relURL = NULL;
char *UDN = NULL;
UDN = upnp_igd_get_first_document_item(igd_ctxt, desc_doc, "UDN");
deviceType = upnp_igd_get_first_document_item(igd_ctxt, desc_doc, "deviceType");
friendlyName = upnp_igd_get_first_document_item(igd_ctxt, desc_doc, "friendlyName");
baseURL = upnp_igd_get_first_document_item(igd_ctxt, desc_doc, "URLBase");
relURL = upnp_igd_get_first_document_item(igd_ctxt, desc_doc, "presentationURL");
ret = UpnpResolveURL((baseURL ? baseURL : d_event->Location), relURL, presURL);
if (UPNP_E_SUCCESS != ret) {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Error generating presURL from %s + %s", baseURL, relURL);
}
ithread_mutex_lock(&igd_ctxt->devices_mutex);
if (strcmp(deviceType, IGDDeviceType) == 0) {
/* Check if this device is already in the list */
tmpdevnode = igd_ctxt->devices;
while (tmpdevnode) {
if (strcmp(tmpdevnode->device.udn, UDN) == 0) {
found = 1;
break;
}
tmpdevnode = tmpdevnode->next;
}
if (found) {
/* The device is already there, so just update */
/* the advertisement timeout field */
tmpdevnode->device.advr_time_out = d_event->Expires;
upnp_igd_print(igd_ctxt, UPNP_IGD_DEBUG, "IGD device: %s[%s] | Update expires(%d)", friendlyName, UDN, tmpdevnode->device.advr_time_out);
} else {
upnp_igd_print(igd_ctxt, UPNP_IGD_MESSAGE, "Add IGD device: %s[%s]", friendlyName, UDN);
/* Create a new device node */
deviceNode = (upnp_igd_device_node *) malloc(sizeof(upnp_igd_device_node));
memset(deviceNode->device.services, '\0', sizeof(upnp_igd_service) * IGD_SERVICE_SERVCOUNT);
strcpy(deviceNode->device.udn, UDN);
strcpy(deviceNode->device.desc_doc_url, d_event->Location);
strcpy(deviceNode->device.friendly_name, friendlyName);
strcpy(deviceNode->device.pres_url, presURL);
deviceNode->device.advr_time_out = d_event->Expires;
// Reset values
serviceId = NULL;
event_url = NULL;
controlURL = NULL;
eventSID[0] = '\0';
for (service = 0; service < IGD_SERVICE_SERVCOUNT;
service++) {
if (upnp_igd_get_find_and_parse_service(igd_ctxt, desc_doc, d_event->Location,
IGDServiceType[service], &serviceId, &event_url, &controlURL)) {
upnp_igd_print(igd_ctxt, UPNP_IGD_DEBUG, "Subscribing to EventURL %s...",event_url);
ret =
UpnpSubscribe(igd_ctxt->upnp_handle, event_url, &IGDTimeOut[service], eventSID);
if (ret == UPNP_E_SUCCESS) {
upnp_igd_print(igd_ctxt, UPNP_IGD_DEBUG, "Subscribed to EventURL with SID=%s", eventSID);
} else {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Error Subscribing to EventURL -- %d", ret);
strcpy(eventSID, "");
}
} else {
upnp_igd_print(igd_ctxt, UPNP_IGD_ERROR, "Could not find Service: %s", IGDServiceType[service]);
}
if(serviceId != NULL)
strcpy(deviceNode->device.services[service].service_id, serviceId);
strcpy(deviceNode->device.services[service].service_type, IGDServiceName[service]);
if(controlURL != NULL)
strcpy(deviceNode->device.services[service].control_url, controlURL);
if(event_url != NULL)
strcpy(deviceNode->device.services[service].event_url, event_url);
if(eventSID != NULL)
strcpy(deviceNode->device.services[service].sid, eventSID);
for (var = 0; var < IGDVarCount[service]; var++) {
deviceNode->device.services[service].variables[var] = (char *)malloc(IGD_MAX_VAL_LEN);
strcpy(deviceNode->device.services[service].variables[var], "");
}
}
deviceNode->next = NULL;
/* Insert the new device node in the list */
if ((tmpdevnode = igd_ctxt->devices)) {
while (tmpdevnode) {
if (tmpdevnode->next) {
tmpdevnode = tmpdevnode->next;
} else {
tmpdevnode->next = deviceNode;
break;
}
}
} else {
igd_ctxt->devices = deviceNode;
}
if(igd_ctxt->callback_fct != NULL) {
igd_ctxt->callback_fct(igd_ctxt->cookie, UPNP_IGD_DEVICE_ADDED, NULL);
}
if (serviceId)
free(serviceId);
if (controlURL)
free(controlURL);
if (event_url)