windows-nt/Source/XPSP1/NT/multimedia/directx/dplay/dpnathlp/dpnhupnp/dpnhupnpintfobj.cpp

24295 lines
627 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/***************************************************************************
*
* 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
#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 <?xml> 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("<s:Envelope" EOL)
+ strlen(" xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL)
+ strlen(" s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL)
+ strlen(" <s:Body>" EOL)
+ strlen(" <u:" CONTROL_QUERYSTATEVARIABLE_A " xmlns:u=\"" URI_CONTROL_A "\">" EOL)
+ strlen(" <u:" ARG_CONTROL_VARNAME_A ">" VAR_EXTERNALIPADDRESS_A "</u:" ARG_CONTROL_VARNAME_A ">" EOL)
+ strlen(" </u:" CONTROL_QUERYSTATEVARIABLE_A ">" EOL)
+ strlen(" </s:Body>" EOL)
+ strlen("</s:Envelope>" 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("<s:Envelope" EOL)
+ strlen(" xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL)
+ strlen(" s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL)
+ strlen(" <s:Body>" EOL)
+ strlen(" <u:" ACTION_GETEXTERNALIPADDRESS_A " xmlns:u=\"") + pUPnPDevice->GetStaticServiceURILength() + strlen("\">" EOL)
+ strlen(" </u:" ACTION_GETEXTERNALIPADDRESS_A ">" EOL)
+ strlen(" </s:Body>" EOL)
+ strlen("</s:Envelope>" 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, "<s:Envelope" EOL);
strcat(pszMessage, " xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL);
strcat(pszMessage, " s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL);
strcat(pszMessage, " <s:Body>" EOL);
strcat(pszMessage, " <u:" CONTROL_QUERYSTATEVARIABLE_A " xmlns:u=\"" URI_CONTROL_A "\">" EOL);
strcat(pszMessage, " <u:" ARG_CONTROL_VARNAME_A ">" VAR_EXTERNALIPADDRESS_A "</u:" ARG_CONTROL_VARNAME_A ">" EOL);
strcat(pszMessage, " </u:" CONTROL_QUERYSTATEVARIABLE_A ">" EOL);
strcat(pszMessage, " </s:Body>" EOL);
strcat(pszMessage, "</s:Envelope>" 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, "<s:Envelope" EOL);
strcat(pszMessage, " xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL);
strcat(pszMessage, " s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL);
strcat(pszMessage, " <s:Body>" EOL);
strcat(pszMessage, " <u:" ACTION_GETEXTERNALIPADDRESS_A " xmlns:u=\"");
strcat(pszMessage, pUPnPDevice->GetStaticServiceURI());
strcat(pszMessage, "\">" EOL);
strcat(pszMessage, " </u:" ACTION_GETEXTERNALIPADDRESS_A ">" EOL);
strcat(pszMessage, " </s:Body>" EOL);
strcat(pszMessage, "</s:Envelope>" 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("<s:Envelope" EOL)
+ strlen(" xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL)
+ strlen(" s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL)
+ strlen(" <s:Body>" EOL)
+ strlen(" <u:" ACTION_ADDPORTMAPPING_A " xmlns:u=\"") + pUPnPDevice->GetStaticServiceURILength() + strlen("\">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "</" ARG_ADDPORTMAPPING_NEWREMOTEHOST_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWEXTERNALPORT_A ">") + _tcslen(tszExternalPort) + strlen("</" ARG_ADDPORTMAPPING_NEWEXTERNALPORT_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWPROTOCOL_A ">") + 3 + strlen("</" ARG_ADDPORTMAPPING_NEWPROTOCOL_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWINTERNALPORT_A ">") + _tcslen(tszInternalPort) + strlen("</" ARG_ADDPORTMAPPING_NEWINTERNALPORT_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWINTERNALCLIENT_A ">") + _tcslen(tszInternalClient) + strlen("</" ARG_ADDPORTMAPPING_NEWINTERNALCLIENT_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWENABLED_A ">" UPNP_BOOLEAN_TRUE "</" ARG_ADDPORTMAPPING_NEWENABLED_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWPORTMAPPINGDESCRIPTION_A ">") + _tcslen(tszDescription) + strlen("</" ARG_ADDPORTMAPPING_NEWPORTMAPPINGDESCRIPTION_A ">" EOL)
+ strlen(" <" ARG_ADDPORTMAPPING_NEWLEASEDURATION_A ">") + _tcslen(tszLeaseDuration) + strlen("</" ARG_ADDPORTMAPPING_NEWLEASEDURATION_A ">" EOL)
+ strlen(" </u:" ACTION_ADDPORTMAPPING_A ">" EOL)
+ strlen(" </s:Body>" EOL)
+ strlen("</s:Envelope>" 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, "<s:Envelope" EOL);
strcat(pszMessage, " xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL);
strcat(pszMessage, " s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL);
strcat(pszMessage, " <s:Body>" EOL);
strcat(pszMessage, " <u:" ACTION_ADDPORTMAPPING_A " xmlns:u=\"");
strcat(pszMessage, pUPnPDevice->GetStaticServiceURI());
strcat(pszMessage, "\">" EOL);
strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "</" ARG_ADDPORTMAPPING_NEWREMOTEHOST_A ">" 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, "</" ARG_ADDPORTMAPPING_NEWEXTERNALPORT_A ">" EOL);
strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWPROTOCOL_A ">");
strcat(pszMessage, ((pRegisteredPort->IsTCP()) ? "TCP" : "UDP"));
strcat(pszMessage, "</" ARG_ADDPORTMAPPING_NEWPROTOCOL_A ">" 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, "</" ARG_ADDPORTMAPPING_NEWINTERNALPORT_A ">" 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, "</" ARG_ADDPORTMAPPING_NEWINTERNALCLIENT_A ">" EOL);
strcat(pszMessage, " <" ARG_ADDPORTMAPPING_NEWENABLED_A ">" UPNP_BOOLEAN_TRUE "</" ARG_ADDPORTMAPPING_NEWENABLED_A ">" 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, "</" ARG_ADDPORTMAPPING_NEWPORTMAPPINGDESCRIPTION_A ">" 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, "</" ARG_ADDPORTMAPPING_NEWLEASEDURATION_A ">" EOL);
strcat(pszMessage, " </u:" ACTION_ADDPORTMAPPING_A ">" EOL);
strcat(pszMessage, " </s:Body>" EOL);
strcat(pszMessage, "</s:Envelope>" 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("<s:Envelope" EOL)
+ strlen(" xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL)
+ strlen(" s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL)
+ strlen(" <s:Body>" EOL)
+ strlen(" <u:" ACTION_GETSPECIFICPORTMAPPINGENTRY_A " xmlns:u=\"") + pUPnPDevice->GetStaticServiceURILength() + strlen("\">" EOL)
+ strlen(" <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWREMOTEHOST_A ">" UPNP_WILDCARD "</" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWREMOTEHOST_A ">" EOL)
+ strlen(" <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWEXTERNALPORT_A ">") + _tcslen(tszExternalPort) + strlen("</" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWEXTERNALPORT_A ">" EOL)
+ strlen(" <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPROTOCOL_A ">") + 3 + strlen("</" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPROTOCOL_A ">" EOL)
+ strlen(" </u:" ACTION_GETSPECIFICPORTMAPPINGENTRY_A ">" EOL)
+ strlen(" </s:Body>" EOL)
+ strlen("</s:Envelope>" 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, "<s:Envelope" EOL);
strcat(pszMessage, " xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL);
strcat(pszMessage, " s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL);
strcat(pszMessage, " <s:Body>" EOL);
strcat(pszMessage, " <u:" ACTION_GETSPECIFICPORTMAPPINGENTRY_A " xmlns:u=\"");
strcat(pszMessage, pUPnPDevice->GetStaticServiceURI());
strcat(pszMessage, "\">" EOL);
strcat(pszMessage, " <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWREMOTEHOST_A ">" UPNP_WILDCARD "</" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWREMOTEHOST_A ">" 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, "</" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWEXTERNALPORT_A ">" EOL);
strcat(pszMessage, " <" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPROTOCOL_A ">");
strcat(pszMessage, ((dwFlags & DPNHQUERYADDRESS_TCP) ? "TCP" : "UDP"));
strcat(pszMessage, "</" ARG_GETSPECIFICPORTMAPPINGENTRY_NEWPROTOCOL_A ">" EOL);
strcat(pszMessage, " </u:" ACTION_GETSPECIFICPORTMAPPINGENTRY_A ">" EOL);
strcat(pszMessage, " </s:Body>" EOL);
strcat(pszMessage, "</s:Envelope>" 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("<s:Envelope" EOL)
+ strlen(" xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL)
+ strlen(" s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL)
+ strlen(" <s:Body>" EOL)
+ strlen(" <u:" ACTION_DELETEPORTMAPPING_A " xmlns:u=\"") + pUPnPDevice->GetStaticServiceURILength() + strlen("\">" EOL)
+ strlen(" <" ARG_DELETEPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "</" ARG_DELETEPORTMAPPING_NEWREMOTEHOST_A ">" EOL)
+ strlen(" <" ARG_DELETEPORTMAPPING_NEWEXTERNALPORT_A ">") + _tcslen(tszExternalPort) + strlen("</" ARG_DELETEPORTMAPPING_NEWEXTERNALPORT_A ">" EOL)
+ strlen(" <" ARG_DELETEPORTMAPPING_NEWPROTOCOL_A ">") + 3 + strlen("</" ARG_DELETEPORTMAPPING_NEWPROTOCOL_A ">" EOL)
+ strlen(" </u:" ACTION_DELETEPORTMAPPING_A ">" EOL)
+ strlen(" </s:Body>" EOL)
+ strlen("</s:Envelope>" 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, "<s:Envelope" EOL);
strcat(pszMessage, " xmlns:s=\"" URL_SOAPENVELOPE_A "\"" EOL);
strcat(pszMessage, " s:encodingStyle=\"" URL_SOAPENCODING_A "\">" EOL);
strcat(pszMessage, " <s:Body>" EOL);
strcat(pszMessage, " <u:" ACTION_DELETEPORTMAPPING_A " xmlns:u=\"");
strcat(pszMessage, pUPnPDevice->GetStaticServiceURI());
strcat(pszMessage, "\">" EOL);
strcat(pszMessage, " <" ARG_DELETEPORTMAPPING_NEWREMOTEHOST_A ">" UPNP_WILDCARD "</" ARG_DELETEPORTMAPPING_NEWREMOTEHOST_A ">" 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, "</" ARG_DELETEPORTMAPPING_NEWEXTERNALPORT_A ">" EOL);
strcat(pszMessage, " <" ARG_DELETEPORTMAPPING_NEWPROTOCOL_A ">");
strcat(pszMessage, ((pRegisteredPort->IsTCP()) ? "TCP" : "UDP"));
strcat(pszMessage, "</" ARG_DELETEPORTMAPPING_NEWPROTOCOL_A ">" EOL);
strcat(pszMessage, " </u:" ACTION_DELETEPORTMAPPING_A ">" EOL);
strcat(pszMessage, " </s:Body>" EOL);
strcat(pszMessage, "</s:Envelope>" 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 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();
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 (dwError != WSAEWOULDBLOCK)
{
#ifdef DBG
DPFX(DPFPREP, 0, "Couldn't connect socket, error = %u!", dwError);
#endif // DBG
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 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();
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 (dwError != WSAEWOULDBLOCK)
{
#ifdef DBG
DPFX(DPFPREP, 0, "Couldn't connect socket, error = %u!", dwError);
#endif // DBG
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. "<tag/>").
//
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 <?xml> 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