/*++ Copyright (c) 1998, Microsoft Corporation Module Name: dnsquery.c Abstract: This module contains code for the DNS proxy's query-management. Author: Abolade Gbadegesin (aboladeg) 11-Mar-1998 Revision History: Raghu Gatta (rgatta) 1-Dec-2000 Added ICSDomain registry key change notify code. --*/ #include "precomp.h" #pragma hdrstop #include "dnsmsg.h" // // Structure: DNS_QUERY_TIMEOUT_CONTEXT // // This structure is used to pass context information // to the timeout callback-routine for DNS queries. // typedef struct _DNS_QUERY_TIMEOUT_CONTEXT { ULONG Index; USHORT QueryId; } DNS_QUERY_TIMEOUT_CONTEXT, *PDNS_QUERY_TIMEOUT_CONTEXT; // // GLOBAL DATA DEFINITIONS // const WCHAR DnsDhcpNameServerString[] = L"DhcpNameServer"; const WCHAR DnsNameServerString[] = L"NameServer"; HANDLE DnsNotifyChangeKeyEvent = NULL; IO_STATUS_BLOCK DnsNotifyChangeKeyIoStatus; HANDLE DnsNotifyChangeKeyWaitHandle = NULL; HANDLE DnsNotifyChangeAddressEvent = NULL; OVERLAPPED DnsNotifyChangeAddressOverlapped; HANDLE DnsNotifyChangeAddressWaitHandle = NULL; PULONG DnsServerList[DnsProxyCount] = { NULL, NULL }; HANDLE DnsTcpipInterfacesKey = NULL; const WCHAR DnsTcpipInterfacesString[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services" L"\\Tcpip\\Parameters\\Interfaces"; HANDLE DnsNotifyChangeKeyICSDomainEvent = NULL; IO_STATUS_BLOCK DnsNotifyChangeKeyICSDomainIoStatus; HANDLE DnsNotifyChangeKeyICSDomainWaitHandle = NULL; HANDLE DnsTcpipParametersKey = NULL; const WCHAR DnsTcpipParametersString[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services" L"\\Tcpip\\Parameters"; const WCHAR DnsICSDomainValueName[] = L"ICSDomain"; PWCHAR DnsICSDomainSuffix = NULL; // // FORWARD DECLARATIONS // VOID NTAPI DnsNotifyChangeAddressCallbackRoutine( PVOID Context, BOOLEAN TimedOut ); VOID NTAPI DnspQueryTimeoutCallbackRoutine( PVOID Context, BOOLEAN TimedOut ); VOID APIENTRY DnspQueryTimeoutWorkerRoutine( PVOID Context ); VOID DnsReadCompletionRoutine( ULONG ErrorCode, ULONG BytesTransferred, PNH_BUFFER Bufferp ); VOID DnsDeleteQuery( PDNS_INTERFACE Interfacep, PDNS_QUERY Queryp ) /*++ Routine Description: This routine is called to delete a pending query. Arguments: Interfacep - the query's interface Queryp - the query to be deleted Return Value: none. Environment: Invoked with 'Interfacep' locked by the caller. --*/ { PROFILE("DnsDeleteQuery"); if (Queryp->Bufferp) { NhReleaseBuffer(Queryp->Bufferp); } if (Queryp->TimerHandle) { // // This query is associated with a timer; // Rather than cancel the timeout-routine, we let it run, // so that it can release any references it has on the component. // When it does run, though, the routine won't find this query. // Queryp->TimerHandle = NULL; } RemoveEntryList(&Queryp->Link); NH_FREE(Queryp); } // DnsDeleteQuery BOOLEAN DnsIsPendingQuery( PDNS_INTERFACE Interfacep, PNH_BUFFER QueryBuffer ) /*++ Routine Description: This routine is invoked to determine whether a query is already pending for the client-request in the given buffer. The list of queries is sorted on 'QueryId', but we will be searching on 'SourceId' and 'SourceAddress' and 'SourcePort'; hence, we must do an exhaustive search of the pending-query list. Arguments: Interfacep - the interface on which to look QueryBuffer - the query to be searched for Return Value: BOOLEAN - TRUE if the query is already pending, FALSE otherwise. Environment: Invoked with 'Interfacep' locked by the caller. --*/ { BOOLEAN Exists; PDNS_HEADER Headerp; PLIST_ENTRY Link; PDNS_QUERY Queryp; PROFILE("DnsIsPendingQuery"); Exists = FALSE; Headerp = (PDNS_HEADER)QueryBuffer->Buffer; for (Link = Interfacep->QueryList.Flink; Link != &Interfacep->QueryList; Link = Link->Flink ) { Queryp = CONTAINING_RECORD(Link, DNS_QUERY, Link); if (Queryp->SourceId != Headerp->Xid || Queryp->SourceAddress != QueryBuffer->ReadAddress.sin_addr.s_addr || Queryp->SourcePort != QueryBuffer->ReadAddress.sin_port ) { continue; } Exists = TRUE; break; } return Exists; } // DnsIsPendingQuery PDNS_QUERY DnsMapResponseToQuery( PDNS_INTERFACE Interfacep, USHORT ResponseId ) /*++ Routine Description: This routine is invoked to map an incoming response from a DNS server to a pending query for a DNS client. Arguments: Interfacep - the interface holding the pending query, if any ResponseId - the ID in the response received from the server Return Value: PDNS_QUERY - the pending query, if any Environment: Invoked with 'Interfacep' locked by the caller. --*/ { PLIST_ENTRY Link; PDNS_QUERY Queryp; PROFILE("DnsMapResponseToQuery"); for (Link = Interfacep->QueryList.Flink; Link != &Interfacep->QueryList; Link = Link->Flink ) { Queryp = CONTAINING_RECORD(Link, DNS_QUERY, Link); if (ResponseId > Queryp->QueryId) { continue; } else if (ResponseId < Queryp->QueryId) { break; } return Queryp; } return NULL; } // DnsMapResponseToQuery VOID NTAPI DnsNotifyChangeAddressCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is invoked to notify us of when changes occur in the (system) table that maps IP addresses to interfaces. Arguments: Context - unused TimedOut - indicates a time-out occurred 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 when the wait is cancelled, unless an error occurs here, in which case it is released immediately. --*/ { ULONG Error; HANDLE UnusedTcpipHandle; PROFILE("DnsNotifyChangeAddressCallbackRoutine"); EnterCriticalSection(&DnsGlobalInfoLock); if (!DnsNotifyChangeAddressEvent) { LeaveCriticalSection(&DnsGlobalInfoLock); DEREFERENCE_DNS(); return; } // // Rebuild the list of DNS servers // DnsQueryServerList(); // // Repost the address change-notification // ZeroMemory(&DnsNotifyChangeAddressOverlapped, sizeof(OVERLAPPED)); DnsNotifyChangeAddressOverlapped.hEvent = DnsNotifyChangeAddressEvent; Error = NotifyAddrChange( &UnusedTcpipHandle, &DnsNotifyChangeAddressOverlapped ); if (Error != ERROR_IO_PENDING) { if (DnsNotifyChangeAddressWaitHandle) { RtlDeregisterWait(DnsNotifyChangeAddressWaitHandle); DnsNotifyChangeAddressWaitHandle = NULL; } NtClose(DnsNotifyChangeAddressEvent); DnsNotifyChangeAddressEvent = NULL; LeaveCriticalSection(&DnsGlobalInfoLock); DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsNotifyChangeAddressCallbackRoutine: error %08x " "for change address", Error ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, Error, "" ); return; } LeaveCriticalSection(&DnsGlobalInfoLock); } // DnsNotifyChangeAddressCallbackRoutine VOID NTAPI DnsNotifyChangeKeyCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is invoked to notify us of a change to the TCP/IP parameters subkey containing the DNS adapter information. Arguments: Context - unused TimedOut - indicates a time-out occurred 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 when the wait is cancelled, unless an error occurs here, in which case it is released immediately. --*/ { NTSTATUS status; PROFILE("DnsNotifyChangeKeyCallbackRoutine"); EnterCriticalSection(&DnsGlobalInfoLock); if (!DnsNotifyChangeKeyEvent) { LeaveCriticalSection(&DnsGlobalInfoLock); DEREFERENCE_DNS(); return; } // // Rebuild the list of DNS servers // DnsQueryServerList(); // // Repost the change-notification // status = NtNotifyChangeKey( DnsTcpipInterfacesKey, DnsNotifyChangeKeyEvent, NULL, NULL, &DnsNotifyChangeKeyIoStatus, REG_NOTIFY_CHANGE_LAST_SET, TRUE, NULL, 0, TRUE ); if (!NT_SUCCESS(status)) { if (DnsNotifyChangeKeyWaitHandle) { RtlDeregisterWait(DnsNotifyChangeKeyWaitHandle); DnsNotifyChangeKeyWaitHandle = NULL; } NtClose(DnsNotifyChangeKeyEvent); DnsNotifyChangeKeyEvent = NULL; LeaveCriticalSection(&DnsGlobalInfoLock); NhTrace( TRACE_FLAG_DNS, "DnsNotifyChangeKeyCallbackRoutine: status %08x " "enabling change notify", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); DEREFERENCE_DNS(); return; } LeaveCriticalSection(&DnsGlobalInfoLock); } // DnsNotifyChangeKeyCallbackRoutine VOID NTAPI DnsNotifyChangeKeyICSDomainCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is invoked to notify us of a change to the TCP/IP parameters subkey containing the ICS Domain suffix string information. Arguments: Context - unused TimedOut - indicates a time-out occurred 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 when the wait is cancelled, unless an error occurs here, in which case it is released immediately. --*/ { NTSTATUS status; PROFILE("DnsNotifyChangeKeyICSDomainCallbackRoutine"); EnterCriticalSection(&DnsGlobalInfoLock); if (!DnsNotifyChangeKeyICSDomainEvent) { LeaveCriticalSection(&DnsGlobalInfoLock); DEREFERENCE_DNS(); return; } // // Check to see if the domain string changed; // if it doesnt exist - create one. // DnsQueryICSDomainSuffix(); // // Repost the change-notification // status = NtNotifyChangeKey( DnsTcpipParametersKey, DnsNotifyChangeKeyICSDomainEvent, NULL, NULL, &DnsNotifyChangeKeyICSDomainIoStatus, REG_NOTIFY_CHANGE_LAST_SET, FALSE, // not interested in the subtree NULL, 0, TRUE ); if (!NT_SUCCESS(status)) { if (DnsNotifyChangeKeyICSDomainWaitHandle) { RtlDeregisterWait(DnsNotifyChangeKeyICSDomainWaitHandle); DnsNotifyChangeKeyICSDomainWaitHandle = NULL; } NtClose(DnsNotifyChangeKeyICSDomainEvent); DnsNotifyChangeKeyICSDomainEvent = NULL; LeaveCriticalSection(&DnsGlobalInfoLock); NhTrace( TRACE_FLAG_DNS, "DnsNotifyChangeKeyICSDomainCallbackRoutine: status %08x " "enabling change notify", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_ICSD_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); DEREFERENCE_DNS(); return; } LeaveCriticalSection(&DnsGlobalInfoLock); } // DnsNotifyChangeKeyICSDomainCallbackRoutine VOID NTAPI DnspQueryTimeoutCallbackRoutine( PVOID Context, BOOLEAN TimedOut ) /*++ Routine Description: This routine is called when the timeout for a query expires. We may need to resubmit the query and install a new timer, but we cannot do this in the context of an Rtl timer-routine. Therefore, queue an RTUTILS.DLL work-item to handle the timeout. Arguments: Context - holds the timer context TimedOut - unused. Return Value: none. Environment: Invoked in the context of an Rtl timer-thread with a reference made to the component on our behalf at the time 'RtlCreateTimer' was invoked. --*/ { ULONG Error; PDNS_INTERFACE Interfacep; PDNS_QUERY Queryp; NTSTATUS status; PDNS_QUERY_TIMEOUT_CONTEXT TimeoutContext; PROFILE("DnspQueryTimeoutCallbackRoutine"); TimeoutContext = (PDNS_QUERY_TIMEOUT_CONTEXT)Context; // // Look up the interface for the timeout // EnterCriticalSection(&DnsInterfaceLock); Interfacep = DnsLookupInterface(TimeoutContext->Index, NULL); if (!Interfacep || !DNS_REFERENCE_INTERFACE(Interfacep)) { LeaveCriticalSection(&DnsInterfaceLock); NhTrace( TRACE_FLAG_DNS, "DnspQueryTimeoutCallbackRoutine: interface %d not found", TimeoutContext->Index ); NH_FREE(TimeoutContext); DEREFERENCE_DNS(); return; } LeaveCriticalSection(&DnsInterfaceLock); // // Look up the query which timed out // ACQUIRE_LOCK(Interfacep); Queryp = DnsMapResponseToQuery(Interfacep, TimeoutContext->QueryId); if (!Queryp) { RELEASE_LOCK(Interfacep); DNS_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_DNS, "DnspQueryTimeoutCallbackRoutine: query %d interface %d not found", TimeoutContext->QueryId, TimeoutContext->Index ); NH_FREE(TimeoutContext); DEREFERENCE_DNS(); return; } Queryp->TimerHandle = NULL; // // Try to queue a work-item for the timeout; // if this succeeds, keep the reference on the component. // Otherwise, we have to drop the reference here. // status = RtlQueueWorkItem( DnspQueryTimeoutWorkerRoutine, Context, WT_EXECUTEINIOTHREAD ); if (NT_SUCCESS(status)) { RELEASE_LOCK(Interfacep); DNS_DEREFERENCE_INTERFACE(Interfacep); } else { NH_FREE(TimeoutContext); NhTrace( TRACE_FLAG_DNS, "DnspQueryTimeoutCallbackRoutine: RtlQueueWorkItem=%d, aborting", status ); DnsDeleteQuery(Interfacep, Queryp); RELEASE_LOCK(Interfacep); DNS_DEREFERENCE_INTERFACE(Interfacep); DEREFERENCE_DNS(); } } // DnspQueryTimeoutCallbackRoutine VOID APIENTRY DnspQueryTimeoutWorkerRoutine( PVOID Context ) /*++ Routine Description: This routine is called when the timeout for a query expires. It is queued by the query's timer-handler. Arguments: Context - holds the timer context Return Value: none. Environment: Invoked in the context of an RTUTILS worker-thread with a reference made to the component on our behalf at the time 'RtlCreateTimer' was invoked. --*/ { ULONG Error; PDNS_INTERFACE Interfacep; PDNS_QUERY Queryp; PDNS_QUERY_TIMEOUT_CONTEXT TimeoutContext; PROFILE("DnspQueryTimeoutWorkerRoutine"); TimeoutContext = (PDNS_QUERY_TIMEOUT_CONTEXT)Context; // // Look up the interface for the timeout // EnterCriticalSection(&DnsInterfaceLock); Interfacep = DnsLookupInterface(TimeoutContext->Index, NULL); if (!Interfacep || !DNS_REFERENCE_INTERFACE(Interfacep)) { LeaveCriticalSection(&DnsInterfaceLock); NhTrace( TRACE_FLAG_DNS, "DnspQueryTimeoutWorkerRoutine: interface %d not found", TimeoutContext->Index ); NH_FREE(TimeoutContext); DEREFERENCE_DNS(); return; } LeaveCriticalSection(&DnsInterfaceLock); // // Look up the query which timed out // ACQUIRE_LOCK(Interfacep); Queryp = DnsMapResponseToQuery(Interfacep, TimeoutContext->QueryId); if (!Queryp) { RELEASE_LOCK(Interfacep); DNS_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_DNS, "DnspQueryTimeoutWorkerRoutine: query %d interface %d not found", TimeoutContext->QueryId, TimeoutContext->Index ); NH_FREE(TimeoutContext); DEREFERENCE_DNS(); return; } NH_FREE(TimeoutContext); // // Have 'DnsSendQuery' repost the timed-out query. // Note that we retain our reference to the interface // on behalf of the send to be initiated in 'DnsSendQuery'. // Error = DnsSendQuery( Interfacep, Queryp, TRUE ); if (!Error) { RELEASE_LOCK(Interfacep); } else { DnsDeleteQuery(Interfacep, Queryp); RELEASE_LOCK(Interfacep); DNS_DEREFERENCE_INTERFACE(Interfacep); } DEREFERENCE_DNS(); } // DnspQueryTimeoutWorkerRoutine ULONG DnsQueryServerList( VOID ) /*++ Routine Description: This routine is invoked to read the list of DNS servers from the registry. Arguments: none. Return Value: ULONG - Win32 status code. Environment: Invoked in an arbitrary context with 'DnsGlobalInfoLock' acquired by the caller. --*/ { PUCHAR Buffer; ULONG Error; PKEY_VALUE_PARTIAL_INFORMATION Information; PDNS_INTERFACE Interfacep; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS status; UNICODE_STRING UnicodeString; PROFILE("DnsQueryServerList"); if (!DnsTcpipInterfacesKey) { RtlInitUnicodeString(&UnicodeString, DnsTcpipInterfacesString); InitializeObjectAttributes( &ObjectAttributes, &UnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); // // Open the 'Tcpip\Parameters\Interfaces' registry key // status = NtOpenKey( &DnsTcpipInterfacesKey, KEY_ALL_ACCESS, &ObjectAttributes ); if (!NT_SUCCESS(status)) { Error = RtlNtStatusToDosError(status); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: error %x opening registry key", status ); NhErrorLog( IP_DNS_PROXY_LOG_NO_SERVER_LIST, Error, "" ); return Error; } } // // See if we need to install change-notification, // and reference ourselves if so. // The reference is made on behalf of the change-notification routine // which will be invoked by a wait-thread when a change occurs. // if (!DnsNotifyChangeKeyEvent && REFERENCE_DNS()) { // // Attempt to set up change notification on the key // status = NtCreateEvent( &DnsNotifyChangeKeyEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE ); if (!NT_SUCCESS(status)) { DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: status %08x creating notify-change event", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } else { // // Register a wait on the notify-change event // status = RtlRegisterWait( &DnsNotifyChangeKeyWaitHandle, DnsNotifyChangeKeyEvent, DnsNotifyChangeKeyCallbackRoutine, NULL, INFINITE, 0 ); if (!NT_SUCCESS(status)) { NtClose(DnsNotifyChangeKeyEvent); DnsNotifyChangeKeyEvent = NULL; DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: status %08x registering wait", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } else { // // Register for change-notification on the key // status = NtNotifyChangeKey( DnsTcpipInterfacesKey, DnsNotifyChangeKeyEvent, NULL, NULL, &DnsNotifyChangeKeyIoStatus, REG_NOTIFY_CHANGE_LAST_SET, TRUE, NULL, 0, TRUE ); if (!NT_SUCCESS(status)) { RtlDeregisterWait(DnsNotifyChangeKeyWaitHandle); DnsNotifyChangeKeyWaitHandle = NULL; NtClose(DnsNotifyChangeKeyEvent); DnsNotifyChangeKeyEvent = NULL; DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: status %08x (%08x) " "enabling change notify", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } } } } // // See if we need to install address-change-notification, // and reference ourselves if so. // The reference is made on behalf of the address-change-notification // routine which will be invoked by a wait-thread when a change occurs. // if (!DnsNotifyChangeAddressEvent && REFERENCE_DNS()) { // // Attempt to set up address change notification // status = NtCreateEvent( &DnsNotifyChangeAddressEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE ); if (!NT_SUCCESS(status)) { DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: status %08x creating " "notify-change address event", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } else { // // Register a wait on the notify-change address event // status = RtlRegisterWait( &DnsNotifyChangeAddressWaitHandle, DnsNotifyChangeAddressEvent, DnsNotifyChangeAddressCallbackRoutine, NULL, INFINITE, 0 ); if (!NT_SUCCESS(status)) { NtClose(DnsNotifyChangeAddressEvent); DnsNotifyChangeAddressEvent = NULL; DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: status %08x registering wait" "for change address", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } else { HANDLE UnusedTcpipHandle; // // Register for change-notification // ZeroMemory( &DnsNotifyChangeAddressOverlapped, sizeof(OVERLAPPED) ); DnsNotifyChangeAddressOverlapped.hEvent = DnsNotifyChangeAddressEvent; Error = NotifyAddrChange( &UnusedTcpipHandle, &DnsNotifyChangeAddressOverlapped ); if (Error != ERROR_IO_PENDING) { RtlDeregisterWait(DnsNotifyChangeAddressWaitHandle); DnsNotifyChangeAddressWaitHandle = NULL; NtClose(DnsNotifyChangeAddressEvent); DnsNotifyChangeAddressEvent = NULL; DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: error %08x" "for change address", Error ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_NOTIFY_FAILED, Error, "" ); } } } } { PIP_ADAPTER_INFO AdapterInfo; PIP_ADAPTER_INFO AdaptersInfo = NULL; ULONG Address; PIP_ADDR_STRING AddrString; ULONG dnsLength = 0; PULONG dnsServerList = NULL; PFIXED_INFO FixedInfo = NULL; LONG i; ULONG Length; PIP_PER_ADAPTER_INFO PerAdapterInfo; ULONG tempLength; PULONG tempServerList; ULONG winsLength; PULONG winsServerList = NULL; // // Read the DNS and WINS server lists. // 'GetAdaptersInfo' provides the WINS servers for each adapter, // while 'GetPerAdapterInfo' provides the DNS servers for each adapter. // While 'GetAdaptersInfo' returns an array of all adapters, // 'GetPerAdapterInfo' must be invoked for each individual adapter. // Hence we begin with 'GetAdaptersInfo', and enumerate each adapter // building the WINS and DNS server lists in parallel. // do { // // Retrieve the size of the adapter list // Length = 0; Error = GetAdaptersInfo(NULL, &Length); if (!Error) { break; } else if (Error != ERROR_BUFFER_OVERFLOW) { NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: GetAdaptersInfo=%d", Error ); NhErrorLog( IP_DNS_PROXY_LOG_ERROR_SERVER_LIST, Error, "" ); break; } // // Allocate a buffer to hold the list // AdaptersInfo = (PIP_ADAPTER_INFO)NH_ALLOCATE(Length); if (!AdaptersInfo) { NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: error allocating %d bytes", Length ); NhErrorLog( IP_DNS_PROXY_LOG_ALLOCATION_FAILED, 0, "%d", Length ); break; } // // Retrieve the list // Error = GetAdaptersInfo(AdaptersInfo, &Length); if (Error) { NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: GetAdaptersInfo=%d", Error ); NhErrorLog( IP_DNS_PROXY_LOG_NO_SERVER_LIST, Error, "" ); break; } // // Count the WINS servers // for (AdapterInfo = AdaptersInfo, winsLength = 1; AdapterInfo; AdapterInfo = AdapterInfo->Next ) { if (inet_addr(AdapterInfo->PrimaryWinsServer.IpAddress.String)) { ++winsLength; } if (inet_addr(AdapterInfo->SecondaryWinsServer.IpAddress.String)) { ++winsLength; } } // // Allocate space for the WINS servers // winsServerList = (PULONG)NH_ALLOCATE(winsLength * sizeof(ULONG)); if (!winsServerList) { NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: error allocating %d-byte WINS server list", winsLength * sizeof(ULONG) ); NhErrorLog( IP_DNS_PROXY_LOG_ALLOCATION_FAILED, 0, "%d", winsLength * sizeof(ULONG) ); break; } // // Now fill in the WINS server names from each adapter. // In the process, we pick up the DNS server lists for each adapter. // for (AdapterInfo = AdaptersInfo, Length = 0; AdapterInfo; AdapterInfo = AdapterInfo->Next ) { Address = inet_addr(AdapterInfo->PrimaryWinsServer.IpAddress.String); if (Address) { for (i = 0; i < (LONG)Length; i++) { if (Address == winsServerList[i]) { break; } } if (i >= (LONG)Length) { winsServerList[Length++] = Address; } } Address = inet_addr(AdapterInfo->SecondaryWinsServer.IpAddress.String); if (Address) { for (i = 0; i < (LONG)Length; i++) { if (Address == winsServerList[i]) { break; } } if (i >= (LONG)Length) { winsServerList[Length++] = Address; } } // // Now obtain the DNS servers for the adapter. // Error = GetPerAdapterInfo(AdapterInfo->Index, NULL, &tempLength); if (Error != ERROR_BUFFER_OVERFLOW) { continue; } // // Allocate memory for the per-adapter info // PerAdapterInfo = reinterpret_cast( NH_ALLOCATE(tempLength) ); if (!PerAdapterInfo) { NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: error allocating %d bytes", tempLength ); NhErrorLog( IP_DNS_PROXY_LOG_ALLOCATION_FAILED, 0, "%d", tempLength ); continue; } // // Retrieve the per-adapter info // Error = GetPerAdapterInfo( AdapterInfo->Index, PerAdapterInfo, &tempLength ); if (Error) { NH_FREE(PerAdapterInfo); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: GetPerAdapterInfo=%d", Error ); NhErrorLog( IP_DNS_PROXY_LOG_NO_SERVER_LIST, Error, "" ); continue; } // // Count the DNS servers for the adapter // for (AddrString = &PerAdapterInfo->DnsServerList, tempLength = 0; AddrString; AddrString = AddrString->Next ) { if (inet_addr(AddrString->IpAddress.String)) { ++tempLength; } } if (!tempLength) { NH_FREE(PerAdapterInfo); continue; } // // Allocate space for the adapter's DNS servers // tempServerList = reinterpret_cast( NH_ALLOCATE((dnsLength + tempLength + 1) * sizeof(ULONG)) ); if (!tempServerList) { NH_FREE(PerAdapterInfo); NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: error allocating %d bytes", (dnsLength + tempLength + 1) * sizeof(ULONG) ); NhErrorLog( IP_DNS_PROXY_LOG_ALLOCATION_FAILED, 0, "%d", (dnsLength + tempLength + 1) * sizeof(ULONG) ); continue; } // // Copy the existing servers // if (dnsServerList) { CopyMemory( tempServerList, dnsServerList, dnsLength * sizeof(ULONG) ); } // // Read the new servers into the new server list // for (AddrString = &PerAdapterInfo->DnsServerList; AddrString; AddrString = AddrString->Next ) { Address = inet_addr(AddrString->IpAddress.String); if (!Address) { continue; } for (i = 0; i < (LONG)dnsLength; i++) { if (Address == tempServerList[i]) { break; } } if (i < (LONG)dnsLength) { continue; } // // The current DNS server goes in the front of the list, // while any other server is appended. // if (PerAdapterInfo->CurrentDnsServer != AddrString) { tempServerList[dnsLength] = Address; } else { MoveMemory( tempServerList + sizeof(ULONG), tempServerList, dnsLength * sizeof(ULONG) ); tempServerList[0] = Address; } ++dnsLength; } tempServerList[dnsLength] = 0; NH_FREE(PerAdapterInfo); // // Replace the existing server list // if (dnsServerList) { NH_FREE(dnsServerList); } dnsServerList = tempServerList; } winsServerList[Length] = 0; } while(FALSE); if (AdaptersInfo) { NH_FREE(AdaptersInfo); } // // Store the new server lists // NhTrace( TRACE_FLAG_DNS, "DnsQueryServerList: new server list lengths are : DNS (%d) WINS (%d)", dnsLength, Length ); if (DnsServerList[DnsProxyDns]) { NH_FREE(DnsServerList[DnsProxyDns]); } DnsServerList[DnsProxyDns] = dnsServerList; if (DnsServerList[DnsProxyWins]) { NH_FREE(DnsServerList[DnsProxyWins]); } DnsServerList[DnsProxyWins] = winsServerList; } return NO_ERROR; } // DnsQueryServerList VOID DnsQueryRegistryICSDomainSuffix( VOID ) /*++ Routine Description: This routine is invoked to read the ICS Domain suffix from the registry. Arguments: none. Return Value: VOID. Environment: Invoked in an arbitrary context with 'DnsGlobalInfoLock' acquired by the caller. --*/ { NTSTATUS status; PKEY_VALUE_PARTIAL_INFORMATION Information; DWORD dwSize = 0; LPVOID lpMsgBuf; BOOL fSuffixChanged = FALSE; BOOL fUseDefaultSuffix = FALSE; // // retrieve current suffix string (if any) // status = NhQueryValueKey( DnsTcpipParametersKey, DnsICSDomainValueName, &Information ); if (!NT_SUCCESS(status)) { NhTrace( TRACE_FLAG_DNS, "DnsQueryRegistryICSDomainSuffix: error (0x%08x) querying " "ICS Domain suffix name", status ); // // if we did not find it in the registry and we had previously // got some suffix - we revert to default string (happens below) // if ((STATUS_OBJECT_NAME_NOT_FOUND == status) && DnsICSDomainSuffix) { NH_FREE(DnsICSDomainSuffix); DnsICSDomainSuffix = NULL; } // // if we have no idea of the string, set our copy to default string // if (NULL == DnsICSDomainSuffix) { dwSize = wcslen(DNS_HOMENET_SUFFIX) + 1; DnsICSDomainSuffix = reinterpret_cast( NH_ALLOCATE(sizeof(WCHAR) * dwSize) ); if (!DnsICSDomainSuffix) { NhTrace( TRACE_FLAG_DNS, "DnsQueryRegistryICSDomainSuffix: allocation " "failed for DnsICSDomainSuffix" ); return; } wcscpy(DnsICSDomainSuffix, DNS_HOMENET_SUFFIX); fSuffixChanged = TRUE; } } else { // // overwrite our current version of suffix string // dwSize = lstrlenW((PWCHAR)Information->Data); if (dwSize) { // // we have a nonzero string // dwSize++; // add 1 for terminating null } else { // // the data is a null string - use default suffix // dwSize = wcslen(DNS_HOMENET_SUFFIX) + 1; fUseDefaultSuffix = TRUE; } if (DnsICSDomainSuffix) { NH_FREE(DnsICSDomainSuffix); DnsICSDomainSuffix = NULL; } DnsICSDomainSuffix = reinterpret_cast( NH_ALLOCATE(sizeof(WCHAR) * dwSize) ); if (!DnsICSDomainSuffix) { NH_FREE(Information); NhTrace( TRACE_FLAG_DNS, "DnsQueryRegistryICSDomainSuffix: allocation " "failed for DnsICSDomainSuffix" ); return; } if (!fUseDefaultSuffix) { wcscpy(DnsICSDomainSuffix, (PWCHAR) Information->Data); } else { wcscpy(DnsICSDomainSuffix, DNS_HOMENET_SUFFIX); } fSuffixChanged = TRUE; NH_FREE(Information); } if (fSuffixChanged) { // // enumerate existing entries and replace old ones // + we must do this because otherwise forward and reverse lookups // are dependent on the way in which the entries are ordered in // the hosts.ics file // //DnsReplaceOnSuffixChange(); } } // DnsQueryRegistryICSDomainSuffix ULONG DnsQueryICSDomainSuffix( VOID ) /*++ Routine Description: This routine invokes DnsQueryRegistryICSDomainSuffix and installs change notification for this reg key if necessary. Arguments: none. Return Value: ULONG - Win32 status code. Environment: Invoked in an arbitrary context with 'DnsGlobalInfoLock' acquired by the caller. --*/ { PUCHAR Buffer; ULONG Error; PKEY_VALUE_PARTIAL_INFORMATION Information; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS status; UNICODE_STRING UnicodeString; PROFILE("DnsQueryICSDomainSuffix"); if (!DnsTcpipParametersKey) { RtlInitUnicodeString(&UnicodeString, DnsTcpipParametersString); InitializeObjectAttributes( &ObjectAttributes, &UnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); // // Open the 'Tcpip\Parameters' registry key // status = NtOpenKey( &DnsTcpipParametersKey, KEY_ALL_ACCESS, &ObjectAttributes ); if (!NT_SUCCESS(status)) { Error = RtlNtStatusToDosError(status); NhTrace( TRACE_FLAG_DNS, "DnsQueryICSDomainSuffix: error %x opening registry key", status ); NhErrorLog( IP_DNS_PROXY_LOG_NO_ICSD_SUFFIX, Error, "" ); return Error; } } // // See if we need to install change-notification, // and reference ourselves if so. // The reference is made on behalf of the change-notification routine // which will be invoked by a wait-thread when a change occurs. // if (!DnsNotifyChangeKeyICSDomainEvent && REFERENCE_DNS()) { // // Attempt to set up change notification on the key // status = NtCreateEvent( &DnsNotifyChangeKeyICSDomainEvent, EVENT_ALL_ACCESS, NULL, SynchronizationEvent, FALSE ); if (!NT_SUCCESS(status)) { DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryICSDomainSuffix: status %08x creating notify-change event", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_ICSD_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } else { // // Register a wait on the notify-change event // status = RtlRegisterWait( &DnsNotifyChangeKeyICSDomainWaitHandle, DnsNotifyChangeKeyICSDomainEvent, DnsNotifyChangeKeyICSDomainCallbackRoutine, NULL, INFINITE, 0 ); if (!NT_SUCCESS(status)) { NtClose(DnsNotifyChangeKeyICSDomainEvent); DnsNotifyChangeKeyICSDomainEvent = NULL; DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryICSDomainSuffix: status %08x registering wait", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_ICSD_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } else { // // Register for change-notification on the key // status = NtNotifyChangeKey( DnsTcpipParametersKey, DnsNotifyChangeKeyICSDomainEvent, NULL, NULL, &DnsNotifyChangeKeyICSDomainIoStatus, REG_NOTIFY_CHANGE_LAST_SET, FALSE, // not interested in the subtree NULL, 0, TRUE ); if (!NT_SUCCESS(status)) { RtlDeregisterWait(DnsNotifyChangeKeyICSDomainWaitHandle); DnsNotifyChangeKeyICSDomainWaitHandle = NULL; NtClose(DnsNotifyChangeKeyICSDomainEvent); DnsNotifyChangeKeyICSDomainEvent = NULL; DEREFERENCE_DNS(); NhTrace( TRACE_FLAG_DNS, "DnsQueryICSDomainSuffix: status %08x (%08x) " "enabling change notify", status ); NhWarningLog( IP_DNS_PROXY_LOG_CHANGE_ICSD_NOTIFY_FAILED, RtlNtStatusToDosError(status), "" ); } } } } DnsQueryRegistryICSDomainSuffix(); return NO_ERROR; } // DnsQueryICSDomainSuffix PDNS_QUERY DnsRecordQuery( PDNS_INTERFACE Interfacep, PNH_BUFFER QueryBuffer ) /*++ Routine Description: This routine is invoked to create a pending-query entry for a client's DNS query. Arguments: Interfacep - the interface on which to create the record QueryBuffer - the DNS request for which to create a record Return Value: PDNS_QUERY - the pending query if created Environment: Invoked with 'Interfacep' locked by the caller. --*/ { BOOLEAN ConflictFound; PDNS_HEADER Headerp; PLIST_ENTRY Link; USHORT QueryId; PDNS_QUERY Queryp; ULONG RetryCount = MAXCHAR; ULONG Seed = GetTickCount(); PROFILE("DnsRecordQuery"); // // Attempt to generate a random ID for the query. // Assuming we succeed, we leave the loop with 'Link' // set to the correct insertion-point for the new query. // do { QueryId = (USHORT)((RtlRandom(&Seed) & 0xffff0000) >> 16); ConflictFound = FALSE; for (Link = Interfacep->QueryList.Flink; Link != &Interfacep->QueryList; Link = Link->Flink) { Queryp = CONTAINING_RECORD(Link, DNS_QUERY, Link); if (QueryId > Queryp->QueryId) { continue; } else if (QueryId < Queryp->QueryId) { break; } ConflictFound = TRUE; break; } } while(ConflictFound && --RetryCount); if (ConflictFound) { return NULL; } // // Allocate and initialize the new query // Queryp = reinterpret_cast(NH_ALLOCATE(sizeof(DNS_QUERY))); if (!Queryp) { NhTrace( TRACE_FLAG_DNS, "DnsRecordQuery: allocation failed for DNS query" ); NhErrorLog( IP_DNS_PROXY_LOG_ALLOCATION_FAILED, 0, "%d", sizeof(DNS_QUERY) ); return NULL; } Headerp = (PDNS_HEADER)QueryBuffer->Buffer; Queryp->QueryId = QueryId; Queryp->SourceId = Headerp->Xid; Queryp->SourceAddress = QueryBuffer->ReadAddress.sin_addr.s_addr; Queryp->SourcePort = QueryBuffer->ReadAddress.sin_port; Queryp->Type = DNS_PROXY_PORT_TO_TYPE(NhQueryPortSocket(QueryBuffer->Socket)); Queryp->QueryLength = QueryBuffer->BytesTransferred; Queryp->Bufferp = QueryBuffer; Queryp->Interfacep = Interfacep; Queryp->TimerHandle = NULL; Queryp->RetryCount = 0; // // Insert the new query in the location determined above. // InsertTailList(Link, &Queryp->Link); return Queryp; } // DnsRecordQuery ULONG DnsSendQuery( PDNS_INTERFACE Interfacep, PDNS_QUERY Queryp, BOOLEAN Resend ) /*++ Routine Description: This routine is invoked to forward a query to our DNS servers. Arguments: Interfacep - the interface on which to send the query Queryp - the DNS request to be sent Resend - if TRUE, the buffer is being resent; otherwise, the buffer is being sent for the first time. Return Value: ULONG - Win32 status code. On success, 'Queryp' may have been deleted. Environment: Invoked with 'Interfacep' locked by the caller, and with a reference made to it for the send which occurs here. If the routine fails, it is the caller's responsibility to release that reference. --*/ { PNH_BUFFER Bufferp; ULONG Error; ULONG i, j; PULONG ServerList; SOCKET Socket; NTSTATUS status; PDNS_QUERY_TIMEOUT_CONTEXT TimeoutContext; ULONG TimeoutSeconds; PROFILE("DnsSendQuery"); // // For WINS queries, we use a global socket to work around the fact that // even though we're bound to the WINS port, responses will only be // delivered to the first socket bound to the socket, which is // the kernel-mode NetBT driver. // EnterCriticalSection(&DnsGlobalInfoLock); if (Queryp->Type == DnsProxyDns) { Socket = Queryp->Bufferp->Socket; ServerList = DnsServerList[DnsProxyDns]; } else { Socket = DnsGlobalSocket; ServerList = DnsServerList[DnsProxyWins]; } LeaveCriticalSection(&DnsGlobalInfoLock); // // See if there are any servers to be tried. // if (!ServerList || !ServerList[0] || Queryp->RetryCount++ > DNS_QUERY_RETRY) { if (!ServerList) { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: no server list" ); } else if (!ServerList[0]) { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: no server entries in list" ); } else { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: retry count for query %d " "greater than DNS_QUERY_RETRY(%d)", Queryp->QueryId, DNS_QUERY_RETRY ); } if (REFERENCE_DNS()) { // // Initiate an attempt to connect the default interface, if any. // status = RtlQueueWorkItem( DnsConnectDefaultInterface, NULL, WT_EXECUTEINIOTHREAD ); if (!NT_SUCCESS(status)) { DEREFERENCE_DNS(); } } NhInformationLog( IP_DNS_PROXY_LOG_NO_SERVERS_LEFT, 0, "%I", Queryp->SourceAddress ); return ERROR_NO_MORE_ITEMS; } // // Send the query to each server on the list // for (i = 0; ServerList[i]; i++) { for (j = 0; j < Interfacep->BindingCount; j++) { if (Interfacep->BindingArray[j].Address == ServerList[i]) { break; } } if (j < Interfacep->BindingCount) { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: server %s is self, ignoring", INET_NTOA(ServerList[i]) ); continue; } if (!DNS_REFERENCE_INTERFACE(Interfacep) || !(Bufferp = NhDuplicateBuffer(Queryp->Bufferp))) { continue; } NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: sending query %d interface %d to %s", (PVOID)((PDNS_HEADER)Bufferp->Buffer)->Xid, Interfacep->Index, INET_NTOA(ServerList[i]) ); // // Send the message // Error = NhWriteDatagramSocket( &DnsComponentReference, Socket, ServerList[i], DNS_PROXY_TYPE_TO_PORT(Queryp->Type), Bufferp, Queryp->QueryLength, DnsWriteCompletionRoutine, Interfacep, (PVOID)Queryp->QueryId ); if (!Error) { InterlockedIncrement( reinterpret_cast(&DnsStatistics.QueriesSent) ); } else { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: error %d sending query %d interface %d", Error, Queryp->QueryId, Interfacep->Index ); NhErrorLog( IP_DNS_PROXY_LOG_QUERY_FAILED, Error, "%I%I%I", Queryp->SourceAddress, ServerList[i], NhQueryAddressSocket(Bufferp->Socket) ); Error = NO_ERROR; NhReleaseBuffer(Bufferp); DNS_DEREFERENCE_INTERFACE(Interfacep); } } // // Set up the query's timeout. // Note that we are now certain that the write-completion routine // will be executed. However, if the timeout cannot be set, // we want to be assured that the query will still be deleted. // Therefore, on failure we delete the query immediately, // and the write-completion routine will simply not find it. // status = STATUS_UNSUCCESSFUL; EnterCriticalSection(&DnsGlobalInfoLock); TimeoutSeconds = DnsGlobalInfo->TimeoutSeconds; LeaveCriticalSection(&DnsGlobalInfoLock); if (Queryp->TimerHandle) { // // Update the timer-queue entry for the query // status = NhUpdateTimer( Queryp->TimerHandle, TimeoutSeconds * 1000 ); } else { // // Allocate a timer-queue entry context block // TimeoutContext = reinterpret_cast( NH_ALLOCATE(sizeof(*TimeoutContext)) ); if (!TimeoutContext) { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: error allocating query %d timeout context", Queryp->QueryId ); status = STATUS_UNSUCCESSFUL; } else { TimeoutContext->Index = Interfacep->Index; TimeoutContext->QueryId = Queryp->QueryId; // // Insert a timer-queue entry to check the status of the query // status = NhSetTimer( &DnsComponentReference, &Queryp->TimerHandle, DnspQueryTimeoutCallbackRoutine, TimeoutContext, TimeoutSeconds * 1000 ); if (!NT_SUCCESS(status)) { NH_FREE(TimeoutContext); Queryp->TimerHandle = NULL; } } } // // If the above failed, delete the query now. // if (!NT_SUCCESS(status)) { NhTrace( TRACE_FLAG_DNS, "DnsSendQuery: status %08x setting timer for query %d", status, Queryp->QueryId ); DnsDeleteQuery(Interfacep, Queryp); } DNS_DEREFERENCE_INTERFACE(Interfacep); return NO_ERROR; } // DnsSendQuery