/*++ Copyright (c) 2001, Microsoft Corporation Module Name: cudpbcast.cpp Abstract: Implementation of CUdpBroadcastMapper -- support for mapping a public UDP port to the private network's broadcast address. Author: Jonathan Burstein (jonburs) 12 April 2001 Revision History: --*/ #include "precomp.h" #pragma hdrstop #define INADDR_LOOPBACK_NO 0x0100007f // 127.0.0.1 in network order // // ATL Methods // HRESULT CUdpBroadcastMapper::FinalConstruct() { HRESULT hr = S_OK; DWORD dwError; // // Create our UDP listening socket and obtain // its port. // dwError = NhCreateDatagramSocket( INADDR_LOOPBACK_NO, 0, &m_hsUdpListen ); if (ERROR_SUCCESS == dwError) { m_usUdpListenPort = NhQueryPortSocket(m_hsUdpListen); } else { hr = HRESULT_FROM_WIN32(dwError); } if (SUCCEEDED(hr)) { // // Create our raw UDP send socket // dwError = NhCreateRawDatagramSocket(&m_hsUdpRaw); if (ERROR_SUCCESS != dwError) { hr = HRESULT_FROM_WIN32(dwError); } } if (SUCCEEDED(hr)) { // // Obtain a handle to the NAT // dwError = NatOpenDriver(&m_hNat); if (ERROR_SUCCESS != dwError) { hr = HRESULT_FROM_WIN32(dwError); } } return hr; } HRESULT CUdpBroadcastMapper::FinalRelease() { if (INVALID_SOCKET != m_hsUdpListen) { closesocket(m_hsUdpListen); } if (INVALID_SOCKET != m_hsUdpRaw) { closesocket(m_hsUdpRaw); } if (NULL != m_hNat) { CloseHandle(m_hNat); } ASSERT(IsListEmpty(&m_MappingList)); return S_OK; } // // Initialization // HRESULT CUdpBroadcastMapper::Initialize( PCOMPONENT_REFERENCE pComponentReference ) { HRESULT hr = S_OK; if (NULL != pComponentReference) { m_pCompRef = pComponentReference; } else { hr = E_INVALIDARG; } return hr; } // // IUdpBroadcastMapper methods // STDMETHODIMP CUdpBroadcastMapper::CreateUdpBroadcastMapping( USHORT usPublicPort, DWORD dwPublicInterfaceIndex, ULONG ulDestinationAddress, VOID **ppvCookie ) { HRESULT hr = S_OK; CUdpBroadcast *pMapping; CUdpBroadcast *pDuplicate; PLIST_ENTRY pInsertionPoint; ULONG ulError; if (NULL != ppvCookie) { *ppvCookie = NULL; if (0 == usPublicPort || 0 == dwPublicInterfaceIndex || 0 == ulDestinationAddress) { hr = E_INVALIDARG; } else if (!m_fActive) { // // We've already been shutdown // hr = E_UNEXPECTED; } } else { hr = E_POINTER; } if (SUCCEEDED(hr)) { pMapping = new CUdpBroadcast( usPublicPort, dwPublicInterfaceIndex, ulDestinationAddress ); if (NULL == pMapping) { hr = E_OUTOFMEMORY; } } if (SUCCEEDED(hr)) { // // Check for duplicate and insert on list. It's OK for // the entry to be on the list before we've created the // dynamic redirect for it. // Lock(); pDuplicate = LookupMapping( usPublicPort, dwPublicInterfaceIndex, &pInsertionPoint ); if (NULL == pDuplicate) { InsertTailList(pInsertionPoint, &pMapping->Link); } else { delete pMapping; hr = HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS); } Unlock(); } if (SUCCEEDED(hr)) { // // Create the dynamic redirect for this entry // ulError = NatCreateDynamicAdapterRestrictedPortRedirect( NatRedirectFlagReceiveOnly, NAT_PROTOCOL_UDP, usPublicPort, INADDR_LOOPBACK_NO, m_usUdpListenPort, dwPublicInterfaceIndex, 0, &pMapping->hDynamicRedirect ); if (ERROR_SUCCESS != ulError) { hr = HRESULT_FROM_WIN32(ulError); Lock(); RemoveEntryList(&pMapping->Link); Unlock(); delete pMapping; } } if (SUCCEEDED(hr)) { // // Make sure that we've posted a read on our UDP socket // hr = StartUdpRead(); if (SUCCEEDED(hr)) { *ppvCookie = reinterpret_cast(pMapping); } else { NatCancelDynamicRedirect(pMapping->hDynamicRedirect); Lock(); RemoveEntryList(&pMapping->Link); Unlock(); delete pMapping; } } return hr; } STDMETHODIMP CUdpBroadcastMapper::CancelUdpBroadcastMapping( VOID *pvCookie ) { HRESULT hr = S_OK; CUdpBroadcast *pMapping; ULONG ulError; if (NULL != pvCookie) { pMapping = reinterpret_cast(pvCookie); Lock(); RemoveEntryList(&pMapping->Link); Unlock(); ASSERT(NULL != pMapping->hDynamicRedirect); ulError = NatCancelDynamicRedirect(pMapping->hDynamicRedirect); if (ERROR_SUCCESS != ulError) { hr = HRESULT_FROM_WIN32(ulError); } delete pMapping; } else { hr = E_INVALIDARG; } return hr; } STDMETHODIMP CUdpBroadcastMapper::Shutdown() { InterlockedExchange( reinterpret_cast(&m_fActive), FALSE ); // // We need to close our read socket handle so that any // pending reads complete. We don't need to close our // raw send socket since for that completion will never // be blocked waiting for an incoming packet. // Lock(); if (INVALID_SOCKET != m_hsUdpListen) { closesocket(m_hsUdpListen); m_hsUdpListen = INVALID_SOCKET; } Unlock(); return S_OK; } // // Protected methods // CUdpBroadcast* CUdpBroadcastMapper::LookupMapping( USHORT usPublicPort, DWORD dwInterfaceIndex, PLIST_ENTRY * ppInsertionPoint ) { PLIST_ENTRY pLink; CUdpBroadcast *pMapping; CUdpBroadcast *pMappingToReturn = NULL; // // The caller should have already locked the object before calling // this method to guarantee that what we return will still be // valid. However, we'll still grab the lock again to ensure that // it's safe to traverse the list. // Lock(); for (pLink = m_MappingList.Flink; pLink != &m_MappingList; pLink = pLink->Flink) { pMapping = CONTAINING_RECORD(pLink, CUdpBroadcast, Link); if (pMapping->usPublicPort < usPublicPort) { continue; } else if (pMapping->usPublicPort > usPublicPort) { break; } // // Primary key matches, check secondary key // if (pMapping->dwInterfaceIndex < dwInterfaceIndex) { continue; } else if (pMapping->dwInterfaceIndex > dwInterfaceIndex) { break; } // // Found it. // pMappingToReturn = pMapping; break; } Unlock(); if (NULL == pMappingToReturn && NULL != ppInsertionPoint) { *ppInsertionPoint = pLink; } return pMappingToReturn; } HRESULT CUdpBroadcastMapper::StartUdpRead() { HRESULT hr = S_OK; ULONG ulError; LONG fReadStarted; Lock(); if (!m_fReadStarted) { AddRef(); ulError = NhReadDatagramSocket( m_pCompRef, m_hsUdpListen, NULL, UdpReadCompletionRoutine, this, m_pCompRef ); if (ERROR_SUCCESS == ulError) { m_fReadStarted = TRUE; } else { hr = HRESULT_FROM_WIN32(ulError); Release(); } } Unlock(); return hr; } VOID CUdpBroadcastMapper::UdpReadCompletionRoutine( ULONG ulError, ULONG ulBytesTransferred, PNH_BUFFER pBuffer ) { CUdpBroadcastMapper *pMapper; PCOMPONENT_REFERENCE pCompRef; pMapper = reinterpret_cast(pBuffer->Context); pCompRef = reinterpret_cast(pBuffer->Context2); ASSERT(NULL != pMapper); ASSERT(NULL != pCompRef); // // Do the actual work // pMapper->ProcessUdpRead(ulError, ulBytesTransferred, pBuffer); // // Release the references obtained on both the object and // the component // pMapper->Release(); ReleaseComponentReference(pCompRef); } VOID CUdpBroadcastMapper::ProcessUdpRead( ULONG ulError, ULONG ulBytesTransferred, PNH_BUFFER pBuffer ) { NAT_KEY_SESSION_MAPPING_EX_INFORMATION MappingInfo; ULONG ulBufferSize; DWORD dwError; CUdpBroadcast *pMapping; ULONG ulDestinationAddress = 0; // // If an error occured check to see if we should repost // the read. If we're not active release the buffer // and exit. // if (ERROR_SUCCESS != ulError || !Active()) { Lock(); if (Active() && !NhIsFatalSocketError(ulError) && INVALID_SOCKET != m_hsUdpListen) { AddRef(); dwError = NhReadDatagramSocket( m_pCompRef, m_hsUdpListen, pBuffer, UdpReadCompletionRoutine, this, m_pCompRef ); if (ERROR_SUCCESS != dwError) { Release(); NhReleaseBuffer(pBuffer); } } else { NhReleaseBuffer(pBuffer); } Unlock(); return; } // // Lookup the original destination address for this packet. // ulBufferSize = sizeof(MappingInfo); dwError = NatLookupAndQueryInformationSessionMapping( m_hNat, NAT_PROTOCOL_UDP, INADDR_LOOPBACK_NO, m_usUdpListenPort, pBuffer->ReadAddress.sin_addr.s_addr, pBuffer->ReadAddress.sin_port, &MappingInfo, &ulBufferSize, NatKeySessionMappingExInformation ); if (ERROR_SUCCESS == dwError) { // // See if we have a port mapping for the original destination, // and, if so, get the destination address // Lock(); pMapping = LookupMapping( MappingInfo.DestinationPort, MappingInfo.AdapterIndex, NULL ); if (NULL != pMapping) { ulDestinationAddress = pMapping->ulDestinationAddress; } Unlock(); } if (0 != ulDestinationAddress) { // // Construct the new packet and send it on its way // BuildAndSendRawUdpPacket( ulDestinationAddress, MappingInfo.DestinationPort, pBuffer ); } // // If we're still active repost the read, otherwise free the // buffer. // Lock(); if (Active() && INVALID_SOCKET != m_hsUdpListen) { AddRef(); dwError = NhReadDatagramSocket( m_pCompRef, m_hsUdpListen, pBuffer, UdpReadCompletionRoutine, this, m_pCompRef ); if (ERROR_SUCCESS != dwError) { Release(); NhReleaseBuffer(pBuffer); } } else { NhReleaseBuffer(pBuffer); } Unlock(); } HRESULT CUdpBroadcastMapper::BuildAndSendRawUdpPacket( ULONG ulDestinationAddress, USHORT usDestinationPort, PNH_BUFFER pPacketData ) { HRESULT hr = S_OK; PNH_BUFFER pBuffer; ULONG ulPacketSize; PIP_HEADER pIpHeader; UDP_HEADER UNALIGNED *pUdpHeader; PUCHAR pucData; DWORD dwError; // // Allocate a buffer large enough for the headers and packet data // ulPacketSize = sizeof(IP_HEADER) + sizeof(UDP_HEADER) + pPacketData->BytesTransferred; pBuffer = NhAcquireVariableLengthBuffer(ulPacketSize); if (NULL != pBuffer) { // // Locate offsets w/in the buffer // pIpHeader = reinterpret_cast(pBuffer->Buffer); pUdpHeader = reinterpret_cast(pBuffer->Buffer + sizeof(IP_HEADER)); pucData = pBuffer->Buffer + sizeof(IP_HEADER) + sizeof(UDP_HEADER); // // Copy over the packet data // CopyMemory(pucData, pPacketData->Buffer, pPacketData->BytesTransferred); // // Fill out the IP header // pIpHeader->VersionAndHeaderLength = (4 << 4) | (sizeof(IP_HEADER) / sizeof(ULONG)); pIpHeader->TypeOfService = 0; pIpHeader->TotalLength = htons(static_cast(ulPacketSize)); pIpHeader->Identification = htons(++m_usIpId); pIpHeader->OffsetAndFlags = 0; pIpHeader->TimeToLive = 128; pIpHeader->Protocol = NAT_PROTOCOL_UDP; pIpHeader->Checksum = 0; pIpHeader->SourceAddress = pPacketData->ReadAddress.sin_addr.s_addr; pIpHeader->DestinationAddress = ulDestinationAddress; // // Fill out the UDP header // pUdpHeader->SourcePort = pPacketData->ReadAddress.sin_port; pUdpHeader->DestinationPort = usDestinationPort; pUdpHeader->Length = htons( static_cast( sizeof(UDP_HEADER) + pPacketData->BytesTransferred ) ); pUdpHeader->Checksum = 0; // // Send the buffer on its way // AddRef(); dwError = NhWriteDatagramSocket( m_pCompRef, m_hsUdpRaw, ulDestinationAddress, usDestinationPort, pBuffer, ulPacketSize, RawWriteCompletionRoutine, this, m_pCompRef ); if (ERROR_SUCCESS != dwError) { Release(); NhReleaseBuffer(pBuffer); hr = HRESULT_FROM_WIN32(dwError); } } else { hr = E_OUTOFMEMORY; } return hr; } VOID CUdpBroadcastMapper::RawWriteCompletionRoutine( ULONG ulError, ULONG ulBytesTransferred, PNH_BUFFER pBuffer ) { CUdpBroadcastMapper *pMapper; PCOMPONENT_REFERENCE pCompRef; pMapper = reinterpret_cast(pBuffer->Context); pCompRef = reinterpret_cast(pBuffer->Context2); ASSERT(NULL != pMapper); ASSERT(NULL != pCompRef); // // Free the passed-in buffer // NhReleaseBuffer(pBuffer); // // Release the references obtained on both the object and // the component // pMapper->Release(); ReleaseComponentReference(pCompRef); }