/*++ Copyright (c) 1998, Microsoft Corporation Module Name: natconn.c Abstract: This module contains code for the NAT's RAS connection management. This includes * code to support 'shared-access', in which a RAS client-connection serves as the NAT public network. * code to support 'on-demand dialing', in which a routing-failure results in our attempting to establish a dialup connection with the help of the autodial service. Author: Abolade Gbadegesin (aboladeg) 2-May-1998 Revision History: Jonathan Burstein (jonburs) 6-July-2000 Updated to new config APIs --*/ #include "precomp.h" #pragma hdrstop #include #include #include #include #include "beacon.h" // // EXTERNAL DECLARATIONS // extern "C" ULONG APIENTRY RasGetEntryHrasconnW( LPCWSTR Phonebook, LPCWSTR EntryName, LPHRASCONN Hrasconn ); extern "C" ULONG SetIpForwardEntryToStack( PMIB_IPFORWARDROW IpForwardRow ); extern "C" ULONG NhpAllocateAndGetInterfaceInfoFromStack( IP_INTERFACE_NAME_INFO** Table, PULONG Count, BOOL SortOutput, HANDLE AllocationHeap, ULONG AllocationFlags ); // // Notifications // HANDLE NatConfigurationChangedEvent = NULL; HANDLE NatpConfigurationChangedWaitHandle = NULL; HANDLE NatConnectionNotifyEvent = NULL; HANDLE NatpConnectionNotifyWaitHandle = NULL; HANDLE NatpEnableRouterEvent = NULL; OVERLAPPED NatpEnableRouterOverlapped; HANDLE NatpEnableRouterWaitHandle = NULL; IO_STATUS_BLOCK NatpRoutingFailureIoStatus; IP_NAT_ROUTING_FAILURE_NOTIFICATION NatpRoutingFailureNotification; // // Connection information // LIST_ENTRY NatpConnectionList = {NULL, NULL}; ULONG NatpFirewallConnectionCount = 0; BOOLEAN NatpSharedConnectionPresent = FALSE; PCHAR NatpSharedConnectionDomainName = NULL; LONG NatpNextInterfaceIndex = 1; #define INADDR_LOOPBACK_NO 0x0100007f // 127.0.0.1 in network order // // FORWARD DECLARATIONS // HRESULT NatpAddConnectionEntry( IUnknown *pUnk ); ULONG NatpBindConnection( PNAT_CONNECTION_ENTRY pConEntry, HRASCONN Hrasconn, ULONG AdapterIndex OPTIONAL, PIP_ADAPTER_BINDING_INFO BindingInfo OPTIONAL ); HRESULT NatpBuildPortMappingList( PNAT_CONNECTION_ENTRY pConEntry, PIP_ADAPTER_BINDING_INFO pBindingInfo ); VOID NTAPI NatpConfigurationChangedCallbackRoutine( PVOID Context, BOOLEAN TimedOut ); VOID NTAPI NatpConnectionNotifyCallbackRoutine( PVOID Context, BOOLEAN TimedOut ); VOID NTAPI NatpEnableRouterCallbackRoutine( PVOID Context, BOOLEAN TimedOut ); VOID NatpFreeConnectionEntry( PNAT_CONNECTION_ENTRY pConEntry ); VOID NatpFreePortMappingList( PNAT_CONNECTION_ENTRY pConEntry ); PNAT_INTERFACE NatpLookupInterface( ULONG Index, OUT PLIST_ENTRY* InsertionPoint OPTIONAL ); ULONG NatpQueryConnectionAdapter( ULONG Index ); PIP_NAT_INTERFACE_INFO NatpQueryConnectionInformation( PNAT_CONNECTION_ENTRY pConEntry, PIP_ADAPTER_BINDING_INFO BindingInfo ); VOID NatpProcessConfigurationChanged( VOID ); VOID NatpProcessConnectionNotify( VOID ); VOID NTAPI NatpRoutingFailureCallbackRoutine( PVOID Context, PIO_STATUS_BLOCK IoStatus, ULONG Reserved ); VOID NTAPI NatpRoutingFailureWorkerRoutine( PVOID Context ); ULONG NatpStartSharedConnectionManagement( VOID ); ULONG NatpStopSharedConnectionManagement( VOID ); VOID NatpUpdateSharedConnectionDomainName( ULONG AdapterIndex ); BOOLEAN NatpUnbindConnection( PNAT_CONNECTION_ENTRY pConEntry ); PNAT_CONNECTION_ENTRY NatFindConnectionEntry( GUID *pGuid ) /*++ Routine Description: Locates a connection entry by guid Arguments: pGuid - identifies the connection to locate Return Value: PNAT_CONNECTION_ENTRY - a pointer to the connection, or NULL if not found Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { PNAT_CONNECTION_ENTRY pConnection; PLIST_ENTRY pLink; for (pLink = NatpConnectionList.Flink; pLink != &NatpConnectionList; pLink = pLink->Flink) { pConnection = CONTAINING_RECORD(pLink, NAT_CONNECTION_ENTRY, Link); if (IsEqualGUID(pConnection->Guid, *pGuid)) { return pConnection; } } return NULL; } // NatFindConnectionEntry PNAT_PORT_MAPPING_ENTRY NatFindPortMappingEntry( PNAT_CONNECTION_ENTRY pConnection, GUID *pGuid ) /*++ Routine Description: Locates a port mapping entry for a connection Arguments: pConnection - the connection to search pGuid - identifies the port mapping entry to locate Return Value: PNAT_PORT_MAPPING_ENTRY - a pointer to the port mapping, or NULL if not found Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { PNAT_PORT_MAPPING_ENTRY pMapping; PLIST_ENTRY pLink; for (pLink = pConnection->PortMappingList.Flink; pLink != &pConnection->PortMappingList; pLink = pLink->Flink) { pMapping = CONTAINING_RECORD(pLink, NAT_PORT_MAPPING_ENTRY, Link); if (IsEqualGUID(*pMapping->pProtocolGuid, *pGuid)) { return pMapping; } } return NULL; } // NatFindPortMappingEntry VOID NatFreePortMappingEntry( PNAT_PORT_MAPPING_ENTRY pEntry ) /*++ Routine Description: Frees all resources associated with a port mapping entry. This entry must have already been removed from the containing port mapping list and destroyed at the kernel / UDP broadcast mapper level. Arguments: pEntry - the entry to free Return Value: none. --*/ { ASSERT(NULL != pEntry); if (NULL != pEntry->pProtocolGuid) { CoTaskMemFree(pEntry->pProtocolGuid); } if (NULL != pEntry->pProtocol) { pEntry->pProtocol->Release(); } if (NULL != pEntry->pBinding) { pEntry->pBinding->Release(); } NH_FREE(pEntry); } // NatFreePortMappingEntry HRESULT NatpAddConnectionEntry( IUnknown *pUnk ) /*++ Routine Description: Creates a NAT_CONNECTION_ENTRY for a firewalled or Ics public connection. Arguments: pUnk - pointer to an IHNetFirewalledConnection or IHNetIcsPublicConnection. This need not be the canonical IUnknown (i.e., it's fine to pass in a pointer of either of the above interfaces). Return Value: Standard HRESULT Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { HRESULT hr = S_OK; PNAT_CONNECTION_ENTRY pNewEntry = NULL; IHNetConnection *pNetCon = NULL; // // Allocate new entry stucture // pNewEntry = reinterpret_cast( NH_ALLOCATE(sizeof(*pNewEntry)) ); if (NULL != pNewEntry) { RtlZeroMemory(pNewEntry, sizeof(*pNewEntry)); InitializeListHead(&pNewEntry->Link); InitializeListHead(&pNewEntry->PortMappingList); } else { hr = E_OUTOFMEMORY; } // // Get IHNetConnection interface // if (S_OK == hr) { hr = pUnk->QueryInterface(IID_PPV_ARG(IHNetConnection, &pNetCon)); if (SUCCEEDED(hr)) { pNewEntry->pHNetConnection = pNetCon; pNewEntry->pHNetConnection->AddRef(); HNET_CONN_PROPERTIES *pProps; // // Get the properties for the connection // hr = pNetCon->GetProperties(&pProps); if (SUCCEEDED(hr)) { // // Copy properties into entry // RtlCopyMemory( &pNewEntry->HNetProperties, pProps, sizeof(*pProps) ); CoTaskMemFree(pProps); } } if (SUCCEEDED(hr)) { GUID *pGuid; // // Get the guid of the connectoin // hr = pNetCon->GetGuid(&pGuid); if (SUCCEEDED(hr)) { RtlCopyMemory(&pNewEntry->Guid, pGuid, sizeof(GUID)); CoTaskMemFree(pGuid); } } if (SUCCEEDED(hr) && !pNewEntry->HNetProperties.fLanConnection) { // // Get the RAS phonebook path. We don't cache the // name since that can change over time. // hr = pNetCon->GetRasPhonebookPath( &pNewEntry->wszPhonebookPath ); } } if (SUCCEEDED(hr) && pNewEntry->HNetProperties.fFirewalled) { // // Get the firewall control interface // hr = pNetCon->GetControlInterface( IID_PPV_ARG(IHNetFirewalledConnection, &pNewEntry->pHNetFwConnection) ); if (SUCCEEDED(hr)) { NatpFirewallConnectionCount += 1; } } if (SUCCEEDED(hr) && pNewEntry->HNetProperties.fIcsPublic) { // // Get the ICS public control interface // hr = pNetCon->GetControlInterface( IID_PPV_ARG(IHNetIcsPublicConnection, &pNewEntry->pHNetIcsPublicConnection) ); if (SUCCEEDED(hr)) { // // Remember that we now have a shared connection // NatpSharedConnectionPresent = TRUE; } } if (NULL != pNetCon) { pNetCon->Release(); } if (SUCCEEDED(hr)) { // // Add the new entry to the connection list. Ordering doesn't matter. // InsertTailList(&NatpConnectionList, &pNewEntry->Link); } else if (NULL != pNewEntry) { // // Cleanup the partially constructed entry // NatpFreeConnectionEntry(pNewEntry); } return hr; } ULONG NatpBindConnection( PNAT_CONNECTION_ENTRY pConEntry, HRASCONN Hrasconn, ULONG AdapterIndex, PIP_ADAPTER_BINDING_INFO BindingInfo ) /*++ Routine Description: This routine is responsible for binding the shared-connection. Arguments: pConEntry - the entry to bind Hrasconn - if the connection is a dialup connection, contains the handle for the active RAS connection. AdapterIndex - if the connection is a LAN connection, contains the adapter index for the active LAN connection. BindingInfo - if the connection is a LAN connection, contains the binding information for the active LAN interface. Return Value: ULONG - Win32 error. Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { ULONG Error; MIB_IPFORWARDROW IpForwardRow; GUID Guid; RASPPPIPA RasPppIp; ULONG Size; PLIST_ENTRY InsertionPoint; PLIST_ENTRY Link; PNAT_PORT_MAPPING_ENTRY PortMapping; HRESULT hr; if (NAT_INTERFACE_BOUND(&pConEntry->Interface)) { return NO_ERROR; } // // LAN public interfaces are handled differently than RAS public interfaces. // With a LAN interface, the binding information is passed in from // 'NatpProcessConnectionNotify'. // With a RAS inteface, though, we retrieve the projection-information // for the active connection, and map the address to an adapter index. // if (!pConEntry->HNetProperties.fLanConnection) { // // Allocate space for the binding info, if this has not yet // occured. (This memory will be freed in NatpFreeConnectionEntry.) // if (NULL == pConEntry->pBindingInfo) { pConEntry->pBindingInfo = reinterpret_cast( NH_ALLOCATE( FIELD_OFFSET(IP_ADAPTER_BINDING_INFO, Address) + sizeof(IP_LOCAL_BINDING) ) ); if (NULL == pConEntry->pBindingInfo) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection: Unable to allocate binding info" ); return ERROR_NOT_ENOUGH_MEMORY; } } // // Retrieve the PPP projection information for the interface. // ZeroMemory(&RasPppIp, sizeof(RasPppIp)); Size = RasPppIp.dwSize = sizeof(RasPppIp); Error = RasGetProjectionInfoA( Hrasconn, RASP_PppIp, &RasPppIp, &Size ); if (Error) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection: RasGetProjectionInfoA=%d", Error ); return Error; } // // Convert the projection information to our format // BindingInfo = pConEntry->pBindingInfo; BindingInfo->AddressCount = 1; BindingInfo->RemoteAddress = 0; BindingInfo->Address[0].Address = inet_addr(RasPppIp.szIpAddress); BindingInfo->Address[0].Mask = 0xffffffff; // // Attempt to find the TCP/IP adapter index for the connection // AdapterIndex = NhMapAddressToAdapter(BindingInfo->Address[0].Address); if (AdapterIndex == (ULONG)-1) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection: MapAddressToAdapter failed" ); return ERROR_NO_SUCH_INTERFACE; } // // Install a default route through the interface, if this is // the shared connection. (We don't want to do this for a // firewall-only connection.) // if (pConEntry->HNetProperties.fIcsPublic) { ZeroMemory(&IpForwardRow, sizeof(IpForwardRow)); IpForwardRow.dwForwardNextHop = BindingInfo->Address[0].Address; IpForwardRow.dwForwardIfIndex = AdapterIndex; IpForwardRow.dwForwardType = MIB_IPROUTE_TYPE_DIRECT; IpForwardRow.dwForwardProto = PROTO_IP_NAT; IpForwardRow.dwForwardMetric1 = 1; Error = SetIpForwardEntryToStack(&IpForwardRow); if (Error) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection: SetIpForwardEntryToStack=%d", Error ); return Error; } } } pConEntry->AdapterIndex = AdapterIndex; // // Make sure the interface type is correct. // pConEntry->Interface.Type = ROUTER_IF_TYPE_INTERNAL; // // Set the interface index value. This can be anything except 0 // (as 0 is reserved for the private connection). // do { pConEntry->Interface.Index = static_cast(InterlockedIncrement(&NatpNextInterfaceIndex)); } while (0 == pConEntry->Interface.Index); // // Build the port mapping list for this connection // hr = NatpBuildPortMappingList(pConEntry, BindingInfo); if (FAILED(hr)) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection: NatpBuildPortMappingList=0x%08x", hr ); return ERROR_CAN_NOT_COMPLETE; } // // Bind the interface, building its configuration to include // any port-mappings configured as part of shared access settings. // pConEntry->Interface.Info = NatpQueryConnectionInformation(pConEntry, BindingInfo); if (NULL == pConEntry->Interface.Info) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection[%i]: NatpQueryConnectionInformation failed", pConEntry->Interface.Index ); // // Free the port mapping list // NatpFreePortMappingList(pConEntry); return ERROR_CAN_NOT_COMPLETE; } Error = NatBindInterface( pConEntry->Interface.Index, &pConEntry->Interface, BindingInfo, AdapterIndex ); if (Error) { NhTrace( TRACE_FLAG_NAT, "NatpBindConnection[%i]: NatBindInterface=%d", pConEntry->Interface.Index, Error ); // // Free the port mapping list // NatpFreePortMappingList(pConEntry); return Error; } // // At this point NAT_INTERFACE_FLAG_BOUND has been set on the // interface, so we don't need to clean up the port mapping // list on error, as the list will be cleaned up in // NatpUnbindConnection. // // // Create UDP broadcast mappings if this is the ICS // public connection. // if (pConEntry->HNetProperties.fIcsPublic && 0 != pConEntry->UdpBroadcastPortMappingCount) { DWORD dwAddress; DWORD dwMask; DWORD dwBroadcastAddress; ASSERT(NULL != NhpUdpBroadcastMapper); ASSERT(!IsListEmpty(&pConEntry->PortMappingList)); if (NhQueryScopeInformation(&dwAddress, &dwMask)) { dwBroadcastAddress = (dwAddress & dwMask) | ~dwMask; for (Link = pConEntry->PortMappingList.Flink; Link != &pConEntry->PortMappingList; Link = Link->Flink) { PortMapping = CONTAINING_RECORD(Link, NAT_PORT_MAPPING_ENTRY, Link); if (!PortMapping->fUdpBroadcastMapping) { continue; } hr = NhpUdpBroadcastMapper->CreateUdpBroadcastMapping( PortMapping->usPublicPort, AdapterIndex, dwBroadcastAddress, &PortMapping->pvBroadcastCookie ); if (FAILED(hr)) { // // We'll continue if an error occurs here. // NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: CreateUdpBroadcastMapping=0x%08x", hr ); } } } } // // Make sure that the interface is on the global list (so that the // FTP, ALG, and H.323 proxies will be able to find its configuration). // if (!NatpLookupInterface( pConEntry->Interface.Index, &InsertionPoint )) { InsertTailList(InsertionPoint, &pConEntry->Interface.Link); } #ifndef NO_FTP_PROXY // // Add the interface the the FTP proxy, if this has not yet // happened. // if (!NAT_INTERFACE_ADDED_FTP(&pConEntry->Interface)) { Error = FtpRmAddInterface( NULL, pConEntry->Interface.Index, PERMANENT, IF_TYPE_OTHER, IF_ACCESS_BROADCAST, IF_CONNECTION_DEDICATED, NULL, IP_NAT_VERSION, 0, 0 ); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: FtpRmAddInterface=%d", Error ); return Error; } pConEntry->Interface.Flags |= NAT_INTERFACE_FLAG_ADDED_FTP; } // // Bind and enable the interface for FTP // Error = FtpRmBindInterface(pConEntry->Interface.Index, BindingInfo); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: FtpRmBindInterface=%d", Error ); return Error; } Error = FtpRmEnableInterface(pConEntry->Interface.Index); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: FtpRmEnableInterface=%d", Error ); return Error; } #endif // // Add the interface the the ALG proxy, if this has not yet // happened. // if (!NAT_INTERFACE_ADDED_ALG(&pConEntry->Interface)) { Error = AlgRmAddInterface( NULL, pConEntry->Interface.Index, PERMANENT, IF_TYPE_OTHER, IF_ACCESS_BROADCAST, IF_CONNECTION_DEDICATED, NULL, IP_NAT_VERSION, 0, 0 ); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: AlgRmAddInterface=%d", Error ); return Error; } pConEntry->Interface.Flags |= NAT_INTERFACE_FLAG_ADDED_ALG; } // // Bind and enable the interface for ALG // Error = AlgRmBindInterface(pConEntry->Interface.Index, BindingInfo); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: AlgRmBindInterface=%d", Error ); return Error; } Error = AlgRmEnableInterface(pConEntry->Interface.Index); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: AlgRmEnableInterface=%d", Error ); return Error; } // // Add the interface the the H.323 proxy, if this has not yet // happened. // if (!NAT_INTERFACE_ADDED_H323(&pConEntry->Interface)) { Error = H323RmAddInterface( NULL, pConEntry->Interface.Index, PERMANENT, IF_TYPE_OTHER, IF_ACCESS_BROADCAST, IF_CONNECTION_DEDICATED, NULL, IP_NAT_VERSION, 0, 0 ); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: H323RmAddInterface=%d", Error ); return Error; } pConEntry->Interface.Flags |= NAT_INTERFACE_FLAG_ADDED_H323; } // // Bind and enable the interface for H323 // Error = H323RmBindInterface(pConEntry->Interface.Index, BindingInfo); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: H323RmBindInterface=%d", Error ); return Error; } Error = H323RmEnableInterface(pConEntry->Interface.Index); if (Error) { NhTrace( TRACE_FLAG_INIT, "NatpBindConnection: H323RmEnableInterface=%d", Error ); return Error; } if (pConEntry->HNetProperties.fIcsPublic) { // // Finally, update the DNS domain name cached for the shared connection. // NatpUpdateSharedConnectionDomainName(AdapterIndex); } return NO_ERROR; } // NatpBindConnection HRESULT NatpBuildPortMappingList( PNAT_CONNECTION_ENTRY pConEntry, PIP_ADAPTER_BINDING_INFO pBindingInfo ) /*++ Routine Description: Builds the list of port mappings for a connection entry Arguments: pConEntry - the entry to build the list for pBindingInfo - the binding info for that entry Return Value: Standard HRESULT. Environment: NatInterfaceLock must be held by the caller. --*/ { HRESULT hr; IHNetPortMappingBinding *pBinding; PNAT_PORT_MAPPING_ENTRY pEntry; IEnumHNetPortMappingBindings *pEnum; PLIST_ENTRY pLink; IHNetPortMappingProtocol *pProtocol; ULONG ulCount; PROFILE("NatpBuildPortMappingList"); hr = pConEntry->pHNetConnection->EnumPortMappings(TRUE, &pEnum); if (FAILED(hr)) { NhTrace( TRACE_FLAG_NAT, "NatpBuildPortMappingList: EnumPortMappings 0x%08x", hr ); return hr; } // // Process enumeration, creating the port mapping entries. // do { hr = pEnum->Next(1, &pBinding, &ulCount); if (SUCCEEDED(hr) && 1 == ulCount) { pEntry = reinterpret_cast( NH_ALLOCATE(sizeof(*pEntry)) ); if (NULL != pEntry) { ZeroMemory(pEntry, sizeof(*pEntry)); // // Get the protocol for the binding // hr = pBinding->GetProtocol(&pProtocol); } else { hr = E_OUTOFMEMORY; } if (SUCCEEDED(hr)) { // // Fill out the entry // hr = pProtocol->GetGuid(&pEntry->pProtocolGuid); if (SUCCEEDED(hr)) { hr = pProtocol->GetIPProtocol(&pEntry->ucProtocol); } if (SUCCEEDED(hr)) { hr = pProtocol->GetPort(&pEntry->usPublicPort); } if (SUCCEEDED(hr)) { hr = pBinding->GetTargetPort(&pEntry->usPrivatePort); } if (SUCCEEDED(hr)) { // // We need to know if the name is active in order to // avoid rebuilding the DHCP reservation list more // than necessary. // hr = pBinding->GetCurrentMethod(&pEntry->fNameActive); } if (SUCCEEDED(hr)) { // // If this is a FW-only connection, use the address from // our binding info instead of the protocol binding. // if (!pConEntry->HNetProperties.fIcsPublic) { pEntry->ulPrivateAddress = pBindingInfo->Address[0].Address; } else { hr = pBinding->GetTargetComputerAddress( &pEntry->ulPrivateAddress ); if (SUCCEEDED(hr) && INADDR_LOOPBACK_NO == pEntry->ulPrivateAddress) { // // If the port mapping targets the loopback address // we want to use the address from the binding // info instead. // pEntry->ulPrivateAddress = pBindingInfo->Address[0].Address; } } } if (SUCCEEDED(hr)) { pEntry->pBinding = pBinding; pEntry->pBinding->AddRef(); pEntry->pProtocol = pProtocol; pEntry->pProtocol->AddRef(); // // Check to see if this mapping is: // 1) targeted at the broadcast address, and // 2) is UDP. // if (NAT_PROTOCOL_UDP == pEntry->ucProtocol && 0xffffffff == pEntry->ulPrivateAddress) { pEntry->fUdpBroadcastMapping = TRUE; pConEntry->UdpBroadcastPortMappingCount += 1; } else { pConEntry->PortMappingCount += 1; } InsertTailList(&pConEntry->PortMappingList, &pEntry->Link); } else { NatFreePortMappingEntry(pEntry); } pProtocol->Release(); } // // If anything failed above we still want to continue operation -- // it's preferable to have the firewall running w/ some port // mapping entries missing instead of not having the firewall // run at all. // hr = S_OK; pBinding->Release(); } } while (SUCCEEDED(hr) && 1 == ulCount); pEnum->Release(); if (FAILED(hr)) { // // Free the port mapping list // NatpFreePortMappingList(pConEntry); } return hr; }// NatpBuildPortMappingList VOID NTAPI NatpConfigurationChangedCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is invoked upon a change in the NAT/Firewall configuration. It may also be invoked when cleanup is in progress. Arguments: Context - unused TimedOut - unused Return Value: none. Environment: The routine runs in the context of an Rtl wait-thread. (See 'RtlRegisterWait'.) A reference to the component will have been made on our behalf when 'RtlRegisterWait' was called. The reference is released and re-acquired here. --*/ { BOOLEAN ComInitialized = TRUE; HRESULT hr; PROFILE("NatpConfigurationChangedCallbackRoutine"); // // See whether cleanup has occurred // EnterCriticalSection(&NatInterfaceLock); if (!NatConfigurationChangedEvent) { LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); return; } LeaveCriticalSection(&NatInterfaceLock); // // Acquire a new reference to the component (and release // our original reference on failure). // if (!REFERENCE_NAT()) { DEREFERENCE_NAT(); return; } // // Make sure the thread is COM-initialized // hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE ); if (FAILED(hr)) { ComInitialized = FALSE; if (RPC_E_CHANGED_MODE == hr) { ASSERT(FALSE); hr = S_OK; NhTrace( TRACE_FLAG_NAT, "NatpConfigurationChangedCallbackRoutine: Unexpectedly in STA." ); } } // // Process connection notifications // if (SUCCEEDED(hr)) { NatpProcessConfigurationChanged(); } // // Uninitialize COM, if necessary // if (TRUE == ComInitialized) { CoUninitialize(); } // // Release our original reference to the component. // DEREFERENCE_NAT(); } // NatpConfigurationChangedCallbackRoutine VOID NTAPI NatpConnectionNotifyCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is invoked upon connection or disconnection of a RAS phonebook entry. It may also be invoked when cleanup is in progress. Arguments: Context - unused TimedOut - unused Return Value: none. Environment: The routine runs in the context of an Rtl wait-thread. (See 'RtlRegisterWait'.) A reference to the component will have been made on our behalf when 'RtlRegisterWait' was called. The reference is released and re-acquired here. --*/ { BOOLEAN ComInitialized = TRUE; HRESULT hr; PROFILE("NatpConnectionNotifyCallbackRoutine"); // // See whether cleanup has occurred // EnterCriticalSection(&NatInterfaceLock); if (!NatConnectionNotifyEvent) { LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); return; } LeaveCriticalSection(&NatInterfaceLock); // // Acquire a new reference to the component (and release // our original reference on failure). // if (!REFERENCE_NAT()) { DEREFERENCE_NAT(); return; } // // Make sure the thread is COM-initialized // hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE ); if (FAILED(hr)) { ComInitialized = FALSE; if (RPC_E_CHANGED_MODE == hr) { ASSERT(FALSE); hr = S_OK; NhTrace( TRACE_FLAG_NAT, "NatpConnectionNotifyCallbackRoutine: Unexpectedly in STA." ); } } // // Process connection notifications // if (SUCCEEDED(hr)) { NatpProcessConnectionNotify(); } // // Uninitialize COM, if necessary // if (TRUE == ComInitialized) { CoUninitialize(); } // // Release our original reference to the component. // DEREFERENCE_NAT(); } // NatpConnectionNotifyCallbackRoutine VOID NTAPI NatpEnableRouterCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is invoked upon completion or cancellation of an outstanding request to enable IP forwarding. It determines whether the module is still running and, if so, re-enables forwarding. Otherwise, it cancels any existing request and returns control immediately. Arguments: none used. Return Value: none. Environment: The routine runs in the context of an Rtl wait-thread. (See 'RtlRegisterWait'.) A reference to the component will have been made on our behalf when 'RtlRegisterWait' was called. The reference is released and re-acquired here. --*/ { ULONG Error; HANDLE UnusedHandle; PROFILE("NatpEnableRouterCallbackRoutine"); // // See whether cleanup has occurred and, if so, restore forwarding // to its original setting. Otherwise, acquire a new reference to the // component, and release the original reference. // EnterCriticalSection(&NatInterfaceLock); if (!NatpEnableRouterEvent || !REFERENCE_NAT()) { UnenableRouter(&NatpEnableRouterOverlapped, NULL); LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); return; } DEREFERENCE_NAT(); // // Re-enable forwarding // ZeroMemory(&NatpEnableRouterOverlapped, sizeof(OVERLAPPED)); NatpEnableRouterOverlapped.hEvent = NatpEnableRouterEvent; Error = EnableRouter(&UnusedHandle, &NatpEnableRouterOverlapped); if (Error != ERROR_IO_PENDING) { NhTrace( TRACE_FLAG_NAT, "NatpEnableRouterCallbackRoutine: EnableRouter=%d", Error ); } LeaveCriticalSection(&NatInterfaceLock); } // NatpEnableRouterCallbackRoutine VOID NatpFreeConnectionEntry( PNAT_CONNECTION_ENTRY pConEntry ) /*++ Routine Description: Frees all resources associated with a connection entry. This entry must have already been removed from the connection list. Arguments: pConEntry - the entry to free Return Value: none. --*/ { PROFILE("NatpFreeConnectionEntry"); if (NULL != pConEntry->pInterfaceInfo) { NH_FREE(pConEntry->pInterfaceInfo); } if (NULL != pConEntry->pBindingInfo) { NH_FREE(pConEntry->pBindingInfo); } if (NULL != pConEntry->pHNetConnection) { pConEntry->pHNetConnection->Release(); } if (NULL != pConEntry->pHNetFwConnection) { pConEntry->pHNetFwConnection->Release(); } if (NULL != pConEntry->pHNetIcsPublicConnection) { pConEntry->pHNetIcsPublicConnection->Release(); } if (NULL != pConEntry->wszPhonebookPath) { CoTaskMemFree(pConEntry->wszPhonebookPath); } NatpFreePortMappingList(pConEntry); NH_FREE(pConEntry); } // NatpFreeConnectionEntry VOID NatpFreePortMappingList( PNAT_CONNECTION_ENTRY pConEntry ) /*++ Routine Description: Frees the port mapping list for a connection entry. This includes cancelling any active UDP broadcast mappings. Arguments: pConEntry - the entry to free Return Value: none. Environment: Invoked w/ NatInterfaceLock held by the caller --*/ { PLIST_ENTRY pLink; PNAT_PORT_MAPPING_ENTRY pMapping; while (!IsListEmpty(&pConEntry->PortMappingList)) { pLink = RemoveHeadList(&pConEntry->PortMappingList); pMapping = CONTAINING_RECORD(pLink, NAT_PORT_MAPPING_ENTRY, Link); if (pMapping->fUdpBroadcastMapping && NULL != pMapping->pvBroadcastCookie) { ASSERT(NULL != NhpUdpBroadcastMapper); NhpUdpBroadcastMapper->CancelUdpBroadcastMapping( pMapping->pvBroadcastCookie ); } NatFreePortMappingEntry(pMapping); } pConEntry->PortMappingCount = 0; pConEntry->UdpBroadcastPortMappingCount = 0; } // NatpFreePortMappingList VOID NatpProcessConfigurationChanged( VOID ) /*++ Routine Description: This routine is invoked to see when the NAT/Firewall configuration changes. It unbinds the old interfaces, and binds the new ones. It is also responsible for making sure that the autodial service is running. Arguments: none. Return Value: none. --*/ { PLIST_ENTRY Link; PNAT_CONNECTION_ENTRY pConEntry; HRESULT hr; IHNetCfgMgr *pCfgMgr = NULL; IHNetFirewallSettings *pFwSettings; IHNetIcsSettings *pIcsSettings; IEnumHNetFirewalledConnections *pFwEnum; IHNetFirewalledConnection *pFwConn; IEnumHNetIcsPublicConnections *pIcsEnum; IHNetIcsPublicConnection *pIcsConn; ULONG ulCount; UNICODE_STRING UnicodeString; PROFILE("NatpProcessConfigurationChanged"); EnterCriticalSection(&NatInterfaceLock); // // Start by deleting all of our current connections // while (!IsListEmpty(&NatpConnectionList)) { Link = RemoveHeadList(&NatpConnectionList); pConEntry = CONTAINING_RECORD(Link, NAT_CONNECTION_ENTRY, Link); NatpUnbindConnection(pConEntry); NatpFreeConnectionEntry(pConEntry); } // // Reset other items to initial state // NatpFirewallConnectionCount = 0; NatpSharedConnectionPresent = FALSE; if (NULL != NatpSharedConnectionDomainName) { NH_FREE(NatpSharedConnectionDomainName); NatpSharedConnectionDomainName = NULL; } // // Get the configuration manager // hr = NhGetHNetCfgMgr(&pCfgMgr); if (NhPolicyAllowsFirewall) { if (SUCCEEDED(hr)) { // // Get the firewall settings interface // hr = pCfgMgr->QueryInterface( IID_PPV_ARG(IHNetFirewallSettings, &pFwSettings) ); } if (SUCCEEDED(hr)) { // // Get the enumeration of firewalled connections // hr = pFwSettings->EnumFirewalledConnections(&pFwEnum); pFwSettings->Release(); } if (SUCCEEDED(hr)) { // // Process the enumeration // do { hr = pFwEnum->Next(1, &pFwConn, &ulCount); if (SUCCEEDED(hr) && 1 == ulCount) { // // We don't check the return code for NatpAddConnectionEntry. // NatpAddConnectionEntry will clean up gracefully if an // error occurs and will leave the system in a consistent // state, so an error will not prevent us from processing // the rest of the connections. // NatpAddConnectionEntry(pFwConn); pFwConn->Release(); } } while (SUCCEEDED(hr) && 1 == ulCount); pFwEnum->Release(); } } // // If we don't yet have a shared connection (i.e., none of the // firewalled connections were also IcsPublic), retrieve that // enumeration now. // if (FALSE == NatpSharedConnectionPresent && NULL != pCfgMgr && NhPolicyAllowsSharing) { // // Get the IcsSettings interface // hr = pCfgMgr->QueryInterface( IID_PPV_ARG(IHNetIcsSettings, &pIcsSettings) ); if (SUCCEEDED(hr)) { // // Get the enumeration of ICS public connections // hr = pIcsSettings->EnumIcsPublicConnections(&pIcsEnum); pIcsSettings->Release(); } if (SUCCEEDED(hr)) { // // See if we can get a connection out of the enum // hr = pIcsEnum->Next(1, &pIcsConn, &ulCount); if (SUCCEEDED(hr) && 1 == ulCount) { // // We don't check the return code for NatpAddConnectionEntry. // NatpAddConnectionEntry will clean up gracefully if an // error occurs and will leave the system in a consistent // state, so an error will not prevent us from processing // the rest of the connections. // NatpAddConnectionEntry(pIcsConn); pIcsConn->Release(); } pIcsEnum->Release(); } } if (TRUE == NatpSharedConnectionPresent && NhPolicyAllowsSharing) { // // Make sure shared connection management is started // NatpStartSharedConnectionManagement(); } else { // // Stop shared connection management // NatpStopSharedConnectionManagement(); } // // Notify the firewall subsystem as to whether it needs to // start or stop logging. (These calls are effectively no-ops if // the logger is already in the correct state.) // if (NatpFirewallConnectionCount > 0 && NhPolicyAllowsFirewall) { FwStartLogging(); } else { FwStopLogging(); } // // Bind connections // NatpProcessConnectionNotify(); if (NULL != pCfgMgr) { pCfgMgr->Release(); } LeaveCriticalSection(&NatInterfaceLock); } // NatpProcessConfigurationChanged VOID NatpProcessConnectionNotify( VOID ) /*++ Routine Description: This routine is invoked to see if the shared or firewall connections, if any, have been connected or disconnected since its last invocation. Arguments: none. Return Value: none. --*/ { PLIST_ENTRY Link; PNAT_CONNECTION_ENTRY pConEntry; BOOLEAN Active; ULONG i; ULONG AdapterIndex; PIP_ADAPTER_BINDING_INFO BindingInfo = NULL; ULONG Error; HRASCONN Hrasconn; GUID Guid; UNICODE_STRING UnicodeString; NTSTATUS Status; BOOLEAN bUPnPEventAlreadyFired = FALSE; PROFILE("NatpProcessConnectionNotify"); EnterCriticalSection(&NatInterfaceLock); // // Walk through the connection list // for (Link = NatpConnectionList.Flink; Link != &NatpConnectionList; Link = Link->Flink) { pConEntry = CONTAINING_RECORD(Link, NAT_CONNECTION_ENTRY, Link); // // If the connection is a LAN connection, // it is always active. // // If the connection is a dialup connection, // find out whether the connection is active. // if (pConEntry->HNetProperties.fLanConnection) { Hrasconn = NULL; Active = TRUE; // // The connection is a LAN connection, so we need to detect // any changes to its IP address if it is already bound. // To do so we retrieve the current binding information // and compare it to the active binding information. // If the two are different, we unbind the interface and rebind. // Status = RtlStringFromGUID(pConEntry->Guid, &UnicodeString); if (NT_SUCCESS(Status)) { AdapterIndex = NhMapGuidToAdapter(UnicodeString.Buffer); RtlFreeUnicodeString(&UnicodeString); } else { AdapterIndex = (ULONG)-1; NhTrace( TRACE_FLAG_NAT, "NatpProcessConnectionNotify: RtlStringFromGUID failed\n" ); } if (AdapterIndex == (ULONG)-1) { NhTrace( TRACE_FLAG_NAT, "NatpProcessConnectionNotify: MapGuidToAdapter failed\n" ); Active = FALSE; } else { BindingInfo = NhQueryBindingInformation(AdapterIndex); if (!BindingInfo) { NhTrace( TRACE_FLAG_NAT, "NatpProcessConnectionNotify: QueryBinding failed\n" ); Active = FALSE; } else if (NAT_INTERFACE_BOUND(&pConEntry->Interface)) { // // The interface is already bound; // compare the retrieved binding to the active binding, // and unbind the connection if they are different. // if (!pConEntry->pBindingInfo || BindingInfo->AddressCount != pConEntry->pBindingInfo->AddressCount || !BindingInfo->AddressCount || !RtlEqualMemory( &BindingInfo->Address[0], &pConEntry->pBindingInfo->Address[0], sizeof(IP_LOCAL_BINDING) )) { NatpUnbindConnection(pConEntry); if ( pConEntry->HNetProperties.fIcsPublic ) { FireNATEvent_PublicIPAddressChanged(); bUPnPEventAlreadyFired = TRUE; } } else { // // The bindings are the same, and the interface is bound // already, so we won't be needing the newly-retrieved // binding information. // NH_FREE(BindingInfo); BindingInfo = NULL; } } } } else { AdapterIndex = (ULONG)-1; Hrasconn = NULL; // // Obtain the name of the connection // HRESULT hr; LPWSTR wszEntryName; hr = pConEntry->pHNetConnection->GetName(&wszEntryName); if (SUCCEEDED(hr)) { Error = RasGetEntryHrasconnW( pConEntry->wszPhonebookPath, wszEntryName, &Hrasconn ); CoTaskMemFree(wszEntryName); } Active = ((FAILED(hr) || Error || !Hrasconn) ? FALSE : TRUE); } // // Activate or deactivate the shared-connection as needed; // when activating a LAN connection, we save the binding information // so we can detect address changes later on. // if (!Active && NAT_INTERFACE_BOUND(&pConEntry->Interface)) { NatpUnbindConnection(pConEntry); if (pConEntry->HNetProperties.fIcsPublic && (FALSE == bUPnPEventAlreadyFired)) { FireNATEvent_PublicIPAddressChanged(); } } else if (Active && !NAT_INTERFACE_BOUND(&pConEntry->Interface)) { // // N.B. When a media-sense event occurs and TCP/IP revokes the IP // address for a LAN connection, the connection's IP address becomes // 0.0.0.0. We treat that as though we don't have an IP address at all, // and bypass the binding below. When the IP address is reinstated, // we will rebind correctly, since we will then detect the change. // if (pConEntry->HNetProperties.fLanConnection) { if (BindingInfo->AddressCount != 1 || BindingInfo->Address[0].Address) { NatpBindConnection(pConEntry, Hrasconn, AdapterIndex, BindingInfo); } if (pConEntry->pBindingInfo) { NH_FREE(pConEntry->pBindingInfo); } pConEntry->pBindingInfo = BindingInfo; } else { NatpBindConnection(pConEntry, Hrasconn, AdapterIndex, BindingInfo); } if ( pConEntry->HNetProperties.fIcsPublic && (FALSE == bUPnPEventAlreadyFired) && NAT_INTERFACE_BOUND(&pConEntry->Interface)) { FireNATEvent_PublicIPAddressChanged(); } } } // // If we have a shared connection, also need to update the private interface // if (NatpSharedConnectionPresent) { NhUpdatePrivateInterface(); } LeaveCriticalSection(&NatInterfaceLock); } // NatpProcessConnectionNotify ULONG NatpQueryConnectionAdapter( PNAT_CONNECTION_ENTRY pConEntry ) /*++ Routine Description: This routine is invoked to determine the adapter index corresponding to a connection, if active. Arguments: pConEntry - the connection entry Return Value: ULONG - the adapter index if found, otherwise (ULONG)-1. Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { ULONG AdapterIndex = (ULONG)-1; ULONG Error; HRASCONN Hrasconn = NULL; RASPPPIPA RasPppIp; ULONG Size; UNICODE_STRING UnicodeString; if (pConEntry->HNetProperties.fLanConnection) { RtlStringFromGUID(pConEntry->Guid, &UnicodeString); AdapterIndex = NhMapGuidToAdapter(UnicodeString.Buffer); RtlFreeUnicodeString(&UnicodeString); } else { HRESULT hr; LPWSTR wszEntryName; hr = pConEntry->pHNetConnection->GetName(&wszEntryName); if (SUCCEEDED(hr)) { Error = RasGetEntryHrasconnW( pConEntry->wszPhonebookPath, wszEntryName, &Hrasconn ); if (!Error && Hrasconn) { ZeroMemory(&RasPppIp, sizeof(RasPppIp)); Size = RasPppIp.dwSize = sizeof(RasPppIp); Error = RasGetProjectionInfoA( Hrasconn, RASP_PppIp, &RasPppIp, &Size ); if (!Error) { AdapterIndex = NhMapAddressToAdapter(inet_addr(RasPppIp.szIpAddress)); } } CoTaskMemFree(wszEntryName); } } NhTrace(TRACE_FLAG_NAT, "NatpQueryConnectionAdapter: %d", AdapterIndex); return AdapterIndex; } // NatpQueryConnectionAdapter PIP_NAT_INTERFACE_INFO NatpQueryConnectionInformation( PNAT_CONNECTION_ENTRY pConEntry, PIP_ADAPTER_BINDING_INFO BindingInfo ) /*++ Routine Description: This routine is invoked to construct the configuration of a connection. The configuration consists of basic settings (e.g. interface type and flags) as well as extended information loaded from the configuration store (e.g. port mappings). Arguments: pConEntry - the connection entry BindingInfo - the binding info for the connection Return Value: PIP_NAT_INTERFACE_INFO - the configuration allocated; on error, returns NULL Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { PIP_NAT_PORT_MAPPING Array = NULL; ULONG Count = 0; ULONG Error; PIP_NAT_INTERFACE_INFO Info; PRTR_INFO_BLOCK_HEADER Header; HRESULT hr; ULONG Length; PLIST_ENTRY Link; PRTR_INFO_BLOCK_HEADER NewHeader; PNAT_PORT_MAPPING_ENTRY PortMapping; PROFILE("NatpQueryConnectionInformation"); // // Build the port mapping array from the list // if (pConEntry->PortMappingCount) { Array = reinterpret_cast( NH_ALLOCATE(pConEntry->PortMappingCount * sizeof(IP_NAT_PORT_MAPPING)) ); if (NULL == Array) { NhTrace( TRACE_FLAG_NAT, "NatpQueryConnectionInformation: Unable to allocate array" ); return NULL; } for (Link = pConEntry->PortMappingList.Flink; Link != &pConEntry->PortMappingList; Link = Link->Flink) { PortMapping = CONTAINING_RECORD(Link, NAT_PORT_MAPPING_ENTRY, Link); if (PortMapping->fUdpBroadcastMapping) { continue; } Array[Count].PublicAddress = IP_NAT_ADDRESS_UNSPECIFIED; Array[Count].Protocol = PortMapping->ucProtocol; Array[Count].PublicPort = PortMapping->usPublicPort; Array[Count].PrivateAddress = PortMapping->ulPrivateAddress; Array[Count].PrivatePort = PortMapping->usPrivatePort; Count += 1; } ASSERT(Count == pConEntry->PortMappingCount); } // // Create an info-block header and add the port-mapping array // as the single entry in the info-block. // This info-block header will occupy the 'Header' field // of the final 'IP_NAT_INTERFACE_INFO'. // Error = MprInfoCreate(IP_NAT_VERSION, reinterpret_cast(&Header)); if (Error) { if (Array) { NH_FREE(Array); } return NULL; } if (Count) { Error = MprInfoBlockAdd( Header, IP_NAT_PORT_MAPPING_TYPE, sizeof(IP_NAT_PORT_MAPPING), Count, (PUCHAR)Array, reinterpret_cast(&NewHeader) ); MprInfoDelete(Header); NH_FREE(Array); Header = NewHeader; if (Error) { return NULL; } } else if (Array) { NH_FREE(Array); } // // For firewalled entries, get ICMP settings // if (pConEntry->HNetProperties.fFirewalled && NhPolicyAllowsFirewall) { HNET_FW_ICMP_SETTINGS *pIcmpSettings; DWORD dwIcmpFlags = 0; hr = pConEntry->pHNetConnection->GetIcmpSettings(&pIcmpSettings); if (SUCCEEDED(hr)) { if (pIcmpSettings->fAllowOutboundDestinationUnreachable) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_OB_DEST_UNREACH; } if (pIcmpSettings->fAllowOutboundSourceQuench) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_OB_SOURCE_QUENCH; } if (pIcmpSettings->fAllowRedirect) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_REDIRECT; } if (pIcmpSettings->fAllowInboundEchoRequest) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_IB_ECHO; } if (pIcmpSettings->fAllowInboundRouterRequest) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_IB_ROUTER; } if (pIcmpSettings->fAllowOutboundTimeExceeded) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_OB_TIME_EXCEEDED; } if (pIcmpSettings->fAllowOutboundParameterProblem) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_OB_PARAM_PROBLEM; } if (pIcmpSettings->fAllowInboundTimestampRequest) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_IB_TIMESTAMP; } if (pIcmpSettings->fAllowInboundMaskRequest) { dwIcmpFlags |= IP_NAT_ICMP_ALLOW_IB_MASK; } CoTaskMemFree(pIcmpSettings); Error = MprInfoBlockAdd( Header, IP_NAT_ICMP_CONFIG_TYPE, sizeof(DWORD), 1, (PUCHAR)&dwIcmpFlags, reinterpret_cast(&NewHeader) ); if (NO_ERROR == Error) { MprInfoDelete(Header); Header = NewHeader; } } else { NhTrace( TRACE_FLAG_NAT, "NatpQueryConnectionInformation: GetIcmpSettings 0x%08x", hr ); // // This is a 'soft' error -- we'll still continue even if we // couldn't get the ICMP settings, as our default stance // is more secure than if any of the flags were set. // } } // // Allocate an 'IP_NAT_INTERFACE_INFO' which is large enough to hold // the info-block header which we've just constructed. // Length = FIELD_OFFSET(IP_NAT_INTERFACE_INFO, Header) + Header->Size; Info = reinterpret_cast(NH_ALLOCATE(Length)); if (Info) { RtlZeroMemory(Info, FIELD_OFFSET(IP_NAT_INTERFACE_INFO, Header)); // // Set appropriate flags // if (pConEntry->HNetProperties.fFirewalled && NhPolicyAllowsFirewall) { Info->Flags |= IP_NAT_INTERFACE_FLAGS_FW; } if (pConEntry->HNetProperties.fIcsPublic && NhPolicyAllowsSharing) { Info->Flags |= IP_NAT_INTERFACE_FLAGS_BOUNDARY | IP_NAT_INTERFACE_FLAGS_NAPT; } // // Copy the info-block header into the info structure // RtlCopyMemory(&Info->Header, Header, Header->Size); } MprInfoDelete(Header); return Info; } // NatpQuerySharedConnectionInformation VOID NTAPI NatpRoutingFailureCallbackRoutine( PVOID Context, PIO_STATUS_BLOCK IoStatus, ULONG Reserved ) /*++ Routine Description: This routine is invoked when a routing-failure notification occurs, or when the request is cancelled (e.g. because the request's thread exited). Arguments: Context - unused IoStatus - contains the status of the operation Reserved - unused Return Value: none. Environment: Invoked with a reference made to the component on our behalf. That reference is released here, and if notification is re-requested, it is re-acquired. --*/ { CHAR DestinationAddress[32]; ULONG Error; IP_NAT_REQUEST_NOTIFICATION RequestNotification; PROFILE("NatpRoutingFailureCallbackRoutine"); // // See if cleanup has occurred // EnterCriticalSection(&NatInterfaceLock); if (!NatConnectionNotifyEvent) { LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); return; } LeaveCriticalSection(&NatInterfaceLock); // // Acquire a new reference, and release the old one // if (!REFERENCE_NAT()) { DEREFERENCE_NAT(); return; } DEREFERENCE_NAT(); lstrcpyA( DestinationAddress, inet_ntoa(*(PIN_ADDR)&NatpRoutingFailureNotification.DestinationAddress) ); NhTrace( TRACE_FLAG_NAT, "NatpRoutingFailureCallbackRoutine: %s->%s", inet_ntoa(*(PIN_ADDR)&NatpRoutingFailureNotification.SourceAddress), DestinationAddress ); // // Request an automatic connection if the notification succeeded // if (NT_SUCCESS(IoStatus->Status)) { // // First see if this is a known autodial destination, // requesting a connection if so. // ULONG Count; ULONG Size; Size = 0; Error = RasGetAutodialAddressA( DestinationAddress, NULL, NULL, &Size, &Count ); if (Error != ERROR_BUFFER_TOO_SMALL) { // // This is not a known destination; // try the default shared connection, if any // NhDialSharedConnection(); } else { // // Try initiating a normal autodial connection; // normal autodial may yet lead to the shared-connection. // HINSTANCE Hinstance = LoadLibraryA("RASADHLP.DLL"); if (Hinstance) { BOOL (*WSAttemptAutodialAddr)(PSOCKADDR_IN, INT) = (BOOL (*)(PSOCKADDR_IN, INT)) GetProcAddress( Hinstance, "WSAttemptAutodialAddr" ); if (WSAttemptAutodialAddr) { SOCKADDR_IN SockAddr; SockAddr.sin_family = AF_INET; SockAddr.sin_addr.s_addr = NatpRoutingFailureNotification.DestinationAddress; WSAttemptAutodialAddr(&SockAddr, sizeof(SockAddr)); } FreeLibrary(Hinstance); } } } // // Submit a new request // EnterCriticalSection(&NatInterfaceLock); RequestNotification.Code = NatRoutingFailureNotification; NtDeviceIoControlFile( NatFileHandle, NULL, NatpRoutingFailureCallbackRoutine, NULL, &NatpRoutingFailureIoStatus, IOCTL_IP_NAT_REQUEST_NOTIFICATION, (PVOID)&RequestNotification, sizeof(RequestNotification), &NatpRoutingFailureNotification, sizeof(NatpRoutingFailureNotification) ); LeaveCriticalSection(&NatInterfaceLock); } // NatpRoutingFailureCallbackRoutine VOID NTAPI NatpRoutingFailureWorkerRoutine( PVOID Context ) /*++ Routine Description: This routine initiates the notification of routing-failures. Arguments: none used. Return Value: none. Environment: Invoked in the context of an alertable I/O worker thread. --*/ { IP_NAT_REQUEST_NOTIFICATION RequestNotification; PROFILE("NatpRoutingFailureWorkerRoutine"); // // Request notification of routing-failures // EnterCriticalSection(&NatInterfaceLock); RequestNotification.Code = NatRoutingFailureNotification; NtDeviceIoControlFile( NatFileHandle, NULL, NatpRoutingFailureCallbackRoutine, NULL, &NatpRoutingFailureIoStatus, IOCTL_IP_NAT_REQUEST_NOTIFICATION, (PVOID)&RequestNotification, sizeof(RequestNotification), &NatpRoutingFailureNotification, sizeof(NatpRoutingFailureNotification) ); LeaveCriticalSection(&NatInterfaceLock); } // NatpRoutingFailureWorkerRoutine ULONG NatpStartSharedConnectionManagement( VOID ) /*++ Routine Description: This routine is called to install routing failure-notification, and to enable the router Arguments: none. Return Value: ULONG - Win32 status code. --*/ { ULONG Error; BOOL SharedAutoDial; NTSTATUS status; PROFILE("NatpStartSharedConnectionManagement"); // // See if the user has enabled shared-autodial. // If so, make sure the autodial service is running, // since it will be needed for performing on-demand dialing. // // (IHNetIcsSettings::GetAutodialEnabled just calls the RAS api below, // which is why we're not getting the information that way right now...) // if (!RasQuerySharedAutoDial(&SharedAutoDial) && SharedAutoDial) { SC_HANDLE ScmHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (ScmHandle) { SC_HANDLE ServiceHandle = OpenService(ScmHandle, TEXT("RasAuto"), SERVICE_ALL_ACCESS); if (ServiceHandle) { StartService(ServiceHandle, 0, NULL); CloseServiceHandle(ServiceHandle); } CloseServiceHandle(ScmHandle); } } EnterCriticalSection(&NatInterfaceLock); if (NatpEnableRouterEvent) { LeaveCriticalSection(&NatInterfaceLock); return NO_ERROR; } // // Acquire a component-reference on behalf of // (1) the enable-router callback routine // (2) the routing-failure-notification worker routine. // if (!REFERENCE_NAT()) { LeaveCriticalSection(&NatInterfaceLock); return ERROR_CAN_NOT_COMPLETE; } else if (!REFERENCE_NAT()) { LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); return ERROR_CAN_NOT_COMPLETE; } do { // // Start DNS and DHCP modules // Error = NhStartICSProtocols(); if (Error) break; // // Enable IP forwarding: // Create an event to be used in the overlapped I/O structure // that will be passed to the 'EnableRouter' API routine, // set up the overlapped structure, and schedule the request // by signalling the event. // NatpEnableRouterEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!NatpEnableRouterEvent) { Error = GetLastError(); break; } status = RtlRegisterWait( &NatpEnableRouterWaitHandle, NatpEnableRouterEvent, NatpEnableRouterCallbackRoutine, NULL, INFINITE, 0 ); if (!NT_SUCCESS(status)) { Error = RtlNtStatusToDosError(status); break; } SetEvent(NatpEnableRouterEvent); // // Queue a work item in whose context we will make a request // for routing-failure notification from the NAT driver. // We use a work-item rather than issuing the request directly // to avoid having our I/O request cancelled if and when the current // (thread pool) thread exits. // RtlQueueWorkItem( NatpRoutingFailureWorkerRoutine, NULL, WT_EXECUTEINIOTHREAD ); LeaveCriticalSection(&NatInterfaceLock); return NO_ERROR; } while (FALSE); if (NatpEnableRouterWaitHandle) { RtlDeregisterWait(NatpEnableRouterWaitHandle); NatpEnableRouterWaitHandle = NULL; } if (NatpEnableRouterEvent) { CloseHandle(NatpEnableRouterEvent); NatpEnableRouterEvent = NULL; } LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); DEREFERENCE_NAT(); return Error; } // NatpStartSharedConnectionManagement ULONG NatpStopSharedConnectionManagement( VOID ) /*++ Routine Description: This routine is called to stop the DNS & DHCP modules and also to remove the routing failure-notification, and to disable the router Arguments: none. Return Value: ULONG - Win32 status code. --*/ { ULONG Error = NO_ERROR; PROFILE("NatpStopSharedConnectionManagement"); EnterCriticalSection(&NatInterfaceLock); // // Stop the DHCP, DNS, QoSWindowAdjustment and Beacon modules // Error = NhStopICSProtocols(); if (NatpEnableRouterWaitHandle) { RtlDeregisterWait(NatpEnableRouterWaitHandle); NatpEnableRouterWaitHandle = NULL; } if (NatpEnableRouterEvent) { CloseHandle(NatpEnableRouterEvent); NatpEnableRouterEvent = NULL; NatpEnableRouterCallbackRoutine(NULL, FALSE); } LeaveCriticalSection(&NatInterfaceLock); return Error; } // NatpStopSharedConnectionManagement BOOLEAN NatpUnbindConnection( PNAT_CONNECTION_ENTRY pConEntry ) /*++ Routine Description: This routine is invoked to unbind a currently-active connection. Arguments: Index - index into the connection array Return Value: TRUE if the entry was previously bound; FALSE otherwise. Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { LIST_ENTRY *pLink; PNAT_PORT_MAPPING_ENTRY pMapping; PROFILE("NatpUnbindConnection"); if (NAT_INTERFACE_BOUND(&pConEntry->Interface)) { NatUnbindInterface( pConEntry->Interface.Index, &pConEntry->Interface ); #ifndef NO_FTP_PROXY if (NAT_INTERFACE_ADDED_FTP(&pConEntry->Interface)) { FtpRmDeleteInterface(pConEntry->Interface.Index); pConEntry->Interface.Flags &= ~NAT_INTERFACE_FLAG_ADDED_FTP; } #endif if (NAT_INTERFACE_ADDED_ALG(&pConEntry->Interface)) { AlgRmDeleteInterface(pConEntry->Interface.Index); pConEntry->Interface.Flags &= ~NAT_INTERFACE_FLAG_ADDED_ALG; } if (NAT_INTERFACE_ADDED_H323(&pConEntry->Interface)) { H323RmDeleteInterface(pConEntry->Interface.Index); pConEntry->Interface.Flags &= ~NAT_INTERFACE_FLAG_ADDED_H323; } RemoveEntryList(&pConEntry->Interface.Link); InitializeListHead(&pConEntry->Interface.Link); if (pConEntry->Interface.Info) { NH_FREE(pConEntry->Interface.Info); pConEntry->Interface.Info = NULL; } // // Clean up the port mapping list // NatpFreePortMappingList(pConEntry); return TRUE; } return FALSE; } // NatpUnbindConnection VOID NatpUpdateSharedConnectionDomainName( ULONG AdapterIndex ) /*++ Routine Description: This routine is called to update the cached DNS domain name, if any, for the shared connection. Arguments: AdapterIndex - the index of the adapter for the shared connection Return Value: none. --*/ { PADAPTER_INFORMATION AdapterInformation; ANSI_STRING AnsiString; ULONG Count; ULONG Error; ULONG i; PDNS_NETWORK_INFORMATION NetworkInformation = NULL; PIP_INTERFACE_NAME_INFO Table = NULL; UNICODE_STRING UnicodeString; PROFILE("NatpUpdateSharedConnectionDomainName"); RtlInitAnsiString(&AnsiString, NULL); RtlInitUnicodeString(&UnicodeString, NULL); EnterCriticalSection(&NatInterfaceLock); if (AdapterIndex == (ULONG)-1) { PLIST_ENTRY Link; PNAT_CONNECTION_ENTRY pConEntry; // // Make sure that the connection list has been initialized; if // it hasn't, Flink will be NULL. // if (!NatpConnectionList.Flink) { LeaveCriticalSection(&NatInterfaceLock); return; } // // See if we actually have a shared connection // for (Link = NatpConnectionList.Flink; Link != &NatpConnectionList; Link = Link->Flink) { pConEntry = CONTAINING_RECORD(Link, NAT_CONNECTION_ENTRY, Link); if (pConEntry->HNetProperties.fIcsPublic) { AdapterIndex = NatpQueryConnectionAdapter(pConEntry); break; } } if (AdapterIndex == (ULONG)-1) { LeaveCriticalSection(&NatInterfaceLock); return; } } do { // // Obtain the GUID for the adapter with the given index, // by querying TCP/IP for information on all available interfaces. // The GUID will then be used to map the shared connection's adapter // to a DNS domain name. // Error = NhpAllocateAndGetInterfaceInfoFromStack( &Table, &Count, FALSE, GetProcessHeap(), 0 ); if (Error != NO_ERROR) { break; } for (i = 0; i < Count && Table[i].Index != AdapterIndex; i++) { } if (i >= Count) { Error = ERROR_INTERNAL_ERROR; break; } RtlStringFromGUID(Table[i].DeviceGuid, &UnicodeString); RtlUnicodeStringToAnsiString(&AnsiString, &UnicodeString, TRUE); // // Query the DNS client for the current network parameters, // and search through the network parameters to find the entry // for the shared-connection's current adapter. // NetworkInformation = (PDNS_NETWORK_INFORMATION) DnsQueryConfigAlloc( DnsConfigNetworkInformation, NULL ); if (!NetworkInformation) { Error = ERROR_INTERNAL_ERROR; break; } for (i = 0; i < NetworkInformation->cAdapterCount; i++) { AdapterInformation = NetworkInformation->aAdapterInfoList[i]; if (lstrcmpiA( AnsiString.Buffer, AdapterInformation->pszAdapterGuidName ) == 0) { break; } } if (i >= NetworkInformation->cAdapterCount) { Error = ERROR_INTERNAL_ERROR; break; } // // 'AdapterInformation' is the entry for the shared-connection's // current adapter. // Clear the previously-cached string, and read in the new value, // if any. // if (NatpSharedConnectionDomainName) { NH_FREE(NatpSharedConnectionDomainName); NatpSharedConnectionDomainName = NULL; } if (AdapterInformation->pszDomain) { NatpSharedConnectionDomainName = reinterpret_cast( NH_ALLOCATE(lstrlenA(AdapterInformation->pszDomain) + 1) ); if (!NatpSharedConnectionDomainName) { Error = ERROR_INTERNAL_ERROR; break; } lstrcpyA( NatpSharedConnectionDomainName, AdapterInformation->pszDomain ); } Error = NO_ERROR; } while(FALSE); if (UnicodeString.Buffer) { RtlFreeUnicodeString(&UnicodeString); } if (AnsiString.Buffer) { RtlFreeAnsiString(&AnsiString); } if (NetworkInformation) { DnsFreeConfigStructure( NetworkInformation, DnsConfigNetworkInformation ); } if (Table) { HeapFree(GetProcessHeap(), 0, Table); } if (Error) { if (NatpSharedConnectionDomainName) { NH_FREE(NatpSharedConnectionDomainName); NatpSharedConnectionDomainName = NULL; } } LeaveCriticalSection(&NatInterfaceLock); } // NatpUpdateSharedConnectionDomainName PCHAR NatQuerySharedConnectionDomainName( VOID ) /*++ Routine Description: This routine is called to retrieve a copy of the DNS domain name cached for the shared connection, if available. Otherwise, it returns the primary DNS domain name for the local machine. Arguments: none. Return Value: PCHAR - contains the allocated copy of the DNS domain name. --*/ { PCHAR DomainName; PROFILE("NatQuerySharedConnectionDomainName"); // // See if there is a cached domain name for the shared connection. // If not, refresh the cache. If there is still no domain name, // return a copy of the local machine's primary DNS domain name. // EnterCriticalSection(&NatInterfaceLock); if (!NatpSharedConnectionDomainName) { NatpUpdateSharedConnectionDomainName((ULONG)-1); } if (NatpSharedConnectionDomainName) { DomainName = reinterpret_cast( NH_ALLOCATE(lstrlenA(NatpSharedConnectionDomainName) + 1) ); if (DomainName) { lstrcpyA(DomainName, NatpSharedConnectionDomainName); } } else { PCHAR DnsDomainName = (PCHAR) DnsQueryConfigAlloc( DnsConfigPrimaryDomainName_A, NULL ); if (!DnsDomainName) { DomainName = NULL; } else { DomainName = reinterpret_cast( NH_ALLOCATE(lstrlenA(DnsDomainName) + 1) ); if (DomainName) { lstrcpyA(DomainName, DnsDomainName); } DnsFreeConfigStructure( DnsDomainName, DnsConfigPrimaryDomainName_A ); } } LeaveCriticalSection(&NatInterfaceLock); return DomainName; } // NatQuerySharedConnectionDomainName ULONG NatStartConnectionManagement( VOID ) /*++ Routine Description: This routine is called to install connection change-notification. Arguments: none. Return Value: ULONG - Win32 status code. --*/ { ULONG Error; NTSTATUS status; PROFILE("NatStartConnectionManagement"); EnterCriticalSection(&NatInterfaceLock); if (NatConnectionNotifyEvent) { LeaveCriticalSection(&NatInterfaceLock); return NO_ERROR; } // // Initialize the connection list // InitializeListHead(&NatpConnectionList); // // Acquire a component-reference on behalf of // (1) the connection-notification routine // (2) the configuration-changed routine // if (!REFERENCE_NAT()) { LeaveCriticalSection(&NatInterfaceLock); return ERROR_CAN_NOT_COMPLETE; } if (!REFERENCE_NAT()) { LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); return ERROR_CAN_NOT_COMPLETE; } do { // // Create the connection-notification event, register a wait // on the event, and register for connect and disconnect notification. // We expect at least one invocation as a result of this registration, // hence the reference made to the NAT module above. // NatConnectionNotifyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!NatConnectionNotifyEvent) { Error = GetLastError(); break; } status = RtlRegisterWait( &NatpConnectionNotifyWaitHandle, NatConnectionNotifyEvent, NatpConnectionNotifyCallbackRoutine, NULL, INFINITE, 0 ); if (!NT_SUCCESS(status)) { Error = RtlNtStatusToDosError(status); break; } Error = RasConnectionNotification( (HRASCONN)INVALID_HANDLE_VALUE, NatConnectionNotifyEvent, RASCN_Connection|RASCN_Disconnection ); if (Error) { break; } // // Create the configuartion-change event and register a wait // on the event. // NatConfigurationChangedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!NatConfigurationChangedEvent) { Error = GetLastError(); break; } status = RtlRegisterWait( &NatpConfigurationChangedWaitHandle, NatConfigurationChangedEvent, NatpConfigurationChangedCallbackRoutine, NULL, INFINITE, 0 ); if (!NT_SUCCESS(status)) { Error = RtlNtStatusToDosError(status); break; } LeaveCriticalSection(&NatInterfaceLock); // // Pick up any existing connections, by signalling the configuration // change event. We cannot invoke the function directly // because it invokes service-control functions to start autodial, // and we could currently be running in a service-controller thread. // NtSetEvent(NatConfigurationChangedEvent, NULL); return NO_ERROR; } while(FALSE); // // A failure occurred; perform cleanup // if (NatpConnectionNotifyWaitHandle) { RtlDeregisterWait(NatpConnectionNotifyWaitHandle); NatpConnectionNotifyWaitHandle = NULL; } if (NatConnectionNotifyEvent) { CloseHandle(NatConnectionNotifyEvent); NatConnectionNotifyEvent = NULL; } if (NatpConfigurationChangedWaitHandle) { RtlDeregisterWait(NatpConfigurationChangedWaitHandle); NatpConfigurationChangedWaitHandle = NULL; } if (NatConfigurationChangedEvent) { CloseHandle(NatConfigurationChangedEvent); NatConfigurationChangedEvent = NULL; } LeaveCriticalSection(&NatInterfaceLock); DEREFERENCE_NAT(); DEREFERENCE_NAT(); return Error; } // NatStartConnectionManagement VOID NatStopConnectionManagement( VOID ) /*++ Routine Description: This routine is invoked to stop the connection-monitoring activity initiated by 'NatStartConnectionManagement' above. Arguments: none. Return Value: none. Environment: Invoked when 'StopProtocol' is received from the IP router-manager. --*/ { PLIST_ENTRY Link; PNAT_CONNECTION_ENTRY pConEntry; PROFILE("NatStopConnectionManagement"); EnterCriticalSection(&NatInterfaceLock); // // Cleanup the wait-handle and event used to receive notification // of RAS connections and disconnections. // if (NatpConnectionNotifyWaitHandle) { RtlDeregisterWait(NatpConnectionNotifyWaitHandle); NatpConnectionNotifyWaitHandle = NULL; } if (NatConnectionNotifyEvent) { RasConnectionNotification( (HRASCONN)INVALID_HANDLE_VALUE, NatConnectionNotifyEvent, 0 ); CloseHandle(NatConnectionNotifyEvent); NatConnectionNotifyEvent = NULL; NatpConnectionNotifyCallbackRoutine(NULL, FALSE); } if (NatpEnableRouterWaitHandle) { RtlDeregisterWait(NatpEnableRouterWaitHandle); NatpEnableRouterWaitHandle = NULL; } if (NatpEnableRouterEvent) { CloseHandle(NatpEnableRouterEvent); NatpEnableRouterEvent = NULL; NatpEnableRouterCallbackRoutine(NULL, FALSE); } if (NatpConfigurationChangedWaitHandle) { RtlDeregisterWait(NatpConfigurationChangedWaitHandle); NatpConfigurationChangedWaitHandle = NULL; } if (NatConfigurationChangedEvent) { CloseHandle(NatConfigurationChangedEvent); NatConfigurationChangedEvent = NULL; NatpConfigurationChangedCallbackRoutine(NULL, FALSE); } if (NatpConnectionList.Flink) { // // Make certain that all of our connections are disabled // NatUnbindAllConnections(); // // Walk through the connection list, freeing all of the entries // while (!IsListEmpty(&NatpConnectionList)) { Link = RemoveHeadList(&NatpConnectionList); pConEntry = CONTAINING_RECORD(Link, NAT_CONNECTION_ENTRY, Link); NatpFreeConnectionEntry(pConEntry); } // // Make sure all ICS protocols are stopped // NhStopICSProtocols(); } // // Clean up the DNS domain name cached for the shared connection. // if (NatpSharedConnectionDomainName) { NH_FREE(NatpSharedConnectionDomainName); NatpSharedConnectionDomainName = NULL; } // // Reset tracking variables to initial state // NatpFirewallConnectionCount = 0; NatpSharedConnectionPresent = FALSE; LeaveCriticalSection(&NatInterfaceLock); } // NatStopConnectionManagement BOOLEAN NatUnbindAllConnections( VOID ) /*++ Routine Description: This routine is invoked to unbind a currently-active connection. Arguments: Index - index into the connection array Return Value: BOOLEAN - TRUE if any interfaces were unbound. Environment: Invoked with 'NatInterfaceLock' held by the caller. --*/ { PLIST_ENTRY Link; PNAT_CONNECTION_ENTRY pConEntry; BOOLEAN Result = FALSE; PROFILE("NatUnbindAllConnections"); for (Link = NatpConnectionList.Flink; Link != &NatpConnectionList; Link = Link->Flink) { pConEntry = CONTAINING_RECORD(Link, NAT_CONNECTION_ENTRY, Link); Result |= NatpUnbindConnection(pConEntry); } return Result; } // NatpUnbindConnection