/***************************************************************************** * 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. *****************************************************************************/ #ifndef UPNP_WRAPPER_H #define UPNP_WRAPPER_H #include #include #include #include #include #include #ifdef _WIN32 #include #endif #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 #ifdef __APPLE__ #include #endif #if defined(TARGET_OS_OSX) && TARGET_OS_OSX #include #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 */