/*************************************************************************** * * Copyright (C) 2001-2002 Microsoft Corporation. All Rights Reserved. * * File: dpnhupnpintfobj.cpp * * Content: DPNHUPNP main interface object class. * * History: * Date By Reason * ======== ======== ========= * 04/16/01 VanceO Split DPNATHLP into DPNHUPNP and DPNHPAST. * ***************************************************************************/ #include "dpnhupnpi.h" //============================================================================= // Definitions //============================================================================= #define ACTIVE_MAPPING_VERSION 2 // version identifier for active mapping registry data #define MAX_LONG_LOCK_WAITING_THREADS 0xFFFF // that's a lot of simultaneous threads! #define UPNP_SEARCH_MESSAGE_INTERVAL 499 // how often discovery multicast messages should be sent, in case of packet loss (note Win9x errata for values 500-1000ms) #define UPNP_DGRAM_RECV_BUFFER_SIZE 1500 #define UPNP_STREAM_RECV_BUFFER_INITIAL_SIZE (4 * 1024) // 4 K, must be less than MAX_RECEIVE_BUFFER_SIZE #define MAX_UPNP_HEADER_LENGTH UPNP_STREAM_RECV_BUFFER_INITIAL_SIZE #define LEASE_RENEW_TIME 120000 // renew if less than 2 minutes remaining #define FAKE_PORT_LEASE_TIME 300000 // 5 minutes #define IOCOMPLETE_WAIT_INTERVAL 100 // 100 ms between attempts #define MAX_NUM_IOCOMPLETE_WAITS 10 // wait at most 1 second #define MAX_NUM_HOMENETUNMAP_ATTEMPTS 3 // 3 tries #define HOMENETUNMAP_SLEEP_FACTOR 10 // 10 ms, 20 ms, 30 ms #define MAX_UPNP_MAPPING_DESCRIPTION_SIZE 256 // 255 characters + NULL termination #define MAX_INSTANCENAMEDOBJECT_SIZE 64 #define INSTANCENAMEDOBJECT_FORMATSTRING _T("DPNHUPnP Instance %u") #define GUID_STRING_LENGTH 42 // maximum length of "{xxx...}" guid string, without NULL termination #define PORTMAPPINGPROTOCOL_TCP 6 #define PORTMAPPINGPROTOCOL_UDP 17 #define MAX_RESERVED_PORT 1024 #define MAX_NUM_INSTANCE_EVENT_ATTEMPTS 5 #define MAX_NUM_RANDOM_PORT_TRIES 5 #define MAX_NUM_TIMEWAITCONNECT_RETRIES 20 // allow a decent number since these are detected quickly and without net traffic #ifdef DBG #define MAX_TRANSACTION_LOG_SIZE (5 * 1024 * 1024) // 5 MB #endif // DBG #ifndef DPNBUILD_NOWINSOCK2 //============================================================================= // WinSock 1 version of IP options //============================================================================= #define IP_MULTICAST_IF_WINSOCK1 2 #define IP_MULTICAST_TTL_WINSOCK1 3 #define IP_TTL_WINSOCK1 7 #endif // ! DPNBUILD_NOWINSOCK2 //============================================================================= // Macros //============================================================================= //#ifdef _X86 #define IS_CLASSD_IPV4_ADDRESS(dwAddr) (( (*((BYTE*) &(dwAddr))) & 0xF0) == 0xE0) // 1110 high bits or 224.0.0.0 - 239.255.255.255 multicast address, in network byte order #define NTOHS(x) ( (((x) >> 8) & 0x00FF) | (((x) << 8) & 0xFF00) ) #define HTONS(x) NTOHS(x) //#endif _X86 //============================================================================= // HTTP/SSDP/SOAP/UPnP header strings (from upnpmsgs.h) //============================================================================= const char * c_szResponseHeaders[] = { // // Headers used in discovery response // "CACHE-CONTROL", "DATE", "EXT", "LOCATION", "SERVER", "ST", "USN", // // Additional headers used in description response // "CONTENT-LANGUAGE", "CONTENT-LENGTH", "CONTENT-TYPE", "TRANSFER-ENCODING", // // Other known headers // "HOST", "NT", "NTS", "MAN", "MX", "AL", "CALLBACK", "TIMEOUT", "SCOPE", "SID", "SEQ", }; //============================================================================= // Pre-built UPnP message strings (from upnpmsgs.h) //============================================================================= const char c_szUPnPMsg_Discover_Service_WANIPConnection[] = "M-SEARCH * " HTTP_VERSION EOL "HOST: " UPNP_DISCOVERY_MULTICAST_ADDRESS ":" UPNP_PORT_A EOL "MAN: \"ssdp:discover\"" EOL "MX: 2" EOL "ST: " URI_SERVICE_WANIPCONNECTION_A EOL EOL; const char c_szUPnPMsg_Discover_Service_WANPPPConnection[] = "M-SEARCH * " HTTP_VERSION EOL "HOST: " UPNP_DISCOVERY_MULTICAST_ADDRESS ":" UPNP_PORT_A EOL "MAN: \"ssdp:discover\"" EOL "MX: 2" EOL "ST: " URI_SERVICE_WANPPPCONNECTION_A EOL EOL; // // The disclaimer: // // A UPnP device may implement both the WANIPConnection and WANPPPConnection // services. We do not have any fancy logic to pick one, we just use the first // device that responds to our discovery requests, and use the first matching // service we encounter in the device description XML. // // Additionally, future UPnP devices may wish to present multiple device or // service instances with the intention that one client gets control of the // entire instance (additional clients would need to use a different instance). // It's not clear to me what a UPnP device (or client, for that matter) would // really gain by having such a setup. I imagine a new error code would have // to be returned whenever a client tried to control an instance that another // client already owned (don't ask me how it would know that, by picking the // first user or selectively responding to discovery requests, I guess). // Regardless, we do not currently support that. As noted above, we pick the // first instance and run with it. // // // Topmost tag is considered optional for all XML and is ignored. // // // This solution assumes InternetGatewayDevice (not WANDevice or // WANConnectionDevice) will be the topmost item in the response. This is based // on the following UPnP spec excerpt: // // "Note that a single physical device may include multiple logical devices. // Multiple logical devices can be modeled as a single root device with // embedded devices (and services) or as multiple root devices (perhaps with // no embedded devices). In the former case, there is one UPnP device // description for the root device, and that device description contains a // description for all embedded devices. In the latter case, there are // multiple UPnP device descriptions, one for each root device." // const char * c_szElementStack_service[] = { "root", "device", // InternetGatewayDevice "deviceList", "device", // WANDevice "deviceList", "device", // WANConnectionDevice "serviceList", "service" }; /* const char * c_szElementStack_QueryStateVariableResponse[] = { "Envelope", "Body", CONTROL_QUERYSTATEVARIABLE_A CONTROL_RESPONSESUFFIX_A }; */ const char * c_szElementStack_GetExternalIPAddressResponse[] = { "Envelope", "Body", ACTION_GETEXTERNALIPADDRESS_A CONTROL_RESPONSESUFFIX_A }; const char * c_szElementStack_AddPortMappingResponse[] = { "Envelope", "Body", ACTION_ADDPORTMAPPING_A CONTROL_RESPONSESUFFIX_A }; const char * c_szElementStack_GetSpecificPortMappingEntryResponse[] = { "Envelope", "Body", ACTION_GETSPECIFICPORTMAPPINGENTRY_A CONTROL_RESPONSESUFFIX_A }; const char * c_szElementStack_DeletePortMappingResponse[] = { "Envelope", "Body", ACTION_DELETEPORTMAPPING_A CONTROL_RESPONSESUFFIX_A }; const char * c_szElementStack_ControlResponseFailure[] = { "Envelope", "Body", "Fault", "detail", "UPnPError" }; #ifdef WINNT //============================================================================= // Related UPnP services //============================================================================= TCHAR * c_tszUPnPServices[] = { _T("SSDPSRV"), // SSDP Discovery Service _T("UPNPHOST"), // Universal Plug and Play Device Host - we key off this even though it's for device hosts instead of control points }; #endif // WINNT //============================================================================= // Local structures //============================================================================= typedef struct _CONTROLRESPONSEPARSECONTEXT { CONTROLRESPONSETYPE ControlResponseType; // type of control response expected CUPnPDevice * pUPnPDevice; // pointer to UPnP device being used DWORD dwHTTPResponseCode; // HTTP response code for this message PUPNP_CONTROLRESPONSE_INFO pControlResponseInfo; // place to info returned in control response } CONTROLRESPONSEPARSECONTEXT, * PCONTROLRESPONSEPARSECONTEXT; typedef struct _DPNHACTIVEFIREWALLMAPPING { DWORD dwVersion; // version identifier for this mapping DWORD dwInstanceKey; // key identifying DPNHUPNP instance that created this mapping DWORD dwFlags; // flags describing port being registered DWORD dwAddressV4; // address being mapped WORD wPort; // port being mapped } DPNHACTIVEFIREWALLMAPPING, * PDPNHACTIVEFIREWALLMAPPING; typedef struct _DPNHACTIVENATMAPPING { DWORD dwVersion; // version identifier for this mapping DWORD dwInstanceKey; // key identifying DPNHUPNP instance that created this mapping DWORD dwUPnPDeviceID; // identifier for particular UPnP device corresponding to this mapping (meaningful only to owning instance) DWORD dwFlags; // flags describing port being registered DWORD dwInternalAddressV4; // internal client address being mapped WORD wInternalPort; // internal client port being mapped DWORD dwExternalAddressV4; // external public address that was mapped WORD wExternalPort; // external public port that was mapped } DPNHACTIVENATMAPPING, * PDPNHACTIVENATMAPPING; //============================================================================= // Local functions //============================================================================= VOID strtrim(CHAR ** pszStr); #ifdef WINCE void GetExeName(WCHAR * wszPath); #endif // WINCE #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CNATHelpUPnP" //============================================================================= // CNATHelpUPnP constructor //----------------------------------------------------------------------------- // // Description: Initializes the new CNATHelpUPnP object. // // Arguments: // BOOL fNotCreatedWithCOM - TRUE if this object is being instantiated // without COM, FALSE if it is through COM. // // Returns: None (the object). //============================================================================= CNATHelpUPnP::CNATHelpUPnP(const BOOL fNotCreatedWithCOM) { this->m_blList.Initialize(); this->m_Sig[0] = 'N'; this->m_Sig[1] = 'A'; this->m_Sig[2] = 'T'; this->m_Sig[3] = 'H'; this->m_lRefCount = 1; // someone must have a pointer to this object if (fNotCreatedWithCOM) { this->m_dwFlags = NATHELPUPNPOBJ_NOTCREATEDWITHCOM; } else { this->m_dwFlags = 0; } this->m_hLongLockSemaphore = NULL; this->m_lNumLongLockWaitingThreads = 0; this->m_dwLockThreadID = 0; #ifndef DPNBUILD_NOWINSOCK2 this->m_hAlertEvent = NULL; this->m_hAlertIOCompletionPort = NULL; this->m_dwAlertCompletionKey = 0; #endif // ! DPNBUILD_NOWINSOCK2 this->m_blDevices.Initialize(); this->m_blRegisteredPorts.Initialize(); this->m_blUnownedPorts.Initialize(); this->m_dwLastUpdateServerStatusTime = 0; this->m_dwNextPollInterval = 0; this->m_dwNumLeases = 0; this->m_dwEarliestLeaseExpirationTime = 0; this->m_blUPnPDevices.Initialize(); this->m_dwInstanceKey = 0; this->m_dwCurrentUPnPDeviceID = 0; this->m_hMappingStillActiveNamedObject = NULL; #ifndef DPNBUILD_NOWINSOCK2 this->m_hIpHlpApiDLL = NULL; this->m_pfnGetAdaptersInfo = NULL; this->m_pfnGetIpForwardTable = NULL; this->m_pfnGetBestRoute = NULL; this->m_hRasApi32DLL = NULL; this->m_pfnRasGetEntryHrasconnW = NULL; this->m_pfnRasGetProjectionInfo = NULL; this->m_sIoctls = INVALID_SOCKET; this->m_polAddressListChange = NULL; #endif // ! DPNBUILD_NOWINSOCK2 this->m_hWinSockDLL = NULL; this->m_pfnWSAStartup = NULL; this->m_pfnWSACleanup = NULL; this->m_pfnWSAGetLastError = NULL; this->m_pfnsocket = NULL; this->m_pfnclosesocket = NULL; this->m_pfnbind = NULL; this->m_pfnsetsockopt = NULL; this->m_pfngetsockname = NULL; this->m_pfnselect = NULL; this->m_pfn__WSAFDIsSet = NULL; this->m_pfnrecvfrom = NULL; this->m_pfnsendto = NULL; this->m_pfngethostname = NULL; this->m_pfngethostbyname = NULL; this->m_pfninet_addr = NULL; #ifndef DPNBUILD_NOWINSOCK2 this->m_pfnWSASocketA = NULL; this->m_pfnWSAIoctl = NULL; this->m_pfnWSAGetOverlappedResult = NULL; #endif // ! DPNBUILD_NOWINSOCK2 this->m_pfnioctlsocket = NULL; this->m_pfnconnect = NULL; this->m_pfnshutdown = NULL; this->m_pfnsend = NULL; this->m_pfnrecv = NULL; #ifdef DBG this->m_pfngetsockopt = NULL; this->m_dwNumDeviceAdds = 0; this->m_dwNumDeviceRemoves = 0; this->m_dwNumServerFailures = 0; #endif // DBG } // CNATHelpUPnP::CNATHelpUPnP #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::~CNATHelpUPnP" //============================================================================= // CNATHelpUPnP destructor //----------------------------------------------------------------------------- // // Description: Frees the CNATHelpUPnP object. // // Arguments: None. // // Returns: None. //============================================================================= CNATHelpUPnP::~CNATHelpUPnP(void) { DPFX(DPFPREP, 8, "(0x%p) NumDeviceAdds = %u, NumDeviceRemoves = %u, NumServerFailures = %u", this, this->m_dwNumDeviceAdds, this->m_dwNumDeviceRemoves, this->m_dwNumServerFailures); DNASSERT(this->m_blList.IsEmpty()); DNASSERT(this->m_lRefCount == 0); DNASSERT((this->m_dwFlags & ~NATHELPUPNPOBJ_NOTCREATEDWITHCOM) == 0); DNASSERT(this->m_hLongLockSemaphore == NULL); DNASSERT(this->m_lNumLongLockWaitingThreads == 0); DNASSERT(this->m_dwLockThreadID == 0); #ifndef DPNBUILD_NOWINSOCK2 DNASSERT(this->m_hAlertEvent == NULL); DNASSERT(this->m_hAlertIOCompletionPort == NULL); #endif // ! DPNBUILD_NOWINSOCK2 DNASSERT(this->m_blDevices.IsEmpty()); DNASSERT(this->m_blRegisteredPorts.IsEmpty()); DNASSERT(this->m_blUnownedPorts.IsEmpty()); DNASSERT(this->m_dwNumLeases == 0); DNASSERT(this->m_blUPnPDevices.IsEmpty()); DNASSERT(this->m_hMappingStillActiveNamedObject == NULL); #ifndef DPNBUILD_NOWINSOCK2 DNASSERT(this->m_hIpHlpApiDLL == NULL); DNASSERT(this->m_hRasApi32DLL == NULL); DNASSERT(this->m_hWinSockDLL == NULL); DNASSERT(this->m_sIoctls == INVALID_SOCKET); DNASSERT(this->m_polAddressListChange == NULL); #endif // ! DPNBUILD_NOWINSOCK2 // // For grins, change the signature before deleting the object. // this->m_Sig[3] = 'h'; } // CNATHelpUPnP::~CNATHelpUPnP #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::QueryInterface" //============================================================================= // CNATHelpUPnP::QueryInterface //----------------------------------------------------------------------------- // // Description: Retrieves a new reference for an interfaces supported by this // CNATHelpUPnP object. // // Arguments: // REFIID riid - Reference to interface ID GUID. // LPVOID * ppvObj - Place to store pointer to object. // // Returns: HRESULT // S_OK - Returning a valid interface pointer. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPOINTER - The destination pointer is invalid. // E_NOINTERFACE - Invalid interface was specified. //============================================================================= STDMETHODIMP CNATHelpUPnP::QueryInterface(REFIID riid, LPVOID * ppvObj) { HRESULT hr = DPNH_OK; DPFX(DPFPREP, 3, "(0x%p) Parameters: (REFIID, 0x%p)", this, ppvObj); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid NATHelper object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if ((! IsEqualIID(riid, IID_IUnknown)) && (! IsEqualIID(riid, IID_IDirectPlayNATHelp))) { DPFX(DPFPREP, 0, "Unsupported interface!"); hr = E_NOINTERFACE; goto Failure; } if ((ppvObj == NULL) || (IsBadWritePtr(ppvObj, sizeof(void*)))) { DPFX(DPFPREP, 0, "Invalid interface pointer specified!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } // // Add a reference, and return the interface pointer (which is actually // just the object pointer, they line up because CNATHelpUPnP inherits from // the interface declaration). // this->AddRef(); (*ppvObj) = this; Exit: DPFX(DPFPREP, 3, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::QueryInterface #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::AddRef" //============================================================================= // CNATHelpUPnP::AddRef //----------------------------------------------------------------------------- // // Description: Adds a reference to this CNATHelpUPnP object. // // Arguments: None. // // Returns: New refcount. //============================================================================= STDMETHODIMP_(ULONG) CNATHelpUPnP::AddRef(void) { LONG lRefCount; DNASSERT(this->IsValidObject()); // // There must be at least 1 reference to this object, since someone is // calling AddRef. // DNASSERT(this->m_lRefCount > 0); lRefCount = InterlockedIncrement(&this->m_lRefCount); DPFX(DPFPREP, 3, "[0x%p] RefCount [0x%lx]", this, lRefCount); return lRefCount; } // CNATHelpUPnP::AddRef #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::Release" //============================================================================= // CNATHelpUPnP::Release //----------------------------------------------------------------------------- // // Description: Removes a reference to this CNATHelpUPnP object. When the // refcount reaches 0, this object is destroyed. // You must NULL out your pointer to this object after calling // this function. // // Arguments: None. // // Returns: New refcount. //============================================================================= STDMETHODIMP_(ULONG) CNATHelpUPnP::Release(void) { LONG lRefCount; DNASSERT(this->IsValidObject()); // // There must be at least 1 reference to this object, since someone is // calling Release. // DNASSERT(this->m_lRefCount > 0); lRefCount = InterlockedDecrement(&this->m_lRefCount); // // Was that the last reference? If so, we're going to destroy this object. // if (lRefCount == 0) { DPFX(DPFPREP, 3, "[0x%p] RefCount hit 0, destroying object.", this); // // First pull it off the global list. // DNEnterCriticalSection(&g_csGlobalsLock); this->m_blList.RemoveFromList(); DNASSERT(g_lOutstandingInterfaceCount > 0); g_lOutstandingInterfaceCount--; // update count so DLL can unload now works correctly DNLeaveCriticalSection(&g_csGlobalsLock); // // Make sure it's closed. // if (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) { // // Assert so that the user can fix his/her broken code! // DNASSERT(! "DirectPlayNATHelpUPNP object being released without calling Close first!"); // // Then go ahead and do the right thing. Ignore error, we can't do // much about it. // this->Close(0); } // // Then uninitialize the object. // this->UninitializeObject(); // // Finally delete this (!) object. // delete this; } else { DPFX(DPFPREP, 3, "[0x%p] RefCount [0x%lx]", this, lRefCount); } return lRefCount; } // CNATHelpUPnP::Release #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::Initialize" //============================================================================= // CNATHelpUPnP::Initialize //----------------------------------------------------------------------------- // // Description: Prepares the object for use. No attempt is made to contact // any Internet gateway servers at this time. The user should // call GetCaps with the DPNHGETCAPS_UPDATESERVERSTATUS flag to // search for a server. // // Initialize must be called before using any other function, // and must be balanced with a call to Close. Initialize can only // be called once unless Close returns it to the uninitialized // state. // // One of DPNHINITIALIZE_DISABLEREMOTENATSUPPORT or // DPNHINITIALIZE_DISABLELOCALFIREWALLSUPPORT may be specified, // but not both. // // Arguments: // DWORD dwFlags - Flags to use when initializing. // // Returns: HRESULT // DPNH_OK - Initialization was successful. // DPNHERR_ALREADYINITIALIZED - Initialize has already been called. // DPNHERR_GENERIC - An error occurred while initializing. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_OUTOFMEMORY - There is not enough memory to initialize. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::Initialize(const DWORD dwFlags) { HRESULT hr; BOOL fHaveLock = FALSE; BOOL fSetFlags = FALSE; #ifndef WINCE OSVERSIONINFO osvi; #endif // ! WINCE BOOL fWinSockStarted = FALSE; WSADATA wsadata; int iError; #ifndef DPNBUILD_NOWINSOCK2 SOCKADDR_IN saddrinTemp; #endif // ! DPNBUILD_NOWINSOCK2 TCHAR tszObjectName[MAX_INSTANCENAMEDOBJECT_SIZE]; PSECURITY_ATTRIBUTES pSecurityAttributes; DWORD dwTry; #ifdef WINNT SID_IDENTIFIER_AUTHORITY SidIdentifierAuthorityWorld = SECURITY_WORLD_SID_AUTHORITY; PSID pSid = NULL; DWORD dwAclLength; ACL * pAcl = NULL; BYTE abSecurityDescriptorBuffer[SECURITY_DESCRIPTOR_MIN_LENGTH]; SECURITY_ATTRIBUTES SecurityAttributes; #endif // WINNT #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%lx)", this, dwFlags); #ifndef WINCE // // Print info about the current build. // #ifdef WINNT DPFX(DPFPREP, 7, "Build type = NT, platform = %s", ((DNGetOSType() == VER_PLATFORM_WIN32_NT) ? _T("NT") : _T("9x"))); #else // ! WINNT DPFX(DPFPREP, 7, "Build type = 9x, platform = %s, filedate = %s", ((DNGetOSType() == VER_PLATFORM_WIN32_NT) ? _T("NT") : _T("9x"))); #endif // ! WINNT #endif // ! WINCE // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; // // Skip the failure cleanup code, we haven't set anything up. // goto Exit; } // // Validate the parameters. // if (dwFlags & ~(DPNHINITIALIZE_DISABLEGATEWAYSUPPORT | DPNHINITIALIZE_DISABLELOCALFIREWALLSUPPORT)) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; // // Skip the failure cleanup code, we haven't set anything up. // goto Exit; } // // Both flags cannot be specified at the same time. If the caller doesn't // want any NAT functionality, why use this object all? // if ((dwFlags & (DPNHINITIALIZE_DISABLEGATEWAYSUPPORT | DPNHINITIALIZE_DISABLELOCALFIREWALLSUPPORT)) == (DPNHINITIALIZE_DISABLEGATEWAYSUPPORT | DPNHINITIALIZE_DISABLELOCALFIREWALLSUPPORT)) { DPFX(DPFPREP, 0, "Either DISABLEGATEWAYSUPPORT flag or DISABLELOCALFIREWALLSUPPORT flag can be used, but not both!"); hr = DPNHERR_INVALIDFLAGS; // // Skip the failure cleanup code, we haven't set anything up. // goto Exit; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); // // Skip the failure cleanup code, we haven't set anything up. // goto Exit; } fHaveLock = TRUE; // // Make sure object is in right state. // if ((this->m_dwFlags & ~NATHELPUPNPOBJ_NOTCREATEDWITHCOM) != 0) { DPFX(DPFPREP, 0, "Object already initialized!"); hr = DPNHERR_ALREADYINITIALIZED; // // Skip the failure cleanup code, we haven't set anything up. // goto Exit; } // // Read in the manual override settings from the registry // ReadRegistrySettings(); // // We're not completely initialized yet, but set the flag(s) now. // this->m_dwFlags |= NATHELPUPNPOBJ_INITIALIZED; fSetFlags = TRUE; // // Store the user's settings. // if (dwFlags & DPNHINITIALIZE_DISABLEGATEWAYSUPPORT) { DPFX(DPFPREP, 1, "User requested that Internet gateways not be supported."); } else { this->m_dwFlags |= NATHELPUPNPOBJ_USEUPNP; } #ifndef DPNBUILD_NOHNETFWAPI if (dwFlags & DPNHINITIALIZE_DISABLELOCALFIREWALLSUPPORT) { DPFX(DPFPREP, 1, "User requested that local firewalls not be supported."); } else { this->m_dwFlags |= NATHELPUPNPOBJ_USEHNETFWAPI; } #endif // ! DPNBUILD_NOHNETFWAPI switch (g_dwUPnPMode) { case OVERRIDEMODE_FORCEON: { // // Force UPnP on. // DPFX(DPFPREP, 1, "Forcing UPnP support on."); this->m_dwFlags |= NATHELPUPNPOBJ_USEUPNP; break; } case OVERRIDEMODE_FORCEOFF: { // // Force UPnP off. // DPFX(DPFPREP, 1, "Forcing UPnP support off."); this->m_dwFlags &= ~NATHELPUPNPOBJ_USEUPNP; break; } default: { // // Leave UPnP settings as they were set by the application. // #ifdef WINNT // // But if UPnP related service(s) are disabled, we'll take that as // our cue to not use UPnP NAT traversal even though we don't // actually use those services. We assume the user wanted to // squelch all SSDP/UPnP activity. It can still be forced back on // with a reg key, though, as indicated by the other switch cases. // if (this->IsUPnPServiceDisabled()) { DPFX(DPFPREP, 1, "Not using UPnP because a related service was disabled."); this->m_dwFlags &= ~NATHELPUPNPOBJ_USEUPNP; } #endif // WINNT break; } } #ifndef DPNBUILD_NOHNETFWAPI switch (g_dwHNetFWAPIMode) { case OVERRIDEMODE_FORCEON: { // // Force HNet firewall API on. // DPFX(DPFPREP, 1, "Forcing HNet firewall API support on."); this->m_dwFlags |= NATHELPUPNPOBJ_USEHNETFWAPI; break; } case OVERRIDEMODE_FORCEOFF: { // // Force HNet firewall API off. // DPFX(DPFPREP, 1, "Forcing HNet firewall API support off."); this->m_dwFlags &= ~NATHELPUPNPOBJ_USEHNETFWAPI; break; } default: { // // Leave HNet firewall API settings alone. // break; } } #endif // ! DPNBUILD_NOHNETFWAPI #ifndef WINCE // // Determine whether we're on a Win2K or higher NT OS, and if so, use the // "Global\\" prefix for named kernel objects so we can have Terminal // Server and Fast User Switching support. // ZeroMemory(&osvi, sizeof(osvi)); osvi.dwOSVersionInfoSize = sizeof(osvi); if (GetVersionEx(&osvi)) { if ((osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osvi.dwMajorVersion >= 5)) { DPFX(DPFPREP, 8, "Running Win2K or higher NT OS, using \"Global\\\" prefix."); this->m_dwFlags |= NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX; } #ifdef DBG else { DPFX(DPFPREP, 8, "Not on NT, or its pre-Win2K, not using \"Global\\\" prefix."); } #endif // DBG } #ifdef DBG else { dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't get OS version information (err = %u)! Not using \"Global\\\" prefix.", dwError); } #endif // DBG #endif // ! WINCE #ifdef DPNBUILD_NOWINSOCK2 #if defined(WINCE) && !defined(WINCE_ON_DESKTOP) this->m_hWinSockDLL = LoadLibrary( _T("winsock.dll") ); #else // ! WINCE this->m_hWinSockDLL = LoadLibrary( _T("wsock32.dll") ); #endif // ! WINCE if (this->m_hWinSockDLL == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't load WinSock 1 DLL (err = 0x%lx)!.", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } #else // ! DPNBUILD_NOWINSOCK2 // // Try loading the IP helper DLL. // this->m_hIpHlpApiDLL = LoadLibrary( _T("iphlpapi.dll") ); if (this->m_hIpHlpApiDLL == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 1, "Unable to load \"iphlpapi.dll\" (error = 0x%lx).", dwError); #endif // DBG // // That's not fatal, we can still function. // } else { // // Load the functions we'll use. // this->m_pfnGetAdaptersInfo = (PFN_GETADAPTERSINFO) GetProcAddress(this->m_hIpHlpApiDLL, _TWINCE("GetAdaptersInfo")); if (this->m_pfnGetAdaptersInfo == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Unable to get \"GetAdaptersInfo\" function (error = 0x%lx)!", dwError); #endif // DBG goto Exit; } this->m_pfnGetIpForwardTable = (PFN_GETIPFORWARDTABLE) GetProcAddress(this->m_hIpHlpApiDLL, _TWINCE("GetIpForwardTable")); if (this->m_pfnGetIpForwardTable == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Unable to get \"GetIpForwardTable\" function (error = 0x%lx)!", dwError); #endif // DBG goto Exit; } this->m_pfnGetBestRoute = (PFN_GETBESTROUTE) GetProcAddress(this->m_hIpHlpApiDLL, _TWINCE("GetBestRoute")); if (this->m_pfnGetBestRoute == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Unable to get \"GetBestRoute\" function (error = 0x%lx)!", dwError); #endif // DBG goto Exit; } } // // Try loading the RAS API DLL. // this->m_hRasApi32DLL = LoadLibrary( _T("rasapi32.dll") ); if (this->m_hRasApi32DLL == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 1, "Unable to load \"rasapi32.dll\" (error = 0x%lx).", dwError); #endif // DBG // // That's not fatal, we can still function. // } else { // // Load the functions we'll use. // this->m_pfnRasGetEntryHrasconnW = (PFN_RASGETENTRYHRASCONNW) GetProcAddress(this->m_hRasApi32DLL, _TWINCE("RasGetEntryHrasconnW")); if (this->m_pfnRasGetEntryHrasconnW == NULL) { // // This function does not exist on non-NT platforms. That's fine, // just dump the DLL handle so we don't try to use it. // #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 1, "Unable to get \"RasGetEntryHrasconnW\" function (error = 0x%lx), forgetting RAS DLL.", dwError); #endif // DBG FreeLibrary(this->m_hRasApi32DLL); this->m_hRasApi32DLL = NULL; } else { this->m_pfnRasGetProjectionInfo = (PFN_RASGETPROJECTIONINFO) GetProcAddress(this->m_hRasApi32DLL, #ifdef UNICODE _TWINCE("RasGetProjectionInfoW")); #else // ! UNICODE _TWINCE("RasGetProjectionInfoA")); #endif // ! UNICODE if (this->m_pfnRasGetProjectionInfo == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Unable to get \"RasGetProjectionInfoA/W\" function (error = 0x%lx)!", dwError); #endif // DBG goto Exit; } } } // // Load WinSock because we may be using our private UPnP implementation, or // we just need to get the devices. // this->m_hWinSockDLL = LoadLibrary( _T("ws2_32.dll") ); if (this->m_hWinSockDLL == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 1, "Couldn't load \"ws2_32.dll\" (err = 0x%lx), resorting to WinSock 1 functionality.", dwError); #endif // DBG this->m_hWinSockDLL = LoadLibrary( _T("wsock32.dll") ); if (this->m_hWinSockDLL == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't load \"wsock32.dll\" either (err = 0x%lx)!.", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // Remember that we had to resort to WinSock 1. // this->m_dwFlags |= NATHELPUPNPOBJ_WINSOCK1; } else { DPFX(DPFPREP, 1, "Loaded \"ws2_32.dll\", using WinSock 2 functionality."); } #endif // DPNBUILD_NOWINSOCK2 // // Load pointers to all the functions we use in WinSock. // hr = this->LoadWinSockFunctionPointers(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't load WinSock function pointers!"); goto Failure; } // // Fire up WinSock. Request 2.2 if we can. For the most part we only use // version 1.1 capabilities and interfaces anyway. The only exceptions are // using the event or I/O completion port handles for notification. // ZeroMemory(&wsadata, sizeof(wsadata)); #ifndef DPNBUILD_NOWINSOCK2 if (this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1) { #endif // ! DPNBUILD_NOWINSOCK2 iError = this->m_pfnWSAStartup(MAKEWORD(1, 1), &wsadata); #ifndef DPNBUILD_NOWINSOCK2 } else { iError = this->m_pfnWSAStartup(MAKEWORD(2, 2), &wsadata); } #endif // ! DPNBUILD_NOWINSOCK2 if (iError != 0) { DPFX(DPFPREP, 0, "Couldn't startup WinSock (error = %i)!", iError); hr = DPNHERR_GENERIC; goto Failure; } fWinSockStarted = TRUE; DPFX(DPFPREP, 4, "Initialized WinSock version %u.%u.", LOBYTE(wsadata.wVersion), HIBYTE(wsadata.wVersion)); #ifndef DPNBUILD_NOWINSOCK2 // // Try creating a UDP socket for use with WSAIoctl. Do this even if we're // WinSock 1 and can't use WSAIoctl socket. This allows us to make sure // TCP/IP is installed and working. // this->m_sIoctls = this->m_pfnsocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (this->m_sIoctls == INVALID_SOCKET) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't create Ioctl socket, error = %u!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // Try binding the socket. This is a continuation of the validation. // ZeroMemory(&saddrinTemp, sizeof(saddrinTemp)); saddrinTemp.sin_family = AF_INET; //saddrinTemp.sin_addr.S_un.S_addr = INADDR_ANY; //saddrinTemp.sin_port = 0; if (this->m_pfnbind(this->m_sIoctls, (SOCKADDR *) (&saddrinTemp), sizeof(saddrinTemp)) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't bind the Ioctl socket to arbitrary port on any interface, error = %u!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } #endif // ! DPNBUILD_NOWINSOCK2 // // Build appropriate access control structures. On NT, we want to allow // read access to everyone. On other platforms, security is ignored. // #ifdef WINNT if (! AllocateAndInitializeSid(&SidIdentifierAuthorityWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pSid)) { #ifdef DEBUG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't allocate and initialize SID, error = %u!", dwError); #endif // DEBUG hr = DPNHERR_GENERIC; goto Failure; } dwAclLength = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) // subtract out sizeof(ACCESS_ALLOWED_ACE.SidStart) + GetLengthSid(pSid); pAcl = (ACL*) DNMalloc(dwAclLength); if (pAcl == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } if (! InitializeAcl(pAcl, dwAclLength, ACL_REVISION)) { #ifdef DEBUG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't initialize ACL, error = %u!", dwError); #endif // DEBUG hr = DPNHERR_GENERIC; goto Failure; } if (! AddAccessAllowedAce(pAcl, ACL_REVISION, SYNCHRONIZE, pSid)) { #ifdef DEBUG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't add access allowed ACE, error = %u!", dwError); #endif // DEBUG hr = DPNHERR_GENERIC; goto Failure; } if (! InitializeSecurityDescriptor((PSECURITY_DESCRIPTOR) abSecurityDescriptorBuffer, SECURITY_DESCRIPTOR_REVISION)) { #ifdef DEBUG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't initialize security descriptor, error = %u!", dwError); #endif // DEBUG hr = DPNHERR_GENERIC; goto Failure; } if (! SetSecurityDescriptorDacl((PSECURITY_DESCRIPTOR) abSecurityDescriptorBuffer, TRUE, pAcl, FALSE)) { #ifdef DEBUG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't set security descriptor DACL, error = %u!", dwError); #endif // DEBUG hr = DPNHERR_GENERIC; goto Failure; } SecurityAttributes.nLength = sizeof(SecurityAttributes); SecurityAttributes.lpSecurityDescriptor = abSecurityDescriptorBuffer; SecurityAttributes.bInheritHandle = FALSE; pSecurityAttributes = &SecurityAttributes; #else // ! WINNT pSecurityAttributes = NULL; #endif // ! WINNT // // Use a random number for the instance key and event. We use this to let // other instances know that we're alive to avoid the crash-cleanup code. // Try to create the named event a couple times before giving up. // dwTry = 0; do { this->m_dwInstanceKey = GetGlobalRand(); DPFX(DPFPREP, 2, "Using crash cleanup key %u.", this->m_dwInstanceKey); #ifndef WINCE if (this->m_dwFlags & NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX) { wsprintf(tszObjectName, _T("Global\\") INSTANCENAMEDOBJECT_FORMATSTRING, this->m_dwInstanceKey); this->m_hMappingStillActiveNamedObject = DNCreateEvent(pSecurityAttributes, FALSE, FALSE, tszObjectName); } else #endif // ! WINCE { wsprintf(tszObjectName, INSTANCENAMEDOBJECT_FORMATSTRING, this->m_dwInstanceKey); this->m_hMappingStillActiveNamedObject = DNCreateEvent(pSecurityAttributes, FALSE, FALSE, tszObjectName); } if (this->m_hMappingStillActiveNamedObject == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't create mapping-still-active named object, error = %u!", dwError); #endif // DBG dwTry++; if (dwTry >= MAX_NUM_INSTANCE_EVENT_ATTEMPTS) { hr = DPNHERR_GENERIC; goto Failure; } // // Continue... // } } while (this->m_hMappingStillActiveNamedObject == NULL); #ifdef WINNT DNFree(pAcl); pAcl = NULL; FreeSid(pSid); pSid = NULL; #endif // WINNT // // Build the list of IP capable devices. // hr = this->CheckForNewDevices(NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't build device list!"); goto Failure; } // // We could technically try to contact UPnP devices right now, but we don't // because it's a slow blocking operation, and users have to call GetCaps // at least once anyway. // Exit: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (this->m_hMappingStillActiveNamedObject != NULL) { DNCloseHandle(this->m_hMappingStillActiveNamedObject); this->m_hMappingStillActiveNamedObject = NULL; } #ifdef WINNT if (pAcl != NULL) { DNFree(pAcl); pAcl = NULL; } if (pSid != NULL) { FreeSid(pSid); pSid = NULL; } #endif // WINNT this->RemoveAllItems(); #ifndef DPNBUILD_NOWINSOCK2 if (this->m_sIoctls != INVALID_SOCKET) { this->m_pfnclosesocket(this->m_sIoctls); // ignore error this->m_sIoctls = INVALID_SOCKET; } #endif // ! DPNBUILD_NOWINSOCK2 if (fWinSockStarted) { this->m_pfnWSACleanup(); // ignore error } #ifndef DPNBUILD_NOWINSOCK2 if (this->m_hWinSockDLL != NULL) { this->m_pfnWSAStartup = NULL; this->m_pfnWSACleanup = NULL; this->m_pfnWSAGetLastError = NULL; this->m_pfnsocket = NULL; this->m_pfnclosesocket = NULL; this->m_pfnbind = NULL; this->m_pfnsetsockopt = NULL; this->m_pfngetsockname = NULL; this->m_pfnselect = NULL; this->m_pfn__WSAFDIsSet = NULL; this->m_pfnrecvfrom = NULL; this->m_pfnsendto = NULL; this->m_pfngethostname = NULL; this->m_pfngethostbyname = NULL; this->m_pfninet_addr = NULL; this->m_pfnWSASocketA = NULL; this->m_pfnWSAIoctl = NULL; this->m_pfnWSAGetOverlappedResult = NULL; this->m_pfnioctlsocket = NULL; this->m_pfnconnect = NULL; this->m_pfnshutdown = NULL; this->m_pfnsend = NULL; this->m_pfnrecv = NULL; #ifdef DBG this->m_pfngetsockopt = NULL; #endif // DBG this->m_dwFlags &= ~NATHELPUPNPOBJ_WINSOCK1; FreeLibrary(this->m_hWinSockDLL); this->m_hWinSockDLL = NULL; } if (this->m_hRasApi32DLL != NULL) { this->m_pfnRasGetEntryHrasconnW = NULL; this->m_pfnRasGetProjectionInfo = NULL; FreeLibrary(this->m_hRasApi32DLL); this->m_hRasApi32DLL = NULL; } if (this->m_hIpHlpApiDLL != NULL) { this->m_pfnGetAdaptersInfo = NULL; this->m_pfnGetIpForwardTable = NULL; this->m_pfnGetBestRoute = NULL; FreeLibrary(this->m_hIpHlpApiDLL); this->m_hIpHlpApiDLL = NULL; } #endif // ! DPNBUILD_NOWINSOCK2 if (fSetFlags) { this->m_dwFlags &= ~(NATHELPUPNPOBJ_INITIALIZED | NATHELPUPNPOBJ_USEUPNP | #ifndef DPNBUILD_NOHNETFWAPI NATHELPUPNPOBJ_USEHNETFWAPI | #endif // ! DPNBUILD_NOHNETFWAPI #ifdef WINCE NATHELPUPNPOBJ_DEVICECHANGED); #else // ! WINCE NATHELPUPNPOBJ_DEVICECHANGED | NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX); #endif // ! WINCE } goto Exit; } // CNATHelpUPnP::Initialize #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::Close" //============================================================================= // CNATHelpUPnP::Close //----------------------------------------------------------------------------- // // Description: Shuts down and de-registers this application with any // Internet gateway servers. All port assignments are implicitly // freed as a result of this operation. // // This must balance a successful call to Initialize. // // Arguments: // DWORD dwFlags - Unused, must be zero. // // Returns: HRESULT // DPNH_OK - Closing the helper API was successful. // DPNHERR_GENERIC - An error occurred while closing. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to close. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::Close(const DWORD dwFlags) { HRESULT hr; BOOL fHaveLock = FALSE; int iError; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%lx)", this, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if (dwFlags != 0) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } // // We need to actively deregister any devices which are registered with // Internet gateways. // this->RemoveAllItems(); // // Close the named object since this process is going away. // if (this->m_hMappingStillActiveNamedObject != NULL) { DNCloseHandle(this->m_hMappingStillActiveNamedObject); this->m_hMappingStillActiveNamedObject = NULL; } #ifndef DPNBUILD_NOWINSOCK2 // // Close the Ioctl socket. // DNASSERT(this->m_sIoctls != INVALID_SOCKET); this->m_pfnclosesocket(this->m_sIoctls); // ignore error this->m_sIoctls = INVALID_SOCKET; // // If we submitted overlapped I/O, see if it got cancelled. // if (this->m_polAddressListChange != NULL) { OSVERSIONINFO osvi; OSVERSIONINFOEX osvix; BOOL fCanWait; DWORD dwAttempt; ZeroMemory(&osvi, sizeof(osvi)); osvi.dwOSVersionInfoSize = sizeof(osvi); if (GetVersionEx(&osvi)) { // // Any platform but Win2K Gold, Win2K + SP1, or Win2K + SP2 can // just go ahead and wait for the I/O to complete. // if ((osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) || (osvi.dwMajorVersion > 5) || (osvi.dwMinorVersion > 0)) { DPFX(DPFPREP, 3, "Windows %s version %u.%u detected, waiting for address list change Ioctl to complete.", ((osvi.dwPlatformId != VER_PLATFORM_WIN32_NT) ? _T("9x") : _T("NT")), osvi.dwMajorVersion, osvi.dwMinorVersion); fCanWait = TRUE; } else { // // Win2K versions < SP3 have a bug where the I/O is not always // cancelled by closing the socket. We can't wait for the // completion, sometimes it doesn't happen. // fCanWait = FALSE; ZeroMemory(&osvix, sizeof(osvix)); osvix.dwOSVersionInfoSize = sizeof(osvix); if (GetVersionEx((LPOSVERSIONINFO) (&osvix))) { // // If SP3 or later is applied, we know it's fixed. // if (osvix.wServicePackMajor >= 3) { DPFX(DPFPREP, 3, "Windows 2000 Service Pack %u detected, waiting for address list change Ioctl to complete.", osvix.wServicePackMajor); fCanWait = TRUE; } #ifdef DBG else { if (osvix.wServicePackMajor == 0) { DPFX(DPFPREP, 2, "Windows 2000 Gold detected, not waiting for address list change Ioctl to complete."); } else { DPFX(DPFPREP, 2, "Windows 2000 Service Pack %u detected, not waiting for address list change Ioctl to complete.", osvix.wServicePackMajor); } } #endif // DBG } #ifdef DBG else { dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't get extended OS version information (err = %u)! Assuming not Win2K < SP3.", dwError); } #endif // DBG } // // Wait, if we can. Otherwise, leak the memory. // if (fCanWait) { // // Keep looping until I/O completes. We will give up after a // while to prevent hangs. // dwAttempt = 0; while (! HasOverlappedIoCompleted(this->m_polAddressListChange)) { DPFX(DPFPREP, 2, "Waiting %u ms for address list change Ioctl to complete.", IOCOMPLETE_WAIT_INTERVAL); // // Give the OS some time to complete it. // Sleep(IOCOMPLETE_WAIT_INTERVAL); dwAttempt++; if (dwAttempt >= MAX_NUM_IOCOMPLETE_WAITS) { break; } } } else { // // Just leak the memory. See above notes and debug print // statements // } } #ifdef DBG else { dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't get OS version information (err = %u)! Assuming not Win2K < SP3.", dwError); } #endif // DBG // // We've either freed the memory or committed to leaking the object. // if (HasOverlappedIoCompleted(this->m_polAddressListChange)) { // // We didn't allocate it through DNMalloc, use the matching free // function. // HeapFree(GetProcessHeap(), 0, this->m_polAddressListChange); } else { DPFX(DPFPREP, 1, "Overlapped address list change Ioctl has not completed yet, leaking %u byte overlapped structure at 0x%p.", sizeof(WSAOVERLAPPED), this->m_polAddressListChange); } this->m_polAddressListChange = NULL; } #endif // ! DPNBUILD_NOWINSOCK2 // // Cleanup WinSock. // iError = this->m_pfnWSACleanup(); if (iError != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't cleanup WinSock (error = %u)!", dwError); #endif // DBG // // Continue anyway, so we can finish cleaning up the object. // } // // Unload the library. // this->m_pfnWSAStartup = NULL; this->m_pfnWSACleanup = NULL; this->m_pfnWSAGetLastError = NULL; this->m_pfnsocket = NULL; this->m_pfnclosesocket = NULL; this->m_pfnbind = NULL; this->m_pfnsetsockopt = NULL; this->m_pfngetsockname = NULL; this->m_pfnselect = NULL; this->m_pfn__WSAFDIsSet = NULL; this->m_pfnrecvfrom = NULL; this->m_pfnsendto = NULL; this->m_pfngethostname = NULL; this->m_pfngethostbyname = NULL; this->m_pfninet_addr = NULL; #ifndef DPNBUILD_NOWINSOCK2 this->m_pfnWSASocketA = NULL; this->m_pfnWSAIoctl = NULL; this->m_pfnWSAGetOverlappedResult = NULL; #endif // ! DPNBUILD_NOWINSOCK2 this->m_pfnioctlsocket = NULL; this->m_pfnconnect = NULL; this->m_pfnshutdown = NULL; this->m_pfnsend = NULL; this->m_pfnrecv = NULL; #ifdef DBG this->m_pfngetsockopt = NULL; #endif // DBG FreeLibrary(this->m_hWinSockDLL); this->m_hWinSockDLL = NULL; #ifndef DPNBUILD_NOWINSOCK2 // // If we loaded RASAPI32.DLL, unload it. // if (this->m_hRasApi32DLL != NULL) { this->m_pfnRasGetEntryHrasconnW = NULL; this->m_pfnRasGetProjectionInfo = NULL; FreeLibrary(this->m_hRasApi32DLL); this->m_hRasApi32DLL = NULL; } // // If we loaded IPHLPAPI.DLL, unload it. // if (this->m_hIpHlpApiDLL != NULL) { this->m_pfnGetAdaptersInfo = NULL; this->m_pfnGetIpForwardTable = NULL; this->m_pfnGetBestRoute = NULL; FreeLibrary(this->m_hIpHlpApiDLL); this->m_hIpHlpApiDLL = NULL; } // // If there was an alert event, we're done with it. // if (this->m_hAlertEvent != NULL) { CloseHandle(this->m_hAlertEvent); this->m_hAlertEvent = NULL; } // // If there was an alert I/O completion port, we're done with it. // if (this->m_hAlertIOCompletionPort != NULL) { CloseHandle(this->m_hAlertIOCompletionPort); this->m_hAlertIOCompletionPort = NULL; } #endif // ! DPNBUILD_NOWINSOCK2 // // Turn off flags which should reset it back to 0 or just the // NOTCREATEDWITHCOM flag. // this->m_dwFlags &= ~(NATHELPUPNPOBJ_INITIALIZED | NATHELPUPNPOBJ_USEUPNP | #ifndef DPNBUILD_NOHNETFWAPI NATHELPUPNPOBJ_USEHNETFWAPI | #endif // ! DPNBUILD_NOHNETFWAPI #ifndef DPNBUILD_NOWINSOCK2 NATHELPUPNPOBJ_WINSOCK1 | #endif // ! DPNBUILD_NOWINSOCK2 NATHELPUPNPOBJ_DEVICECHANGED | NATHELPUPNPOBJ_ADDRESSESCHANGED | #ifdef WINCE NATHELPUPNPOBJ_PORTREGISTERED); #else // ! WINCE NATHELPUPNPOBJ_PORTREGISTERED | NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX); #endif // ! WINCE DNASSERT((this->m_dwFlags & ~NATHELPUPNPOBJ_NOTCREATEDWITHCOM) == 0); this->DropLock(); fHaveLock = FALSE; Exit: DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } goto Exit; } // CNATHelpUPnP::Close #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetCaps" //============================================================================= // CNATHelpUPnP::GetCaps //----------------------------------------------------------------------------- // // Description: Retrieves the capabilities of the Internet gateway server(s) // and information on leased ports. This function should be // called periodically with the DPNHGETCAPS_UPDATESERVERSTATUS // flag to automatically extend port leases that are about to // expire (that are in last 2 minutes of their lease). // // The DPNHGETCAPS_UPDATESERVERSTATUS flag also causes // detection of changes in the servers' status since the last // similar call to GetCaps. If a new server becomes available, an // existing one became unavailable, or a server's public address // changed in a way that affects an existing registered port // mapping, then DPNHSUCCESS_ADDRESSESCHANGED is returned instead // of DPNH_OK. The user should then update its port binding // information via GetRegisteredAddresses. // // When DPNHGETCAPS_UPDATESERVERSTATUS is specified, this // function may block for a short period of time while attempts // are made to communicate with the server(s). // // GetCaps must be called with the // DPNHGETCAPS_UPDATESERVERSTATUS flag at least once prior to // using the GetRegisteredAddresses or QueryAddress methods. // // Arguments: // DPNHCAPS * pdpnhcaps - Pointer to structure to be filled with the NAT // helper's current capabilities. The dwSize // field of the structure must be filled in before // calling GetCaps. // DWORD dwFlags - Flags to use when retrieving capabilities // (DPNHGETCAPS_xxx). // // Returns: HRESULT // DPNH_OK - Determining capabilities was successful. // Address status has not changed. // DPNHSUCCESS_ADDRESSESCHANGED - One or more of the registered port // mappings' addresses changed, retrieve // updated mappings with // GetRegisteredAddress. // DPNHERR_GENERIC - An error occurred while determining // capabilities. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_INVALIDPOINTER - An invalid pointer was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to get // capabilities. // DPNHERR_REENTRANT - The interface has been re-entered on the // same thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::GetCaps(DPNHCAPS * const pdpnhcaps, const DWORD dwFlags) { HRESULT hr; BOOL fHaveLock = FALSE; DWORD dwCurrentTime; DWORD dwLeaseTimeRemaining; CBilink * pBilink; CRegisteredPort * pRegisteredPort; CDevice * pDevice; CUPnPDevice * pUPnPDevice = NULL; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, 0x%lx)", this, pdpnhcaps, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if ((pdpnhcaps == NULL) || (IsBadWritePtr(pdpnhcaps, sizeof(DPNHCAPS)))) { DPFX(DPFPREP, 0, "Invalid caps structure pointer specified!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (pdpnhcaps->dwSize != sizeof(DPNHCAPS)) { DPFX(DPFPREP, 0, "Invalid caps structure specified, dwSize must be %u!", sizeof(DPNHCAPS)); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwFlags & ~DPNHGETCAPS_UPDATESERVERSTATUS) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } // // Fill in the base caps structure. // pdpnhcaps->dwFlags = 0; pdpnhcaps->dwNumRegisteredPorts = 0; pdpnhcaps->dwMinLeaseTimeRemaining = -1; // // pdpnhcaps->dwRecommendedGetCapsInterval is initialized below // if (dwFlags & DPNHGETCAPS_UPDATESERVERSTATUS) { // // Remove any cached mappings that have expired. // this->ExpireOldCachedMappings(); // // Extend leases, if necessary. // hr = this->ExtendAllExpiringLeases(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Extending all expiring leases failed!"); goto Failure; } // // Check for any new devices. // hr = this->CheckForNewDevices(NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Checking for new devices failed!"); goto Failure; } // // Check for possible changes in any server's status. The // ADDRESSESCHANGED flag will be set on this object if there were // changes that affected existing port mappings. // hr = this->UpdateServerStatus(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Updating servers' status failed!"); goto Failure; } // // Okay, so if things are different, alert the caller. // if (this->m_dwFlags & NATHELPUPNPOBJ_ADDRESSESCHANGED) { hr = DPNHSUCCESS_ADDRESSESCHANGED; this->m_dwFlags &= ~NATHELPUPNPOBJ_ADDRESSESCHANGED; } #ifdef DBG // // This flag should have been turned off by now if it ever got turned // on. // DNASSERT(! (this->m_dwFlags & NATHELPUPNPOBJ_DEVICECHANGED)); // // Print the current device and mapping status for debugging purposes. // this->DebugPrintCurrentStatus(); #endif // DBG } else { // // Not extending expiring leases or updating server status. // } // // Loop through all the devices, getting their gateway capabilities. // pBilink = this->m_blDevices.GetNext(); while (pBilink != (&this->m_blDevices)) { DNASSERT(! pBilink->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilink); #ifndef DPNBUILD_NOHNETFWAPI if (pDevice->IsHNetFirewalled()) { // // The firewall does not actively notify you of it going down. // pdpnhcaps->dwFlags |= DPNHCAPSFLAG_LOCALFIREWALLPRESENT | DPNHCAPSFLAG_PUBLICADDRESSAVAILABLE | DPNHCAPSFLAG_NOTALLSUPPORTACTIVENOTIFY; } #endif // ! DPNBUILD_NOHNETFWAPI pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { DNASSERT(pUPnPDevice->IsReady()); pdpnhcaps->dwFlags |= DPNHCAPSFLAG_GATEWAYPRESENT; if (pUPnPDevice->IsLocal()) { pdpnhcaps->dwFlags |= DPNHCAPSFLAG_GATEWAYISLOCAL; } if (pUPnPDevice->GetExternalIPAddressV4() != 0) { pdpnhcaps->dwFlags |= DPNHCAPSFLAG_PUBLICADDRESSAVAILABLE; } // // The custom UPnP stack currently does not support active // notification... // pdpnhcaps->dwFlags |= DPNHCAPSFLAG_NOTALLSUPPORTACTIVENOTIFY; } pBilink = pBilink->GetNext(); } // // Loop through all registered ports, counting them. // We have the appropriate lock. // pBilink = this->m_blRegisteredPorts.GetNext(); dwCurrentTime = GETTIMESTAMP(); while (pBilink != (&this->m_blRegisteredPorts)) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_GLOBAL_BILINK(pBilink); // // Count these registered addresses toward the total. // pdpnhcaps->dwNumRegisteredPorts += pRegisteredPort->GetNumAddresses(); pDevice = pRegisteredPort->GetOwningDevice(); if (pDevice != NULL) { DNASSERT(! (pRegisteredPort->m_blDeviceList.IsListMember(&this->m_blUnownedPorts))); // // If they're registered with any UPnP devices using a non- // permanent lease, calculate the minimum lease time remaining. // if ((pRegisteredPort->HasUPnPPublicAddresses()) && (! pRegisteredPort->HasPermanentUPnPLease())) { dwLeaseTimeRemaining = pRegisteredPort->GetUPnPLeaseExpiration() - dwCurrentTime; if (dwLeaseTimeRemaining < pdpnhcaps->dwMinLeaseTimeRemaining) { // // Temporarily store how much time remains. // pdpnhcaps->dwMinLeaseTimeRemaining = dwLeaseTimeRemaining; } } } else { DNASSERT(pRegisteredPort->m_blDeviceList.IsListMember(&this->m_blUnownedPorts)); } pBilink = pBilink->GetNext(); } // // There are different default recommended GetCaps intervals depending on // whether there's a server present, and whether it supports active address // change notification (that we can alert on) or not. // // If there are any leases which need to be renewed before that default // time, the recommendation will be shortened appropriately. // // // If GetCaps hasn't been called with UPDATESERVERSTATUS yet, recommend an // immediate check. // if (this->m_dwLastUpdateServerStatusTime == 0) { DPFX(DPFPREP, 1, "Server status has not been updated yet, recommending immediate GetCaps."); // // Drop the lock, we're done here. // this->DropLock(); fHaveLock = FALSE; goto Exit; } // // In an ideal world, we could get notified of changes and we would never // have to poll. Unfortunately that isn't the case. We need to recommend // a relatively short poll interval. // // Start by figuring out how long it's been since the last server update. // This calculation really should not go negative. If it does, it means // the caller hasn't updated the server status in ages anyway, so we should // recommend immediate GetCaps. // // Otherwise if the 'port registered' flag is still set at this point, then // the user must have called GetCaps previously, then RegisterPorts, then // made this second GetCaps call before g_dwMinUpdateServerStatusInterval // elapsed. Recommend that the user call us again as soon as the minimum // update interval does elapse. // // In all other cases, generate a recommendation based on the current // backed off poll interval. // dwCurrentTime = dwCurrentTime - this->m_dwLastUpdateServerStatusTime; if ((int) dwCurrentTime < 0) { DPFX(DPFPREP, 1, "Server status was last updated a really long time ago (%u ms), recommending immediate GetCaps.", dwCurrentTime); pdpnhcaps->dwRecommendedGetCapsInterval = 0; } else if (this->m_dwFlags & NATHELPUPNPOBJ_PORTREGISTERED) { DPFX(DPFPREP, 1, "Didn't handle new port registration because server was last updated %u ms ago, (poll interval staying at %u ms).", dwCurrentTime, this->m_dwNextPollInterval); pdpnhcaps->dwRecommendedGetCapsInterval = g_dwMinUpdateServerStatusInterval - dwCurrentTime; if ((int) pdpnhcaps->dwRecommendedGetCapsInterval < 0) { pdpnhcaps->dwRecommendedGetCapsInterval = 0; } } else { DPFX(DPFPREP, 7, "Server was last updated %u ms ago, current poll interval is %u ms.", dwCurrentTime, this->m_dwNextPollInterval); // // Calculate a new recommended interval based on the current value, and // backoff that interval if necessary. // pdpnhcaps->dwRecommendedGetCapsInterval = this->m_dwNextPollInterval - dwCurrentTime; this->m_dwNextPollInterval += GetGlobalRand() % g_dwPollIntervalBackoff; if (this->m_dwNextPollInterval > g_dwMaxPollInterval) { this->m_dwNextPollInterval = g_dwMaxPollInterval; DPFX(DPFPREP, 3, "Capping next poll interval at %u ms.", this->m_dwNextPollInterval); } else { DPFX(DPFPREP, 8, "Next poll interval will be %u ms.", this->m_dwNextPollInterval); } // // If that time went negative, then it implies that the interval has // already elapsed. Recommend immediate GetCaps. // if (((int) pdpnhcaps->dwRecommendedGetCapsInterval) < 0) { DPFX(DPFPREP, 1, "Recommended interval already elapsed (%i ms), suggesting immediate GetCaps.", ((int) pdpnhcaps->dwRecommendedGetCapsInterval)); pdpnhcaps->dwRecommendedGetCapsInterval = 0; } } this->DropLock(); fHaveLock = FALSE; // // If there is a non-INFINITE lease time remaining, see if that affects the // GetCaps interval. // if (pdpnhcaps->dwMinLeaseTimeRemaining != -1) { // // If there are leases that need to be refreshed before the default // recommendation, then use those instead. // if (pdpnhcaps->dwMinLeaseTimeRemaining < LEASE_RENEW_TIME) { DPFX(DPFPREP, 1, "Lease needs renewing right away (min %u < %u ms), recommending immediate GetCaps.", pdpnhcaps->dwMinLeaseTimeRemaining, LEASE_RENEW_TIME); pdpnhcaps->dwRecommendedGetCapsInterval = 0; } else { // // Either pick the time when the lease should be renewed or leave // it as the recommended time, whichever is shorter. // if ((pdpnhcaps->dwMinLeaseTimeRemaining - LEASE_RENEW_TIME) < pdpnhcaps->dwRecommendedGetCapsInterval) { pdpnhcaps->dwRecommendedGetCapsInterval = pdpnhcaps->dwMinLeaseTimeRemaining - LEASE_RENEW_TIME; } } } DPFX(DPFPREP, 7, "GetCaps flags = 0x%lx, num registered ports = %u, min lease time remaining = %i, recommended interval = %i.", pdpnhcaps->dwFlags, pdpnhcaps->dwNumRegisteredPorts, ((int) pdpnhcaps->dwMinLeaseTimeRemaining), ((int) pdpnhcaps->dwRecommendedGetCapsInterval)); Exit: DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } goto Exit; } // CNATHelpUPnP::GetCaps #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::RegisterPorts" //============================================================================= // CNATHelpUPnP::RegisterPorts //----------------------------------------------------------------------------- // // Description: Asks for public realm port(s) that are aliases for the local // port(s) on this private realm node. If a server is available, // all traffic directed to the gateway on the public side at the // allocated public ports-- which the gateway provides and // specifies in the response-- will be directed to the specified // local ports. If the DPNHREGISTERPORTS_FIXEDPORTS flag is not // specified, the ports assigned on the public interface are // arbitrary (i.e. may not be the same as those in awLocalPort). // The address and ports actually allocated can be retrieved by // calling GetRegisteredAddresses. // // The address component for every SOCKADDR structure in the // array must be the same. A separate RegisterPorts call is // required to register multiple ports that are not using the same // interface. The address can be INADDR_ANY, in which case the // "best" server will be used. If multiple servers are available // via different adapters, an adapter with an Internet gateway is // selected. If no adapters have Internet gateways, the first // adapter with a local firewall is selected. If neither are // available, then the first one where either a gateway or a // firewall becomes available will be automatically selected. // Once one of the adapters has been assigned, it cannot be // changed. Since the server chosen by this method may not be // optimal for a particular application, it is recommended that // individual addresses be registered instead of INADDR_ANY. // // If the address in aLocalAddresses is not one of those // available to the local machine, the registration will still // succeed. If an adapter with that address becomes available, // the port mapping will automatically be applied, and it will // gain a public mapping with any server available to that // adapter. If the address was originally available but the // network adapter is subsequently removed from the system, any // public address mapping is lost. It will be automatically // regained if the local address becomes available again. It is // recommended that the caller detect local address changes // independently and de-register/re-register mappings per adapter // as appropriate for maximum control. // // If the DPNHREGISTERPORTS_SHAREDPORTS flag is used, the // server will allow other NAT clients to register it as well. // Any UDP traffic received on the public interface will be // forwarded to all clients registered. This requires the // DPNHREGISTERPORTS_FIXEDPORTS flag and cannot be used with // DPNHREGISTERPORTS_TCP. // // The user should specify a requested lease time that the // server will attempt to honor. The actual time remaining can be // can be retrieved by calling GetRegisteredAddresses. // // Note that if a server is not available, this function will // still succeed. GetRegisteredAddresses will return // DPNHERR_NOMAPPING for the handle returned in phRegisteredPorts // in that case. If the server arrives later during the session, // calling GetCaps periodically can detect this and automatically // map previously registered ports. Use GetRegisteredAddresses to // retrieve the newly mapped address when that occurs. // // Only 16 ports may be registered at a time, but RegisterPorts // may be called as many times as desired. // // The same array of addresses may be registered more than // once. Each DPNHHANDLE returned must be released with // DeregisterPorts or Close. If an individual address was // previously registered but in a different array or a different // order in the array, then the DPNHERR_PORTALREADYREGISTERED // error code is returned. // // Arguments: // SOCKADDR * aLocalAddresses - Array of local address and port tuples // for which remote ports are requested. // DWORD dwAddressesSize - Size of entire local addresses array. // DWORD dwNumAddresses - Number of SOCKADDR structures in local // addresses array. // DWORD dwLeaseTime - Requested time, in milliseconds, to lease // the ports. As long as GetCaps is // called before this time has expired, // the lease will automatically be // renewed. // DPNHHANDLE * phRegisteredPorts - Place to store an identifier for this // binding which can later be used to // query or release the binding. // DWORD dwFlags - Flags to use when registering the port // (DPNHREGISTERPORTS_xxx). // // Returns: HRESULT // DPNH_OK - The ports were successfully registered // (although no public address may be // available yet). // DPNHERR_GENERIC - An error occurred that prevented // registration of the requested ports. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_INVALIDPOINTER - An invalid pointer was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to register // the ports. // DPNHERR_PORTALREADYREGISTERED - At least one of the ports has already // been registered in a different address // array or order. // DPNHERR_REENTRANT - The interface has been re-entered on the // same thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::RegisterPorts(const SOCKADDR * const aLocalAddresses, const DWORD dwAddressesSize, const DWORD dwNumAddresses, const DWORD dwLeaseTime, DPNHHANDLE * const phRegisteredPorts, const DWORD dwFlags) { HRESULT hr; ULONG ulFirstAddress; DWORD dwTemp; DWORD dwMatch; BOOL fHaveLock = FALSE; CRegisteredPort * pRegisteredPort = NULL; CDevice * pDevice = NULL; CBilink * pBilink; SOCKADDR_IN * pasaddrinTemp; CUPnPDevice * pUPnPDevice = NULL; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, %u, %u, %u, 0x%p, 0x%lx)", this, aLocalAddresses, dwAddressesSize, dwNumAddresses, dwLeaseTime, phRegisteredPorts, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if (aLocalAddresses == NULL) { DPFX(DPFPREP, 0, "Local addresses array cannot be NULL!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (dwNumAddresses == 0) { DPFX(DPFPREP, 0, "Number of addresses cannot be 0!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwAddressesSize != (dwNumAddresses * sizeof(SOCKADDR))) { DPFX(DPFPREP, 0, "Addresses array size invalid!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (IsBadReadPtr(aLocalAddresses, dwAddressesSize)) { DPFX(DPFPREP, 0, "Local addresses array buffer is invalid!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (dwNumAddresses > DPNH_MAX_SIMULTANEOUS_PORTS) { DPFX(DPFPREP, 0, "Only %u ports may be registered at a time!", DPNH_MAX_SIMULTANEOUS_PORTS); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (((SOCKADDR_IN*) aLocalAddresses)->sin_family != AF_INET) { DPFX(DPFPREP, 0, "First address in array is not AF_INET, only IPv4 addresses are supported!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (((SOCKADDR_IN*) aLocalAddresses)->sin_addr.S_un.S_addr == INADDR_BROADCAST) { DPFX(DPFPREP, 0, "First address cannot be broadcast address!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (((SOCKADDR_IN*) aLocalAddresses)->sin_port == 0) { DPFX(DPFPREP, 0, "First port in array is 0, a valid port must be specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } ulFirstAddress = ((SOCKADDR_IN*) aLocalAddresses)->sin_addr.S_un.S_addr; for(dwTemp = 1; dwTemp < dwNumAddresses; dwTemp++) { // // Make sure this address family type is supported. // if (((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_family != AF_INET) { DPFX(DPFPREP, 0, "Address at array index %u is not AF_INET, all items in the array must be the same IPv4 address!", dwTemp); hr = DPNHERR_INVALIDPARAM; goto Failure; } // // If this address doesn't match the first, then the caller broke the // rules. // if (((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_addr != ulFirstAddress) { // // Don't use inet_ntoa because we may not be initialized yet. // DPFX(DPFPREP, 0, "Address %u.%u.%u.%u at array index %u differs from the first, all addresses in the array must match!", ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_un_b.s_b4, dwTemp); hr = DPNHERR_INVALIDPARAM; goto Failure; } // // Make sure this port isn't 0 either. // if (((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_port == 0) { DPFX(DPFPREP, 0, "Port at array index %u is 0, valid ports must be specified!", dwTemp); hr = DPNHERR_INVALIDPARAM; goto Failure; } } if (dwLeaseTime == 0) { DPFX(DPFPREP, 0, "Invalid lease time specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if ((phRegisteredPorts == NULL) || (IsBadWritePtr(phRegisteredPorts, sizeof(DPNHHANDLE)))) { DPFX(DPFPREP, 0, "Invalid port mapping handle pointer specified!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (dwFlags & ~(DPNHREGISTERPORTS_TCP | DPNHREGISTERPORTS_FIXEDPORTS | DPNHREGISTERPORTS_SHAREDPORTS)) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } if (dwFlags & DPNHREGISTERPORTS_SHAREDPORTS) { // // SHAREDPORTS cannot be used with TCP and requires a FIXEDPORTS. // if ((dwFlags & DPNHREGISTERPORTS_TCP) || (! (dwFlags & DPNHREGISTERPORTS_FIXEDPORTS))) { DPFX(DPFPREP, 0, "SHAREDPORTS flag requires FIXEDPORTS flag and cannot be used with TCP flag!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } // // Loop through all existing registered port mappings and look for this // array of ports. // pBilink = this->m_blRegisteredPorts.GetNext(); while (pBilink != &this->m_blRegisteredPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_GLOBAL_BILINK(pBilink); // // Don't bother looking at addresses of the wrong type or at arrays of // the wrong size. // if (((pRegisteredPort->IsTCP() && (dwFlags & DPNHREGISTERPORTS_TCP)) || ((! pRegisteredPort->IsTCP()) && (! (dwFlags & DPNHREGISTERPORTS_TCP)))) && (pRegisteredPort->GetNumAddresses() == dwNumAddresses)) { pasaddrinTemp = pRegisteredPort->GetPrivateAddressesArray(); for(dwTemp = 0; dwTemp < dwNumAddresses; dwTemp++) { // // If the addresses don't match, stop looping. // if ((pasaddrinTemp[dwTemp].sin_port != ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_port) || (pasaddrinTemp[dwTemp].sin_addr.S_un.S_addr != ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_addr)) { break; } } // // If all the addresses matched, then this item was already // registered. // if (dwTemp >= dwNumAddresses) { DPFX(DPFPREP, 1, "Array of %u addresses was already registered, returning existing mapping 0x%p.", dwNumAddresses, pRegisteredPort); goto ReturnUserHandle; } DPFX(DPFPREP, 7, "Existing mapping 0x%p does not match all %u addresses.", pRegisteredPort, dwNumAddresses); } else { // // Existing mapping isn't same type or doesn't have same number of // items in array. // } pBilink = pBilink->GetNext(); } // // If we're here, none of the existing mappings match. Loop through each // of the ports and make sure they aren't already registered inside some // other mapping. // for(dwTemp = 0; dwTemp < dwNumAddresses; dwTemp++) { pBilink = this->m_blRegisteredPorts.GetNext(); while (pBilink != &this->m_blRegisteredPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_GLOBAL_BILINK(pBilink); pasaddrinTemp = pRegisteredPort->GetPrivateAddressesArray(); for(dwMatch = 0; dwMatch < pRegisteredPort->GetNumAddresses(); dwMatch++) { // // If the addresses match, then we can't map these ports. // if ((pasaddrinTemp[dwMatch].sin_port == ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_port) && (pasaddrinTemp[dwMatch].sin_addr.S_un.S_addr == ((SOCKADDR_IN*) (&aLocalAddresses[dwTemp]))->sin_addr.S_un.S_addr)) { DPFX(DPFPREP, 0, "Existing mapping 0x%p already registered the address %u.%u.%u.%u:%u!", pRegisteredPort, pasaddrinTemp[dwMatch].sin_addr.S_un.S_un_b.s_b1, pasaddrinTemp[dwMatch].sin_addr.S_un.S_un_b.s_b2, pasaddrinTemp[dwMatch].sin_addr.S_un.S_un_b.s_b3, pasaddrinTemp[dwMatch].sin_addr.S_un.S_un_b.s_b4, NTOHS(pasaddrinTemp[dwMatch].sin_port)); // // Clear the pointer so we don't delete the object. // pRegisteredPort = NULL; hr = DPNHERR_PORTALREADYREGISTERED; goto Failure; } } pBilink = pBilink->GetNext(); } } // // If we're here the ports are all unique. Create a new mapping object // we'll use to refer to the binding. // pRegisteredPort = new CRegisteredPort(dwLeaseTime, dwFlags); if (pRegisteredPort == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } hr = pRegisteredPort->SetPrivateAddresses((SOCKADDR_IN*) aLocalAddresses, dwNumAddresses); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't store private addresses array!"); goto Failure; } // // Find the device that matches the given addresses. // // The first entry of aLocalAddresses is representative of all entries since // they should all share the same address. // // Since there won't be an existing registered port for this address, don't // bother looking through them for a matching address. // pDevice = this->FindMatchingDevice((SOCKADDR_IN*) (&aLocalAddresses[0]), FALSE); if (pDevice == NULL) { DPFX(DPFPREP, 1, "No device for given address (%u.%u.%u.%u), storing 0x%p in unowned list.", ((SOCKADDR_IN*) aLocalAddresses)->sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) aLocalAddresses)->sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) aLocalAddresses)->sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) aLocalAddresses)->sin_addr.S_un.S_un_b.s_b4, pRegisteredPort); pRegisteredPort->m_blDeviceList.InsertBefore(&this->m_blUnownedPorts); } else { pRegisteredPort->MakeDeviceOwner(pDevice); #ifndef DPNBUILD_NOHNETFWAPI // // Start by mapping with the local firewall, if there is one. // if (pDevice->IsHNetFirewalled()) { hr = this->CheckForLocalHNetFirewallAndMapPorts(pDevice, pRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't check for local HNet firewall and map ports (err = 0x%lx)! Continuing.", hr); DNASSERT(! pDevice->IsHNetFirewalled()); hr = DPNH_OK; } } else { // // No local HomeNet firewall (last time we checked). // } #endif // ! DPNBUILD_NOHNETFWAPI // // Map the ports on the UPnP device, if there is one. // pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { // // GetUPnPDevice did not add a reference to pUPnPDevice for us. // pUPnPDevice->AddRef(); DNASSERT(pUPnPDevice->IsReady()); // // Actually map the ports. // hr = this->MapPortsOnUPnPDevice(pUPnPDevice, pRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't map ports on UPnP device 0x%p (0x%lx)! Ignoring.", pUPnPDevice, hr); // // It may have been cleared already, but doing it twice // shouldn't be harmful. // this->ClearDevicesUPnPDevice(pRegisteredPort->GetOwningDevice()); hr = DPNH_OK; } pUPnPDevice->DecRef(); pUPnPDevice = NULL; } else { // // No UPnP device. // } } // // Save the mapping in the global list (we have the lock). // pRegisteredPort->m_blGlobalList.InsertBefore(&this->m_blRegisteredPorts); ReturnUserHandle: // // Remember that a port has been registered. // this->m_dwFlags |= NATHELPUPNPOBJ_PORTREGISTERED; // // We're about to give the port to the user. // pRegisteredPort->AddUserRef(); // // We're going to give the user a direct pointer to the object (disguised // as an opaque DPNHHANDLE, of course). // (*phRegisteredPorts) = (DPNHHANDLE) pRegisteredPort; this->DropLock(); fHaveLock = FALSE; DPFX(DPFPREP, 5, "Returning registered port 0x%p (first private address = %u.%u.%u.%u:%u).", pRegisteredPort, ((SOCKADDR_IN*) aLocalAddresses)[0].sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) aLocalAddresses)[0].sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) aLocalAddresses)[0].sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) aLocalAddresses)[0].sin_addr.S_un.S_un_b.s_b4, NTOHS(((SOCKADDR_IN*) aLocalAddresses)[0].sin_port)); hr = DPNH_OK; Exit: DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pUPnPDevice != NULL) { pUPnPDevice->DecRef(); } if (pRegisteredPort != NULL) { #ifndef DPNBUILD_NOHNETFWAPI if (pRegisteredPort->IsMappedOnHNetFirewall()) { HRESULT temphr; // // Unmap the port. // // Don't bother alerting user about address change. It would have // already been taken care of if this was due to a fatal error, and // there would be no perceived changed if not. // temphr = this->UnmapPortOnLocalHNetFirewall(pRegisteredPort, TRUE, FALSE); if (temphr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed unmapping registered port 0x%p on local HomeNet firewall (err = 0x%lx)! Ignoring.", pRegisteredPort, temphr); pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); } } #endif // ! DPNBUILD_NOHNETFWAPI if (pDevice != NULL) { pRegisteredPort->ClearDeviceOwner(); } pRegisteredPort->ClearPrivateAddresses(); delete pRegisteredPort; } if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } goto Exit; } // CNATHelpUPnP::RegisterPorts #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetRegisteredAddresses" //============================================================================= // CNATHelpUPnP::GetRegisteredAddresses //----------------------------------------------------------------------------- // // Description: Returns the current public address mappings for a given // registered port group. If there are no servers currently // available, then DPNHERR_SERVERNOTAVAILABLE is returned. If the // servers' public interfaces are not currently valid, then // DPNHERR_NOMAPPING is returned, but appropriate values will // still be placed in pdwAddressTypeFlags and // pdwLeaseTimeRemaining. // // If the mapping was registered with the // DPNHREGISTERPORTS_FIXEDPORTS flag, but at least one port is // already in use on the gateway, then DPNHERR_PORTUNAVAILABLE is // returned and appropriate flags will still be placed in // pdwAddressTypeFlags. // // If the local machine has a cooperative firewall installed, // the requested port is opened locally on the firewall before // being mapped on the Internet gateway. Normally this function // returns the public address on the Internet gateway address when // both are present. Since some firewalls remap the port number // when opening non-fixed ports, the // DPNHGETREGISTEREDADDRESSES_LOCALFIREWALLREMAPONLY allows the // caller to retrieve the locally remapped address, even if there // is a mapping on an Internet gateway. // // Some gateway devices do not natively support ports that are // not fixed, and may generate the DPNHERR_PORTUNAVAILABLE return // code even when the DPNHREGISTERPORTS_FIXEDPORTS flag was not // specified. The caller should de-register the port mapping // handle, rebind the application to different ports, and call // RegisterPorts again. // // If the buffer indicated by paPublicAddresses is too small, // then the size required is returned in pdwPublicAddressesSize // and DPNHERR_BUFFERTOOSMALL is returned. Otherwise the number of // bytes written is returned in pdwPublicAddressesSize. // // Even though the addresses are returned as individual // SOCKADDRs, all ports registered at the same time will share the // same public address. Only the port components will vary. // // All buffers are optional and may be NULL, but if // paPublicAddresses is specified, it must be accompanied by an // appropriate size in pdwPublicAddressesSize. // // If GetCaps has not been previously called with the // DPNHGETCAPS_UPDATESERVERSTATUS flag at least once, then the // error code DPNHERR_UPDATESERVERSTATUS is returned. // // Arguments: // DPNHHANDLE hRegisteredPorts - Handle for a specific binding returned by // RegisterPorts. // SOCKADDR * paPublicAddresses - Buffer to return assigned public realm // address, or NULL if not desired. // DWORD * pdwPublicAddressesSize - Pointer to size of paPublicAddresses // buffer, or place to store size // required/written. Cannot be NULL if // paPublicAddresses is not NULL. // DWORD * pdwAddressTypeFlags - Place to store flags describing the // address types returned, or NULL if not // desired. // DWORD * pdwLeaseTimeRemaining - Place to store approximate number of // milliseconds remaining in the port // lease, or NULL if not desired. Call // GetCaps to automatically extend leases // about to expire. // DWORD dwFlags - Unused, must be zero. // // Returns: HRESULT // DPNH_OK - Information on the port mapping was found and // the addresses were stored in // paPublicAddresses. // DPNHERR_BUFFERTOOSMALL - There was not enough room in the buffer to // store the addresses. // DPNHERR_GENERIC - An error occurred while retrieving the // requested port mapping. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_INVALIDPOINTER - An invalid pointer was specified. // DPNHERR_NOMAPPING - The server(s) do not have valid public // interfaces. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to get the // addresses. // DPNHERR_PORTUNAVAILABLE - At least one of the ports is not available on // the server. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. // DPNHERR_SERVERNOTAVAILABLE - No servers are currently present. // DPNHERR_UPDATESERVERSTATUS - GetCaps has not been called with the // DPNHGETCAPS_UPDATESERVERSTATUS flag yet. //============================================================================= STDMETHODIMP CNATHelpUPnP::GetRegisteredAddresses(const DPNHHANDLE hRegisteredPorts, SOCKADDR * const paPublicAddresses, DWORD * const pdwPublicAddressesSize, DWORD * const pdwAddressTypeFlags, DWORD * const pdwLeaseTimeRemaining, const DWORD dwFlags) { HRESULT hr; CRegisteredPort * pRegisteredPort; BOOL fHaveLock = FALSE; BOOL fRegisteredWithServer = FALSE; BOOL fFoundValidMapping = FALSE; BOOL fPortIsUnavailable = FALSE; DWORD dwSizeRequired; DWORD dwAddressTypeFlags; DWORD dwCurrentTime; //DWORD dwTempLeaseTimeRemaining; DWORD dwLeaseTimeRemaining = -1; CDevice * pDevice; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, 0x%p, 0x%p, 0x%lx)", this, hRegisteredPorts, paPublicAddresses, pdwPublicAddressesSize, pdwAddressTypeFlags, pdwLeaseTimeRemaining, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // pRegisteredPort = (CRegisteredPort*) hRegisteredPorts; if (! pRegisteredPort->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid registered port mapping handle specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (paPublicAddresses != NULL) { if ((pdwPublicAddressesSize == NULL) || (IsBadWritePtr(pdwPublicAddressesSize, sizeof(DWORD)))) { DPFX(DPFPREP, 0, "When specifying a public addresses buffer, a valid size must be given!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (IsBadWritePtr(paPublicAddresses, (*pdwPublicAddressesSize))) { DPFX(DPFPREP, 0, "The public addresses buffer is invalid!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } } else { if ((pdwPublicAddressesSize != NULL) && (IsBadWritePtr(pdwPublicAddressesSize, sizeof(DWORD)))) { DPFX(DPFPREP, 0, "Invalid pointer for size of public addresses buffer!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } } if ((pdwAddressTypeFlags != NULL) && (IsBadWritePtr(pdwAddressTypeFlags, sizeof(DWORD)))) { DPFX(DPFPREP, 0, "Invalid pointer for address type flags!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if ((pdwLeaseTimeRemaining != NULL) && (IsBadWritePtr(pdwLeaseTimeRemaining, sizeof(DWORD)))) { DPFX(DPFPREP, 0, "Invalid pointer for lease time remaining!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (dwFlags & ~DPNHGETREGISTEREDADDRESSES_LOCALFIREWALLREMAPONLY) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Start the flags off with the information regarding TCP vs. UDP. // if (pRegisteredPort->IsTCP()) { dwAddressTypeFlags = DPNHADDRESSTYPE_TCP; } else { dwAddressTypeFlags = 0; } // // Add in other flags we know already. // if (pRegisteredPort->IsFixedPort()) { dwAddressTypeFlags |= DPNHADDRESSTYPE_FIXEDPORTS; } if (pRegisteredPort->IsSharedPort()) { dwAddressTypeFlags |= DPNHADDRESSTYPE_SHAREDPORTS; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } if (this->m_dwLastUpdateServerStatusTime == 0) { DPFX(DPFPREP, 0, "GetCaps has not been called with UPDATESERVERSTATUS flag yet!"); hr = DPNHERR_UPDATESERVERSTATUS; goto Failure; } // // Get a shortcut pointer to the device (may not exist). // pDevice = pRegisteredPort->GetOwningDevice(); // // Get the current time for both the remote and local lease // calculations. // dwCurrentTime = GETTIMESTAMP(); if (! (dwFlags & DPNHGETREGISTEREDADDRESSES_LOCALFIREWALLREMAPONLY)) { CUPnPDevice * pUPnPDevice; // // First check for a mapping on the UPnP device. // if (pRegisteredPort->HasUPnPPublicAddresses()) { DNASSERT(pDevice != NULL); pUPnPDevice = pDevice->GetUPnPDevice(); DNASSERT(pUPnPDevice != NULL); fRegisteredWithServer = TRUE; // // Make sure the UPnP device currently has a valid external // address. If so, hand the mapping out. // if (pUPnPDevice->GetExternalIPAddressV4() != 0) { if (pdwPublicAddressesSize != NULL) { dwSizeRequired = pRegisteredPort->GetAddressesSize(); if ((paPublicAddresses == NULL) || (dwSizeRequired > (*pdwPublicAddressesSize))) { // // Not enough room in buffer, return the size required // and the BUFFERTOOSMALL error code. // (*pdwPublicAddressesSize) = dwSizeRequired; hr = DPNHERR_BUFFERTOOSMALL; } else { // // Buffer was large enough, return the size written. // (*pdwPublicAddressesSize) = dwSizeRequired; pRegisteredPort->CopyUPnPPublicAddresses((SOCKADDR_IN*) paPublicAddresses); } } else { // // Not using address buffer. // } fFoundValidMapping = TRUE; } else { DPFX(DPFPREP, 8, "The UPnP Internet Gateway Device does not currently have a valid public address."); } // // Add in the flag indicating that there's a UPnP device. // dwAddressTypeFlags |= DPNHADDRESSTYPE_GATEWAY; // // See if the UPnP device is local. // if (pUPnPDevice->IsLocal()) { dwAddressTypeFlags |= DPNHADDRESSTYPE_GATEWAYISLOCAL; } // // Get the relative UPnP lease time remaining, if it's not // permanent. // if (! pRegisteredPort->HasPermanentUPnPLease()) { dwLeaseTimeRemaining = pRegisteredPort->GetUPnPLeaseExpiration() - dwCurrentTime; if (((int) dwLeaseTimeRemaining) < 0) { DPFX(DPFPREP, 1, "Registered port mapping's UPnP lease has already expired, returning 0 for lease time remaining."); dwLeaseTimeRemaining = 0; } } } else if (pRegisteredPort->IsUPnPPortUnavailable()) { DNASSERT(pDevice != NULL); pUPnPDevice = pDevice->GetUPnPDevice(); DNASSERT(pUPnPDevice != NULL); fRegisteredWithServer = TRUE; fPortIsUnavailable = TRUE; DPFX(DPFPREP, 8, "The UPnP device indicates the port(s) are unavailable."); // // Add in the flag indicating that there's a UPnP device. // dwAddressTypeFlags |= DPNHADDRESSTYPE_GATEWAY; // // See if the UPnP device is local. // if (pUPnPDevice->IsLocal()) { dwAddressTypeFlags |= DPNHADDRESSTYPE_GATEWAYISLOCAL; } } } else { // // We're not allowed to return the UPnP mapping. // DPFX(DPFPREP, 8, "Ignoring any Internet gateway mappings, LOCALFIREWALLREMAPONLY was specified."); } #ifndef DPNBUILD_NOHNETFWAPI // // Finally, check for a mapping on a local firewall. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { DNASSERT(pDevice != NULL); DNASSERT(pDevice->IsHNetFirewalled()); fRegisteredWithServer = TRUE; // // If we didn't already get a remote mapping, return this local one. // if (! fFoundValidMapping) { if (pdwPublicAddressesSize != NULL) { dwSizeRequired = pRegisteredPort->GetAddressesSize(); if ((paPublicAddresses == NULL) || (dwSizeRequired > (*pdwPublicAddressesSize))) { // // Not enough room in buffer, return the size required // and the BUFFERTOOSMALL error code. // (*pdwPublicAddressesSize) = dwSizeRequired; hr = DPNHERR_BUFFERTOOSMALL; } else { SOCKADDR_IN * pasaddrinPrivate; DWORD dwTemp; // // Buffer was large enough, return the size written. // (*pdwPublicAddressesSize) = dwSizeRequired; // // Note that the addresses mapped on the firewall are the // same as the private addresses. // pasaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); DNASSERT(pasaddrinPrivate != NULL); memcpy(paPublicAddresses, pasaddrinPrivate, dwSizeRequired); // // However, we don't want to ever return 0.0.0.0, so make // sure they get the device address. // if (pasaddrinPrivate[0].sin_addr.S_un.S_addr == INADDR_ANY) { for(dwTemp = 0; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { ((SOCKADDR_IN*) paPublicAddresses)[dwTemp].sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); } DPFX(DPFPREP, 7, "Returning device address %u.%u.%u.%u instead of INADDR_ANY for firewalled port mapping 0x%p.", ((SOCKADDR_IN*) paPublicAddresses)[0].sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) paPublicAddresses)[0].sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) paPublicAddresses)[0].sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) paPublicAddresses)[0].sin_addr.S_un.S_un_b.s_b4, pRegisteredPort); } } } else { // // Not using address buffer. // } fFoundValidMapping = TRUE; } else { DPFX(DPFPREP, 6, "Ignoring local HomeNet firewall mapping due to UPnP mapping."); } // // Add in the flag indicating the local firewall. // dwAddressTypeFlags |= DPNHADDRESSTYPE_LOCALFIREWALL; // // The firewall API does not allow for lease times. // } else { if (pRegisteredPort->IsHNetFirewallPortUnavailable()) { DNASSERT(pDevice != NULL); DNASSERT(pDevice->IsHNetFirewalled()); fRegisteredWithServer = TRUE; fPortIsUnavailable = TRUE; DPFX(DPFPREP, 8, "The local HomeNet firewall indicates the port(s) are unavailable."); // // Add in the flag indicating the local firewall. // dwAddressTypeFlags |= DPNHADDRESSTYPE_LOCALFIREWALL; } #ifdef DBG else { // // No local firewall or it's an unowned port. // if (pDevice != NULL) { DNASSERT(! pDevice->IsHNetFirewalled()); } else { DNASSERT(pRegisteredPort->m_blDeviceList.IsListMember(&this->m_blUnownedPorts)); } } #endif // DBG } #endif // ! DPNBUILD_NOHNETFWAPI this->DropLock(); fHaveLock = FALSE; if (fRegisteredWithServer) { DNASSERT(dwAddressTypeFlags & (DPNHADDRESSTYPE_LOCALFIREWALL | DPNHADDRESSTYPE_GATEWAY)); if (! fFoundValidMapping) { if (fPortIsUnavailable) { // // The servers indicated that the ports were already in use. // Return PORTUNAVAILABLE. // DPFX(DPFPREP, 1, "The Internet gateway(s) could not map the port, returning PORTUNAVAILABLE."); hr = DPNHERR_PORTUNAVAILABLE; } else { // // The servers didn't have public addresses. Return NOMAPPING. // DPFX(DPFPREP, 1, "The Internet gateway(s) did not offer valid public addresses, returning NOMAPPING."); hr = DPNHERR_NOMAPPING; } } else { // // One of the servers had a public address. // DNASSERT((hr == DPNH_OK) || (hr == DPNHERR_BUFFERTOOSMALL)); } } else { // // The ports aren't registered, because there aren't any gateways. // Return SERVERNOTAVAILABLE. // DPFX(DPFPREP, 1, "No Internet gateways, returning SERVERNOTAVAILABLE."); hr = DPNHERR_SERVERNOTAVAILABLE; } // // If the caller wants information on the type of these addresses, return // the flags we detected. // if (pdwAddressTypeFlags != NULL) { (*pdwAddressTypeFlags) = dwAddressTypeFlags; } // // Return the minimum lease time remaining that we already calculated, if // the caller wants it. // if (pdwLeaseTimeRemaining != NULL) { (*pdwLeaseTimeRemaining) = dwLeaseTimeRemaining; } #ifdef DBG // // If the port is unavailable or there aren't any servers, we better not // have a lease time. // if ((hr == DPNHERR_PORTUNAVAILABLE) || (hr == DPNHERR_SERVERNOTAVAILABLE)) { DNASSERT(dwLeaseTimeRemaining == -1); } // // If there aren't any servers, we better not have server flags. // if (hr == DPNHERR_SERVERNOTAVAILABLE) { DNASSERT(! (dwAddressTypeFlags & (DPNHADDRESSTYPE_LOCALFIREWALL | DPNHADDRESSTYPE_GATEWAY))); } #endif // DBG DPFX(DPFPREP, 5, "Registered port 0x%p addr type flags = 0x%lx, lease time remaining = %i.", pRegisteredPort, dwAddressTypeFlags, (int) dwLeaseTimeRemaining); Exit: DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } goto Exit; } // CNATHelpUPnP::GetRegisteredAddresses #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::DeregisterPorts" //============================================================================= // CNATHelpUPnP::DeregisterPorts //----------------------------------------------------------------------------- // // Description: Removes the lease record for the port group and informs the // Internet gateway server that the binding is no longer needed. // The port mapping handle must not be used after de-registering // it. // // Arguments: // DPNHHANDLE hRegisteredPorts - Handle for a specific binding returned by // RegisterPorts. // DWORD dwFlags - Unused, must be zero. // // Returns: HRESULT // DPNH_OK - The binding was successfully released. // DPNHERR_GENERIC - An error occurred that prevented the // de-registration of the ports. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to de-register. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::DeregisterPorts(const DPNHHANDLE hRegisteredPorts, const DWORD dwFlags) { HRESULT hr; CRegisteredPort * pRegisteredPort; BOOL fHaveLock = FALSE; LONG lResult; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, 0x%lx)", this, hRegisteredPorts, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // pRegisteredPort = (CRegisteredPort*) hRegisteredPorts; if (! pRegisteredPort->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid registered port mapping handle specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwFlags != 0) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } // // If this isn't the last user reference on the registered port, don't // unmap it yet. // lResult = pRegisteredPort->DecUserRef(); if (lResult != 0) { DPFX(DPFPREP, 1, "Still %i references left on registered port 0x%p, not unmapping.", lResult, pRegisteredPort); goto Exit; } // // First unmap from UPnP device, if necessary. // if (pRegisteredPort->HasUPnPPublicAddresses()) { hr = this->UnmapUPnPPort(pRegisteredPort, pRegisteredPort->GetNumAddresses(), // free all ports TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't delete port mapping with UPnP device (0x%lx)! Ignoring.", hr); // // We'll treat this as non-fatal, but we have to dump the device. // this->ClearDevicesUPnPDevice(pRegisteredPort->GetOwningDevice()); hr = DPNH_OK; } } #ifndef DPNBUILD_NOHNETFWAPI // // Then unmap from the local firewall, if necessary. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { // // Unmap the port. // // Don't bother alerting user about address change, this is normal // operation. // hr = this->UnmapPortOnLocalHNetFirewall(pRegisteredPort, TRUE, FALSE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed unmapping registered port 0x%p on local HomeNet firewall (err = 0x%lx)! Ignoring.", pRegisteredPort, hr); pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); hr = DPNH_OK; } } #endif // ! DPNBUILD_NOHNETFWAPI // // Pull the item out of the lists. // We have the appropriate lock. // DNASSERT(pRegisteredPort->m_blGlobalList.IsListMember(&this->m_blRegisteredPorts)); pRegisteredPort->m_blGlobalList.RemoveFromList(); if (pRegisteredPort->GetOwningDevice() != NULL) { DNASSERT(pRegisteredPort->m_blDeviceList.IsListMember(&((pRegisteredPort->GetOwningDevice())->m_blOwnedRegPorts))); pRegisteredPort->ClearDeviceOwner(); } else { DNASSERT(pRegisteredPort->m_blDeviceList.IsListMember(&this->m_blUnownedPorts)); pRegisteredPort->m_blDeviceList.RemoveFromList(); } pRegisteredPort->ClearPrivateAddresses(); delete pRegisteredPort; Exit: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::DeregisterPorts #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::QueryAddress" //============================================================================= // CNATHelpUPnP::QueryAddress //----------------------------------------------------------------------------- // // Description: Some Internet gateways do not loopback if an attempt is made // to connect to an address behind (on the same private side of) // the public interface. QueryAddress is used to determine a // possible private alias for a given public address. // // In most cases, this function is called prior to connecting // to a new address. pSourceAddress should contain the address of // the socket that will perform the connect. Similar to // RegisterPorts, the address may be INADDR_ANY, in which case the // "best" server will be used. Since the server chosen may not be // optimal for a particular application, it is recommended that a // specific network interface be used instead of INADDR_ANY, when // possible. // // If no mapping for that address has been made by the gateway, // the error code DPNHERR_NOMAPPING is returned. When the // DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED flag is used, an // extra effort is made to determine whether the address is behind // the same Internet gateway without being mapped on the gateway. // If that is the case, DPNHERR_NOMAPPINGBUTPRIVATE is returned. // DPNHERR_NOMAPPING is still returned for addresses that are // neither mapped nor private. // // pQueryAddress may not be INADDR_ANY or INADDR_BROADCAST. // The port component may be zero if and only if the // DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED flag is used. If // the port is zero, a specific mapping cannot be verified, and // only the DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED aspect of // the address is tested. // // The resulting address (or lack thereof) can be cached for // quick future retrieval using the DPNHQUERYADDRESS_CACHEFOUND // and DPNHQUERYADDRESS_CACHENOTFOUND flags. The cached mappings // will expire in 1 minute, or whenever the server's address // changes. // // If the given source address is not currently connected to an // Internet gateway, then the error DPNHERR_SERVERNOTAVAILABLE is // returned. // // If GetCaps has not been previously called with the // DPNHGETCAPS_UPDATESERVERSTATUS flag at least once, then the // error code DPNHERR_UPDATESERVERSTATUS is returned. // // Arguments: // SOCKADDR * pSourceAddress - Address for network interface that is using // the address in question. // SOCKADDR * pQueryAddress - Address to look up. // SOCKADDR * pResponseAddress - Place to store public address, if one exists. // int iAddressesSize - Size of the SOCKADDR structure used for the // pSourceAddress, pQueryAddress and // pResponseAddress buffers. // DWORD dwFlags - Flags to use when querying // (DPNHQUERYADDRESS_xxx). // // Returns: HRESULT // DPNH_OK - The address was found and its mapping was // stored in pResponseAddress. // DPNHERR_GENERIC - An error occurred that prevented mapping the // requested address. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_INVALIDPOINTER - An invalid pointer was specified. // DPNHERR_NOMAPPING - The server indicated that no mapping for the // requested address was found. // DPNHERR_NOMAPPINGBUTPRIVATE - The server indicated that no mapping was // found, but it is a private address. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to query. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. // DPNHERR_SERVERNOTAVAILABLE - There are no servers to query. // DPNHERR_UPDATESERVERSTATUS - GetCaps has not been called with the // DPNHGETCAPS_UPDATESERVERSTATUS flag yet. //============================================================================= STDMETHODIMP CNATHelpUPnP::QueryAddress(const SOCKADDR * const pSourceAddress, const SOCKADDR * const pQueryAddress, SOCKADDR * const pResponseAddress, const int iAddressesSize, const DWORD dwFlags) { HRESULT hr; BOOL fHaveLock = FALSE; CDevice * pDevice; SOCKADDR_IN * psaddrinNextServerQueryAddress = NULL; CUPnPDevice * pUPnPDevice = NULL; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, %i, 0x%lx)", this, pSourceAddress, pQueryAddress, pResponseAddress, iAddressesSize, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if (pSourceAddress == NULL) { DPFX(DPFPREP, 0, "Invalid source address specified!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (pQueryAddress == NULL) { DPFX(DPFPREP, 0, "Invalid query address specified!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (pResponseAddress == NULL) { DPFX(DPFPREP, 0, "Invalid response address specified!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (iAddressesSize < sizeof(SOCKADDR_IN)) { DPFX(DPFPREP, 0, "The address buffers must be at least %i bytes!", sizeof(SOCKADDR_IN)); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (IsBadReadPtr(pSourceAddress, sizeof(SOCKADDR_IN))) { DPFX(DPFPREP, 0, "Invalid source address buffer used!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (IsBadReadPtr(pQueryAddress, sizeof(SOCKADDR_IN))) { DPFX(DPFPREP, 0, "Invalid query address buffer used!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if (IsBadWritePtr(pResponseAddress, sizeof(SOCKADDR_IN))) { DPFX(DPFPREP, 0, "Invalid response address buffer used!"); hr = DPNHERR_INVALIDPOINTER; goto Failure; } if ((((SOCKADDR_IN*) pSourceAddress)->sin_family != AF_INET) || (((SOCKADDR_IN*) pQueryAddress)->sin_family != AF_INET)) { DPFX(DPFPREP, 0, "Source or query address is not AF_INET, only IPv4 addresses are supported!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (((SOCKADDR_IN*) pSourceAddress)->sin_addr.S_un.S_addr == INADDR_BROADCAST) { DPFX(DPFPREP, 0, "Source address cannot be broadcast address!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if ((((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_addr == INADDR_ANY) || (((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_addr == INADDR_BROADCAST)) { // // Don't use inet_ntoa because we may not be initialized yet. // DPFX(DPFPREP, 0, "Query address (%u.%u.%u.%u) is invalid, cannot be zero or broadcast!", ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b4); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwFlags & ~(DPNHQUERYADDRESS_TCP | DPNHQUERYADDRESS_CACHEFOUND | DPNHQUERYADDRESS_CACHENOTFOUND | DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED)) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } if ((((SOCKADDR_IN*) pQueryAddress)->sin_port == 0) && (! (dwFlags & DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED))) { DPFX(DPFPREP, 0, "Query address port cannot be zero unless CHECKFORPRIVATEBUTUNMAPPED is specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } if (this->m_dwLastUpdateServerStatusTime == 0) { DPFX(DPFPREP, 0, "GetCaps has not been called with UPDATESERVERSTATUS flag yet!"); hr = DPNHERR_UPDATESERVERSTATUS; goto Failure; } pDevice = this->FindMatchingDevice((SOCKADDR_IN*) pSourceAddress, TRUE); if (pDevice == NULL) { DPFX(DPFPREP, 1, "Couldn't determine owning device for source %u.%u.%u.%u, returning SERVERNOTAVAILABLE for query %u.%u.%u.%u:%u.", ((SOCKADDR_IN*) pSourceAddress)->sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) pSourceAddress)->sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) pSourceAddress)->sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) pSourceAddress)->sin_addr.S_un.S_un_b.s_b4, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b1, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b2, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b3, ((SOCKADDR_IN*) pQueryAddress)->sin_addr.S_un.S_un_b.s_b4, NTOHS(((SOCKADDR_IN*) pQueryAddress)->sin_port)); hr = DPNHERR_SERVERNOTAVAILABLE; goto Exit; } // // Assume no servers are available. This will get overridden as // appropriate. // hr = DPNHERR_SERVERNOTAVAILABLE; // // Start by querying the address passed in. // psaddrinNextServerQueryAddress = (SOCKADDR_IN*) pQueryAddress; // // If the port is zero, then we can't actually lookup a mapping. Just do // the address locality check. // if (psaddrinNextServerQueryAddress->sin_port == 0) { // // We should have caught this in parameter validation above, but I'm // being paranoid. // DNASSERT(dwFlags & DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED); // // We don't cache these results, since there's no server (and thus, no // network traffic) associated with it. No need to look anything up. // // // If there aren't any Internet gateways, then no need to check. // #ifdef DPNBUILD_NOHNETFWAPI if (pDevice->GetUPnPDevice() == NULL) #else // ! DPNBUILD_NOHNETFWAPI if ((pDevice->GetUPnPDevice() == NULL) && (! pDevice->IsHNetFirewalled())) #endif // ! DPNBUILD_NOHNETFWAPI { DPFX(DPFPREP, 5, "No port queried and there aren't any gateways, returning SERVERNOTAVAILABLE."); hr = DPNHERR_SERVERNOTAVAILABLE; } else { // // There is an Internet gateway of some kind, our locality check // would be meaningful. // if (this->IsAddressLocal(pDevice, psaddrinNextServerQueryAddress)) { DPFX(DPFPREP, 5, "No port queried, but address appears to be local, returning NOMAPPINGBUTPRIVATE."); hr = DPNHERR_NOMAPPINGBUTPRIVATE; } else { DPFX(DPFPREP, 5, "No port queried and address does not appear to be local, returning NOMAPPING."); hr = DPNHERR_NOMAPPING; } } // // We've done all we can do. // goto Exit; } // // Query the UPnP gateway, if there is one. // pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { // // GetUPnPDevice did not add a reference to pUPnPDevice for us. // pUPnPDevice->AddRef(); DNASSERT(pUPnPDevice->IsReady()); // // Actually query the device. // hr = this->InternalUPnPQueryAddress(pUPnPDevice, psaddrinNextServerQueryAddress, (SOCKADDR_IN*) pResponseAddress, dwFlags); switch (hr) { case DPNH_OK: { // // There was a mapping. // //psaddrinNextServerQueryAddress = (SOCKADDR_IN*) pResponseAddress; break; } case DPNHERR_NOMAPPING: { // // There's no mapping. // break; } case DPNHERR_NOMAPPINGBUTPRIVATE: { // // There's no mapping although the address is private. // break; } case DPNHERR_SERVERNOTRESPONDING: { // // The device stopped responding, so we should get rid of it. // DPFX(DPFPREP, 1, "UPnP device stopped responding while querying port mapping, removing it."); this->ClearDevicesUPnPDevice(pDevice); // // We also set the return code back to SERVERNOTAVAILABLE. // hr = DPNHERR_SERVERNOTAVAILABLE; // // Continue through to querying the HomeNet firewall. // break; } default: { DPFX(DPFPREP, 0, "Querying UPnP device for port mapping failed!"); goto Failure; break; } } pUPnPDevice->DecRef(); pUPnPDevice = NULL; } else { // // No UPnP device. // } #ifndef DPNBUILD_NOHNETFWAPI // // If there's a HomeNet firewall and we didn't already get a UPnP result, // take the easy way out and return NOMAPPING instead of going through the // trouble of looking up the mapping and returning success only if it maps // to a local address. // // Note: we may want to look it up, but right now I'm not seeing any // benefit to implementing that code. // if ((pDevice->IsHNetFirewalled()) && (hr == DPNHERR_SERVERNOTAVAILABLE)) { DPFX(DPFPREP, 7, "Device is HomeNet firewalled, and no UPnP result obtained, returning NOMAPPING."); hr = DPNHERR_NOMAPPING; } #endif // ! DPNBUILD_NOHNETFWAPI // // If we got here with hr still set to SERVERNOTAVAILABLE, that means // there weren't any servers. The error code is appropriate, leave it // alone. // #ifdef DBG if (hr == DPNHERR_SERVERNOTAVAILABLE) { DPFX(DPFPREP, 1, "No Internet gateways, unable to query port mapping."); } #endif // DBG Exit: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pUPnPDevice != NULL) { pUPnPDevice->DecRef(); } goto Exit; } // CNATHelpUPnP::QueryAddress #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::SetAlertEvent" //============================================================================= // CNATHelpUPnP::SetAlertEvent //----------------------------------------------------------------------------- // // Description: This function allows the user to specify an event that will // be set when some maintenance needs to be performed. The user // should call GetCaps using the DPNHGETCAPS_UPDATESERVERSTATUS // flag when the event is signalled. // // This function is not available on Windows 95 without WinSock // 2, may only be called once, and cannot be used after // SetAlertIOCompletionPort is called. // // Note that the event is used in addition to the regular // polling of GetCaps, it simply allows the polling to be less // frequent. // // Arguments: // HANDLE hEvent - Handle to event to signal when GetCaps is to be called. // DWORD dwFlags - Unused, must be zero. // // Returns: HRESULT // DPNH_OK - The event was successfully registered. // DPNHERR_GENERIC - An error occurred that prevented registering the // event. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::SetAlertEvent(const HANDLE hEvent, const DWORD dwFlags) { #ifdef DPNBUILD_NOWINSOCK2 DPFX(DPFPREP, 0, "Cannot set alert event (0x%p)!", hEvent); return E_NOTIMPL; #else // ! DPNBUILD_NOWINSOCK2 HRESULT hr; BOOL fHaveLock = FALSE; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, 0x%lx)", this, hEvent, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if (hEvent == NULL) { DPFX(DPFPREP, 0, "Invalid event handle specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwFlags != 0) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED)) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } if (this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1) { DPFX(DPFPREP, 0, "Cannot use alert mechanism on WinSock 1!"); hr = DPNHERR_GENERIC; goto Failure; } if ((this->m_hAlertEvent != NULL) || (this->m_hAlertIOCompletionPort != NULL)) { DPFX(DPFPREP, 0, "An alert event or I/O completion port has already been set!"); hr = DPNHERR_GENERIC; goto Failure; } // // Now save the event handle. // if (! DuplicateHandle(GetCurrentProcess(), hEvent, GetCurrentProcess(), &this->m_hAlertEvent, 0, FALSE, DUPLICATE_SAME_ACCESS)) { #ifdef DBG DWORD dwError; dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't duplicate event (error = %u)!", dwError); #endif // DBG DNASSERT(this->m_hAlertEvent == NULL); hr = DPNHERR_INVALIDPARAM; goto Failure; } // // Create overlapped structure. Don't allocate it through DNMalloc, // because we may have to leak it on purpose. We don't want those memory // allocation asserts firing in that case. // this->m_polAddressListChange = (WSAOVERLAPPED*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WSAOVERLAPPED)); if (this->m_polAddressListChange == NULL) { // // Close the alert handle we set. // CloseHandle(this->m_hAlertEvent); this->m_hAlertEvent = NULL; hr = DPNHERR_OUTOFMEMORY; goto Failure; } // // Save the event in the address list change overlapped structure. // this->m_polAddressListChange->hEvent = this->m_hAlertEvent; // // Start getting notified of local address changes. // hr = this->RequestLocalAddressListChangeNotification(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't request local address list change notification!"); // // Free the memory we allocated. // HeapFree(GetProcessHeap(), 0, this->m_polAddressListChange); this->m_polAddressListChange = NULL; // // Close the alert handle we set. // CloseHandle(this->m_hAlertEvent); this->m_hAlertEvent = NULL; goto Failure; } Exit: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; #endif // ! DPNBUILD_NOWINSOCK2 } // CNATHelpUPnP::SetAlertEvent #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::SetAlertIOCompletionPort" //============================================================================= // CNATHelpUPnP::SetAlertIOCompletionPort //----------------------------------------------------------------------------- // // Description: This function allows the user to specify an I/O completion // port that will receive notification when some maintenance needs // to be performed. The user should call GetCaps using the // DPNHGETCAPS_UPDATESERVERSTATUS flag when the packet with the // given completion key is dequeued. // // This function is only available on Windows NT, may only be // called once, and cannot be used after SetAlertEvent is called. // // Note that the completion port is used in addition to the // regular polling of GetCaps, it simply allows the polling to be // less frequent. // // Arguments: // HANDLE hIOCompletionPort - Handle to I/O completion port which will // be used to signal when GetCaps is to be // called. // DWORD dwCompletionKey - Key to use when indicating I/O // completion. // DWORD dwNumConcurrentThreads - Number of concurrent threads allowed to // process, or zero for default. // DWORD dwFlags - Unused, must be zero. // // Returns: HRESULT // DPNH_OK - The I/O completion port was successfully // registered. // DPNHERR_GENERIC - An error occurred that prevented registering the // I/O completion port. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::SetAlertIOCompletionPort(const HANDLE hIOCompletionPort, const DWORD dwCompletionKey, const DWORD dwNumConcurrentThreads, const DWORD dwFlags) { #ifdef DPNBUILD_NOWINSOCK2 DPFX(DPFPREP, 0, "Cannot set alert I/O completion port (0x%p, %u, %u)!", hIOCompletionPort, dwCompletionKey, dwNumConcurrentThreads); return E_NOTIMPL; #else // ! DPNBUILD_NOWINSOCK2 HRESULT hr; BOOL fHaveLock = FALSE; HANDLE hIOCompletionPortResult; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, 0x%lx, %u, 0x%lx)", this, hIOCompletionPort, dwCompletionKey, dwNumConcurrentThreads, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // if (hIOCompletionPort == NULL) { DPFX(DPFPREP, 0, "Invalid I/O completion port handle specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwFlags != 0) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED)) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } if (this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1) { DPFX(DPFPREP, 0, "Cannot use alert mechanism on WinSock 1!"); hr = DPNHERR_GENERIC; goto Failure; } if ((this->m_hAlertEvent != NULL) || (this->m_hAlertIOCompletionPort != NULL)) { DPFX(DPFPREP, 0, "An alert event or I/O completion port has already been set!"); hr = DPNHERR_GENERIC; goto Failure; } // // Now save the I/O completion port handle. // if (! DuplicateHandle(GetCurrentProcess(), hIOCompletionPort, GetCurrentProcess(), &this->m_hAlertIOCompletionPort, 0, FALSE, DUPLICATE_SAME_ACCESS)) { #ifdef DBG DWORD dwError; dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't duplicate I/O completion port (error = %u)!", dwError); #endif // DBG DNASSERT(this->m_hAlertIOCompletionPort == NULL); hr = DPNHERR_INVALIDPARAM; goto Failure; } this->m_dwAlertCompletionKey = dwCompletionKey; // // Associate our Ioctl socket with this IO completion port. // DNASSERT(this->m_sIoctls != INVALID_SOCKET); hIOCompletionPortResult = CreateIoCompletionPort((HANDLE) this->m_sIoctls, this->m_hAlertIOCompletionPort, dwCompletionKey, dwNumConcurrentThreads); if (hIOCompletionPortResult == NULL) { #ifdef DBG DWORD dwError; dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't associate I/O completion port with Ioctl socket (error = %u)!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // We should have just gotten the same I/O completion port back. // DNASSERT(hIOCompletionPortResult == this->m_hAlertIOCompletionPort); // // Create overlapped structure. Don't allocate it through DNMalloc, // because we may have to leak it on purpose. We don't want those memory // allocation asserts firing in that case. // this->m_polAddressListChange = (WSAOVERLAPPED*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WSAOVERLAPPED)); if (this->m_polAddressListChange == NULL) { // // Close the alert IOCP we set. // CloseHandle(this->m_hAlertIOCompletionPort); this->m_hAlertIOCompletionPort = NULL; hr = DPNHERR_OUTOFMEMORY; goto Failure; } // // Start getting notified of local address changes. // hr = this->RequestLocalAddressListChangeNotification(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't request local address list change notification!"); // // Free the memory we allocated. // HeapFree(GetProcessHeap(), 0, this->m_polAddressListChange); this->m_polAddressListChange = NULL; // // Close the alert IOCP we set. // CloseHandle(this->m_hAlertIOCompletionPort); this->m_hAlertIOCompletionPort = NULL; goto Failure; } Exit: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; #endif // ! DPNBUILD_NOWINSOCK2 } // CNATHelpUPnP::SetAlertIOCompletionPort #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ExtendRegisteredPortsLease" //============================================================================= // CNATHelpUPnP::ExtendRegisteredPortsLease //----------------------------------------------------------------------------- // // Description: Manually extends the lease of the given registered port // mapping by the requested time. The periodic calling of GetCaps // can take care of this for the user, this function is only // necessary to change the lease extension time or for finer // control of individual mappings. // // The user should specify a requested lease extension time // that the server will attempt to honor. It will be added to any // time remaining in the existing lease, and the new total can be // retrieved by calling GetRegisteredAddresses. // // Arguments: // DPNHHANDLE hRegisteredPorts - Handle for a specific binding returned by // RegisterPorts. // DWORD dwLeaseTime - Requested time, in milliseconds, to // extend the lease. If 0, the previous // requested lease time is used. // DWORD dwFlags - Unused, must be zero. // // Returns: HRESULT // DPNH_OK - The lease was successfully extended. // DPNHERR_GENERIC - An error occurred that prevented the extending // the lease. // DPNHERR_INVALIDFLAGS - Invalid flags were specified. // DPNHERR_INVALIDOBJECT - The interface object is invalid. // DPNHERR_INVALIDPARAM - An invalid parameter was specified. // DPNHERR_NOTINITIALIZED - Initialize has not been called. // DPNHERR_OUTOFMEMORY - There is not enough memory to extend the lease. // DPNHERR_REENTRANT - The interface has been re-entered on the same // thread. //============================================================================= STDMETHODIMP CNATHelpUPnP::ExtendRegisteredPortsLease(const DPNHHANDLE hRegisteredPorts, const DWORD dwLeaseTime, const DWORD dwFlags) { HRESULT hr; CRegisteredPort * pRegisteredPort; CDevice * pDevice; BOOL fHaveLock = FALSE; DPFX(DPFPREP, 2, "(0x%p) Parameters: (0x%p, %u, 0x%lx)", this, hRegisteredPorts, dwLeaseTime, dwFlags); // // Validate the object. // if (! this->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid DirectPlay NAT Help object!"); hr = DPNHERR_INVALIDOBJECT; goto Failure; } // // Validate the parameters. // pRegisteredPort = (CRegisteredPort*) hRegisteredPorts; if (! pRegisteredPort->IsValidObject()) { DPFX(DPFPREP, 0, "Invalid registered port mapping handle specified!"); hr = DPNHERR_INVALIDPARAM; goto Failure; } if (dwFlags != 0) { DPFX(DPFPREP, 0, "Invalid flags specified!"); hr = DPNHERR_INVALIDFLAGS; goto Failure; } // // Attempt to take the lock, but be prepared for the re-entrancy error. // hr = this->TakeLock(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Could not lock object!"); goto Failure; } fHaveLock = TRUE; // // Make sure object is in right state. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED) ) { DPFX(DPFPREP, 0, "Object not initialized!"); hr = DPNHERR_NOTINITIALIZED; goto Failure; } // // If they wanted to change the lease time, update it. // if (dwLeaseTime != 0) { pRegisteredPort->UpdateRequestedLeaseTime(dwLeaseTime); } pDevice = pRegisteredPort->GetOwningDevice(); // // If the port is registered with the UPnP device, extend that lease. // if (pRegisteredPort->HasUPnPPublicAddresses()) { DNASSERT(pDevice != NULL); hr = this->ExtendUPnPLease(pRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't extend port mapping lease on UPnP device (0x%lx)! Ignoring.", hr); // // We'll treat this as non-fatal, but we have to dump the // server. This may have already been done, but doing it // twice shouldn't be harmful. // this->ClearDevicesUPnPDevice(pDevice); hr = DPNH_OK; } } else { DPFX(DPFPREP, 2, "Port mapping not registered with UPnP gateway device."); } // // Firewall mappings never have lease times to extend. // this->DropLock(); fHaveLock = FALSE; Exit: DPFX(DPFPREP, 2, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (fHaveLock) { this->DropLock(); fHaveLock = FALSE; } goto Exit; } // CNATHelpUPnP::ExtendRegisteredPortsLease #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::InitializeObject" //============================================================================= // CNATHelpUPnP::InitializeObject //----------------------------------------------------------------------------- // // Description: Sets up the object for use like the constructor, but may // fail with OUTOFMEMORY. Should only be called by class factory // creation routine. // // Arguments: None. // // Returns: HRESULT // S_OK - Initialization was successful. // E_OUTOFMEMORY - There is not enough memory to initialize. //============================================================================= HRESULT CNATHelpUPnP::InitializeObject(void) { HRESULT hr; BOOL fInittedCriticalSection = FALSE; DPFX(DPFPREP, 5, "(0x%p) Enter", this); DNASSERT(this->IsValidObject()); // // Create the lock. // if (! DNInitializeCriticalSection(&this->m_csLock)) { hr = E_OUTOFMEMORY; goto Failure; } fInittedCriticalSection = TRUE; // // Don't allow critical section reentry. // DebugSetCriticalSectionRecursionCount(&this->m_csLock, 0); this->m_hLongLockSemaphore = DNCreateSemaphore(NULL, 0, MAX_LONG_LOCK_WAITING_THREADS, NULL); if (this->m_hLongLockSemaphore == NULL) { hr = DPNHERR_GENERIC; goto Failure; } hr = S_OK; Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (fInittedCriticalSection) { DNDeleteCriticalSection(&this->m_csLock); fInittedCriticalSection = FALSE; } goto Exit; } // CNATHelpUPnP::InitializeObject #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::UninitializeObject" //============================================================================= // CNATHelpUPnP::UninitializeObject //----------------------------------------------------------------------------- // // Description: Cleans up the object like the destructor, mostly to balance // InitializeObject. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::UninitializeObject(void) { DPFX(DPFPREP, 5, "(0x%p) Enter", this); DNASSERT(this->IsValidObject()); DNCloseHandle(this->m_hLongLockSemaphore); this->m_hLongLockSemaphore = NULL; DNDeleteCriticalSection(&this->m_csLock); DPFX(DPFPREP, 5, "(0x%p) Leave", this); } // CNATHelpUPnP::UninitializeObject #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::TakeLock" //============================================================================= // CNATHelpUPnP::TakeLock //----------------------------------------------------------------------------- // // Description: Takes the main object lock. If some other thread is already // holding the long lock, we wait for that first. // // Arguments: None. // // Returns: DPNH_OK if lock was taken successfully, DPNHERR_REENTRANT if lock // was re-entered. //============================================================================= HRESULT CNATHelpUPnP::TakeLock(void) { HRESULT hr = DPNH_OK; #ifdef DBG DWORD dwStartTime; dwStartTime = GETTIMESTAMP(); #endif // DBG DNEnterCriticalSection(&this->m_csLock); // // If this same thread is already holding the lock, then bail. // if (this->m_dwLockThreadID == GetCurrentThreadId()) { DPFX(DPFPREP, 0, "Thread re-entering!"); goto Failure; } // // If someone is holding the long lock, we need to wait for that. Of // course another thread could come in and take the long lock after the // first one drops it and before we can take the main one. This algorithm // does not attempt to be fair in this case. Theoretically we could wait // forever if this continued to occur. That shouldn't happen in the real // world. // This whole mess of code is a huge... uh... workaround for stress hits // involving critical section timeouts. // while (this->m_dwFlags & NATHELPUPNPOBJ_LONGLOCK) { DNASSERT(this->m_lNumLongLockWaitingThreads >= 0); this->m_lNumLongLockWaitingThreads++; // // We need to keep looping until we do get the lock. // DNLeaveCriticalSection(&this->m_csLock); DPFX(DPFPREP, 3, "Waiting for long lock to be released."); DNWaitForSingleObject(this->m_hLongLockSemaphore, INFINITE); DNEnterCriticalSection(&this->m_csLock); // // If this same thread is already holding the lock, then bail. // if (this->m_dwLockThreadID == GetCurrentThreadId()) { DPFX(DPFPREP, 0, "Thread re-entering after waiting for long lock!"); goto Failure; } } #ifdef DBG DPFX(DPFPREP, 8, "Took main object lock, elapsed time = %u ms.", (GETTIMESTAMP() - dwStartTime)); #endif // DBG // // Save this thread's ID so we know who's holding the lock. // this->m_dwLockThreadID = GetCurrentThreadId(); Exit: return hr; Failure: // // We're reentering. Drop the lock and return the failure. // DNLeaveCriticalSection(&this->m_csLock); hr = DPNHERR_REENTRANT; goto Exit; } // CNATHelpUPnP::TakeLock #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::DropLock" //============================================================================= // CNATHelpUPnP::DropLock //----------------------------------------------------------------------------- // // Description: Drops the main object lock. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::DropLock(void) { DNASSERT(! (this->m_dwFlags & NATHELPUPNPOBJ_LONGLOCK)); DNASSERT(this->m_lNumLongLockWaitingThreads == 0); DNASSERT(this->m_dwLockThreadID == GetCurrentThreadId()); this->m_dwLockThreadID = 0; DNLeaveCriticalSection(&this->m_csLock); DPFX(DPFPREP, 8, "Dropped main object lock."); } // CNATHelpUPnP::DropLock #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::SwitchToLongLock" //============================================================================= // CNATHelpUPnP::SwitchToLongLock //----------------------------------------------------------------------------- // // Description: Switches from holding the main object lock to holding the // long lock. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::SwitchToLongLock(void) { AssertCriticalSectionIsTakenByThisThread(&this->m_csLock, TRUE); DNASSERT(! (this->m_dwFlags & NATHELPUPNPOBJ_LONGLOCK)); DNASSERT(this->m_lNumLongLockWaitingThreads == 0); DPFX(DPFPREP, 8, "Switching to long lock."); this->m_dwFlags |= NATHELPUPNPOBJ_LONGLOCK; DNLeaveCriticalSection(&this->m_csLock); } // CNATHelpUPnP::SwitchToLongLock #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::SwitchFromLongLock" //============================================================================= // CNATHelpUPnP::SwitchFromLongLock //----------------------------------------------------------------------------- // // Description: Switches from holding the long lock back to holding the main // object lock. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::SwitchFromLongLock(void) { DNEnterCriticalSection(&this->m_csLock); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_LONGLOCK); this->m_dwFlags &= ~NATHELPUPNPOBJ_LONGLOCK; DPFX(DPFPREP, 8, "Switching from long lock, alerting %i threads.", this->m_lNumLongLockWaitingThreads); // // This is non-optimal in that we release the semaphore but the waiting // threads still won't actually be able to do anything since we now hold // the main lock. // DNASSERT(this->m_lNumLongLockWaitingThreads >= 0); DNReleaseSemaphore(this->m_hLongLockSemaphore, this->m_lNumLongLockWaitingThreads, NULL); this->m_lNumLongLockWaitingThreads = 0; } // CNATHelpUPnP::SwitchFromLongLock #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::LoadWinSockFunctionPointers" //============================================================================= // CNATHelpUPnP::LoadWinSockFunctionPointers //----------------------------------------------------------------------------- // // Description: Loads pointers to all the functions that we use in WinSock. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: HRESULT // DPNH_OK - Loading was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::LoadWinSockFunctionPointers(void) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #ifdef DBG #define PRINTERRORIFDEBUG(name) \ {\ dwError = GetLastError();\ DPFX(DPFPREP, 0, "Couldn't get \"%hs\" function! 0x%lx", name, dwError);\ } #else // ! DBG #define PRINTERRORIFDEBUG(name) #endif // ! DBG #define LOADWINSOCKFUNCTION(var, proctype, name) \ {\ var = (##proctype) GetProcAddress(this->m_hWinSockDLL, _TWINCE(name));\ if (var == NULL)\ {\ PRINTERRORIFDEBUG(name);\ hr = DPNHERR_GENERIC;\ goto Failure;\ }\ } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - HRESULT hr = DPNH_OK; #ifdef DBG DWORD dwError; #endif // DBG LOADWINSOCKFUNCTION(this->m_pfnWSAStartup, LPFN_WSASTARTUP, "WSAStartup"); LOADWINSOCKFUNCTION(this->m_pfnWSACleanup, LPFN_WSACLEANUP, "WSACleanup"); #ifdef WINCE this->m_pfnWSAGetLastError = (LPFN_WSAGETLASTERROR) GetLastError; #else // ! WINCE LOADWINSOCKFUNCTION(this->m_pfnWSAGetLastError, LPFN_WSAGETLASTERROR, "WSAGetLastError"); #endif // ! WINCE LOADWINSOCKFUNCTION(this->m_pfnsocket, LPFN_SOCKET, "socket"); LOADWINSOCKFUNCTION(this->m_pfnclosesocket, LPFN_CLOSESOCKET, "closesocket"); LOADWINSOCKFUNCTION(this->m_pfnbind, LPFN_BIND, "bind"); LOADWINSOCKFUNCTION(this->m_pfnsetsockopt, LPFN_SETSOCKOPT, "setsockopt"); LOADWINSOCKFUNCTION(this->m_pfngetsockname, LPFN_GETSOCKNAME, "getsockname"); LOADWINSOCKFUNCTION(this->m_pfnselect, LPFN_SELECT, "select"); LOADWINSOCKFUNCTION(this->m_pfn__WSAFDIsSet, LPFN___WSAFDISSET, "__WSAFDIsSet"); LOADWINSOCKFUNCTION(this->m_pfnrecvfrom, LPFN_RECVFROM, "recvfrom"); LOADWINSOCKFUNCTION(this->m_pfnsendto, LPFN_SENDTO, "sendto"); LOADWINSOCKFUNCTION(this->m_pfngethostname, LPFN_GETHOSTNAME, "gethostname"); LOADWINSOCKFUNCTION(this->m_pfngethostbyname, LPFN_GETHOSTBYNAME, "gethostbyname"); LOADWINSOCKFUNCTION(this->m_pfninet_addr, LPFN_INET_ADDR, "inet_addr"); #ifndef DPNBUILD_NOWINSOCK2 if (! (this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1)) { LOADWINSOCKFUNCTION(this->m_pfnWSASocketA, LPFN_WSASOCKETA, "WSASocketA"); LOADWINSOCKFUNCTION(this->m_pfnWSAIoctl, LPFN_WSAIOCTL, "WSAIoctl"); LOADWINSOCKFUNCTION(this->m_pfnWSAGetOverlappedResult, LPFN_WSAGETOVERLAPPEDRESULT, "WSAGetOverlappedResult"); } #endif // ! DPNBUILD_NOWINSOCK2 LOADWINSOCKFUNCTION(this->m_pfnioctlsocket, LPFN_IOCTLSOCKET, "ioctlsocket"); LOADWINSOCKFUNCTION(this->m_pfnconnect, LPFN_CONNECT, "connect"); LOADWINSOCKFUNCTION(this->m_pfnshutdown, LPFN_SHUTDOWN, "shutdown"); LOADWINSOCKFUNCTION(this->m_pfnsend, LPFN_SEND, "send"); LOADWINSOCKFUNCTION(this->m_pfnrecv, LPFN_RECV, "recv"); #ifdef DBG LOADWINSOCKFUNCTION(this->m_pfngetsockopt, LPFN_GETSOCKOPT, "getsockopt"); #endif // DBG Exit: return hr; Failure: hr = DPNHERR_GENERIC; goto Exit; } // CNATHelpUPnP::LoadWinSockFunctionPointers #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CheckForNewDevices" //============================================================================= // CNATHelpUPnP::CheckForNewDevices //----------------------------------------------------------------------------- // // Description: Detects new IP capable devices that have been added and // removes old ones no longer available. // // The object lock is assumed to be held. // // Arguments: // BOOL * pfFoundNewDevices Pointer to boolean to set to TRUE if new // devices were added, or NULL if don't care. // // Returns: HRESULT // DPNH_OK - The check was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CheckForNewDevices(BOOL * const pfFoundNewDevices) { HRESULT hr = DPNH_OK; #if ((defined(DBG)) || (! defined(DPNBUILD_NOWINSOCK2))) DWORD dwError; #endif // DBG or ! DPNBUILD_NOWINSOCK2 #ifndef DPNBUILD_NOWINSOCK2 int iReturn; #endif // ! DPNBUILD_NOWINSOCK2 char szName[1000]; PHOSTENT phostent; IN_ADDR ** ppinaddr; DWORD dwAddressesSize = 0; DWORD dwNumAddresses = 0; IN_ADDR * painaddrAddresses = NULL; CBilink * pBilinkDevice; CDevice * pDevice = NULL; // NULL it for PREfix, even though fDeviceCreated guards it BOOL fDeviceCreated = FALSE; BOOL fFound; CBilink * pBilinkRegPort; CRegisteredPort * pRegisteredPort; SOCKET sTemp = INVALID_SOCKET; SOCKADDR_IN saddrinTemp; DWORD dwTemp; #ifndef DPNBUILD_NOWINSOCK2 SOCKET_ADDRESS * paSocketAddresses; #endif // ! DPNBUILD_NOWINSOCK2 DPFX(DPFPREP, 5, "(0x%p) Parameters (0x%p)", this, pfFoundNewDevices); #ifndef DPNBUILD_NOWINSOCK2 // // Handle any address list change Ioctl completions that may have gotten us // here. // if ((this->m_hAlertEvent != NULL) || (this->m_hAlertIOCompletionPort != NULL)) { DNASSERT(this->m_sIoctls != INVALID_SOCKET); DNASSERT(this->m_polAddressListChange != NULL); if (this->m_pfnWSAGetOverlappedResult(this->m_sIoctls, // this->m_polAddressListChange, // &dwTemp, // ignore bytes transferred FALSE, // don't wait &dwTemp)) // ignore flags { DPFX(DPFPREP, 1, "Received address list change notification."); // // Overlapped result completed. Reissue it. // hr = this->RequestLocalAddressListChangeNotification(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't request local address list change notification!"); goto Failure; } } else { // // Figure out what error it was. // dwError = this->m_pfnWSAGetLastError(); switch (dwError) { case WSA_IO_INCOMPLETE: { // // It hasn't completed yet. // break; } case ERROR_OPERATION_ABORTED: { // // The thread that we originally submitted the Ioctl on // went away and so the OS kindly cancelled the operation // on us. How nice. Well, let's try resubmitting it. // DPFX(DPFPREP, 1, "Thread that submitted previous address list change notification went away, rerequesting."); hr = this->RequestLocalAddressListChangeNotification(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't request local address list change notification!"); goto Failure; } break; } default: { DPFX(DPFPREP, 0, "Couldn't get overlapped result, error = %u! Ignoring.", dwError); break; } } // end switch (on error) } } // // If we're on WinSock 2, let's try getting the address list with // an Ioctl. // if (! (this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1)) { DNASSERT(this->m_sIoctls != INVALID_SOCKET); DNASSERT(this->m_pfnWSAIoctl != NULL); // // Keep trying to get the address list until we have a large enough // buffer. We use the IN_ADDR array pointer simply because it's // already there. We know that IN_ADDRs are smaller than // SOCKET_ADDRESSes, so we can reuse the same buffer. // do { iReturn = this->m_pfnWSAIoctl(this->m_sIoctls, // use the special Ioctl socket SIO_ADDRESS_LIST_QUERY, // NULL, // no input data 0, // no input data painaddrAddresses, // output buffer dwAddressesSize, // output buffer size &dwTemp, // bytes needed NULL, // no overlapped structure NULL); // no completion routine if (iReturn != 0) { dwError = this->m_pfnWSAGetLastError(); // // Free the previous buffer, no matter what error it was. // if (painaddrAddresses != NULL) { DNFree(painaddrAddresses); painaddrAddresses = NULL; } if (dwError != WSAEFAULT) { DPFX(DPFPREP, 1, "Retrieving address list failed (err = %u), trying WinSock 1 method.", dwError); // // We'll try the old-fashioned WinSock 1 way. // break; } // // Be absolutely sure WinSock isn't causing us trouble. // if (dwTemp < sizeof(SOCKET_ADDRESS_LIST)) { DPFX(DPFPREP, 0, "Received an invalid buffer size (%u < %u)!", dwTemp, sizeof(SOCKET_ADDRESS_LIST)); // // We'll try the old-fashioned WinSock 1 way. // break; } // // The buffer wasn't large enough. Try again. // painaddrAddresses = (IN_ADDR*) DNMalloc(dwTemp); if (painaddrAddresses == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } dwAddressesSize = dwTemp; } else { // // Success! We're going to being sneaky and reuse the buffer. // We know that the SOCKET_ADDRESS_LIST returned will be larger // than an array of IN_ADDRs, so we can save a malloc. // // But first, be absolutely sure WinSock isn't causing us // trouble. // if (painaddrAddresses == NULL) { DPFX(DPFPREP, 0, "WinSock returned success with a NULL buffer!"); // // We'll try the old-fashioned WinSock 1 way. // break; } dwNumAddresses = ((SOCKET_ADDRESS_LIST*) painaddrAddresses)->iAddressCount; dwAddressesSize = 0; // // Make sure there are addresses. // if (dwNumAddresses > 0) { DPFX(DPFPREP, 7, "WinSock 2 Ioctl returned %u addresses:", dwNumAddresses); paSocketAddresses = ((SOCKET_ADDRESS_LIST*) painaddrAddresses)->Address; for(dwTemp = 0; dwTemp < dwNumAddresses; dwTemp++) { DNASSERT(paSocketAddresses[dwTemp].iSockaddrLength == sizeof(SOCKADDR_IN)); DNASSERT(paSocketAddresses[dwTemp].lpSockaddr != NULL); DNASSERT(paSocketAddresses[dwTemp].lpSockaddr->sa_family == AF_INET); // // Ignore 0.0.0.0 addresses. // if (((SOCKADDR_IN*) (paSocketAddresses[dwTemp].lpSockaddr))->sin_addr.S_un.S_addr != INADDR_NONE) { // // Move the IN_ADDR component of this address // toward the front of the buffer, into it's // correct place in the array. // painaddrAddresses[dwTemp].S_un.S_addr = ((SOCKADDR_IN*) (paSocketAddresses[dwTemp].lpSockaddr))->sin_addr.S_un.S_addr; DPFX(DPFPREP, 7, "\t%u- %u.%u.%u.%u", dwTemp, painaddrAddresses[dwTemp].S_un.S_un_b.s_b1, painaddrAddresses[dwTemp].S_un.S_un_b.s_b2, painaddrAddresses[dwTemp].S_un.S_un_b.s_b3, painaddrAddresses[dwTemp].S_un.S_un_b.s_b4); } else { DPFX(DPFPREP, 1, "\t%u- Ignoring 0.0.0.0 address.", dwTemp); dwAddressesSize++; // // The code should handle this fine, but why is // WinSock doing this to us? // DNASSERT(FALSE); } } // // Subtract out any invalid addresses that we skipped. // dwNumAddresses -= dwAddressesSize; if (dwNumAddresses == 0) { DPFX(DPFPREP, 1, "WinSock 2 reported only invalid addresses, hoping WinSock 1 method picks up the loopback address."); DNFree(painaddrAddresses); painaddrAddresses = NULL; } } else { DPFX(DPFPREP, 1, "WinSock 2 Ioctl did not report any valid addresses, hoping WinSock 1 method picks up the loopback address."); DNFree(painaddrAddresses); painaddrAddresses = NULL; } // // Get out of the loop. // break; } } while (TRUE); } // // Get the list of all available addresses from the WinSock 1 API if we // don't already have them. // if (painaddrAddresses == NULL) #endif // ! DPNBUILD_NOWINSOCK2 { if (this->m_pfngethostname(szName, 1000) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't get host name, error = %u!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } phostent = this->m_pfngethostbyname(szName); if (phostent == NULL) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't retrieve addresses, error = %u!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // WinSock says that you need to copy this data before you make any // other API calls. So first we count the number of entries we need to // copy. // ppinaddr = (IN_ADDR**) phostent->h_addr_list; while ((*ppinaddr) != NULL) { // // Ignore 0.0.0.0 addresses. // if ((*ppinaddr)->S_un.S_addr != INADDR_NONE) { dwNumAddresses++; } else { DPFX(DPFPREP, 1, "Ignoring 0.0.0.0 address."); // // The code should handle this fine, but why is WinSock doing // this to us? // DNASSERT(FALSE); } ppinaddr++; } // // If there aren't any addresses, we must fail. WinSock 1 ought to // report the loopback address at least. // if (dwNumAddresses == 0) { DPFX(DPFPREP, 0, "WinSock 1 did not report any valid addresses!"); hr = DPNHERR_GENERIC; goto Failure; } DPFX(DPFPREP, 7, "WinSock 1 method returned %u valid addresses:", dwNumAddresses); painaddrAddresses = (IN_ADDR*) DNMalloc(dwNumAddresses * sizeof(IN_ADDR)); if (painaddrAddresses == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } // // Now copy all the addresses. // ppinaddr = (IN_ADDR**) phostent->h_addr_list; dwTemp = 0; while ((*ppinaddr) != NULL) { // // Ignore 0.0.0.0 addresses again. // if ((*ppinaddr)->S_un.S_addr != INADDR_NONE) { painaddrAddresses[dwTemp].S_un.S_addr = (*ppinaddr)->S_un.S_addr; DPFX(DPFPREP, 7, "\t%u- %u.%u.%u.%u", dwTemp, painaddrAddresses[dwTemp].S_un.S_un_b.s_b1, painaddrAddresses[dwTemp].S_un.S_un_b.s_b2, painaddrAddresses[dwTemp].S_un.S_un_b.s_b3, painaddrAddresses[dwTemp].S_un.S_un_b.s_b4); dwTemp++; } ppinaddr++; } DNASSERT(dwTemp == dwNumAddresses); } /* else { // // Already have addresses array. // } */ // // Make sure that all of the devices we currently know about are still // around. // pBilinkDevice = this->m_blDevices.GetNext(); while (pBilinkDevice != &this->m_blDevices) { DNASSERT(! pBilinkDevice->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilinkDevice); pBilinkDevice = pBilinkDevice->GetNext(); fFound = FALSE; for(dwTemp = 0; dwTemp < dwNumAddresses; dwTemp++) { if (painaddrAddresses[dwTemp].S_un.S_addr == pDevice->GetLocalAddressV4()) { fFound = TRUE; break; } } if (fFound) { // // It may be time for this device to use a different port... // dwTemp = pDevice->GetFirstUPnPDiscoveryTime(); if ((dwTemp != 0) && ((GETTIMESTAMP() - dwTemp) > g_dwReusePortTime)) { ZeroMemory(&saddrinTemp, sizeof(saddrinTemp)); saddrinTemp.sin_family = AF_INET; saddrinTemp.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); sTemp = this->CreateSocket(&saddrinTemp, SOCK_DGRAM, IPPROTO_UDP); if (sTemp != INVALID_SOCKET) { // // Sanity check that we didn't lose the device address. // DNASSERT(saddrinTemp.sin_addr.S_un.S_addr == pDevice->GetLocalAddressV4()); DPFX(DPFPREP, 4, "Device 0x%p UPnP discovery socket 0x%p (%u.%u.%u.%u:%u) created to replace port %u.", pDevice, sTemp, saddrinTemp.sin_addr.S_un.S_un_b.s_b1, saddrinTemp.sin_addr.S_un.S_un_b.s_b2, saddrinTemp.sin_addr.S_un.S_un_b.s_b3, saddrinTemp.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinTemp.sin_port), NTOHS(pDevice->GetUPnPDiscoverySocketPort())); #ifndef DPNBUILD_NOHNETFWAPI // // If we used the HomeNet firewall API to open a hole for UPnP // discovery multicasts, close it. // if (pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()) { hr = this->CloseDevicesUPnPDiscoveryPort(pDevice, NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't close device 0x%p's previous UPnP discovery socket's port on firewall (err = 0x%lx)! Ignoring.", pDevice, hr); // // Continue... // pDevice->NoteNotUPnPDiscoverySocketMappedOnHNetFirewall(); hr = DPNH_OK; } } #endif // ! DPNBUILD_NOHNETFWAPI pDevice->SetUPnPDiscoverySocketPort(saddrinTemp.sin_port); pDevice->SetFirstUPnPDiscoveryTime(0); // // Close the existing socket. // this->m_pfnclosesocket(pDevice->GetUPnPDiscoverySocket()); // // Transfer ownership of the new socket to the device. // pDevice->SetUPnPDiscoverySocket(sTemp); sTemp = INVALID_SOCKET; DPFX(DPFPREP, 8, "Device 0x%p got re-assigned UPnP socket 0x%p.", pDevice, pDevice->GetUPnPDiscoverySocket()); // // We'll let the normal "check for firewall" code detect // the fact that the discovery socket is not mapped on the // firewall and try to do so there (if it even needs to be // mapped). See UpdateServerStatus. // } else { DPFX(DPFPREP, 0, "Couldn't create a replacement UPnP discovery socket for device 0x%p! Using existing port %u.", pDevice, NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } } } else { // // Didn't find this device in the returned list, forget about // it. // #ifdef DBG { IN_ADDR inaddrTemp; inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); DPFX(DPFPREP, 1, "Device 0x%p no longer exists, removing (address was %u.%u.%u.%u).", pDevice, inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); } this->m_dwNumDeviceRemoves++; #endif // DBG // // Override the minimum UpdateServerStatus interval so that we can // get information on any local public address changes due to the // possible loss of a server on this interface. // this->m_dwFlags |= NATHELPUPNPOBJ_DEVICECHANGED; // // Since there was a change in the network, go back to polling // relatively quickly. // this->ResetNextPollInterval(); // // Forcefully mark the UPnP gateway device as disconnected. // if (pDevice->GetUPnPDevice() != NULL) { this->ClearDevicesUPnPDevice(pDevice); } // // Mark all ports that were registered to this device as unowned // by putting them into the wildcard list. First unmap them from // the firewall. // pBilinkRegPort = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilinkRegPort != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilinkRegPort->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilinkRegPort); pBilinkRegPort = pBilinkRegPort->GetNext(); DPFX(DPFPREP, 1, "Registered port 0x%p's device went away, marking as unowned.", pRegisteredPort); #ifndef DPNBUILD_NOHNETFWAPI // // Even though the device is gone, we can still remove the // firewall mapping. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { // // Unmap the port. // // Alert the user since this is unexpected. // hr = this->UnmapPortOnLocalHNetFirewall(pRegisteredPort, TRUE, TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't unmap registered port 0x%p from device 0x%p's firewall (err = 0x%lx)! Ignoring.", pRegisteredPort, pDevice, hr); pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); // // Continue anyway. // hr = DPNH_OK; } } pRegisteredPort->NoteNotHNetFirewallPortUnavailable(); #endif // ! DPNBUILD_NOHNETFWAPI DNASSERT(! pRegisteredPort->HasUPnPPublicAddresses()); DNASSERT(! pRegisteredPort->IsUPnPPortUnavailable()); pRegisteredPort->ClearDeviceOwner(); pRegisteredPort->m_blDeviceList.RemoveFromList(); pRegisteredPort->m_blDeviceList.InsertBefore(&this->m_blUnownedPorts); // // The user doesn't directly need to be informed. If the ports // previously had public addresses, the ADDRESSESCHANGED flag // would have already been set by ClearDevicesUPnPDevice. If // they didn't have ports with public addresses, then the user // won't see any difference and thus ADDRESSESCHANGED wouldn't // need to be set. // } #ifndef DPNBUILD_NOHNETFWAPI // // If we used the HomeNet firewall API to open a hole for UPnP // discovery multicasts, close it. // if (pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()) { hr = this->CloseDevicesUPnPDiscoveryPort(pDevice, NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't close device 0x%p's UPnP discovery socket's port on firewall (err = 0x%lx)! Ignoring.", pDevice, hr); // // Continue... // pDevice->NoteNotUPnPDiscoverySocketMappedOnHNetFirewall(); hr = DPNH_OK; } } #endif // ! DPNBUILD_NOHNETFWAPI pDevice->m_blList.RemoveFromList(); // // Close the socket, if we had one. // if (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) { this->m_pfnclosesocket(pDevice->GetUPnPDiscoverySocket()); pDevice->SetUPnPDiscoverySocket(INVALID_SOCKET); } delete pDevice; } } // // Search for all returned devices in our existing list, and add new // entries for each one that we didn't already know about. // for(dwTemp = 0; dwTemp < dwNumAddresses; dwTemp++) { fFound = FALSE; pBilinkDevice = this->m_blDevices.GetNext(); while (pBilinkDevice != &this->m_blDevices) { DNASSERT(! pBilinkDevice->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilinkDevice); pBilinkDevice = pBilinkDevice->GetNext(); if (pDevice->GetLocalAddressV4() == painaddrAddresses[dwTemp].S_un.S_addr) { fFound = TRUE; break; } } if (! fFound) { // // We didn't know about this device. Create a new object. // pDevice = new CDevice(painaddrAddresses[dwTemp].S_un.S_addr); if (pDevice == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } fDeviceCreated = TRUE; #ifdef DBG DPFX(DPFPREP, 1, "Found new device %u.%u.%u.%u, (object = 0x%p).", painaddrAddresses[dwTemp].S_un.S_un_b.s_b1, painaddrAddresses[dwTemp].S_un.S_un_b.s_b2, painaddrAddresses[dwTemp].S_un.S_un_b.s_b3, painaddrAddresses[dwTemp].S_un.S_un_b.s_b4, pDevice); this->m_dwNumDeviceAdds++; #endif // DBG // // Override the minimum UpdateServerStatus interval so that we can // get information on this new device. // this->m_dwFlags |= NATHELPUPNPOBJ_DEVICECHANGED; // // Since there was a change in the network, go back to polling // relatively quickly. // this->ResetNextPollInterval(); // // Create the UPnP discovery socket, if we're allowed. // if (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) { ZeroMemory(&saddrinTemp, sizeof(saddrinTemp)); saddrinTemp.sin_family = AF_INET; saddrinTemp.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); sTemp = this->CreateSocket(&saddrinTemp, SOCK_DGRAM, IPPROTO_UDP); if (sTemp == INVALID_SOCKET) { DPFX(DPFPREP, 0, "Couldn't create a UPnP discovery socket! Ignoring address (and destroying device 0x%p).", pDevice); // // Get rid of the device. // delete pDevice; pDevice = NULL; // // Forget about device in case of failure later. // fDeviceCreated = FALSE; // // Move to next address. // continue; } // // Sanity check that we didn't lose the device address. // DNASSERT(saddrinTemp.sin_addr.S_un.S_addr == pDevice->GetLocalAddressV4()); DPFX(DPFPREP, 4, "Device 0x%p UPnP discovery socket 0x%p (%u.%u.%u.%u:%u) created.", pDevice, sTemp, saddrinTemp.sin_addr.S_un.S_un_b.s_b1, saddrinTemp.sin_addr.S_un.S_un_b.s_b2, saddrinTemp.sin_addr.S_un.S_un_b.s_b3, saddrinTemp.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinTemp.sin_port)); pDevice->SetUPnPDiscoverySocketPort(saddrinTemp.sin_port); // // Transfer ownership of the socket to the device. // pDevice->SetUPnPDiscoverySocket(sTemp); sTemp = INVALID_SOCKET; DPFX(DPFPREP, 8, "Device 0x%p got assigned UPnP socket 0x%p.", pDevice, pDevice->GetUPnPDiscoverySocket()); } #ifndef DPNBUILD_NOHNETFWAPI if (this->m_dwFlags & NATHELPUPNPOBJ_USEHNETFWAPI) { // // Check if the local firewall is enabled. // hr = this->CheckForLocalHNetFirewallAndMapPorts(pDevice, NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't check for local HNet firewall and map ports (err = 0x%lx)! Continuing.", hr); DNASSERT(! pDevice->IsHNetFirewalled()); hr = DPNH_OK; } } else { // // Not using firewall traversal. // } #endif // ! DPNBUILD_NOHNETFWAPI // // Add the device to our known list. // pDevice->m_blList.InsertBefore(&this->m_blDevices); // // Inform the caller if they care. // if (pfFoundNewDevices != NULL) { (*pfFoundNewDevices) = TRUE; } // // Forget about device in case of failure later. // fDeviceCreated = FALSE; } } // // If we got some very weird failures and ended up here without any // devices, complain to management (or the caller of this function, that's // probably more convenient). // if (this->m_blDevices.IsEmpty()) { DPFX(DPFPREP, 0, "No usable devices, cannot proceed!", 0); DNASSERTX(! "No usable devices!", 2); hr = DPNHERR_GENERIC; goto Failure; } Exit: if (painaddrAddresses != NULL) { DNFree(painaddrAddresses); painaddrAddresses = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (sTemp != INVALID_SOCKET) { this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; } if (fDeviceCreated) { delete pDevice; } goto Exit; } // CNATHelpUPnP::CheckForNewDevices #ifndef DPNBUILD_NOHNETFWAPI #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CheckForLocalHNetFirewallAndMapPorts" //============================================================================= // CNATHelpUPnP::CheckForLocalHNetFirewallAndMapPorts //----------------------------------------------------------------------------- // // Description: Looks for a local HomeNet API aware firewall, and ensures // there are mappings for each of the device's registered ports, // if a firewall is found. // // If any registered port (except pDontAlertRegisteredPort if // not NULL) gets mapped, then it will trigger an address update // alert the next time the user calls GetCaps. // // The main object lock is assumed to be held. It will be // converted into the long lock for the duration of this function. // // Arguments: // CDevice * pDevice - Pointer to device to check. // CRegisteredPort * pDontAlertRegisteredPort - Pointer to registered port // that should not trigger an // address update alert, or // NULL. // // Returns: HRESULT // DPNH_OK - Search completed successfully. There may or may not // be a firewall. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CheckForLocalHNetFirewallAndMapPorts(CDevice * const pDevice, CRegisteredPort * const pDontAlertRegisteredPort) { HRESULT hr = DPNH_OK; BOOL fSwitchedToLongLock = FALSE; BOOL fUninitializeCOM = FALSE; IHNetCfgMgr * pHNetCfgMgr = NULL; IHNetConnection * pHNetConnection = NULL; CBilink * pBilink; CRegisteredPort * pRegisteredPort; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p)", this, pDevice, pDontAlertRegisteredPort); // // If this is the loopback address, don't bother trying to map anything. // if (pDevice->GetLocalAddressV4() == NETWORKBYTEORDER_INADDR_LOOPBACK) { DPFX(DPFPREP, 7, "No firewall behavior necessary with loopback device 0x%p.", pDevice); goto Exit; } // // If we don't have IPHLPAPI or RASAPI32, we can't do anything (and // shouldn't need to). // if ((this->m_hIpHlpApiDLL == NULL) || (this->m_hRasApi32DLL == NULL)) { DPFX(DPFPREP, 7, "Didn't load IPHLPAPI and/or RASAPI32, not getting HNet interfaces for device 0x%p.", pDevice); goto Exit; } // // Using the HomeNet API (particularly the out-of-proc COM calls) during // stress is really, really, painfully slow. Since we have one global lock // the controls everything, other threads may be sitting for an equally // long time... so long, in fact, that the critical section timeout fires // and we get a false stress hit. So we have a sneaky workaround to // prevent that from happening while still maintaining ownership of the // object. // this->SwitchToLongLock(); fSwitchedToLongLock = TRUE; // // Try to initialize COM if we weren't instantiated through COM. It may // have already been initialized in a different mode, which is okay. As // long as it has been initialized somehow, we're fine. // if (this->m_dwFlags & NATHELPUPNPOBJ_NOTCREATEDWITHCOM) { hr = CoInitializeEx(NULL, (COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)); switch (hr) { case S_OK: { // // Success, that's good. Cleanup when we're done. // DPFX(DPFPREP, 8, "Successfully initialized COM."); fUninitializeCOM = TRUE; break; } case S_FALSE: { // // Someone else already initialized COM, but that's okay. // Cleanup when we're done. // DPFX(DPFPREP, 8, "Initialized COM (again)."); fUninitializeCOM = TRUE; break; } case RPC_E_CHANGED_MODE: { // // Someone else already initialized COM in a different mode. // It should be okay, but we don't have to balance the CoInit // call with a CoUninit. // DPFX(DPFPREP, 8, "Didn't initialize COM, already initialized in a different mode."); break; } default: { // // Hmm, something else is going on. We can't handle that. // DPFX(DPFPREP, 0, "Initializing COM failed (err = 0x%lx)!", hr); goto Failure; break; } } } else { DPFX(DPFPREP, 8, "Object was instantiated through COM, no need to initialize COM."); } // // Try creating the main HNet manager object. // hr = CoCreateInstance(CLSID_HNetCfgMgr, NULL, CLSCTX_INPROC_SERVER, IID_IHNetCfgMgr, (PVOID*) (&pHNetCfgMgr)); if (hr != S_OK) { DPFX(DPFPREP, 1, "Couldn't create IHNetCfgMgr interface for device 0x%p (err = 0x%lx), assuming firewall control interface unavailable.", pDevice, hr); hr = DPNH_OK; goto Exit; } // // We created the IHNetCfgMgr object as in-proc, so there's no proxy that // requires security settings. // //SETDEFAULTPROXYBLANKET(pHNetCfgMgr); // // Get the HNetConnection object for this device. // hr = this->GetIHNetConnectionForDeviceIfFirewalled(pDevice, pHNetCfgMgr, &pHNetConnection); if (hr != DPNH_OK) { DPFX(DPFPREP, 1, "Couldn't get IHNetConnection interface for device 0x%p (err = 0x%lx), assuming firewall not enabled.", pDevice, hr); // // If the device was previously firewalled, we need to clear our info. // if (pDevice->IsHNetFirewalled()) { DPFX(DPFPREP, 2, "Firewall is no longer enabled for device 0x%p.", pDevice); // // Since there was a change in the network, go back to polling // relatively quickly. // this->ResetNextPollInterval(); DNASSERT(pDevice->HasCheckedForFirewallAvailability()); pBilink = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilink != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); // // Unmap items mapped on the firewall. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { DPFX(DPFPREP, 1, "Unmapping registered port 0x%p from device 0x%p's disappearing firewall.", pRegisteredPort, pDevice); hr = this->UnmapPortOnLocalHNetFirewallInternal(pRegisteredPort, TRUE, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't unmap registered port 0x%p from device 0x%p's firewall (err = 0x%lx)! Ignoring.", pRegisteredPort, pDevice, hr); pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); // // Continue anyway. // hr = DPNH_OK; } // // Alert the user. // this->m_dwFlags |= NATHELPUPNPOBJ_ADDRESSESCHANGED; } else { DPFX(DPFPREP, 1, "Registered port 0x%p was not mapped on device 0x%p's disappearing firewall, assuming being called within RegisterPorts.", pRegisteredPort, pDevice); } // // Go to next port. // pBilink = pBilink->GetNext(); } // // If we used the HomeNet firewall API to open a hole for UPnP // discovery multicasts, unmap that, too. // if (pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()) { DPFX(DPFPREP, 0, "Device 0x%p's UPnP discovery socket's forcefully unmapped from disappearing firewall.", pDevice); hr = this->CloseDevicesUPnPDiscoveryPort(pDevice, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't close device 0x%p's UPnP discovery socket's port on firewall (err = 0x%lx)! Ignoring.", pDevice, hr); // // Continue... // pDevice->NoteNotUPnPDiscoverySocketMappedOnHNetFirewall(); hr = DPNH_OK; } } // // Turn off the flag now that all registered ports have been // removed. // pDevice->NoteNotHNetFirewalled(); } else { if (! pDevice->HasCheckedForFirewallAvailability()) { // // The firewall is not enabled. // DPFX(DPFPREP, 2, "Firewall is not enabled for device 0x%p.", pDevice); pDevice->NoteCheckedForFirewallAvailability(); // // Since it is possible to remove mappings even without the // firewall enabled, we can be courteous and unmap any stale // entries left by previous app crashes when the firewall was // still enabled. // // // Pretend that it currently had been firewalled. // pDevice->NoteHNetFirewalled(); // // Cleanup the mappings. // hr = this->CleanupInactiveFirewallMappings(pDevice, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed cleaning up inactive firewall mappings with device 0x%p (firewall not initially enabled)!", pDevice); goto Failure; } // // Turn off the flag we temporarily enabled while clearing // the mappings. // pDevice->NoteNotHNetFirewalled(); } else { // // The firewall is still not enabled. // DPFX(DPFPREP, 2, "Firewall is still not enabled for device 0x%p.", pDevice); } } hr = DPNH_OK; goto Exit; } // // If firewalling is enabled now, and wasn't before, we need to map all the // existing ports. If it had been, we're fine. // if (! pDevice->IsHNetFirewalled()) { DPFX(DPFPREP, 2, "Firewall is now enabled for device 0x%p.", pDevice); pDevice->NoteCheckedForFirewallAvailability(); pDevice->NoteHNetFirewalled(); // // Since there was a change in the network, go back to polling // relatively quickly. // this->ResetNextPollInterval(); // // If we're allowed, we need to try opening a hole so that we can // receive responses from device discovery multicasts. We'll // ignore failures, since this is only to support the funky case of // enabling firewall behind a NAT. // if ((g_fMapUPnPDiscoverySocket) && (pDevice->GetLocalAddressV4() != NETWORKBYTEORDER_INADDR_LOOPBACK) && (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP)) { hr = this->OpenDevicesUPnPDiscoveryPort(pDevice, pHNetCfgMgr, pHNetConnection); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't open device 0x%p's UPnP discovery socket's port on firewall (err = 0x%lx)! Ignoring, NAT may be undetectable.", pDevice, hr); hr = DPNH_OK; // // Continue... // } } else { DPFX(DPFPREP, 3, "Not opening device 0x%p's UPnP discovery port (domap = %i, loopback = %i, upnp = %i).", pDevice, g_fMapUPnPDiscoverySocket, ((pDevice->GetLocalAddressV4() != NETWORKBYTEORDER_INADDR_LOOPBACK) ? FALSE : TRUE), ((this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) ? TRUE : FALSE)); } // // Try to remove any mappings that were not freed earlier because // we crashed. // hr = this->CleanupInactiveFirewallMappings(pDevice, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed cleaning up inactive firewall mappings with device 0x%p's new firewall!", pDevice); goto Failure; } } else { DPFX(DPFPREP, 2, "Firewall is still enabled for device 0x%p.", pDevice); DNASSERT(pDevice->HasCheckedForFirewallAvailability()); // // Try to map the discovery socket if it hasn't been (and we're allowed // & supposed to). // if ((g_fMapUPnPDiscoverySocket) && (! pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()) && (pDevice->GetLocalAddressV4() != NETWORKBYTEORDER_INADDR_LOOPBACK) && (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP)) { hr = this->OpenDevicesUPnPDiscoveryPort(pDevice, pHNetCfgMgr, pHNetConnection); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't open device 0x%p's UPnP discovery socket's port on firewall (err = 0x%lx)! Ignoring, NAT may be undetectable.", pDevice, hr); hr = DPNH_OK; // // Continue... // } } else { DPFX(DPFPREP, 3, "Not opening device 0x%p's UPnP discovery port (domap = %i, already = %i, loopback = %i, upnp = %i).", pDevice, g_fMapUPnPDiscoverySocket, pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall(), ((pDevice->GetLocalAddressV4() != NETWORKBYTEORDER_INADDR_LOOPBACK) ? FALSE : TRUE), ((this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) ? TRUE : FALSE)); } } // // Map all the ports that haven't been yet. // hr = this->MapUnmappedPortsOnLocalHNetFirewall(pDevice, pHNetCfgMgr, pHNetConnection, pDontAlertRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't map ports on device 0x%p's new firewall (err = 0x%lx)!", pDevice, hr); goto Failure; } DNASSERT(hr == DPNH_OK); Exit: if (pHNetConnection != NULL) { pHNetConnection->Release(); pHNetConnection = NULL; } if (pHNetCfgMgr != NULL) { pHNetCfgMgr->Release(); pHNetCfgMgr = NULL; } if (fUninitializeCOM) { DPFX(DPFPREP, 8, "Uninitializing COM."); CoUninitialize(); fUninitializeCOM = FALSE; } if (fSwitchedToLongLock) { this->SwitchFromLongLock(); fSwitchedToLongLock = FALSE; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: // // Ensure that the device is not considered to be firewalled. // pDevice->NoteNotUPnPDiscoverySocketMappedOnHNetFirewall(); pDevice->NoteNotHNetFirewalled(); // // Make sure no registered ports are marked as firewalled either. // pBilink = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilink != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); if (pRegisteredPort->IsMappedOnHNetFirewall()) { DPFX(DPFPREP, 1, "Registered port 0x%p forcefully marked as not mapped on HomeNet firewall.", pRegisteredPort); pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); } pBilink = pBilink->GetNext(); } goto Exit; } // CNATHelpUPnP::CheckForLocalHNetFirewallAndMapPorts #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetIHNetConnectionForDeviceIfFirewalled" //============================================================================= // CNATHelpUPnP::GetIHNetConnectionForDeviceIfFirewalled //----------------------------------------------------------------------------- // // Description: Returns an IHNetConnection interface for the given device. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device whose // IHNetConnection interface // should be retrieved. // IHNetCfgMgr * pHNetCfgMgr - IHNetCfgMgr interface to use. // IHNetConnection ** ppHNetConnection - Place to store IHetConnection // interface retrieved. // // Returns: HRESULT // DPNH_OK - Interface retrieved successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::GetIHNetConnectionForDeviceIfFirewalled(CDevice * const pDevice, IHNetCfgMgr * const pHNetCfgMgr, IHNetConnection ** const ppHNetConnection) { HRESULT hr; DWORD dwError; IHNetFirewallSettings * pHNetFirewallSettings = NULL; IEnumHNetFirewalledConnections * pEnumHNetFirewalledConnections = NULL; IHNetFirewalledConnection * pHNetFirewalledConnection = NULL; ULONG ulNumFound; IHNetConnection * pHNetConnection = NULL; HNET_CONN_PROPERTIES * pHNetConnProperties; BOOL fLanConnection; IN_ADDR inaddrTemp; TCHAR tszDeviceIPAddress[16]; // "nnn.nnn.nnn.nnn" + NULL termination BOOL fHaveDeviceGUID = FALSE; TCHAR tszGuidDevice[GUID_STRING_LENGTH + 1]; // include NULL termination TCHAR tszGuidHNetConnection[GUID_STRING_LENGTH + 1]; // include NULL termination GUID * pguidHNetConnection = NULL; WCHAR * pwszPhonebookPath = NULL; WCHAR * pwszName = NULL; HRASCONN hrasconn; RASPPPIP raspppip; DWORD dwSize; DPFX(DPFPREP, 6, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p)", this, pDevice, pHNetCfgMgr, ppHNetConnection); // // Convert the IP address right away. We use it frequently so there's no // sense in continually regenerating it. // inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); wsprintf(tszDeviceIPAddress, _T("%u.%u.%u.%u"), inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); // // Here is what we're going to do in this function: // // IHNetCfgMgr::QueryInterface for IHNetFirewallSettings // IHNetFirewallSettings::EnumFirewalledConnections // IHNetFirewalledConnection::QueryInterface for IHNetConnection // get the IHNetConnection's HNET_CONN_PROPERTIES // if HNET_CONN_PROPERTIES.fLanConnection // IHNetConnection::GetGuid() // if GUID matches IPHLPAPI GUID // We've got the one we want, we're done // else // Keep looping // else // IHNetConnection::GetRasPhonebookPath and IHNetConnection::GetName to pass into RasGetEntryHrasconnW as pszPhonebook and pszEntry, respectively // if got HRASCONN // RasGetProjectionInfo // if IP matches the IP we're looking for // We've got the one we want, we're done // else // Keep looping // else // RAS entry is not dialed, keep looping // if didn't find object // it's not firewalled // // // Get the firewall settings object. // hr = pHNetCfgMgr->QueryInterface(IID_IHNetFirewallSettings, (PVOID*) (&pHNetFirewallSettings)); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't query for IHNetFirewallSettings interface (err = 0x%lx)!", hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetFirewallSettings); // // Get the firewalled connections enumeration via IHNetFirewallSettings. // hr = pHNetFirewallSettings->EnumFirewalledConnections(&pEnumHNetFirewalledConnections); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't query for IHNetFirewallSettings interface (err = 0x%lx)!", hr); // // Make sure we don't try to release a bogus pointer in case it got // set. // pEnumHNetFirewalledConnections = NULL; goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pEnumHNetFirewalledConnections); // // Don't need the IHNetFirewallSettings interface anymore. // pHNetFirewallSettings->Release(); pHNetFirewallSettings = NULL; // // Keep looping until we find the item or run out of items. // do { hr = pEnumHNetFirewalledConnections->Next(1, &pHNetFirewalledConnection, &ulNumFound); if (FAILED(hr)) { DPFX(DPFPREP, 0, "Couldn't get next connection (err = 0x%lx)!", hr); goto Failure; } // // If there aren't any more items, bail. // if (ulNumFound == 0) { // // pEnumHNetFirewalledConnections->Next might have returned // S_FALSE. // hr = DPNH_OK; break; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetFirewalledConnection); // // Get the IHNetConnection interface. // hr = pHNetFirewalledConnection->QueryInterface(IID_IHNetConnection, (PVOID*) (&pHNetConnection)); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't query for IHNetConnection interface (err = 0x%lx)!", hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetConnection); // // We don't need the firewalled connection object anymore. // pHNetFirewalledConnection->Release(); pHNetFirewalledConnection = NULL; // // Get the internal properties for this adapter. // hr = pHNetConnection->GetProperties(&pHNetConnProperties); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get home net connection properties (err = 0x%lx)!", hr); goto Failure; } // // Be somewhat picky about whether adapters returned by // IEnumHNetFirewalledConnections actually be firewalled. // DNASSERTX(pHNetConnProperties->fFirewalled, 2); fLanConnection = pHNetConnProperties->fLanConnection; // // Free the properties buffer. // CoTaskMemFree(pHNetConnProperties); //pHNetConnProperties = NULL; // // Now if it's a LAN connection, see if the GUID matches the one // returned by IPHLPAPI. // If it's a RAS connection, see if this phonebook entry is dialed and // has the right IP address. // if (fLanConnection) { // // LAN case. If we haven't already retrieved the device's GUID, do // so now. // if (! fHaveDeviceGUID) { hr = this->GetIPAddressGuidString(tszDeviceIPAddress, tszGuidDevice); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get device 0x%p's GUID (err = 0x%lx)!", hr); goto Failure; } fHaveDeviceGUID = TRUE; } // // Get the HNetConnection object's GUID. // hr = pHNetConnection->GetGuid(&pguidHNetConnection); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get HNetConnection 0x%p's GUID (err = 0x%lx)!", pHNetConnection, hr); goto Failure; } // // Convert the GUID into a string. // wsprintf(tszGuidHNetConnection, _T("{%-08.8X-%-04.4X-%-04.4X-%02.2X%02.2X-%02.2X%02.2X%02.2X%02.2X%02.2X%02.2X}"), pguidHNetConnection->Data1, pguidHNetConnection->Data2, pguidHNetConnection->Data3, pguidHNetConnection->Data4[0], pguidHNetConnection->Data4[1], pguidHNetConnection->Data4[2], pguidHNetConnection->Data4[3], pguidHNetConnection->Data4[4], pguidHNetConnection->Data4[5], pguidHNetConnection->Data4[6], pguidHNetConnection->Data4[7]); CoTaskMemFree(pguidHNetConnection); pguidHNetConnection = NULL; #ifdef DBG // // Attempt to get the HNetConnection object's name for debugging // purposes. // hr = pHNetConnection->GetName(&pwszName); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get HNetConnection 0x%p name (err = 0x%lx)!", pHNetConnection, hr); goto Failure; } #endif // DBG // // See if we found the object we need. // if (_tcsicmp(tszGuidHNetConnection, tszGuidDevice) == 0) { DPFX(DPFPREP, 7, "Matched IHNetConnection object 0x%p \"%ls\" to device 0x%p (LAN GUID %s).", pHNetConnection, pwszName, pDevice, tszGuidHNetConnection); // // Transfer reference to caller. // (*ppHNetConnection) = pHNetConnection; pHNetConnection = NULL; #ifdef DBG CoTaskMemFree(pwszName); pwszName = NULL; #endif // DBG // // We're done here. // hr = DPNH_OK; goto Exit; } DPFX(DPFPREP, 7, "Non-matching IHNetConnection 0x%p \"%ls\"", pHNetConnection, pwszName); DPFX(DPFPREP, 7, "\t(LAN GUID %s <> %s).", tszGuidHNetConnection, tszGuidDevice); #ifdef DBG CoTaskMemFree(pwszName); pwszName = NULL; #endif // DBG } else { // // RAS case. // DNASSERT(this->m_hRasApi32DLL != NULL); // // Get the HNetConnection object's phonebook path. // hr = pHNetConnection->GetRasPhonebookPath(&pwszPhonebookPath); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get HNetConnection's RAS phonebook path (err = 0x%lx)!", hr); goto Failure; } // // Get the HNetConnection object's name. // hr = pHNetConnection->GetName(&pwszName); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get HNetConnection's name (err = 0x%lx)!", hr); goto Failure; } // // Look for an active RAS connection from that phonebook with that // name. // dwError = this->m_pfnRasGetEntryHrasconnW(pwszPhonebookPath, pwszName, &hrasconn); if (dwError != 0) { // // It's probably ERROR_NO_CONNECTION (668). // DPFX(DPFPREP, 1, "Couldn't get entry's active RAS connection (err = %u), assuming not dialed", dwError); DPFX(DPFPREP, 1, "\tname \"%ls\", phonebook \"%ls\".", pwszName, pwszPhonebookPath); } else { // // Get the IP address. // ZeroMemory(&raspppip, sizeof(raspppip)); raspppip.dwSize = sizeof(raspppip); dwSize = sizeof(raspppip); dwError = this->m_pfnRasGetProjectionInfo(hrasconn, RASP_PppIp, &raspppip, &dwSize); if (dwError != 0) { DPFX(DPFPREP, 0, "Couldn't get RAS connection's IP information (err = %u)!", dwError); hr = DPNHERR_GENERIC; goto Failure; } // // See if we found the object we need. // if (_tcsicmp(raspppip.szIpAddress, tszDeviceIPAddress) == 0) { DPFX(DPFPREP, 7, "Matched IHNetConnection object 0x%p to device 0x%p (RAS IP %s)", pHNetConnection, pDevice, raspppip.szIpAddress); DPFX(DPFPREP, 7, "\tname \"%ls\", phonebook \"%ls\".", pwszName, pwszPhonebookPath); // // Transfer reference to caller. // (*ppHNetConnection) = pHNetConnection; pHNetConnection = NULL; // // We're done here. // hr = DPNH_OK; goto Exit; } DPFX(DPFPREP, 7, "Non-matching IHNetConnection 0x%p (RAS IP %s != %s)", pHNetConnection, raspppip.szIpAddress, tszDeviceIPAddress); DPFX(DPFPREP, 7, "\tname \"%ls\", phonebook \"%ls\").", pwszName, pwszPhonebookPath); } CoTaskMemFree(pwszPhonebookPath); pwszPhonebookPath = NULL; CoTaskMemFree(pwszName); pwszName = NULL; } // // If we're here, we pHNetConnection is not the one we're looking for. // pHNetConnection->Release(); pHNetConnection = NULL; } while (TRUE); // // If we're here, then we didn't find a matching firewall connection. // DPFX(DPFPREP, 3, "Didn't find device 0x%p in list of firewalled connections.", pDevice); hr = DPNHERR_GENERIC; Exit: if (pEnumHNetFirewalledConnections != NULL) { pEnumHNetFirewalledConnections->Release(); pEnumHNetFirewalledConnections = NULL; } DPFX(DPFPREP, 6, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pwszName != NULL) { CoTaskMemFree(pwszName); pwszName = NULL; } if (pwszPhonebookPath == NULL) { CoTaskMemFree(pwszPhonebookPath); pwszPhonebookPath = NULL; } if (pHNetConnection != NULL) { pHNetConnection->Release(); pHNetConnection = NULL; } if (pHNetFirewalledConnection != NULL) { pHNetFirewalledConnection->Release(); pHNetFirewalledConnection = NULL; } if (pHNetFirewallSettings != NULL) { pHNetFirewallSettings->Release(); pHNetFirewallSettings = NULL; } goto Exit; } // CNATHelpUPnP::GetIHNetConnectionForDeviceIfFirewalled #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetIPAddressGuidString" //============================================================================= // CNATHelpUPnP::GetIPAddressGuidString //----------------------------------------------------------------------------- // // Description: Retrieves the IPHLPAPI assigned GUID (in string format) for // the given IP address string. ptszGuidString must be able to // hold GUID_STRING_LENGTH + 1 characters. // // The object lock is assumed to be held. // // Arguments: // TCHAR * tszDeviceIPAddress - IP address string to lookup. // TCHAR * ptszGuidString - Place to store device's GUID string. // // Returns: HRESULT // DPNH_OK - Interface retrieved successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::GetIPAddressGuidString(const TCHAR * const tszDeviceIPAddress, TCHAR * const ptszGuidString) { HRESULT hr = DPNH_OK; DWORD dwError; PIP_ADAPTER_INFO pAdaptersBuffer = NULL; ULONG ulSize; PIP_ADAPTER_INFO pAdapterInfo; PIP_ADDR_STRING pIPAddrString; char * pszAdapterGuid = NULL; #ifdef UNICODE char szDeviceIPAddress[16]; // "nnn.nnn.nnn.nnn" + NULL termination #endif // UNICODE #ifdef DBG char szIPList[256]; char * pszCurrentIP; #endif // DBG DPFX(DPFPREP, 6, "(0x%p) Parameters: (\"%s\", 0x%p)", this, tszDeviceIPAddress, ptszGuidString); DNASSERT(this->m_hIpHlpApiDLL != NULL); // // Keep trying to get the list of adapters until we get ERROR_SUCCESS or a // legitimate error (other than ERROR_BUFFER_OVERFLOW or // ERROR_INSUFFICIENT_BUFFER). // ulSize = 0; do { dwError = this->m_pfnGetAdaptersInfo(pAdaptersBuffer, &ulSize); if (dwError == ERROR_SUCCESS) { // // We succeeded, we should be set. But make sure there are // adapters for us to use. // if (ulSize < sizeof(IP_ADAPTER_INFO)) { DPFX(DPFPREP, 0, "Getting adapters info succeeded but didn't return any valid adapters (%u < %u)!", ulSize, sizeof(IP_ADAPTER_INFO)); hr = DPNHERR_GENERIC; goto Failure; } break; } if ((dwError != ERROR_BUFFER_OVERFLOW) && (dwError != ERROR_INSUFFICIENT_BUFFER)) { DPFX(DPFPREP, 0, "Unable to get adapters info (error = 0x%lx)!", dwError); hr = DPNHERR_GENERIC; goto Failure; } // // We need more adapter space. Make sure there are adapters for us to // use. // if (ulSize < sizeof(IP_ADAPTER_INFO)) { DPFX(DPFPREP, 0, "Getting adapters info didn't return any valid adapters (%u < %u)!", ulSize, sizeof(IP_ADAPTER_INFO)); hr = DPNHERR_GENERIC; goto Failure; } // // If we previously had a buffer, free it. // if (pAdaptersBuffer != NULL) { DNFree(pAdaptersBuffer); } // // Allocate the buffer. // pAdaptersBuffer = (PIP_ADAPTER_INFO) DNMalloc(ulSize); if (pAdaptersBuffer == NULL) { DPFX(DPFPREP, 0, "Unable to allocate memory for adapters info!"); hr = DPNHERR_OUTOFMEMORY; goto Failure; } } while (TRUE); #ifdef UNICODE STR_jkWideToAnsi(szDeviceIPAddress, tszDeviceIPAddress, 16); szDeviceIPAddress[15] = 0; // ensure it's NULL terminated #endif // UNICODE // // Now find the device in the adapter list returned. Loop through all // adapters. // pAdapterInfo = pAdaptersBuffer; while (pAdapterInfo != NULL) { #ifdef DBG // // Initialize IP address list string. // szIPList[0] = '\0'; pszCurrentIP = szIPList; #endif // DBG // // Loop through all addresses for this adapter looking for the one for // the device we have bound. // pIPAddrString = &pAdapterInfo->IpAddressList; while (pIPAddrString != NULL) { #ifdef DBG int iStrLen; // // Copy the IP address string (if there's enough room), then tack // on a space and NULL terminator. // iStrLen = strlen(pIPAddrString->IpAddress.String); if ((pszCurrentIP + iStrLen + 2) < (szIPList + sizeof(szIPList))) { memcpy(pszCurrentIP, pIPAddrString->IpAddress.String, iStrLen); pszCurrentIP += iStrLen; (*pszCurrentIP) = ' '; pszCurrentIP++; (*pszCurrentIP) = '\0'; pszCurrentIP++; } #endif // DBG #ifdef UNICODE if (strcmp(pIPAddrString->IpAddress.String, szDeviceIPAddress) == 0) #else // ! UNICODE if (strcmp(pIPAddrString->IpAddress.String, tszDeviceIPAddress) == 0) #endif // ! UNICODE { DPFX(DPFPREP, 8, "Found %s under adapter index %u (\"%hs\").", tszDeviceIPAddress, pAdapterInfo->Index, pAdapterInfo->Description); DNASSERT(pszAdapterGuid == NULL); pszAdapterGuid = pAdapterInfo->AdapterName; // // Drop out of the loop in retail, keep going in debug. // #ifndef DBG break; #endif // ! DBG } pIPAddrString = pIPAddrString->Next; } // // Drop out of the loop in retail, print this entry and keep going in // debug. // #ifdef DBG DPFX(DPFPREP, 7, "Adapter index %u IPs = %hs, %hs, \"%hs\".", pAdapterInfo->Index, szIPList, pAdapterInfo->AdapterName, pAdapterInfo->Description); #else // ! DBG if (pszAdapterGuid != NULL) { break; } #endif // ! DBG pAdapterInfo = pAdapterInfo->Next; } // // pszAdapterGuid will be NULL if we never found the device. // if (pszAdapterGuid == NULL) { DPFX(DPFPREP, 0, "Did not find adapter with matching address for address %s!", tszDeviceIPAddress); hr = DPNHERR_GENERIC; goto Failure; } // // Copy the adapter GUID string to the buffer supplied. // #ifdef UNICODE STR_jkAnsiToWide(ptszGuidString, pszAdapterGuid, (GUID_STRING_LENGTH + 1)); #else // ! UNICODE strncpy(ptszGuidString, pszAdapterGuid, (GUID_STRING_LENGTH + 1)); #endif // ! UNICODE ptszGuidString[GUID_STRING_LENGTH] = 0; // ensure it's NULL terminated Exit: if (pAdaptersBuffer != NULL) { DNFree(pAdaptersBuffer); pAdaptersBuffer = NULL; } DPFX(DPFPREP, 6, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::GetIPAddressGuidString #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::OpenDevicesUPnPDiscoveryPort" //============================================================================= // CNATHelpUPnP::OpenDevicesUPnPDiscoveryPort //----------------------------------------------------------------------------- // // Description: Maps the UPnP discovery socket's port if a firewall is // found. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device whose port should // be opened. // IHNetCfgMgr * pHNetCfgMgr - Pointer to IHNetCfgMgr interface to // use. // IHNetConnection * pHNetConnection - Pointer to IHNetConnection interface // for the given device. // // Returns: HRESULT // DPNH_OK - Mapping completed successfully. There may or may not // be a firewall. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::OpenDevicesUPnPDiscoveryPort(CDevice * const pDevice, IHNetCfgMgr * const pHNetCfgMgr, IHNetConnection * const pHNetConnection) { HRESULT hr = DPNH_OK; CRegisteredPort * pRegisteredPort = NULL; SOCKADDR_IN saddrinTemp; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p)", this, pDevice, pHNetCfgMgr, pHNetConnection); DNASSERT(pDevice->IsHNetFirewalled()); // // Create a fake UDP registered port to map. // pRegisteredPort = new CRegisteredPort(0, 0); if (pRegisteredPort == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pRegisteredPort->MakeDeviceOwner(pDevice); ZeroMemory(&saddrinTemp, sizeof(saddrinTemp)); saddrinTemp.sin_family = AF_INET; saddrinTemp.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); saddrinTemp.sin_port = pDevice->GetUPnPDiscoverySocketPort(); DNASSERT(saddrinTemp.sin_port != 0); hr = pRegisteredPort->SetPrivateAddresses(&saddrinTemp, 1); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't set registered port 0x%p's private addresses (err = 0x%lx)!", pRegisteredPort, hr); goto Failure; } // // Map the port. // hr = this->MapPortOnLocalHNetFirewall(pRegisteredPort, pHNetCfgMgr, pHNetConnection, FALSE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't map UPnP discovery socket port (temp regport = 0x%p) on device 0x%p's initial firewall (err = 0x%lx)!", pRegisteredPort, pDevice, hr); goto Failure; } // // If the port was unavailable, we have to give up on supporting the // scenario (firewall enabled behind a NAT). Otherwise, remember the fact // that we mapped the port, and then delete the registered port object. We // will unmap it when we shut down the device. // if (! pRegisteredPort->IsHNetFirewallPortUnavailable()) { DPFX(DPFPREP, 3, "Mapped UPnP discovery socket for device 0x%p on firewall (removing temp regport 0x%p).", pDevice, pRegisteredPort); pDevice->NoteUPnPDiscoverySocketMappedOnHNetFirewall(); // // Clear this to prevent an assert. // pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); } else { DPFX(DPFPREP, 1, "Could not map UPnP discovery socket on firewall for device 0x%p, unable to support an upstream NAT.", pDevice); } pRegisteredPort->ClearPrivateAddresses(); pRegisteredPort->ClearDeviceOwner(); // // Delete the item. // delete pRegisteredPort; pRegisteredPort = NULL; Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pRegisteredPort != NULL) { // // Clear any settings that might cause an assert. // pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); pRegisteredPort->ClearPrivateAddresses(); pRegisteredPort->ClearDeviceOwner(); // // Delete the item. // delete pRegisteredPort; pRegisteredPort = NULL; } goto Exit; } // CNATHelpUPnP::OpenDevicesUPnPDiscoveryPort #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CloseDevicesUPnPDiscoveryPort" //============================================================================= // CNATHelpUPnP::CloseDevicesUPnPDiscoveryPort //----------------------------------------------------------------------------- // // Description: Unmaps the UPnP discovery socket's port from the firewall. // pHNetCfgMgr can be NULL if it has not previously been obtained. // // COM is assumed to have been initialized if pHNetCfgMgr is // not NULL. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device whose port should be // close. // IHNetCfgMgr * pHNetCfgMgr - Pointer to IHNetCfgMgr interface to use, or // NULL if not previously obtained. // // Returns: HRESULT // DPNH_OK - Unmapping completed successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CloseDevicesUPnPDiscoveryPort(CDevice * const pDevice, IHNetCfgMgr * const pHNetCfgMgr) { HRESULT hr = DPNH_OK; CRegisteredPort * pRegisteredPort = NULL; SOCKADDR_IN saddrinTemp; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p)", this, pDevice, pHNetCfgMgr); // // Create a fake UDP registered port to unmap. // pRegisteredPort = new CRegisteredPort(0, 0); if (pRegisteredPort == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pRegisteredPort->MakeDeviceOwner(pDevice); pRegisteredPort->NoteMappedOnHNetFirewall(); ZeroMemory(&saddrinTemp, sizeof(saddrinTemp)); saddrinTemp.sin_family = AF_INET; saddrinTemp.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); saddrinTemp.sin_port = pDevice->GetUPnPDiscoverySocketPort(); DNASSERT(saddrinTemp.sin_port != 0); hr = pRegisteredPort->SetPrivateAddresses(&saddrinTemp, 1); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't set registered port 0x%p's private addresses (err = 0x%lx)!", pRegisteredPort, hr); goto Failure; } // // Unmap the port using the internal method if possible. // if (pHNetCfgMgr != NULL) { hr = this->UnmapPortOnLocalHNetFirewallInternal(pRegisteredPort, TRUE, pHNetCfgMgr); } else { // // Don't alert the user since he/she doesn't know about this port. // hr = this->UnmapPortOnLocalHNetFirewall(pRegisteredPort, TRUE, FALSE); } if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't unmap UPnP discovery socket port (temp regport = 0x%p) on device 0x%p's firewall (err = 0x%lx)!", pRegisteredPort, pDevice, hr); goto Failure; } // // Destroy the registered port object (note that the port mapping still // exists). We will unmap when we shut down the device. // pRegisteredPort->ClearPrivateAddresses(); pRegisteredPort->ClearDeviceOwner(); // // Delete the item. // delete pRegisteredPort; pRegisteredPort = NULL; pDevice->NoteNotUPnPDiscoverySocketMappedOnHNetFirewall(); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pRegisteredPort != NULL) { // // Clear any settings that might cause an assert. // pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); pRegisteredPort->ClearPrivateAddresses(); pRegisteredPort->ClearDeviceOwner(); // // Delete the item. // delete pRegisteredPort; pRegisteredPort = NULL; } goto Exit; } // CNATHelpUPnP::CloseDevicesUPnPDiscoveryPort #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::MapUnmappedPortsOnLocalHNetFirewall" //============================================================================= // CNATHelpUPnP::MapUnmappedPortsOnLocalHNetFirewall //----------------------------------------------------------------------------- // // Description: Maps any ports associated with the given device that have // not been mapped with the local firewall yet. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Device with (new) firewall. // IHNetCfgMgr * pHNetCfgMgr - Pointer to IHNetCfgMgr // interface to use. // IHNetConnection * pHNetConnection - Pointer to IHNetConnection // interface for the given // device. // CRegisteredPort * pDontAlertRegisteredPort - Pointer to registered port // that should not trigger an // address update alert, or // NULL. // // Returns: HRESULT // DPNH_OK - Mapping completed successfully. Note that the ports // may be marked as unavailable. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::MapUnmappedPortsOnLocalHNetFirewall(CDevice * const pDevice, IHNetCfgMgr * const pHNetCfgMgr, IHNetConnection * const pHNetConnection, CRegisteredPort * const pDontAlertRegisteredPort) { HRESULT hr = DPNH_OK; CBilink * pBilink; CRegisteredPort * pRegisteredPort; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, 0x%p)", this, pDevice, pHNetCfgMgr, pHNetConnection, pDontAlertRegisteredPort); DNASSERT(pDevice->IsHNetFirewalled()); // // Loop through all the registered ports associated with the device. // pBilink = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilink != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); pBilink = pBilink->GetNext(); // // If this port has already been mapped, we can skip it. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { DPFX(DPFPREP, 7, "Registered port 0x%p has already been mapped on the firewall for device 0x%p.", pRegisteredPort, pDevice); continue; } // // If this port has already been determined to be unavailable, we can // skip it. // if (pRegisteredPort->IsHNetFirewallPortUnavailable()) { DPFX(DPFPREP, 7, "Registered port 0x%p has already been determined to be unavailable on the firewall for device 0x%p.", pRegisteredPort, pDevice); continue; } DPFX(DPFPREP, 3, "Mapping registered port 0x%p on firewall for device 0x%p.", pRegisteredPort, pDevice); hr = this->MapPortOnLocalHNetFirewall(pRegisteredPort, pHNetCfgMgr, pHNetConnection, ((pRegisteredPort == pDontAlertRegisteredPort) ? FALSE : TRUE)); if (hr != DPNH_OK) { DPFX(DPFPREP, 7, "Failed mapping registered port 0x%p on firewall for device 0x%p.", pRegisteredPort, pDevice); goto Failure; } // // Go to next registered port. // } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::MapUnmappedPortsOnLocalHNetFirewall #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::MapPortOnLocalHNetFirewall" //============================================================================= // CNATHelpUPnP::MapPortOnLocalHNetFirewall //----------------------------------------------------------------------------- // // Description: Maps the given port with the corresponding firewall. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // CRegisteredPort * pRegisteredPort - Port to map. // IHNetCfgMgr * pHNetCfgMgr - Pointer to IHNetCfgMgr interface to // use. // IHNetConnection * pHNetConnection - Pointer to IHNetConnection interface // for the given device. // BOOL fNoteAddressChange - Whether to alert the user of the // address change or not. // // Returns: HRESULT // DPNH_OK - Mapping completed successfully. Note that the ports // may be marked as unavailable. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::MapPortOnLocalHNetFirewall(CRegisteredPort * const pRegisteredPort, IHNetCfgMgr * const pHNetCfgMgr, IHNetConnection * const pHNetConnection, const BOOL fNoteAddressChange) { HRESULT hr = DPNH_OK; CDevice * pDevice; SOCKADDR_IN * pasaddrinPrivate; UCHAR ucProtocolToMatch; ULONG ulNumFound; BOOLEAN fTemp; IHNetProtocolSettings * pHNetProtocolSettings = NULL; IEnumHNetPortMappingProtocols * pEnumHNetPortMappingProtocols = NULL; IHNetPortMappingProtocol ** papHNetPortMappingProtocol = NULL; DWORD dwTemp; BOOL fCreatedCurrentPortMappingProtocol = FALSE; IHNetPortMappingBinding * pHNetPortMappingBinding = NULL; DWORD dwTargetAddressV4; WORD wPort; UCHAR ucProtocol; DWORD dwDescriptionLength; TCHAR tszPort[32]; CRegistry RegObject; WCHAR wszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; DPNHACTIVEFIREWALLMAPPING dpnhafm; BOOLEAN fBuiltIn = FALSE; WCHAR * pwszPortMappingProtocolName = NULL; #ifdef UNICODE TCHAR * ptszDescription = wszDescription; #else // ! UNICODE char szDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; TCHAR * ptszDescription = szDescription; #endif // ! UNICODE DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, %i)", this, pRegisteredPort, pHNetCfgMgr, pHNetConnection, fNoteAddressChange); DNASSERT(! pRegisteredPort->IsMappedOnHNetFirewall()); DNASSERT(! pRegisteredPort->IsHNetFirewallPortUnavailable()); pDevice = pRegisteredPort->GetOwningDevice(); DNASSERT(pDevice != NULL); DNASSERT(pDevice->IsHNetFirewalled()); // // Get a protocol settings interface. // hr = pHNetCfgMgr->QueryInterface(IID_IHNetProtocolSettings, (PVOID*) (&pHNetProtocolSettings)); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get IHNetProtocolSettings interface from IHNetCfgMgr 0x%p (err = 0x%lx)!", pHNetCfgMgr, hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetProtocolSettings); // // Get ready to enumerate the existing mappings. // hr = pHNetProtocolSettings->EnumPortMappingProtocols(&pEnumHNetPortMappingProtocols); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't enumerate port mapping protocols (err = 0x%lx)!", hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pEnumHNetPortMappingProtocols); // // Allocate an array to keep track of previous ports in case of failure. // papHNetPortMappingProtocol = (IHNetPortMappingProtocol**) DNMalloc(DPNH_MAX_SIMULTANEOUS_PORTS * sizeof(IHNetPortMappingProtocol*)); if (papHNetPortMappingProtocol == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pasaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); if (pRegisteredPort->IsTCP()) { ucProtocolToMatch = PORTMAPPINGPROTOCOL_TCP; } else { ucProtocolToMatch = PORTMAPPINGPROTOCOL_UDP; } // // Map each individual address associated with the port. // for(dwTemp = 0; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { DNASSERT(pasaddrinPrivate[dwTemp].sin_port != 0); // // Loop until we find a duplicate item or run out of items. // do { hr = pEnumHNetPortMappingProtocols->Next(1, &papHNetPortMappingProtocol[dwTemp], &ulNumFound); if (FAILED(hr)) { DPFX(DPFPREP, 0, "Couldn't get next port mapping protocol (err = 0x%lx)!", hr); goto Failure; } // // If there aren't any more items, bail. // if (ulNumFound == 0) { // // pEnumHNetPortMappingProtocols->Next might have returned // S_FALSE. // hr = DPNH_OK; break; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(papHNetPortMappingProtocol[dwTemp]); // // Get the port. // hr = papHNetPortMappingProtocol[dwTemp]->GetPort(&wPort); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol 0x%p's port (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetPort!"), 2); goto Failure; } // // Get the protocol. // hr = papHNetPortMappingProtocol[dwTemp]->GetIPProtocol(&ucProtocol); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol 0x%p's IP protocol (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetIPProtocol!"), 2); goto Failure; } #ifdef DBG hr = papHNetPortMappingProtocol[dwTemp]->GetName(&pwszPortMappingProtocolName); if (hr == S_OK) { DPFX(DPFPREP, 7, "Found %s port mapping protocol 0x%p (\"%ls\") for port %u.", (((wPort == pasaddrinPrivate[dwTemp].sin_port) && (ucProtocol == ucProtocolToMatch)) ? _T("matching") : _T("non-matching")), papHNetPortMappingProtocol[dwTemp], pwszPortMappingProtocolName, NTOHS(wPort)); CoTaskMemFree(pwszPortMappingProtocolName); pwszPortMappingProtocolName = NULL; } else { DPFX(DPFPREP, 7, "Found %s port mapping protocol 0x%p for port %u, (unable to retrieve name, err = %0lx).", (((wPort == pasaddrinPrivate[dwTemp].sin_port) && (ucProtocol == ucProtocolToMatch)) ? _T("matching") : _T("non-matching")), NTOHS(wPort), papHNetPortMappingProtocol[dwTemp], hr); } #endif // DBG // // See if we found the object we need. // if ((wPort == pasaddrinPrivate[dwTemp].sin_port) && (ucProtocol == ucProtocolToMatch)) { break; } // // Get ready for the next object. // papHNetPortMappingProtocol[dwTemp]->Release(); papHNetPortMappingProtocol[dwTemp] = NULL; } while (TRUE); // // Generate a description for this mapping. The format is: // // [executable_name] nnnnn {"TCP" | "UDP"} // // unless it's shared, in which case it's // // [executable_name] (255.255.255.255:nnnnn) nnnnn {"TCP" | "UDP"} // // That way nothing needs to be localized. // wsprintf(tszPort, _T("%u"), NTOHS(pasaddrinPrivate[dwTemp].sin_port)); dwDescriptionLength = GetModuleFileName(NULL, ptszDescription, (MAX_UPNP_MAPPING_DESCRIPTION_SIZE - 1)); if (dwDescriptionLength != 0) { // // Be paranoid and make sure the description string is valid. // ptszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE - 1] = 0; // // Get just the executable name from the path. // #ifdef WINCE GetExeName(ptszDescription); #else // ! WINCE #ifdef UNICODE _wsplitpath(ptszDescription, NULL, NULL, ptszDescription, NULL); #else // ! UNICODE _splitpath(ptszDescription, NULL, NULL, ptszDescription, NULL); #endif // ! UNICODE #endif // ! WINCE if (pRegisteredPort->IsSharedPort()) { dwDescriptionLength = _tcslen(ptszDescription) // executable name + strlen(" (255.255.255.255:") // " (255.255.255.255:" + _tcslen(tszPort) // port + strlen(") ") // ") " + _tcslen(tszPort) // port + 4; // " TCP" | " UDP" } else { dwDescriptionLength = _tcslen(ptszDescription) // executable name + 1 // " " +_tcslen(tszPort) // port + 4; // " TCP" | " UDP" } // // Make sure the long string will fit. If not, use the // abbreviated version. // if (dwDescriptionLength > MAX_UPNP_MAPPING_DESCRIPTION_SIZE) { dwDescriptionLength = 0; } } if (dwDescriptionLength == 0) { // // Use the abbreviated version we know will fit. // if (pRegisteredPort->IsSharedPort()) { wsprintf(ptszDescription, _T("(255.255.255.255:%s) %s %s"), tszPort, tszPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } else { wsprintf(ptszDescription, _T("%s %s"), tszPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } } else { // // There's enough room, tack on the rest of the description. // if (pRegisteredPort->IsSharedPort()) { wsprintf((ptszDescription + _tcslen(ptszDescription)), _T(" (255.255.255.255:%s) %s %s"), tszPort, tszPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } else { wsprintf((ptszDescription + _tcslen(ptszDescription)), _T(" %s %s"), tszPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } } #ifndef UNICODE dwDescriptionLength = MAX_UPNP_MAPPING_DESCRIPTION_SIZE; hr = STR_AnsiToWide(szDescription, -1, wszDescription, &dwDescriptionLength); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't convert NAT mapping description to Unicode (err = 0x%lx)!", hr); goto Failure; } #endif // ! UNICODE // // If there wasn't a port mapping already, create it. Otherwise make // sure it's not already in use by some other client. // if (papHNetPortMappingProtocol[dwTemp] == NULL) { DPFX(DPFPREP, 7, "Creating new port mapping protocol \"%ls\".", wszDescription); // // Create a new port mapping protocol. // DPFX(DPFPREP, 9, "++ pHNetProtocolSettings(0x%p)->CreatePortMappingProtocol(\"%ls\", %u, 0x%lx, 0x%p)", pHNetProtocolSettings, wszDescription, ucProtocolToMatch, pasaddrinPrivate[dwTemp].sin_port, &papHNetPortMappingProtocol[dwTemp]); hr = pHNetProtocolSettings->CreatePortMappingProtocol(wszDescription, ucProtocolToMatch, pasaddrinPrivate[dwTemp].sin_port, &papHNetPortMappingProtocol[dwTemp]); DPFX(DPFPREP, 9, "-- pHNetProtocolSettings(0x%p)->CreatePortMappingProtocol = 0x%lx", pHNetProtocolSettings, hr); if (hr != S_OK) { // // This might be WBEM_E_ACCESSDENIED (0x80041003), which means // the current user doesn't have permissions to open holes in // the firewall. // DPFX(DPFPREP, 0, "Couldn't create new port mapping protocol (err = 0x%lx)!", hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(papHNetPortMappingProtocol[dwTemp]); fCreatedCurrentPortMappingProtocol = TRUE; // // Retrieve its binding. // DPFX(DPFPREP, 9, "++ pHNetConnection(0x%p)->GetBindingForPortMappingProtocol(0x%p, 0x%p)", pHNetConnection, papHNetPortMappingProtocol[dwTemp], &pHNetPortMappingBinding); hr = pHNetConnection->GetBindingForPortMappingProtocol(papHNetPortMappingProtocol[dwTemp], &pHNetPortMappingBinding); DPFX(DPFPREP, 9, "-- pHNetConnection(0x%p)->GetBindingForPortMappingProtocol = 0x%lx", pHNetConnection, hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get binding for port mapping protocol 0x%p (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetPortMappingBinding); // // Make sure it refers to the local device (or the broadcast // address, if shared). Although shared ports are a strange // concept on a firewall, Microsoft's firewall implementation // shares mappings with the NAT, so we'd rather be safe than sorry. // Mapping it to the broadcast address makes it behave the same if // the firewalled adapter also happens to be shared. // if (pRegisteredPort->IsSharedPort()) { DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress((broadcast) 0x%lx)", pHNetPortMappingBinding, INADDR_BROADCAST); hr = pHNetPortMappingBinding->SetTargetComputerAddress(INADDR_BROADCAST); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress = 0x%lx", pHNetPortMappingBinding, hr); } else { DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress(0x%lx)", pHNetPortMappingBinding, pDevice->GetLocalAddressV4()); hr = pHNetPortMappingBinding->SetTargetComputerAddress(pDevice->GetLocalAddressV4()); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress = 0x%lx", pHNetPortMappingBinding, hr); } if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't set binding 0x%p's target computer address (err = 0x%lx)!", pHNetPortMappingBinding, hr); goto Failure; } } else { // // Retrieve the existing binding. // DPFX(DPFPREP, 9, "++ pHNetConnection(0x%p)->GetBindingForPortMappingProtocol(0x%p, 0x%p)", pHNetConnection, papHNetPortMappingProtocol[dwTemp], &pHNetPortMappingBinding); hr = pHNetConnection->GetBindingForPortMappingProtocol(papHNetPortMappingProtocol[dwTemp], &pHNetPortMappingBinding); DPFX(DPFPREP, 9, "-- pHNetConnection(0x%p)->GetBindingForPortMappingProtocol = 0x%lx", pHNetConnection, hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get binding for port mapping protocol 0x%p (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetPortMappingBinding); // // Find out where this mapping goes. // DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->GetTargetComputerAddress(0x%p)", pHNetPortMappingBinding, &dwTargetAddressV4); hr = pHNetPortMappingBinding->GetTargetComputerAddress(&dwTargetAddressV4); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->GetTargetComputerAddress = 0x%lx", pHNetPortMappingBinding, hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get binding 0x%p's target computer address (err = 0x%lx)!", pHNetPortMappingBinding, hr); goto Failure; } // // If it's not for the local device, we may have to leave it alone. // if ((dwTargetAddressV4 != pDevice->GetLocalAddressV4()) && ((! pRegisteredPort->IsSharedPort()) || (dwTargetAddressV4 != INADDR_BROADCAST))) { // // Find out if it's turned on. // DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->GetEnabled(0x%p)", pHNetPortMappingBinding, &fTemp); hr = pHNetPortMappingBinding->GetEnabled(&fTemp); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->GetEnabled = 0x%lx", pHNetPortMappingBinding, hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get binding 0x%p's target computer address (err = 0x%lx)!", pHNetPortMappingBinding, hr); goto Failure; } // // If it's currently active, it's better to be safe than sorry. // Don't attempt to replace it. // if (fTemp) { DPFX(DPFPREP, 1, "Existing active binding points to different target %u.%u.%u.%u, can't reuse for device 0x%p.", ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b4, pDevice); // // Mark this port as unavailable. // pRegisteredPort->NoteHNetFirewallPortUnavailable(); // // Cleanup this port mapping. // pHNetPortMappingBinding->Release(); pHNetPortMappingBinding = NULL; papHNetPortMappingProtocol[dwTemp]->Release(); papHNetPortMappingProtocol[dwTemp] = NULL; // // Reset for next port. // DPFX(DPFPREP, 9, "++ pEnumHNetPortMappingProtocols(0x%p)->Reset()", pEnumHNetPortMappingProtocols); hr = pEnumHNetPortMappingProtocols->Reset(); DPFX(DPFPREP, 9, "-- pEnumHNetPortMappingProtocols(0x%p)->Reset = 0x%lx", pEnumHNetPortMappingProtocols, hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reset port mapping protocol enumeration 0x%p (err = 0x%lx)!", pEnumHNetPortMappingProtocols, hr); goto Failure; } // // Get out of the loop. // break; } // // It's inactive. // DPFX(DPFPREP, 7, "Modifying inactive port mapping protocol (target was %u.%u.%u.%u) for device 0x%p (new name = \"%ls\").", ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&dwTargetAddressV4))->S_un.S_un_b.s_b4, pDevice, wszDescription); } else { // // It matches the local device, or we're mapping a shared port // and the mapping pointed to the broadcast address. // Assume it's okay to replace. // DPFX(DPFPREP, 7, "Modifying existing port mapping protocol (device = 0x%p, new name = \"%ls\" unless built-in).", pDevice, wszDescription); } // // Otherwise, it's safe to change it. // // // Make sure it refers to the local device (or the broadcast // address, if shared). Although shared ports are a strange // concept on a firewall, Microsoft's firewall implementation // shares mappings with the NAT, so we'd rather be safe than sorry. // Mapping it to the broadcast address makes it behave the same if // the firewalled adapter also happens to be shared. // if (pRegisteredPort->IsSharedPort()) { DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress((broadcast) 0x%lx)", pHNetPortMappingBinding, INADDR_BROADCAST); hr = pHNetPortMappingBinding->SetTargetComputerAddress(INADDR_BROADCAST); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress = 0x%lx", pHNetPortMappingBinding, hr); } else { DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress(0x%lx)", pHNetPortMappingBinding, pDevice->GetLocalAddressV4()); hr = pHNetPortMappingBinding->SetTargetComputerAddress(pDevice->GetLocalAddressV4()); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->SetTargetComputerAddress = 0x%lx", pHNetPortMappingBinding, hr); } if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't set binding 0x%p's target computer address (err = 0x%lx)!", pHNetPortMappingBinding, hr); goto Failure; } // // See if this protocol is built-in. // DPFX(DPFPREP, 9, "++ papHNetPortMappingProtocol[%u](0x%p)->GetBuiltIn(0x%p)", dwTemp, papHNetPortMappingProtocol[dwTemp], &fBuiltIn); hr = papHNetPortMappingProtocol[dwTemp]->GetBuiltIn(&fBuiltIn); DPFX(DPFPREP, 9, "-- papHNetPortMappingProtocol[%u](0x%p)->GetBuiltIn = 0x%lx", dwTemp, papHNetPortMappingProtocol[dwTemp], hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get protocol 0x%p's built-in status (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); goto Failure; } // // If it's not built-in, we can change the name. // if (! fBuiltIn) { // // Update the description. // DPFX(DPFPREP, 9, "++ papHNetPortMappingProtocol[%u](0x%p)->SetName(\"%ls\")", dwTemp, papHNetPortMappingProtocol[dwTemp], wszDescription); hr = papHNetPortMappingProtocol[dwTemp]->SetName(wszDescription); DPFX(DPFPREP, 9, "-- papHNetPortMappingProtocol[%u](0x%p)->SetName = 0x%lx", dwTemp, papHNetPortMappingProtocol[dwTemp], hr); if (hr != S_OK) { // // This might be WBEM_E_ACCESSDENIED (0x80041003), which // means the current user doesn't truly have permissions to // open holes in the firewall (even though the // SetTargetComputerAddress call above succeeded). // DPFX(DPFPREP, 0, "Couldn't rename existing port mapping protocol 0x%p (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); goto Failure; } } else { pRegisteredPort->NoteHNetFirewallMappingBuiltIn(); DPFX(DPFPREP, 9, "++ papHNetPortMappingProtocol[%u](0x%p)->GetName(0x%p)", dwTemp, papHNetPortMappingProtocol[dwTemp], &pwszPortMappingProtocolName); hr = papHNetPortMappingProtocol[dwTemp]->GetName(&pwszPortMappingProtocolName); DPFX(DPFPREP, 9, "-- papHNetPortMappingProtocol[%u](0x%p)->GetName = 0x%lx", dwTemp, papHNetPortMappingProtocol[dwTemp], hr); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get built-in port mapping protocol 0x%p's name (err = 0x%lx)!", papHNetPortMappingProtocol[dwTemp], hr); goto Failure; } DPFX(DPFPREP, 1, "Re-using built in port mapping protocol \"%ls\" (can't rename to \"%ls\").", pwszPortMappingProtocolName, wszDescription); } } // end else (found port mapping protocol) // // Enable the binding. // DPFX(DPFPREP, 9, "++ pHNetPortMappingBinding(0x%p)->SetEnabled(TRUE)", pHNetPortMappingBinding); hr = pHNetPortMappingBinding->SetEnabled(TRUE); DPFX(DPFPREP, 9, "-- pHNetPortMappingBinding(0x%p)->SetEnabled = 0x%lx", pHNetPortMappingBinding, hr); if (hr != S_OK) { // // This might be WBEM_E_ACCESSDENIED (0x80041003), which means the // current user doesn't truly have permissions to open holes in the // firewall (even though the SetTargetComputerAddress call above // succeeded). // DPFX(DPFPREP, 0, "Couldn't enable binding 0x%p (err = 0x%lx)!", pHNetPortMappingBinding, hr); goto Failure; } // // Remember this firewall mapping, in case we crash before cleaning it // up in this session. That we can clean it up next time we launch. // Don't do this if the port is shared, since we can't tell when it's // no longer in use. // if (! pRegisteredPort->IsSharedPort()) { if (fBuiltIn) { DPFX(DPFPREP, 7, "Remembering built-in firewall mapping \"%ls\" (a.k.a. \"%ls\") in case of crash.", pwszPortMappingProtocolName, wszDescription); } else { DPFX(DPFPREP, 7, "Remembering regular firewall mapping \"%ls\" in case of crash.", wszDescription); } if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVEFIREWALLMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 0, "Couldn't open active firewall mapping key, unable to save in case of crash!"); } else { DNASSERT(this->m_dwInstanceKey != 0); ZeroMemory(&dpnhafm, sizeof(dpnhafm)); dpnhafm.dwVersion = ACTIVE_MAPPING_VERSION; dpnhafm.dwInstanceKey = this->m_dwInstanceKey; dpnhafm.dwFlags = pRegisteredPort->GetFlags(); dpnhafm.dwAddressV4 = pDevice->GetLocalAddressV4(); dpnhafm.wPort = pasaddrinPrivate[dwTemp].sin_port; // // If it's built-in, use its existing name since it couldn't be // renamed. This allows the unmapping code to find it in the // registry again. See UnmapPortOnLocalHNetFirewallInternal. // RegObject.WriteBlob(((fBuiltIn) ? pwszPortMappingProtocolName : wszDescription), (LPBYTE) (&dpnhafm), sizeof(dpnhafm)); RegObject.Close(); } } else { DPFX(DPFPREP, 7, "Not remembering shared port firewall mapping \"%ls\".", wszDescription); } // // Cleanup from this port mapping, and get ready for the next one. // if (fBuiltIn) { CoTaskMemFree(pwszPortMappingProtocolName); pwszPortMappingProtocolName = NULL; } pHNetPortMappingBinding->Release(); pHNetPortMappingBinding = NULL; fCreatedCurrentPortMappingProtocol = FALSE; hr = pEnumHNetPortMappingProtocols->Reset(); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reset port mapping protocol enumeration 0x%p (err = 0x%lx)!", pEnumHNetPortMappingProtocols, hr); goto Failure; } // // Alert the user to the change the next time GetCaps is called, if // requested. // if (fNoteAddressChange) { DPFX(DPFPREP, 8, "Noting that addresses changed (for registered port 0x%p).", pRegisteredPort); this->m_dwFlags |= NATHELPUPNPOBJ_ADDRESSESCHANGED; } // // Go on to the next port. // } // // dwTemp == pRegisteredPort->GetNumAddresses() if everything succeeded, or // or the index of the item that was unavailable if not. // // // Free all the port mapping protocol objects. If we successfully bound // all of them, that's all we need to do. If the port was unavailable, we // have to unmap any ports that were successful up to the one that failed. // while (dwTemp > 0) { dwTemp--; // // If we failed to map all ports, delete this previous mapping. // if (pRegisteredPort->IsHNetFirewallPortUnavailable()) { papHNetPortMappingProtocol[dwTemp]->Delete(); // ignore error } // // Free the object. // papHNetPortMappingProtocol[dwTemp]->Release(); papHNetPortMappingProtocol[dwTemp] = NULL; } // // If we succeeded, mark the registered port as mapped. // if (! pRegisteredPort->IsHNetFirewallPortUnavailable()) { pRegisteredPort->NoteMappedOnHNetFirewall(); } DNFree(papHNetPortMappingProtocol); papHNetPortMappingProtocol = NULL; DNASSERT(hr == DPNH_OK); Exit: if (pEnumHNetPortMappingProtocols != NULL) { pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; } if (pHNetProtocolSettings != NULL) { pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pwszPortMappingProtocolName != NULL) { CoTaskMemFree(pwszPortMappingProtocolName); pwszPortMappingProtocolName = NULL; } // // If we have an array, then we need to clean it up. dwTemp will still // hold the index of the item we were working on. // if (papHNetPortMappingProtocol != NULL) { // // Delete the one we were working on, if we created it. // if (papHNetPortMappingProtocol[dwTemp] != NULL) { if (fCreatedCurrentPortMappingProtocol) { papHNetPortMappingProtocol[dwTemp]->Delete(); // ignore error } papHNetPortMappingProtocol[dwTemp]->Release(); papHNetPortMappingProtocol[dwTemp] = NULL; } // // Delete all the mappings we successfully made up to the last one. // while (dwTemp > 0) { dwTemp--; DNASSERT(papHNetPortMappingProtocol[dwTemp] != NULL); papHNetPortMappingProtocol[dwTemp]->Delete(); // ignore error papHNetPortMappingProtocol[dwTemp]->Release(); papHNetPortMappingProtocol[dwTemp] = NULL; } DNFree(papHNetPortMappingProtocol); papHNetPortMappingProtocol = NULL; } if (pHNetPortMappingBinding != NULL) { pHNetPortMappingBinding->Release(); pHNetPortMappingBinding = NULL; } goto Exit; } // CNATHelpUPnP::MapPortOnLocalHNetFirewall #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::UnmapPortOnLocalHNetFirewall" //============================================================================= // CNATHelpUPnP::UnmapPortOnLocalHNetFirewall //----------------------------------------------------------------------------- // // Description: Removes the mappings for the given ports from the local // firewall. // // The main object lock is assumed to be held. It will be // converted into the long lock for the duration of this function. // // Arguments: // CRegisteredPort * pRegisteredPort - Pointer to port to be opened on the // firewall. // BOOL fNeedToDeleteRegValue - Whether the corresponding crash // recovery registry value needs to // be deleted as well. // BOOL fNoteAddressChange - Whether to alert the user of the // address change or not. // // Returns: HRESULT // DPNH_OK - Unmapping completed successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::UnmapPortOnLocalHNetFirewall(CRegisteredPort * const pRegisteredPort, const BOOL fNeedToDeleteRegValue, const BOOL fNoteAddressChange) { HRESULT hr = DPNH_OK; BOOL fSwitchedToLongLock = FALSE; BOOL fUninitializeCOM = FALSE; IHNetCfgMgr * pHNetCfgMgr = NULL; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i, %i)", this, pRegisteredPort, fNeedToDeleteRegValue, fNoteAddressChange); DNASSERT(pRegisteredPort->IsMappedOnHNetFirewall()); // // If the port is shared, leave it mapped since we can't tell when the // last person using it is done with it. // if (pRegisteredPort->IsSharedPort()) { DPFX(DPFPREP, 2, "Leaving shared registered port 0x%p mapped.", pRegisteredPort); // // Pretend like we unmapped it, though. // pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); goto Exit; } // // Using the HomeNet API (particularly the out-of-proc COM calls) during // stress is really, really, painfully slow. Since we have one global lock // the controls everything, other threads may be sitting for an equally // long time... so long, in fact, that the critical section timeout fires // and we get a false stress hit. So we have a sneaky workaround to // prevent that from happening while still maintaining ownership of the // object. // this->SwitchToLongLock(); fSwitchedToLongLock = TRUE; // // Try to initialize COM if we weren't instantiated through COM. It may // have already been initialized in a different mode, which is okay. As // long as it has been initialized somehow, we're fine. // if (this->m_dwFlags & NATHELPUPNPOBJ_NOTCREATEDWITHCOM) { hr = CoInitializeEx(NULL, (COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE)); switch (hr) { case S_OK: { // // Success, that's good. Cleanup when we're done. // DPFX(DPFPREP, 8, "Successfully initialized COM."); fUninitializeCOM = TRUE; break; } case S_FALSE: { // // Someone else already initialized COM, but that's okay. // Cleanup when we're done. // DPFX(DPFPREP, 8, "Initialized COM (again)."); fUninitializeCOM = TRUE; break; } case RPC_E_CHANGED_MODE: { // // Someone else already initialized COM in a different mode. // It should be okay, but we don't have to balance the CoInit // call with a CoUninit. // DPFX(DPFPREP, 8, "Didn't initialize COM, already initialized in a different mode."); break; } default: { // // Hmm, something else is going on. We can't handle that. // DPFX(DPFPREP, 0, "Initializing COM failed (err = 0x%lx)!", hr); goto Failure; break; } } } else { DPFX(DPFPREP, 8, "Object was instantiated through COM, no need to initialize COM."); } // // Create the main HNet manager object. // hr = CoCreateInstance(CLSID_HNetCfgMgr, NULL, CLSCTX_INPROC_SERVER, IID_IHNetCfgMgr, (PVOID*) (&pHNetCfgMgr)); if (hr != S_OK) { DPFX(DPFPREP, 1, "Couldn't create IHNetCfgMgr interface (err = 0x%lx)!", hr); goto Failure; } // // We created the IHNetCfgMgr object as in-proc, so there's no proxy that // requires security settings. // //SETDEFAULTPROXYBLANKET(pHNetCfgMgr); // // Actually unmap the port(s). // hr = this->UnmapPortOnLocalHNetFirewallInternal(pRegisteredPort, fNeedToDeleteRegValue, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't unmap ports from local HNet firewall (err = 0x%lx)!", hr); goto Failure; } // // Alert the user to the change the next time GetCaps is called, if requested. // if (fNoteAddressChange) { DPFX(DPFPREP, 8, "Noting that addresses changed (for registered port 0x%p).", pRegisteredPort); this->m_dwFlags |= NATHELPUPNPOBJ_ADDRESSESCHANGED; } Exit: if (pHNetCfgMgr != NULL) { pHNetCfgMgr->Release(); pHNetCfgMgr = NULL; } if (fUninitializeCOM) { DPFX(DPFPREP, 8, "Uninitializing COM."); CoUninitialize(); fUninitializeCOM = FALSE; } if (fSwitchedToLongLock) { this->SwitchFromLongLock(); fSwitchedToLongLock = FALSE; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::UnmapPortOnLocalHNetFirewall #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::UnmapPortOnLocalHNetFirewallInternal" //============================================================================= // CNATHelpUPnP::UnmapPortOnLocalHNetFirewallInternal //----------------------------------------------------------------------------- // // Description: Removes the mappings for the given ports from the local // firewall. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // CRegisteredPort * pRegisteredPort - Pointer to port to be opened on the // firewall. // BOOL fNeedToDeleteRegValue - Whether the corresponding crash // recovery registry value needs to // be deleted as well. // IHNetCfgMgr * pHNetCfgMgr - Pointer to IHNetCfgMgr interface to // use. // // Returns: HRESULT // DPNH_OK - Unmapping completed successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::UnmapPortOnLocalHNetFirewallInternal(CRegisteredPort * const pRegisteredPort, const BOOL fNeedToDeleteRegValue, IHNetCfgMgr * const pHNetCfgMgr) { HRESULT hr = DPNH_OK; CDevice * pDevice; DWORD dwAttempts = 0; IHNetProtocolSettings * pHNetProtocolSettings = NULL; IEnumHNetPortMappingProtocols * pEnumHNetPortMappingProtocols = NULL; SOCKADDR_IN * pasaddrinPrivate; UCHAR ucProtocolToMatch; IHNetPortMappingProtocol * pHNetPortMappingProtocol = NULL; DWORD dwStartingPort = 0; DWORD dwTemp; ULONG ulNumFound; WORD wPort; UCHAR ucProtocol; WCHAR * pwszName = NULL; BOOLEAN fBuiltIn; CRegistry RegObject; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i, 0x%p)", this, pRegisteredPort, fNeedToDeleteRegValue, pHNetCfgMgr); DNASSERT(pRegisteredPort->IsMappedOnHNetFirewall()); pDevice = pRegisteredPort->GetOwningDevice(); DNASSERT(pDevice != NULL); DNASSERT(pDevice->IsHNetFirewalled()); DNASSERT(this->m_hIpHlpApiDLL != NULL); Restart: // // Get a protocol settings interface. // hr = pHNetCfgMgr->QueryInterface(IID_IHNetProtocolSettings, (PVOID*) (&pHNetProtocolSettings)); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get IHNetProtocolSettings interface from IHNetCfgMgr 0x%p (err = 0x%lx)!", pHNetCfgMgr, hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetProtocolSettings); // // Get ready to enumerate the existing mappings. // hr = pHNetProtocolSettings->EnumPortMappingProtocols(&pEnumHNetPortMappingProtocols); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't enumerate port mapping protocols (err = 0x%lx)!", hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pEnumHNetPortMappingProtocols); pasaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); if (pRegisteredPort->IsTCP()) { ucProtocolToMatch = PORTMAPPINGPROTOCOL_TCP; } else { ucProtocolToMatch = PORTMAPPINGPROTOCOL_UDP; } // // Loop through all the ports (that we haven't successfully unmapped yet). // for(dwTemp = dwStartingPort; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { // // Loop until we find a duplicate item or run out of items. // do { hr = pEnumHNetPortMappingProtocols->Next(1, &pHNetPortMappingProtocol, &ulNumFound); if (FAILED(hr)) { dwAttempts++; if (dwAttempts < MAX_NUM_HOMENETUNMAP_ATTEMPTS) { DPFX(DPFPREP, 0, "Couldn't get next port mapping protocol (err = 0x%lx)! Trying again after %u ms.", hr, (dwAttempts * HOMENETUNMAP_SLEEP_FACTOR)); // // Dump the object pointers we currently have. // pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; // // Sleep, then go back to the top and try again. // Sleep(dwAttempts * HOMENETUNMAP_SLEEP_FACTOR); goto Restart; } DPFX(DPFPREP, 0, "Couldn't get next port mapping protocol (err = 0x%lx)!", hr); goto Failure; } // // If there aren't any more items, bail. // if (ulNumFound == 0) { // // Be sure that IEnumHNetPortMappingProtocols::Next returned // the right thing, for PREfix's sake. // if (pHNetPortMappingProtocol != NULL) { pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; } // // pEnumHNetPortMappingProtocols->Next might have returned // S_FALSE. // hr = DPNH_OK; break; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetPortMappingProtocol); // // Get the port. // hr = pHNetPortMappingProtocol->GetPort(&wPort); if (hr != S_OK) { DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetPort!"), 2); // // Dump the unusable mapping object. // pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; dwAttempts++; if (dwAttempts < MAX_NUM_HOMENETUNMAP_ATTEMPTS) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol port (err = 0x%lx)! Trying again after %u ms.", hr, (dwAttempts * HOMENETUNMAP_SLEEP_FACTOR)); // // Dump the object pointers we currently have. // pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; // // Sleep, then go back to the top and try again. // Sleep(dwAttempts * HOMENETUNMAP_SLEEP_FACTOR); goto Restart; } // // Break out of the search loop, but continue. // DPFX(DPFPREP, 0, "Couldn't get port mapping protocol port (err = 0x%lx)!", hr); break; } // // Get the protocol. // hr = pHNetPortMappingProtocol->GetIPProtocol(&ucProtocol); if (hr != S_OK) { DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetIPProtocol!"), 2); // // Dump the unusable mapping object. // pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; dwAttempts++; if (dwAttempts < MAX_NUM_HOMENETUNMAP_ATTEMPTS) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol's IP protocol (err = 0x%lx)! Trying again after %u ms.", hr, (dwAttempts * HOMENETUNMAP_SLEEP_FACTOR)); // // Dump the object pointers we currently have. // pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; // // Sleep, then go back to the top and try again. // Sleep(dwAttempts * HOMENETUNMAP_SLEEP_FACTOR); goto Restart; } // // Break out of the search loop, but continue. // DPFX(DPFPREP, 0, "Couldn't get port mapping protocol's IP protocol (err = 0x%lx)!", hr); break; } // // See if we found the object we need. Note that we don't verify // the target address for simplicity (neither does UPnP). // if ((wPort == pasaddrinPrivate[dwTemp].sin_port) && (ucProtocol == ucProtocolToMatch)) { // // Retrieve the mapping name. // hr = pHNetPortMappingProtocol->GetName(&pwszName); if (hr != S_OK) { DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetName!"), 2); // // Dump the unusable mapping object. // pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; dwAttempts++; if (dwAttempts < MAX_NUM_HOMENETUNMAP_ATTEMPTS) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol's name (err = 0x%lx)! Trying again after %u ms.", hr, (dwAttempts * HOMENETUNMAP_SLEEP_FACTOR)); // // Dump the object pointers we currently have. // pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; // // Sleep, then go back to the top and try again. // Sleep(dwAttempts * HOMENETUNMAP_SLEEP_FACTOR); goto Restart; } // // Break out of the search loop, but continue. // DPFX(DPFPREP, 0, "Couldn't get port mapping protocol's name (err = 0x%lx)!", hr); break; } DPFX(DPFPREP, 8, "Found port mapping protocol 0x%p (\"%ls\").", pHNetPortMappingProtocol, pwszName); // // See if this protocol is built-in. // hr = pHNetPortMappingProtocol->GetBuiltIn(&fBuiltIn); if (hr != S_OK) { DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetBuiltIn!"), 2); // // Dump the unusable mapping object and its name. // pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; CoTaskMemFree(pwszName); pwszName = NULL; dwAttempts++; if (dwAttempts < MAX_NUM_HOMENETUNMAP_ATTEMPTS) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol's built-in status (err = 0x%lx)! Trying again after %u ms.", hr, (dwAttempts * HOMENETUNMAP_SLEEP_FACTOR)); // // Dump the object pointers we currently have. // pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; // // Sleep, then go back to the top and try again. // Sleep(dwAttempts * HOMENETUNMAP_SLEEP_FACTOR); goto Restart; } // // Break out of the search loop, but continue. // DPFX(DPFPREP, 0, "Couldn't get port mapping protocol's built-in status (err = 0x%lx)!", hr); break; } break; } #ifdef DBG else { // // Try to retrieve the mapping name for informational purposes. // hr = pHNetPortMappingProtocol->GetName(&pwszName); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get port mapping protocol 0x%p's name (err = 0x%lx)!", pHNetPortMappingProtocol, hr); DNASSERTX((! "Got unexpected error executing IHNetPortMappingProtocol::GetName!"), 2); // // Ignore error... // } else { DPFX(DPFPREP, 7, "Skipping non-matching port mapping protocol 0x%p (\"%ls\").", pHNetPortMappingProtocol, pwszName); CoTaskMemFree(pwszName); pwszName = NULL; } } #endif // DBG // // Get ready for the next object. // pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; } while (TRUE); // // Remove the mapping (if we found it). // if (pHNetPortMappingProtocol != NULL) { // // If the mapping is built-in we can't delete it. Disabling it is // the best we can do. // if (fBuiltIn) { DPFX(DPFPREP, 7, "Disabling built-in port mapping protocol \"%ls\".", pwszName); DNASSERT(pRegisteredPort->IsHNetFirewallMappingBuiltIn()); hr = this->DisableAllBindingsForHNetPortMappingProtocol(pHNetPortMappingProtocol, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't disable all bindings for built-in port mapping protocol \"%ls\" (err = 0x%lx)!", pwszName, hr); goto Failure; } } else { DPFX(DPFPREP, 7, "Deleting port mapping protocol \"%ls\".", pwszName); DNASSERT(! pRegisteredPort->IsHNetFirewallMappingBuiltIn()); hr = pHNetPortMappingProtocol->Delete(); if (hr != S_OK) { // // This might be WBEM_E_ACCESSDENIED (0x80041003), which // means the current user doesn't have permissions to // modify firewall mappings. // DPFX(DPFPREP, 0, "Couldn't delete port mapping protocol (err = 0x%lx)!", hr); goto Failure; } } if (fNeedToDeleteRegValue) { // // Delete the crash cleanup registry entry. The mapping // description/name will match the registry key name even in // the case of built-in mappings with names we didn't generate. // See MapPortOnLocalHNetFirewall. // if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVEFIREWALLMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 0, "Couldn't open active firewall mapping key, unable to remove crash cleanup reference!"); } else { BOOL fResult; // // Ignore error. // fResult = RegObject.DeleteValue(pwszName); if (! fResult) { DPFX(DPFPREP, 0, "Couldn't delete firewall mapping value \"%ls\"! Continuing.", pwszName); } RegObject.Close(); } } else { DPFX(DPFPREP, 6, "No need to delete firewall crash cleanup registry key \"%ls\".", pwszName); } // // Cleanup pointers we accumulated. // CoTaskMemFree(pwszName); pwszName = NULL; pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; } else { // // We didn't find the mapping. // DPFX(DPFPREP, 0, "Didn't find port mapping protocol for port %u %s! Continuing.", NTOHS(pasaddrinPrivate[dwTemp].sin_port), ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } // // Cleanup from this port mapping, and get ready for the next one. // hr = pEnumHNetPortMappingProtocols->Reset(); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reset port mapping protocol enumeration 0x%p (err = 0x%lx)!", pEnumHNetPortMappingProtocols, hr); goto Failure; } // // Go on to the next port, and update the starting counter in case we // encounter a failure next time. // dwStartingPort++; } pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); DNASSERT(hr == DPNH_OK); Exit: if (pHNetPortMappingProtocol != NULL) { pHNetPortMappingProtocol->Release(); pHNetPortMappingProtocol = NULL; } if (pEnumHNetPortMappingProtocols != NULL) { pEnumHNetPortMappingProtocols->Release(); pEnumHNetPortMappingProtocols = NULL; } if (pHNetProtocolSettings != NULL) { pHNetProtocolSettings->Release(); pHNetProtocolSettings = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pwszName != NULL) { CoTaskMemFree(pwszName); pwszName = NULL; } goto Exit; } // CNATHelpUPnP::UnmapPortOnLocalHNetFirewallInternal #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::DisableAllBindingsForHNetPortMappingProtocol" //============================================================================= // CNATHelpUPnP::DisableAllBindingsForHNetPortMappingProtocol //----------------------------------------------------------------------------- // // Description: Disables all HNetPortMappingBindings on all HNetConnection // interfaces for the given port mapping protocol object. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // IHNetPortMappingProtocol * pHNetPortMappingProtocol - Pointer to port // mapping // protocol to // disable on all // connections. // IHNetCfgMgr * pHNetCfgMgr - Pointer to // IHNetCfgMgr // interface to // use. // // Returns: HRESULT // DPNH_OK - Disabling was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::DisableAllBindingsForHNetPortMappingProtocol(IHNetPortMappingProtocol * const pHNetPortMappingProtocol, IHNetCfgMgr * const pHNetCfgMgr) { HRESULT hr; INetConnectionManager * pNetConnectionManager = NULL; IEnumNetConnection * pEnumNetConnections = NULL; ULONG ulNumFound; INetConnection * pNetConnection = NULL; IHNetConnection * pHNetConnection = NULL; IHNetPortMappingBinding * pHNetPortMappingBinding = NULL; #ifdef DBG WCHAR * pwszName = NULL; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p)", this, pHNetPortMappingProtocol, pHNetCfgMgr); // // Try creating the base connection object. // hr = CoCreateInstance(CLSID_ConnectionManager, NULL, CLSCTX_SERVER, IID_INetConnectionManager, (PVOID*) (&pNetConnectionManager)); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't create INetConnectionManager interface (err = 0x%lx)!", hr); goto Failure; } SETDEFAULTPROXYBLANKET(pNetConnectionManager); DPFX(DPFPREP, 7, "Successfully created net connection manager object 0x%p.", pNetConnectionManager); // // Get the net connection enumeration object. // hr = pNetConnectionManager->EnumConnections(NCME_DEFAULT, &pEnumNetConnections); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't enum connections (err = 0x%lx)!", hr); goto Failure; } SETDEFAULTPROXYBLANKET(pEnumNetConnections); // // We don't need the base object anymore. // pNetConnectionManager->Release(); pNetConnectionManager = NULL; // // Keep looping until we find the item or run out of items. // do { hr = pEnumNetConnections->Next(1, &pNetConnection, &ulNumFound); if (FAILED(hr)) { DPFX(DPFPREP, 0, "Couldn't get next connection (err = 0x%lx)!", hr); goto Failure; } // // If there aren't any more items, bail. // if (ulNumFound == 0) { // // pEnumNetConnections->Next might have returned S_FALSE. // hr = DPNH_OK; break; } SETDEFAULTPROXYBLANKET(pNetConnection); // // Get the HNetConnection object for this NetConnection. // hr = pHNetCfgMgr->GetIHNetConnectionForINetConnection(pNetConnection, &pHNetConnection); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get IHNetConnection interface for INetConnection 0x%p (err = 0x%lx)!", pNetConnection, hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetConnection); // // Don't need the INetConnection interface anymore. // pNetConnection->Release(); pNetConnection = NULL; #ifdef DBG // // Retrieve the connection name, for debug printing purposes. // hr = pHNetConnection->GetName(&pwszName); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get name of HNetConnection 0x%p (err = 0x%lx)!", pHNetConnection, hr); goto Failure; } #endif // DBG // // Retrieve the existing binding. // hr = pHNetConnection->GetBindingForPortMappingProtocol(pHNetPortMappingProtocol, &pHNetPortMappingBinding); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't get binding for port mapping protocol 0x%p (err = 0x%lx)!", pHNetPortMappingProtocol, hr); goto Failure; } // // The HNetxxx objects appear to not be proxied... // //SETDEFAULTPROXYBLANKET(pHNetPortMappingBinding); // // Don't need the HomeNet Connection object anymore. // pHNetConnection->Release(); pHNetConnection = NULL; DPFX(DPFPREP, 6, "Disabling binding 0x%p on connection \"%ls\".", pHNetPortMappingBinding, pwszName); // // Disable it. // hr = pHNetPortMappingBinding->SetEnabled(FALSE); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't disable port mapping binding 0x%p (err = 0x%lx)!", pHNetPortMappingBinding, hr); goto Failure; } pHNetPortMappingBinding->Release(); pHNetPortMappingBinding = NULL; // // Go to the next mapping. // #ifdef DBG CoTaskMemFree(pwszName); pwszName = NULL; #endif // DBG } while (TRUE); // // If we're here, we made it through unscathed. // hr = DPNH_OK; Exit: if (pEnumNetConnections != NULL) { pEnumNetConnections->Release(); pEnumNetConnections = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pHNetPortMappingBinding != NULL) { pHNetPortMappingBinding->Release(); pHNetPortMappingBinding = NULL; } #ifdef DBG if (pwszName != NULL) { CoTaskMemFree(pwszName); pwszName = NULL; } #endif // DBG if (pHNetConnection != NULL) { pHNetConnection->Release(); pHNetConnection = NULL; } if (pNetConnection != NULL) { pNetConnection->Release(); pNetConnection = NULL; } if (pNetConnectionManager != NULL) { pNetConnectionManager->Release(); pNetConnectionManager = NULL; } goto Exit; } // CNATHelpUPnP::DisableAllBindingsForHNetPortMappingProtocol #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CleanupInactiveFirewallMappings" //============================================================================= // CNATHelpUPnP::CleanupInactiveFirewallMappings //----------------------------------------------------------------------------- // // Description: Looks for any mappings previously made by other DPNATHLP // instances that are no longer active (because of a crash), and // unmaps them. // // COM is assumed to have been initialized. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device to use. // IHNetCfgMgr * pHNetCfgMgr - Pointer to IHNetCfgMgr interface to // use. // // Returns: HRESULT // DPNH_OK - The cleanup was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CleanupInactiveFirewallMappings(CDevice * const pDevice, IHNetCfgMgr * const pHNetCfgMgr) { HRESULT hr = DPNH_OK; CRegistry RegObject; BOOL fOpenedRegistry = FALSE; DWORD dwIndex; WCHAR wszValueName[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; DWORD dwValueNameSize; DPNHACTIVEFIREWALLMAPPING dpnhafm; DWORD dwValueSize; TCHAR tszObjectName[MAX_INSTANCENAMEDOBJECT_SIZE]; DNHANDLE hNamedObject = NULL; CRegisteredPort * pRegisteredPort = NULL; BOOL fSetPrivateAddresses = FALSE; SOCKADDR_IN saddrinPrivate; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p)", this, pDevice, pHNetCfgMgr); DNASSERT(pDevice != NULL); DNASSERT(pDevice->IsHNetFirewalled()); if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVEFIREWALLMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 1, "Couldn't open active firewall mapping key, not performing crash cleanup."); DNASSERT(hr == DPNH_OK); goto Exit; } fOpenedRegistry = TRUE; // // Walk the list of active mappings. // dwIndex = 0; do { dwValueNameSize = MAX_UPNP_MAPPING_DESCRIPTION_SIZE; if (! RegObject.EnumValues(wszValueName, &dwValueNameSize, dwIndex)) { // // There was an error or there aren't any more keys. We're done. // break; } // // Try reading that mapping's data. // dwValueSize = sizeof(dpnhafm); if (! RegObject.ReadBlob(wszValueName, (LPBYTE) (&dpnhafm), &dwValueSize)) { // // We don't have a lock protecting the registry, so some other // instance could have deleted the key between when we enumerated // it and now. We'll stop trying (and hopefully that other // instance will cover the rest of the items). // DPFX(DPFPREP, 0, "Couldn't read \"%ls\" mapping value! Done with cleanup.", wszValueName); DNASSERT(hr == DPNH_OK); goto Exit; } // // Validate the data read. // if ((dwValueSize != sizeof(dpnhafm)) || (dpnhafm.dwVersion != ACTIVE_MAPPING_VERSION)) { DPFX(DPFPREP, 0, "The \"%ls\" mapping value is invalid! Done with cleanup.", wszValueName); // // Move to next item. // dwIndex++; continue; } // // See if that DPNHUPNP instance is still around. // if (this->m_dwFlags & NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX) { wsprintf(tszObjectName, _T("Global\\") INSTANCENAMEDOBJECT_FORMATSTRING, dpnhafm.dwInstanceKey); } else { wsprintf(tszObjectName, INSTANCENAMEDOBJECT_FORMATSTRING, dpnhafm.dwInstanceKey); } hNamedObject = DNOpenEvent(SYNCHRONIZE, FALSE, tszObjectName); if (hNamedObject != NULL) { // // This is still an active mapping. // DPFX(DPFPREP, 4, "Firewall mapping \"%ls\" belongs to instance %u, which is still active.", wszValueName, dpnhafm.dwInstanceKey); DNCloseHandle(hNamedObject); hNamedObject = NULL; // // Move to next item. // dwIndex++; continue; } DPFX(DPFPREP, 4, "Firewall mapping \"%ls\" belongs to instance %u, which no longer exists.", wszValueName, dpnhafm.dwInstanceKey); // // Delete the value now that we have the information we need. // if (! RegObject.DeleteValue(wszValueName)) { // // See ReadBlob comments. Stop trying to cleanup. // DPFX(DPFPREP, 0, "Couldn't delete \"%ls\"! Done with cleanup.", wszValueName); DNASSERT(hr == DPNH_OK); goto Exit; } // // Create a fake registered port that we will deregister. Ignore the // NAT state flags. // pRegisteredPort = new CRegisteredPort(0, (dpnhafm.dwFlags & REGPORTOBJMASK_HNETFWAPI)); if (pRegisteredPort == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } // // Assert that the other information/state flags are correct. // DNASSERT(! pRegisteredPort->IsHNetFirewallPortUnavailable()); DNASSERT(! pRegisteredPort->IsRemovingUPnPLease()); // // Temporarily associate the registered port with the device. // pRegisteredPort->MakeDeviceOwner(pDevice); ZeroMemory(&saddrinPrivate, sizeof(saddrinPrivate)); saddrinPrivate.sin_family = AF_INET; saddrinPrivate.sin_addr.S_un.S_addr = dpnhafm.dwAddressV4; saddrinPrivate.sin_port = dpnhafm.wPort; // // Store the private address. // hr = pRegisteredPort->SetPrivateAddresses(&saddrinPrivate, 1); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed creating UPnP address array!"); goto Failure; } fSetPrivateAddresses = TRUE; // // Pretend it has been mapped on the local firewall. Note that this // flag shouldn't have been set at the time it was stored in registry // but we masked it out if it had been. // pRegisteredPort->NoteMappedOnHNetFirewall(); // // Actually free the port. // hr = this->UnmapPortOnLocalHNetFirewallInternal(pRegisteredPort, FALSE, pHNetCfgMgr); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed deleting temporary HNet firewall port (err = 0x%lx)! Ignoring.", hr); // // Jump to the failure cleanup case, but don't actually return a // failure. // hr = DPNH_OK; goto Failure; } pRegisteredPort->ClearPrivateAddresses(); fSetPrivateAddresses = FALSE; pRegisteredPort->ClearDeviceOwner(); delete pRegisteredPort; pRegisteredPort = NULL; // // Move to the next mapping. Don't increment index since we just // deleted the previous entry and everything shifts down one. // } while (TRUE); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pRegisteredPort != NULL) { pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); if (fSetPrivateAddresses) { pRegisteredPort->ClearPrivateAddresses(); fSetPrivateAddresses = FALSE; } pRegisteredPort->ClearDeviceOwner(); delete pRegisteredPort; pRegisteredPort = NULL; } if (fOpenedRegistry) { RegObject.Close(); } goto Exit; } // CNATHelpUPnP::CleanupInactiveFirewallMappings #endif // ! DPNBUILD_NOHNETFWAPI #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::RemoveAllItems" //============================================================================= // CNATHelpUPnP::RemoveAllItems //----------------------------------------------------------------------------- // // Description: Removes all devices (de-registering with Internet gateways // if necessary). This removes all registered port mapping // objects and UPnP device objects, as well. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::RemoveAllItems(void) { HRESULT hr; CBilink * pBilinkDevice; CDevice * pDevice; CBilink * pBilinkRegisteredPort; CRegisteredPort * pRegisteredPort; CUPnPDevice * pUPnPDevice; DPFX(DPFPREP, 7, "(0x%p) Enter", this); pBilinkDevice = this->m_blDevices.GetNext(); while (pBilinkDevice != &this->m_blDevices) { DNASSERT(! pBilinkDevice->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilinkDevice); pBilinkDevice = pBilinkDevice->GetNext(); DPFX(DPFPREP, 5, "Destroying device 0x%p.", pDevice); pDevice->m_blList.RemoveFromList(); // // All of the device's registered ports are implicitly freed. // pBilinkRegisteredPort = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilinkRegisteredPort != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilinkRegisteredPort->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilinkRegisteredPort); pBilinkRegisteredPort = pBilinkRegisteredPort->GetNext(); DPFX(DPFPREP, 5, "Destroying registered port 0x%p (under device 0x%p).", pRegisteredPort, pDevice); // // Unmap on UPnP server if necessary. // if (pRegisteredPort->HasUPnPPublicAddresses()) { hr = this->UnmapUPnPPort(pRegisteredPort, pRegisteredPort->GetNumAddresses(), // free all ports TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't delete UPnP registered port 0x%p mapping (err = 0x%lx)! Ignoring.", pRegisteredPort, hr); // // Continue anyway, so we can finish cleaning up the object. // } DNASSERT(! pRegisteredPort->HasUPnPPublicAddresses()); pRegisteredPort->NoteNotPermanentUPnPLease(); pRegisteredPort->NoteNotUPnPPortUnavailable(); } #ifndef DPNBUILD_NOHNETFWAPI // // Then unmap from the local firewall, if necessary. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { // // Unmap the port. // // Alert the user since this is unexpected. // hr = this->UnmapPortOnLocalHNetFirewall(pRegisteredPort, TRUE, TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed unmapping registered port 0x%p on local HomeNet firewall (err = 0x%lx)! Ignoring.", pRegisteredPort, hr); pRegisteredPort->NoteNotMappedOnHNetFirewall(); pRegisteredPort->NoteNotHNetFirewallMappingBuiltIn(); // // Continue anyway, so we can finish cleaning up the object. // } } #endif // ! DPNBUILD_NOHNETFWAPI pRegisteredPort->ClearDeviceOwner(); DNASSERT(pRegisteredPort->m_blGlobalList.IsListMember(&this->m_blRegisteredPorts)); pRegisteredPort->m_blGlobalList.RemoveFromList(); pRegisteredPort->ClearPrivateAddresses(); // // The user implicitly released this port. // pRegisteredPort->ClearAllUserRefs(); delete pRegisteredPort; } // // The device's UPnP gateway is implicitly removed. // pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { if ((pUPnPDevice->IsConnecting()) || (pUPnPDevice->IsConnected())) { if (this->m_pfnshutdown(pUPnPDevice->GetControlSocket(), 0) != 0) { #ifdef DBG int iError; iError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Failed shutting down UPnP device 0x%p's control socket (err = %u)! Ignoring.", pUPnPDevice, iError); #endif // DBG } } pUPnPDevice->ClearDeviceOwner(); DNASSERT(pUPnPDevice->m_blList.IsListMember(&this->m_blUPnPDevices)); pUPnPDevice->m_blList.RemoveFromList(); // // Transfer list reference to our pointer, since GetUPnPDevice did // not give us one. // this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); pUPnPDevice->ClearLocationURL(); pUPnPDevice->ClearUSN(); pUPnPDevice->ClearServiceControlURL(); pUPnPDevice->DestroyReceiveBuffer(); pUPnPDevice->RemoveAllCachedMappings(); pUPnPDevice->DecRef(); pUPnPDevice = NULL; } #ifndef DPNBUILD_NOHNETFWAPI // // If we used the HomeNet firewall API to open a hole for UPnP // discovery multicasts, close it. // if (pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()) { hr = this->CloseDevicesUPnPDiscoveryPort(pDevice, NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't close device 0x%p's UPnP discovery socket's port on firewall (err = 0x%lx)! Ignoring.", pDevice, hr); // // Continue... // pDevice->NoteNotUPnPDiscoverySocketMappedOnHNetFirewall(); hr = DPNH_OK; } } #endif // ! DPNBUILD_NOHNETFWAPI // // Close the socket. // if (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) { this->m_pfnclosesocket(pDevice->GetUPnPDiscoverySocket()); pDevice->SetUPnPDiscoverySocket(INVALID_SOCKET); } // // Now we can dump the device object. // delete pDevice; } // // Removing all the devices normally removes all the registered ports, but // there may still be more wildcard ports that were never associated with // any device. // pBilinkRegisteredPort = this->m_blUnownedPorts.GetNext(); while (pBilinkRegisteredPort != &this->m_blUnownedPorts) { DNASSERT(! pBilinkRegisteredPort->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilinkRegisteredPort); pBilinkRegisteredPort = pBilinkRegisteredPort->GetNext(); DPFX(DPFPREP, 5, "Destroying unowned registered port 0x%p.", pRegisteredPort); pRegisteredPort->m_blDeviceList.RemoveFromList(); DNASSERT(pRegisteredPort->m_blGlobalList.IsListMember(&this->m_blRegisteredPorts)); pRegisteredPort->m_blGlobalList.RemoveFromList(); pRegisteredPort->ClearPrivateAddresses(); #ifndef DPNBUILD_NOHNETFWAPI DNASSERT(! pRegisteredPort->IsMappedOnHNetFirewall()); DNASSERT(! pRegisteredPort->IsHNetFirewallPortUnavailable()); #endif // ! DPNBUILD_NOHNETFWAPI DNASSERT(! pRegisteredPort->HasUPnPPublicAddresses()); DNASSERT(! pRegisteredPort->IsUPnPPortUnavailable()); // // The user implicitly released this port. // pRegisteredPort->ClearAllUserRefs(); delete pRegisteredPort; } #ifdef DBG DNASSERT(this->m_blRegisteredPorts.IsEmpty()); DNASSERT(this->m_blUPnPDevices.IsEmpty()); // // Print all items still in the registry. // #ifndef DPNBUILD_NOHNETFWAPI this->DebugPrintActiveFirewallMappings(); #endif // ! DPNBUILD_NOHNETFWAPI this->DebugPrintActiveNATMappings(); #endif // DBG DPFX(DPFPREP, 7, "(0x%p) Leave", this); } // CNATHelpUPnP::RemoveAllItems #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::FindMatchingDevice" //============================================================================= // CNATHelpUPnP::FindMatchingDevice //----------------------------------------------------------------------------- // // Description: Searches the list of devices for the object matching the // given address, or NULL if one could not be found. If the // address is INADDR_ANY, then the first device with a remote NAT // is selected. If none exist, then the first device with a local // firewall is selected. // // If fUseAllInfoSources is TRUE, the list of registered ports // associated with devices is searched first for an exact match to // the address passed in. If that fails, then devices are // searched as above. In addition, if the address is INADDR_ANY, /// the first device with a local NAT can be selected. // // The object lock is assumed to be held. // // Arguments: // SOCKADDR_IN * psaddrinMatch - Pointer to address to look up. // BOOL fUseAllInfoSources - Whether all possible sources of // information should be considered. // // Returns: CDevice // NULL if no match, valid object otherwise. //============================================================================= CDevice * CNATHelpUPnP::FindMatchingDevice(const SOCKADDR_IN * const psaddrinMatch, const BOOL fUseAllInfoSources) { HRESULT hr; BOOL fUpdatedDeviceList = FALSE; CDevice * pDeviceUPnPGateway = NULL; #ifndef DPNBUILD_NOHNETFWAPI CDevice * pDeviceLocalHNetFirewall = NULL; #endif // ! DPNBUILD_NOHNETFWAPI SOCKADDR_IN * pasaddrinTemp; CBilink * pBilink; CRegisteredPort * pRegisteredPort; CDevice * pDevice; DWORD dwTemp; do { // // First, make sure there are devices to choose from. // if (this->m_blDevices.IsEmpty()) { DPFX(DPFPREP, 0, "No devices, can't match address %u.%u.%u.%u!", psaddrinMatch->sin_addr.S_un.S_un_b.s_b1, psaddrinMatch->sin_addr.S_un.S_un_b.s_b2, psaddrinMatch->sin_addr.S_un.S_un_b.s_b3, psaddrinMatch->sin_addr.S_un.S_un_b.s_b4); pDevice = NULL; goto Exit; } // // It's possible that the address we're trying to match is an already // registered port. Look through all owned port mappings for this // address, if we're allowed. // if (fUseAllInfoSources) { pBilink = this->m_blRegisteredPorts.GetNext(); while (pBilink != &this->m_blRegisteredPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_GLOBAL_BILINK(pBilink); // // Only check this registered port if it has an owning device. // pDevice = pRegisteredPort->GetOwningDevice(); if (pDevice != NULL) { // // Check each port in the array. // pasaddrinTemp = pRegisteredPort->GetPrivateAddressesArray(); for(dwTemp = 0; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { // // If the address matches, we have a winner. // if ((pasaddrinTemp[dwTemp].sin_addr.S_un.S_addr == psaddrinMatch->sin_addr.S_un.S_addr) && (pasaddrinTemp[dwTemp].sin_port == psaddrinMatch->sin_port)) { DPFX(DPFPREP, 7, "Registered port 0x%p index %u matches address %u.%u.%u.%u:%u, returning owning device 0x%p.", pRegisteredPort, dwTemp, psaddrinMatch->sin_addr.S_un.S_un_b.s_b1, psaddrinMatch->sin_addr.S_un.S_un_b.s_b2, psaddrinMatch->sin_addr.S_un.S_un_b.s_b3, psaddrinMatch->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinMatch->sin_port), pDevice); goto Exit; } } } pBilink = pBilink->GetNext(); } } // // Darn, the address is not already registered. Well, match it up with // a device as best as possible. // pBilink = this->m_blDevices.GetNext(); do { DNASSERT(! pBilink->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilink); if ((pDevice->GetLocalAddressV4() == psaddrinMatch->sin_addr.S_un.S_addr)) { DPFX(DPFPREP, 7, "Device 0x%p matches address %u.%u.%u.%u.", pDevice, psaddrinMatch->sin_addr.S_un.S_un_b.s_b1, psaddrinMatch->sin_addr.S_un.S_un_b.s_b2, psaddrinMatch->sin_addr.S_un.S_un_b.s_b3, psaddrinMatch->sin_addr.S_un.S_un_b.s_b4); goto Exit; } // // Remember this device if it has the first remote UPnP gateway // device we've seen. // if ((pDevice->GetUPnPDevice() != NULL) && ((! pDevice->GetUPnPDevice()->IsLocal()) || (fUseAllInfoSources)) && (pDeviceUPnPGateway == NULL)) { pDeviceUPnPGateway = pDevice; } #ifndef DPNBUILD_NOHNETFWAPI // // Remember this device if it has the first HomeNet firewall we've // seen. // if ((pDevice->IsHNetFirewalled()) && (pDeviceLocalHNetFirewall == NULL)) { pDeviceLocalHNetFirewall = pDevice; } #endif // ! DPNBUILD_NOHNETFWAPI DPFX(DPFPREP, 7, "Device 0x%p does not match address %u.%u.%u.%u.", pDevice, psaddrinMatch->sin_addr.S_un.S_un_b.s_b1, psaddrinMatch->sin_addr.S_un.S_un_b.s_b2, psaddrinMatch->sin_addr.S_un.S_un_b.s_b3, psaddrinMatch->sin_addr.S_un.S_un_b.s_b4); pBilink = pBilink->GetNext(); } while (pBilink != &this->m_blDevices); // // If we got here, there's no matching device. It might be because the // caller detected an address change faster than we did. Try updating // our device list and searching again (if we haven't already). // if (fUpdatedDeviceList) { break; } // // Don't bother updating the list to match INADDR_ANY, we know that // will never match anything. // if (psaddrinMatch->sin_addr.S_un.S_addr == INADDR_ANY) { DPFX(DPFPREP, 7, "Couldn't find matching device for INADDR_ANY, as expected."); break; } DPFX(DPFPREP, 5, "Couldn't find matching device for %u.%u.%u.%u, updating device list and searching again.", psaddrinMatch->sin_addr.S_un.S_un_b.s_b1, psaddrinMatch->sin_addr.S_un.S_un_b.s_b2, psaddrinMatch->sin_addr.S_un.S_un_b.s_b3, psaddrinMatch->sin_addr.S_un.S_un_b.s_b4); hr = this->CheckForNewDevices(&fUpdatedDeviceList); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't check for new devices (0x%lx), continuing.", hr); // // Hmm, we have to treat it as non-fatal. Don't search again, // though. // break; } // // If we didn't actually get any new devices, don't bother searching // again. // if (! fUpdatedDeviceList) { break; } // // fUpdatedDeviceList is set to TRUE so we'll only loop one more time. // } while (TRUE); // // If we got here, there's still no matching device. If it's the wildcard // value, that's to be expected, but we need to pick a device in the // following order: // 1. device has an Internet gateway // 2. device has a firewall // If none of those exists or it's not the wildcard value, we have to give // up. // if (psaddrinMatch->sin_addr.S_un.S_addr == INADDR_ANY) { if (pDeviceUPnPGateway != NULL) { pDevice = pDeviceUPnPGateway; DPFX(DPFPREP, 1, "Picking device 0x%p with UPnP gateway device to match INADDR_ANY.", pDevice); } #ifndef DPNBUILD_NOHNETFWAPI else if (pDeviceLocalHNetFirewall != NULL) { pDevice = pDeviceLocalHNetFirewall; DPFX(DPFPREP, 1, "Picking device 0x%p with local HomeNet firewall to match INADDR_ANY.", pDevice); } #endif // ! DPNBUILD_NOHNETFWAPI else { pDevice = NULL; DPFX(DPFPREP, 1, "No suitable device to match INADDR_ANY."); } } else { pDevice = NULL; DPFX(DPFPREP, 7, "No devices match address %u.%u.%u.%u.", psaddrinMatch->sin_addr.S_un.S_un_b.s_b1, psaddrinMatch->sin_addr.S_un.S_un_b.s_b2, psaddrinMatch->sin_addr.S_un.S_un_b.s_b3, psaddrinMatch->sin_addr.S_un.S_un_b.s_b4); } Exit: return pDevice; } // CNATHelpUPnP::FindMatchingDevice #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ExtendAllExpiringLeases" //============================================================================= // CNATHelpUPnP::ExtendAllExpiringLeases //----------------------------------------------------------------------------- // // Description: Renews any port leases that are close to expiring (within 2 // minutes of expiration time). // // The object lock is assumed to be held. // // Arguments: None. // // Returns: HRESULT // DPNH_OK - Lease extension was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::ExtendAllExpiringLeases(void) { HRESULT hr = DPNH_OK; CBilink * pBilink; CRegisteredPort * pRegisteredPort; CDevice * pDevice; DWORD dwLeaseTimeRemaining; DPFX(DPFPREP, 5, "Enter"); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); // // Walk the list of all registered ports and check for leases that need to // be extended. // The lock is already held. // pBilink = this->m_blRegisteredPorts.GetNext(); while (pBilink != (&this->m_blRegisteredPorts)) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_GLOBAL_BILINK(pBilink); pDevice = pRegisteredPort->GetOwningDevice(); // // If the port is registered with the UPnP device, extend that lease, // if necessary. // if ((pRegisteredPort->HasUPnPPublicAddresses()) && (! pRegisteredPort->HasPermanentUPnPLease())) { DNASSERT(pDevice != NULL); dwLeaseTimeRemaining = pRegisteredPort->GetUPnPLeaseExpiration() - GETTIMESTAMP(); if (dwLeaseTimeRemaining < LEASE_RENEW_TIME) { hr = this->ExtendUPnPLease(pRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't extend port mapping lease on remote UPnP device (0x%lx)! Ignoring.", hr); // // We'll treat this as non-fatal, but we have to dump the // server. This may have already been done, but doing it // twice shouldn't be harmful. // this->ClearDevicesUPnPDevice(pDevice); hr = DPNH_OK; } } } // // The local firewall never uses leases, no need to extend. // pBilink = pBilink->GetNext(); } DNASSERT(hr == DPNH_OK); DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; } // CNATHelpUPnP::ExtendAllExpiringLeases #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::UpdateServerStatus" //============================================================================= // CNATHelpUPnP::UpdateServerStatus //----------------------------------------------------------------------------- // // Description: Checks to see if any Internet gateways have stopped // responding or are now available. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: HRESULT // DPNH_OK - The update was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::UpdateServerStatus(void) { HRESULT hr = DPNH_OK; DWORD dwMinUpdateServerStatusInterval; DWORD dwCurrentTime; CBilink * pBilink; CDevice * pDevice; CUPnPDevice * pUPnPDevice = NULL; CDevice * pDeviceRemoteUPnPGateway = NULL; #ifndef DPNBUILD_NOHNETFWAPI CDevice * pDeviceLocalHNetFirewall = NULL; #endif // ! DPNBUILD_NOHNETFWAPI BOOL fSendRemoteGatewayDiscovery; DPFX(DPFPREP, 5, "Enter"); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); // // Cache the current value of the global. This should be atomic so no need // to take the globals lock. // dwMinUpdateServerStatusInterval = g_dwMinUpdateServerStatusInterval; // // Capture the current time. // dwCurrentTime = GETTIMESTAMP(); // // If this isn't the first time to update server status, but it hasn't been // very long since we last checked, don't. This will prevent unnecessary // network traffic if GetCaps is called frequently (in response to many // alert events, for example). // // However, if we just found a new device, update the status anyway. // if (this->m_dwLastUpdateServerStatusTime != 0) { if ((dwCurrentTime - this->m_dwLastUpdateServerStatusTime) < dwMinUpdateServerStatusInterval) { if (! (this->m_dwFlags & NATHELPUPNPOBJ_DEVICECHANGED)) { DPFX(DPFPREP, 5, "Server status was just updated at %u, not updating again (time = %u, min interval = %u).", this->m_dwLastUpdateServerStatusTime, dwCurrentTime, dwMinUpdateServerStatusInterval); // // hr == DPNH_OK // goto Exit; } DPFX(DPFPREP, 5, "Server status was just updated at %u (time = %u, min interval = %u), but there was a device change that may affect things.", this->m_dwLastUpdateServerStatusTime, dwCurrentTime, dwMinUpdateServerStatusInterval); // // Continue... // } // // If we're allowed to keep polling for remote gateways after startup, // do so. Otherwise, only do it if a device has changed or a port has // been registered since our last check. // if ((g_fKeepPollingForRemoteGateway) || (this->m_dwFlags & NATHELPUPNPOBJ_DEVICECHANGED) || (this->m_dwFlags & NATHELPUPNPOBJ_PORTREGISTERED)) { fSendRemoteGatewayDiscovery = TRUE; } else { fSendRemoteGatewayDiscovery = FALSE; } } else { // // We always poll for new remote gateways during startup. // fSendRemoteGatewayDiscovery = TRUE; } // // Prevent the timer from landing exactly on 0. // if (dwCurrentTime == 0) { dwCurrentTime = 1; } this->m_dwLastUpdateServerStatusTime = dwCurrentTime; // // Turn off the 'device changed' and 'port registered' flags, if they were // on. // this->m_dwFlags &= ~(NATHELPUPNPOBJ_DEVICECHANGED | NATHELPUPNPOBJ_PORTREGISTERED); // // Locate any new UPnP devices. // if (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) { // // We're not listening on the UPnP multicast address and can't hear // unsolicited new device announcements. In order to detect new // devices, we need to resend the discovery request periodically so // that responses get sent directly to our listening socket. // hr = this->CheckForUPnPAnnouncements(g_dwUPnPAnnounceResponseWaitTime, fSendRemoteGatewayDiscovery); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't check for UPnP announcements!"); goto Failure; } } else { // // Not using UPnP. // } // // Loop through all the devices. // pBilink = this->m_blDevices.GetNext(); while (pBilink != &this->m_blDevices) { DNASSERT(! pBilink->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilink); // // This might be a new device, so register any ports with this address // that were previously unowned (because this device's address was // unknown at the time). // hr = this->RegisterPreviouslyUnownedPortsWithDevice(pDevice, FALSE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't register previously unowned ports with device 0x%p!.", pDevice); goto Failure; } #ifndef DPNBUILD_NOHNETFWAPI if (this->m_dwFlags & NATHELPUPNPOBJ_USEHNETFWAPI) { // // See if the local firewall state has changed. // hr = this->CheckForLocalHNetFirewallAndMapPorts(pDevice, NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't check for local HNet firewall and map ports (err = 0x%lx)! Ignoring.", hr); DNASSERT(! pDevice->IsHNetFirewalled()); hr = DPNH_OK; } // // If there's a local firewall, remember the device if it's the // first one we've found. // if ((pDevice->IsHNetFirewalled()) && (pDeviceLocalHNetFirewall == NULL)) { pDeviceLocalHNetFirewall = pDevice; } } else { // // Not using firewall traversal. // } #endif // ! DPNBUILD_NOHNETFWAPI if (this->m_dwFlags & NATHELPUPNPOBJ_USEUPNP) { pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { // // GetUPnPDevice did not add a reference to pUPnPDevice for us. // pUPnPDevice->AddRef(); // // Update the public addresses for the UPnP device, if any. // hr = this->UpdateUPnPExternalAddress(pUPnPDevice, TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed updating UPnP device external address!"); // // It may have been cleared already, but doing it twice // shouldn't be harmful. // this->ClearDevicesUPnPDevice(pDevice); hr = DPNH_OK; } else { // // Save this UPnP device, if it's the first one we've // found and it's not local. // if ((pDeviceRemoteUPnPGateway == NULL) && (! pUPnPDevice->IsLocal())) { pDeviceRemoteUPnPGateway = pDevice; } } pUPnPDevice->DecRef(); pUPnPDevice = NULL; } else { // // No UPnP device. // } } else { // // Not using UPnP. // } pBilink = pBilink->GetNext(); } // // Some new servers may have come online. If so, we can now map wildcard // ports that were registered previously. Figure out which device that is. // if (pDeviceRemoteUPnPGateway != NULL) { pDevice = pDeviceRemoteUPnPGateway; } #ifndef DPNBUILD_NOHNETFWAPI else if (pDeviceLocalHNetFirewall != NULL) { pDevice = pDeviceLocalHNetFirewall; } #endif // ! DPNBUILD_NOHNETFWAPI else { pDevice = NULL; } if (pDevice != NULL) { // // Register any wildcard ports that are unowned with this best device. // hr = this->RegisterPreviouslyUnownedPortsWithDevice(pDevice, TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't register unowned wildcard ports with device 0x%p!.", pDevice); goto Failure; } } #ifdef DBG else { DPFX(DPFPREP, 7, "No devices have a UPnP gateway device or a local HomeNet firewall."); } #endif // DBG DPFX(DPFPREP, 7, "Spent %u ms updating server status, starting at %u.", (GETTIMESTAMP() - dwCurrentTime), dwCurrentTime); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::UpdateServerStatus #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::RegisterPreviouslyUnownedPortsWithDevice" //============================================================================= // CNATHelpUPnP::RegisterPreviouslyUnownedPortsWithDevice //----------------------------------------------------------------------------- // // Description: Associates unknown ports with the given device, and // registers them with the device's UPnP device or firewall. // // If fWildcardToo is FALSE, only previously unowned ports that // match the device's address are associated. If TRUE, unowned // INADDR_ANY ports are associated as well. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device to take ownership of ports. // BOOL fAll - Whether all ports should be associated. // // Returns: HRESULT // DPNH_OK - The extension was successful. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::RegisterPreviouslyUnownedPortsWithDevice(CDevice * const pDevice, const BOOL fWildcardToo) { HRESULT hr = DPNH_OK; CBilink * pBilink; CRegisteredPort * pRegisteredPort; SOCKADDR_IN * pasaddrinPrivate; CUPnPDevice * pUPnPDevice; #ifdef DBG BOOL fAssignedPort = FALSE; IN_ADDR inaddrTemp; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i)", this, pDevice, fWildcardToo); // // Loop through all unowned ports, assign them to the device if // appropriate, then register them. // pBilink = this->m_blUnownedPorts.GetNext(); while (pBilink != &this->m_blUnownedPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); pBilink = pBilink->GetNext(); // // The registered port must match the device's address in order to // associate them. If wildcards are allowed, then INADDR_ANY // registrations can be associated, too. // // // All addresses should be same (if there are more than one), so just // compare the first one in the array. // pasaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); if (pasaddrinPrivate[0].sin_addr.S_un.S_addr != pDevice->GetLocalAddressV4()) { if (pasaddrinPrivate[0].sin_addr.S_un.S_addr != INADDR_ANY) { DPFX(DPFPREP, 7, "Unowned registered port 0x%p private address %u.%u.%u.%u doesn't match device 0x%p's, skipping.", pRegisteredPort, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b1, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b2, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b3, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b4, pDevice); continue; } #ifdef DBG inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); #endif // DBG if (! fWildcardToo) { #ifdef DBG DPFX(DPFPREP, 7, "Unowned registered port 0x%p (INADDR_ANY) not allowed to be associated with device 0x%p (address %u.%u.%u.%u), skipping.", pRegisteredPort, pDevice, inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); #endif // DBG continue; } #ifdef DBG DPFX(DPFPREP, 7, "Unowned registered port 0x%p (INADDR_ANY) becoming associated with device 0x%p (address %u.%u.%u.%u).", pRegisteredPort, pDevice, inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); #endif // DBG } else { DPFX(DPFPREP, 7, "Unowned registered port 0x%p private address %u.%u.%u.%u matches device 0x%p's, associating.", pRegisteredPort, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b1, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b2, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b3, pasaddrinPrivate[0].sin_addr.S_un.S_un_b.s_b4, pDevice); // // The way it's currently implemented, all non-wildcard ports // should be registered before we even try to register the wildcard // ones. // DNASSERT(! fWildcardToo); } // // If we made it here, we can associate the port with the device. // pRegisteredPort->m_blDeviceList.RemoveFromList(); pRegisteredPort->MakeDeviceOwner(pDevice); #ifndef DPNBUILD_NOHNETFWAPI // // Start by automatically mapping with the local firewall, if there is // one and we're allowed. // if (this->m_dwFlags & NATHELPUPNPOBJ_USEHNETFWAPI) { hr = this->CheckForLocalHNetFirewallAndMapPorts(pDevice, NULL); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't check for local HNet firewall and map ports (err = 0x%lx)! Ignoring.", hr); DNASSERT(! pDevice->IsHNetFirewalled()); hr = DPNH_OK; } } else { // // Not using firewall traversal. // } #endif // ! DPNBUILD_NOHNETFWAPI // // Attempt to automatically map it with the (new) UPnP gateway device, // if present. // pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { // // GetUPnPDevice did not add a reference to pUPnPDevice for us. // pUPnPDevice->AddRef(); DNASSERT(pUPnPDevice->IsReady()); hr = this->MapPortsOnUPnPDevice(pUPnPDevice, pRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't map existing ports on UPnP device 0x%p!", pUPnPDevice); // // It may have been cleared already, but doing it twice // shouldn't be harmful. // this->ClearDevicesUPnPDevice(pDevice); hr = DPNH_OK; } pUPnPDevice->DecRef(); pUPnPDevice = NULL; } } #ifdef DBG if (! fAssignedPort) { DPFX(DPFPREP, 1, "No unowned ports were bound to device object 0x%p.", pDevice); } #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; } // CNATHelpUPnP::RegisterPreviouslyUnownedPortsWithDevice #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::SendUPnPSearchMessagesForDevice" //============================================================================= // CNATHelpUPnP::SendUPnPSearchMessagesForDevice //----------------------------------------------------------------------------- // // Description: Sends one UPnP search message via the given device locally // and if fRemoteAllowed is TRUE, to the multicast or gateway // address. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device to use. // BOOL fRemoteAllowed - Whether we can search remotely or not. // // Returns: HRESULT // DPNH_OK - The messages were sent successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::SendUPnPSearchMessagesForDevice(CDevice * const pDevice, const BOOL fRemoteAllowed) { HRESULT hr = DPNH_OK; SOCKADDR_IN saddrinRemote; SOCKADDR_IN saddrinLocal; BOOL fTryRemote; int iWANIPConnectionMsgSize; int iWANPPPConnectionMsgSize; int iReturn; SOCKET sTemp = INVALID_SOCKET; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i)", this, pDevice, fRemoteAllowed); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); DNASSERT(pDevice->GetUPnPDiscoverySocket() != INVALID_SOCKET); ZeroMemory(&saddrinRemote, sizeof(saddrinRemote)); saddrinRemote.sin_family = AF_INET; //saddrinRemote.sin_addr.S_un.S_addr = ? saddrinRemote.sin_port = HTONS(UPNP_PORT); // // If we're allowed to try remotely, use the gateway's address, or the // multicast address, as appropriate. // if ((fRemoteAllowed) && (! pDevice->GotRemoteUPnPDiscoveryConnReset())) { if (g_fUseMulticastUPnPDiscovery) { saddrinRemote.sin_addr.S_un.S_addr = this->m_pfninet_addr(UPNP_DISCOVERY_MULTICAST_ADDRESS); fTryRemote = TRUE; } else { // // Try to get the device's gateway's address. This might return FALSE // if the device does not have a gateway. In that case, we will ignore // the device. Otherwise the address should be filled in with the // gateway or broadcast address. // fTryRemote = this->GetAddressToReachGateway(pDevice, &saddrinRemote.sin_addr); } } else { fTryRemote = FALSE; } ZeroMemory(&saddrinLocal, sizeof(saddrinLocal)); saddrinLocal.sin_family = AF_INET; saddrinLocal.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); saddrinLocal.sin_port = HTONS(UPNP_PORT); // // Note that these message strings contain: // // HOST: multicast_addr:port // // even though we send the messages to addresses other than the multicast // address. It shouldn't matter. // iWANIPConnectionMsgSize = strlen(c_szUPnPMsg_Discover_Service_WANIPConnection); iWANPPPConnectionMsgSize = strlen(c_szUPnPMsg_Discover_Service_WANPPPConnection); #ifdef DBG this->PrintUPnPTransactionToFile(c_szUPnPMsg_Discover_Service_WANIPConnection, iWANIPConnectionMsgSize, "Outbound WANIPConnection discovery messages", pDevice); this->PrintUPnPTransactionToFile(c_szUPnPMsg_Discover_Service_WANPPPConnection, iWANPPPConnectionMsgSize, "Outbound WANPPPConnection discovery messages", pDevice); #endif // DBG DNASSERT(pDevice->GetUPnPDiscoverySocket() != INVALID_SOCKET); // // First, fire off messages to the remote gateway, if possible. // if (fTryRemote) { DPFX(DPFPREP, 7, "Sending UPnP discovery messages (WANIPConnection and WANPPPConnection) to gateway/multicast %u.%u.%u.%u:%u via device 0x%p.", saddrinRemote.sin_addr.S_un.S_un_b.s_b1, saddrinRemote.sin_addr.S_un.S_un_b.s_b2, saddrinRemote.sin_addr.S_un.S_un_b.s_b3, saddrinRemote.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinRemote.sin_port), pDevice); // // Remember that we're trying remotely. // pDevice->NotePerformingRemoteUPnPDiscovery(); // // Remember the current time, if this is the first thing we've sent // from this port. // if (pDevice->GetFirstUPnPDiscoveryTime() == 0) { pDevice->SetFirstUPnPDiscoveryTime(GETTIMESTAMP()); } // // Multicast/send to gateway a WANIPConnection discovery message. // iReturn = this->m_pfnsendto(pDevice->GetUPnPDiscoverySocket(), c_szUPnPMsg_Discover_Service_WANIPConnection, iWANIPConnectionMsgSize, 0, (SOCKADDR*) (&saddrinRemote), sizeof(saddrinRemote)); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending WANIPConnection discovery to UPnP gateway/multicast address on device 0x%p! Ignoring.", dwError, pDevice); #endif // DBG // // It's possible that we caught WinSock at a bad time, // particularly with WSAEADDRNOTAVAIL (10049), which seems to // occur if the address is going away (and we haven't detected // it in CheckForNewDevices yet). // // Ignore the error, we can survive. // } else { if (iReturn != iWANIPConnectionMsgSize) { DPFX(DPFPREP, 0, "Didn't multicast send entire WANIPConnection discovery datagram on device 0x%p (%i != %i)?!", pDevice, iReturn, iWANIPConnectionMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } } // // Multicast/send to gateway a WANPPPConnection discovery message. // iReturn = this->m_pfnsendto(pDevice->GetUPnPDiscoverySocket(), c_szUPnPMsg_Discover_Service_WANPPPConnection, iWANPPPConnectionMsgSize, 0, (SOCKADDR*) (&saddrinRemote), sizeof(saddrinRemote)); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending WANPPPConnection discovery to UPnP multicast/gateway address on device 0x%p! Ignoring.", dwError, pDevice); #endif // DBG // // It's possible that we caught WinSock at a bad time, // particularly with WSAEADDRNOTAVAIL (10049), which seems to // occur if the address is going away (and we haven't detected // it in CheckForNewDevices yet). // // Ignore the error, we can survive. // } else { if (iReturn != iWANPPPConnectionMsgSize) { DPFX(DPFPREP, 0, "Didn't multicast send entire WANPPPConnection discovery datagram on device 0x%p (%i != %i)?!", pDevice, iReturn, iWANPPPConnectionMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } } } else { DPFX(DPFPREP, 2, "Device 0x%p should not attempt to reach a remote gateway.", pDevice); // // Remember that we're not trying remotely. // pDevice->NoteNotPerformingRemoteUPnPDiscovery(); } // // If we didn't already get a CONNRESET from a previous attempt, try to // bind a socket locally to the UPnP discovery port. If it's not in use, // then we know nobody will be listening so there's no point in trying the // local address. If it is in use, then this computer might be a UPnP // gateway itself. // if (! pDevice->GotLocalUPnPDiscoveryConnReset()) { sTemp = this->m_pfnsocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sTemp == INVALID_SOCKET) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't create temporary datagram socket, error = %u!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } if (this->m_pfnbind(sTemp, (SOCKADDR *) (&saddrinLocal), sizeof(saddrinLocal)) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 2, "Couldn't bind socket to UPnP discovery port (%u.%u.%u.%u:%u), assuming local device (error = %u).", saddrinLocal.sin_addr.S_un.S_un_b.s_b1, saddrinLocal.sin_addr.S_un.S_un_b.s_b2, saddrinLocal.sin_addr.S_un.S_un_b.s_b3, saddrinLocal.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinLocal.sin_port), dwError); #endif // DBG // // Remember that we're trying locally. // pDevice->NotePerformingLocalUPnPDiscovery(); // // Remember the current time, if this is the first thing we've sent // from this port. // if (pDevice->GetFirstUPnPDiscoveryTime() == 0) { pDevice->SetFirstUPnPDiscoveryTime(GETTIMESTAMP()); } // // Do WANIPConnection first. // DPFX(DPFPREP, 7, "Sending UPnP discovery messages (WANIPConnection and WANPPPConnection) locally to device 0x%p (address %u.%u.%u.%u:%u).", pDevice, saddrinLocal.sin_addr.S_un.S_un_b.s_b1, saddrinLocal.sin_addr.S_un.S_un_b.s_b2, saddrinLocal.sin_addr.S_un.S_un_b.s_b3, saddrinLocal.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinLocal.sin_port)); iReturn = this->m_pfnsendto(pDevice->GetUPnPDiscoverySocket(), c_szUPnPMsg_Discover_Service_WANIPConnection, iWANIPConnectionMsgSize, 0, (SOCKADDR*) (&saddrinLocal), sizeof(saddrinLocal)); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending WANIPConnection discovery to local address on device 0x%p! Ignoring.", dwError, pDevice); #endif // DBG // // It's possible that we caught WinSock at a bad time, // particularly with WSAEADDRNOTAVAIL (10049), which seems to // occur if the address is going away (and we haven't detected // it in CheckForNewDevices yet). // // Ignore the error, we can survive. // } else { if (iReturn != iWANIPConnectionMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire WANIPConnection discovery datagram locally on device 0x%p (%i != %i)?!", pDevice, iReturn, iWANIPConnectionMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } } // // Now send WANPPPConnection discovery message locally. // iReturn = this->m_pfnsendto(pDevice->GetUPnPDiscoverySocket(), c_szUPnPMsg_Discover_Service_WANPPPConnection, iWANPPPConnectionMsgSize, 0, (SOCKADDR*) (&saddrinLocal), sizeof(saddrinLocal)); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending WANPPPConnection discovery to local address on device 0x%p! Ignoring.", dwError, pDevice); #endif // DBG // // It's possible that we caught WinSock at a bad time, // particularly with WSAEADDRNOTAVAIL (10049), which seems to // occur if the address is going away (and we haven't detected // it in CheckForNewDevices yet). // // Ignore the error, we can survive. // } else { if (iReturn != iWANPPPConnectionMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire WANPPPConnection discovery datagram locally on device 0x%p (%i != %i)?!", pDevice, iReturn, iWANPPPConnectionMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } } } else { DPFX(DPFPREP, 2, "Successfully bound socket to UPnP discovery port (%u.%u.%u.%u:%u), assuming no local UPnP device.", saddrinLocal.sin_addr.S_un.S_un_b.s_b1, saddrinLocal.sin_addr.S_un.S_un_b.s_b2, saddrinLocal.sin_addr.S_un.S_un_b.s_b3, saddrinLocal.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinLocal.sin_port)); // // Remember that we're not trying locally. // pDevice->NoteNotPerformingLocalUPnPDiscovery(); } } else { // // We got a CONNRESET last time. // } Exit: if (sTemp != INVALID_SOCKET) { this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::SendUPnPSearchMessagesForDevice #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::SendUPnPDescriptionRequest" //============================================================================= // CNATHelpUPnP::SendUPnPDescriptionRequest //----------------------------------------------------------------------------- // // Description: Requests a description from the given UPnP device. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device to use. // // Returns: HRESULT // DPNH_OK - The message was sent successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::SendUPnPDescriptionRequest(CUPnPDevice * const pUPnPDevice) { HRESULT hr = DPNH_OK; SOCKADDR_IN * psaddrinHost; TCHAR tszHost[22]; // "xxx.xxx.xxx.xxx:xxxxx" + NULL termination char * pszMessage = NULL; int iMsgSize; int iReturn; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p)", this, pUPnPDevice); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); DNASSERT(pUPnPDevice->IsConnected()); DNASSERT(pUPnPDevice->GetLocationURL() != NULL); psaddrinHost = pUPnPDevice->GetHostAddress(); wsprintf(tszHost, _T("%u.%u.%u.%u:%u"), psaddrinHost->sin_addr.S_un.S_un_b.s_b1, psaddrinHost->sin_addr.S_un.S_un_b.s_b2, psaddrinHost->sin_addr.S_un.S_un_b.s_b3, psaddrinHost->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinHost->sin_port)); iMsgSize = strlen("GET ") + strlen(pUPnPDevice->GetLocationURL()) + strlen(" " HTTP_VERSION EOL) + strlen("HOST: ") + _tcslen(tszHost) + strlen(EOL) + strlen("ACCEPT-LANGUAGE: en" EOL) + strlen(EOL); pszMessage = (char*) DNMalloc(iMsgSize + 1); // include room for NULL termination that isn't actually sent if (pszMessage == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } strcpy(pszMessage, "GET "); strcat(pszMessage, pUPnPDevice->GetLocationURL()); strcat(pszMessage, " " HTTP_VERSION EOL); strcat(pszMessage, "HOST: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszHost, (_tcslen(tszHost) + 1)); #else // ! UNICODE strcat(pszMessage, tszHost); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "ACCEPT-LANGUAGE: en" EOL); strcat(pszMessage, EOL); #ifdef DBG this->PrintUPnPTransactionToFile(pszMessage, iMsgSize, "Outbound description request", pUPnPDevice->GetOwningDevice()); #endif // DBG iReturn = this->m_pfnsend(pUPnPDevice->GetControlSocket(), pszMessage, iMsgSize, 0); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending to UPnP device!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } if (iReturn != iMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire message (%i != %i)?!", iReturn, iMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } Exit: if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::SendUPnPDescriptionRequest #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::UpdateUPnPExternalAddress" //============================================================================= // CNATHelpUPnP::UpdateUPnPExternalAddress //----------------------------------------------------------------------------- // // Description: Retreives the current external address for the given UPnP // Internet Gateway Device. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device that should be // updated. // BOOL fUpdateRegisteredPorts - TRUE if existing registered ports should be // updated to reflect the new address if it // changed, FALSE if not. // // Returns: HRESULT // DPNH_OK - The update was successful. // DPNHERR_GENERIC - An error occurred. // DPNHERR_SERVERNOTRESPONDING - The server did not respond to the // message. //============================================================================= HRESULT CNATHelpUPnP::UpdateUPnPExternalAddress(CUPnPDevice * const pUPnPDevice, const BOOL fUpdateRegisteredPorts) { HRESULT hr; BOOL fStartedWaitingForControlResponse = FALSE; CDevice * pDevice; SOCKADDR_IN * psaddrinTemp; int iContentLength; TCHAR tszContentLength[32]; TCHAR tszHost[22]; // "xxx.xxx.xxx.xxx:xxxxx" + NULL termination char * pszMessage = NULL; int iMsgSize; int iPrevMsgSize = 0; int iReturn; UPNP_CONTROLRESPONSE_INFO RespInfo; DWORD dwStartTime; DWORD dwTimeout; CBilink * pBilink; CRegisteredPort * pRegisteredPort; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i)", this, pUPnPDevice, fUpdateRegisteredPorts); DNASSERT(pUPnPDevice != NULL); DNASSERT(pUPnPDevice->IsReady()); pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pDevice != NULL); DNASSERT(this->m_dwFlags & (NATHELPUPNPOBJ_INITIALIZED | NATHELPUPNPOBJ_USEUPNP)); DNASSERT(pUPnPDevice->GetServiceControlURL() != NULL); // // If the control socket got disconnected after the last message, then // reconnect. // if (! pUPnPDevice->IsConnected()) { hr = this->ReconnectUPnPControlSocket(pUPnPDevice); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reconnect UPnP control socket!"); goto Failure; } } DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); psaddrinTemp = pUPnPDevice->GetHostAddress(); wsprintf(tszHost, _T("%u.%u.%u.%u:%u"), psaddrinTemp->sin_addr.S_un.S_un_b.s_b1, psaddrinTemp->sin_addr.S_un.S_un_b.s_b2, psaddrinTemp->sin_addr.S_un.S_un_b.s_b3, psaddrinTemp->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinTemp->sin_port)); /* iContentLength = strlen("" EOL) + strlen(" " EOL) + strlen(" " EOL) + strlen(" " VAR_EXTERNALIPADDRESS_A "" EOL) + strlen(" " EOL) + strlen(" " EOL) + strlen("" EOL) + strlen(EOL); wsprintf(tszContentLength, _T("%i"), iContentLength); iMsgSize = strlen("POST ") + strlen(pUPnPDevice->GetServiceControlURL()) + strlen(" " HTTP_VERSION EOL) + strlen("HOST: ") + _tcslen(tszHost) + strlen(EOL) + strlen("CONTENT-LENGTH: ") + strlen(szContentLength) + strlen(EOL) + strlen("CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL) + strlen("SOAPACTION: " URI_CONTROL_A "#" CONTROL_QUERYSTATEVARIABLE_A "" EOL) + strlen(EOL) + iContentLength; */ iContentLength = strlen("" EOL) + strlen(" " EOL) + strlen(" GetStaticServiceURILength() + strlen("\">" EOL) + strlen(" " EOL) + strlen(" " EOL) + strlen("" EOL) + strlen(EOL); wsprintf(tszContentLength, _T("%i"), iContentLength); iMsgSize = strlen("POST ") + strlen(pUPnPDevice->GetServiceControlURL()) + strlen(" " HTTP_VERSION EOL) + strlen("HOST: ") + _tcslen(tszHost) + strlen(EOL) + strlen("CONTENT-LENGTH: ") + _tcslen(tszContentLength) + strlen(EOL) + strlen("CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL) + strlen("SOAPACTION: ") + pUPnPDevice->GetStaticServiceURILength() + strlen("#" ACTION_GETEXTERNALIPADDRESS_A EOL) + strlen(EOL) + iContentLength; // // Allocate (or reallocate) the message buffer. // if (iMsgSize > iPrevMsgSize) { if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } pszMessage = (char*) DNMalloc(iMsgSize + 1); // include room for NULL termination that isn't actually sent if (pszMessage == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } iPrevMsgSize = iMsgSize; } /* strcpy(pszMessage, "POST "); strcat(pszMessage, pUPnPDevice->GetServiceControlURL()); strcat(pszMessage, " " HTTP_VERSION EOL); strcat(pszMessage, "HOST: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszHost, (_tcslen(tszHost) + 1)); #else // ! UNICODE strcat(pszMessage, tszHost); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-LENGTH: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszContentLength, (_tcslen(tszContentLength) + 1)); #else // ! UNICODE strcat(pszMessage, tszContentLength); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL); strcat(pszMessage, "SOAPACTION: " URI_CONTROL_A "#" CONTROL_QUERYSTATEVARIABLE_A "" EOL); strcat(pszMessage, EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " VAR_EXTERNALIPADDRESS_A "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, EOL); */ strcpy(pszMessage, "POST "); strcat(pszMessage, pUPnPDevice->GetServiceControlURL()); strcat(pszMessage, " " HTTP_VERSION EOL); strcat(pszMessage, "HOST: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszHost, (_tcslen(tszHost) + 1)); #else // ! UNICODE strcat(pszMessage, tszHost); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-LENGTH: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszContentLength, (_tcslen(tszContentLength) + 1)); #else // ! UNICODE strcat(pszMessage, tszContentLength); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL); strcat(pszMessage, "SOAPACTION: "); strcat(pszMessage, pUPnPDevice->GetStaticServiceURI()); strcat(pszMessage, "#" ACTION_GETEXTERNALIPADDRESS_A EOL); strcat(pszMessage, EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " GetStaticServiceURI()); strcat(pszMessage, "\">" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, EOL); #ifdef DBG this->PrintUPnPTransactionToFile(pszMessage, iMsgSize, //"Outbound query external IP address", "Outbound get external IP address", pDevice); #endif // DBG iReturn = this->m_pfnsend(pUPnPDevice->GetControlSocket(), pszMessage, iMsgSize, 0); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending control request to UPnP device!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } if (iReturn != iMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire message (%i != %i)?!", iReturn, iMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } // // We have the lock so no one could have tried to receive data from the // control socket yet. Mark the device as waiting for a response. // ZeroMemory(&RespInfo, sizeof(RespInfo)); //pUPnPDevice->StartWaitingForControlResponse(CONTROLRESPONSETYPE_QUERYSTATEVARIABLE_EXTERNALIPADDRESS, pUPnPDevice->StartWaitingForControlResponse(CONTROLRESPONSETYPE_GETEXTERNALIPADDRESS, &RespInfo); fStartedWaitingForControlResponse = TRUE; // // Actually wait for the response. // dwStartTime = GETTIMESTAMP(); dwTimeout = g_dwUPnPResponseTimeout; do { hr = this->CheckForReceivedUPnPMsgsOnDevice(pUPnPDevice, dwTimeout); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed receiving UPnP messages!"); goto Failure; } // // We either timed out or got some data. Check if we got a // response of some type. // if (! pUPnPDevice->IsWaitingForControlResponse()) { break; } // // Make sure our device is still connected. // if (! pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 0, "UPnP device 0x%p disconnected while retrieving external IP address!", pUPnPDevice); pUPnPDevice->StopWaitingForControlResponse(); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // Calculate how long we have left to wait. If the calculation // goes negative, it means we're done. // dwTimeout = g_dwUPnPResponseTimeout - (GETTIMESTAMP() - dwStartTime); } while (((int) dwTimeout > 0)); // // If we never got the response, stop waiting for it. // if (pUPnPDevice->IsWaitingForControlResponse()) { pUPnPDevice->StopWaitingForControlResponse(); DPFX(DPFPREP, 0, "Server didn't respond in time!"); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // If we're here, then we've gotten a valid response from the server. // if (RespInfo.hrErrorCode != DPNH_OK) { DPFX(DPFPREP, 1, "Server returned failure response 0x%lx when retrieving external IP address.", RespInfo.hrErrorCode); hr = RespInfo.hrErrorCode; goto Failure; } DPFX(DPFPREP, 1, "Server returned external IP address \"%u.%u.%u.%u\".", ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b4); // // Convert the loopback address to the device address. // if (RespInfo.dwExternalIPAddressV4 == NETWORKBYTEORDER_INADDR_LOOPBACK) { RespInfo.dwExternalIPAddressV4 = pDevice->GetLocalAddressV4(); DPFX(DPFPREP, 0, "Converted private loopback address to device address (%u.%u.%u.%u)!", ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&RespInfo.dwExternalIPAddressV4))->S_un.S_un_b.s_b4); DNASSERTX(! "Got loopback address as external IP address!", 2); } #ifdef DBG // // If this is a local UPnP gateway, print out the device corresponding to // the public address. // if (pUPnPDevice->IsLocal()) { if (RespInfo.dwExternalIPAddressV4 != 0) { CDevice * pPublicDevice; // // Loop through every device. // pBilink = this->m_blDevices.GetNext(); while (pBilink != &this->m_blDevices) { pPublicDevice = DEVICE_FROM_BILINK(pBilink); if (pPublicDevice->GetLocalAddressV4() == RespInfo.dwExternalIPAddressV4) { DPFX(DPFPREP, 7, "Local UPnP gateway 0x%p for device 0x%p's public address is device 0x%p.", pUPnPDevice, pDevice, pPublicDevice); break; } pBilink = pBilink->GetNext(); } // // If we made it through the entire list without matching the device, // that's odd. It's possible we're slow in detecting new devices, so // don't get bent out of shape. // if (pBilink == &this->m_blDevices) { DPFX(DPFPREP, 0, "Couldn't match up local UPnP gateway 0x%p (device 0x%p)'s public address to a device!", pUPnPDevice, pDevice); DNASSERTX(! "Couldn't match up local UPnP gateway public address to a device!", 2); } } else { DPFX(DPFPREP, 4, "Local UPnP gateway 0x%p (device 0x%p) does not have a valid public address.", pUPnPDevice, pDevice); } } #endif // DBG // // If the public address has changed, update all the existing mappings. // if (RespInfo.dwExternalIPAddressV4 != pUPnPDevice->GetExternalIPAddressV4()) { DPFX(DPFPREP, 1, "UPnP Internet Gateway Device (0x%p) external address changed.", pUPnPDevice); // // Since there was a change in the network, go back to polling // relatively quickly. // this->ResetNextPollInterval(); // // Loop through all the existing registered ports and update their // public addresses, if allowed. // if (fUpdateRegisteredPorts) { pBilink = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilink != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); if (! pRegisteredPort->IsUPnPPortUnavailable()) { DPFX(DPFPREP, 7, "Updating registered port 0x%p's public address.", pRegisteredPort); pRegisteredPort->UpdateUPnPPublicV4Addresses(RespInfo.dwExternalIPAddressV4); // // The user should call GetCaps to detect the address change. // this->m_dwFlags |= NATHELPUPNPOBJ_ADDRESSESCHANGED; } else { DPFX(DPFPREP, 7, "Not updating registered port 0x%p's public address because the port is unavailable.", pRegisteredPort); } pBilink = pBilink->GetNext(); } } // // Store the new public address. // pUPnPDevice->SetExternalIPAddressV4(RespInfo.dwExternalIPAddressV4); } Exit: if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: // // If we started waiting for a response, clear that. // if (fStartedWaitingForControlResponse) { pUPnPDevice->StopWaitingForControlResponse(); } goto Exit; } // CNATHelpUPnP::UpdateUPnPExternalAddress #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::MapPortsOnUPnPDevice" //============================================================================= // CNATHelpUPnP::MapPortsOnUPnPDevice //----------------------------------------------------------------------------- // // Description: Maps the given ports on the given UPnP device. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device to use. // CRegisteredPort * pRegisteredPort - Pointer to ports to register. // // Returns: HRESULT // DPNH_OK - The message was sent successfully. // DPNHERR_GENERIC - An error occurred. // DPNHERR_SERVERNOTRESPONDING - A response didn't arrive in time. //============================================================================= HRESULT CNATHelpUPnP::MapPortsOnUPnPDevice(CUPnPDevice * const pUPnPDevice, CRegisteredPort * const pRegisteredPort) { HRESULT hr = DPNH_OK; HRESULT temphr; BOOL fStartedWaitingForControlResponse = FALSE; CDevice * pDevice; DWORD dwLeaseExpiration; IN_ADDR inaddrTemp; SOCKADDR_IN * psaddrinTemp; WORD wOriginalExternalPortHostOrder = 0; WORD wExternalPortHostOrder; TCHAR tszInternalPort[32]; TCHAR tszExternalPort[32]; TCHAR tszInternalClient[16]; // "xxx.xxx.xxx.xxx" + NULL termination TCHAR tszLeaseDuration[32]; TCHAR tszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; int iContentLength; TCHAR tszContentLength[32]; TCHAR tszHost[22]; // "xxx.xxx.xxx.xxx:xxxxx" + NULL termination char * pszMessage = NULL; int iMsgSize; int iPrevMsgSize = 0; int iReturn; DWORD dwTemp = 0; UPNP_CONTROLRESPONSE_INFO RespInfo; DWORD dwStartTime; DWORD dwTimeout; BOOL fFirstLease; DWORD dwDescriptionLength; #ifndef DPNBUILD_NOWINSOCK2 BOOL fResult; #endif // ! DPNBUILD_NOWINSOCK2 #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p)", this, pUPnPDevice); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); DNASSERT(pUPnPDevice->IsReady()); pDevice = pRegisteredPort->GetOwningDevice(); DNASSERT(pDevice != NULL); DNASSERT(pUPnPDevice->GetOwningDevice() == pDevice); // // Set up variables we'll need. // DNASSERT(pUPnPDevice->GetServiceControlURL() != NULL); // // If the port is shared, register the broadcast address instead of the // local device address. // if (pRegisteredPort->IsSharedPort()) { _tcscpy(tszInternalClient, _T("255.255.255.255")); } else { // // Note that the device address is not necessarily the same as the // address the user originally registered, particularly the 0.0.0.0 // wildcard address will get remapped. // inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); wsprintf(tszInternalClient, _T("%u.%u.%u.%u"), inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); } // // The current Microsoft UPnP Internet Gateway Device implementation-- // and most others-- do not support lease durations that are not set // for INFINITE time, so we will almost certainly fail. Because of // that, and since there's no server to test against anyway, we will // not even attempt to use non-INFINITE leases. However, you can use // the registry key to take a shot at it if you like living on the // edge. // Note that there's no way to detect whether a given UPnP // implementation will allow non-INFINITE lease durations ahead of time, // so we have to try it first, and fall back to the infinite lease // behavior if it doesn't work. Ugh. // if ((! pUPnPDevice->DoesNotSupportLeaseDurations()) && (g_fUseLeaseDurations)) { wsprintf(tszLeaseDuration, _T("%u"), (pRegisteredPort->GetRequestedLeaseTime() / 1000)); } else { _tcscpy(tszLeaseDuration, _T("0")); pRegisteredPort->NotePermanentUPnPLease(); } psaddrinTemp = pUPnPDevice->GetHostAddress(); wsprintf(tszHost, _T("%u.%u.%u.%u:%u"), psaddrinTemp->sin_addr.S_un.S_un_b.s_b1, psaddrinTemp->sin_addr.S_un.S_un_b.s_b2, psaddrinTemp->sin_addr.S_un.S_un_b.s_b3, psaddrinTemp->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinTemp->sin_port)); // // Create the array to hold the resulting public addresses. // hr = pRegisteredPort->CreateUPnPPublicAddressesArray(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't create UPnP public addresses array!"); goto Failure; } // // Note whether this was the first lease or not. // fFirstLease = (this->m_dwNumLeases == 0) ? TRUE : FALSE; this->m_dwNumLeases++; DPFX(DPFPREP, 7, "UPnP lease for 0x%p added, total num leases = %u.", pRegisteredPort, this->m_dwNumLeases); // // Assuming all goes well, the first port lease will expire approximately // GetRequestedLeaseTime() ms from now. // See above note about whether this lease will actually be used, though. // dwLeaseExpiration = GETTIMESTAMP() + pRegisteredPort->GetRequestedLeaseTime(); // // Get a pointer to the addresses we're mapping. We don't have to worry // about whether it's mapped on a local firewall since the HomeNet API will // always map it to the same port. // psaddrinTemp = pRegisteredPort->GetPrivateAddressesArray(); // // Loop through each port and map it. // for(dwTemp = 0; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { // // Determine the public port number to register. // if (! pRegisteredPort->IsFixedPort()) { // // UPnP does not support wildcard ports (where the gateway // device picks an unused public port number for us). We must // select a port ahead of time to try mapping on the server. // // Worse, UPnP does not require the device support selecting a // public port that is different from the client's private port // (a.k.a. asymmetric, x to y, or floating port mappings). // This means that even non fixed ports will act that way. To // top it all off, there's no way to detect whether a given // UPnP implementation will allow the ports to differ ahead of // time, so we have to try it first, and fall back to the fixed // port behavior if it doesn't work. Ugh. // if (pUPnPDevice->DoesNotSupportAsymmetricMappings()) { // // We are forced to use the client's private port. // wExternalPortHostOrder = NTOHS(psaddrinTemp[dwTemp].sin_port); } else { if (wOriginalExternalPortHostOrder == 0) { DNASSERT(dwTemp == 0); // // Ideally we would pick a random port that isn't in // the reserved range (i.e. is greater than 1024). // However, truly random ports cause problems with the // Windows XP ICS implementation in a fairly obscure // manner: // // 1. DPlay application starts hosting behind ICS on // port 2302. // 2. Port 2302 gets mapped to random port x. // 3. External DPlay client is told to connect to x. // 4. ICS detects inbound traffic to x, sees mapping // for internal_ip:2302, and creates a virtual // connection. // 5. Internal client removes 2302<->x mapping and // closes socket. // 6. Internal DPlay application begins hosting on // port 2302 again. // 7. Port 2302 gets mapped to random port y. // 8. External DPlay client is told to connect to y. // 9. ICS detects inbound traffic to y, sees mapping // for internal_ip:2302, but can't create virtual // connection because the 2302<->x connection // still exists. // // Windows XP ICS keeps the virtual connections around // and cleans them up every 60 seconds. If the // reconnect occurs within (up to) 2 minutes, the // packets might get dropped due to the mapping // collision. // // This causes all sorts of heartache with automated // NAT tests that bring up and tear down connections // between the same two machines across the NAT over // and over. // // So, to avoid that, we need to make mappings // deterministic, such that if the same internal client // tries to map the same internal port, it should ask // for the same external port every time. That way, if // there happens to be a virtual connection hanging // around from a previous attempt, we'll reuse it // instead of clashing and getting the packet dropped. // // Our actual algorithm: // 1. start with the internal client IP. // 2. combine in the internal port being mapped. // 3. add 1025 if necessary to get it out of the // reserved range. // inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); wOriginalExternalPortHostOrder = inaddrTemp.S_un.S_un_w.s_w1; wOriginalExternalPortHostOrder ^= inaddrTemp.S_un.S_un_w.s_w2; wOriginalExternalPortHostOrder ^= psaddrinTemp[dwTemp].sin_port; if (wOriginalExternalPortHostOrder <= MAX_RESERVED_PORT) { wOriginalExternalPortHostOrder += MAX_RESERVED_PORT + 1; } // // This is the starting point, we'll increment by one // as we go. // wExternalPortHostOrder = wOriginalExternalPortHostOrder; } else { // // Go to the next sequential port. If we've wrapped // around to 0, move to the first non reserved range // port. // wExternalPortHostOrder++; if (wExternalPortHostOrder == 0) { wExternalPortHostOrder = MAX_RESERVED_PORT + 1; } // // If we wrapped all the way back around to the first // port we tried, we have to fail. // if (wExternalPortHostOrder == wOriginalExternalPortHostOrder) { DPFX(DPFPREP, 0, "All ports were exhausted (before index %u), marking port as unavailable.", dwTemp); // // Delete all mappings successfully made up until // now. // DNASSERT(dwTemp > 0); hr = this->UnmapUPnPPort(pRegisteredPort, dwTemp, TRUE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed deleting %u previously mapped ports after getting failure response 0x%lx!", dwTemp, RespInfo.hrErrorCode); goto Failure; } // // The port is unavailable. // pRegisteredPort->NoteUPnPPortUnavailable(); // // We're done here. Ideally we would goto Exit, but // we want to execute the public address array // cleanup code. hr will == DPNH_OK. // goto Failure; } } // end if (haven't selected first port yet) } } else { // // Use the fixed port. // wExternalPortHostOrder = NTOHS(psaddrinTemp[dwTemp].sin_port); } wsprintf(tszInternalPort, _T("%u"), NTOHS(psaddrinTemp[dwTemp].sin_port)); Retry: // // Because the UPnP spec allows a device to overwrite existing // mappings if they are for the same client, we have to make sure // that no local DPNHUPNP instances, including this one, have an // active mapping for that public port. // Rather than having the retry code in multiple places, I'll use // the somewhat ugly 'goto' to jump straight to the existing port // unavailable handling. // if (this->IsNATPublicPortInUseLocally(wExternalPortHostOrder)) { DPFX(DPFPREP, 1, "Port %u is already in use locally."); RespInfo.hrErrorCode = DPNHERR_PORTUNAVAILABLE; goto PortUnavailable; } // // If the control socket got disconnected after the last message, // then reconnect. // if (! pUPnPDevice->IsConnected()) { hr = this->ReconnectUPnPControlSocket(pUPnPDevice); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reconnect UPnP control socket!"); goto Failure; } } DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); wsprintf(tszExternalPort, _T("%u"), wExternalPortHostOrder); // // Generate a description for this mapping. The format is: // // [executable_name] (nnn.nnn.nnn.nnn:nnnnn) nnnnn {"TCP" | "UDP"} // // That way nothing needs to be localized. // dwDescriptionLength = GetModuleFileName(NULL, tszDescription, (MAX_UPNP_MAPPING_DESCRIPTION_SIZE - 1)); if (dwDescriptionLength != 0) { // // Be paranoid and make sure the description string is valid. // tszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE - 1] = 0; // // Get just the executable name from the path. // #ifdef WINCE GetExeName(tszDescription); #else // ! WINCE #ifdef UNICODE _wsplitpath(tszDescription, NULL, NULL, tszDescription, NULL); #else // ! UNICODE _splitpath(tszDescription, NULL, NULL, tszDescription, NULL); #endif // ! UNICODE #endif // ! WINCE dwDescriptionLength = _tcslen(tszDescription) // executable name + 2 // " (" + _tcslen(tszInternalClient) // private IP address + 1 // ":" + _tcslen(tszInternalPort) // private port + 2 // ") " + _tcslen(tszExternalPort) // public port + 4; // " TCP" | " UDP" // // Make sure the long string will fit. If not, use the // abbreviated version. // if (dwDescriptionLength > MAX_UPNP_MAPPING_DESCRIPTION_SIZE) { dwDescriptionLength = 0; } } if (dwDescriptionLength == 0) { // // Use the abbreviated version we know will fit. // wsprintf(tszDescription, _T("(%s:%s) %s %s"), tszInternalClient, tszInternalPort, tszExternalPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } else { // // There's enough room, tack on the rest of the description. // wsprintf((tszDescription + _tcslen(tszDescription)), _T(" (%s:%s) %s %s"), tszInternalClient, tszInternalPort, tszExternalPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } DPFX(DPFPREP, 6, "Requesting mapping \"%s\".", tszDescription); iContentLength = strlen("" EOL) + strlen(" " EOL) + strlen(" GetStaticServiceURILength() + strlen("\">" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWEXTERNALPORT_A ">") + _tcslen(tszExternalPort) + strlen("" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWPROTOCOL_A ">") + 3 + strlen("" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWINTERNALPORT_A ">") + _tcslen(tszInternalPort) + strlen("" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWINTERNALCLIENT_A ">") + _tcslen(tszInternalClient) + strlen("" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWENABLED_A ">" UPNP_BOOLEAN_TRUE "" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWPORTMAPPINGDESCRIPTION_A ">") + _tcslen(tszDescription) + strlen("" EOL) + strlen(" <" ARG_ADDPORTMAPPING_NEWLEASEDURATION_A ">") + _tcslen(tszLeaseDuration) + strlen("" EOL) + strlen(" " EOL) + strlen(" " EOL) + strlen("" EOL) + strlen(EOL); wsprintf(tszContentLength, _T("%i"), iContentLength); iMsgSize = strlen("POST ") + strlen(pUPnPDevice->GetServiceControlURL()) + strlen(" " HTTP_VERSION EOL) + strlen("HOST: ") + _tcslen(tszHost) + strlen(EOL) + strlen("CONTENT-LENGTH: ") + _tcslen(tszContentLength) + strlen(EOL) + strlen("CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL) + strlen("SOAPACTION: ") + pUPnPDevice->GetStaticServiceURILength() + strlen("#" ACTION_ADDPORTMAPPING_A EOL) + strlen(EOL) + iContentLength; // // Allocate (or reallocate) the message buffer. // if (iMsgSize > iPrevMsgSize) { if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } pszMessage = (char*) DNMalloc(iMsgSize + 1); // include room for NULL termination that isn't actually sent if (pszMessage == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } iPrevMsgSize = iMsgSize; } strcpy(pszMessage, "POST "); strcat(pszMessage, pUPnPDevice->GetServiceControlURL()); strcat(pszMessage, " " HTTP_VERSION EOL); strcat(pszMessage, "HOST: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszHost, (_tcslen(tszHost) + 1)); #else // ! UNICODE strcat(pszMessage, tszHost); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-LENGTH: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszContentLength, (_tcslen(tszContentLength) + 1)); #else // ! UNICODE strcat(pszMessage, tszContentLength); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL); strcat(pszMessage, "SOAPACTION: "); strcat(pszMessage, pUPnPDevice->GetStaticServiceURI()); strcat(pszMessage, "#" ACTION_ADDPORTMAPPING_A EOL); strcat(pszMessage, EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " GetStaticServiceURI()); strcat(pszMessage, "\">" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWEXTERNALPORT_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszExternalPort, (_tcslen(tszExternalPort) + 1)); #else // ! UNICODE strcat(pszMessage, tszExternalPort); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWPROTOCOL_A ">"); strcat(pszMessage, ((pRegisteredPort->IsTCP()) ? "TCP" : "UDP")); strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWINTERNALPORT_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszInternalPort, (_tcslen(tszInternalPort) + 1)); #else // ! UNICODE strcat(pszMessage, tszInternalPort); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWINTERNALCLIENT_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszInternalClient, (_tcslen(tszInternalClient) + 1)); #else // ! UNICODE strcat(pszMessage, tszInternalClient); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWENABLED_A ">" UPNP_BOOLEAN_TRUE "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWPORTMAPPINGDESCRIPTION_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszDescription, (_tcslen(tszDescription) + 1)); #else // ! UNICODE strcat(pszMessage, tszDescription); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWLEASEDURATION_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszLeaseDuration, (_tcslen(tszLeaseDuration) + 1)); #else // ! UNICODE strcat(pszMessage, tszLeaseDuration); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, EOL); #ifdef DBG this->PrintUPnPTransactionToFile(pszMessage, iMsgSize, "Outbound add port mapping request", pDevice); #endif // DBG iReturn = this->m_pfnsend(pUPnPDevice->GetControlSocket(), pszMessage, iMsgSize, 0); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending control request to UPnP device!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } if (iReturn != iMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire message (%i != %i)?!", iReturn, iMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } // // We have the lock so no one could have tried to receive data from // the control socket yet. Mark the device as waiting for a // response. // ZeroMemory(&RespInfo, sizeof(RespInfo)); pUPnPDevice->StartWaitingForControlResponse(CONTROLRESPONSETYPE_ADDPORTMAPPING, &RespInfo); fStartedWaitingForControlResponse = TRUE; // // Actually wait for the response. // dwStartTime = GETTIMESTAMP(); dwTimeout = g_dwUPnPResponseTimeout; do { hr = this->CheckForReceivedUPnPMsgsOnDevice(pUPnPDevice, dwTimeout); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed receiving UPnP messages!"); goto Failure; } // // We either timed out or got some data. Check if we got the // response we need. // if (! pUPnPDevice->IsWaitingForControlResponse()) { if (RespInfo.hrErrorCode != DPNH_OK) { // // Make sure it's not the "I can't handle asymmetric // mappings" error. If it is, note the fact that this // device is going to force us to have worse behavior // and try again. // if (RespInfo.hrErrorCode == (HRESULT) UPNPERR_IGD_SAMEPORTVALUESREQUIRED) { DPFX(DPFPREP, 1, "UPnP device 0x%p does not support asymmetric mappings.", pUPnPDevice); // // Make sure we're not getting this error from a bad // device. Otherwise we might get caught in a loop. // if ((! pUPnPDevice->DoesNotSupportAsymmetricMappings()) && (! pRegisteredPort->IsFixedPort()) && (dwTemp == 0)) { // // Remember that this device is unfriendly. // pUPnPDevice->NoteDoesNotSupportAsymmetricMappings(); // // Use the same port externally next time. // DNASSERT(wExternalPortHostOrder != NTOHS(psaddrinTemp[dwTemp].sin_port)); wExternalPortHostOrder = NTOHS(psaddrinTemp[dwTemp].sin_port); // // Try again. // goto Retry; } DPFX(DPFPREP, 1, "DoesNotSupportAsymmetricMappings = %i, fixed port = %i, port index = %u", pUPnPDevice->DoesNotSupportAsymmetricMappings(), pRegisteredPort->IsFixedPort(), dwTemp); DNASSERTX(! "Getting UPNPERR_IGD_SAMEPORTVALUESREQUIRED from bad device!", 2); // // Continue through to failure case... // } // // Make sure it's not the "I can't handle lease times" // error. If it is, note the fact that this device is // going to force us to have worse behavior and try // again. // if (RespInfo.hrErrorCode == (HRESULT) UPNPERR_IGD_ONLYPERMANENTLEASESSUPPORTED) { DPFX(DPFPREP, 1, "UPnP device 0x%p does not support non-INFINITE lease durations.", pUPnPDevice); // // Make sure we're not getting this error from a bad // device. Otherwise we might get caught in a loop. // if ((! pUPnPDevice->DoesNotSupportLeaseDurations()) && (dwTemp == 0)) { // // Remember that this device is unfriendly. // pUPnPDevice->NoteDoesNotSupportLeaseDurations(); // // Use an INFINITE lease next time around. // DNASSERT(_tcscmp(tszLeaseDuration, _T("0")) != 0); _tcscpy(tszLeaseDuration, _T("0")); pRegisteredPort->NotePermanentUPnPLease(); // // Try again. // goto Retry; } DPFX(DPFPREP, 1, "DoesNotSupportLeaseDurations = %i, port index = %u", pUPnPDevice->DoesNotSupportLeaseDurations(), dwTemp); DNASSERTX(! "Getting UPNPERR_IGD_ONLYPERMANENTLEASESSUPPORTED from bad device!", 2); // // Continue through to failure case... // } #ifdef DBG if (RespInfo.hrErrorCode == DPNHERR_PORTUNAVAILABLE) { DPFX(DPFPREP, 2, "Port %u (for address index %u) is reportedly unavailable.", wExternalPortHostOrder, dwTemp); } #endif // DBG PortUnavailable: // // If it's the port-unavailable error but we're able to // try a different port, go for it. // if ((RespInfo.hrErrorCode == DPNHERR_PORTUNAVAILABLE) && (! pRegisteredPort->IsFixedPort()) && (! pUPnPDevice->DoesNotSupportAsymmetricMappings())) { // // Go to the next sequential port. If we've wrapped // around to 0, move to the first non reserved range // port. // wExternalPortHostOrder++; if (wExternalPortHostOrder == 0) { wExternalPortHostOrder = MAX_RESERVED_PORT + 1; } // // If we haven't wrapped all the way back around to // the first port we tried, try again. // if (wExternalPortHostOrder != wOriginalExternalPortHostOrder) { DPFX(DPFPREP, 2, "Retrying next port (%u) for index %u.", wExternalPortHostOrder, dwTemp); goto Retry; } DPFX(DPFPREP, 0, "All ports were exhausted (after index %u), marking port as unavailable.", dwTemp); } // // If it's not the port-unavailable error, it's // serious. Bail. // if (RespInfo.hrErrorCode != DPNHERR_PORTUNAVAILABLE) { DPFX(DPFPREP, 1, "Port index %u got failure response 0x%lx.", dwTemp, RespInfo.hrErrorCode); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // The port is unavailable. // pRegisteredPort->NoteUPnPPortUnavailable(); // // We're done here. Ideally we would goto Exit, but we // want to execute the public address array cleanup // code. hr should == DPNH_OK. // DNASSERT(hr == DPNH_OK); goto Failure; } // // If we got here, we successfully registered this port. // // // UPnP Internet Gateway Device mapping protocol doesn't // hand you the external IP address in the success response // message. We had to have known it through other means // (querying the ExternalIPAddress variable). // DPFX(DPFPREP, 2, "Port index %u got success response.", dwTemp); pRegisteredPort->SetUPnPPublicV4Address(dwTemp, pUPnPDevice->GetExternalIPAddressV4(), HTONS(wExternalPortHostOrder)); // // If the lease is permanent and it's not for a shared // port, we need to remember it, in case we crash before // cleaning it up in this session. That we can clean it up // next time we launch. // if ((pRegisteredPort->HasPermanentUPnPLease()) && (! pRegisteredPort->IsSharedPort())) { CRegistry RegObject; DPNHACTIVENATMAPPING dpnhanm; #ifndef UNICODE WCHAR wszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; #endif // ! UNICODE DPFX(DPFPREP, 7, "Remembering NAT lease \"%s\" in case of crash.", tszDescription); if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVENATMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 0, "Couldn't open active NAT mapping key, unable to save in case of crash!"); } else { #ifndef UNICODE dwDescriptionLength = strlen(tszDescription) + 1; hr = STR_jkAnsiToWide(wszDescription, tszDescription, dwDescriptionLength); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't convert NAT mapping description to Unicode (err = 0x%lx), unable to save in case of crash!", hr); // // Ignore error and continue. // hr = S_OK; } else #endif // ! UNICODE { DNASSERT(this->m_dwInstanceKey != 0); ZeroMemory(&dpnhanm, sizeof(dpnhanm)); dpnhanm.dwVersion = ACTIVE_MAPPING_VERSION; dpnhanm.dwInstanceKey = this->m_dwInstanceKey; dpnhanm.dwUPnPDeviceID = pUPnPDevice->GetID(); dpnhanm.dwFlags = pRegisteredPort->GetFlags(); dpnhanm.dwInternalAddressV4 = pDevice->GetLocalAddressV4(); dpnhanm.wInternalPort = psaddrinTemp[dwTemp].sin_port; dpnhanm.dwExternalAddressV4 = pUPnPDevice->GetExternalIPAddressV4(); dpnhanm.wExternalPort = HTONS(wExternalPortHostOrder); #ifdef UNICODE RegObject.WriteBlob(tszDescription, #else // ! UNICODE RegObject.WriteBlob(wszDescription, #endif // ! UNICODE (LPBYTE) (&dpnhanm), sizeof(dpnhanm)); } RegObject.Close(); } } // // Break out of the wait loop. // break; } // // Make sure our device is still connected. // if (! pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 0, "UPnP device 0x%p disconnected while adding port index %u!", pUPnPDevice, dwTemp); pUPnPDevice->StopWaitingForControlResponse(); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // Calculate how long we have left to wait. If the calculation // goes negative, it means we're done. // dwTimeout = g_dwUPnPResponseTimeout - (GETTIMESTAMP() - dwStartTime); } while (((int) dwTimeout > 0)); // // If we never got the response, stop waiting for it. // if (pUPnPDevice->IsWaitingForControlResponse()) { pUPnPDevice->StopWaitingForControlResponse(); DPFX(DPFPREP, 0, "Port index %u didn't get response in time!", dwTemp); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // Continue on to the next port. // } // // If we're here, all ports were successfully registered. // if (pRegisteredPort->HasPermanentUPnPLease()) { DPFX(DPFPREP, 3, "All %u ports successfully registered with UPnP device (no expiration).", dwTemp); } else { pRegisteredPort->SetUPnPLeaseExpiration(dwLeaseExpiration); DPFX(DPFPREP, 3, "All %u ports successfully registered with UPnP device, expiration = %u.", dwTemp, dwLeaseExpiration); // // Remember this expiration time if it's the one that's going to expire // soonest. // if ((fFirstLease) || ((int) (dwLeaseExpiration - this->m_dwEarliestLeaseExpirationTime) < 0)) { if (fFirstLease) { DPFX(DPFPREP, 1, "Registered port 0x%p's UPnP lease is the first lease (expires at %u).", pRegisteredPort, dwLeaseExpiration); } else { DPFX(DPFPREP, 1, "Registered port 0x%p's UPnP lease expires at %u which is earlier than the next earliest lease expiration (%u).", pRegisteredPort, dwLeaseExpiration, this->m_dwEarliestLeaseExpirationTime); } this->m_dwEarliestLeaseExpirationTime = dwLeaseExpiration; #ifndef DPNBUILD_NOWINSOCK2 // // Ping the event if there is one so that the user's GetCaps // interval doesn't miss this new, shorter lease. // if (this->m_hAlertEvent != NULL) { fResult = SetEvent(this->m_hAlertEvent); #ifdef DBG if (! fResult) { dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't set alert event 0x%p! err = %u", this->m_hAlertEvent, dwError); // // Ignore failure... // } #endif // DBG } // // Ping the I/O completion port if there is one so that the user's // GetCaps interval doesn't miss this new, shorter lease. // if (this->m_hAlertIOCompletionPort != NULL) { fResult = PostQueuedCompletionStatus(this->m_hAlertIOCompletionPort, 0, this->m_dwAlertCompletionKey, NULL); #ifdef DBG if (! fResult) { dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't queue key %u on alert IO completion port 0x%p! err = %u", this->m_dwAlertCompletionKey, this->m_hAlertIOCompletionPort, dwError); // // Ignore failure... // } #endif // DBG } #endif // ! DPNBUILD_NOWINSOCK2 } else { // // Not first or shortest lease. // } } // end if (not permanent UPnP lease) Exit: if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: // // If we allocated the array, clean it up. // if (pRegisteredPort->HasUPnPPublicAddresses()) { // // If we started waiting for a response, clear that. // if (fStartedWaitingForControlResponse) { pUPnPDevice->StopWaitingForControlResponse(); } // // Delete all mappings successfully made up until now. // if (dwTemp > 0) { temphr = this->UnmapUPnPPort(pRegisteredPort, dwTemp, TRUE); if (temphr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed deleting %u previously mapped ports! Err = 0x%lx", dwTemp, temphr); if (hr == DPNH_OK) { hr = temphr; } } } else { // // Remove the addresses array we created. // pRegisteredPort->DestroyUPnPPublicAddressesArray(); // // Remove the lease counter. // DNASSERT(this->m_dwNumLeases > 0); this->m_dwNumLeases--; DPFX(DPFPREP, 7, "UPnP lease for 0x%p removed, total num leases = %u.", pRegisteredPort, this->m_dwNumLeases); } } // // Turn off the permanent lease flag we may have turned on at the top of // this function. // pRegisteredPort->NoteNotPermanentUPnPLease(); goto Exit; } // CNATHelpUPnP::MapPortsOnUPnPDevice #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::InternalUPnPQueryAddress" //============================================================================= // CNATHelpUPnP::InternalUPnPQueryAddress //----------------------------------------------------------------------------- // // Description: Queries a port mapping with a UPnP device. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device that // should be queried. // SOCKADDR_IN * psaddrinQueryAddress - Address to look up. // SOCKADDR_IN * psaddrinResponseAddress - Place to store public address, if // one exists. // DWORD dwFlags - Flags to use when querying. // // Returns: HRESULT // DPNH_OK - The query was successful. // DPNHERR_GENERIC - An error occurred. // DPNHERR_NOMAPPING - The server did not have a mapping for the // given address. // DPNHERR_NOMAPPINGBUTPRIVATE - The server indicated that no mapping was // found, but it is a private address. // DPNHERR_SERVERNOTRESPONDING - The server did not respond to the // message. //============================================================================= HRESULT CNATHelpUPnP::InternalUPnPQueryAddress(CUPnPDevice * const pUPnPDevice, const SOCKADDR_IN * const psaddrinQueryAddress, SOCKADDR_IN * const psaddrinResponseAddress, const DWORD dwFlags) { HRESULT hr; BOOL fStartedWaitingForControlResponse = FALSE; BOOL fNoPortMapping = FALSE; CDevice * pDevice; CBilink * pblCachedMaps; DWORD dwCurrentTime; CBilink * pBilink; CCacheMap * pCacheMap; SOCKADDR_IN * psaddrinTemp; TCHAR tszExternalPort[32]; int iContentLength; TCHAR tszContentLength[32]; TCHAR tszHost[22]; // "xxx.xxx.xxx.xxx:xxxxx" + NULL termination char * pszMessage = NULL; int iMsgSize; int iPrevMsgSize = 0; int iReturn; UPNP_CONTROLRESPONSE_INFO RespInfo; DWORD dwStartTime; DWORD dwTimeout; DWORD dwCacheMapFlags; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, 0x%lx)", this, pUPnPDevice, psaddrinQueryAddress, psaddrinResponseAddress, dwFlags); DNASSERT(pUPnPDevice != NULL); pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pDevice != NULL); DNASSERT(pUPnPDevice->IsReady()); DNASSERT(psaddrinQueryAddress != NULL); DNASSERT(psaddrinResponseAddress != NULL); DNASSERT(this->m_dwFlags & (NATHELPUPNPOBJ_INITIALIZED | NATHELPUPNPOBJ_USEUPNP)); DPFX(DPFPREP, 7, "Querying for address %u.%u.%u.%u:%u %hs.", psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinQueryAddress->sin_port), ((dwFlags & DPNHQUERYADDRESS_TCP) ? "TCP" : "UDP")); // // First, check if we've looked this address up recently and already have // the result cached. // The lock is already held. // pblCachedMaps = pUPnPDevice->GetCachedMaps(); dwCurrentTime = GETTIMESTAMP(); pBilink = pblCachedMaps->GetNext(); while (pBilink != pblCachedMaps) { DNASSERT(! pBilink->IsEmpty()); pCacheMap = CACHEMAP_FROM_BILINK(pBilink); pBilink = pBilink->GetNext(); // // Make sure this cached mapping hasn't expired. // if ((int) (pCacheMap->GetExpirationTime() - dwCurrentTime) < 0) { DPFX(DPFPREP, 5, "Cached mapping 0x%p has expired.", pCacheMap); pCacheMap->m_blList.RemoveFromList(); delete pCacheMap; } else { // // If this mapping is for the right address and type of address, // then we've already got our answer. // if (pCacheMap->DoesMatchQuery(psaddrinQueryAddress, dwFlags)) { if (pCacheMap->IsNotFound()) { if ((dwFlags & DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED) && (pCacheMap->IsPrivateButUnmapped())) { DPFX(DPFPREP, 5, "Address was already determined to not have a mapping but still be private."); hr = DPNHERR_NOMAPPINGBUTPRIVATE; } else { DPFX(DPFPREP, 5, "Address was already determined to not have a mapping."); hr = DPNHERR_NOMAPPING; } } else { pCacheMap->GetResponseAddressV4(psaddrinResponseAddress); DPFX(DPFPREP, 5, "Address was already determined to have a mapping."); hr = DPNH_OK; } goto Exit; } } } // // If the address we're querying isn't the NAT's public address, it can't // possibly be mapped. So only perform the actual query if it's // appropriate. // if (psaddrinQueryAddress->sin_addr.S_un.S_addr == pUPnPDevice->GetExternalIPAddressV4()) { // // If we're here, we haven't already cached the answer. Query the UPnP // device. // DNASSERT(pUPnPDevice->GetServiceControlURL() != NULL); // // If the control socket got disconnected after the last message, // then reconnect. // if (! pUPnPDevice->IsConnected()) { hr = this->ReconnectUPnPControlSocket(pUPnPDevice); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reconnect UPnP control socket!"); goto Failure; } } DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); wsprintf(tszExternalPort, _T("%u"), NTOHS(psaddrinQueryAddress->sin_port)); psaddrinTemp = pUPnPDevice->GetHostAddress(); wsprintf(tszHost, _T("%u.%u.%u.%u:%u"), psaddrinTemp->sin_addr.S_un.S_un_b.s_b1, psaddrinTemp->sin_addr.S_un.S_un_b.s_b2, psaddrinTemp->sin_addr.S_un.S_un_b.s_b3, psaddrinTemp->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinTemp->sin_port)); iContentLength = strlen("" EOL) + strlen(" " EOL) + strlen(" GetStaticServiceURILength() + strlen("\">" EOL) + strlen(" <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWREMOTEHOST_A ">" UPNP_WILDCARD "" EOL) + strlen(" <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWEXTERNALPORT_A ">") + _tcslen(tszExternalPort) + strlen("" EOL) + strlen(" <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPROTOCOL_A ">") + 3 + strlen("" EOL) + strlen(" " EOL) + strlen(" " EOL) + strlen("" EOL) + strlen(EOL); wsprintf(tszContentLength, _T("%i"), iContentLength); iMsgSize = strlen("POST ") + strlen(pUPnPDevice->GetServiceControlURL()) + strlen(" " HTTP_VERSION EOL) + strlen("HOST: ") + _tcslen(tszHost) + strlen(EOL) + strlen("CONTENT-LENGTH: ") + _tcslen(tszContentLength) + strlen(EOL) + strlen("CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL) + strlen("SOAPACTION: ") + pUPnPDevice->GetStaticServiceURILength() + strlen("#" ACTION_GETSPECIFICPORTMAPPINGENTRY_A EOL) + strlen(EOL) + iContentLength; // // Allocate (or reallocate) the message buffer. // if (iMsgSize > iPrevMsgSize) { if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } pszMessage = (char*) DNMalloc(iMsgSize + 1); // include room for NULL termination that isn't actually sent if (pszMessage == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } iPrevMsgSize = iMsgSize; } strcpy(pszMessage, "POST "); strcat(pszMessage, pUPnPDevice->GetServiceControlURL()); strcat(pszMessage, " " HTTP_VERSION EOL); strcat(pszMessage, "HOST: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszHost, (_tcslen(tszHost) + 1)); #else // ! UNICODE strcat(pszMessage, tszHost); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-LENGTH: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszContentLength, (_tcslen(tszContentLength) + 1)); #else // ! UNICODE strcat(pszMessage, tszContentLength); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL); strcat(pszMessage, "SOAPACTION: "); strcat(pszMessage, pUPnPDevice->GetStaticServiceURI()); strcat(pszMessage, "#" ACTION_GETSPECIFICPORTMAPPINGENTRY_A EOL); strcat(pszMessage, EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " GetStaticServiceURI()); strcat(pszMessage, "\">" EOL); strcat(pszMessage, " <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWREMOTEHOST_A ">" UPNP_WILDCARD "" EOL); strcat(pszMessage, " <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWEXTERNALPORT_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszExternalPort, (_tcslen(tszExternalPort) + 1)); #else // ! UNICODE strcat(pszMessage, tszExternalPort); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPROTOCOL_A ">"); strcat(pszMessage, ((dwFlags & DPNHQUERYADDRESS_TCP) ? "TCP" : "UDP")); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, EOL); #ifdef DBG this->PrintUPnPTransactionToFile(pszMessage, iMsgSize, "Outbound get port mapping request", pDevice); #endif // DBG iReturn = this->m_pfnsend(pUPnPDevice->GetControlSocket(), pszMessage, iMsgSize, 0); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending control request to UPnP device!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } if (iReturn != iMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire message (%i != %i)?!", iReturn, iMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } // // We have the lock so no one could have tried to receive data from // the control socket yet. Mark the device as waiting for a response. // ZeroMemory(&RespInfo, sizeof(RespInfo)); pUPnPDevice->StartWaitingForControlResponse(CONTROLRESPONSETYPE_GETSPECIFICPORTMAPPINGENTRY, &RespInfo); fStartedWaitingForControlResponse = TRUE; // // Actually wait for the response. // dwStartTime = GETTIMESTAMP(); dwTimeout = g_dwUPnPResponseTimeout; do { hr = this->CheckForReceivedUPnPMsgsOnDevice(pUPnPDevice, dwTimeout); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed receiving UPnP messages!"); goto Failure; } // // We either timed out or got some data. Check if we got a // response of some type. // if (! pUPnPDevice->IsWaitingForControlResponse()) { break; } // // Make sure our device is still connected. // if (! pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 0, "UPnP device 0x%p disconnected while querying port!", pUPnPDevice); pUPnPDevice->StopWaitingForControlResponse(); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // Calculate how long we have left to wait. If the calculation // goes negative, it means we're done. // dwTimeout = g_dwUPnPResponseTimeout - (GETTIMESTAMP() - dwStartTime); } while (((int) dwTimeout > 0)); // // If we never got the response, stop waiting for it. // if (pUPnPDevice->IsWaitingForControlResponse()) { pUPnPDevice->StopWaitingForControlResponse(); DPFX(DPFPREP, 0, "Server didn't respond in time!"); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // If we're here, then we've gotten a valid response from the server. // if (RespInfo.hrErrorCode != DPNH_OK) { DPFX(DPFPREP, 1, "Server returned failure response 0x%lx, assuming no port mapping.", RespInfo.hrErrorCode); fNoPortMapping = TRUE; } } else { DPFX(DPFPREP, 1, "Address %u.%u.%u.%u doesn't match NAT's external IP address, not querying.", psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinQueryAddress->sin_addr.S_un.S_un_b.s_b4); fNoPortMapping = TRUE; } dwCacheMapFlags = QUERYFLAGSMASK(dwFlags); // // Determine address locality (if requested) and cache the no-mapping // result. // if (fNoPortMapping) { // // Try determining if the address is local, if allowed. // if (dwFlags & DPNHQUERYADDRESS_CHECKFORPRIVATEBUTUNMAPPED) { if (this->IsAddressLocal(pDevice, psaddrinQueryAddress)) { DPFX(DPFPREP, 5, "Address appears to be local, returning NOMAPPINGBUTPRIVATE."); dwCacheMapFlags |= CACHEMAPOBJ_PRIVATEBUTUNMAPPED; hr = DPNHERR_NOMAPPINGBUTPRIVATE; } else { DPFX(DPFPREP, 5, "Address does not appear to be local, returning NOMAPPING."); hr = DPNHERR_NOMAPPING; } } else { hr = DPNHERR_NOMAPPING; } // // Cache the fact that we could not determine a mapping for that // address, if allowed. // if (dwFlags & DPNHQUERYADDRESS_CACHENOTFOUND) { pCacheMap = new CCacheMap(psaddrinQueryAddress, (GETTIMESTAMP() + g_dwCacheLifeNotFound), (dwCacheMapFlags | CACHEMAPOBJ_NOTFOUND)); if (pCacheMap == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pCacheMap->m_blList.InsertBefore(pblCachedMaps); } goto Failure; } DPFX(DPFPREP, 1, "Server returned a private mapping (%u.%u.%u.%u:%u).", ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b4, NTOHS(RespInfo.wInternalPort)); // // Convert the loopback address to the device address. // if (RespInfo.dwInternalClientV4 == NETWORKBYTEORDER_INADDR_LOOPBACK) { RespInfo.dwInternalClientV4 = pDevice->GetLocalAddressV4(); DPFX(DPFPREP, 1, "Converted private loopback address to device address (%u.%u.%u.%u).", ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b4); } // // If the UPnP device doesn't support asymmetric mappings and thus didn't // return a port, or it did return one but it's the bogus port 0, assume // the internal port is the same port as the external one. // if (RespInfo.wInternalPort == 0) { RespInfo.wInternalPort = psaddrinQueryAddress->sin_port; DPFX(DPFPREP, 2, "Converted invalid internal port to the query address public port (%u).", NTOHS(psaddrinQueryAddress->sin_port)); } // // Ensure that we're not getting something bogus. // SOCKADDR_IN saddrinTemp; saddrinTemp.sin_addr.S_un.S_addr = RespInfo.dwInternalClientV4; if ((RespInfo.dwInternalClientV4 == 0) || (! this->IsAddressLocal(pDevice, &saddrinTemp))) { // // If the returned address is the same as the NAT's public address, // it's probably Windows ICS returning an ICF mapping. We still treat // it as an invalid mapping, but we will cache the results since there // are legimitate cases where we can see this. // if (RespInfo.dwInternalClientV4 == pUPnPDevice->GetExternalIPAddressV4()) { DPFX(DPFPREP, 1, "UPnP device returned its public address as the private address (%u.%u.%u.%u:%u). Probably ICS + ICF, but treating as no mapping.", ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b4, NTOHS(RespInfo.wInternalPort)); DNASSERTX(! "UPnP device returned public address as the private address!", 3); // // Cache the fact that we did not get a valid mapping for that // address, if allowed. // if (dwFlags & DPNHQUERYADDRESS_CACHENOTFOUND) { pCacheMap = new CCacheMap(psaddrinQueryAddress, (GETTIMESTAMP() + g_dwCacheLifeNotFound), (dwCacheMapFlags | CACHEMAPOBJ_NOTFOUND)); if (pCacheMap == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pCacheMap->m_blList.InsertBefore(pblCachedMaps); } } else { DPFX(DPFPREP, 0, "UPnP device returned an invalid private address (%u.%u.%u.%u:%u)! Assuming no mapping.", ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&RespInfo.dwInternalClientV4))->S_un.S_un_b.s_b4, NTOHS(RespInfo.wInternalPort)); DNASSERTX(! "Why is UPnP device returning invalid private address?", 2); } hr = DPNHERR_NOMAPPING; goto Failure; } // // Return the address mapping to our caller. // ZeroMemory(psaddrinResponseAddress, sizeof(SOCKADDR_IN)); psaddrinResponseAddress->sin_family = AF_INET; psaddrinResponseAddress->sin_addr.s_addr = RespInfo.dwInternalClientV4; psaddrinResponseAddress->sin_port = RespInfo.wInternalPort; // // Cache the fact that we found a mapping for that address, if allowed. // if (dwFlags & DPNHQUERYADDRESS_CACHEFOUND) { pCacheMap = new CCacheMap(psaddrinQueryAddress, (GETTIMESTAMP() + g_dwCacheLifeFound), dwCacheMapFlags); if (pCacheMap == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pCacheMap->SetResponseAddressV4(psaddrinResponseAddress->sin_addr.S_un.S_addr, psaddrinResponseAddress->sin_port); pCacheMap->m_blList.InsertBefore(pblCachedMaps); } Exit: if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: // // If we started waiting for a response, clear that. // if (fStartedWaitingForControlResponse) { pUPnPDevice->StopWaitingForControlResponse(); } goto Exit; } // CNATHelpUPnP::InternalUPnPQueryAddress #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ExtendUPnPLease" //============================================================================= // CNATHelpUPnP::ExtendUPnPLease //----------------------------------------------------------------------------- // // Description: Asks the UPnP server to extend a port mapping lease. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference if it's using the // pointer. // // The object lock is assumed to be held. // // Arguments: // CRegisteredPort * pRegisteredPort - Pointer to port object mapping to // extend. // // Returns: HRESULT // DPNH_OK - The extension was successful. // DPNHERR_GENERIC - An error occurred. // DPNHERR_SERVERNOTRESPONDING - The server did not respond to the // message. //============================================================================= HRESULT CNATHelpUPnP::ExtendUPnPLease(CRegisteredPort * const pRegisteredPort) { HRESULT hr; CDevice * pDevice; CUPnPDevice * pUPnPDevice; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p)", this, pRegisteredPort); DNASSERT(pRegisteredPort != NULL); pDevice = pRegisteredPort->GetOwningDevice(); DNASSERT(pDevice != NULL); pUPnPDevice = pDevice->GetUPnPDevice(); DNASSERT(pUPnPDevice != NULL); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); #ifdef DBG if (pRegisteredPort->HasPermanentUPnPLease()) { DPFX(DPFPREP, 1, "Extending already permanent UPnP lease for registered port 0x%p.", pRegisteredPort); } #endif // DBG // // UPnP devices don't have port extension per se, you just reregister the // mapping. // hr = this->MapPortsOnUPnPDevice(pUPnPDevice, pRegisteredPort); DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; } // CNATHelpUPnP::ExtendUPnPLease #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::UnmapUPnPPort" //============================================================================= // CNATHelpUPnP::UnmapUPnPPort //----------------------------------------------------------------------------- // // Description: Asks the UPnP server to release a port mapping. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference if it's using the // device. // // The object lock is assumed to be held. // // Arguments: // CRegisteredPort * pRegisteredPort - Pointer to port object mapping to // release. // DWORD dwMaxValidPort - Highest address index in array to // try freeing. This may be one more // than the actual number to indicate // all should be freed. // BOOL fNeedToDeleteRegValue - Whether the corresponding crash // recovery registry value needs to // be deleted as well. // // Returns: HRESULT // DPNH_OK - The extension was successful. // DPNHERR_GENERIC - An error occurred. // DPNHERR_SERVERNOTRESPONDING - The server did not respond to the // message. //============================================================================= HRESULT CNATHelpUPnP::UnmapUPnPPort(CRegisteredPort * const pRegisteredPort, const DWORD dwMaxValidPort, const BOOL fNeedToDeleteRegValue) { HRESULT hr = DPNH_OK; BOOL fStartedWaitingForControlResponse = FALSE; CDevice * pDevice; CUPnPDevice * pUPnPDevice; SOCKADDR_IN * psaddrinPublic; SOCKADDR_IN * psaddrinPrivate; TCHAR tszExternalPort[32]; int iContentLength; TCHAR tszContentLength[32]; TCHAR tszHost[22]; // "xxx.xxx.xxx.xxx:xxxxx" + NULL termination char * pszMessage = NULL; int iMsgSize; int iPrevMsgSize = 0; int iReturn; DWORD dwTemp; UPNP_CONTROLRESPONSE_INFO RespInfo; DWORD dwStartTime; DWORD dwTimeout; SOCKADDR_IN * psaddrinHostAddress; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i, %i)", this, pRegisteredPort, ((int) dwMaxValidPort), fNeedToDeleteRegValue); DNASSERT(pRegisteredPort != NULL); DNASSERT(dwMaxValidPort != 0); DNASSERT(dwMaxValidPort <= pRegisteredPort->GetNumAddresses()); pDevice = pRegisteredPort->GetOwningDevice(); DNASSERT(pDevice != NULL); pUPnPDevice = pDevice->GetUPnPDevice(); DNASSERT(pUPnPDevice != NULL); // // GetUPnPDevice did not add a reference to pUPnPDevice for us. // pUPnPDevice->AddRef(); DNASSERT(pUPnPDevice->IsReady()); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); // // Prevent trying to remove the lease twice. // pRegisteredPort->NoteRemovingUPnPLease(); if (dwMaxValidPort == pRegisteredPort->GetNumAddresses()) { DPFX(DPFPREP, 7, "Unmapping all %u addresses for registered port 0x%p on UPnP device 0x%p.", dwMaxValidPort, pRegisteredPort, pUPnPDevice); } else { DPFX(DPFPREP, 7, "Error cleanup code, only unmapping first %u addresses (of %u possible) for registered port 0x%p on UPnP device 0x%p.", dwMaxValidPort, pRegisteredPort->GetNumAddresses(), pRegisteredPort, pUPnPDevice); } // // Set up variables we'll need. // DNASSERT(pUPnPDevice->GetServiceControlURL() != NULL); psaddrinHostAddress = pUPnPDevice->GetHostAddress(); wsprintf(tszHost, _T("%u.%u.%u.%u:%u"), psaddrinHostAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinHostAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinHostAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinHostAddress->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinHostAddress->sin_port)); // // Get the array of ports we're releasing. // psaddrinPublic = pRegisteredPort->GetUPnPPublicAddressesArray(); psaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); // // Loop through each port that we're unmapping. // for(dwTemp = 0; dwTemp < dwMaxValidPort; dwTemp++) { // // The UPnP Internet Gateway Device spec does not require reference // counting for mapped ports. If you register something that had // already been registered, it will succeed silently. // // This means that we will never be able to tell which NAT client was // the last person to use a given shared port. You could try detecting // any other users at the app level (above DPNATHLP), but there is // always a race condition. You could also have a concept of shared- // port owner, but then you'd have to implement owner migration a la // DPlay host migration. That is sooo not worth it. // // The other option is to just never unmap shared ports. You can // probably imagine the implications of this solution, but it's what we // have to do. // if (pRegisteredPort->IsSharedPort()) { DPFX(DPFPREP, 1, "Registered port 0x%p address index %u (private address %u.%u.%u.%u:%u) is shared, not unmapping.", pRegisteredPort, dwTemp, psaddrinPublic[dwTemp].sin_addr.S_un.S_un_b.s_b1, psaddrinPublic[dwTemp].sin_addr.S_un.S_un_b.s_b2, psaddrinPublic[dwTemp].sin_addr.S_un.S_un_b.s_b3, psaddrinPublic[dwTemp].sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinPublic[dwTemp].sin_port)); continue; } // // If the control socket got disconnected after the last message, // then reconnect. // if (! pUPnPDevice->IsConnected()) { hr = this->ReconnectUPnPControlSocket(pUPnPDevice); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't reconnect UPnP control socket!"); goto Failure; } } DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); wsprintf(tszExternalPort, _T("%u"), NTOHS(psaddrinPublic[dwTemp].sin_port)); iContentLength = strlen("" EOL) + strlen(" " EOL) + strlen(" GetStaticServiceURILength() + strlen("\">" EOL) + strlen(" <" ARG_DELETEPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "" EOL) + strlen(" <" ARG_DELETEPORTMAPPING_NEWEXTERNALPORT_A ">") + _tcslen(tszExternalPort) + strlen("" EOL) + strlen(" <" ARG_DELETEPORTMAPPING_NEWPROTOCOL_A ">") + 3 + strlen("" EOL) + strlen(" " EOL) + strlen(" " EOL) + strlen("" EOL) + strlen(EOL); wsprintf(tszContentLength, _T("%i"), iContentLength); iMsgSize = strlen("POST ") + strlen(pUPnPDevice->GetServiceControlURL()) + strlen(" " HTTP_VERSION EOL) + strlen("HOST: ") + _tcslen(tszHost) + strlen(EOL) + strlen("CONTENT-LENGTH: ") + _tcslen(tszContentLength) + strlen(EOL) + strlen("CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL) + strlen("SOAPACTION: ") + pUPnPDevice->GetStaticServiceURILength() + strlen("#" ACTION_DELETEPORTMAPPING_A EOL) + strlen(EOL) + iContentLength; // // Allocate (or reallocate) the message buffer. // if (iMsgSize > iPrevMsgSize) { if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } pszMessage = (char*) DNMalloc(iMsgSize + 1); // include room for NULL termination that isn't actually sent if (pszMessage == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } iPrevMsgSize = iMsgSize; } strcpy(pszMessage, "POST "); strcat(pszMessage, pUPnPDevice->GetServiceControlURL()); strcat(pszMessage, " " HTTP_VERSION EOL); strcat(pszMessage, "HOST: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszHost, (_tcslen(tszHost) + 1)); #else // ! UNICODE strcat(pszMessage, tszHost); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-LENGTH: "); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszContentLength, (_tcslen(tszContentLength) + 1)); #else // ! UNICODE strcat(pszMessage, tszContentLength); #endif // ! UNICODE strcat(pszMessage, EOL); strcat(pszMessage, "CONTENT-TYPE: text/xml; charset=\"utf-8\"" EOL); strcat(pszMessage, "SOAPACTION: "); strcat(pszMessage, pUPnPDevice->GetStaticServiceURI()); strcat(pszMessage, "#" ACTION_DELETEPORTMAPPING_A EOL); strcat(pszMessage, EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " GetStaticServiceURI()); strcat(pszMessage, "\">" EOL); strcat(pszMessage, " <" ARG_DELETEPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "" EOL); strcat(pszMessage, " <" ARG_DELETEPORTMAPPING_NEWEXTERNALPORT_A ">"); #ifdef UNICODE STR_jkWideToAnsi((pszMessage + strlen(pszMessage)), tszExternalPort, (_tcslen(tszExternalPort) + 1)); #else // ! UNICODE strcat(pszMessage, tszExternalPort); #endif // ! UNICODE strcat(pszMessage, "" EOL); strcat(pszMessage, " <" ARG_DELETEPORTMAPPING_NEWPROTOCOL_A ">"); strcat(pszMessage, ((pRegisteredPort->IsTCP()) ? "TCP" : "UDP")); strcat(pszMessage, "" EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, " " EOL); strcat(pszMessage, "" EOL); strcat(pszMessage, EOL); #ifdef DBG this->PrintUPnPTransactionToFile(pszMessage, iMsgSize, "Outbound delete port mapping request", pDevice); #endif // DBG iReturn = this->m_pfnsend(pUPnPDevice->GetControlSocket(), pszMessage, iMsgSize, 0); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u when sending control request to UPnP device!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } if (iReturn != iMsgSize) { DPFX(DPFPREP, 0, "Didn't send entire message (%i != %i)?!", iReturn, iMsgSize); DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; } // // We have the lock so no one could have tried to receive data from // the control socket yet. Mark the device as waiting for a // response. // ZeroMemory(&RespInfo, sizeof(RespInfo)); pUPnPDevice->StartWaitingForControlResponse(CONTROLRESPONSETYPE_DELETEPORTMAPPING, &RespInfo); fStartedWaitingForControlResponse = TRUE; // // Actually wait for the response. // dwStartTime = GETTIMESTAMP(); dwTimeout = g_dwUPnPResponseTimeout; do { hr = this->CheckForReceivedUPnPMsgsOnDevice(pUPnPDevice, dwTimeout); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed receiving UPnP messages!"); goto Failure; } // // We either timed out or got some data. Check if we got a // response of some type. // if (! pUPnPDevice->IsWaitingForControlResponse()) { break; } // // Make sure our device is still connected. // if (! pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 0, "UPnP device 0x%p disconnected while deleting port index %u!", pUPnPDevice, dwTemp); pUPnPDevice->StopWaitingForControlResponse(); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // Calculate how long we have left to wait. If the calculation // goes negative, it means we're done. // dwTimeout = g_dwUPnPResponseTimeout - (GETTIMESTAMP() - dwStartTime); } while (((int) dwTimeout > 0)); // // If we never got the response, stop waiting for it. // if (pUPnPDevice->IsWaitingForControlResponse()) { pUPnPDevice->StopWaitingForControlResponse(); DPFX(DPFPREP, 0, "Server didn't respond in time for port index %u!", dwTemp); hr = DPNHERR_SERVERNOTRESPONDING; goto Failure; } // // If we're here, then we've gotten a valid response (success or // failure) from the server. If it's a failure, print out a note // but continue. // #ifdef DBG switch (RespInfo.hrErrorCode) { case DPNH_OK: { // // Succeeded. // break; } case DPNHERR_NOMAPPING: { // // UPnP device didn't know what we were talking about. // DPFX(DPFPREP, 1, "Server didn't recognize mapping for port index %u, continuing.", dwTemp); break; } default: { // // Something else happened. // DPFX(DPFPREP, 0, "Server returned failure response 0x%lx for port index %u! Ignoring.", RespInfo.hrErrorCode, dwTemp); break; } } #endif // DBG // // If the lease is permanent, we need to remove the reference from // the registry. // if (pRegisteredPort->HasPermanentUPnPLease()) { IN_ADDR inaddrTemp; TCHAR tszInternalClient[16]; // "xxx.xxx.xxx.xxx" + NULL termination TCHAR tszInternalPort[32]; DWORD dwDescriptionLength; CRegistry RegObject; WCHAR wszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; #ifdef UNICODE TCHAR * ptszDescription = wszDescription; #else // ! UNICODE char szDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; TCHAR * ptszDescription = szDescription; #endif // ! UNICODE // // Note that the device address is not necessarily the same as // the address the user originally registered, particularly the // 0.0.0.0 wildcard address will get remapped. // inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); wsprintf(tszInternalClient, _T("%u.%u.%u.%u"), inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); wsprintf(tszInternalPort, _T("%u"), NTOHS(psaddrinPrivate[dwTemp].sin_port)); // // Generate a description for this mapping. The format is: // // [executable_name] (nnn.nnn.nnn.nnn:nnnnn) nnnnn {"TCP" | "UDP"} // // That way nothing needs to be localized. // dwDescriptionLength = GetModuleFileName(NULL, ptszDescription, (MAX_UPNP_MAPPING_DESCRIPTION_SIZE - 1)); if (dwDescriptionLength != 0) { // // Be paranoid and make sure the description string is valid. // ptszDescription[MAX_UPNP_MAPPING_DESCRIPTION_SIZE - 1] = 0; // // Get just the executable name from the path. // #ifdef WINCE GetExeName(ptszDescription); #else // ! WINCE #ifdef UNICODE _wsplitpath(ptszDescription, NULL, NULL, ptszDescription, NULL); #else // ! UNICODE _splitpath(ptszDescription, NULL, NULL, ptszDescription, NULL); #endif // ! UNICODE #endif // ! WINCE dwDescriptionLength = _tcslen(ptszDescription) // executable name + 2 // " (" + _tcslen(tszInternalClient) // private IP address + 1 // ":" + _tcslen(tszInternalPort) // private port + 2 // ") " + _tcslen(tszExternalPort) // public port + 4; // " TCP" | " UDP" // // Make sure the long string will fit. If not, use the // abbreviated version. // if (dwDescriptionLength > MAX_UPNP_MAPPING_DESCRIPTION_SIZE) { dwDescriptionLength = 0; } } if (dwDescriptionLength == 0) { // // Use the abbreviated version we know will fit. // wsprintf(ptszDescription, _T("(%s:%s) %s %s"), tszInternalClient, tszInternalPort, tszExternalPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } else { // // There's enough room, tack on the rest of the // description. // wsprintf((ptszDescription + _tcslen(ptszDescription)), _T(" (%s:%s) %s %s"), tszInternalClient, tszInternalPort, tszExternalPort, ((pRegisteredPort->IsTCP()) ? _T("TCP") : _T("UDP"))); } if (fNeedToDeleteRegValue) { DPFX(DPFPREP, 7, "Removing NAT lease \"%s\" crash cleanup registry entry.", ptszDescription); if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVENATMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 0, "Couldn't open active NAT mapping key, unable to remove crash cleanup reference!"); } else { #ifndef UNICODE dwDescriptionLength = strlen(szDescription) + 1; hr = STR_jkAnsiToWide(wszDescription, szDescription, dwDescriptionLength); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't convert NAT mapping description to Unicode (err = 0x%lx), unable to remove crash cleanup reference!", hr); // // Ignore error and continue. // hr = S_OK; } else #endif // ! UNICODE { BOOL fResult; // // Ignore error. // fResult = RegObject.DeleteValue(wszDescription); if (! fResult) { DPFX(DPFPREP, 0, "Couldn't delete NAT mapping value \"%s\"! Continuing.", ptszDescription); } } RegObject.Close(); } } else { DPFX(DPFPREP, 6, "No need to remove NAT lease \"%s\" crash cleanup registry entry.", ptszDescription); } } else { // // Registered port doesn't have a permanent UPnP lease. // } // // Move on to next port. // } // // If we're here, everything was successful. // DPFX(DPFPREP, 8, "Registered port 0x%p mapping successfully deleted from UPnP device (0x%p).", pRegisteredPort, pUPnPDevice); Exit: if (pszMessage != NULL) { DNFree(pszMessage); pszMessage = NULL; } // // No matter whether we succeeded or failed, remove the UPnP public addresses // array and decrement the total lease count. // pRegisteredPort->DestroyUPnPPublicAddressesArray(); DNASSERT(this->m_dwNumLeases > 0); this->m_dwNumLeases--; DPFX(DPFPREP, 7, "UPnP lease for 0x%p removed, total num leases = %u.", pRegisteredPort, this->m_dwNumLeases); pRegisteredPort->NoteNotPermanentUPnPLease(); pRegisteredPort->NoteNotRemovingUPnPLease(); pUPnPDevice->DecRef(); DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: // // If we started waiting for a response, clear that. // if (fStartedWaitingForControlResponse) { pUPnPDevice->StopWaitingForControlResponse(); } goto Exit; } // CNATHelpUPnP::UnmapUPnPPort #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CleanupInactiveNATMappings" //============================================================================= // CNATHelpUPnP::CleanupInactiveNATMappings //----------------------------------------------------------------------------- // // Description: Looks for any mappings previously made by other DPNHUPNP // instances that are no longer active (because of a crash), and // unmaps them. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference if it's using the // device. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device to use. // // Returns: HRESULT // DPNH_OK - The extension was successful. // DPNHERR_GENERIC - An error occurred. // DPNHERR_SERVERNOTRESPONDING - The server did not respond to the // message. //============================================================================= HRESULT CNATHelpUPnP::CleanupInactiveNATMappings(CUPnPDevice * const pUPnPDevice) { HRESULT hr = DPNH_OK; CDevice * pDevice; CRegistry RegObject; BOOL fOpenedRegistry = FALSE; DWORD dwIndex; WCHAR wszValueName[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; DWORD dwValueNameSize; DPNHACTIVENATMAPPING dpnhanm; DWORD dwValueSize; CBilink * pBilink; CUPnPDevice * pUPnPDeviceTemp = NULL; // NULLed out because PREfast has been hassling me for a while, even though the code is safe TCHAR tszObjectName[MAX_INSTANCENAMEDOBJECT_SIZE]; DNHANDLE hNamedObject = NULL; CRegisteredPort * pRegisteredPort = NULL; BOOL fSetPrivateAddresses = FALSE; SOCKADDR_IN saddrinPrivate; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p)", this, pUPnPDevice); DNASSERT(pUPnPDevice != NULL); pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pDevice != NULL); if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVENATMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 1, "Couldn't open active NAT mapping key, not performing crash cleanup."); DNASSERT(hr == DPNH_OK); goto Exit; } fOpenedRegistry = TRUE; // // Walk the list of active mappings. // dwIndex = 0; do { dwValueNameSize = MAX_UPNP_MAPPING_DESCRIPTION_SIZE; if (! RegObject.EnumValues(wszValueName, &dwValueNameSize, dwIndex)) { // // There was an error or there aren't any more keys. We're done. // break; } // // Try reading that mapping's data. // dwValueSize = sizeof(dpnhanm); if (! RegObject.ReadBlob(wszValueName, (LPBYTE) (&dpnhanm), &dwValueSize)) { // // We don't have a lock protecting the registry, so some other // instance could have deleted the key between when we enumerated // it and now. We'll stop trying (and hopefully that other // instance will cover the rest of the items). // DPFX(DPFPREP, 0, "Couldn't read \"%ls\" mapping value! Done with cleanup.", wszValueName); DNASSERT(hr == DPNH_OK); goto Exit; } // // Validate the data read. // if ((dwValueSize != sizeof(dpnhanm)) || (dpnhanm.dwVersion != ACTIVE_MAPPING_VERSION)) { DPFX(DPFPREP, 0, "The \"%ls\" mapping value is invalid! Done with cleanup.", wszValueName); // // Move to next item. // dwIndex++; continue; } // // See if it's owned by the local NATHelp instance. // if (dpnhanm.dwInstanceKey == this->m_dwInstanceKey) { // // We own(ed) it. See if it was associated with a UPnP device // that's now gone. // pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDeviceTemp = UPNPDEVICE_FROM_BILINK(pBilink); if (pUPnPDeviceTemp->GetID() == dpnhanm.dwUPnPDeviceID) { // // This mapping truly is active, leave it alone. // break; } pBilink = pBilink->GetNext(); } // // If we found the mapping, go on to the next one. // if (pBilink != &this->m_blUPnPDevices) { // // Note that despite what PREfast v1.0.1195 says, // pUPnPDeviceTemp will always be valid if we get here. // However, I gave in and NULLed out the pointer up top. // DPFX(DPFPREP, 4, "NAT mapping \"%ls\" belongs to current instance (%u)'s UPnP device 0x%p.", wszValueName, dpnhanm.dwInstanceKey, pUPnPDeviceTemp); // // Move to next item. // dwIndex++; continue; } // // Otherwise, we gave up on this mapping earlier. // DNASSERT((this->m_dwNumDeviceRemoves > 0) || (this->m_dwNumServerFailures > 0)); DPFX(DPFPREP, 4, "NAT mapping \"%ls\" was owned by current instance (%u)'s UPnP device ID %u that no longer exists.", wszValueName, dpnhanm.dwInstanceKey, dpnhanm.dwUPnPDeviceID); } else { // // See if that DPNHUPNP instance is still around. // #ifndef WINCE if (this->m_dwFlags & NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX) { wsprintf(tszObjectName, _T( "Global\\" ) INSTANCENAMEDOBJECT_FORMATSTRING, dpnhanm.dwInstanceKey); } else #endif // ! WINCE { wsprintf(tszObjectName, INSTANCENAMEDOBJECT_FORMATSTRING, dpnhanm.dwInstanceKey); } hNamedObject = DNOpenEvent(SYNCHRONIZE, FALSE, tszObjectName); if (hNamedObject != NULL) { // // This is still an active mapping. // DPFX(DPFPREP, 4, "NAT mapping \"%ls\" belongs to instance %u, which is still active.", wszValueName, dpnhanm.dwInstanceKey); DNCloseHandle(hNamedObject); hNamedObject = NULL; // // Move to next item. // dwIndex++; continue; } DPFX(DPFPREP, 4, "NAT mapping \"%ls\" belongs to instance %u, which no longer exists.", wszValueName, dpnhanm.dwInstanceKey); } // // Delete the value now that we have the information we need. // if (! RegObject.DeleteValue(wszValueName)) { // // See ReadBlob comments. Stop trying to cleanup. // DPFX(DPFPREP, 0, "Couldn't delete \"%ls\"! Done with cleanup.", wszValueName); DNASSERT(hr == DPNH_OK); goto Exit; } // // Create a fake registered port that we will deregister. Ignore the // firewall state flags. // pRegisteredPort = new CRegisteredPort(0, (dpnhanm.dwFlags & REGPORTOBJMASK_UPNP)); if (pRegisteredPort == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } // // Assert that the other UPnP information/state flags are not set. // DNASSERT(! pRegisteredPort->IsUPnPPortUnavailable()); DNASSERT(! pRegisteredPort->IsRemovingUPnPLease()); // // Temporarily associate the registered port with the device. // pRegisteredPort->MakeDeviceOwner(pDevice); ZeroMemory(&saddrinPrivate, sizeof(saddrinPrivate)); saddrinPrivate.sin_family = AF_INET; saddrinPrivate.sin_addr.S_un.S_addr = dpnhanm.dwInternalAddressV4; saddrinPrivate.sin_port = dpnhanm.wInternalPort; // // Store the private address. // hr = pRegisteredPort->SetPrivateAddresses(&saddrinPrivate, 1); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed creating UPnP address array!"); goto Failure; } fSetPrivateAddresses = TRUE; // // Create the public address array. // hr = pRegisteredPort->CreateUPnPPublicAddressesArray(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed creating UPnP address array!"); goto Failure; } // // Fake increase the number of leases we have. It will just get // decremented in UnmapUPnPPort. // DPFX(DPFPREP, 7, "Creating temporary UPnP lease 0x%p, total num leases = %u.", pRegisteredPort, this->m_dwNumLeases); this->m_dwNumLeases++; // // Store the public port. // pRegisteredPort->SetUPnPPublicV4Address(0, dpnhanm.dwExternalAddressV4, dpnhanm.wExternalPort); // // Actually free the port. // hr = this->UnmapUPnPPort(pRegisteredPort, 1, FALSE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed deleting temporary UPnP port!"); goto Failure; } pRegisteredPort->ClearPrivateAddresses(); fSetPrivateAddresses = FALSE; pRegisteredPort->ClearDeviceOwner(); delete pRegisteredPort; pRegisteredPort = NULL; // // Move to the next mapping. Don't increment index since we just // deleted the previous entry and everything shifts down one. // } while (TRUE); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pRegisteredPort != NULL) { if (pRegisteredPort->HasUPnPPublicAddresses()) { pRegisteredPort->DestroyUPnPPublicAddressesArray(); // // Remove the lease counter. // DNASSERT(this->m_dwNumLeases > 0); this->m_dwNumLeases--; DPFX(DPFPREP, 7, "UPnP lease for 0x%p removed, total num leases = %u.", pRegisteredPort, this->m_dwNumLeases); } if (fSetPrivateAddresses) { pRegisteredPort->ClearPrivateAddresses(); fSetPrivateAddresses = FALSE; } pRegisteredPort->ClearDeviceOwner(); delete pRegisteredPort; pRegisteredPort = NULL; } if (fOpenedRegistry) { RegObject.Close(); } goto Exit; } // CNATHelpUPnP::CleanupInactiveNATMappings #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::IsNATPublicPortInUseLocally" //============================================================================= // CNATHelpUPnP::IsNATPublicPortInUseLocally //----------------------------------------------------------------------------- // // Description: Looks for any mappings previously made by DPNHUPNP instances // that are still active that use the given public port. // // The object lock is assumed to be held. // // Arguments: // WORD wPortHostOrder - Port to check, in host byte order. // // Returns: BOOL //============================================================================= BOOL CNATHelpUPnP::IsNATPublicPortInUseLocally(const WORD wPortHostOrder) { BOOL fResult = FALSE; WORD wExternalPort; CRegistry RegObject; BOOL fOpenedRegistry = FALSE; DWORD dwIndex; WCHAR wszValueName[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; DWORD dwValueNameSize; DPNHACTIVENATMAPPING dpnhanm; CBilink * pBilink; CUPnPDevice * pUPnPDevice = NULL; // NULLed out because PREfast has been hassling me for a while, even though the code is safe DWORD dwValueSize; TCHAR tszObjectName[MAX_INSTANCENAMEDOBJECT_SIZE]; DNHANDLE hNamedObject = NULL; DPFX(DPFPREP, 6, "(0x%p) Parameters: (%u)", this, wPortHostOrder); wExternalPort = HTONS(wPortHostOrder); if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVENATMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 1, "Couldn't open active NAT mapping key, assuming port not in use."); goto Exit; } fOpenedRegistry = TRUE; // // Walk the list of active mappings. // dwIndex = 0; do { dwValueNameSize = MAX_UPNP_MAPPING_DESCRIPTION_SIZE; if (! RegObject.EnumValues(wszValueName, &dwValueNameSize, dwIndex)) { // // There was an error or there aren't any more keys. We're done. // break; } // // Try reading that mapping's data. // dwValueSize = sizeof(dpnhanm); if (! RegObject.ReadBlob(wszValueName, (LPBYTE) (&dpnhanm), &dwValueSize)) { // // We don't have a lock protecting the registry, so some other // instance could have deleted the key between when we enumerated // it and now. We'll stop trying (and hopefully that other // instance will cover the rest of the items). // DPFX(DPFPREP, 0, "Couldn't read \"%ls\" mapping value, assuming port not in use.", wszValueName); goto Exit; } // // Validate the data read. // if ((dwValueSize != sizeof(dpnhanm)) || (dpnhanm.dwVersion != ACTIVE_MAPPING_VERSION)) { DPFX(DPFPREP, 0, "The \"%ls\" mapping value is invalid, assuming port not in use.", wszValueName); // // Move to next item. // dwIndex++; continue; } // // Is this the right port? // if (dpnhanm.wExternalPort == wExternalPort) { // // See if it's owned by the local NATHelp instance. // if (dpnhanm.dwInstanceKey == this->m_dwInstanceKey) { // // We own(ed) it. See if it was associated with a UPnP device // that's now gone. // pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); if (pUPnPDevice->GetID() == dpnhanm.dwUPnPDeviceID) { // // This mapping truly still active. // fResult = TRUE; break; } pBilink = pBilink->GetNext(); } if (pBilink != &this->m_blUPnPDevices) { // // Note that despite what PREfast v1.0.1195 says, // pUPnPDevice will always be valid if we get here. // However, I gave in and NULLed out the pointer up top. // DPFX(DPFPREP, 4, "NAT mapping \"%ls\" belongs to current instance (%u)'s UPnP device 0x%p.", wszValueName, dpnhanm.dwInstanceKey, pUPnPDevice); } else { DPFX(DPFPREP, 4, "NAT mapping \"%ls\" was owned by current instance (%u)'s UPnP device ID %u that no longer exists.", wszValueName, dpnhanm.dwInstanceKey, dpnhanm.dwUPnPDeviceID); } } else { // // See if that DPNHUPNP instance is still around. // #ifndef WINCE if (this->m_dwFlags & NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX) { wsprintf(tszObjectName, _T( "Global\\" ) INSTANCENAMEDOBJECT_FORMATSTRING, dpnhanm.dwInstanceKey); } else #endif // ! WINCE { wsprintf(tszObjectName, INSTANCENAMEDOBJECT_FORMATSTRING, dpnhanm.dwInstanceKey); } hNamedObject = DNOpenEvent(SYNCHRONIZE, FALSE, tszObjectName); if (hNamedObject != NULL) { // // This is still an active instance. Since we can't walk // his list of UPnP devices, we have to assume the port is // still in use. // DPFX(DPFPREP, 4, "NAT mapping \"%ls\" belongs to instance %u, which is still active. Assuming port in use.", wszValueName, dpnhanm.dwInstanceKey); DNCloseHandle(hNamedObject); hNamedObject = NULL; fResult = TRUE; } else { DPFX(DPFPREP, 4, "NAT mapping \"%ls\" belongs to instance %u, which no longer exists.", wszValueName, dpnhanm.dwInstanceKey); } } // // We found the mapping. We have our result now. // goto Exit; } // // If we're here, this is not the external port we're looking for. // DPFX(DPFPREP, 8, "NAT mapping \"%ls\" does not use external port %u.", wszValueName, wPortHostOrder); // // Move to the next mapping. // dwIndex++; } while (TRUE); // // If we're here, we didn't find the mapping. // DPFX(DPFPREP, 4, "Didn't find any local NAT mappings that use external port %u.", wPortHostOrder); Exit: if (fOpenedRegistry) { RegObject.Close(); } DPFX(DPFPREP, 6, "(0x%p) Returning: [%i]", this, fResult); return fResult; } // CNATHelpUPnP::IsNATPublicPortInUseLocally #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CheckForUPnPAnnouncements" //============================================================================= // CNATHelpUPnP::CheckForUPnPAnnouncements //----------------------------------------------------------------------------- // // Description: Receives any UPnP announcement messages sent to this control // point. The entire timeout period will elapse, unless all // devices get responses earlier. // // This will only send discovery requests for local devices // unless fSendRemoteGatewayDiscovery is TRUE. However, we may // still detect new ones if we got a straggling response from the // last time we were allowed to send remotely. // // The object lock is assumed to be held. // // Arguments: // DWORD dwTimeout - How long to wait for messages to // arrive. // BOOL fSendRemoteGatewayDiscovery - Whether we can search remotely or // not. // // Returns: HRESULT // DPNH_OK - Messages were received successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CheckForUPnPAnnouncements(const DWORD dwTimeout, const BOOL fSendRemoteGatewayDiscovery) { HRESULT hr; DWORD dwNumberOfTimes = 0; DWORD dwCurrentTime; DWORD dwEndTime; DWORD dwNextSearchMessageTime; FD_SET fdsRead; DWORD dwNumDevicesSearchingForUPnPDevices; timeval tv; CBilink * pBilink; CDevice * pDevice; int iReturn; int iRecvAddressSize; char acBuffer[UPNP_DGRAM_RECV_BUFFER_SIZE]; SOCKADDR_IN saddrinRecvAddress; DWORD dwError; BOOL fInitiatedConnect = FALSE; #ifdef DBG BOOL fGotData = FALSE; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters:(%u, %i)", this, dwTimeout, fSendRemoteGatewayDiscovery); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); dwCurrentTime = GETTIMESTAMP(); dwEndTime = dwCurrentTime + dwTimeout; dwNextSearchMessageTime = dwCurrentTime; // // Keep looping until the timeout elapses. // do { FD_ZERO(&fdsRead); dwNumDevicesSearchingForUPnPDevices = 0; // // Build an FD_SET for all the sockets and send out search messages for // all devices. // DNASSERT(! this->m_blDevices.IsEmpty()); pBilink = this->m_blDevices.GetNext(); while (pBilink != &this->m_blDevices) { DNASSERT(! pBilink->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilink); // // We add it to the set whether we search or not, since if we're // not searching, we're going to be clearing straggling messages. // DNASSERT(pDevice->GetUPnPDiscoverySocket() != INVALID_SOCKET); FD_SET(pDevice->GetUPnPDiscoverySocket(), &fdsRead); // // Don't send search messages if we already have a UPnP device or // this is the loopback adapter. // if ((pDevice->GetUPnPDevice() == NULL) && (pDevice->GetLocalAddressV4() != NETWORKBYTEORDER_INADDR_LOOPBACK)) { // // If this is the first time through the loop, clear the // CONNRESET warning flags. // if (dwNumberOfTimes == 0) { pDevice->NoteNotGotRemoteUPnPDiscoveryConnReset(); pDevice->NoteNotGotLocalUPnPDiscoveryConnReset(); } // // Send out search messages if it's time. // if ((int) (dwNextSearchMessageTime - dwCurrentTime) <= 0) { hr = this->SendUPnPSearchMessagesForDevice(pDevice, fSendRemoteGatewayDiscovery); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't send UPnP search messages via every device!"); goto Failure; } } else { // // Not time to send search messages. // } // // For subsequent times through the loop, make sure we didn't // get a CONNRESET earlier telling us not to try again. // The "attempt?" flags got set in // SendUPnPSearchMessagesForDevice, and the CONNRESET flags // were cleared the first time we entered here. // if ((pDevice->IsOKToPerformRemoteUPnPDiscovery()) || (pDevice->IsOKToPerformLocalUPnPDiscovery())) { // // Remember that we're trying to detect an Internet Gateway // for this device. See caveat immediately following, and // below for this variable's usage. // dwNumDevicesSearchingForUPnPDevices++; // // Minor optimization: // // If we're only supposed to be trying locally, and we're // the public address for a local gateway, assume that we // actually shouldn't be trying locally. This is because // Windows XP ICS keeps port 1900 open even on the public // adapter, so we think we need to look for a local one // even though we won't find one. So once the remote // lookup comes back with a CONNRESET, we no longer need to // bother trying. // // So first check if we're only trying locally. // if ((pDevice->IsOKToPerformLocalUPnPDiscovery()) && (! pDevice->IsOKToPerformRemoteUPnPDiscovery())) { CBilink * pBilinkPrivateDevice; CDevice * pPrivateDevice; CUPnPDevice * pUPnPDevice; // // Then loop through every device. // pBilinkPrivateDevice = this->m_blDevices.GetNext(); while (pBilinkPrivateDevice != &this->m_blDevices) { pPrivateDevice = DEVICE_FROM_BILINK(pBilinkPrivateDevice); pUPnPDevice = pPrivateDevice->GetUPnPDevice(); // // If it's not the device we're querying and it has // a ready UPnP device, dig deeper. // if ((pPrivateDevice != pDevice) && (pUPnPDevice != NULL) && (pUPnPDevice->IsReady())) { // // If its a local UPnP device and its public // address is this device's address, we found a // match. // if ((pUPnPDevice->IsLocal()) && (pUPnPDevice->GetExternalIPAddressV4() == pDevice->GetLocalAddressV4())) { DPFX(DPFPREP, 4, "Device 0x%p is the public address for device 0x%p's local UPnP device 0x%p, not including in search.", pDevice, pPrivateDevice, pUPnPDevice); // // Remove the count we added above. // dwNumDevicesSearchingForUPnPDevices--; // // Stop searching. // break; } // // Otherwise keep going. // DPFX(DPFPREP, 8, "Skipping device 0x%p, UPnP device 0x%p not local (%i, control addr = %u.%u.%u.%u) or its public address doesn't match device 0x%p's address.", pPrivateDevice, pUPnPDevice, (! pUPnPDevice->IsLocal()), pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b1, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b2, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b3, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b4, pDevice); } else { DPFX(DPFPREP, 8, "Skipping device 0x%p, it's the one we're looking for (matched 0x%p), it doesn't have a UPnP device (0x%p is NULL), and/or its UPnP device is not ready.", pPrivateDevice, pDevice, pUPnPDevice); } // // Go on to next device. // pBilinkPrivateDevice = pBilinkPrivateDevice->GetNext(); } } else { // // Either not searching locally, or searching both // locally and remotely. // DPFX(DPFPREP, 8, "Device 0x%p local search OK = %i, remote search OK = %i.", pDevice, pDevice->IsOKToPerformLocalUPnPDiscovery(), pDevice->IsOKToPerformRemoteUPnPDiscovery()); } } else { DPFX(DPFPREP, 3, "Device 0x%p should not perform UPnP discovery.", pDevice); } } else { DPFX(DPFPREP, 8, "Device 0x%p already has UPnP device (0x%p) or is loopback address.", pDevice, pDevice->GetUPnPDevice()); } pBilink = pBilink->GetNext(); } // // Wait for any data, unless all devices already have an Internet // Gateway, in which case we only want to clear the receive queue for // the sockets. // if (dwNumDevicesSearchingForUPnPDevices == 0) { DPFX(DPFPREP, 7, "No devices need to search for UPnP devices, clearing straggling messages from sockets."); tv.tv_usec = 0; } else { // // Calculate the next time to send if we just sent search messages. // if ((int) (dwNextSearchMessageTime - dwCurrentTime) <= 0) { dwNextSearchMessageTime += UPNP_SEARCH_MESSAGE_INTERVAL; // // If we took way longer than expected in a previous loop // (because of stress or Win9x errata), the next search time // may have already passed. Just search right now if that's // the case. // if ((int) (dwNextSearchMessageTime - dwCurrentTime) <= 0) { dwNextSearchMessageTime = dwCurrentTime; } } // // See how long we should wait for responses. Choose the total end // time or the next search message time, whichever is shorter. // if ((int) (dwEndTime - dwNextSearchMessageTime) < 0) { DPFX(DPFPREP, 7, "Waiting %u ms for incoming responses.", (dwEndTime - dwCurrentTime)); tv.tv_usec = (dwEndTime - dwCurrentTime) * 1000; } else { DPFX(DPFPREP, 7, "Waiting %u ms for incoming responses, and then might send search messages again.", (dwNextSearchMessageTime - dwCurrentTime)); tv.tv_usec = (dwNextSearchMessageTime - dwCurrentTime) * 1000; } } tv.tv_sec = 0; iReturn = this->m_pfnselect(0, &fdsRead, NULL, NULL, &tv); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u trying to select on UPnP discovery sockets!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // See if any sockets were selected. // if (iReturn > 0) { // // Loop through all devices, looking for those that have data. // pBilink = this->m_blDevices.GetNext(); while (pBilink != &this->m_blDevices) { DNASSERT(! pBilink->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilink); // // If this device's socket is set there's data to read. // //if (FD_ISSET(pDevice->GetUPnPDiscoverySocket(), &fdsRead)) if (this->m_pfn__WSAFDIsSet(pDevice->GetUPnPDiscoverySocket(), &fdsRead)) { #ifdef DBG fGotData = TRUE; #endif // DBG iRecvAddressSize = sizeof(saddrinRecvAddress); iReturn = this->m_pfnrecvfrom(pDevice->GetUPnPDiscoverySocket(), acBuffer, (sizeof(acBuffer) - 1), // -1 to allow string termination 0, (SOCKADDR*) (&saddrinRecvAddress), &iRecvAddressSize); if ((iReturn == 0) || (iReturn == SOCKET_ERROR)) { dwError = this->m_pfnWSAGetLastError(); // // WSAENOBUFS means WinSock is out of memory. // if (dwError == WSAENOBUFS) { DPFX(DPFPREP, 0, "WinSock returned WSAENOBUFS while receiving discovery response!"); hr = DPNHERR_OUTOFMEMORY; goto Failure; } // // All other errors besides WSAECONNRESET are // unexpected and mean we should bail. // if (dwError != WSAECONNRESET) { DPFX(DPFPREP, 0, "Got sockets error %u trying to receive on device 0x%p!", dwError, pDevice); hr = DPNHERR_GENERIC; goto Failure; } // // If we're here, it must be WSAECONNRESET. Correlate // it with the outbound message that generated it so we // don't bother waiting for a response from that // location. // Validate that it's for the port to which the message // should have been sent. // if (saddrinRecvAddress.sin_port == HTONS(UPNP_PORT)) { if (saddrinRecvAddress.sin_addr.S_un.S_addr == pDevice->GetLocalAddressV4()) { DPFX(DPFPREP, 1, "Got CONNRESET for local discovery attempt on device 0x%p (%u.%u.%u.%u:%u).", pDevice, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b1, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b2, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b3, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinRecvAddress.sin_port)); // // Note the local error. // pDevice->NoteGotLocalUPnPDiscoveryConnReset(); } else { if (! g_fUseMulticastUPnPDiscovery) { IN_ADDR inaddrGateway; if ((! this->GetAddressToReachGateway(pDevice, &inaddrGateway)) || (inaddrGateway.S_un.S_addr == INADDR_BROADCAST) || (saddrinRecvAddress.sin_addr.S_un.S_addr == inaddrGateway.S_un.S_addr)) { DPFX(DPFPREP, 2, "Got CONNRESET for remote discovery attempt on device 0x%p (%u.%u.%u.%u:%u).", pDevice, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b1, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b2, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b3, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinRecvAddress.sin_port)); // // Note the remote error. // pDevice->NoteGotRemoteUPnPDiscoveryConnReset(); } else { DPFX(DPFPREP, 1, "Ignoring CONNRESET on device 0x%p, sender %u.%u.%u.%u is not gateway %u.%u.%u.%u.", pDevice, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b1, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b2, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b3, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b4, inaddrGateway.S_un.S_un_b.s_b1, inaddrGateway.S_un.S_un_b.s_b2, inaddrGateway.S_un.S_un_b.s_b3, inaddrGateway.S_un.S_un_b.s_b4); } } else { DPFX(DPFPREP, 1, "Ignoring CONNRESET on device 0x%p from sender %u.%u.%u.%u, we are using multicast discovery.", pDevice, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b1, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b2, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b3, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b4); } } } else { DPFX(DPFPREP, 1, "Ignoring CONNRESET on device 0x%p for invalid port (%u.%u.%u.%u:%u).", pDevice, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b1, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b2, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b3, saddrinRecvAddress.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinRecvAddress.sin_port)); } } else { DNASSERT(iRecvAddressSize == sizeof(saddrinRecvAddress)); DNASSERT(iReturn < sizeof(acBuffer)); hr = this->HandleUPnPDiscoveryResponseMsg(pDevice, &saddrinRecvAddress, acBuffer, iReturn, &fInitiatedConnect); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't handle UPnP discovery response message (err = 0x%lx), ignoring.", hr); } } } pBilink = pBilink->GetNext(); } // // Make sure we actually found a socket with data. // DNASSERT(fGotData); } else { // // We timed out. If we were just clearing receive buffers for the // socket(s), we're done. // if (dwNumDevicesSearchingForUPnPDevices == 0) { break; } } // // Increase the counter. // dwNumberOfTimes++; // // Get current time for figuring out how much longer to wait. // dwCurrentTime = GETTIMESTAMP(); } while ((int) (dwEndTime - dwCurrentTime) > 0); hr = DPNH_OK; // // If we initiated connections to any UPnP devices, wait for them to // complete. // if (fInitiatedConnect) { hr = this->WaitForUPnPConnectCompletions(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't wait for UPnP connect completions!"); goto Failure; } } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::CheckForUPnPAnnouncements #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::WaitForUPnPConnectCompletions" //============================================================================= // CNATHelpUPnP::WaitForUPnPConnectCompletions //----------------------------------------------------------------------------- // // Description: Waits for completions for pending TCP connects to UPnP // Internet gateway devices. // // UPnP devices may get removed from list if a failure occurs. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: HRESULT // DPNH_OK - Connects were handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::WaitForUPnPConnectCompletions(void) { HRESULT hr; int iNumSockets; FD_SET fdsWrite; FD_SET fdsExcept; CBilink * pBilink; CUPnPDevice * pUPnPDevice; timeval tv; int iReturn; BOOL fRequestedDescription = FALSE; DWORD dwStartTime; DWORD dwTimeout; CDevice * pDevice; #ifdef DBG BOOL fFoundCompletion; DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Enter", this); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); // // Loop until all sockets are connected or there's a timeout. // do { // // Check for any connect completions. Start by building two FD_SETs // for all the sockets with pending connects. // FD_ZERO(&fdsWrite); FD_ZERO(&fdsExcept); iNumSockets = 0; pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); if (pUPnPDevice->IsConnecting()) { DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); FD_SET(pUPnPDevice->GetControlSocket(), &fdsWrite); FD_SET(pUPnPDevice->GetControlSocket(), &fdsExcept); iNumSockets++; } pBilink = pBilink->GetNext(); } // // If there weren't any sockets that had pending connections, then // we're done here. // if (iNumSockets <= 0) { DPFX(DPFPREP, 7, "No more UPnP device control sockets with pending connections."); break; } DPFX(DPFPREP, 7, "There are %i UPnP device control sockets with pending connections.", iNumSockets); // // Wait for connect completions. We don't wait for the full TCP/IP // timeout (which is why we made it non-blocking). // tv.tv_usec = 0; tv.tv_sec = g_dwUPnPConnectTimeout; iReturn = this->m_pfnselect(0, NULL, &fdsWrite, &fdsExcept, &tv); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u trying to select on UPnP device sockets!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // If no sockets were selected, that means the connections timed out. // Remove all the devices that were waiting. // if (iReturn == 0) { DPFX(DPFPREP, 3, "Select for %u seconds timed out.", g_dwUPnPConnectTimeout); pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); pBilink = pBilink->GetNext(); if (pUPnPDevice->IsConnecting()) { DPFX(DPFPREP, 7, "UPnP device 0x%p is still connecting, removing.", pUPnPDevice); // // Dump this unusable UPnP device and continue. // pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pUPnPDevice->GetOwningDevice() != NULL); DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); // // This may cause our pUPnPDevice pointer to become // invalid. // this->ClearDevicesUPnPDevice(pDevice); #ifdef DBG iNumSockets--; #endif // DBG } else { DPFX(DPFPREP, 7, "UPnP device 0x%p is not trying to connect or has safely connected.", pUPnPDevice); } } // // We should have destroyed the same number of devices that were // waiting. // DNASSERT(iNumSockets == 0); // // Continue on to handling any sockets succeeded previously. // break; } // // If we're here, some sockets were signalled. // #ifdef DBG DPFX(DPFPREP, 7, "There are %i sockets with connect activity.", iReturn); fFoundCompletion = FALSE; #endif // DBG pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); pBilink = pBilink->GetNext(); if (pUPnPDevice->IsConnecting()) { DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); // // If this UPnP device's socket is in the write set it // connected successfully. // //if (FD_ISSET(pUPnPDevice->GetControlSocket(), &fdsWrite)) if (this->m_pfn__WSAFDIsSet(pUPnPDevice->GetControlSocket(), &fdsWrite)) { pUPnPDevice->NoteConnected(); #ifdef DBG fFoundCompletion = TRUE; #endif // DBG if (! pUPnPDevice->IsReady()) { DPFX(DPFPREP, 2, "UPnP device object 0x%p now connected to Internet gateway device.", pUPnPDevice); hr = this->SendUPnPDescriptionRequest(pUPnPDevice); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't send UPnP description request to device object 0x%p! Disconnecting.", pUPnPDevice); // // Dump this unusable UPnP device and continue. // pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pUPnPDevice->GetOwningDevice() != NULL); // // This may cause our pUPnPDevice pointer to become // invalid. // this->ClearDevicesUPnPDevice(pDevice); } else { fRequestedDescription = TRUE; } } else { DPFX(DPFPREP, 2, "UPnP device object 0x%p successfully reconnected to Internet gateway device.", pUPnPDevice); } } else { // // If this UPnP device's socket is in the except set it // failed to connect. // //if (FD_ISSET(pUPnPDevice->GetControlSocket(), &fdsExcept)) if (this->m_pfn__WSAFDIsSet(pUPnPDevice->GetControlSocket(), &fdsExcept)) { #ifdef DBG int iError; int iErrorSize; fFoundCompletion = TRUE; // // Print out the reason why it couldn't connect. // Ignore the direct return code from getsockopt. // iError = 0; iErrorSize = sizeof(iError); this->m_pfngetsockopt(pUPnPDevice->GetControlSocket(), SOL_SOCKET, SO_ERROR, (char*) (&iError), &iErrorSize); DPFX(DPFPREP, 1, "Connecting to UPnP device object 0x%p failed with error %i, removing from list.", pUPnPDevice, iError); #endif // DBG // // This UPnP device is useless if it doesn't respond. // Throw it out. // pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pUPnPDevice->GetOwningDevice() != NULL); this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); // // This may cause our pUPnPDevice pointer to become // invalid. // this->ClearDevicesUPnPDevice(pDevice); } else { // // Socket is still connecting. // } } } else { // // This socket is already connected. // } } // // Make sure we actually found a socket with a connect completion this // time through. // DNASSERT(fFoundCompletion); } while (TRUE); // // If we're here, all UPnP devices are connected or have since been // destroyed. // if (fRequestedDescription) { // // Wait for the description responses to come back. // dwStartTime = GETTIMESTAMP(); dwTimeout = g_dwUPnPResponseTimeout; do { hr = this->CheckForReceivedUPnPMsgsOnAllDevices(dwTimeout); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed receiving UPnP messages!"); goto Failure; } // // We either timed out or got some data. Check if we got the // response(s) we need. Reuse the fRequestedDescription // boolean. // fRequestedDescription = FALSE; pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); if (! pUPnPDevice->IsReady()) { if (pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 7, "UPnP device 0x%p is not ready yet.", pUPnPDevice); fRequestedDescription = TRUE; } else { DPFX(DPFPREP, 4, "UPnP device 0x%p got disconnected before receiving description response.", pUPnPDevice); } break; } pBilink = pBilink->GetNext(); } if (! fRequestedDescription) { DPFX(DPFPREP, 6, "All UPnP devices are ready or disconnected now."); // // Break out of the wait loop. // break; } // // Calculate how long we have left to wait. If the calculation // goes negative, it means we're done. // dwTimeout = g_dwUPnPResponseTimeout - (GETTIMESTAMP() - dwStartTime); } while (((int) dwTimeout > 0)); // // Any devices that still aren't ready yet were either disconnected or // are taking too long and should be removed. // pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); pBilink = pBilink->GetNext(); if (! pUPnPDevice->IsReady()) { DPFX(DPFPREP, 1, "UPnP device 0x%p got disconnected or took too long to get ready, removing.", pUPnPDevice); pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pUPnPDevice->GetOwningDevice() != NULL); // // This may cause our pUPnPDevice pointer to become // invalid. // this->ClearDevicesUPnPDevice(pDevice); } } } else { DPFX(DPFPREP, 7, "Did not request any descriptions."); } // // If we're here, everything is successful and we're done. // hr = DPNH_OK; Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::WaitForUPnPConnectCompletions #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CheckForReceivedUPnPMsgsOnAllDevices" //============================================================================= // CNATHelpUPnP::CheckForReceivedUPnPMsgsOnAllDevices //----------------------------------------------------------------------------- // // Description: Handles any incoming data on the TCP sockets connect to UPnP // Internet gateway devices. // // UPnP devices with failures may get removed from the list. // // The object lock is assumed to be held. // // Arguments: // DWORD dwTimeout - How long to wait for messages to arrive, or 0 to just // poll. // // Returns: HRESULT // DPNH_OK - Messages were handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CheckForReceivedUPnPMsgsOnAllDevices(const DWORD dwTimeout) { HRESULT hr; int iNumSockets; FD_SET fdsRead; CBilink * pBilink; CUPnPDevice * pUPnPDevice; timeval tv; int iReturn; #ifdef DBG BOOL fFoundData = FALSE; DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (%u)", this, dwTimeout); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); Rewait: iNumSockets = 0; // // Check for any data. Start by building an FD_SET for all the sockets // with completed connections. // FD_ZERO(&fdsRead); pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); if (pUPnPDevice->IsConnected()) { DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); FD_SET(pUPnPDevice->GetControlSocket(), &fdsRead); iNumSockets++; } pBilink = pBilink->GetNext(); } // // If there weren't any sockets that were connected, then we're done here. // if (iNumSockets <= 0) { DPFX(DPFPREP, 7, "No connected UPnP device control sockets."); hr = DPNH_OK; goto Exit; } DPFX(DPFPREP, 7, "There are %i connected UPnP device control sockets.", iNumSockets); // // Wait for received data. // tv.tv_usec = dwTimeout * 1000; tv.tv_sec = 0; iReturn = this->m_pfnselect(0, &fdsRead, NULL, NULL, &tv); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u trying to select on UPnP device sockets!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // If no sockets were selected, we're done. // if (iReturn == 0) { DPFX(DPFPREP, 7, "Timed out waiting for data on %i sockets.", iNumSockets); hr = DPNH_OK; goto Exit; } // // If we're here, some sockets were signalled. // pBilink = this->m_blUPnPDevices.GetNext(); while (pBilink != &this->m_blUPnPDevices) { DNASSERT(! pBilink->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilink); pBilink = pBilink->GetNext(); if (pUPnPDevice->IsConnected()) { DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); // // If this UPnP device's socket is in the read set it has data. // //if (FD_ISSET(pUPnPDevice->GetControlSocket(), &fdsRead)) if (this->m_pfn__WSAFDIsSet(pUPnPDevice->GetControlSocket(), &fdsRead)) { #ifdef DBG fFoundData = TRUE; #endif // DBG // // Grab a reference, since ReceiveUPnPDataStream may clear the // device. // pUPnPDevice->AddRef(); hr = this->ReceiveUPnPDataStream(pUPnPDevice); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't receive UPnP stream from device object 0x%p (err = 0x%lx)! Disconnecting.", pUPnPDevice, hr); // // Dump this unusable UPnP device and continue. // if (pUPnPDevice->GetOwningDevice() != NULL) { this->ClearDevicesUPnPDevice(pUPnPDevice->GetOwningDevice()); } else { DPFX(DPFPREP, 1, "UPnP device 0x%p's has already been removed from owning device.", pUPnPDevice); } hr = DPNH_OK; } // // Remove the reference we added. // pUPnPDevice->DecRef(); } else { // // Socket does not have any data. // DPFX(DPFPREP, 8, "Skipping UPnP device 0x%p because it does not have any data.", pUPnPDevice); } } else { // // This socket is not connected yet/anymore. // DPFX(DPFPREP, 7, "Skipping unconnected UPnP device 0x%p.", pUPnPDevice); } } // // Make sure we actually found a socket with data. // DNASSERT(fFoundData); // // We found data, so see if there's more. Connection should be closed // after responses. // DPFX(DPFPREP, 7, "Waiting for more data on the sockets."); goto Rewait; Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::CheckForReceivedUPnPMsgsOnAllDevices #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CheckForReceivedUPnPMsgsOnDevice" //============================================================================= // CNATHelpUPnP::CheckForReceivedUPnPMsgsOnDevice //----------------------------------------------------------------------------- // // Description: Handles any incoming data on the TCP socket for the given // UPnP device. // // If the UPnP device encounters a failure, it may get removed // from the list. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device to receive data. // DWORD dwTimeout - How long to wait for messages to arrive, or 0 // to just poll. // // Returns: HRESULT // DPNH_OK - Messages were handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::CheckForReceivedUPnPMsgsOnDevice(CUPnPDevice * const pUPnPDevice, const DWORD dwTimeout) { HRESULT hr; FD_SET fdsRead; timeval tv; int iReturn; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %u)", this, pUPnPDevice, dwTimeout); DNASSERT(this->m_dwFlags & NATHELPUPNPOBJ_INITIALIZED); DNASSERT(pUPnPDevice != NULL); DNASSERT(pUPnPDevice->IsConnected()); DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); do { // // Create an FD_SET for the socket in question. // FD_ZERO(&fdsRead); FD_SET(pUPnPDevice->GetControlSocket(), &fdsRead); // // Wait for received data. // tv.tv_usec = dwTimeout * 1000; tv.tv_sec = 0; iReturn = this->m_pfnselect(0, &fdsRead, NULL, NULL, &tv); if (iReturn == SOCKET_ERROR) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Got sockets error %u trying to select on UPnP device sockets!", dwError); #endif // DBG hr = DPNHERR_GENERIC; goto Failure; } // // If no sockets were selected, we're done. // if (iReturn == 0) { DPFX(DPFPREP, 7, "Timed out waiting for data on UPnP device 0x%p's socket.", pUPnPDevice); break; } DNASSERT(iReturn == 1); //DNASSERT(FD_ISSET(pUPnPDevice->GetControlSocket(), &fdsRead)); DNASSERT(this->m_pfn__WSAFDIsSet(pUPnPDevice->GetControlSocket(), &fdsRead)); hr = this->ReceiveUPnPDataStream(pUPnPDevice); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't receive UPnP stream from device object 0x%p! Disconnecting.", pUPnPDevice); // // Dump this unusable UPnP device and continue. // if (pUPnPDevice->GetOwningDevice() != NULL) { this->ClearDevicesUPnPDevice(pUPnPDevice->GetOwningDevice()); } else { DPFX(DPFPREP, 1, "UPnP device 0x%p's has already been removed from owning device.", pUPnPDevice); } break; } // // If the UPnP device is no longer connected, we're done. // if (! pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 7, "UPnP device 0x%p no longer connected.", pUPnPDevice); break; } // // We found data, so see if there's more. Connection should be closed // after responses. // DPFX(DPFPREP, 7, "Waiting for more data on the UPnP device 0x%p's socket.", pUPnPDevice); } while (TRUE); // // If we're here, we're no worse for wear. // hr = DPNH_OK; Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::CheckForReceivedUPnPMsgsOnDevice #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::HandleUPnPDiscoveryResponseMsg" //============================================================================= // CNATHelpUPnP::HandleUPnPDiscoveryResponseMsg //----------------------------------------------------------------------------- // // Description: Handles a UPnP discovery response message sent to this // control point. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device which received message. // SOCKADDR_IN * psaddrinSource - Pointer to address that sent the response // message. // char * pcMsg - Pointer to buffer containing the UPnP // message. It will be modified. // int iMsgSize - Size of message buffer in bytes. There // must be an extra byte after the end of // the message. // BOOL * pfInitiatedConnect - Pointer to boolean to set to TRUE if a // new UPnP device was found and a // connection to it was begun. // // Returns: HRESULT // DPNH_OK - Message was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::HandleUPnPDiscoveryResponseMsg(CDevice * const pDevice, const SOCKADDR_IN * const psaddrinSource, char * const pcMsg, const int iMsgSize, BOOL * const pfInitiatedConnect) { HRESULT hr = DPNH_OK; char * pszToken; UPNP_HEADER_INFO HeaderInfo; SOCKADDR_IN saddrinHost; char * pszRelativePath; SOCKET sTemp = INVALID_SOCKET; SOCKADDR_IN saddrinLocal; CUPnPDevice * pUPnPDevice = NULL; DWORD dwNumConnectRetries = 0; DWORD dwError; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, %i, 0x%p)", this, pDevice, psaddrinSource, pcMsg, iMsgSize, pfInitiatedConnect); #ifdef DBG // // Log the message. // this->PrintUPnPTransactionToFile(pcMsg, iMsgSize, "Inbound UPnP datagram headers", pDevice); #endif // DBG // // Any errors we get while analyzing the message will cause us to jump to // the Exit label with hr == DPNH_OK. Once we start trying to connect to // the UPnP device, that will change. See below. // // // First of all, if this device already has a UPnP device, then we'll just // ignore this response. Either it's a duplicate of an earlier response, // a cache-refresh, or it's from a different device. Duplicates we should // ignore. Cache-refresh is essentially a duplicate. We can't handle any // information changes, so ignore those too. And finally, we don't handle // multiple Internet gateway UPnP devices, so ignore those, too. // pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { DPFX(DPFPREP, 6, "Already have UPnP device (0x%p) ignoring message.", pUPnPDevice); // // GetUPnPDevice did not add a reference to pUPnPDevice. // goto Exit; } // // Make sure the sender of this response is valid. It should be either // the local device address, or the address of the gateway. If we // broadcasted or multicasted, we'll need to be more lenient. We'll just // ensure that the response came from someone local (it doesn't make sense // that in order to make mappings for our private network we would need to // contact something outside). // if (psaddrinSource->sin_addr.S_un.S_addr != pDevice->GetLocalAddressV4()) { if (g_fUseMulticastUPnPDiscovery) { if (! this->IsAddressLocal(pDevice, psaddrinSource)) { DPFX(DPFPREP, 1, "Multicast search responding device (%u.%u.%u.%u:%u) is not local, ignoring message.", psaddrinSource->sin_addr.S_un.S_un_b.s_b1, psaddrinSource->sin_addr.S_un.S_un_b.s_b2, psaddrinSource->sin_addr.S_un.S_un_b.s_b3, psaddrinSource->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinSource->sin_port)); goto Exit; } } else { // // Retrieve the gateway's address (using saddrinHost as a temporary // variable. If that fails or returns the broadcast address, just // make sure the address is local. Otherwise, expect an exact // match. // if ((! this->GetAddressToReachGateway(pDevice, &saddrinHost.sin_addr)) || (saddrinHost.sin_addr.S_un.S_addr == INADDR_BROADCAST)) { if (! this->IsAddressLocal(pDevice, psaddrinSource)) { DPFX(DPFPREP, 1, "No gateway/broadcast search responding device (%u.%u.%u.%u:%u) is not local, ignoring message.", psaddrinSource->sin_addr.S_un.S_un_b.s_b1, psaddrinSource->sin_addr.S_un.S_un_b.s_b2, psaddrinSource->sin_addr.S_un.S_un_b.s_b3, psaddrinSource->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinSource->sin_port)); goto Exit; } } else { if (psaddrinSource->sin_addr.S_un.S_addr != saddrinHost.sin_addr.S_un.S_addr) { DPFX(DPFPREP, 1, "Unicast search responding device (%u.%u.%u.%u:%u) is not gateway (%u.%u.%u.%u), ignoring message.", psaddrinSource->sin_addr.S_un.S_un_b.s_b1, psaddrinSource->sin_addr.S_un.S_un_b.s_b2, psaddrinSource->sin_addr.S_un.S_un_b.s_b3, psaddrinSource->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinSource->sin_port), saddrinHost.sin_addr.S_un.S_un_b.s_b1, saddrinHost.sin_addr.S_un.S_un_b.s_b2, saddrinHost.sin_addr.S_un.S_un_b.s_b3, saddrinHost.sin_addr.S_un.S_un_b.s_b4); goto Exit; } } } // end else (not multicasting search) } else { // // Response was from the local device. // } // // Ensure the buffer is NULL terminated to prevent buffer overruns when // using the string routines. // pcMsg[iMsgSize] = '\0'; // // Find the version string. // pszToken = strtok(pcMsg, " \t\n"); if (pszToken == NULL) { DPFX(DPFPREP, 9, "Could not locate first white-space separator."); goto Exit; } // // Check the version string, case insensitive. // if ((_stricmp(pszToken, HTTP_VERSION) != 0) && (_stricmp(pszToken, HTTP_VERSION_ALT) != 0)) { DPFX(DPFPREP, 1, "The version specified in the response message is not \"" HTTP_VERSION "\" or \"" HTTP_VERSION_ALT "\" (it's \"%hs\").", pszToken); goto Exit; } // // Find the response code string. // pszToken = strtok(NULL, " "); if (pszToken == NULL) { DPFX(DPFPREP, 1, "Could not find the response code space."); goto Exit; } // // Make sure it's the success result, case insensitive. // if (_stricmp(pszToken, "200") != 0) { DPFX(DPFPREP, 1, "The response code specified is not \"200\" (it's \"%hs\").", pszToken); goto Exit; } // // Find the response code message. // pszToken = strtok(NULL, " \t\r"); if (pszToken == NULL) { DPFX(DPFPREP, 9, "Could not locate response code message white-space separator."); goto Exit; } // // Make sure it's the right string, case insensitive. // if (_stricmp(pszToken, "OK") != 0) { DPFX(DPFPREP, 1, "The response code message specified is not \"OK\" (it's \"%hs\").", pszToken); goto Exit; } // // Parse the header information. // ZeroMemory(&HeaderInfo, sizeof(HeaderInfo)); this->ParseUPnPHeaders((pszToken + strlen(pszToken) + 1), &HeaderInfo); // // Skip responses which don't include the required headers. // if ((HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CACHECONTROL] == NULL) || (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_EXT] == NULL) || (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_LOCATION] == NULL) || (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_SERVER] == NULL) || (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_ST] == NULL) || (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_USN] == NULL)) { DPFX(DPFPREP, 1, "One of the expected headers was not specified, ignoring message."); goto Exit; } // // Make sure the service type is correct. // if ((_stricmp(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_ST], URI_SERVICE_WANIPCONNECTION_A) != 0) && (_stricmp(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_ST], URI_SERVICE_WANPPPCONNECTION_A) != 0)) { DPFX(DPFPREP, 1, "Service type \"%hs\" is not desired \"" URI_SERVICE_WANIPCONNECTION_A "\" or \"" URI_SERVICE_WANPPPCONNECTION_A "\", ignoring message.", HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_ST]); goto Exit; } // // Parse the location header into an address and port. // hr = this->GetAddressFromURL(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_LOCATION], &saddrinHost, &pszRelativePath); if (hr != DPNH_OK) { DPFX(DPFPREP, 1, "Couldn't get address from URL (err = 0x%lx), ignoring message.", hr); hr = DPNH_OK; goto Exit; } // // Don't accept responses that refer to addresses other than the one that // sent this response. // if (psaddrinSource->sin_addr.S_un.S_addr != saddrinHost.sin_addr.S_un.S_addr) { DPFX(DPFPREP, 1, "Host IP address designated (%u.%u.%u.%u:%u) is not the same as source of response (%u.%u.%u.%u:%u), ignoring message.", saddrinHost.sin_addr.S_un.S_un_b.s_b1, saddrinHost.sin_addr.S_un.S_un_b.s_b2, saddrinHost.sin_addr.S_un.S_un_b.s_b3, saddrinHost.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinHost.sin_port), psaddrinSource->sin_addr.S_un.S_un_b.s_b1, psaddrinSource->sin_addr.S_un.S_un_b.s_b2, psaddrinSource->sin_addr.S_un.S_un_b.s_b3, psaddrinSource->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinSource->sin_port)); hr = DPNH_OK; goto Exit; } // // Don't accept responses that refer to ports in the reserved range (less // than or equal to 1024), other than the standard HTTP port. // if ((NTOHS(saddrinHost.sin_port) <= MAX_RESERVED_PORT) && (saddrinHost.sin_port != HTONS(HTTP_PORT))) { DPFX(DPFPREP, 1, "Host address designated invalid port %u, ignoring message.", NTOHS(saddrinHost.sin_port)); hr = DPNH_OK; goto Exit; } // // Any errors we get from here on out will cause us to jump to the Failure // label, instead of going straight to Exit. // // // Create a socket to connect to that address. // ZeroMemory(&saddrinLocal, sizeof(saddrinLocal)); saddrinLocal.sin_family = AF_INET; saddrinLocal.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); CreateSocket: sTemp = this->CreateSocket(&saddrinLocal, SOCK_STREAM, 0); if (sTemp == INVALID_SOCKET) { DPFX(DPFPREP, 0, "Couldn't create stream socket!"); hr = DPNHERR_GENERIC; goto Failure; } // // Initiate the connection to the UPnP device. It is expected that connect // will return WSAEWOULDBLOCK. // if (this->m_pfnconnect(sTemp, (SOCKADDR*) (&saddrinHost), sizeof(saddrinHost)) != 0) { dwError = this->m_pfnWSAGetLastError(); // // If we hit a particular local/remote pair that is in TCP's TIME_WAIT // state, then we need to pick a different port. // if (dwError == WSAEADDRINUSE) { dwNumConnectRetries++; if (dwNumConnectRetries > MAX_NUM_TIMEWAITCONNECT_RETRIES) { DPFX(DPFPREP, 0, "Could not connect to %u.%u.%u.%u:%u after %u attempts!", saddrinHost.sin_addr.S_un.S_un_b.s_b1, saddrinHost.sin_addr.S_un.S_un_b.s_b2, saddrinHost.sin_addr.S_un.S_un_b.s_b3, saddrinHost.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinHost.sin_port), dwNumConnectRetries); hr = DPNHERR_GENERIC; goto Failure; } DPFX(DPFPREP, 1, "Could not connect to %u.%u.%u.%u:%u using local address %u.%u.%u.%u:%u, picking different local port.", saddrinHost.sin_addr.S_un.S_un_b.s_b1, saddrinHost.sin_addr.S_un.S_un_b.s_b2, saddrinHost.sin_addr.S_un.S_un_b.s_b3, saddrinHost.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinHost.sin_port), saddrinLocal.sin_addr.S_un.S_un_b.s_b1, saddrinLocal.sin_addr.S_un.S_un_b.s_b2, saddrinLocal.sin_addr.S_un.S_un_b.s_b3, saddrinLocal.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinLocal.sin_port)); this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; goto CreateSocket; } if (dwError != WSAEWOULDBLOCK) { DPFX(DPFPREP, 0, "Couldn't connect socket, error = %u!", dwError); hr = DPNHERR_GENERIC; goto Failure; } } else { // // connect() on non-blocking sockets is explicitly documented as // always returning WSAEWOULDBLOCK, but CE seems to do it anyway. // DPFX(DPFPREP, 8, "Socket connected right away."); } // // Create a new object to represent the UPnP device to which we're trying // to connect. // pUPnPDevice = new CUPnPDevice(this->m_dwCurrentUPnPDeviceID++); if (pUPnPDevice == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } hr = pUPnPDevice->SetLocationURL(pszRelativePath); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't set UPnP device's location URL!"); goto Failure; } pUPnPDevice->SetHostAddress(&saddrinHost); hr = pUPnPDevice->SetUSN(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_USN]); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't set UPnP device's USN!"); goto Failure; } hr = pUPnPDevice->CreateReceiveBuffer(UPNP_STREAM_RECV_BUFFER_INITIAL_SIZE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't create UPnP device's receive buffer!"); goto Failure; } DPFX(DPFPREP, 7, "Created new UPnP device object 0x%p ID %u.", pUPnPDevice, pUPnPDevice->GetID()); // // It's connecting... // pUPnPDevice->NoteConnecting(); // // See if we need to avoid trying asymmetric port mappings. // if (g_fNoAsymmetricMappings) { DPFX(DPFPREP, 1, "Preventing asymmetric port mappings on new UPnP device 0x%p.", pUPnPDevice); pUPnPDevice->NoteDoesNotSupportAsymmetricMappings(); } // // Transfer ownership of the socket to the object. // pUPnPDevice->SetControlSocket(sTemp); // // Associate it with the device. // pUPnPDevice->MakeDeviceOwner(pDevice); // // Add it to the global list, and transfer ownership of the reference. // pUPnPDevice->m_blList.InsertBefore(&this->m_blUPnPDevices); pUPnPDevice = NULL; // // Inform the caller that there's a new connection pending. // (*pfInitiatedConnect) = TRUE; // // Clear the device's discovery flags, now that we have a device, we're not // going to be searching anymore. // pDevice->NoteNotPerformingRemoteUPnPDiscovery(); pDevice->NoteNotPerformingLocalUPnPDiscovery(); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (pUPnPDevice != NULL) { //pUPnPDevice->DestroyReceiveBuffer(); pUPnPDevice->ClearUSN(); pUPnPDevice->ClearLocationURL(); pUPnPDevice->DecRef(); } if (sTemp != INVALID_SOCKET) { this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; } goto Exit; } // CNATHelpUPnP::HandleUPnPDiscoveryResponseMsg #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ReconnectUPnPControlSocket" //============================================================================= // CNATHelpUPnP::ReconnectUPnPControlSocket //----------------------------------------------------------------------------- // // Description: Re-establishes a UPnP device TCP/IP connection. // // UPnP devices may get removed from list if a failure occurs. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device to reconnect. // // Returns: HRESULT // DPNH_OK - Message was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::ReconnectUPnPControlSocket(CUPnPDevice * const pUPnPDevice) { HRESULT hr = DPNH_OK; SOCKET sTemp = INVALID_SOCKET; CDevice * pDevice; SOCKADDR_IN saddrinLocal; DWORD dwNumConnectRetries = 0; DWORD dwError; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p)", this, pUPnPDevice); DNASSERT(pUPnPDevice->GetControlSocket() == INVALID_SOCKET); // // Create a socket to connect to that address. // pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pDevice != NULL); ZeroMemory(&saddrinLocal, sizeof(saddrinLocal)); saddrinLocal.sin_family = AF_INET; saddrinLocal.sin_addr.S_un.S_addr = pDevice->GetLocalAddressV4(); CreateSocket: sTemp = this->CreateSocket(&saddrinLocal, SOCK_STREAM, 0); if (sTemp == INVALID_SOCKET) { DPFX(DPFPREP, 0, "Couldn't create stream socket!"); hr = DPNHERR_GENERIC; goto Failure; } // // Initiate the connection to the UPnP device. It is expected that connect // will return WSAEWOULDBLOCK. // if (this->m_pfnconnect(sTemp, (SOCKADDR*) (pUPnPDevice->GetControlAddress()), sizeof(SOCKADDR_IN)) != 0) { dwError = this->m_pfnWSAGetLastError(); // // If we hit a particular local/remote pair that is in TCP's TIME_WAIT // state, then we need to pick a different port. // if (dwError == WSAEADDRINUSE) { dwNumConnectRetries++; if (dwNumConnectRetries > MAX_NUM_TIMEWAITCONNECT_RETRIES) { DPFX(DPFPREP, 0, "Could not connect to %u.%u.%u.%u:%u after %u attempts!", pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b1, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b2, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b3, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b4, NTOHS(pUPnPDevice->GetControlAddress()->sin_port), dwNumConnectRetries); hr = DPNHERR_GENERIC; goto Failure; } DPFX(DPFPREP, 1, "Could not connect to %u.%u.%u.%u:%u using local address %u.%u.%u.%u:%u, picking different local port.", pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b1, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b2, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b3, pUPnPDevice->GetControlAddress()->sin_addr.S_un.S_un_b.s_b4, NTOHS(pUPnPDevice->GetControlAddress()->sin_port), saddrinLocal.sin_addr.S_un.S_un_b.s_b1, saddrinLocal.sin_addr.S_un.S_un_b.s_b2, saddrinLocal.sin_addr.S_un.S_un_b.s_b3, saddrinLocal.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinLocal.sin_port)); this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; goto CreateSocket; } if (dwError != WSAEWOULDBLOCK) { DPFX(DPFPREP, 0, "Couldn't connect socket, error = %u!", dwError); hr = DPNHERR_GENERIC; goto Failure; } } else { // // connect() on non-blocking sockets is explicitly documented as // always returning WSAEWOULDBLOCK, but CE seems to do it anyway. // DPFX(DPFPREP, 8, "Socket connected right away."); } // // It's reconnecting... // pUPnPDevice->NoteConnecting(); // // Transfer ownership of the socket to the object. // pUPnPDevice->SetControlSocket(sTemp); sTemp = INVALID_SOCKET; // // Wait for the connect to complete. // hr = this->WaitForUPnPConnectCompletions(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't wait for UPnP connect completions!"); goto Failure; } // // Make sure the connect completed successfully. // if (! pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 0, "UPnP device 0x%p failed reconnecting!", pUPnPDevice); // // Note that the device is cleaned up and is not in any lists anymore. // hr = DPNHERR_SERVERNOTRESPONDING; goto Exit; } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: if (sTemp != INVALID_SOCKET) { this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; } goto Exit; } // CNATHelpUPnP::ReconnectUPnPControlSocket #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ReceiveUPnPDataStream" //============================================================================= // CNATHelpUPnP::ReceiveUPnPDataStream //----------------------------------------------------------------------------- // // Description: Receives incoming data from a UPnP TCP connection. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device with data to receive. // // Returns: HRESULT // DPNH_OK - Data was received successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::ReceiveUPnPDataStream(CUPnPDevice * const pUPnPDevice) { HRESULT hr = DPNH_OK; char * pszDeChunkedBuffer = NULL; int iReturn; DWORD dwError; char * pszCurrent; char * pszEndOfBuffer; UPNP_HEADER_INFO HeaderInfo; DWORD dwContentLength; char * pszToken; DWORD dwHTTPResponseCode; int iHeaderLength; DWORD dwBufferRemaining; char * pszChunkData; DWORD dwChunkSize; char * pszDestination; #ifdef DBG char * pszPrintIfFailed = NULL; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p)", this, pUPnPDevice); do { // // Make sure there's room in the buffer to actually get the data. // if (pUPnPDevice->GetRemainingReceiveBufferSize() == 0) { DPFX(DPFPREP, 7, "Increasing receive buffer size prior to receiving."); hr = pUPnPDevice->IncreaseReceiveBufferSize(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't increase receive buffer size prior to receiving!"); goto Failure; } } // // Actually get the data that was indicated. // iReturn = this->m_pfnrecv(pUPnPDevice->GetControlSocket(), pUPnPDevice->GetCurrentReceiveBufferPtr(), pUPnPDevice->GetRemainingReceiveBufferSize(), 0); switch (iReturn) { case 0: { // // Since the connection has been broken, shutdown the socket. // this->m_pfnshutdown(pUPnPDevice->GetControlSocket(), 0); // ignore error this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); // // Mark the socket as not connected. // pUPnPDevice->NoteNotConnected(); // // There may have been HTTP success/error information sent // before the connection was closed. // if (pUPnPDevice->GetUsedReceiveBufferSize() == 0) { DPFX(DPFPREP, 3, "UPnP device 0x%p shut down connection (no more data).", pUPnPDevice); // // Hopefully we got what we needed, but we're done now. // goto Exit; } DPFX(DPFPREP, 3, "UPnP device 0x%p gracefully closed connection after sending data.", pUPnPDevice); // // Continue through and parse what data we have. // break; } case SOCKET_ERROR: { dwError = this->m_pfnWSAGetLastError(); switch (dwError) { case WSAEMSGSIZE: { // // There's not enough room in the buffer. Double the // buffer and try again. // DPFX(DPFPREP, 7, "Increasing receive buffer size after WSAEMSGSIZE error."); hr = pUPnPDevice->IncreaseReceiveBufferSize(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't increase receive buffer size!"); goto Failure; } break; } case WSAECONNABORTED: case WSAECONNRESET: { DPFX(DPFPREP, 1, "UPnP device shutdown connection (err = %u).", dwError); // // Our caller should remove this device. // hr = DPNHERR_GENERIC; goto Failure; break; } case WSAENOBUFS: { DPFX(DPFPREP, 0, "WinSock returned WSAENOBUFS while receiving!"); // // Our caller should remove this device. // hr = DPNHERR_OUTOFMEMORY; goto Failure; break; } default: { DPFX(DPFPREP, 0, "Got unknown sockets error %u while receiving data!", dwError); hr = DPNHERR_GENERIC; goto Failure; break; } } break; } default: { DPFX(DPFPREP, 2, "Received %i bytes of data from UPnP device 0%p.", iReturn, pUPnPDevice); pUPnPDevice->UpdateUsedReceiveBufferSize(iReturn); // // We'll also break out of the do-while loop below. // break; } } } while (iReturn == SOCKET_ERROR); // // If we're here, we've gotten the data that has currently arrived. // // // If we have all the headers, specifically the content length header, we // can tell whether we have the whole message or not. If not, we can't do // anything until the rest of the data comes in. // if (pUPnPDevice->IsWaitingForContent()) { dwContentLength = pUPnPDevice->GetExpectedContentSize(); if (dwContentLength == -1) { // // We have all the headers, but a faulty server implementation did // not send a content-length header. We're going to wait until the // socket is closed by the other side, and then consider all of the // data received at that time to be the content. NOTE: It is // expected that there will be a higher level timeout preventing us // from waiting forever. // // So if the device is still connected, keep waiting. // // If we're using chunked transfer we won't get a content-length // header or a total size legitimately. We will know the sizes of // individual chunks, but that doesn't help us much. We basically // need to scan for the "last chunk" indicator (or socket // shutdown). // if (pUPnPDevice->IsConnected()) { // // If we're using chunked transfer, see if we have enough // information already to determine if we're done. // if (pUPnPDevice->IsUsingChunkedTransferEncoding()) { // // Walk all of the chunks we have so far to see if we have // the last one (the zero terminator). // pszCurrent = pUPnPDevice->GetReceiveBufferStart(); dwBufferRemaining = pUPnPDevice->GetUsedReceiveBufferSize(); do { if (! this->GetNextChunk(pszCurrent, dwBufferRemaining, &pszChunkData, &dwChunkSize, &pszCurrent, &dwBufferRemaining)) { DPFX(DPFPREP, 1, "Body contains invalid chunk (at offset %u)! Disconnecting.", (DWORD_PTR) (pszCurrent - pUPnPDevice->GetReceiveBufferStart())); goto Failure; } if (pszChunkData == NULL) { DPFX(DPFPREP, 1, "Did not receive end of chunked data (%u bytes received so far), continuing to waiting for data.", pUPnPDevice->GetUsedReceiveBufferSize()); goto Exit; } } while (dwChunkSize != 0); } else { DPFX(DPFPREP, 1, "Waiting for connection to be shutdown (%u bytes received).", pUPnPDevice->GetUsedReceiveBufferSize()); goto Exit; } } DPFX(DPFPREP, 1, "Socket closed with %u bytes received, parsing.", pUPnPDevice->GetUsedReceiveBufferSize()); } else { if (dwContentLength > pUPnPDevice->GetUsedReceiveBufferSize()) { // // We still haven't received all the data yet. Keep waiting // (unless the socket is closed). // if (pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 1, "Still waiting for all content (%u bytes of %u total received).", pUPnPDevice->GetUsedReceiveBufferSize(), dwContentLength); goto Exit; } DPFX(DPFPREP, 1, "Socket closed before all content received (%u bytes of %u total), parsing anyway.", pUPnPDevice->GetUsedReceiveBufferSize(), dwContentLength); // // Try parsing it anyway. // } } // // Retrieve the HTTP response code stored earlier. // dwHTTPResponseCode = pUPnPDevice->GetHTTPResponseCode(); // // All of the content that's going to arrive, has. // pUPnPDevice->NoteNotWaitingForContent(); // // Make sure the buffer is NULL terminated. But first ensure the // buffer can hold a new NULL termination character. // if (pUPnPDevice->GetRemainingReceiveBufferSize() == 0) { DPFX(DPFPREP, 7, "Increasing receive buffer size to hold NULL termination (for content)."); hr = pUPnPDevice->IncreaseReceiveBufferSize(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't increase receive buffer size to accommodate NULL termination (for content)!"); goto Failure; } } // // Move to the end of the buffer and NULL terminate it for string ops. // pszEndOfBuffer = pUPnPDevice->GetReceiveBufferStart() + pUPnPDevice->GetUsedReceiveBufferSize(); (*pszEndOfBuffer) = '\0'; #ifdef DBG // // Print from the start of the buffer if we fail. // pszPrintIfFailed = pUPnPDevice->GetReceiveBufferStart(); #endif // DBG // // We now have all the data in a string buffer. Continue... // } else { // // We haven't already received the headers. The data we just got // should be those headers. // pszCurrent = pUPnPDevice->GetReceiveBufferStart(); // // Quick check to make sure the buffer starts with something reasonable // in hopes of catching completely bogus responses earlier. Note that // the buffer is not necessarily NULL terminated or completely // available yet. // if ((pUPnPDevice->GetUsedReceiveBufferSize() >= strlen(HTTP_PREFIX)) && (_strnicmp(pszCurrent, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0)) { DPFX(DPFPREP, 1, "Headers do not begin with \"" HTTP_PREFIX "\"! Disconnecting."); goto Failure; } // // We don't want to walk off the end of the buffer, so only search up // to the last possible location for the sequence, which is the end of // the buffer minus the double EOL sequence. // pszEndOfBuffer = pszCurrent + pUPnPDevice->GetUsedReceiveBufferSize() - strlen(EOL EOL); while (pszCurrent < pszEndOfBuffer) { if (_strnicmp(pszCurrent, EOL EOL, strlen(EOL EOL)) == 0) { // // Found end of headers. // // // Possible loss of data on 64-bit is okay, we're just saving // this for logging purposes. // iHeaderLength = (int) ((INT_PTR) (pszCurrent - pUPnPDevice->GetReceiveBufferStart())); break; } pszCurrent++; } // // If we didn't find the end of the headers, we're done (for now). // if (pszCurrent >= pszEndOfBuffer) { // // We still haven't received all the data yet. Keep waiting // (unless the socket is closed). // if (pUPnPDevice->IsConnected()) { // // Make sure the length is still within reason. // if (pUPnPDevice->GetUsedReceiveBufferSize() > MAX_UPNP_HEADER_LENGTH) { DPFX(DPFPREP, 1, "Headers are too large (%u > %u)! Disconnecting.", pUPnPDevice->GetUsedReceiveBufferSize(), MAX_UPNP_HEADER_LENGTH); goto Failure; } DPFX(DPFPREP, 1, "Have not detected end of headers yet (%u bytes received).", pUPnPDevice->GetUsedReceiveBufferSize()); goto Exit; } DPFX(DPFPREP, 1, "Socket closed before end of headers detected (%u bytes received), parsing anyway.", pUPnPDevice->GetUsedReceiveBufferSize()); // // Consider the whole buffer the headers length. // iHeaderLength = pUPnPDevice->GetUsedReceiveBufferSize(); // // Try parsing it anyway. // } #ifdef DBG // // Log the headers. // this->PrintUPnPTransactionToFile(pUPnPDevice->GetReceiveBufferStart(), iHeaderLength, "Inbound UPnP stream headers", pUPnPDevice->GetOwningDevice()); #endif // DBG // // Make sure the buffer is NULL terminated. But first ensure the // buffer can hold a new NULL termination character. // if (pUPnPDevice->GetRemainingReceiveBufferSize() == 0) { DPFX(DPFPREP, 7, "Increasing receive buffer size to hold NULL termination (for headers)."); hr = pUPnPDevice->IncreaseReceiveBufferSize(); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't increase receive buffer size to accommodate NULL termination (for headers)!"); goto Failure; } // // Find the end of the new buffer, and make sure it's NULL // terminated for string ops. // pszEndOfBuffer = pUPnPDevice->GetReceiveBufferStart() + pUPnPDevice->GetUsedReceiveBufferSize(); } else { // // Move to the end of the buffer and NULL terminate it for string // ops. // pszEndOfBuffer += strlen(EOL EOL); } (*pszEndOfBuffer) = '\0'; // // Make sure the buffer is a valid response. Find the version string. // pszToken = strtok(pUPnPDevice->GetReceiveBufferStart(), " \t\n"); if (pszToken == NULL) { DPFX(DPFPREP, 1, "Could not locate first white-space separator! Disconnecting."); goto Failure; } // // Check the version string, case insensitive. // if ((_stricmp(pszToken, HTTP_VERSION) != 0) && (_stricmp(pszToken, HTTP_VERSION_ALT) != 0)) { DPFX(DPFPREP, 1, "The version specified in the response message is not \"" HTTP_VERSION "\" or \"" HTTP_VERSION_ALT "\" (it's \"%hs\")! Disconnecting.", pszToken); goto Failure; } // // Find the response code number string. // pszToken = strtok(NULL, " "); if (pszToken == NULL) { DPFX(DPFPREP, 1, "Could not find the response code number space! Disconnecting."); goto Failure; } // // Retrieve the success/failure code value. // dwHTTPResponseCode = atoi(pszToken); // // Find the response code message. // pszToken = strtok(NULL, "\t\r"); if (pszToken == NULL) { DPFX(DPFPREP, 1, "Could not locate response code message white-space separator! Disconnecting."); goto Failure; } DPFX(DPFPREP, 1, "Received HTTP response %u \"%hs\".", dwHTTPResponseCode, pszToken); // // Try parsing the headers (after the response status line). // ZeroMemory(&HeaderInfo, sizeof(HeaderInfo)); this->ParseUPnPHeaders((pszToken + strlen(pszToken) + 1), &HeaderInfo); #ifdef DBG // // Print from the start of the message body if we fail. // pszPrintIfFailed = HeaderInfo.pszMsgBody; #endif // DBG if ((HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_TRANSFERENCODING] != NULL) && (_strnicmp(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_TRANSFERENCODING], "chunked", strlen("chunked")) == 0)) { pUPnPDevice->NoteUsingChunkedTransferEncoding(); } // // We're pretty lenient about missing headers... // if (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTLENGTH] == NULL) { DPFX(DPFPREP, 1, "Content-length header was not specified in response (chunked = %i).", pUPnPDevice->IsUsingChunkedTransferEncoding()); // // May be because we're using chunked transfer encoding, or it // could be a bad device. Either way, we'll continue... // dwContentLength = -1; } else { dwContentLength = atoi(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTLENGTH]); #ifdef DBG if (dwContentLength == 0) { DPFX(DPFPREP, 1, "Content length (\"%hs\") is zero.", HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTLENGTH]); } #endif // DBG if (HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTTYPE] == NULL) { DPFX(DPFPREP, 1, "Expected content-type header was not specified in response, continuing."); } else { if (_strnicmp(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTTYPE], "text/xml", strlen("text/xml")) != 0) { DPFX(DPFPREP, 1, "Content type does not start with \"text/xml\" (it's \"%hs\")! Disconnecting.", HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTTYPE]); goto Failure; } #ifdef DBG // // Note whether the content type is qualified with // "charset=utf-8" or not. // if (_stricmp(HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTTYPE], "text/xml; charset=\"utf-8\"") != 0) { DPFX(DPFPREP, 1, "Content type is xml, but it's not \"text/xml; charset=\"utf-8\"\" (it's \"%hs\"), continuing.", HeaderInfo.apszHeaderStrings[RESPONSEHEADERINDEX_CONTENTTYPE]); // // The check was just for information purposes, continue. // } #endif // DBG } } // // Content length may be valid, or the special value -1 at this // point. // DPFX(DPFPREP, 7, "Moving past 0x%p bytes of header.", HeaderInfo.pszMsgBody - pUPnPDevice->GetReceiveBufferStart()); // // Forget about all the headers, we only care about data now. // pUPnPDevice->UpdateReceiveBufferStart(HeaderInfo.pszMsgBody); // // The buffer has been destroyed up to the end of the headers (meaning // that calling ParseUPnPHeaders won't work on the same buffer again), // so if we don't have all the content yet, we need to save the // response code, remember the fact that we're not done yet, and // continue waiting for the rest of the data. // Of course, if there wasn't a content-length header, we have to wait // for the socket to shutdown before we can parse. // // Also, if we're using chunked transfer we won't get a content-length // header or a total size legitimately. We will know the sizes of // individual chunks, but that doesn't help us much. We basically need // to scan for the "last chunk" indicator (or socket shutdown). // if (dwContentLength == -1) { if (pUPnPDevice->IsConnected()) { // // If we're using chunked transfer, see if we have enough // information already to determine if we're done. // if (pUPnPDevice->IsUsingChunkedTransferEncoding()) { // // Walk all of the chunks we have so far to see if we have // the last one (the zero terminator). // pszCurrent = pUPnPDevice->GetReceiveBufferStart(); dwBufferRemaining = pUPnPDevice->GetUsedReceiveBufferSize(); do { if (! this->GetNextChunk(pszCurrent, dwBufferRemaining, &pszChunkData, &dwChunkSize, &pszCurrent, &dwBufferRemaining)) { DPFX(DPFPREP, 1, "Body contains invalid chunk (at offset %u)! Disconnecting.", (DWORD_PTR) (pszCurrent - pUPnPDevice->GetReceiveBufferStart())); goto Failure; } if (pszChunkData == NULL) { DPFX(DPFPREP, 1, "Did not receive end of chunked data (%u bytes received so far), continuing to waiting for data.", pUPnPDevice->GetUsedReceiveBufferSize()); pUPnPDevice->NoteWaitingForContent(dwContentLength, dwHTTPResponseCode); goto Exit; } } while (dwChunkSize != 0); } else { DPFX(DPFPREP, 1, "Unknown content length (%u bytes received so far), waiting for connection to close before parsing.", pUPnPDevice->GetUsedReceiveBufferSize()); pUPnPDevice->NoteWaitingForContent(dwContentLength, dwHTTPResponseCode); goto Exit; } } } else { if ((pUPnPDevice->IsConnected()) && (dwContentLength > pUPnPDevice->GetUsedReceiveBufferSize())) { DPFX(DPFPREP, 1, "Not all content has been received (%u bytes of %u total), waiting for remainder of message.", pUPnPDevice->GetUsedReceiveBufferSize(), dwContentLength); pUPnPDevice->NoteWaitingForContent(dwContentLength, dwHTTPResponseCode); goto Exit; } } // // We have all the data already (and it's in string form). // Continue... // } // // If we got here, it means we have all the data that we're expecting. // Shutdown the socket if it hasn't been already. // if (pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 7, "Forcing UPnP device 0x%p socket disconnection.", pUPnPDevice); DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); this->m_pfnshutdown(pUPnPDevice->GetControlSocket(), 0); // ignore error this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); pUPnPDevice->NoteNotConnected(); } else { DNASSERT(pUPnPDevice->GetControlSocket() == INVALID_SOCKET); } // // If the sender used chunked-transfer encoding, copy each of the chunks // into a contiguous "dechunked" buffer. // if (pUPnPDevice->IsUsingChunkedTransferEncoding()) { // // Prepare a dechunked buffer. // pszDeChunkedBuffer = (char*) DNMalloc(pUPnPDevice->GetUsedReceiveBufferSize()); if (pszDeChunkedBuffer == NULL) { hr = DPNHERR_OUTOFMEMORY; goto Failure; } pszDestination = pszDeChunkedBuffer; // // Walk all of the chunks. // pszCurrent = pUPnPDevice->GetReceiveBufferStart(); dwBufferRemaining = pUPnPDevice->GetUsedReceiveBufferSize(); do { if (! this->GetNextChunk(pszCurrent, dwBufferRemaining, &pszChunkData, &dwChunkSize, &pszCurrent, &dwBufferRemaining)) { DPFX(DPFPREP, 1, "Body contains invalid chunk (at offset %u)!", (DWORD_PTR) (pszCurrent - pUPnPDevice->GetReceiveBufferStart())); goto Failure; } // // If this chunk is unfinished, bail. // if (pszChunkData == NULL) { DPFX(DPFPREP, 1, "Did not receive complete chunked data!", pUPnPDevice->GetUsedReceiveBufferSize()); goto Failure; } // // If this is the last chunk, terminate the string here and stop. // if (dwChunkSize == 0) { (*pszDestination) = '\0'; break; } // // Otherwise copy the chunk data to the dechunked buffer. // memcpy(pszDestination, pszChunkData, dwChunkSize); pszDestination += dwChunkSize; } while (TRUE); // // Turn off the flag since it's no longer relevant. // pUPnPDevice->NoteNotUsingChunkedTransferEncoding(); // // Parse the dechunked version of the message. // pszCurrent = pszDeChunkedBuffer; } else { // // Get a pointer to the start of the message body. // pszCurrent = pUPnPDevice->GetReceiveBufferStart(); } // // Clear the buffer for the next message. Note that this does not // invalidate the pszMessageBody pointer we just retrieved because // ClearReceiveBuffer just resets the pointers back to the beginning (it // does not zero out the buffer). We need to reset the buffer because the // handler we're about to call may try to receive data as well. The buffer // must be "empty" (reset) at that time. Of course, if the handler does do // that, then it had better have saved off copies of any strings it needs // because they will get overwritten once receiving starts. // // Content length of -1 means we never detected a valid CONTENT-LENGTH // header, so we will assume the rest of the data that has been received up // to now is (all of) the content. // #ifdef DBG if ((dwContentLength != -1) && (dwContentLength < pUPnPDevice->GetUsedReceiveBufferSize())) { // // The string was terminated before this data, so the handler will // never even see it. // DPFX(DPFPREP, 1, "Ignoring %u bytes of extra data after response from UPnP device 0x%p.", (pUPnPDevice->GetUsedReceiveBufferSize() - dwContentLength), pUPnPDevice); } // // HandleUPnPControlResponseBody or HandleUPnPDescriptionResponseBody might // print out the body or overwrite the data, so we can't print it out if // they fail. // pszPrintIfFailed = NULL; #endif // DBG pUPnPDevice->ClearReceiveBuffer(); if (pUPnPDevice->IsWaitingForControlResponse()) { // // It looks like it's a control response, because someone is waiting // for one. // hr = this->HandleUPnPControlResponseBody(pUPnPDevice, dwHTTPResponseCode, pszCurrent); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't handle control response!", hr); goto Failure; } } else { // // Not waiting for a control response, assume it's a description // response. // hr = this->HandleUPnPDescriptionResponseBody(pUPnPDevice, dwHTTPResponseCode, pszCurrent); if (hr != DPNH_OK) { // // UPnP device may have been removed from list. // DPFX(DPFPREP, 0, "Couldn't handle description response!", hr); goto Failure; } } Exit: if (pszDeChunkedBuffer != NULL) { DNFree(pszDeChunkedBuffer); pszDeChunkedBuffer = NULL; } DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: // // Something went wrong, break the connection if it exists. // if (pUPnPDevice->IsConnected()) { this->m_pfnshutdown(pUPnPDevice->GetControlSocket(), 0); // ignore error this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); // // Mark the socket as not connected. // pUPnPDevice->NoteNotConnected(); } #ifdef DBG if (pszPrintIfFailed != NULL) { this->PrintUPnPTransactionToFile(pszPrintIfFailed, strlen(pszPrintIfFailed), "Inbound ignored data", pUPnPDevice->GetOwningDevice()); } #endif // DBG // // Forget all data received. // pUPnPDevice->ClearReceiveBuffer(); goto Exit; } // CNATHelpUPnP::ReceiveUPnPDataStream #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ParseUPnPHeaders" //============================================================================= // CNATHelpUPnP::ParseUPnPHeaders //----------------------------------------------------------------------------- // // Description: Parses UPnP header information out of a message buffer. // // Arguments: // char * pszMsg - Pointer to string containing the UPnP // message. It will be modified. // UPNP_HEADER_INFO * pHeaderInfo - Structure used to return parsing results. // // Returns: None. //============================================================================= void CNATHelpUPnP::ParseUPnPHeaders(char * const pszMsg, UPNP_HEADER_INFO * pHeaderInfo) { char * pszCurrent; char * pszLineStart; char * pszHeaderDelimiter; int i; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p)", this, pszMsg, pHeaderInfo); // // Loop until we reach the last SSDP header (indicated by a blank line). // pszCurrent = pszMsg; pszLineStart = pszMsg; do { // // Find the end of the current line (CR LF). // while ((*pszCurrent) != '\n') { if ((*pszCurrent) == '\0') { // // We hit the end of the buffer. Bail. // DPFX(DPFPREP, 1, "Hit end of buffer, parsing terminated."); return; } pszCurrent++; } // // Assuming this is the last header line, update the message body // pointer to be after this line. // pHeaderInfo->pszMsgBody = pszCurrent + 1; // // If it's a valid line, then a CR will precede the LF we just found. // If so, truncate the string there. If not, we'll just continue. The // wacky newline in the middle of nowhere will probably just be // ignored. // if ((pszCurrent > (pszMsg + 1)) && (*(pszCurrent - 1)) == '\r') { // // Truncate the string at the effective end of the line (i.e. // replace the CR with NULL terminator). // *(pszCurrent - 1) = '\0'; // // If this is the empty line denoting the end of the headers, we're // done here. // if (strlen(pszLineStart) == 0) { // // Stop looping. // break; } } else { // // Truncate the string here. // (*pszCurrent) = '\0'; DPFX(DPFPREP, 9, "Line has a newline in it (offset 0x%p) that isn't preceded by a carriage return.", (pszCurrent - pszMsg)); } // // Whitespace means continuation of previous line, so if this line // starts that way, erase the termination for the previous line (unless // this is the first line). // if (((*pszLineStart) == ' ') || ((*pszLineStart) == '\t')) { if (pszLineStart >= (pszMsg + 2)) { // // The previous line should have ended with {CR, LF}, which // gets modified to {NULL termination, LF}. // if ((*(pszLineStart - 2) != '\0') || (*(pszLineStart - 1) != '\n')) { DPFX(DPFPREP, 7, "Ignoring line \"%hs\" because previous character sequence was not {NULL terminator, LF}.", pszLineStart); } else { DPFX(DPFPREP, 7, "Appending line \"%hs\" to previous line.", pszLineStart); // // Replace the NULL terminator/LF pair with spaces so // future parsing sees the previous line and this one as // one string. // *(pszLineStart - 2) = ' '; *(pszLineStart - 1) = ' '; } } else { DPFX(DPFPREP, 7, "Ignoring initial line \"%hs\" that starts with whitespace.", pszLineStart); } } // // Find the colon separating the header. // pszHeaderDelimiter = strchr(pszLineStart, ':'); if (pszHeaderDelimiter != NULL) { // // Truncate the string at the end of the header. // (*pszHeaderDelimiter) = '\0'; // // Remove the white space surrounding the header name. // strtrim(&pszLineStart); // // Parse the header type. // for(i = 0; i < NUM_RESPONSE_HEADERS; i++) { if (_stricmp(c_szResponseHeaders[i], pszLineStart) == 0) { // // Found the header. Save it if it's not a duplicate. // if (pHeaderInfo->apszHeaderStrings[i] == NULL) { char * pszTrimmedValue; // // Skip leading and trailing whitespace in the value. // pszTrimmedValue = pszHeaderDelimiter + 1; strtrim(&pszTrimmedValue); pHeaderInfo->apszHeaderStrings[i] = pszTrimmedValue; DPFX(DPFPREP, 7, "Recognized header %i:\"%hs\", data = \"%hs\".", i, pszLineStart, pHeaderInfo->apszHeaderStrings[i]); } else { DPFX(DPFPREP, 7, "Ignoring duplicate header %i:\"%hs\", data = \"%hs\".", i, pszLineStart, (pszHeaderDelimiter + 1)); } break; } } #ifdef DBG // // Print unrecognized headers. // if (i >= NUM_RESPONSE_HEADERS) { DPFX(DPFPREP, 7, "Ignoring unrecognized header \"%hs\", data = \"%hs\".", pszLineStart, (pszHeaderDelimiter + 1)); } #endif // DBG } else { DPFX(DPFPREP, 7, "Ignoring line \"%hs\", no header delimiter.", pszLineStart); } // // Go to the next UPnP header (if any). // pszCurrent++; pszLineStart = pszCurrent; } while (TRUE); // // At this point pHeaderInfo->apszHeaderStrings should contain pointers to // the data for all the headers that were found, and // pHeaderInfo->pszMsgBody should point to the end of the headers. // } // CNATHelpUPnP::ParseUPnPHeaders #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetAddressFromURL" //============================================================================= // CNATHelpUPnP::GetAddressFromURL //----------------------------------------------------------------------------- // // Description: Parses a UPnP URL into a SOCKADDR_IN structure. Only "http://" // URLs are parsed. The string passed in may be temporarily // modified. // // Arguments: // char * pszLocation - Pointer to buffer containing the Location // header. It will be modified. // SOCKADDR_IN * psaddrinLocation - Place to store address contained in // header string. // char ** ppszRelativePath - Place to store pointer to rest of path // (stuff after hostname and optional // port). // // Returns: HRESULT // DPNH_OK - String was parsed successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::GetAddressFromURL(char * const pszLocation, SOCKADDR_IN * psaddrinLocation, char ** ppszRelativePath) { HRESULT hr; BOOL fModifiedDelimiterChar = FALSE; char * pszStart; char * pszDelimiter; char cTempChar; PHOSTENT phostent; // // Initialize the address. Default to the standard HTTP port. // ZeroMemory(psaddrinLocation, sizeof(SOCKADDR_IN)); psaddrinLocation->sin_family = AF_INET; psaddrinLocation->sin_port = HTONS(HTTP_PORT); // // Clear the relative path pointer. // (*ppszRelativePath) = NULL; // // Skip past "http://". If it's not "http://", then fail. // if (_strnicmp(pszLocation, "http://", strlen("http://")) != 0) { DPFX(DPFPREP, 1, "Location URL (\"%hs\") does not start with \"http://\".", pszLocation); hr = DPNHERR_GENERIC; goto Exit; } pszStart = pszLocation + strlen("http://"); // // See if there's a port specified or any extraneous junk after an IP // address or hostname to figure out the string to use. Search from the // start of the string until we hit the end of the string or a reserved URL // character. // pszDelimiter = pszStart + 1; while (((*pszDelimiter) != '\0') && ((*pszDelimiter) != '/') && ((*pszDelimiter) != '?') && ((*pszDelimiter) != '=') && ((*pszDelimiter) != '#')) { if ((*pszDelimiter) == ':') { char * pszPortEnd; // // We found the start of a port, search for the end. It must // contain only numeric characters. // pszPortEnd = pszDelimiter + 1; while (((*pszPortEnd) >= '0') && ((*pszPortEnd) <= '9')) { pszPortEnd++; } // // Temporarily truncate the string. // cTempChar = (*pszPortEnd); (*pszPortEnd) = '\0'; DPFX(DPFPREP, 7, "Found port \"%hs\".", (pszDelimiter + 1)); psaddrinLocation->sin_port = HTONS((u_short) atoi(pszDelimiter + 1)); // // Restore the character. // (*pszPortEnd) = cTempChar; // // Save the relative path // (*ppszRelativePath) = pszPortEnd; break; } pszDelimiter++; } // // Remember the character that stopped the search, and then temporarily // truncate the string. // cTempChar = (*pszDelimiter); (*pszDelimiter) = '\0'; fModifiedDelimiterChar = TRUE; // // Save the relative path if we haven't already (because of a port). // if ((*ppszRelativePath) == NULL) { (*ppszRelativePath) = pszDelimiter; } DPFX(DPFPREP, 7, "Relative path = \"%hs\".", (*ppszRelativePath)); // // Convert the hostname. // psaddrinLocation->sin_addr.S_un.S_addr = this->m_pfninet_addr(pszStart); // // If it's bogus, give up. // if (psaddrinLocation->sin_addr.S_un.S_addr == INADDR_ANY) { DPFX(DPFPREP, 0, "Host name \"%hs\" is invalid!", pszStart); hr = DPNHERR_GENERIC; goto Exit; } if (psaddrinLocation->sin_addr.S_un.S_addr == INADDR_NONE) { // // It's not a straight IP address. Lookup the hostname. // phostent = this->m_pfngethostbyname(pszStart); if (phostent == NULL) { DPFX(DPFPREP, 0, "Couldn't lookup host name \"%hs\"!", pszStart); hr = DPNHERR_GENERIC; goto Exit; } if (phostent->h_addr_list[0] == NULL) { DPFX(DPFPREP, 0, "Host name \"%hs\" has no address entries!", pszStart); hr = DPNHERR_GENERIC; goto Exit; } // // Pick the first address returned. // #ifdef DBG { IN_ADDR ** ppinaddr; DWORD dwNumAddrs; ppinaddr = (IN_ADDR**) phostent->h_addr_list; dwNumAddrs = 0; while ((*ppinaddr) != NULL) { ppinaddr++; dwNumAddrs++; } DPFX(DPFPREP, 7, "Picking first (of %u IP addresses) for \"%hs\".", dwNumAddrs, pszStart); } #endif // DBG psaddrinLocation->sin_addr.S_un.S_addr = ((IN_ADDR*) phostent->h_addr_list[0])->S_un.S_addr; } else { DPFX(DPFPREP, 7, "Successfully converted IP address \"%hs\".", pszStart); } hr = DPNH_OK; Exit: // // If we found a port, restore the string. If not, use the default port. // if (fModifiedDelimiterChar) { // // Note that PREfast reported this as being used before being // initialized for a while. For some reason it didn't notice that I // key off of fModifiedDelimiterChar. This appeared to get fixed, but // PREfast is still giving me a false hit for a similar reason // elsewhere. // (*pszDelimiter) = cTempChar; } DPFX(DPFPREP, 8, "Returning %u.%u.%u.%u:%u, hr = 0x%lx.", psaddrinLocation->sin_addr.S_un.S_un_b.s_b1, psaddrinLocation->sin_addr.S_un.S_un_b.s_b2, psaddrinLocation->sin_addr.S_un.S_un_b.s_b3, psaddrinLocation->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinLocation->sin_port), hr); return hr; } // CNATHelpUPnP::GetAddressFromURL #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::HandleUPnPDescriptionResponseBody" //============================================================================= // CNATHelpUPnP::HandleUPnPDescriptionResponseBody //----------------------------------------------------------------------------- // // Description: Handles a UPnP device description response. The string will // be modified. // // The UPnP device may get removed from list if a failure // occurs, the caller needs to have a reference. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device being described. // DWORD dwHTTPResponseCode - HTTP header response code. // char * pszDescriptionXML - UPnP device description XML string. // // Returns: HRESULT // DPNH_OK - Description response was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::HandleUPnPDescriptionResponseBody(CUPnPDevice * const pUPnPDevice, const DWORD dwHTTPResponseCode, char * const pszDescriptionXML) { HRESULT hr; PARSEXML_SUBELEMENT aSubElements[MAX_NUM_DESCRIPTION_XML_SUBELEMENTS]; PARSEXML_ELEMENT ParseElement; CDevice * pDevice; CBilink * pBilink; CRegisteredPort * pRegisteredPort; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %u, 0x%p)", this, pUPnPDevice, dwHTTPResponseCode, pszDescriptionXML); // // Make sure it was the success result. // if (dwHTTPResponseCode != 200) { DPFX(DPFPREP, 0, "Got error response %u from UPnP description request!", dwHTTPResponseCode); hr = DPNHERR_GENERIC; goto Failure; } ZeroMemory(aSubElements, sizeof(aSubElements)); ZeroMemory(&ParseElement, sizeof(ParseElement)); ParseElement.papszElementStack = (char**) (&c_szElementStack_service); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_service) / sizeof(char*); ParseElement.paSubElements = (PARSEXML_SUBELEMENT*) (aSubElements); ParseElement.dwMaxNumSubElements = MAX_NUM_DESCRIPTION_XML_SUBELEMENTS; //ParseElement.dwNumSubElements = 0; //ParseElement.fFoundMatchingElement = FALSE; hr = this->ParseXML(pszDescriptionXML, &ParseElement, PARSECALLBACK_DESCRIPTIONRESPONSE, pUPnPDevice); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't parse XML!"); goto Failure; } // // If we did not find a WANIPConnection or WANPPPConnection service, then // this response was not valid. // if (pUPnPDevice->GetServiceControlURL() == NULL) { DPFX(DPFPREP, 0, "Couldn't find WANIPConnection or WANPPPConnection service in XML description!"); hr = DPNHERR_GENERIC; goto Failure; } // // The UPnP device is now controllable. // pUPnPDevice->NoteReady(); // // Find out what the device's external IP address is. Note that calling // UpdateUPnPExternalAddress will overwrite the buffer containing the // pszDescriptionXML string. That's fine, because we've saved all the // stuff in there that we need already. // hr = this->UpdateUPnPExternalAddress(pUPnPDevice, FALSE); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't update new UPnP device 0x%p's external address!", pUPnPDevice); goto Failure; } // // Map existing registered ports with this new UPnP device. // pDevice = pUPnPDevice->GetOwningDevice(); DNASSERT(pDevice != NULL); pBilink = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilink != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); // // Note that calling MapPortsOnUPnPDevice will overwrite the buffer // containing the pszDescriptionXML string. That's fine, because // we've saved all the stuff in there that we need already. // hr = this->MapPortsOnUPnPDevice(pUPnPDevice, pRegisteredPort); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't map existing ports on new UPnP device 0x%p!", pUPnPDevice); goto Failure; } // // Let the user know the addresses changed next time GetCaps is // called. // DPFX(DPFPREP, 8, "Noting that addresses changed (for registered port 0x%p).", pRegisteredPort); this->m_dwFlags |= NATHELPUPNPOBJ_ADDRESSESCHANGED; pBilink = pBilink->GetNext(); } // // Try to remove any mappings that were not freed earlier because we // crashed. // hr = this->CleanupInactiveNATMappings(pUPnPDevice); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Failed cleaning up inactive mappings with new UPnP device 0x%p!", pUPnPDevice); goto Failure; } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::HandleUPnPDescriptionResponseBody #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::HandleUPnPControlResponseBody" //============================================================================= // CNATHelpUPnP::HandleUPnPControlResponseBody //----------------------------------------------------------------------------- // // Description: Handles a UPnP control response. The string will be // modified. // // The object lock is assumed to be held. // // Arguments: // CUPnPDevice * pUPnPDevice - Pointer to UPnP device being described. // DWORD dwHTTPResponseCode - HTTP header response code. // char * pszControlResponseSOAP - UPnP device response SOAP XML string. // // Returns: HRESULT // DPNH_OK - Description response was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::HandleUPnPControlResponseBody(CUPnPDevice * const pUPnPDevice, const DWORD dwHTTPResponseCode, char * const pszControlResponseSOAP) { HRESULT hr = DPNH_OK; CONTROLRESPONSEPARSECONTEXT crpc; PARSEXML_SUBELEMENT aSubElements[MAX_NUM_UPNPCONTROLOUTARGS]; PARSEXML_ELEMENT ParseElement; DNASSERT(pUPnPDevice->IsWaitingForControlResponse()); DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %u, 0x%p)", this, pUPnPDevice, dwHTTPResponseCode, pszControlResponseSOAP); ZeroMemory(&crpc, sizeof(crpc)); crpc.ControlResponseType = pUPnPDevice->GetControlResponseType(); crpc.pUPnPDevice = pUPnPDevice; crpc.dwHTTPResponseCode = dwHTTPResponseCode; crpc.pControlResponseInfo = pUPnPDevice->GetControlResponseInfo(); ZeroMemory(aSubElements, sizeof(aSubElements)); ZeroMemory(&ParseElement, sizeof(ParseElement)); if (dwHTTPResponseCode == 200) { switch (crpc.ControlResponseType) { /* case CONTROLRESPONSETYPE_QUERYSTATEVARIABLE_EXTERNALIPADDRESS: { ParseElement.papszElementStack = (char**) (&c_szElementStack_QueryStateVariableResponse); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_QueryStateVariableResponse) / sizeof(char*); break; } */ case CONTROLRESPONSETYPE_GETEXTERNALIPADDRESS: { ParseElement.papszElementStack = (char**) (&c_szElementStack_GetExternalIPAddressResponse); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_GetExternalIPAddressResponse) / sizeof(char*); break; } case CONTROLRESPONSETYPE_ADDPORTMAPPING: { ParseElement.papszElementStack = (char**) (&c_szElementStack_AddPortMappingResponse); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_AddPortMappingResponse) / sizeof(char*); break; } case CONTROLRESPONSETYPE_GETSPECIFICPORTMAPPINGENTRY: { ParseElement.papszElementStack = (char**) (&c_szElementStack_GetSpecificPortMappingEntryResponse); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_GetSpecificPortMappingEntryResponse) / sizeof(char*); break; } case CONTROLRESPONSETYPE_DELETEPORTMAPPING: { ParseElement.papszElementStack = (char**) (&c_szElementStack_DeletePortMappingResponse); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_DeletePortMappingResponse) / sizeof(char*); break; } default: { DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; break; } } } else { ParseElement.papszElementStack = (char**) (&c_szElementStack_ControlResponseFailure); ParseElement.dwElementStackDepth = sizeof(c_szElementStack_ControlResponseFailure) / sizeof(char*); } ParseElement.paSubElements = (PARSEXML_SUBELEMENT*) (aSubElements); ParseElement.dwMaxNumSubElements = MAX_NUM_UPNPCONTROLOUTARGS; //ParseElement.dwNumSubElements = 0; //ParseElement.fFoundMatchingElement = FALSE; hr = this->ParseXML(pszControlResponseSOAP, &ParseElement, PARSECALLBACK_CONTROLRESPONSE, &crpc); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't parse XML!"); goto Failure; } // // If we didn't a matching item, map it to a generic failure. // if (! ParseElement.fFoundMatchingElement) { if (dwHTTPResponseCode == 200) { DPFX(DPFPREP, 1, "Didn't find XML items in success response, mapping to generic failure."); } else { DPFX(DPFPREP, 1, "Didn't find failure XML items, using generic failure."); } crpc.pControlResponseInfo->hrErrorCode = DPNHERR_GENERIC; } pUPnPDevice->StopWaitingForControlResponse(); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::HandleUPnPControlResponseBody #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ParseXML" //============================================================================= // CNATHelpUPnP::ParseXML //----------------------------------------------------------------------------- // // Description: Parses an XML string for a specific element, and calls a // helper function for each instance found. // // Subelement values cannot themselves contain subelements. If // they do, the sub-subelements will be ignored. // // The string buffer is modified. // // Arguments: // char * pszXML - XML string to parse. // PARSEXML_ELEMENT * pParseElement - Pointer to element whose sub element // values should be retrieved. // PARSECALLBACK ParseCallback - Enum indicating what helper function // to use. // PVOID pvContext - Pointer to context value to pass to // helper function. // // Returns: HRESULT // DPNH_OK - Description response was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::ParseXML(char * const pszXML, PARSEXML_ELEMENT * const pParseElement, const PARSECALLBACK ParseCallback, PVOID pvContext) { HRESULT hr = DPNH_OK; PARSEXML_STACKENTRY aElementStack[MAX_XMLELEMENT_DEPTH]; DWORD dwCurrentElementDepth = 0; char * pszElementTagStart = NULL; BOOL fInElement = FALSE; BOOL fEmptyElement = FALSE; char * pszCurrent; DWORD dwStackDepth; PARSEXML_SUBELEMENT * pSubElement; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p)", this, pszXML, pParseElement); // // Need room for entire stack + at least one subelement level. // DNASSERT(pParseElement->dwElementStackDepth < MAX_XMLELEMENT_DEPTH); #ifdef DBG this->PrintUPnPTransactionToFile(pszXML, strlen(pszXML), "Inbound XML Body", NULL); #endif // DBG // // Loop through the XML looking for the given elements. // pszCurrent = pszXML; while ((*pszCurrent) != '\0') { switch (*pszCurrent) { case '<': { // // If we're in an element tag already, this is bogus XML or a // CDATA section (which we don't handle). Fail. // if (pszElementTagStart != NULL) { DPFX(DPFPREP, 0, "Encountered '<' character in element tag, XML parsing failed."); goto Failure; } // // Truncate the string here in case this is the start of an // element end tag. This delimits a value string. // (*pszCurrent) = '\0'; pszElementTagStart = pszCurrent + 1; if ((*pszElementTagStart) == '\0') { DPFX(DPFPREP, 0, "Encountered '<' character at end of string, XML parsing failed."); goto Failure; } break; } case '>': { // // If we're not in an element tag, this is bogus XML or a CDATA // section (which we don't handle). Fail. // if (pszElementTagStart == NULL) { DPFX(DPFPREP, 0, "Encountered '>' character outside of element tag, XML parsing failed."); goto Failure; } // // Truncate the string here. // (*pszCurrent) = '\0'; // // This could either be a start or an end tag. If the first // character of the tag is '/', then it's an end tag. // // Note that empty element tags begin by being parsed like a // start tag, but then jump into the end tag clause. // if ((*pszElementTagStart) == '/') { pszElementTagStart++; // // Make sure the element tag stack is valid. The name of // this end tag should match the start tag at the top of // the stack. XML elements are case sensitive. // if (dwCurrentElementDepth == 0) { DPFX(DPFPREP, 0, "Encountered extra element end tag \"%hs\", XML parsing failed.", pszElementTagStart); goto Failure; } if (strcmp(pszElementTagStart, aElementStack[dwCurrentElementDepth - 1].pszName) != 0) { DPFX(DPFPREP, 0, "Encountered non-matching element end tag (\"%hs\" != \"%hs\"), XML parsing failed.", pszElementTagStart, aElementStack[dwCurrentElementDepth - 1].pszName); goto Failure; } TagEnd: // // If we're here, then we have a complete element. If we // were in the element, then it's either: // the end of a sub-sub element, // the end of a sub element, or // the end of the element itself. // if (fInElement) { switch (dwCurrentElementDepth - pParseElement->dwElementStackDepth) { case 0: { // // It's the end of the element. Call the // helper function. Reuse fInElement as the // fContinueParsing BOOL. // switch (ParseCallback) { case PARSECALLBACK_DESCRIPTIONRESPONSE: { hr = this->ParseXMLCallback_DescriptionResponse(pParseElement, pvContext, aElementStack, &fInElement); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Description response parse helper function failed!"); goto Failure; } break; } case PARSECALLBACK_CONTROLRESPONSE: { hr = this->ParseXMLCallback_ControlResponse(pParseElement, pvContext, aElementStack, &fInElement); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Control response parse helper function failed!"); goto Failure; } break; } default: { DNASSERT(FALSE); break; } } if (! fInElement) { DPFX(DPFPREP, 1, "Parse callback function discontinued parsing."); goto Exit; } // // Keep parsing, but we're no longer in the // element. Reset the sub element counter in // case we found entries. // fInElement = FALSE; pParseElement->dwNumSubElements = 0; break; } case 1: { // // It's the end of a subelement. Complete this // instance, if there's room. // if (pParseElement->dwNumSubElements < pParseElement->dwMaxNumSubElements) { pSubElement = &pParseElement->paSubElements[pParseElement->dwNumSubElements]; pSubElement->pszNameFound = pszElementTagStart; pSubElement->dwNumAttributes = aElementStack[dwCurrentElementDepth - 1].dwNumAttributes; if (pSubElement->dwNumAttributes > 0) { memcpy(pSubElement->apszAttributeNames, aElementStack[dwCurrentElementDepth - 1].apszAttributeNames, (pSubElement->dwNumAttributes * sizeof(char*))); memcpy(pSubElement->apszAttributeValues, aElementStack[dwCurrentElementDepth - 1].apszAttributeValues, (pSubElement->dwNumAttributes * sizeof(char*))); } pSubElement->pszValueFound = aElementStack[dwCurrentElementDepth - 1].pszValue; pParseElement->dwNumSubElements++; DPFX(DPFPREP, 7, "Completed subelement instance #%u, name = \"%hs\", %u attributes, value = \"%hs\".", pParseElement->dwNumSubElements, pSubElement->pszNameFound, pSubElement->dwNumAttributes, pSubElement->pszValueFound); } else { DPFX(DPFPREP, 0, "Ignoring subelement instance \"%hs\" (%u attributes, value = \"%hs\"), no room in array.", pszElementTagStart, aElementStack[dwCurrentElementDepth - 1].dwNumAttributes, aElementStack[dwCurrentElementDepth - 1].pszValue); } break; } default: { // // It's the end of a sub-subelement. // DPFX(DPFPREP, 1, "Ignoring sub-sub element \"%hs\" (%u attributes, value = \"%hs\").", pszElementTagStart, aElementStack[dwCurrentElementDepth - 1].dwNumAttributes, aElementStack[dwCurrentElementDepth - 1].pszValue); break; } } } // // Pop the element off the stack. // dwCurrentElementDepth--; } else { // // It's not an end tag, but it might be an empty element // (i.e. ""). // if (*(pszCurrent - 1) == '/') { // // Truncate the string early. // *(pszCurrent - 1) = '\0'; // // Remember this state so we can parse it properly. // fEmptyElement = TRUE; DPFX(DPFPREP, 7, "XML element \"%hs\" is empty (i.e. is both a start and end tag).", pszElementTagStart); } // // Push the element on the tag stack, if there's room. // if (dwCurrentElementDepth >= MAX_XMLELEMENT_DEPTH) { DPFX(DPFPREP, 0, "Too many nested element tags (%u), XML parsing failed.", dwCurrentElementDepth); goto Failure; } aElementStack[dwCurrentElementDepth].pszName = pszElementTagStart; // // If there are attributes to this element, separate them // into a different array. They will not be parsed, // though. // Attributes are delimited by whitespace. // while ((*pszElementTagStart) != '\0') { pszElementTagStart++; // // If it's whitespace, that's the end of the element // name. Truncate the string and break out of the // loops. // if (((*pszElementTagStart) == ' ') || ((*pszElementTagStart) == '\t') || ((*pszElementTagStart) == '\r') || ((*pszElementTagStart) == '\n')) { (*pszElementTagStart) = '\0'; pszElementTagStart++; DPFX(DPFPREP, 8, "Attribute whitespace found at offset 0x%p, string length = %i.", (pszElementTagStart - aElementStack[dwCurrentElementDepth].pszName), strlen(pszElementTagStart)); break; } } // // If there weren't any attributes, pszElementTagStart will // just point to an empty (but not NULL) string. // // So save the start of the value string. // aElementStack[dwCurrentElementDepth].pszValue = pszElementTagStart + strlen(pszElementTagStart) + 1; // // Then parse out the attributes. // this->ParseXMLAttributes(pszElementTagStart, aElementStack[dwCurrentElementDepth].apszAttributeNames, aElementStack[dwCurrentElementDepth].apszAttributeValues, MAX_XMLNAMESPACES_PER_ELEMENT, &(aElementStack[dwCurrentElementDepth].dwNumAttributes)); // // The tag is considered optional by this parser, // and will be ignored. // if (_stricmp(aElementStack[dwCurrentElementDepth].pszName, "?xml") != 0) { // // Bump the stack pointer. // dwCurrentElementDepth++; // // See if this the right element. If the stack depth // isn't right, it can't be the desired item. // Otherwise, make sure the stack matches. // if (dwCurrentElementDepth == pParseElement->dwElementStackDepth) { // // Work through the entire element stack, making // sure each name matches. // for(dwStackDepth = 0; dwStackDepth < dwCurrentElementDepth; dwStackDepth++) { if (! this->MatchesXMLStringWithoutNamespace(aElementStack[dwStackDepth].pszName, pParseElement->papszElementStack[dwStackDepth], aElementStack, NULL, (dwStackDepth + 1))) { // // It didn't match. Stop looping. // break; } } // // If they all matched, we found the value desired. // if (dwStackDepth == dwCurrentElementDepth) { fInElement = TRUE; DPFX(DPFPREP, 7, "Found requested element \"%hs\" at depth %u, has %u attributes.", aElementStack[dwCurrentElementDepth - 1].pszName, dwCurrentElementDepth, aElementStack[dwCurrentElementDepth - 1].dwNumAttributes); } } } else { DPFX(DPFPREP, 7, "Ignoring element \"%hs\" at depth %u that has %u attributes.", aElementStack[dwCurrentElementDepth].pszName, dwCurrentElementDepth, aElementStack[dwCurrentElementDepth].dwNumAttributes); // // If this assertion fails and it is an empty element, // dwCurrentElementDepth will be off by -1 when we jump // to TagEnd. // DNASSERT(! fInElement); } // // If this is an empty element, go immediately to handling // the tag closure. // if (fEmptyElement) { fEmptyElement = FALSE; goto TagEnd; } } // // Search for another element tag. // pszElementTagStart = NULL; break; } default: { // // Ordinary character, continue. // break; } } // // Move to the next character // pszCurrent++; } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: hr = DPNHERR_GENERIC; goto Exit; } // CNATHelpUPnP::ParseXML #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ParseXMLAttributes" //============================================================================= // CNATHelpUPnP::ParseXMLAttributes //----------------------------------------------------------------------------- // // Description: Parses any XML attributes out of the given string. The input // string buffer will be modified. // // Arguments: // char * pszString - Pointer to attributes string to parse. // This will be modified. // char ** apszAttributeNames - Array in which to store attribute name // string pointers. // char ** apszAttributeValues - Matching array in which to store cor- // responding attribute value strings. // DWORD dwMaxNumAttributes - Maximum number of entries allowed in // previous arrays. // DWORD * pdwNumAttributes - Place to store number of attributes that // were found. // // Returns: HRESULT // DPNH_OK - Description response was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= void CNATHelpUPnP::ParseXMLAttributes(char * const pszString, char ** const apszAttributeNames, char ** const apszAttributeValues, const DWORD dwMaxNumAttributes, DWORD * const pdwNumAttributes) { char * pszStart; char * pszCurrent; char * pszEndOfString; BOOL fInValueString = FALSE; BOOL fInQuotes = FALSE; BOOL fEmptyString = FALSE; #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 8, "(0x%p) Parameters: (\"%hs\", 0x%p, 0x%p, %u, 0x%p)", this, pszString, apszAttributeNames, apszAttributeValues, dwMaxNumAttributes, pdwNumAttributes); #endif // EXTRA_PARSING_SPEW // // Start at the beginning with no entries. // (*pdwNumAttributes) = 0; pszStart = pszString; pszCurrent = pszStart; pszEndOfString = pszString + strlen(pszString); // // Skip empty strings. // if (pszEndOfString == pszStart) { return; } // // Loop through the entire string. // while (pszCurrent <= pszEndOfString) { switch (*pszCurrent) { case '=': { // // If we're not in quotes or a value string, this is the end of // the attribute name and the start of a value string. // if ((! fInQuotes) && (! fInValueString)) { (*pszCurrent) = '\0'; apszAttributeNames[(*pdwNumAttributes)] = pszStart; pszStart = pszCurrent + 1; fInValueString = TRUE; } break; } case '\0': case ' ': case '\t': case '\r': case '\n': { // // Whitespace or the end of the string. If we're not in // quotes, that means it's the end of an attribute. Of course // if it's the end of the string, then we force the end of the // attribute/value. // if ((! fInQuotes) || ((*pszCurrent) == '\0')) { (*pszCurrent) = '\0'; if (fInValueString) { // // End of the value string. // apszAttributeValues[(*pdwNumAttributes)] = pszStart; fInValueString = FALSE; DPFX(DPFPREP, 7, "Found attribute \"%hs\" with value \"%hs\".", apszAttributeNames[(*pdwNumAttributes)], apszAttributeValues[(*pdwNumAttributes)]); } else { // // This may be another whitespace character immediately // following a previous one. If so, ignore it. If // not, save the attribute. // if (pszCurrent == pszStart) { fEmptyString = TRUE; #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 9, "Ignoring extra whitespace at offset 0x%p.", (pszCurrent - pszString)); #endif // EXTRA_PARSING_SPEW } else { // // End of the attribute. Force an empty value string. // apszAttributeNames[(*pdwNumAttributes)] = pszStart; apszAttributeValues[(*pdwNumAttributes)] = pszCurrent; DPFX(DPFPREP, 7, "Found attribute \"%hs\" with no value string.", apszAttributeNames[(*pdwNumAttributes)]); } } // // Update the pointer for the start of the next attribute. // pszStart = pszCurrent + 1; // // Move to next attribute storage location, if this is not // an empty string. If that was the last storage slot, // we're done here. // if (fEmptyString) { fEmptyString = FALSE; } else { (*pdwNumAttributes)++; if ((*pdwNumAttributes) >= dwMaxNumAttributes) { DPFX(DPFPREP, 1, "Maximum number of attributes reached, discontinuing attribute parsing."); pszCurrent = pszEndOfString; } } } break; } case '"': { // // Make sure it's not an escaped quote character. // if ((pszCurrent == pszString) || (*(pszCurrent - 1) != '\\')) { // // Toggle the quote state. // if (fInQuotes) { // // Force the string to terminate here so we skip the // trailing quote character. // fInQuotes = FALSE; (*pszCurrent) = '\0'; } else { fInQuotes = TRUE; // // This should be the start of a (value) string. Skip // this quote character. // if (pszCurrent == pszStart) { pszStart++; } else { DPFX(DPFPREP, 1, "Found starting quote that wasn't at the beginning of the string! Continuing."); } } } else { // // It's an escaped quote character. // } break; } default: { // // Ignore the character and move on. // break; } } // // Move to next character. // pszCurrent++; } #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 8, "(0x%p) Leave (found %u items)", this, (*pdwNumAttributes)); #endif // EXTRA_PARSING_SPEW } // CNATHelpUPnP::ParseXMLNamespaceAttributes #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::MatchesXMLStringWithoutNamespace" //============================================================================= // CNATHelpUPnP::MatchesXMLStringWithoutNamespace //----------------------------------------------------------------------------- // // Description: Determines whether the szCompareString matches szMatchString // when all namespace prefixes in szCompareString are ignored. // TRUE is returned if they do match, FALSE if not. // // Arguments: // char * szCompareString - String that may contain namespace // prefixes to be ignored. // char * szMatchString - Shortened string without any // namespace prefixes to be matched. // PARSEXML_STACKENTRY * aElementStack - Array of nested elements whose // attributes may define XML namespace // aliases. // PARSEXML_SUBELEMENT * pSubElement - Optional subelement entry with // additional attributes to check. // DWORD dwElementStackDepth - Number of entries in previous array. // // Returns: BOOL //============================================================================= BOOL CNATHelpUPnP::MatchesXMLStringWithoutNamespace(const char * const szCompareString, const char * const szMatchString, const PARSEXML_STACKENTRY * const aElementStack, const PARSEXML_SUBELEMENT * const pSubElement, const DWORD dwElementStackDepth) { BOOL fResult; char * pszCompareStringNoNamespace; // // First, do a straight-forward string comparison. // if (_stricmp(szCompareString, szMatchString) == 0) { DPFX(DPFPREP, 7, "\"%hs\" exactly matches the short string.", szCompareString); fResult = TRUE; } else { // // Skip past any namespace prefixes. // pszCompareStringNoNamespace = this->GetStringWithoutNamespacePrefix(szCompareString, aElementStack, pSubElement, dwElementStackDepth); DNASSERT((pszCompareStringNoNamespace >= szCompareString) && (pszCompareStringNoNamespace <= (szCompareString + strlen(szCompareString)))); // // Now try comparing again, if we found any prefixes. // if (pszCompareStringNoNamespace > szCompareString) { if (_stricmp(pszCompareStringNoNamespace, szMatchString) == 0) { DPFX(DPFPREP, 7, "\"%hs\" matches the short string \"%hs\" starting at offset 0x%p.", szCompareString, szMatchString, (pszCompareStringNoNamespace - szCompareString)); fResult = TRUE; } else { DPFX(DPFPREP, 8, "\"%hs\" does not match the short string \"%hs\" (starting at offset 0x%p).", szCompareString, szMatchString, (pszCompareStringNoNamespace - szCompareString)); fResult = FALSE; } } else { DPFX(DPFPREP, 8, "\"%hs\" does not have any namespace prefixes and does not match \"%hs\".", szCompareString, szMatchString); fResult = FALSE; } } return fResult; } // CNATHelpUPnP::MatchesXMLStringWithoutNamespace #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetStringWithoutNamespacePrefix" //============================================================================= // CNATHelpUPnP::GetStringWithoutNamespacePrefix //----------------------------------------------------------------------------- // // Description: Returns a pointer to the first part of the string after any // prefixes found. This will be the start of the string if none // are found. // // Arguments: // char * szString - String that may contain namespace // prefixes to be skipped. // PARSEXML_STACKENTRY * aElementStack - Array of nested elements whose // attributes may define XML namespace // aliases. // PARSEXML_SUBELEMENT * pSubElement - Optional subelement entry with // additional attributes to check. // DWORD dwElementStackDepth - Number of entries in previous array. // // Returns: char * //============================================================================= char * CNATHelpUPnP::GetStringWithoutNamespacePrefix(const char * const szString, const PARSEXML_STACKENTRY * const aElementStack, const PARSEXML_SUBELEMENT * const pSubElement, const DWORD dwElementStackDepth) { char * pszResult; UINT uiXMLNSPrefixLength; DWORD dwAttribute; UINT uiNamespaceNameLength; UINT uiNamespaceValueLength; DWORD dwStackDepth; // // Store the prefix value since we use it frequently in this function. // uiXMLNSPrefixLength = strlen(XML_NAMESPACEDEFINITIONPREFIX); // // Search the stack for an XML namespace definition that matches. Start at // the bottom and work up, since later definitions take precendence over // earlier ones. // // In fact, if this is a subelement, there are attributes associated with // this item that need to be checked, too. Do that first. // if (pSubElement != NULL) { // // Search each attribute // for(dwAttribute = 0; dwAttribute < pSubElement->dwNumAttributes; dwAttribute++) { // // Work with this attribute if it's a valid XML namespace // definition. It needs to start with the prefix, plus have one // extra character for the actual name. // uiNamespaceNameLength = strlen(pSubElement->apszAttributeNames[dwAttribute]); if ((uiNamespaceNameLength >= (uiXMLNSPrefixLength + 1)) && (_strnicmp(pSubElement->apszAttributeNames[dwAttribute], XML_NAMESPACEDEFINITIONPREFIX, uiXMLNSPrefixLength) == 0)) { uiNamespaceNameLength -= uiXMLNSPrefixLength; // // Only work with the item's value if it's valid. // uiNamespaceValueLength = strlen(pSubElement->apszAttributeValues[dwAttribute]); if (uiNamespaceValueLength > 0) { // // Okay, here's an item. See if the name prefixes the // passed in string. // if (_strnicmp(szString, (pSubElement->apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength), uiNamespaceNameLength) == 0) { DPFX(DPFPREP, 8, "\"%hs\" begins with prefix \"%hs\" (subelement).", szString, (pSubElement->apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength)); // // Cast to lose the const. // pszResult = ((char*) szString) + uiNamespaceNameLength; // // Skip the colon delimiter. // if ((*pszResult) == ':') { pszResult++; } else { DPFX(DPFPREP, 1, "\"%hs\" begins with prefix \"%hs\" but does not have colon separator (subelement)! Continuing.", szString, (pSubElement->apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength)); } goto Exit; } // // Namespace doesn't match // #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 9, "\"%hs\" does not begin with prefix \"%hs\" (subelement).", szString, (pSubElement->apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength)); #endif // EXTRA_PARSING_SPEW } else { // // Namespace value is bogus, ignore it. // DPFX(DPFPREP, 1, "Ignoring namespace definition \"%hs\" with empty value string (subelement).", pSubElement->apszAttributeNames[dwAttribute]); } } else { // // Not an XML namespace definition. // #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 9, "Attribute \"%hs\" is not a valid namespace definition (subelement).", pSubElement->apszAttributeNames[dwAttribute]); #endif // EXTRA_PARSING_SPEW } } // end for (each attribute) } else { // // No subelement to check. // } // // Do the same thing for the all items above this one. // dwStackDepth = dwElementStackDepth; while (dwStackDepth > 0) { dwStackDepth--; // // Search each attribute // for(dwAttribute = 0; dwAttribute < aElementStack[dwStackDepth].dwNumAttributes; dwAttribute++) { // // Work with this attribute if it's a valid XML namespace // definition. It needs to start with the prefix, plus have one // extra character for the actual name. // uiNamespaceNameLength = strlen(aElementStack[dwStackDepth].apszAttributeNames[dwAttribute]); if ((uiNamespaceNameLength >= (uiXMLNSPrefixLength + 1)) && (_strnicmp(aElementStack[dwStackDepth].apszAttributeNames[dwAttribute], XML_NAMESPACEDEFINITIONPREFIX, uiXMLNSPrefixLength) == 0)) { uiNamespaceNameLength -= uiXMLNSPrefixLength; // // Only work with the item's value if it's valid. // uiNamespaceValueLength = strlen(aElementStack[dwStackDepth].apszAttributeValues[dwAttribute]); if (uiNamespaceValueLength > 0) { // // Okay, here's an item. See if the value prefixes the // passed in string. // if (_strnicmp(szString, (aElementStack[dwStackDepth].apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength), uiNamespaceNameLength) == 0) { DPFX(DPFPREP, 8, "\"%hs\" begins with prefix \"%hs\" (stack depth %u).", szString, (aElementStack[dwStackDepth].apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength), dwStackDepth); // // Cast to lose the const. // pszResult = ((char*) szString) + uiNamespaceNameLength; // // Skip the colon delimiter. // if ((*pszResult) == ':') { pszResult++; } else { DPFX(DPFPREP, 1, "\"%hs\" begins with prefix \"%hs\" but does not have colon separator (stack depth %u)! Continuing.", szString, (aElementStack[dwStackDepth].apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength), dwStackDepth); } goto Exit; } // // Namespace doesn't match // #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 9, "\"%hs\" does not begin with prefix \"%hs\" (stack depth %u).", szString, (aElementStack[dwStackDepth].apszAttributeNames[dwAttribute] + uiXMLNSPrefixLength), dwStackDepth); #endif // EXTRA_PARSING_SPEW } else { // // Namespace value is bogus, ignore it. // DPFX(DPFPREP, 1, "Ignoring namespace definition \"%hs\" with empty value string (stack depth %u).", aElementStack[dwStackDepth].apszAttributeNames[dwAttribute], dwStackDepth); } } else { // // Not an XML namespace definition. // #ifdef EXTRA_PARSING_SPEW DPFX(DPFPREP, 9, "Attribute \"%hs\" is not a valid namespace definition (stack depth %u).", aElementStack[dwStackDepth].apszAttributeNames[dwAttribute], dwStackDepth); #endif // EXTRA_PARSING_SPEW } } // end for (each attribute) } // // If we're here, it didn't match, even with namespace expansion. // DPFX(DPFPREP, 8, "\"%hs\" does not contain any namespace prefixes.", szString); // // Cast to lose the const. // pszResult = (char*) szString; Exit: return pszResult; } // CNATHelpUPnP::GetStringWithoutNamespacePrefix #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetNextChunk" //============================================================================= // CNATHelpUPnP::GetNextChunk //----------------------------------------------------------------------------- // // Description: Attempts to parse the next chunk out of a message buffer. // If invalid data is encountered, this function returns FALSE. // If not enough data has been received to complete the chunk, // TRUE is returned, but NULL is returned in ppszChunkData. // Otherwise, a pointer to the start of the chunk data is placed // in ppszChunkData, the size of the chunk is placed in // pdwChunkSize, ppszBufferRemaining is set to the start of the // next potential chunk, and pdwBufferSizeRemaining is set to the // amount of the buffer remaining starting at the returned // ppszBufferRemaining value. // // Note that the chunk size may be zero, in which case the // pointer in ppszChunkData & ppszBufferRemaining will be non- // NULL, but useless. // // Arguments: // char * pszBuffer - Pointer to string containing the message // received so far. // DWORD dwBufferSize - Size of the message buffer. // char ** ppszChunkData - Place to store pointer to chunk data, or // NULL if full chunk is not available. // DWORD * pdwChunkSize - Place to store size of chunk. // char ** ppszBufferRemaining - Place to store pointer to end of chunk // if full chunk is available. // DWORD * pdwBufferSizeRemaining - Place to store size of buffer remaining // after returned chunk. // // Returns: None. //============================================================================= BOOL CNATHelpUPnP::GetNextChunk(char * const pszBuffer, const DWORD dwBufferSize, char ** const ppszChunkData, DWORD * const pdwChunkSize, char ** const ppszBufferRemaining, DWORD * const pdwBufferSizeRemaining) { BOOL fReturn = TRUE; char * pszCurrent; char * pszEndOfBuffer; BOOL fFoundChunkSizeEnd; BOOL fExtensions; BOOL fInQuotedString; DPFX(DPFPREP, 8, "(0x%p) Parameters: (0x%p, %u, 0x%p, 0x%p, 0x%p, 0x%p)", this, pszBuffer, dwBufferSize, ppszChunkData, pdwChunkSize, ppszBufferRemaining, pdwBufferSizeRemaining); pszCurrent = pszBuffer; pszEndOfBuffer = pszCurrent + dwBufferSize; fFoundChunkSizeEnd = FALSE; fExtensions = FALSE; fInQuotedString = FALSE; (*ppszChunkData) = NULL; (*pdwChunkSize) = 0; // // The buffer must be large enough to hold 1 hex digit, CR LF chunk size // terminator, and CR LF chunk trailer. // if (dwBufferSize < 5) { DPFX(DPFPREP, 3, "Buffer is not large enough (%u bytes) to hold one valid chunk.", dwBufferSize); goto Exit; } // // Be paranoid to make sure we're not going to having wrap problems. // if (pszEndOfBuffer < pszCurrent) { DPFX(DPFPREP, 0, "Buffer pointer 0x%p cannot have size %u!", pszBuffer, dwBufferSize); goto Failure; } while (pszCurrent < pszEndOfBuffer) { // // Make sure we have a valid hex chunk size string and convert it // as we go. // if (((*pszCurrent) >= '0') && ((*pszCurrent) <= '9')) { (*pdwChunkSize) = ((*pdwChunkSize) * 16) + ((*pszCurrent) - '0'); } else if (((*pszCurrent) >= 'a') && ((*pszCurrent) <= 'f')) { (*pdwChunkSize) = ((*pdwChunkSize) * 16) + ((*pszCurrent) - 'a' + 10); } else if (((*pszCurrent) >= 'A') && ((*pszCurrent) <= 'F')) { (*pdwChunkSize) = ((*pdwChunkSize) * 16) + ((*pszCurrent) - 'A' + 10); } else if ((*pszCurrent) == '\r') { // // This should be the end of the chunk size string. // fFoundChunkSizeEnd = TRUE; break; } else if ((*pszCurrent) == ';') { // // This should be the end of the chunk size string, and the // beginning of extensions. Loop until we find the true end. // while ((*pszCurrent) != '\r') { pszCurrent++; if (pszCurrent >= pszEndOfBuffer) { DPFX(DPFPREP, 5, "Buffer stops in middle of chunk extension."); goto Exit; } // // We do not support quoted extension value strings that // theoretically contain CR characters... // } fFoundChunkSizeEnd = TRUE; break; } else { // // There's a bogus character. This can't be a validly encoded // message. // DPFX(DPFPREP, 1, "Chunk size string contains invalid character 0x%x at offset %u!", (*pszCurrent), (DWORD_PTR) (pszCurrent - pszBuffer)); goto Failure; } // // Validate the chunk size we have so far. // if ((*pdwChunkSize) > MAX_RECEIVE_BUFFER_SIZE) { DPFX(DPFPREP, 1, "Chunk size %u is too large!", (*pdwChunkSize)); goto Failure; } pszCurrent++; } // // If we're here, see if we found the end of the chunk size string. // Make sure we've received enough data, and then validate that the CR // stopping character is followed by the LF character. // if (fFoundChunkSizeEnd) { pszCurrent++; if (pszCurrent < pszEndOfBuffer) { if ((*pszCurrent) != '\n') { DPFX(DPFPREP, 1, "Chunk size string did not end with CRLF sequence (offset %u)!", (DWORD_PTR) (pszCurrent - pszBuffer)); goto Failure; } // // Otherwise we got a complete chunk size string. // pszCurrent++; // // If we have received all of the chunk already, make sure we have // the trailing CR LF sequence before returning the pointer to the // caller. if (((*pdwChunkSize) + 2) <= ((DWORD_PTR) (pszEndOfBuffer - pszCurrent))) { if ((*(pszCurrent + (*pdwChunkSize)) != '\r') || (*(pszCurrent + (*pdwChunkSize) + 1) != '\n')) { DPFX(DPFPREP, 1, "Chunk data did not end with CRLF sequence (offset %u)!", (DWORD_PTR) (pszCurrent - pszBuffer + (*pdwChunkSize))); goto Failure; } // // Return the data pointers to the caller. In the case of the // zero size terminating chunk, the ppszChunkData pointer will // actually be useless, but the caller should recognize that. // (*ppszChunkData) = pszCurrent; (*ppszBufferRemaining) = pszCurrent + ((*pdwChunkSize) + 2); (*pdwBufferSizeRemaining) = (DWORD) ((DWORD_PTR) (pszEndOfBuffer - (*ppszBufferRemaining))); } } } // // If we're here, we didn't encounter invalid data. // Exit: DPFX(DPFPREP, 8, "(0x%p) Returning: [%i]", this, fReturn); return fReturn; Failure: fReturn = FALSE; goto Exit; } // CNATHelpUPnP::GetNextChunk #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ParseXMLCallback_DescriptionResponse" //============================================================================= // CNATHelpUPnP::ParseXMLCallback_DescriptionResponse //----------------------------------------------------------------------------- // // Description: Handles a completed parsed element in the description response // XML. // // Arguments: // PARSEXML_ELEMENT * pParseElement - Pointer to element which was found. // PVOID pvContext - Pointer to parsing context. // PARSEXML_STACKENTRY * aElementStack - Array of parent elements containing // the completed element. // BOOL * pfContinueParsing - Pointer to BOOL that should be set // to FALSE if the calling function // should stop parsing the XML. // // Returns: HRESULT // DPNH_OK - Description response was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::ParseXMLCallback_DescriptionResponse(PARSEXML_ELEMENT * const pParseElement, PVOID pvContext, PARSEXML_STACKENTRY * const aElementStack, BOOL * const pfContinueParsing) { HRESULT hr = DPNH_OK; CUPnPDevice * pUPnPDevice; char * pszServiceType = NULL; char * pszServiceId = NULL; char * pszControlURL = NULL; DWORD dwSubElement; PARSEXML_SUBELEMENT * pSubElement; SOCKADDR_IN saddrinControl; SOCKADDR_IN * psaddrinHost; char * pszRelativePath; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, 0x%p)", this, pParseElement, pvContext, aElementStack, pfContinueParsing); pUPnPDevice = (CUPnPDevice *) pvContext; DNASSERT(pUPnPDevice != NULL); DNASSERT(pParseElement->papszElementStack == (char **) (&c_szElementStack_service)); // // Look for the subelements we want. // for(dwSubElement = 0; dwSubElement < pParseElement->dwNumSubElements; dwSubElement++) { pSubElement = &pParseElement->paSubElements[dwSubElement]; if (_stricmp(pSubElement->pszNameFound, XML_DEVICEDESCRIPTION_SERVICETYPE) == 0) { if (pszServiceType == NULL) { pszServiceType = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" XML_DEVICEDESCRIPTION_SERVICETYPE "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (_stricmp(pSubElement->pszNameFound, XML_DEVICEDESCRIPTION_SERVICEID) == 0) { if (pszServiceId == NULL) { pszServiceId = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" XML_DEVICEDESCRIPTION_SERVICEID "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (_stricmp(pSubElement->pszNameFound, XML_DEVICEDESCRIPTION_CONTROLURL) == 0) { if (pszControlURL == NULL) { pszControlURL = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" XML_DEVICEDESCRIPTION_CONTROLURL "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else { DPFX(DPFPREP, 7, "Ignoring subelement \"%hs\" (value = \"%hs\").", pSubElement->pszNameFound, pSubElement->pszValueFound); } } // // If one of those elements was not specified, then this element is not // helpful. // if ((pszServiceType == NULL) || (pszServiceId == NULL) || (pszControlURL == NULL)) { DPFX(DPFPREP, 1, "Couldn't find either \"" XML_DEVICEDESCRIPTION_SERVICETYPE "\", \"" XML_DEVICEDESCRIPTION_SERVICEID "\", or \"" XML_DEVICEDESCRIPTION_CONTROLURL "\" in XML description, ignoring element."); goto Exit; } // // If the service type is not one of the ones we want, ignore the element. // if (_stricmp(pszServiceType, URI_SERVICE_WANIPCONNECTION_A) == 0) { DPFX(DPFPREP, 7, "Found \"" URI_SERVICE_WANIPCONNECTION_A "\"."); DNASSERT(! pUPnPDevice->IsWANPPPConnection()); } else if (_stricmp(pszServiceType, URI_SERVICE_WANPPPCONNECTION_A) == 0) { DPFX(DPFPREP, 7, "Found \"" URI_SERVICE_WANPPPCONNECTION_A "\"."); pUPnPDevice->NoteWANPPPConnection(); } else { DPFX(DPFPREP, 1, "Ignoring unknown service type \"%hs\".", pszServiceType); goto Exit; } pParseElement->fFoundMatchingElement = TRUE; (*pfContinueParsing) = FALSE; // // Validate and store the service control URL. // hr = this->GetAddressFromURL(pszControlURL, &saddrinControl, &pszRelativePath); if (hr != DPNH_OK) { psaddrinHost = pUPnPDevice->GetHostAddress(); DPFX(DPFPREP, 1, "No control address in URL, using host address %u.%u.%u.%u:%u and full URL as relative path (\"%hs\").", psaddrinHost->sin_addr.S_un.S_un_b.s_b1, psaddrinHost->sin_addr.S_un.S_un_b.s_b2, psaddrinHost->sin_addr.S_un.S_un_b.s_b3, psaddrinHost->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinHost->sin_port), pszControlURL); memcpy(&saddrinControl, psaddrinHost, sizeof(SOCKADDR_IN)); pszRelativePath = pszControlURL; } else { #if 0 // // Ensure that the address to use is local. It doesn't make sense that // in order to make mappings for our private network we would need to // contact something outside. // if (! this->IsAddressLocal(pUPnPDevice->GetOwningDevice(), &saddrinControl)) { DPFX(DPFPREP, 1, "Control address designated (%u.%u.%u.%u:%u) is not local, ignoring message.", saddrinControl.sin_addr.S_un.S_un_b.s_b1, saddrinControl.sin_addr.S_un.S_un_b.s_b2, saddrinControl.sin_addr.S_un.S_un_b.s_b3, saddrinControl.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinControl.sin_port)); goto Exit; } #else psaddrinHost = pUPnPDevice->GetHostAddress(); // // Don't accept responses that refer to addresses other than the one // that sent this response. // if (saddrinControl.sin_addr.S_un.S_addr != psaddrinHost->sin_addr.S_un.S_addr) { DPFX(DPFPREP, 1, "Control IP address designated (%u.%u.%u.%u:%u) is not the same as host IP address (%u.%u.%u.%u:%u), ignoring message.", saddrinControl.sin_addr.S_un.S_un_b.s_b1, saddrinControl.sin_addr.S_un.S_un_b.s_b2, saddrinControl.sin_addr.S_un.S_un_b.s_b3, saddrinControl.sin_addr.S_un.S_un_b.s_b4, NTOHS(saddrinControl.sin_port), psaddrinHost->sin_addr.S_un.S_un_b.s_b1, psaddrinHost->sin_addr.S_un.S_un_b.s_b2, psaddrinHost->sin_addr.S_un.S_un_b.s_b3, psaddrinHost->sin_addr.S_un.S_un_b.s_b4, NTOHS(psaddrinHost->sin_port)); goto Exit; } #endif // // Don't accept responses that refer to ports in the reserved range // (less than or equal to 1024) other than the standard HTTP port. // if ((NTOHS(saddrinControl.sin_port) <= MAX_RESERVED_PORT) && (saddrinControl.sin_port != HTONS(HTTP_PORT))) { DPFX(DPFPREP, 1, "Control address designated invalid port %u, ignoring message.", NTOHS(saddrinControl.sin_port)); goto Exit; } } pUPnPDevice->SetControlAddress(&saddrinControl); // // Save the service control URL. // hr = pUPnPDevice->SetServiceControlURL(pszRelativePath); if (hr != DPNH_OK) { DPFX(DPFPREP, 0, "Couldn't store service control URL!"); goto Failure; } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: hr = DPNHERR_GENERIC; goto Exit; } // CNATHelpUPnP::ParseXMLCallback_DescriptionResponse #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ParseXMLCallback_ControlResponse" //============================================================================= // CNATHelpUPnP::ParseXMLCallback_ControlResponse //----------------------------------------------------------------------------- // // Description: Handles a completed parsed element in a control SOAP response. // // Arguments: // PARSEXML_ELEMENT * pParseElement - Pointer to element which was found. // PVOID pvContext - Pointer to parsing context. // PARSEXML_STACKENTRY * aElementStack - Array of parent elements containing // the completed element. // BOOL * pfContinueParsing - Pointer to BOOL that should be set // to FALSE if the calling function // should stop parsing the XML. // // Returns: HRESULT // DPNH_OK - Description response was handled successfully. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::ParseXMLCallback_ControlResponse(PARSEXML_ELEMENT * const pParseElement, PVOID pvContext, PARSEXML_STACKENTRY * const aElementStack, BOOL * const pfContinueParsing) { HRESULT hr = DPNH_OK; PCONTROLRESPONSEPARSECONTEXT pContext; //char * pszReturn = NULL; char * pszExternalIPAddress = NULL; char * pszInternalPort = NULL; char * pszInternalClient = NULL; char * pszEnabled = NULL; char * pszPortMappingDescription = NULL; char * pszLeaseDuration = NULL; char * pszErrorCode = NULL; char * pszErrorDescription = NULL; DWORD dwSubElement; PARSEXML_SUBELEMENT * pSubElement; int iErrorCode; DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, 0x%p, 0x%p, 0x%p)", this, pParseElement, pvContext, aElementStack, pfContinueParsing); pContext = (PCONTROLRESPONSEPARSECONTEXT) pvContext; DNASSERT(pContext != NULL); DNASSERT(pContext->pUPnPDevice != NULL); // // Look for the subelements we want. // for(dwSubElement = 0; dwSubElement < pParseElement->dwNumSubElements; dwSubElement++) { pSubElement = &pParseElement->paSubElements[dwSubElement]; /* if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_CONTROL_RETURN_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszReturn == NULL) { pszReturn = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_CONTROL_RETURN_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } */ if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_GETEXTERNALIPADDRESS_NEWEXTERNALIPADDRESS_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszExternalIPAddress == NULL) { pszExternalIPAddress = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_GETEXTERNALIPADDRESS_NEWEXTERNALIPADDRESS_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_GETSPECIFICPORTMAPPINGENTRY_NEWINTERNALPORT_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszInternalPort == NULL) { pszInternalPort = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWINTERNALPORT_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_GETSPECIFICPORTMAPPINGENTRY_NEWINTERNALCLIENT_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszInternalClient == NULL) { pszInternalClient = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWINTERNALCLIENT_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_GETSPECIFICPORTMAPPINGENTRY_NEWENABLED_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszEnabled == NULL) { pszEnabled = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWENABLED_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPORTMAPPINGDESCRIPTION_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszPortMappingDescription == NULL) { pszPortMappingDescription = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPORTMAPPINGDESCRIPTION_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_GETSPECIFICPORTMAPPINGENTRY_NEWLEASEDURATION_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszLeaseDuration == NULL) { pszLeaseDuration = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWLEASEDURATION_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_CONTROL_ERROR_ERRORCODE_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszErrorCode == NULL) { pszErrorCode = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_CONTROL_ERROR_ERRORCODE_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else if (this->MatchesXMLStringWithoutNamespace(pSubElement->pszNameFound, ARG_CONTROL_ERROR_ERRORDESCRIPTION_A, aElementStack, pSubElement, pParseElement->dwElementStackDepth)) { if (pszErrorDescription == NULL) { pszErrorDescription = pSubElement->pszValueFound; } else { DPFX(DPFPREP, 7, "Ignoring duplicate \"" ARG_CONTROL_ERROR_ERRORDESCRIPTION_A "\" subelement (value = \"%hs\").", pSubElement->pszValueFound); } } else { DPFX(DPFPREP, 7, "Ignoring subelement \"%hs\" (value = \"%hs\").", pSubElement->pszNameFound, pSubElement->pszValueFound); } } // end for (each sub element) if (pContext->dwHTTPResponseCode == 200) { // // The action succeeded. // switch (pContext->ControlResponseType) { /* case CONTROLRESPONSETYPE_QUERYSTATEVARIABLE_EXTERNALIPADDRESS: { if (pszReturn == NULL) { DPFX(DPFPREP, 1, "Couldn't find \"" ARG_CONTROL_RETURN_A "\" in SOAP response, ignoring element."); goto Exit; } DPFX(DPFPREP, 2, "QueryStateVariable returned \"%hs\".", pszReturn); / * // // Key off of the variable we were querying. // switch (pContext->ControlResponseType) { case CONTROLRESPONSETYPE_QUERYSTATEVARIABLE_EXTERNALIPADDRESS: { * / pContext->pControlResponseInfo->dwExternalIPAddressV4 = this->m_pfninet_addr(pszReturn); if (pContext->pControlResponseInfo->dwExternalIPAddressV4 == INADDR_NONE) { DPFX(DPFPREP, 1, "External IP address string \"%hs\" is invalid, using INADDR_ANY."); pContext->pControlResponseInfo->dwExternalIPAddressV4 = INADDR_ANY; } / * break; } } * / break; } */ case CONTROLRESPONSETYPE_GETEXTERNALIPADDRESS: { if (pszExternalIPAddress == NULL) { DPFX(DPFPREP, 1, "Couldn't find \"" ARG_GETEXTERNALIPADDRESS_NEWEXTERNALIPADDRESS_A "\" in SOAP response, ignoring element."); goto Exit; } DPFX(DPFPREP, 2, "GetExternalIPAddress returned \"%hs\".", pszExternalIPAddress); pContext->pControlResponseInfo->dwExternalIPAddressV4 = this->m_pfninet_addr(pszExternalIPAddress); if ((pContext->pControlResponseInfo->dwExternalIPAddressV4 == INADDR_NONE) || (IS_CLASSD_IPV4_ADDRESS(pContext->pControlResponseInfo->dwExternalIPAddressV4))) { DPFX(DPFPREP, 1, "External IP address string \"%hs\" is invalid, using INADDR_ANY."); pContext->pControlResponseInfo->dwExternalIPAddressV4 = INADDR_ANY; } break; } case CONTROLRESPONSETYPE_ADDPORTMAPPING: { DPFX(DPFPREP, 2, "AddPortMapping got success response."); break; } case CONTROLRESPONSETYPE_GETSPECIFICPORTMAPPINGENTRY: { /* if ((pszInternalPort == NULL) || (pszInternalClient == NULL) || (pszEnabled == NULL) || (pszPortMappingDescription == NULL) || (pszLeaseDuration == NULL)) */ if (pszInternalClient == NULL) { DPFX(DPFPREP, 1, "Couldn't find \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWINTERNALCLIENT_A "\" in SOAP response, ignoring element."); goto Exit; } if (pszInternalPort == NULL) { DPFX(DPFPREP, 1, "Couldn't find \"" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWINTERNALPORT_A "\" in SOAP response, assuming asymmetric mappings are not supported."); pszInternalPort = "0"; } DPFX(DPFPREP, 2, "GetPortMappingPrivateIP returned \"%hs:%hs\".", pszInternalClient, pszInternalPort); pContext->pControlResponseInfo->dwInternalClientV4 = this->m_pfninet_addr(pszInternalClient); if (pContext->pControlResponseInfo->dwInternalClientV4 == INADDR_ANY) { DPFX(DPFPREP, 0, "Internal client address is INADDR_ANY!"); hr = DPNHERR_GENERIC; goto Failure; } pContext->pControlResponseInfo->wInternalPort = HTONS((WORD) atoi(pszInternalPort)); break; } case CONTROLRESPONSETYPE_DELETEPORTMAPPING: { DPFX(DPFPREP, 2, "DeletePortMapping got success response."); break; } default: { DNASSERT(FALSE); hr = DPNHERR_GENERIC; goto Failure; break; } } // // The action completed successfully. // pContext->pControlResponseInfo->hrErrorCode = DPNH_OK; } else { // // The action failed. // // // See if we found an error description that we can print for // informational purposes. // if ((pszErrorCode != NULL) && (pszErrorDescription != NULL)) { iErrorCode = atoi(pszErrorCode); switch (iErrorCode) { case UPNPERR_IGD_NOSUCHENTRYINARRAY: { DPFX(DPFPREP, 1, "Control action was rejected with NoSuchEntryInArray error %hs (description = \"%hs\").", pszErrorCode, pszErrorDescription); pContext->pControlResponseInfo->hrErrorCode = DPNHERR_NOMAPPING; break; } case UPNPERR_IGD_CONFLICTINMAPPINGENTRY: { DPFX(DPFPREP, 1, "Control action was rejected with ConflictInMappingEntry error %hs (description = \"%hs\").", pszErrorCode, pszErrorDescription); pContext->pControlResponseInfo->hrErrorCode = DPNHERR_PORTUNAVAILABLE; break; } case UPNPERR_IGD_SAMEPORTVALUESREQUIRED: { DPFX(DPFPREP, 1, "Control action was rejected with SamePortValuesRequired error %hs (description = \"%hs\").", pszErrorCode, pszErrorDescription); pContext->pControlResponseInfo->hrErrorCode = (HRESULT) UPNPERR_IGD_SAMEPORTVALUESREQUIRED; break; } case UPNPERR_IGD_ONLYPERMANENTLEASESSUPPORTED: { DPFX(DPFPREP, 1, "Control action was rejected with OnlyPermanentLeasesSupported error %hs (description = \"%hs\").", pszErrorCode, pszErrorDescription); pContext->pControlResponseInfo->hrErrorCode = (HRESULT) UPNPERR_IGD_ONLYPERMANENTLEASESSUPPORTED; break; } default: { DPFX(DPFPREP, 1, "Control action was rejected with unknown error \"%hs\", \"%hs\", assuming generic failure.", pszErrorCode, pszErrorDescription); pContext->pControlResponseInfo->hrErrorCode = DPNHERR_GENERIC; break; } } } else { DPFX(DPFPREP, 1, "Couldn't find either \"" ARG_CONTROL_ERROR_ERRORCODE_A "\", or \"" ARG_CONTROL_ERROR_ERRORDESCRIPTION_A "\" in SOAP response, assuming generic failure."); pContext->pControlResponseInfo->hrErrorCode = DPNHERR_GENERIC; } } // // If we got here, we got the information we needed. // pParseElement->fFoundMatchingElement = TRUE; (*pfContinueParsing) = FALSE; Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::ParseXMLCallback_ControlResponse #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ClearDevicesUPnPDevice" //============================================================================= // CNATHelpUPnP::ClearDevicesUPnPDevice //----------------------------------------------------------------------------- // // Description: Forcefully simulates de-registration with a UPnP device /// without actually going to the network. This clears all bind // IDs, public addresses, and cached mappings for a given device's // local or remote server, and should only be called after the // server appears to have died. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device whose UPnP device should be // removed. // // Returns: None. //============================================================================= void CNATHelpUPnP::ClearDevicesUPnPDevice(CDevice * const pDevice) { CUPnPDevice * pUPnPDevice; DNASSERT(pDevice != NULL); pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { #ifdef DBG DPFX(DPFPREP, 1, "Clearing device 0x%p's UPnP device (0x%p).", pDevice, pUPnPDevice); pDevice->IncrementUPnPDeviceFailures(); this->m_dwNumServerFailures++; #endif // DBG // // Since there was a change in the network, go back to polling // relatively quickly. // this->ResetNextPollInterval(); pUPnPDevice->ClearDeviceOwner(); DNASSERT(pUPnPDevice->m_blList.IsListMember(&this->m_blUPnPDevices)); pUPnPDevice->m_blList.RemoveFromList(); // // Transfer list reference to our pointer, since GetUPnPDevice did not give // us one. // if (pUPnPDevice->IsConnected()) { DNASSERT(pUPnPDevice->GetControlSocket() != INVALID_SOCKET); this->m_pfnshutdown(pUPnPDevice->GetControlSocket(), 0); // ignore error this->m_pfnclosesocket(pUPnPDevice->GetControlSocket()); pUPnPDevice->SetControlSocket(INVALID_SOCKET); } else { DNASSERT(pUPnPDevice->GetControlSocket() == INVALID_SOCKET); } this->ClearAllUPnPRegisteredPorts(pDevice); pUPnPDevice->ClearLocationURL(); pUPnPDevice->ClearUSN(); pUPnPDevice->ClearServiceControlURL(); pUPnPDevice->DestroyReceiveBuffer(); pUPnPDevice->RemoveAllCachedMappings(); pUPnPDevice->DecRef(); } else { DPFX(DPFPREP, 1, "Can't clear device 0x%p's UPnP device, it doesn't exist.", pDevice); } } // CNATHelpUPnP::ClearDevicesUPnPDevice #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ClearAllUPnPRegisteredPorts" //============================================================================= // CNATHelpUPnP::ClearAllUPnPRegisteredPorts //----------------------------------------------------------------------------- // // Description: Clears all bind IDs and public addresses for a given // device's UPnP Internet gateway. This should only be called // after the UPnP device dies. // // The object lock is assumed to be held. // // Arguments: // CDevice * pDevice - Pointer to device whose ports should be unbound. // BOOL fRemote - TRUE if clearing remote server, FALSE if clearing // local server. // // Returns: None. //============================================================================= void CNATHelpUPnP::ClearAllUPnPRegisteredPorts(CDevice * const pDevice) { CBilink * pBilink; CRegisteredPort * pRegisteredPort; DNASSERT(pDevice != NULL); pBilink = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilink != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilink->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilink); if (pRegisteredPort->HasUPnPPublicAddresses()) { if (! pRegisteredPort->IsRemovingUPnPLease()) { DPFX(DPFPREP, 1, "Registered port 0x%p losing UPnP public address.", pRegisteredPort); // // Let the user know the next time GetCaps is called. // this->m_dwFlags |= NATHELPUPNPOBJ_ADDRESSESCHANGED; // // Note that this means the crash recovery entry will be left // in the registry for the next person to come along and clean // up. That should be okay, since we should only be doing this // if we had a problem talking to the UPnP device (either it // went AWOL, or we lost the local network interface). In // either case, we can't really clean it up now, so we have to // leave it for someone else to do. // pRegisteredPort->DestroyUPnPPublicAddressesArray(); pRegisteredPort->NoteNotPermanentUPnPLease(); DNASSERT(this->m_dwNumLeases > 0); this->m_dwNumLeases--; DPFX(DPFPREP, 7, "UPnP lease for 0x%p cleared, total num leases = %u.", pRegisteredPort, this->m_dwNumLeases); } else { DPFX(DPFPREP, 1, "Registered port 0x%p already has had UPnP public address removed, skipping.", pRegisteredPort); } } else { // // Port no longer unavailable (if it had been). // pRegisteredPort->NoteNotUPnPPortUnavailable(); } pBilink = pBilink->GetNext(); } } // CNATHelpUPnP::ClearAllUPnPRegisteredPorts #ifndef DPNBUILD_NOWINSOCK2 #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::RequestLocalAddressListChangeNotification" //============================================================================= // CNATHelpUPnP::RequestLocalAddressListChangeNotification //----------------------------------------------------------------------------- // // Description: Attempts to request asynchronous notification (via the // user's alert event or I/O completion port) when the local // address list changes. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: HRESULT // DPNH_OK - The notification request was successfully submitted. // DPNHERR_GENERIC - An error occurred. //============================================================================= HRESULT CNATHelpUPnP::RequestLocalAddressListChangeNotification(void) { HRESULT hr; DWORD dwTemp; int iReturn; DPFX(DPFPREP, 5, "(0x%p) Enter", this); DNASSERT(! (this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1)); DNASSERT(this->m_sIoctls != INVALID_SOCKET); DNASSERT(this->m_pfnWSAIoctl != NULL); DNASSERT((this->m_hAlertEvent != NULL) || (this->m_hAlertIOCompletionPort != NULL)); DNASSERT(this->m_polAddressListChange != NULL); do { iReturn = this->m_pfnWSAIoctl(this->m_sIoctls, // use the special Ioctl socket SIO_ADDRESS_LIST_CHANGE, // NULL, // no input data 0, // no input data NULL, // no output data 0, // no output data &dwTemp, // ignore bytes returned this->m_polAddressListChange, // overlapped structure NULL); // no completion routine if (iReturn != 0) { dwTemp = this->m_pfnWSAGetLastError(); if (dwTemp != WSA_IO_PENDING) { DPFX(DPFPREP, 0, "Submitting address list change notification request failed (err = %u)!", dwTemp); hr = DPNHERR_GENERIC; goto Failure; } // // Pending is what we want, we're set. // hr = DPNH_OK; break; } // // Address list changed right away? // DPFX(DPFPREP, 1, "Address list changed right away somehow, submitting again."); } while (TRUE); Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%lx]", this, hr); return hr; Failure: goto Exit; } // CNATHelpUPnP::RequestLocalAddressListChangeNotification #endif // ! DPNBUILD_NOWINSOCK2 #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::CreateSocket" //============================================================================= // CNATHelpUPnP::CreateSocket //----------------------------------------------------------------------------- // // Description: Creates a UPnP discovery socket bound to a new random port // on the specified IP interface. Completely random (but non- // reserved) port numbers are chosen first, but if those ports are // in use, WinSock is allowed to choose. The port actually // selected will be returned in psaddrinAddress. // // Arguments: // SOCKADDR_IN * psaddrinAddress - Pointer to base address to use when // binding. The port will be modified. // // Returns: SOCKET //============================================================================= SOCKET CNATHelpUPnP::CreateSocket(SOCKADDR_IN * const psaddrinAddress, int iType, int iProtocol) { SOCKET sTemp; DWORD dwTry; int iTemp; BOOL fTemp; ULONG ulEnable; #ifdef DBG DWORD dwError; #endif // DBG DPFX(DPFPREP, 5, "(0x%p) Parameters: (0x%p, %i, %i)", this, psaddrinAddress, iType, iProtocol); // // Create the socket. // sTemp = this->m_pfnsocket(AF_INET, iType, iProtocol); if (sTemp == INVALID_SOCKET) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't create datagram socket, error = %u!", dwError); #endif // DBG goto Failure; } // // Try binding the socket to a completely random port a few times. // for(dwTry = 0; dwTry < MAX_NUM_RANDOM_PORT_TRIES; dwTry++) { // // Pick a completely random port. For the moment, the value is stored // in host byte order while we make sure it's not a reserved value. // do { psaddrinAddress->sin_port = (WORD) GetGlobalRand(); } while ((psaddrinAddress->sin_port <= MAX_RESERVED_PORT) || (psaddrinAddress->sin_port == 1900) || // SSDP (psaddrinAddress->sin_port == 2234) || // PAST (psaddrinAddress->sin_port == 6073) || // DPNSVR (psaddrinAddress->sin_port == 47624)); // DPLAYSVR // // Now try binding to the port (in network byte order). // psaddrinAddress->sin_port = HTONS(psaddrinAddress->sin_port); if (this->m_pfnbind(sTemp, (SOCKADDR*) psaddrinAddress, sizeof(SOCKADDR_IN)) == 0) { // // We successfully bound to the port. // break; } // // Assume that the port is in use. // #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 2, "Couldn't bind to port %u (err = %u), continuing.", NTOHS(psaddrinAddress->sin_port), dwError); #endif // DBG psaddrinAddress->sin_port = 0; } // // If we ran out of completely random port attempts, just let WinSock // choose it. // if (psaddrinAddress->sin_port == 0) { if (this->m_pfnbind(sTemp, (SOCKADDR*) psaddrinAddress, sizeof(SOCKADDR_IN)) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Failed binding to any port (err = %u)!", dwError); #endif // DBG goto Failure; } // // Find out what port WinSock chose. // iTemp = sizeof(SOCKADDR_IN); if (this->m_pfngetsockname(sTemp, (SOCKADDR *) psaddrinAddress, &iTemp) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't get the socket's address, error = %u!", dwError); #endif // DBG goto Failure; } DNASSERT(psaddrinAddress->sin_port != 0); } // // Set the unicast TTL, if requested. Use the appropriate constant for the // version of WinSock we're using. // if (g_iUnicastTTL != 0) { iTemp = this->m_pfnsetsockopt(sTemp, IPPROTO_IP, #ifdef DPNBUILD_NOWINSOCK2 IP_TTL, #else // ! DPNBUILD_NOWINSOCK2 ((this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1) ? IP_TTL_WINSOCK1 : IP_TTL), #endif // ! DPNBUILD_NOWINSOCK2 (char *) (&g_iUnicastTTL), sizeof(g_iUnicastTTL)); if (iTemp != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't set unicast TTL socket option, error = %u! Ignoring.", dwError); #endif // DBG // // Continue... // } } if (iType == SOCK_DGRAM) { if (g_fUseMulticastUPnPDiscovery) { // // Set the multicast interface. Use the appropriate constant for // the version of WinSock we're using. // iTemp = this->m_pfnsetsockopt(sTemp, IPPROTO_IP, #ifdef DPNBUILD_NOWINSOCK2 IP_MULTICAST_IF, #else // ! DPNBUILD_NOWINSOCK2 ((this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1) ? IP_MULTICAST_IF_WINSOCK1 : IP_MULTICAST_IF), #endif // ! DPNBUILD_NOWINSOCK2 (char *) (&psaddrinAddress->sin_addr.S_un.S_addr), sizeof(psaddrinAddress->sin_addr.S_un.S_addr)); if (iTemp != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 1, "Couldn't set multicast interface socket option, error = %u, ignoring.", dwError); #endif // DBG // // Continue... // } // // Set the multicast TTL, if requested. Use the appropriate // constant for the version of WinSock we're using. // if (g_iMulticastTTL != 0) { iTemp = this->m_pfnsetsockopt(sTemp, IPPROTO_IP, #ifdef DPNBUILD_NOWINSOCK2 IP_MULTICAST_TTL, #else // ! DPNBUILD_NOWINSOCK2 ((this->m_dwFlags & NATHELPUPNPOBJ_WINSOCK1) ? IP_MULTICAST_TTL_WINSOCK1 : IP_MULTICAST_TTL), #endif // ! DPNBUILD_NOWINSOCK2 (char *) (&g_iMulticastTTL), sizeof(g_iMulticastTTL)); if (iTemp != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't set multicast TTL socket option, error = %u! Ignoring.", dwError); #endif // DBG // // Continue... // } } } else { // // Not using multicast. Set the socket up to allow broadcasts in // case we can't determine the gateway. // fTemp = TRUE; if (this->m_pfnsetsockopt(sTemp, SOL_SOCKET, SO_BROADCAST, (char *) (&fTemp), sizeof(fTemp)) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't set broadcast socket option, error = %u!", dwError); #endif // DBG goto Failure; } } } else { // // Make the socket non-blocking. // ulEnable = 1; if (this->m_pfnioctlsocket(sTemp, FIONBIO, &ulEnable) != 0) { #ifdef DBG dwError = this->m_pfnWSAGetLastError(); DPFX(DPFPREP, 0, "Couldn't make socket non-blocking, error = %u!", dwError); #endif // DBG goto Failure; } } Exit: DPFX(DPFPREP, 5, "(0x%p) Returning: [0x%x]", this, sTemp); return sTemp; Failure: if (sTemp != INVALID_SOCKET) { this->m_pfnclosesocket(sTemp); sTemp = INVALID_SOCKET; } goto Exit; } // CNATHelpUPnP::CreateSocket #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::GetAddressToReachGateway" //============================================================================= // CNATHelpUPnP::GetAddressToReachGateway //----------------------------------------------------------------------------- // // Description: Retrieves the address of the gateway for the given device, // or the broadcast address if unable to be determined. // // This will return TRUE if the gateway's address was found, or // the IPHLPAPI DLL could not be used (Win95). FALSE is returned // if IPHLPAPI reported that there was no gateway (ICS private // side adapter). // // Arguments: // CDevice * pDevice - Pointer to device whose gateway should be retrieved. // IN_ADDR * pinaddr - Place to store gateway or broadcast address. // // Returns: BOOL // TRUE - Gateway address was found or had to use broadcast. // FALSE - There is no gateway, do not attempt to use the address. //============================================================================= BOOL CNATHelpUPnP::GetAddressToReachGateway(CDevice * const pDevice, IN_ADDR * const pinaddr) { #ifdef DPNBUILD_NOWINSOCK2 // // Fill in the default address. This should be atomic, so don't worry // about locking the globals. // pinaddr->S_un.S_addr = g_dwDefaultGatewayV4; return TRUE; #else // ! DPNBUILD_NOWINSOCK2 DWORD dwError; BOOL fResult = TRUE; ULONG ulSize; PIP_ADAPTER_INFO pAdaptersBuffer = NULL; PIP_ADAPTER_INFO pAdapterInfo; PIP_ADDR_STRING pIPAddrString; DWORD dwAdapterIndex; PMIB_IPFORWARDTABLE pIPForwardTableBuffer = NULL; DWORD dwTemp; PMIB_IPFORWARDROW pIPForwardRow; // // Fill in the default address. This should be atomic, so don't worry // about locking the globals. // pinaddr->S_un.S_addr = g_dwDefaultGatewayV4; #ifdef DBG pDevice->ClearGatewayFlags(); #endif // DBG // // If this is the loopback address, then don't bother looking for a // gateway, we won't find one. // if (pDevice->GetLocalAddressV4() == NETWORKBYTEORDER_INADDR_LOOPBACK) { DPFX(DPFPREP, 8, "No gateway for loopback address (device = 0x%p).", pDevice); // // No gateway. // #ifdef DBG pDevice->NoteNoGateway(); #endif // DBG fResult = FALSE; goto Exit; } // // If we didn't load the IP helper DLL, we can't do our fancy gateway // tricks. // if (this->m_hIpHlpApiDLL == NULL) { DPFX(DPFPREP, 4, "Didn't load \"iphlpapi.dll\", returning default address for device 0x%p.", pDevice); goto Exit; } // // Keep trying to get the list of adapters until we get ERROR_SUCCESS or a // legitimate error (other than ERROR_BUFFER_OVERFLOW or // ERROR_INSUFFICIENT_BUFFER). // ulSize = 0; do { dwError = this->m_pfnGetAdaptersInfo(pAdaptersBuffer, &ulSize); if (dwError == ERROR_SUCCESS) { // // We succeeded, we should be set. But make sure there are // adapters for us to use. // if (ulSize < sizeof(IP_ADAPTER_INFO)) { DPFX(DPFPREP, 0, "Getting adapters info succeeded but didn't return any valid adapters (%u < %u), returning default address for device 0x%p.", ulSize, sizeof(IP_ADAPTER_INFO), pDevice); goto Exit; } break; } if ((dwError != ERROR_BUFFER_OVERFLOW) && (dwError != ERROR_INSUFFICIENT_BUFFER)) { DPFX(DPFPREP, 0, "Unable to get adapters info (error = 0x%lx), returning default address for device 0x%p.", dwError, pDevice); goto Exit; } // // We need more adapter space. Make sure there are adapters for us to // use. // if (ulSize < sizeof(IP_ADAPTER_INFO)) { DPFX(DPFPREP, 0, "Getting adapters info didn't return any valid adapters (%u < %u), returning default address for device 0x%p.", ulSize, sizeof(IP_ADAPTER_INFO), pDevice); goto Exit; } // // If we previously had a buffer, free it. // if (pAdaptersBuffer != NULL) { DNFree(pAdaptersBuffer); } // // Allocate the buffer. // pAdaptersBuffer = (PIP_ADAPTER_INFO) DNMalloc(ulSize); if (pAdaptersBuffer == NULL) { DPFX(DPFPREP, 0, "Unable to allocate memory for adapters info, returning default address for device 0x%p.", pDevice); goto Exit; } } while (TRUE); // // Now find the device in the adapter list returned. Loop through all // adapters. // pAdapterInfo = pAdaptersBuffer; while (pAdapterInfo != NULL) { // // Loop through all addresses for this adapter looking for the one for // the device we have bound. // pIPAddrString = &pAdapterInfo->IpAddressList; while (pIPAddrString != NULL) { if (this->m_pfninet_addr(pIPAddrString->IpAddress.String) == pDevice->GetLocalAddressV4()) { pinaddr->S_un.S_addr = this->m_pfninet_addr(pAdapterInfo->GatewayList.IpAddress.String); if ((pinaddr->S_un.S_addr == INADDR_ANY) || (pinaddr->S_un.S_addr == INADDR_NONE)) { DPFX(DPFPREP, 8, "Found address for device 0x%p under adapter index %u (\"%hs\") but there is no gateway.", pDevice, pAdapterInfo->Index, pAdapterInfo->Description); // // Although this isn't reporting a gateway, we may still // want to use this adapter. That's because this could be // a multihomed machine with multiple NICs on the same // network, where this one isn't the "default" adapter. // So save the index so we can search for it later. // dwAdapterIndex = pAdapterInfo->Index; goto CheckRouteTable; } // // Make sure the address doesn't match the local device. // if (pinaddr->S_un.S_addr == pDevice->GetLocalAddressV4()) { DPFX(DPFPREP, 1, "Gateway address for device 0x%p (adapter index %u, \"%hs\") matches device IP address %hs! Forcing no gateway.", pDevice, pAdapterInfo->Index, pAdapterInfo->Description, pAdapterInfo->GatewayList.IpAddress.String); // // Pretend there's no gateway, since the one we received is // bogus. // #ifdef DBG pDevice->NoteNoGateway(); #endif // DBG fResult = FALSE; } else { DPFX(DPFPREP, 7, "Found address for device 0x%p under adapter index %u (\"%hs\"), gateway = %hs.", pDevice, pAdapterInfo->Index, pAdapterInfo->Description, pAdapterInfo->GatewayList.IpAddress.String); #ifdef DBG pDevice->NotePrimaryDevice(); #endif // DBG } goto Exit; } pIPAddrString = pIPAddrString->Next; } if (! fResult) { break; } pAdapterInfo = pAdapterInfo->Next; } // // If we got here, then we didn't find the address. fResult will still be // TRUE. // DPFX(DPFPREP, 0, "Did not find adapter with matching address, returning default address for device 0x%p.", pDevice); goto Exit; CheckRouteTable: // // The adapter info structure said that the device doesn't have a gateway. // However for some reason the gateway is only reported for the "default" // device when multiple NICs can reach the same network. Check the routing // table to determine if there's a gateway for secondary devices. // // // Keep trying to get the routing table until we get ERROR_SUCCESS or a // legitimate error (other than ERROR_BUFFER_OVERFLOW or // ERROR_INSUFFICIENT_BUFFER). // ulSize = 0; do { dwError = this->m_pfnGetIpForwardTable(pIPForwardTableBuffer, &ulSize, TRUE); if (dwError == ERROR_SUCCESS) { // // We succeeded, we should be set. But make sure the size is // valid. // if (ulSize < sizeof(MIB_IPFORWARDTABLE)) { DPFX(DPFPREP, 0, "Getting IP forward table succeeded but didn't return a valid buffer (%u < %u), returning \"no gateway\" indication for device 0x%p.", ulSize, sizeof(MIB_IPFORWARDTABLE), pDevice); fResult = FALSE; goto Exit; } break; } if ((dwError != ERROR_BUFFER_OVERFLOW) && (dwError != ERROR_INSUFFICIENT_BUFFER)) { DPFX(DPFPREP, 0, "Unable to get IP forward table (error = 0x%lx), returning \"no gateway\" indication for device 0x%p.", dwError, pDevice); fResult = FALSE; goto Exit; } // // We need more table space. Make sure there are adapters for us to // use. // if (ulSize < sizeof(MIB_IPFORWARDTABLE)) { DPFX(DPFPREP, 0, "Getting IP forward table didn't return any valid adapters (%u < %u), returning \"no gateway\" indication for device 0x%p.", ulSize, sizeof(MIB_IPFORWARDTABLE), pDevice); fResult = FALSE; goto Exit; } // // If we previously had a buffer, free it. // if (pIPForwardTableBuffer != NULL) { DNFree(pIPForwardTableBuffer); } // // Allocate the buffer. // pIPForwardTableBuffer = (PMIB_IPFORWARDTABLE) DNMalloc(ulSize); if (pIPForwardTableBuffer == NULL) { DPFX(DPFPREP, 0, "Unable to allocate memory for IP forward table, returning \"no gateway\" indication for device 0x%p.", pDevice); fResult = FALSE; goto Exit; } } while (TRUE); // // Now find the interface. Note that we don't look it up as a destination // address. Instead, we look for it as the interface to use for a 0.0.0.0 // network destination. // // We're looking for a route entry: // // Network Destination Netmask Gateway Interface Metric // 0.0.0.0 0.0.0.0 xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy 1 // // We have yyy.yyy.yyy.yyy, we're trying to get xxx.xxx.xxx.xxx // pIPForwardRow = pIPForwardTableBuffer->table; for(dwTemp = 0; dwTemp < pIPForwardTableBuffer->dwNumEntries; dwTemp++) { // // Is this a 0.0.0.0 network destination? // if (pIPForwardRow->dwForwardDest == INADDR_ANY) { DNASSERT(pIPForwardRow->dwForwardMask == INADDR_ANY); // // Is this the right interface? // if (pIPForwardRow->dwForwardIfIndex == dwAdapterIndex) { if (pIPForwardRow->dwForwardNextHop == INADDR_ANY) { DPFX(DPFPREP, 8, "Found route table entry, but it didn't have a gateway (device = 0x%p).", pDevice); // // No gateway. // #ifdef DBG pDevice->NoteNoGateway(); #endif // DBG fResult = FALSE; } else { // // Make sure the address doesn't match the local device. // if (pinaddr->S_un.S_addr == pDevice->GetLocalAddressV4()) { DPFX(DPFPREP, 1, "Route table gateway for device 0x%p matches device's IP address %u.%u.%u.%u! Forcing no gateway.", pDevice, pinaddr->S_un.S_un_b.s_b1, pinaddr->S_un.S_un_b.s_b2, pinaddr->S_un.S_un_b.s_b3, pinaddr->S_un.S_un_b.s_b4); // // Pretend there's no gateway, since the one we // received is bogus. // #ifdef DBG pDevice->NoteNoGateway(); #endif // DBG fResult = FALSE; } else { pinaddr->S_un.S_addr = pIPForwardRow->dwForwardNextHop; DPFX(DPFPREP, 8, "Found route table entry, gateway = %u.%u.%u.%u (device = 0x%p).", pinaddr->S_un.S_un_b.s_b1, pinaddr->S_un.S_un_b.s_b2, pinaddr->S_un.S_un_b.s_b3, pinaddr->S_un.S_un_b.s_b4, pDevice); // // We found a gateway after all, fResult == TRUE. // #ifdef DBG pDevice->NoteSecondaryDevice(); #endif // DBG } } // // We're done here. // goto Exit; } } // // Move to next row. // pIPForwardRow++; } // // If we got here, then we couldn't find an appropriate entry in the // routing table. // DPFX(DPFPREP, 1, "Did not find adapter in routing table, returning \"no gateway\" indication for device 0x%p.", pDevice); #ifdef DBG pDevice->NoteNoGateway(); #endif // DBG fResult = FALSE; Exit: if (pAdaptersBuffer != NULL) { DNFree(pAdaptersBuffer); pAdaptersBuffer = NULL; } if (pIPForwardTableBuffer != NULL) { DNFree(pIPForwardTableBuffer); pIPForwardTableBuffer = NULL; } return fResult; #endif // ! DPNBUILD_NOWINSOCK2 } // CNATHelpUPnP::GetAddressToReachGateway #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::IsAddressLocal" //============================================================================= // CNATHelpUPnP::IsAddressLocal //----------------------------------------------------------------------------- // // Description: Returns TRUE if the given address is local to the given // device; that is, if the device can send to the address directly // without having to go through the gateway. // // Note that if IPHLPAPI is not available (Win95), this // function will make an educated guess using a reasonable subnet // mask. // // Arguments: // CDevice * pDevice - Pointer to device to use. // SOCKADDR_IN * psaddrinAddress - Address whose locality is in question. // // Returns: BOOL // TRUE - Address is behind the same gateway as the device. // FALSE - Address is not behind the same gateway as the device. //============================================================================= BOOL CNATHelpUPnP::IsAddressLocal(CDevice * const pDevice, const SOCKADDR_IN * const psaddrinAddress) { BOOL fResult; DWORD dwSubnetMaskV4; #ifndef DPNBUILD_NOWINSOCK2 DWORD dwError; MIB_IPFORWARDROW IPForwardRow; #endif // ! DPNBUILD_NOWINSOCK2 // // If the address to query matches the device's local address exactly, then // of course it's local. // if (psaddrinAddress->sin_addr.S_un.S_addr == pDevice->GetLocalAddressV4()) { DPFX(DPFPREP, 6, "The address %u.%u.%u.%u matches device 0x%p's local address exactly.", psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4, pDevice); fResult = TRUE; goto Exit; } // // If it's a multicast address, then it should not be considered local. // if (IS_CLASSD_IPV4_ADDRESS(psaddrinAddress->sin_addr.S_un.S_addr)) { DPFX(DPFPREP, 6, "Address %u.%u.%u.%u is multicast, not considered local for device 0x%p.", psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4, pDevice); fResult = FALSE; goto Exit; } #ifndef DPNBUILD_NOWINSOCK2 // // If we didn't load the IP helper DLL, we will have to guess. // if (this->m_hIpHlpApiDLL == NULL) { goto EducatedGuess; } // // Figure out what IPHLPAPI says about how to get there. // ZeroMemory(&IPForwardRow, sizeof(IPForwardRow)); dwError = this->m_pfnGetBestRoute(psaddrinAddress->sin_addr.S_un.S_addr, pDevice->GetLocalAddressV4(), &IPForwardRow); if (dwError != ERROR_SUCCESS) { DPFX(DPFPREP, 0, "Unable to get best route to %u.%u.%u.%u via device 0x%p (error = 0x%lx)! Using subnet mask.", psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4, pDevice, dwError); goto EducatedGuess; } // // Key off what IPHLPAPI returned. // switch (IPForwardRow.dwForwardType) { case 1: { // // Other. // DPFX(DPFPREP, 6, "The route from device 0x%p to %u.%u.%u.%u is unknown.", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4); fResult = FALSE; break; } case 2: { // // The route is invalid. // DPFX(DPFPREP, 6, "The route from device 0x%p to %u.%u.%u.%u is invalid.", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4); fResult = FALSE; break; } case 3: { // // The next hop is the final destination (local route). // Unfortunately, on multi-NIC machines querying an address // reachable by another device returns success... not sure why, but // if that's the case we need to further qualify this result. We // do that by making sure the next hop address is actually the // device with which we're querying. // if (IPForwardRow.dwForwardNextHop == pDevice->GetLocalAddressV4()) { DPFX(DPFPREP, 6, "Device 0x%p can reach %u.%u.%u.%u directly, it's local.", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4); fResult = TRUE; } else { DPFX(DPFPREP, 6, "Device 0x%p can reach %u.%u.%u.%u but it would be routed via another device (%u.%u.%u.%u).", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4, ((IN_ADDR*) (&IPForwardRow.dwForwardNextHop))->S_un.S_un_b.s_b1, ((IN_ADDR*) (&IPForwardRow.dwForwardNextHop))->S_un.S_un_b.s_b2, ((IN_ADDR*) (&IPForwardRow.dwForwardNextHop))->S_un.S_un_b.s_b3, ((IN_ADDR*) (&IPForwardRow.dwForwardNextHop))->S_un.S_un_b.s_b4); fResult = FALSE; } break; } case 4: { // // The next hop is not the final destination (remote route). // DPFX(DPFPREP, 6, "Device 0x%p cannot reach %u.%u.%u.%u directly.", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4); fResult = FALSE; break; } default: { // // What? // DPFX(DPFPREP, 0, "Unexpected forward type %u for device 0x%p and address %u.%u.%u.%u!", IPForwardRow.dwForwardType, pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4); fResult = FALSE; break; } } goto Exit; EducatedGuess: #endif // ! DPNBUILD_NOWINSOCK2 // // This should be atomic, so don't worry about locking. // dwSubnetMaskV4 = g_dwSubnetMaskV4; if ((pDevice->GetLocalAddressV4() & dwSubnetMaskV4) == (psaddrinAddress->sin_addr.S_un.S_addr & dwSubnetMaskV4)) { DPFX(DPFPREP, 4, "Didn't load \"iphlpapi.dll\", guessing that device 0x%p can reach %u.%u.%u.%u (using subnet mask 0x%08x).", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4, dwSubnetMaskV4); fResult = TRUE; } else { DPFX(DPFPREP, 4, "Didn't load \"iphlpapi.dll\", guessing that device 0x%p cannot reach %u.%u.%u.%u (using subnet mask 0x%08x).", pDevice, psaddrinAddress->sin_addr.S_un.S_un_b.s_b1, psaddrinAddress->sin_addr.S_un.S_un_b.s_b2, psaddrinAddress->sin_addr.S_un.S_un_b.s_b3, psaddrinAddress->sin_addr.S_un.S_un_b.s_b4, dwSubnetMaskV4); fResult = FALSE; } Exit: return fResult; } // CNATHelpUPnP::IsAddressLocal #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::ExpireOldCachedMappings" //============================================================================= // CNATHelpUPnP::ExpireOldCachedMappings //----------------------------------------------------------------------------- // // Description: Removes any cached mappings for any device which has // expired. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::ExpireOldCachedMappings(void) { DWORD dwCurrentTime; CBilink * pBilinkUPnPDevice; CBilink * pCachedMaps; CBilink * pBilinkCacheMap; CCacheMap * pCacheMap; CUPnPDevice * pUPnPDevice; DPFX(DPFPREP, 7, "(0x%p) Enter", this); dwCurrentTime = GETTIMESTAMP(); // // Check the UPnP device cached mappings. // pBilinkUPnPDevice = this->m_blUPnPDevices.GetNext(); while (pBilinkUPnPDevice != &this->m_blUPnPDevices) { DNASSERT(! pBilinkUPnPDevice->IsEmpty()); pUPnPDevice = UPNPDEVICE_FROM_BILINK(pBilinkUPnPDevice); // // Check the actual cached mappings. // pCachedMaps = pUPnPDevice->GetCachedMaps(); pBilinkCacheMap = pCachedMaps->GetNext(); while (pBilinkCacheMap != pCachedMaps) { DNASSERT(! pBilinkCacheMap->IsEmpty()); pCacheMap = CACHEMAP_FROM_BILINK(pBilinkCacheMap); pBilinkCacheMap = pBilinkCacheMap->GetNext(); if ((int) (pCacheMap->GetExpirationTime() - dwCurrentTime) < 0) { DPFX(DPFPREP, 5, "UPnP device 0x%p cached mapping 0x%p has expired.", pUPnPDevice, pCacheMap); pCacheMap->m_blList.RemoveFromList(); delete pCacheMap; } } pBilinkUPnPDevice = pBilinkUPnPDevice->GetNext(); } DPFX(DPFPREP, 7, "(0x%p) Leave", this); } // CNATHelpUPnP::ExpireOldCachedMappings #ifdef WINNT #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::IsUPnPServiceDisabled" //============================================================================= // CNATHelpUPnP::IsUPnPServiceDisabled //----------------------------------------------------------------------------- // // Description: Returns TRUE if at least one UPnP related service is // disabled, FALSE if no UPnP related services are disabled. // // Arguments: // char * szString - Pointer to string to print. // int iStringLength - Length of string to print. // char * szDescription - Description header for the transaction. // CDevice * pDevice - Device handling transaction, or NULL if not // known. // // Returns: BOOL // TRUE - A UPnP related service was disabled. // FALSE - No UPnP related services were disabled. //============================================================================= BOOL CNATHelpUPnP::IsUPnPServiceDisabled(void) { BOOL fResult = FALSE; SC_HANDLE schSCManager = NULL; DWORD dwTemp; SC_HANDLE schService = NULL; QUERY_SERVICE_CONFIG * pQueryServiceConfig = NULL; DWORD dwQueryServiceConfigSize = 0; DWORD dwError; schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (schSCManager == NULL) { #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't open SC Manager (err = %u)!", dwError); #endif // DBG goto Exit; } // // Loop through each relevant service. // for(dwTemp = 0; dwTemp < (sizeof(c_tszUPnPServices) / sizeof(TCHAR*)); dwTemp++) { schService = OpenService(schSCManager, c_tszUPnPServices[dwTemp], SERVICE_QUERY_CONFIG); if (schService != NULL) { do { if (QueryServiceConfig(schService, pQueryServiceConfig, dwQueryServiceConfigSize, &dwQueryServiceConfigSize)) { // // Make sure the size written is valid. // if (dwQueryServiceConfigSize < sizeof(QUERY_SERVICE_CONFIG)) { DPFX(DPFPREP, 0, "Got invalid service config size for \"%s\" (%u < %u)!", c_tszUPnPServices[dwTemp], dwQueryServiceConfigSize, sizeof(QUERY_SERVICE_CONFIG)); goto Exit; } break; } // // Otherwise, we failed. Make sure it's because our buffer was // too small. // dwError = GetLastError(); if (dwError != ERROR_INSUFFICIENT_BUFFER) { DPFX(DPFPREP, 0, "Couldn't query \"%s\" service config (err = %u)!", c_tszUPnPServices[dwTemp], dwError); goto Exit; } // // Make sure the size needed is valid. // if (dwQueryServiceConfigSize < sizeof(QUERY_SERVICE_CONFIG)) { DPFX(DPFPREP, 0, "Got invalid service config size for \"%s\" (%u < %u)!", c_tszUPnPServices[dwTemp], dwQueryServiceConfigSize, sizeof(QUERY_SERVICE_CONFIG)); goto Exit; } // // (Re)-allocate the buffer. // if (pQueryServiceConfig != NULL) { DNFree(pQueryServiceConfig); } pQueryServiceConfig = (QUERY_SERVICE_CONFIG*) DNMalloc(dwQueryServiceConfigSize); if (pQueryServiceConfig == NULL) { DPFX(DPFPREP, 0, "Couldn't allocate memory to query service config."); goto Exit; } } while (TRUE); // // If the service was disabled, we're done here. // if (pQueryServiceConfig->dwStartType == SERVICE_DISABLED) { DPFX(DPFPREP, 1, "The \"%s\" service has been disabled.", c_tszUPnPServices[dwTemp]); fResult = TRUE; goto Exit; } DPFX(DPFPREP, 7, "The \"%s\" service is not disabled (start type = %u).", c_tszUPnPServices[dwTemp], pQueryServiceConfig->dwStartType); } else { // // Win2K doesn't have these services, so it will always fail. // #ifdef DBG dwError = GetLastError(); DPFX(DPFPREP, 1, "Couldn't open \"%s\" service (err = %u), continuing.", c_tszUPnPServices[dwTemp], dwError); #endif // DBG } } Exit: if (pQueryServiceConfig != NULL) { DNFree(pQueryServiceConfig); pQueryServiceConfig = NULL; } if (schService != NULL) { CloseServiceHandle(schService); schService = NULL; } if (schSCManager != NULL) { CloseServiceHandle(schSCManager); schSCManager = NULL; } return fResult; } // CNATHelpUPnP::IsUPnPServiceDisabled #endif // WINNT #ifdef DBG #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::PrintUPnPTransactionToFile" //============================================================================= // CNATHelpUPnP::PrintUPnPTransactionToFile //----------------------------------------------------------------------------- // // Description: Prints a given UPnP transaction to the file if logging is // enabled. // // The object lock is assumed to be held. // // Arguments: // char * szString - Pointer to string to print. // int iStringLength - Length of string to print. // char * szDescription - Description header for the transaction. // CDevice * pDevice - Device handling transaction, or NULL if not // known. // // Returns: None. //============================================================================= void CNATHelpUPnP::PrintUPnPTransactionToFile(const char * const szString, const int iStringLength, const char * const szDescription, CDevice * const pDevice) { DNHANDLE hFile; DWORD dwNumBytesWritten; TCHAR tszHeaderPrefix[256]; DWORD dwError; #ifdef UNICODE char szHeaderPrefix[256]; #endif // UNICODE // // Lock the globals so nobody touches the string while we use it. // DNEnterCriticalSection(&g_csGlobalsLock); // // Only print it if UPnP transaction logging is turned on. // if (wcslen(g_wszUPnPTransactionLog) > 0) { #ifndef UNICODE HRESULT hr; char szUPnPTransactionLog[sizeof(g_wszUPnPTransactionLog) / sizeof(WCHAR)]; DWORD dwLength; // // Convert the Unicode file name/path into ANSI. // dwLength = sizeof(szUPnPTransactionLog) / sizeof(char); hr = STR_WideToAnsi(g_wszUPnPTransactionLog, -1, // NULL terminated szUPnPTransactionLog, &dwLength); if (hr != S_OK) { DPFX(DPFPREP, 0, "Couldn't convert UPnP transaction log file string from Unicode to ANSI (err = 0x%lx)!", hr); hFile = DNINVALID_HANDLE_VALUE; } else { // // Open the file if it exists, or create a new one if it doesn't. // hFile = DNCreateFile(szUPnPTransactionLog, (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); } #else // UNICODE // // Open the file if it exists, or create a new one if it doesn't. // hFile = DNCreateFile(g_wszUPnPTransactionLog, (GENERIC_READ | GENERIC_WRITE), FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); #endif // UNICODE if (hFile == DNINVALID_HANDLE_VALUE) { dwError = GetLastError(); DPFX(DPFPREP, 0, "Couldn't open UPnP transaction log file, err = %u!", dwError); } else { // // Move the write pointer to the end of the file unless the file // has exceeded the maximum size, in which case just start over. // Ignore error. // if (GetFileSize(HANDLE_FROM_DNHANDLE(hFile), NULL) >= MAX_TRANSACTION_LOG_SIZE) { DPFX(DPFPREP, 0, "Transaction log maximum size exceeded, overwriting existing contents!"); SetFilePointer(HANDLE_FROM_DNHANDLE(hFile), 0, NULL, FILE_BEGIN); } else { SetFilePointer(HANDLE_FROM_DNHANDLE(hFile), 0, NULL, FILE_END); } // // Write the descriptive header. Ignore errors. // if (pDevice != NULL) { IN_ADDR inaddr; inaddr.S_un.S_addr = pDevice->GetLocalAddressV4(); wsprintf(tszHeaderPrefix, _T("%u\t0x%lx\t0x%lx\t(0x%p, %u.%u.%u.%u) UPnP transaction \""), GETTIMESTAMP(), GetCurrentProcessId(), GetCurrentThreadId(), pDevice, inaddr.S_un.S_un_b.s_b1, inaddr.S_un.S_un_b.s_b2, inaddr.S_un.S_un_b.s_b3, inaddr.S_un.S_un_b.s_b4); } else { wsprintf(tszHeaderPrefix, _T("%u\t0x%lx\t0x%lx\t(no device) UPnP transaction \""), GETTIMESTAMP(), GetCurrentProcessId(), GetCurrentThreadId()); } #ifdef UNICODE STR_jkWideToAnsi(szHeaderPrefix, tszHeaderPrefix, (_tcslen(tszHeaderPrefix) + 1)); WriteFile(HANDLE_FROM_DNHANDLE(hFile), szHeaderPrefix, strlen(szHeaderPrefix), &dwNumBytesWritten, NULL); #else // ! UNICODE WriteFile(HANDLE_FROM_DNHANDLE(hFile), tszHeaderPrefix, _tcslen(tszHeaderPrefix), &dwNumBytesWritten, NULL); #endif // ! UNICODE WriteFile(HANDLE_FROM_DNHANDLE(hFile), szDescription, strlen(szDescription), &dwNumBytesWritten, NULL); WriteFile(HANDLE_FROM_DNHANDLE(hFile), "\"\r\n", strlen("\"\r\n"), &dwNumBytesWritten, NULL); // // Write the transaction. Ignore error. // WriteFile(HANDLE_FROM_DNHANDLE(hFile), szString, iStringLength, &dwNumBytesWritten, NULL); // // Add blank space. Ignore error. // WriteFile(HANDLE_FROM_DNHANDLE(hFile), "\r\n\r\n", strlen("\r\n\r\n"), &dwNumBytesWritten, NULL); // // Truncate the log at this point in case we are overwriting // existing contents. Ignore error. // SetEndOfFile(HANDLE_FROM_DNHANDLE(hFile)); // // Close the file. // DNCloseHandle(hFile); } } // // Drop the globals lock. // DNLeaveCriticalSection(&g_csGlobalsLock); } // CNATHelpUPnP::PrintUPnPTransactionToFile #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::DebugPrintCurrentStatus" //============================================================================= // CNATHelpUPnP::DebugPrintCurrentStatus //----------------------------------------------------------------------------- // // Description: Prints all the devices and mappings to the debug log // routines. // // The object lock is assumed to be held. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::DebugPrintCurrentStatus(void) { CBilink * pBilinkDevice; CBilink * pBilinkRegisteredPort; CDevice * pDevice; CRegisteredPort * pRegisteredPort; IN_ADDR inaddrTemp; DWORD dwTemp; SOCKADDR_IN * pasaddrinTemp; SOCKADDR_IN * pasaddrinPrivate; CUPnPDevice * pUPnPDevice; SOCKADDR_IN * pasaddrinUPnPPublic; DPFX(DPFPREP, 3, "Object flags = 0x%08x", this->m_dwFlags); pBilinkDevice = this->m_blDevices.GetNext(); while (pBilinkDevice != &this->m_blDevices) { DNASSERT(! pBilinkDevice->IsEmpty()); pDevice = DEVICE_FROM_BILINK(pBilinkDevice); inaddrTemp.S_un.S_addr = pDevice->GetLocalAddressV4(); DPFX(DPFPREP, 3, "Device 0x%p (%u.%u.%u.%u):", pDevice, inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); // // Print the search information. We should have detected it by now. // if (pDevice->IsPerformingRemoteUPnPDiscovery()) { if (pDevice->GotRemoteUPnPDiscoveryConnReset()) { DPFX(DPFPREP, 3, " Performed remote UPnP discovery (from port %u), but got conn reset.", NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } else { DPFX(DPFPREP, 3, " Performed remote UPnP discovery (from port %u).", NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } } else { //DPFX(DPFPREP, 3, " Didn't perform remote UPnP discovery (from port %u).", // NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } if (pDevice->IsPerformingLocalUPnPDiscovery()) { if (pDevice->GotLocalUPnPDiscoveryConnReset()) { DPFX(DPFPREP, 3, " Performed local UPnP discovery (from port %u), but got conn reset.", NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } else { DPFX(DPFPREP, 3, " Performed local UPnP discovery (from port %u).", NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } } else { //DPFX(DPFPREP, 3, " Didn't perform local UPnP discovery (from port %u).", // NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } #ifndef DPNBUILD_NOWINSOCK2 // // Print the gateway information. We may not have detected it yet, // that's okay. // if (pDevice->IsPrimaryDevice()) { DPFX(DPFPREP, 3, " Primary device."); } else if (pDevice->IsSecondaryDevice()) { DPFX(DPFPREP, 3, " Secondary device."); } else if (pDevice->HasNoGateway()) { DPFX(DPFPREP, 3, " Has no gateway."); } else { DPFX(DPFPREP, 3, " No gateway information known."); } #endif // ! DPNBUILD_NOWINSOCK2 #ifndef DPNBUILD_NOHNETFWAPI if (pDevice->IsHNetFirewalled()) { DPFX(DPFPREP, 3, " HNet firewalled."); } else { DNASSERT(! pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()); } if (pDevice->IsUPnPDiscoverySocketMappedOnHNetFirewall()) { DNASSERT(pDevice->IsHNetFirewalled()); DPFX(DPFPREP, 3, " UPnP discovery socket (port %u) mapped on HNet firewall.", NTOHS(pDevice->GetUPnPDiscoverySocketPort())); } #endif // ! DPNBUILD_NOHNETFWAPI pUPnPDevice = pDevice->GetUPnPDevice(); if (pUPnPDevice != NULL) { pasaddrinTemp = pUPnPDevice->GetControlAddress(); DPFX(DPFPREP, 3, " UPnP device (0x%p, ID = %u, control = %u.%u.%u.%u:%u).", pUPnPDevice, pUPnPDevice->GetID(), pasaddrinTemp->sin_addr.S_un.S_un_b.s_b1, pasaddrinTemp->sin_addr.S_un.S_un_b.s_b2, pasaddrinTemp->sin_addr.S_un.S_un_b.s_b3, pasaddrinTemp->sin_addr.S_un.S_un_b.s_b4, NTOHS(pasaddrinTemp->sin_port)); if (pasaddrinTemp->sin_addr.S_un.S_addr == pDevice->GetLocalAddressV4()) { DPFX(DPFPREP, 3, " Is local."); } DNASSERT(pUPnPDevice->IsReady()); if (pUPnPDevice->IsConnected()) { DPFX(DPFPREP, 3, " Is connected."); } if (pUPnPDevice->DoesNotSupportAsymmetricMappings()) { DPFX(DPFPREP, 3, " Does not support asymmetric mappings."); } if (pUPnPDevice->DoesNotSupportLeaseDurations()) { DPFX(DPFPREP, 3, " Does not support lease durations."); } inaddrTemp.S_un.S_addr = pUPnPDevice->GetExternalIPAddressV4(); if (pUPnPDevice->GetExternalIPAddressV4() == 0) { DPFX(DPFPREP, 3, " Does not have a valid external IP address."); } else { DPFX(DPFPREP, 3, " Has external IP %u.%u.%u.%u.", inaddrTemp.S_un.S_un_b.s_b1, inaddrTemp.S_un.S_un_b.s_b2, inaddrTemp.S_un.S_un_b.s_b3, inaddrTemp.S_un.S_un_b.s_b4); } } if (pDevice->m_blOwnedRegPorts.IsEmpty()) { DPFX(DPFPREP, 3, " No registered port mappings."); } else { DPFX(DPFPREP, 3, " Registered port mappings:"); pBilinkRegisteredPort = pDevice->m_blOwnedRegPorts.GetNext(); while (pBilinkRegisteredPort != &pDevice->m_blOwnedRegPorts) { DNASSERT(! pBilinkRegisteredPort->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilinkRegisteredPort); pasaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); if ((pDevice->GetUPnPDevice() != NULL) && (! pRegisteredPort->IsUPnPPortUnavailable())) { if (pRegisteredPort->HasUPnPPublicAddresses()) { pasaddrinUPnPPublic = pRegisteredPort->GetUPnPPublicAddressesArray(); } else { pasaddrinUPnPPublic = NULL; } } else { pasaddrinUPnPPublic = NULL; } DPFX(DPFPREP, 3, " Registered port 0x%p:", pRegisteredPort); for(dwTemp = 0; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { // // Print private address. // DPFX(DPFPREP, 3, " %u-\tPrivate = %u.%u.%u.%u:%u", dwTemp, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b1, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b2, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b3, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b4, NTOHS(pasaddrinPrivate[dwTemp].sin_port)); // // Print flags. // DPFX(DPFPREP, 3, " \tFlags = 0x%lx", pRegisteredPort->GetFlags()); // // Print UPnP information. // if (pasaddrinUPnPPublic != NULL) { if (pRegisteredPort->HasPermanentUPnPLease()) { DPFX(DPFPREP, 3, " \tUPnP = %u.%u.%u.%u:%u, permanently leased", pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b1, pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b2, pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b3, pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b4, NTOHS(pasaddrinUPnPPublic[dwTemp].sin_port)); } else { DPFX(DPFPREP, 3, " \tUPnP = %u.%u.%u.%u:%u, lease expires at %u", pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b1, pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b2, pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b3, pasaddrinUPnPPublic[dwTemp].sin_addr.S_un.S_un_b.s_b4, NTOHS(pasaddrinUPnPPublic[dwTemp].sin_port), pRegisteredPort->GetUPnPLeaseExpiration()); } } else if (pRegisteredPort->IsUPnPPortUnavailable()) { DPFX(DPFPREP, 3, " \tUPnP = port unavailable"); } else if (pDevice->GetUPnPDevice() != NULL) { DPFX(DPFPREP, 3, " \tUPnP = not registered"); } else { // // No UPnP gateway device. // } #ifndef DPNBUILD_NOHNETFWAPI // // Print firewall status. // if (pRegisteredPort->IsMappedOnHNetFirewall()) { DNASSERT(pDevice->IsHNetFirewalled()); if (pRegisteredPort->IsHNetFirewallMappingBuiltIn()) { DPFX(DPFPREP, 3, " \tHNet firewall = built-in mapping"); } else { DPFX(DPFPREP, 3, " \tHNet firewall = mapped"); } } else if (pRegisteredPort->IsHNetFirewallPortUnavailable()) { DNASSERT(! pRegisteredPort->IsMappedOnHNetFirewall()); DPFX(DPFPREP, 3, " \tHNet firewall = port unavailable"); } else { // // It is not mapped on the firewall. // DNASSERT(! pDevice->IsHNetFirewalled()); DNASSERT(! pRegisteredPort->IsMappedOnHNetFirewall()); DNASSERT(! pRegisteredPort->IsHNetFirewallMappingBuiltIn()); } #endif // ! DPNBUILD_NOHNETFWAPI } pBilinkRegisteredPort = pBilinkRegisteredPort->GetNext(); } } pBilinkDevice = pBilinkDevice->GetNext(); } if (this->m_blUnownedPorts.IsEmpty()) { DPFX(DPFPREP, 3, "No unowned registered port mappings."); } else { DPFX(DPFPREP, 3, "Unowned registered port mappings:"); pBilinkRegisteredPort = this->m_blUnownedPorts.GetNext(); while (pBilinkRegisteredPort != &this->m_blUnownedPorts) { DNASSERT(! pBilinkRegisteredPort->IsEmpty()); pRegisteredPort = REGPORT_FROM_DEVICE_BILINK(pBilinkRegisteredPort); pasaddrinPrivate = pRegisteredPort->GetPrivateAddressesArray(); DNASSERT(pRegisteredPort->GetOwningDevice() == NULL); DNASSERT(! (pRegisteredPort->HasUPnPPublicAddresses())); #ifndef DPNBUILD_NOHNETFWAPI DNASSERT(! (pRegisteredPort->IsMappedOnHNetFirewall())); #endif // ! DPNBUILD_NOHNETFWAPI DPFX(DPFPREP, 3, " Registered port 0x%p:", pRegisteredPort); for(dwTemp = 0; dwTemp < pRegisteredPort->GetNumAddresses(); dwTemp++) { // // Print private address. // DPFX(DPFPREP, 3, " %u-\tPrivate = %u.%u.%u.%u:%u", dwTemp, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b1, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b2, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b3, pasaddrinPrivate[dwTemp].sin_addr.S_un.S_un_b.s_b4, NTOHS(pasaddrinPrivate[dwTemp].sin_port)); // // Print flags. // DPFX(DPFPREP, 3, " \tFlags = 0x%lx", pRegisteredPort->GetFlags()); } pBilinkRegisteredPort = pBilinkRegisteredPort->GetNext(); } } } // CNATHelpUPnP::DebugPrintCurrentStatus #ifndef DPNBUILD_NOHNETFWAPI #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::DebugPrintActiveFirewallMappings" //============================================================================= // CNATHelpUPnP::DebugPrintActiveFirewallMappings //----------------------------------------------------------------------------- // // Description: Prints all the active firewall mapping registry entries to // the debug log routines. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::DebugPrintActiveFirewallMappings(void) { HRESULT hr = DPNH_OK; CRegistry RegObject; DWORD dwIndex; WCHAR wszValueName[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; DWORD dwValueNameSize; DPNHACTIVEFIREWALLMAPPING dpnhafm; DWORD dwValueSize; TCHAR tszObjectName[MAX_INSTANCENAMEDOBJECT_SIZE]; DNHANDLE hNamedObject = NULL; if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVEFIREWALLMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 1, "Couldn't open active firewall mapping key, not dumping entries (local instance = %u).", this->m_dwInstanceKey); } else { // // Walk the list of active mappings. // dwIndex = 0; do { dwValueNameSize = MAX_UPNP_MAPPING_DESCRIPTION_SIZE; if (! RegObject.EnumValues(wszValueName, &dwValueNameSize, dwIndex)) { // // There was an error or there aren't any more keys. We're done. // break; } // // Try reading that mapping's data. // dwValueSize = sizeof(dpnhafm); if ((! RegObject.ReadBlob(wszValueName, (LPBYTE) (&dpnhafm), &dwValueSize)) || (dwValueSize != sizeof(dpnhafm)) || (dpnhafm.dwVersion != ACTIVE_MAPPING_VERSION)) { DPFX(DPFPREP, 1, "Couldn't read \"%ls\" mapping value (index %u) or it was invalid! Ignoring.", wszValueName, dwIndex); } else { // // See if that DPNHUPNP instance is still around. // #ifndef WINCE if (this->m_dwFlags & NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX) { wsprintf(tszObjectName, _T( "Global\\" ) INSTANCENAMEDOBJECT_FORMATSTRING, dpnhafm.dwInstanceKey); } else #endif // ! WINCE { wsprintf(tszObjectName, INSTANCENAMEDOBJECT_FORMATSTRING, dpnhafm.dwInstanceKey); } hNamedObject = DNOpenEvent(SYNCHRONIZE, FALSE, tszObjectName); if (hNamedObject != NULL) { // // This is still an active mapping. // DPFX(DPFPREP, 5, "%u: Firewall mapping \"%ls\" belongs to instance %u (local instance = %u), which is still active.", dwIndex, wszValueName, dpnhafm.dwInstanceKey, this->m_dwInstanceKey); DNCloseHandle(hNamedObject); hNamedObject = NULL; } else { DPFX(DPFPREP, 5, "%u: Firewall mapping \"%ls\" belongs to instance %u (local instance = %u), which no longer exists.", dwIndex, wszValueName, dpnhafm.dwInstanceKey, this->m_dwInstanceKey); } } // // Move to next item. // dwIndex++; } while (TRUE); // // Close the registry object. // RegObject.Close(); DPFX(DPFPREP, 5, "Done reading %u registry entries (local instance = %u).", dwIndex, this->m_dwInstanceKey); } } // CNATHelpUPnP::DebugPrintActiveFirewallMappings #endif // ! DPNBUILD_NOHNETFWAPI #undef DPF_MODNAME #define DPF_MODNAME "CNATHelpUPnP::DebugPrintActiveNATMappings" //============================================================================= // CNATHelpUPnP::DebugPrintActiveNATMappings //----------------------------------------------------------------------------- // // Description: Prints all the active NAT mapping registry entries to the // debug log routines. // // Arguments: None. // // Returns: None. //============================================================================= void CNATHelpUPnP::DebugPrintActiveNATMappings(void) { HRESULT hr = DPNH_OK; CRegistry RegObject; DWORD dwIndex; WCHAR wszValueName[MAX_UPNP_MAPPING_DESCRIPTION_SIZE]; DWORD dwValueNameSize; DPNHACTIVENATMAPPING dpnhanm; DWORD dwValueSize; TCHAR tszObjectName[MAX_INSTANCENAMEDOBJECT_SIZE]; DNHANDLE hNamedObject = NULL; if (! RegObject.Open(HKEY_LOCAL_MACHINE, DIRECTPLAYNATHELP_REGKEY L"\\" REGKEY_COMPONENTSUBKEY L"\\" REGKEY_ACTIVENATMAPPINGS, FALSE, TRUE, TRUE, DPN_KEY_ALL_ACCESS)) { DPFX(DPFPREP, 1, "Couldn't open active NAT mapping key, not dumping entries (local instance = %u).", this->m_dwInstanceKey); } else { // // Walk the list of active mappings. // dwIndex = 0; do { dwValueNameSize = MAX_UPNP_MAPPING_DESCRIPTION_SIZE; if (! RegObject.EnumValues(wszValueName, &dwValueNameSize, dwIndex)) { // // There was an error or there aren't any more keys. We're done. // break; } // // Try reading that mapping's data. // dwValueSize = sizeof(dpnhanm); if ((! RegObject.ReadBlob(wszValueName, (LPBYTE) (&dpnhanm), &dwValueSize)) || (dwValueSize != sizeof(dpnhanm)) || (dpnhanm.dwVersion != ACTIVE_MAPPING_VERSION)) { DPFX(DPFPREP, 1, "Couldn't read \"%ls\" mapping value (index %u) or it was invalid! Ignoring.", wszValueName, dwIndex); } else { // // See if that DPNHUPNP instance is still around. // #ifndef WINCE if (this->m_dwFlags & NATHELPUPNPOBJ_USEGLOBALNAMESPACEPREFIX) { wsprintf(tszObjectName, _T( "Global\\" ) INSTANCENAMEDOBJECT_FORMATSTRING, dpnhanm.dwInstanceKey); } else #endif // ! WINCE { wsprintf(tszObjectName, INSTANCENAMEDOBJECT_FORMATSTRING, dpnhanm.dwInstanceKey); } hNamedObject = DNOpenEvent(SYNCHRONIZE, FALSE, tszObjectName); if (hNamedObject != NULL) { // // This is still an active mapping. // DPFX(DPFPREP, 5, "%u: NAT mapping \"%ls\" belongs to instance %u UPnP device %u (local instance = %u), which is still active.", dwIndex, wszValueName, dpnhanm.dwInstanceKey, dpnhanm.dwUPnPDeviceID, this->m_dwInstanceKey); DNCloseHandle(hNamedObject); hNamedObject = NULL; } else { DPFX(DPFPREP, 5, "%u: NAT mapping \"%ls\" belongs to instance %u UPnP device %u (local instance = %u), which no longer exists.", dwIndex, wszValueName, dpnhanm.dwInstanceKey, dpnhanm.dwUPnPDeviceID, this->m_dwInstanceKey); } } // // Move to next item. // dwIndex++; } while (TRUE); // // Close the registry object. // RegObject.Close(); DPFX(DPFPREP, 5, "Done reading %u registry entries (local instance = %u).", dwIndex, this->m_dwInstanceKey); } } // CNATHelpUPnP::DebugPrintActiveNATMappings #endif // DBG #undef DPF_MODNAME #define DPF_MODNAME "strtrim" //============================================================================= // strtrim //----------------------------------------------------------------------------- // // Description: Removes surrounding white space from the given string. Taken // from \nt\net\upnp\ssdp\common\ssdpparser\parser.cpp (author // TingCai). // // Arguments: // CHAR ** pszStr - Pointer to input string, and place to store resulting // pointer. // // Returns: None. //============================================================================= VOID strtrim(CHAR ** pszStr) { CHAR *end; CHAR *begin; // Empty string. Nothing to do. // if (!(**pszStr)) { return; } begin = *pszStr; end = begin + strlen(*pszStr) - 1; while (*begin == ' ' || *begin == '\t') { begin++; } *pszStr = begin; while (*end == ' ' || *end == '\t') { end--; } *(end+1) = '\0'; } // strtrim #ifdef WINCE #undef DPF_MODNAME #define DPF_MODNAME "GetExeName" //============================================================================= // GetExeName //----------------------------------------------------------------------------- // // Description: Updates a path string to hold only the executable name // contained in the path. // // Arguments: // WCHAR * wszPath - Input path string, and place to store resulting // string. // // Returns: None. //============================================================================= void GetExeName(WCHAR * wszPath) { WCHAR * pCurrent; pCurrent = wszPath + wcslen(wszPath); while (pCurrent > wszPath) { if ((*pCurrent) == L'\\') { break; } pCurrent--; } if (pCurrent != wszPath) { memcpy(wszPath, (pCurrent + 1), ((wcslen(pCurrent) + 1) * sizeof(WCHAR))); } } // GetExeName #endif // WINCE