mirror of https://code.videolan.org/videolan/vlc
382 lines
12 KiB
C++
382 lines
12 KiB
C++
/*****************************************************************************
|
|
* upnp-wrapper.hpp : UPnP Instance Wrapper class header
|
|
*****************************************************************************
|
|
* Copyright © 2004-2018 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Rémi Denis-Courmont (original plugin)
|
|
* Christian Henz <henz # c-lab.de>
|
|
* Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
|
|
* Hugo Beauzée-Luyssen <hugo@beauzee.fr>
|
|
* Shaleen Jain <shaleen@jain.sh>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifndef UPNP_WRAPPER_H
|
|
#define UPNP_WRAPPER_H
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_charset.h>
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <wincrypt.h>
|
|
#endif
|
|
#include <upnp.h>
|
|
#include <upnptools.h>
|
|
|
|
#if UPNP_VERSION < 10800
|
|
typedef void* UpnpEventPtr;
|
|
#else
|
|
typedef const void* UpnpEventPtr;
|
|
#endif
|
|
|
|
/**
|
|
* libUpnp allows only one instance per process, so we create a wrapper
|
|
* class around it that acts and behaves as a singleton. Letting us get
|
|
* multiple references to it but only ever having a single instance in memory.
|
|
* At the same time we let any module wishing to get a callback from the library
|
|
* to register a UpnpInstanceWrapper::Listener to get the Listener#onEvent()
|
|
* callback without having any hard dependencies.
|
|
*/
|
|
class UpnpInstanceWrapper
|
|
{
|
|
public:
|
|
class Listener
|
|
{
|
|
public:
|
|
virtual ~Listener() {}
|
|
virtual int onEvent( Upnp_EventType event_type,
|
|
UpnpEventPtr p_event,
|
|
void* p_user_data ) = 0;
|
|
};
|
|
|
|
private:
|
|
static UpnpInstanceWrapper* s_instance;
|
|
static vlc_mutex_t s_lock;
|
|
UpnpClient_Handle m_handle;
|
|
int m_refcount;
|
|
typedef std::shared_ptr<Listener> ListenerPtr;
|
|
typedef std::vector<ListenerPtr> Listeners;
|
|
static Listeners s_listeners;
|
|
|
|
public:
|
|
// This increases the refcount before returning the instance
|
|
static UpnpInstanceWrapper* get( vlc_object_t* p_obj );
|
|
void release();
|
|
UpnpClient_Handle handle() const;
|
|
void addListener(ListenerPtr listener);
|
|
void removeListener(ListenerPtr listener);
|
|
|
|
private:
|
|
static int Callback( Upnp_EventType event_type, UpnpEventPtr p_event, void* p_user_data );
|
|
|
|
UpnpInstanceWrapper();
|
|
~UpnpInstanceWrapper();
|
|
};
|
|
|
|
// **************************
|
|
// Helper functions
|
|
// **************************
|
|
|
|
#if UPNP_VERSION < 10623
|
|
/*
|
|
* Compat functions and typedefs for libupnp prior to 1.8
|
|
*/
|
|
|
|
typedef Upnp_Discovery UpnpDiscovery;
|
|
typedef Upnp_Action_Complete UpnpActionComplete;
|
|
|
|
inline const char* UpnpDiscovery_get_Location_cstr( const UpnpDiscovery* p_discovery )
|
|
{
|
|
return p_discovery->Location;
|
|
}
|
|
|
|
inline const char* UpnpDiscovery_get_DeviceID_cstr( const UpnpDiscovery* p_discovery )
|
|
{
|
|
return p_discovery->DeviceId;
|
|
}
|
|
|
|
inline static IXML_Document* UpnpActionComplete_get_ActionResult( const UpnpActionComplete* p_result )
|
|
{
|
|
return p_result->ActionResult;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Returns the value of a child element, or NULL on error
|
|
*/
|
|
inline const char* xml_getChildElementValue( IXML_Element* p_parent,
|
|
const char* psz_tag_name )
|
|
{
|
|
assert( p_parent );
|
|
assert( psz_tag_name );
|
|
|
|
IXML_NodeList* p_node_list;
|
|
p_node_list = ixmlElement_getElementsByTagName( p_parent, psz_tag_name );
|
|
if ( !p_node_list ) return NULL;
|
|
|
|
IXML_Node* p_element = ixmlNodeList_item( p_node_list, 0 );
|
|
ixmlNodeList_free( p_node_list );
|
|
if ( !p_element ) return NULL;
|
|
|
|
IXML_Node* p_text_node = ixmlNode_getFirstChild( p_element );
|
|
if ( !p_text_node ) return NULL;
|
|
|
|
return ixmlNode_getNodeValue( p_text_node );
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
inline IP_ADAPTER_MULTICAST_ADDRESS* getMulticastAddress(IP_ADAPTER_ADDRESSES* p_adapter)
|
|
{
|
|
const unsigned long i_broadcast_ip = inet_addr("239.255.255.250");
|
|
|
|
IP_ADAPTER_MULTICAST_ADDRESS *p_multicast = p_adapter->FirstMulticastAddress;
|
|
while (p_multicast != NULL)
|
|
{
|
|
if (((struct sockaddr_in *)p_multicast->Address.lpSockaddr)->sin_addr.S_un.S_addr == i_broadcast_ip)
|
|
return p_multicast;
|
|
p_multicast = p_multicast->Next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
inline bool isAdapterSuitable(IP_ADAPTER_ADDRESSES* p_adapter, bool ipv6)
|
|
{
|
|
if ( p_adapter->OperStatus != IfOperStatusUp )
|
|
return false;
|
|
if (p_adapter->Length == sizeof(IP_ADAPTER_ADDRESSES_XP))
|
|
{
|
|
IP_ADAPTER_ADDRESSES_XP* p_adapter_xp = reinterpret_cast<IP_ADAPTER_ADDRESSES_XP*>( p_adapter );
|
|
// On Windows Server 2003 and Windows XP, this member is zero if IPv4 is not available on the interface.
|
|
if (ipv6)
|
|
return p_adapter_xp->Ipv6IfIndex != 0;
|
|
return p_adapter_xp->IfIndex != 0;
|
|
}
|
|
IP_ADAPTER_ADDRESSES_LH* p_adapter_lh = reinterpret_cast<IP_ADAPTER_ADDRESSES_LH*>( p_adapter );
|
|
if (p_adapter_lh->FirstGatewayAddress == NULL)
|
|
return false;
|
|
if (ipv6)
|
|
return p_adapter_lh->Ipv6Enabled;
|
|
return p_adapter_lh->Ipv4Enabled;
|
|
}
|
|
|
|
inline IP_ADAPTER_ADDRESSES* ListAdapters()
|
|
{
|
|
ULONG addrSize;
|
|
const ULONG queryFlags = GAA_FLAG_INCLUDE_GATEWAYS|GAA_FLAG_SKIP_ANYCAST|GAA_FLAG_SKIP_DNS_SERVER;
|
|
IP_ADAPTER_ADDRESSES* addresses = NULL;
|
|
HRESULT hr;
|
|
|
|
/**
|
|
* https://msdn.microsoft.com/en-us/library/aa365915.aspx
|
|
*
|
|
* The recommended method of calling the GetAdaptersAddresses function is to pre-allocate a
|
|
* 15KB working buffer pointed to by the AdapterAddresses parameter. On typical computers,
|
|
* this dramatically reduces the chances that the GetAdaptersAddresses function returns
|
|
* ERROR_BUFFER_OVERFLOW, which would require calling GetAdaptersAddresses function multiple
|
|
* times. The example code illustrates this method of use.
|
|
*/
|
|
addrSize = 15 * 1024;
|
|
do
|
|
{
|
|
free(addresses);
|
|
addresses = (IP_ADAPTER_ADDRESSES*)malloc( addrSize );
|
|
if (addresses == NULL)
|
|
return NULL;
|
|
hr = GetAdaptersAddresses(AF_UNSPEC, queryFlags, NULL, addresses, &addrSize);
|
|
} while (hr == ERROR_BUFFER_OVERFLOW);
|
|
if (hr != NO_ERROR) {
|
|
free(addresses);
|
|
return NULL;
|
|
}
|
|
return addresses;
|
|
}
|
|
|
|
#ifdef UPNP_ENABLE_IPV6
|
|
|
|
inline char* getPreferedAdapter()
|
|
{
|
|
IP_ADAPTER_ADDRESSES *p_adapter, *addresses;
|
|
|
|
addresses = ListAdapters();
|
|
if (addresses == NULL)
|
|
return NULL;
|
|
|
|
/* find one with multicast capabilities */
|
|
p_adapter = addresses;
|
|
while (p_adapter != NULL)
|
|
{
|
|
if (isAdapterSuitable( p_adapter, true ))
|
|
{
|
|
/* make sure it supports 239.255.255.250 */
|
|
IP_ADAPTER_MULTICAST_ADDRESS *p_multicast = getMulticastAddress( p_adapter );
|
|
if (p_multicast != NULL)
|
|
{
|
|
char* res = FromWide( p_adapter->FriendlyName );
|
|
free( addresses );
|
|
return res;
|
|
}
|
|
}
|
|
p_adapter = p_adapter->Next;
|
|
}
|
|
free(addresses);
|
|
return NULL;
|
|
}
|
|
|
|
#else
|
|
|
|
inline char *getIpv4ForMulticast()
|
|
{
|
|
IP_ADAPTER_UNICAST_ADDRESS *p_best_ip = NULL;
|
|
wchar_t psz_uri[32];
|
|
DWORD strSize;
|
|
IP_ADAPTER_ADDRESSES *p_adapter, *addresses;
|
|
|
|
addresses = ListAdapters();
|
|
if (addresses == NULL)
|
|
return NULL;
|
|
|
|
/* find one with multicast capabilities */
|
|
p_adapter = addresses;
|
|
while (p_adapter != NULL)
|
|
{
|
|
if (isAdapterSuitable( p_adapter, false ))
|
|
{
|
|
/* make sure it supports 239.255.255.250 */
|
|
IP_ADAPTER_MULTICAST_ADDRESS *p_multicast = getMulticastAddress( p_adapter );
|
|
if (p_multicast != NULL)
|
|
{
|
|
/* get an IPv4 address */
|
|
IP_ADAPTER_UNICAST_ADDRESS *p_unicast = p_adapter->FirstUnicastAddress;
|
|
while (p_unicast != NULL)
|
|
{
|
|
strSize = sizeof( psz_uri ) / sizeof( wchar_t );
|
|
if( WSAAddressToString( p_unicast->Address.lpSockaddr,
|
|
p_unicast->Address.iSockaddrLength,
|
|
NULL, psz_uri, &strSize ) == 0 )
|
|
{
|
|
if ( p_best_ip == NULL ||
|
|
p_best_ip->ValidLifetime > p_unicast->ValidLifetime )
|
|
{
|
|
p_best_ip = p_unicast;
|
|
}
|
|
}
|
|
p_unicast = p_unicast->Next;
|
|
}
|
|
}
|
|
}
|
|
p_adapter = p_adapter->Next;
|
|
}
|
|
|
|
if ( p_best_ip != NULL )
|
|
goto done;
|
|
|
|
/* find any with IPv4 */
|
|
p_adapter = addresses;
|
|
while (p_adapter != NULL)
|
|
{
|
|
if (isAdapterSuitable(p_adapter, false))
|
|
{
|
|
/* get an IPv4 address */
|
|
IP_ADAPTER_UNICAST_ADDRESS *p_unicast = p_adapter->FirstUnicastAddress;
|
|
while (p_unicast != NULL)
|
|
{
|
|
strSize = sizeof( psz_uri ) / sizeof( wchar_t );
|
|
if( WSAAddressToString( p_unicast->Address.lpSockaddr,
|
|
p_unicast->Address.iSockaddrLength,
|
|
NULL, psz_uri, &strSize ) == 0 )
|
|
{
|
|
if ( p_best_ip == NULL ||
|
|
p_best_ip->ValidLifetime > p_unicast->ValidLifetime )
|
|
{
|
|
p_best_ip = p_unicast;
|
|
}
|
|
}
|
|
p_unicast = p_unicast->Next;
|
|
}
|
|
}
|
|
p_adapter = p_adapter->Next;
|
|
}
|
|
|
|
done:
|
|
if (p_best_ip != NULL)
|
|
{
|
|
strSize = sizeof( psz_uri ) / sizeof( wchar_t );
|
|
WSAAddressToString( p_best_ip->Address.lpSockaddr,
|
|
p_best_ip->Address.iSockaddrLength,
|
|
NULL, psz_uri, &strSize );
|
|
free(addresses);
|
|
return FromWide( psz_uri );
|
|
}
|
|
free(addresses);
|
|
return NULL;
|
|
}
|
|
#endif /* UPNP_ENABLE_IPV6 */
|
|
#else /* _WIN32 */
|
|
|
|
#ifdef UPNP_ENABLE_IPV6
|
|
|
|
#ifdef __APPLE__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#if defined(TARGET_OS_OSX) && TARGET_OS_OSX
|
|
#include <SystemConfiguration/SystemConfiguration.h>
|
|
#include "vlc_charset.h"
|
|
|
|
inline char *getPreferedAdapter()
|
|
{
|
|
SCDynamicStoreRef session = SCDynamicStoreCreate(NULL, CFSTR("session"), NULL, NULL);
|
|
CFDictionaryRef q = (CFDictionaryRef) SCDynamicStoreCopyValue(session, CFSTR("State:/Network/Global/IPv4"));
|
|
char *returnValue = NULL;
|
|
|
|
if (q != NULL) {
|
|
const void *val;
|
|
if (CFDictionaryGetValueIfPresent(q, CFSTR("PrimaryInterface"), &val)) {
|
|
returnValue = FromCFString((CFStringRef)val, kCFStringEncodingUTF8);
|
|
}
|
|
}
|
|
CFRelease(q);
|
|
CFRelease(session);
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
#else
|
|
|
|
inline char *getPreferedAdapter()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
#else
|
|
|
|
inline char *getIpv4ForMulticast()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif /* _WIN32 */
|
|
#endif /* UPNP_WRAPPER_H */
|