diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am index 41c7871a29..f63df23b32 100644 --- a/modules/services_discovery/Makefile.am +++ b/modules/services_discovery/Makefile.am @@ -26,7 +26,9 @@ libmtp_plugin_la_LIBADD = $(MTP_LIBS) EXTRA_LTLIBRARIES += libmtp_plugin.la sd_LTLIBRARIES += $(LTLIBmtp) -libupnp_plugin_la_SOURCES = services_discovery/upnp.cpp services_discovery/upnp.hpp +libupnp_plugin_la_SOURCES = services_discovery/upnp.cpp services_discovery/upnp.hpp \ + services_discovery/upnp-wrapper.hpp \ + services_discovery/upnp-wrapper.cpp libupnp_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(UPNP_CFLAGS) libupnp_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)' libupnp_plugin_la_LIBADD = $(UPNP_LIBS) diff --git a/modules/services_discovery/upnp-wrapper.cpp b/modules/services_discovery/upnp-wrapper.cpp new file mode 100644 index 0000000000..9bdeebc007 --- /dev/null +++ b/modules/services_discovery/upnp-wrapper.cpp @@ -0,0 +1,155 @@ +/***************************************************************************** + * upnp-wrapper.cpp : UPnP Instance Wrapper class + ***************************************************************************** + * Copyright © 2004-2018 VLC authors and VideoLAN + * + * Authors: Rémi Denis-Courmont (original plugin) + * Christian Henz + * Mirsal Ennaime + * Hugo Beauzée-Luyssen + * Shaleen Jain + * + * 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. + *****************************************************************************/ + +#include "upnp-wrapper.hpp" + +UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance; +UpnpInstanceWrapper::Listeners UpnpInstanceWrapper::s_listeners; +vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX; + +UpnpInstanceWrapper::UpnpInstanceWrapper() + : m_handle( -1 ) + , m_refcount( 0 ) +{ +} + +UpnpInstanceWrapper::~UpnpInstanceWrapper() +{ + UpnpUnRegisterClient( m_handle ); + UpnpFinish(); +} + +UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj) +{ + vlc_mutex_locker lock( &s_lock ); + if ( s_instance == NULL ) + { + UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper; + if ( unlikely( !instance ) ) + { + return NULL; + } + + #ifdef UPNP_ENABLE_IPV6 + char* psz_miface = var_InheritString( p_obj, "miface" ); + if (psz_miface == NULL) + psz_miface = getPreferedAdapter(); + msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface ? psz_miface : "default" ); + int i_res = UpnpInit2( psz_miface, 0 ); + free( psz_miface ); + #else + /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */ + char *psz_hostip = getIpv4ForMulticast(); + int i_res = UpnpInit( psz_hostip, 0 ); + free(psz_hostip); + #endif /* UPNP_ENABLE_IPV6 */ + if( i_res != UPNP_E_SUCCESS ) + { + msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) ); + delete instance; + return NULL; + } + + ixmlRelaxParser( 1 ); + + /* Register a control point */ + i_res = UpnpRegisterClient( Callback, instance, &instance->m_handle ); + if( i_res != UPNP_E_SUCCESS ) + { + msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) ); + delete instance; + return NULL; + } + + /* libupnp does not treat a maximum content length of 0 as unlimited + * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate + * between versions */ + if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS ) + { + msg_Err( p_obj, "Failed to set maximum content length: %s", + UpnpGetErrorMessage( i_res )); + delete instance; + return NULL; + } + s_instance = instance; + } + s_instance->m_refcount++; + return s_instance; +} + +void UpnpInstanceWrapper::release() +{ + UpnpInstanceWrapper *p_delete = NULL; + vlc_mutex_lock( &s_lock ); + if (--s_instance->m_refcount == 0) + { + p_delete = s_instance; + s_instance = NULL; + } + vlc_mutex_unlock( &s_lock ); + delete p_delete; +} + +UpnpClient_Handle UpnpInstanceWrapper::handle() const +{ + return m_handle; +} + +int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, UpnpEventPtr p_event, void *p_user_data) +{ + for (Listeners::iterator iter = s_listeners.begin(); iter != s_listeners.end(); ++iter) + { + (*iter)->onEvent(event_type, p_event, p_user_data); + } + + return 0; +} + +void UpnpInstanceWrapper::addListener(ListenerPtr listener) +{ + vlc_mutex_lock( &s_lock ); + if ( std::find( s_listeners.begin(), s_listeners.end(), listener) != s_listeners.end() ) + { + vlc_mutex_unlock( &s_lock ); + return; + } + s_listeners.push_back( std::move(listener) ); + vlc_mutex_unlock( &s_lock ); +} + +void UpnpInstanceWrapper::removeListener(ListenerPtr listener) +{ + vlc_mutex_lock( &s_lock ); + Listeners::iterator iter = std::find( s_listeners.begin(), s_listeners.end(), listener ); + if ( iter == s_listeners.end() ) + { + vlc_mutex_unlock( &s_lock ); + return; + } + + s_listeners.erase( iter ); + vlc_mutex_unlock( &s_lock ); +} diff --git a/modules/services_discovery/upnp-wrapper.hpp b/modules/services_discovery/upnp-wrapper.hpp new file mode 100644 index 0000000000..33412cb993 --- /dev/null +++ b/modules/services_discovery/upnp-wrapper.hpp @@ -0,0 +1,349 @@ +/***************************************************************************** + * upnp-wrapper.hpp : UPnP Instance Wrapper class header + ***************************************************************************** + * Copyright © 2004-2018 VLC authors and VideoLAN + * + * Authors: Rémi Denis-Courmont (original plugin) + * Christian Henz + * Mirsal Ennaime + * Hugo Beauzée-Luyssen + * Shaleen Jain + * + * 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. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#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 ListenerPtr; + typedef std::vector 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( 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( 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 + +inline char *getPreferedAdapter() +{ + return NULL; +} + +#else + +inline char *getIpv4ForMulticast() +{ + return NULL; +} + +#endif + +#endif /* _WIN32 */ diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp index 7f8ed230ae..ae235071a9 100644 --- a/modules/services_discovery/upnp.cpp +++ b/modules/services_discovery/upnp.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include @@ -38,30 +37,6 @@ #include #include -#if UPNP_VERSION < 10623 -/* - * Compat functions and typedefs for libupnp prior to 1.8 - */ - -typedef Upnp_Discovery UpnpDiscovery; -typedef Upnp_Action_Complete UpnpActionComplete; - -static const char* UpnpDiscovery_get_Location_cstr( const UpnpDiscovery* p_discovery ) -{ - return p_discovery->Location; -} - -static const char* UpnpDiscovery_get_DeviceID_cstr( const UpnpDiscovery* p_discovery ) -{ - return p_discovery->DeviceId; -} - -static IXML_Document* UpnpActionComplete_get_ActionResult( const UpnpActionComplete* p_result ) -{ - return p_result->ActionResult; -} -#endif - /* * Constants */ @@ -93,10 +68,6 @@ struct access_sys_t UpnpInstanceWrapper* p_upnp; }; -UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance; -UpnpInstanceWrapper::Listeners UpnpInstanceWrapper::s_listeners; -vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX; - /* * VLC callback prototypes */ @@ -140,30 +111,6 @@ vlc_module_begin() VLC_SD_PROBE_SUBMODULE vlc_module_end() - -/* - * Returns the value of a child element, or NULL on error - */ -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 ); -} - /* * Extracts the result document from a SOAP response */ @@ -1313,329 +1260,3 @@ static void Close( vlc_object_t* p_this ) } } - -UpnpInstanceWrapper::UpnpInstanceWrapper() - : m_handle( -1 ) - , m_refcount( 0 ) -{ -} - -UpnpInstanceWrapper::~UpnpInstanceWrapper() -{ - UpnpUnRegisterClient( m_handle ); - UpnpFinish(); -} - -#ifdef _WIN32 - -static 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; -} - -static 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( 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( p_adapter ); - if (p_adapter_lh->FirstGatewayAddress == NULL) - return false; - if (ipv6) - return p_adapter_lh->Ipv6Enabled; - return p_adapter_lh->Ipv4Enabled; -} - -static 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 - -static 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 - -static 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 - -static char *getPreferedAdapter() -{ - return NULL; -} - -#else - -static char *getIpv4ForMulticast() -{ - return NULL; -} - -#endif - -#endif /* _WIN32 */ - -UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj) -{ - vlc_mutex_locker lock( &s_lock ); - if ( s_instance == NULL ) - { - UpnpInstanceWrapper* instance = new(std::nothrow) UpnpInstanceWrapper; - if ( unlikely( !instance ) ) - { - return NULL; - } - - #ifdef UPNP_ENABLE_IPV6 - char* psz_miface = var_InheritString( p_obj, "miface" ); - if (psz_miface == NULL) - psz_miface = getPreferedAdapter(); - msg_Info( p_obj, "Initializing libupnp on '%s' interface", psz_miface ? psz_miface : "default" ); - int i_res = UpnpInit2( psz_miface, 0 ); - free( psz_miface ); - #else - /* If UpnpInit2 isnt available, initialize on first IPv4-capable interface */ - char *psz_hostip = getIpv4ForMulticast(); - int i_res = UpnpInit( psz_hostip, 0 ); - free(psz_hostip); - #endif /* UPNP_ENABLE_IPV6 */ - if( i_res != UPNP_E_SUCCESS ) - { - msg_Err( p_obj, "Initialization failed: %s", UpnpGetErrorMessage( i_res ) ); - delete instance; - return NULL; - } - - ixmlRelaxParser( 1 ); - - /* Register a control point */ - i_res = UpnpRegisterClient( Callback, instance, &instance->m_handle ); - if( i_res != UPNP_E_SUCCESS ) - { - msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) ); - delete instance; - return NULL; - } - - /* libupnp does not treat a maximum content length of 0 as unlimited - * until 64dedf (~ pupnp v1.6.7) and provides no sane way to discriminate - * between versions */ - if( (i_res = UpnpSetMaxContentLength( INT_MAX )) != UPNP_E_SUCCESS ) - { - msg_Err( p_obj, "Failed to set maximum content length: %s", - UpnpGetErrorMessage( i_res )); - delete instance; - return NULL; - } - s_instance = instance; - } - s_instance->m_refcount++; - return s_instance; -} - -void UpnpInstanceWrapper::release() -{ - UpnpInstanceWrapper *p_delete = NULL; - vlc_mutex_lock( &s_lock ); - if (--s_instance->m_refcount == 0) - { - p_delete = s_instance; - s_instance = NULL; - } - vlc_mutex_unlock( &s_lock ); - delete p_delete; -} - -UpnpClient_Handle UpnpInstanceWrapper::handle() const -{ - return m_handle; -} - -int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, UpnpEventPtr p_event, void *p_user_data) -{ - for (Listeners::iterator iter = s_listeners.begin(); iter != s_listeners.end(); ++iter) - { - (*iter)->onEvent(event_type, p_event, p_user_data); - } - - return 0; -} - -void UpnpInstanceWrapper::addListener(ListenerPtr listener) -{ - vlc_mutex_lock( &s_lock ); - if ( std::find( s_listeners.begin(), s_listeners.end(), listener) != s_listeners.end() ) - return; - s_listeners.push_back( std::move(listener) ); - vlc_mutex_unlock( &s_lock ); -} - -void UpnpInstanceWrapper::removeListener(ListenerPtr listener) -{ - vlc_mutex_lock( &s_lock ); - Listeners::iterator iter = std::find( s_listeners.begin(), s_listeners.end(), listener ); - if ( iter == s_listeners.end() ) - return; - - s_listeners.erase( iter ); - vlc_mutex_unlock( &s_lock ); -} diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp index 325e1251c0..3ff63e8867 100644 --- a/modules/services_discovery/upnp.hpp +++ b/modules/services_discovery/upnp.hpp @@ -23,82 +23,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif +#include "upnp-wrapper.hpp" #include #include -#include #ifdef _WIN32 #include #include #endif -#include -#include -#include #include -#if UPNP_VERSION < 10800 -typedef void* UpnpEventPtr; -#else -typedef const void* UpnpEventPtr; -#endif - -namespace SD -{ - class MediaServerList; -} - -/* - * libUpnp allows only one instance per process, so we have to share one for - * both SD & Access module - * Since the callback is bound to the UpnpClient_Handle, we have to register - * a wrapper callback, in order for the access module to be able to initialize - * libUpnp first. - * When a SD wishes to use libUpnp, it will provide its own callback, that the - * wrapper will forward. - * This way, we always have a register callback & a client handle. - */ -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 ListenerPtr; - typedef std::vector 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(); -}; - namespace SD {