|
|
/*++
Copyright (c) 1996-2001 Microsoft Corporation
Module Name:
send.c
Abstract:
Domain Name System (DNS) API
Send response routines.
Author:
Jim Gilroy (jamesg) October, 1996
Revision History:
--*/
#include "local.h"
//
// Disjoint name space
//
// If DNS name space is disjoint then NAME_ERROR response from one
// adapter does NOT necessarily mean that name does not exist. Rather
// must continue on other adapters.
//
// This flag should be set if name space is disjoint, off otherwise.
//
// DCR_PERF: auto-detect disjoint name space (really cool)
// DCR_ENHANCE: auto-detect disjoint name space (really cool)
// initially continue trying on other adapters and if they always
// coincide, then conclude non-disjoint (and turn off)
//
// DCR_ENHANCE: registry turn off of disjoint name space
//
// Note: should consider that name spaces often disjoint in that
// Intranet is hidden from Internet
//
BOOL fDisjointNameSpace = TRUE;
//
// Query \ response IP matching.
//
// Some resolvers (Win95) have required matching between DNS server IP
// queried and response. This flag allows this matching to be turned on.
// Better now than requiring SP later.
//
// DCR_ENHANCE: registry enable query\response IP matching.
//
BOOL fQueryIpMatching = FALSE;
//
// Timeouts
//
#define HARD_TIMEOUT_LIMIT 16 // 16 seconds, total of 31 seconds
#define INITIAL_UPDATE_TIMEOUT 2 // 3 seconds
#define MAX_UPDATE_TIMEOUT 24 // 24 seconds
#define DNS_MAX_QUERY_TIMEOUTS 10 // 10
#define ONE_HOUR_TIMEOUT 60*60 // One hour
// TCP timeout 10 seconds to come back
#define DEFAULT_TCP_TIMEOUT 10
// Retry limits
#define MAX_SINGLE_SERVER_RETRY (3)
#define NT_TCPIP_REG_LOCATION "System\\CurrentControlSet\\Services\\Tcpip\\Parameters"
#define WIN95_TCPIP_REG_LOCATION "System\\CurrentControlSet\\Services\\VxD\\MSTCP"
#define DNS_QUERY_TIMEOUTS "DnsQueryTimeouts"
#define DNS_QUICK_QUERY_TIMEOUTS "DnsQuickQueryTimeouts"
#define DNS_MULTICAST_QUERY_TIMEOUTS "DnsMulticastQueryTimeouts"
//
// Timeouts
// MUST have terminating 0, this signals end of timeouts.
// This is better than a timeout limit as different query types can
// have different total retries.
//
DWORD QueryTimeouts[] = { 1, // NT5 1,
1, // 2,
2, // 2,
4, // 4,
7, // 8,
0 };
DWORD RegistryQueryTimeouts[DNS_MAX_QUERY_TIMEOUTS + 1]; LPDWORD g_QueryTimeouts = QueryTimeouts;
DWORD QuickQueryTimeouts[] = { 1, 2, 2, 0 };
DWORD RegistryQuickQueryTimeouts[DNS_MAX_QUERY_TIMEOUTS + 1]; LPDWORD g_QuickQueryTimeouts = QuickQueryTimeouts;
//
// Update timeouts.
// Must be long enough to handle zone lock on primary for XFR
// or time required for DS write.
//
DWORD UpdateTimeouts[] = { 5, 10, 20, 0 };
//
// Multicast Query timeouts.
// Local only. 1sec timeout, three retries.
//
DWORD MulticastQueryTimeouts[] = { 1, 1, 1, 0 };
DWORD RegistryMulticastQueryTimeouts[DNS_MAX_QUERY_TIMEOUTS + 1]; LPDWORD g_MulticastQueryTimeouts = MulticastQueryTimeouts;
//
// Failure priority boosts
//
#define TIMEOUT_PRIORITY_DROP (10)
#define SERVER_FAILURE_PRIORITY_DROP (1)
#define NO_DNS_PRIORITY_DROP (200)
//
// Query flag
//
// Flags that terminate query on adapter
#define RUN_FLAG_COMBINED_IGNORE_ADAPTER \
(RUN_FLAG_IGNORE_ADAPTER | RUN_FLAG_STOP_QUERY_ON_ADAPTER)
//
// Return flags
//
// Flags that are not cleaned up
#define DNS_FLAGS_NOT_RESET (DNS_FLAG_IGNORE_ADAPTER)
//
// Authoritative empty response
// - map to NXRRSET for tracking in send code
//
#define DNS_RCODE_AUTH_EMPTY_RESPONSE (DNS_RCODE_NXRRSET)
//
// Dummy no-send-to-this-server error code
//
#define DNS_ERROR_NO_SEND ((DWORD)(-100))
//
// ServerInfo address type check
// Current server info address setup (a mistake) is
// IP4_ADDRESS IpAddress
// DWORD Reserved[3]
// Check that reserved DWORDs are zero is check that have
// IP4 address
//
#if 0
//
// This test is not working on IA64 -- not sure why
//
// For quickie BVT fix we'll just rule out IA64 sends
//
IP6_ADDRESS g_Ip6EmptyAddress = { 0, 0, 0, 0 }; DWORD g_Empty[4] = { 0, 0, 0, 0 };
#define IS_SERVER_INFO_ADDRESS_IP4( pIp ) \
RtlEqualMemory( \ (PDWORD)(pIp)+1, \ g_Empty, \ sizeof(DWORD) * 3 ) #endif
#define IS_SERVER_INFO_ADDRESS_IP4( pIp ) TRUE
//
// OPT failure tracking
//
BOOL Dns_IsServerOptExclude( IN IP4_ADDRESS IpAddress );
VOID Dns_SetServerOptExclude( IN IP4_ADDRESS IpAddress );
//
// Private protos
//
DNS_STATUS SendMessagePrivate( IN OUT PDNS_MSG_BUF pMsg, IN PCHAR pSendIp, IN BOOL fIp4, IN BOOL fNoOpt );
VOID TimeoutDnsServers( IN PDNS_NETINFO pNetInfo, IN DWORD dwTimeout ) /*++
Routine Description:
Mark a DNS server that timed out.
Arguments:
pNetInfo -- struct with list of DNS servers
dwTimeout -- timeout in seconds
Return Value:
None.
--*/ { PDNS_ADAPTER padapter; PDNS_SERVER_INFO pserver; DWORD lastSendIndex; DWORD i;
DNSDBG( SEND, ( "Enter TimeoutDnsServers( %p, timeout=%d )\n", pNetInfo, dwTimeout ));
DNS_ASSERT( pNetInfo );
//
// find DNS server in list,
// -- drop its priority based on timeout
// -- if already has RCODE, then did not time out
//
// if change a priority, then set flag at top of adapter list, so
// that global copy may be updated
//
for( i=0; i<pNetInfo->AdapterCount; i++ ) { padapter = pNetInfo->AdapterArray[i]; DNS_ASSERT( padapter );
lastSendIndex = padapter->ServerIndex;
if ( lastSendIndex == EMPTY_SERVER_INDEX || lastSendIndex >= padapter->ServerCount ) { continue; }
//
// found last send DNS server
// - if it responded with status, then it didn't timeout
// (if responded with success, then query completed and
// we wouldn't be in this function)
//
// - go "easy" on OPT sends;
// don't drop priority, just note timeout
//
pserver = &padapter->ServerArray[lastSendIndex];
if ( TEST_DNSSS_STATUS(pserver->Status, DNSSS_SENT) ) { DNSDBG( SEND, ( "Timeout on server index=%d (padapter=%p)\n", lastSendIndex, padapter ));
if ( TEST_DNSSS_STATUS(pserver->Status, DNSSS_SENT_NON_OPT) ) { SET_SERVER_STATUS( pserver, DNSSS_TIMEOUT_NON_OPT );
pserver->Priority += dwTimeout + TIMEOUT_PRIORITY_DROP; padapter->RunFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; pNetInfo->ReturnFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; } else { DNSDBG( SEND, ( "Timeout on server index=%d OPT only\n", lastSendIndex ));
SET_SERVER_STATUS( pserver, DNSSS_TIMEOUT_OPT ); } } } }
VOID resetOnFinalTimeout( IN PDNS_NETINFO pNetInfo ) /*++
Routine Description:
Markup network info on final timeout.
Arguments:
pNetInfo -- struct with list of DNS servers
dwTimeout -- timeout in seconds
Return Value:
None.
--*/ { DWORD i; PDNS_ADAPTER padapter;
//
// We've timed out against all DNS server for a least
// one of the adapters. Update adapter status to show
// time out error.
//
// DCR: is final timeout correct
// - worried about timeout on some but not all servers
// case; adapter shouldn't show timeout should it?
//
for ( i = 0; i < pNetInfo->AdapterCount; i++ ) { padapter = pNetInfo->AdapterArray[i];
if ( padapter->Status == NO_ERROR && padapter->ServerIndex && padapter->RunFlags & RUN_FLAG_RESET_SERVER_PRIORITY ) { padapter->Status = ERROR_TIMEOUT; } } }
DNS_STATUS ResetDnsServerPriority( IN PDNS_NETINFO pNetInfo, IN IP4_ADDRESS IpDns, //IN IP6_ADDRESS IpDns,
//IN PIP6_ADDRESS pIpDns,
IN DNS_STATUS Status ) /*++
Routine Description:
Reset priority on DNS server that sent response.
// DCR: needs IP6 entry
Arguments:
pNetInfo -- struct with list of DNS servers
IpDns -- IP address of DNS that responded
Status -- RCODE of response
Return Value:
ERROR_SUCCESS if continue query. DNS_ERROR_RCODE_NAME_ERROR if all (valid) adapters have name-error or auth-empty response.
--*/ { PDNS_ADAPTER padapter; PDNS_SERVER_INFO pserver; DWORD i; DWORD j; DNS_STATUS result = DNS_ERROR_RCODE_NAME_ERROR; #if DBG
BOOL freset = FALSE; #endif
DNSDBG( SEND, ( "Enter ResetDnsServerPriority( %p, %s rcode=%d)\n", pNetInfo, IP_STRING(IpDns), Status ));
DNS_ASSERT( pNetInfo );
//
// find DNS server in list, clear its priority field
//
// note: going through full list here after found DNS
// this is to avoid starving DNS by failing to clear priority field;
// if have guaranteed non-overlapping lists, then can terminate
// loop on find
//
for( i=0; i<pNetInfo->AdapterCount; i++ ) { padapter = pNetInfo->AdapterArray[i];
for ( j=0; j<padapter->ServerCount; j++ ) { pserver = & padapter->ServerArray[j];
if ( IpDns != pserver->IpAddress ) //if ( IpDns != *(PIP6_ADDRESS)&pserver->IpAddress )
{ continue; }
pserver->Status = Status; #if DBG
freset = TRUE; #endif
//
// no DNS running
//
// WSAECONNRESET reported for reception of ICMP unreachable, so
// no DNS is currently running on the IP; that's a severe
// priority drop, worse than just TIMEOUT
//
if ( Status == WSAECONNRESET ) { pserver->Priority += NO_DNS_PRIORITY_DROP; padapter->RunFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; pNetInfo->ReturnFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; break; }
// if SERVER_FAILURE rcode, may or may not indicate problem,
// (may be simply unable to contact remote DNS)
// but it certainly suggests trying other DNS servers in
// the list first
//
// DCR_FIX: SEVRFAIL response priority reset
// the explicitly correct approach would be to flag the
// SERVER_FAILURE error, but NOT reset the priority unless
// at the end of the query, we find another server in the list
// got a useful response
if ( Status == DNS_ERROR_RCODE_SERVER_FAILURE ) { pserver->Priority += SERVER_FAILURE_PRIORITY_DROP; padapter->RunFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; pNetInfo->ReturnFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; break; }
//
// other status code indicates functioning DNS server,
// - reset the server's priority
if ( pserver->Priority ) { pserver->Priority = 0; padapter->RunFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; pNetInfo->ReturnFlags |= RUN_FLAG_RESET_SERVER_PRIORITY; }
//
// NAME_ERROR or AUTH-EMPTY response
// - save to server list for adapter to eliminate all
// further retries on this adapter's list
// - if not waiting for all adapters, then
// NAME_ERROR or no-records is terminal
if ( Status == DNS_ERROR_RCODE_NAME_ERROR || Status == DNS_INFO_NO_RECORDS ) { padapter->Status = Status; padapter->RunFlags |= RUN_FLAG_STOP_QUERY_ON_ADAPTER;
if ( !g_WaitForNameErrorOnAll ) { result = DNS_ERROR_RCODE_NAME_ERROR; goto Done; } } break; }
//
// do running check that still adapter worth querying
// - not ignoring in first place
// - hasn't received NAME_ERROR or AUTH_EMPTY response
//
// this is "at recv" check -- only trying to determine if we
// should stop query RIGHT NOW as a result of this receive;
// this does NOT check on whether there are any other servers
// worth querying as that is done when go back for next send
//
// note how this works -- result starts as NAME_ERROR, when find
// ANY adapter that hasn't gotten terminal response, then
// result shifts (and stays) at ERROR_SUCCESS
//
// note, if we fix the twice through list issue above, then have to
// change this so don't skip adapter lists after IP is found
//
if ( !(padapter->RunFlags & RUN_FLAG_COMBINED_IGNORE_ADAPTER) ) { result = ERROR_SUCCESS; } }
Done:
#if DBG
if ( !freset ) { DNSDBG( ANY, ( "ERROR: DNS server %s not in list.\n", IP_STRING(IpDns) )); DNS_ASSERT( FALSE ); } #endif
return( result ); }
PDNS_SERVER_INFO bestDnsServerForNextSend( IN PDNS_ADAPTER pAdapter ) /*++
Routine Description:
Get best DNS server IP address from list.
Arguments:
pAdapter -- struct with list of DNS servers
Return Value:
Ptr to server info of best send. NULL if no server on adapter is worth sending to; this is the case if all servers have received a response.
--*/ { PDNS_SERVER_INFO pserver; DWORD i; DWORD status; DWORD priority; DWORD priorityBest = MAXDWORD; PDNS_SERVER_INFO pbestServer = NULL; DWORD bestIndex = EMPTY_SERVER_INDEX;
DNSDBG( SEND, ( "Enter bestDnsServerForNextSend( %p )\n", pAdapter ));
if ( !pAdapter || !pAdapter->ServerCount ) { DNSDBG( SEND, ( "WARNING: Leaving bestDnsServerForNextSend, no server list\n" )); return( NULL ); }
//
// if already received name error on server in this list, done
//
if ( pAdapter->Status == DNS_ERROR_RCODE_NAME_ERROR || pAdapter->Status == DNS_INFO_NO_RECORDS ) { DNSDBG( SEND, ( "Leaving bestDnsServerForNextSend, NAME_ERROR already received\n" "\ton server in server list %p\n", pAdapter )); return( NULL ); }
//
// check each server in list
//
for ( i=0; i<pAdapter->ServerCount; i++ ) { pserver = & pAdapter->ServerArray[i];
// if server has already recieved a response, then skip it
status = pserver->Status;
if ( TEST_DNSSS_VALID_RECV(status) ) { // NAME_ERROR or EMPTY_AUTH then adapter should have been
// marked as "done" and we shouldn't be here
// NO_ERROR should have exited immediately
DNS_ASSERT( status != NO_ERROR && status != DNS_ERROR_RCODE_NAME_ERROR && status != DNS_INFO_NO_RECORDS ); continue; }
// return first "clean" server
// or return one with lowest dings
//
// DCR: skip NO_DNS server for a while
// skip timeout server for a little while
// perhaps this should be done be ignoring these
// when list is sent down?
priority = pserver->Priority;
if ( priority < priorityBest ) { bestIndex = i; pbestServer = pserver;
if ( priority == 0 ) { break; } } }
// save off IP of server we are using
if ( pbestServer ) { pAdapter->ServerIndex = bestIndex; } return( pbestServer ); }
DNS_STATUS SendUsingServerInfo( IN OUT PDNS_MSG_BUF pMsg, IN OUT PDNS_SERVER_INFO pServInfo ) /*++
Routine Description:
Send DNS message using server info.
This function encapsulates the process of checking server info for validity, sending (as appropriate) and marking servinfo result.
Note: right now this is UDP only; may need to expand
Arguments:
pMsg - message info for message to send
pServInfo - info of server to send to
Return Value:
ERROR_SUCCESS if successful. ErrorCode on send failure.
--*/ { DNS_STATUS status; BOOL fnoOpt; BOOL fip4; IP4_ADDRESS ip4;
DNSDBG( SEND, ( "SendUsingServerInfo( msg=%p, servinfo=%p )\n", pMsg ));
//
// check that haven't already completed send\recv
//
if ( TEST_DNSSS_VALID_RECV( pServInfo->Status ) ) { return DNS_ERROR_NO_SEND; }
//
// check OPT status
// - previous OPT send that timed OUT, then send non-OPT
//
// DCR: known OPT-ok list could screen wasted send
fnoOpt = TEST_DNSSS_STATUS( pServInfo->Status, DNSSS_SENT_OPT );
//
// send
//
status = SendMessagePrivate( pMsg, (PCHAR) &pServInfo->IpAddress, IS_SERVER_INFO_ADDRESS_IP4( &pServInfo->IpAddress ), fnoOpt );
if ( status == ERROR_SUCCESS ) { DNS_ASSERT( !fnoOpt || !pMsg->fLastSendOpt );
SET_SERVER_STATUS( pServInfo, pMsg->fLastSendOpt ? DNSSS_SENT_OPT : DNSSS_SENT_NON_OPT); }
return status; }
DNS_STATUS SendUdpToNextDnsServers( IN OUT PDNS_MSG_BUF pMsgSend, IN OUT PDNS_NETINFO pNetInfo, IN DWORD cRetryCount, IN DWORD dwTimeout, OUT PDWORD pSendCount ) /*++
Routine Description:
Sends to next DNS servers in list.
Arguments:
pMsgSend -- message to send
pNetInfo -- per adapter DNS info
cRetryCount -- retry for this send
dwTimeout -- timeout on last send, if timed out
pSendCount -- addr to receive send count
Return Value:
ERROR_SUCCESS if successful send. ERROR_TIMEOUT if no DNS servers left to send to. Winsock error code on send failure.
--*/ { DWORD i; DWORD j; DWORD sendCount = 0; PDNS_ADAPTER padapter; PDNS_SERVER_INFO pserver; DNS_STATUS status = ERROR_TIMEOUT;
DNSDBG( SEND, ( "Enter SendUdpToNextDnsServers()\n" "\tretry = %d\n", cRetryCount ));
//
// if netinfo not initialized for send, init
//
if ( !(pNetInfo->ReturnFlags & RUN_FLAG_NETINFO_PREPARED) ) { DNSDBG( SEND, ( "Netinfo not prepared for send -- preparing now.\n" ));
NetInfo_Clean( pNetInfo, CLEAR_LEVEL_QUERY ); }
#if DBG
//
// verify i'm getting a clean list on start
//
if ( cRetryCount == 0 ) { for( i=0; i<pNetInfo->AdapterCount; i++ ) { padapter = pNetInfo->AdapterArray[i];
// ignore this adapter because there are no DNS
// servers configured?
if ( padapter->InfoFlags & DNS_FLAG_IGNORE_ADAPTER ) { continue; }
DNS_ASSERT( padapter->ServerIndex == EMPTY_SERVER_INDEX ); DNS_ASSERT( padapter->Status == 0 );
for ( j=0; j<padapter->ServerCount; j++ ) { DNS_ASSERT( padapter->ServerArray[j].Status == DNSSS_NEW ); } } } #endif
//
// if previous send timed out, update adapter list
// - but ONLY do this when sending to individual servers in list
// - timeout on all servers just produces an unnecessary copy and
// can only change ordering relative to servers which have already
// responded with RCODE; since its a timeout, this isn't going to
// lower these server's priority so no point
//
if ( dwTimeout && cRetryCount && cRetryCount < MAX_SINGLE_SERVER_RETRY ) { TimeoutDnsServers( pNetInfo, dwTimeout ); }
//
// send on DNS server(s) for adapter(s)
//
for( i=0; i<pNetInfo->AdapterCount; i++ ) { padapter = pNetInfo->AdapterArray[i];
// ignore this adapter
// - no DNS servers
// - not querying this adapter name
// - already responded to this name
if ( ( padapter->InfoFlags & DNS_FLAG_IGNORE_ADAPTER ) || ( padapter->RunFlags & RUN_FLAG_STOP_QUERY_ON_ADAPTER ) ) { continue; }
//
// first three attempts, we only go to one DNS on a given adapter
//
// - first time through ONLY to first server in first adapter list
// - on subsequent tries go to best server in all lists
//
if ( cRetryCount < MAX_SINGLE_SERVER_RETRY ) { pserver = bestDnsServerForNextSend( padapter ); if ( !pserver ) { continue; }
status = SendUsingServerInfo( pMsgSend, pserver );
if ( status == ERROR_SUCCESS ) { sendCount++; if ( cRetryCount == 0 ) { break; } continue; } if ( status == DNS_ERROR_NO_SEND ) { continue; }
// quit on send error
break; }
//
// after first three tries, send to all servers that
// have not already responded (have RCODE, as if NO_ERROR) we
// already finished
//
else { for ( j=0; j<padapter->ServerCount; j++ ) { status = SendUsingServerInfo( pMsgSend, &padapter->ServerArray[j] );
if ( status == ERROR_SUCCESS ) { sendCount++; continue; } if ( status == DNS_ERROR_NO_SEND ) { continue; } break; } } }
//
// if sent packet, success
//
*pSendCount = sendCount;
DNSDBG( SEND, ( "Leave SendUdpToNextDnsServers()\n" "\tsends = %d\n", sendCount ));
if ( sendCount ) { return( ERROR_SUCCESS ); }
// if no packets sent, alert caller we're done
// - this is possible if servers have responded uselessly
// (NAME_ERROR, SERVER_FAILURE)
if ( status == ERROR_SUCCESS ) { status = ERROR_TIMEOUT; } return( status ); }
//
// Send routines
//
VOID SetMsgRemoteSockaddr( IN OUT PDNS_MSG_BUF pMsg, IN PIP6_ADDRESS pIpAddr, IN BOOL fIp4 ) /*++
Routine Description:
Initialize remote sockaddr.
Note: this handles IP4 or IP6 This could be changed to simply test for IP4_MAPPED and simply pass address pointer.
Arguments:
pMsg - message to send
fIp4 - TRUE if IP4
pIp6Addr - IP address to send
Return Value:
None.
--*/ { // zero
RtlZeroMemory( & pMsg->RemoteAddress, sizeof( pMsg->RemoteAddress ) );
//
// fill in for IP4 or IP6
//
// DCR: just pass in IP6_ADDRESS
// then test for V4 mapped
// if ( IN6_IS_ADDR_V4MAPPED(IpAddress) )
//
if ( fIp4 ) { pMsg->RemoteAddress.In4.sin_family = AF_INET; pMsg->RemoteAddress.In4.sin_port = DNS_PORT_NET_ORDER; pMsg->RemoteAddress.In4.sin_addr.s_addr = *(PIP4_ADDRESS) pIpAddr;
pMsg->RemoteAddressLength = sizeof(SOCKADDR_IN); } else { pMsg->RemoteAddress.In6.sin6_family = AF_INET6; pMsg->RemoteAddress.In6.sin6_port = DNS_PORT_NET_ORDER;
RtlCopyMemory( (PIP6_ADDRESS) &pMsg->RemoteAddress.In6.sin6_addr, pIpAddr, sizeof(IP6_ADDRESS) );
pMsg->RemoteAddressLength = sizeof(SOCKADDR_IN6); } }
VOID Dns_InitializeMsgRemoteSockaddr( IN OUT PDNS_MSG_BUF pMsg, IN IP4_ADDRESS IpAddr ) /*++
Routine Description:
Initialize remote sockaddr.
Note: EXPORTED function
// DCR: EXPORTED may remove when clean
Arguments:
pMsg - message to send
IpAddr - IP4 address to send to
Return Value:
None.
--*/ { IP4_ADDRESS ip4 = IpAddr;
SetMsgRemoteSockaddr( pMsg, (PIP6_ADDRESS) &ip4, TRUE // IP4
); }
DNS_STATUS SendMessagePrivate( IN OUT PDNS_MSG_BUF pMsg, IN PCHAR pSendIp, IN BOOL fIp4, IN BOOL fNoOpt ) /*++
Routine Description:
Send a DNS packet.
This is the generic send routine used for ANY send of a DNS message.
It assumes nothing about the message type, but does assume: - pCurrent points at byte following end of desired data - RR count bytes are in HOST byte order
Arguments:
pMsg - message info for message to send
pSendIp - ptr to IP address to send to OPTIONAL, required only if UDP and message sockaddr not set
fIp4 -- TRUE if IP4, FALSE for IP6
fNoOpt - TRUE if OPT send is forbidden
Return Value:
TRUE if successful. FALSE on send error.
--*/ { PDNS_HEADER pmsgHead; INT err; WORD sendLength; BOOL fexcludedOpt = FALSE;
DNSDBG( SEND, ( "SendMessagePrivate()\n" "\tpMsg = %p\n" "\tpSendIp = %p\n" "\tIs IP4 = %d\n" "\tNo OPT = %d\n", pMsg, pSendIp, fIp4, fNoOpt ));
//
// set header flags
//
// note: since route sends both queries and responses
// caller must set these flags
//
pmsgHead = &pMsg->MessageHead; pmsgHead->Reserved = 0;
//
// set send IP (if given)
//
if ( pSendIp ) { SetMsgRemoteSockaddr( pMsg, (PIP6_ADDRESS) pSendIp, fIp4 ); }
//
// set message length and OPT inclusion
//
// OPT approach is
// - write to pCurrent packet end
// - handles NO OPT written and using OPT
// - unless HAVE written OPT, and specifically excluding
// note, that zero IP (TCP previously connected) gets
// excluded
//
// DCR: we haven't handled OPT for TCP connected and not-aware of IP
// case here
//
// DCR: for now excluding OPT on updates, because harder to detect on
// the recv end why the reason for the failure
//
{ PCHAR pend = pMsg->pCurrent;
if ( pMsg->pPreOptEnd && ( fNoOpt || g_UseEdns == 0 || pMsg->MessageHead.Opcode == DNS_OPCODE_UPDATE || Dns_IsServerOptExclude( MSG_REMOTE_IP4(pMsg) ) ) ) { ASSERT( pMsg->pPreOptEnd > (PCHAR)pmsgHead ); ASSERT( pMsg->pPreOptEnd < pend );
pend = pMsg->pPreOptEnd; pmsgHead->AdditionalCount--; fexcludedOpt = TRUE; }
sendLength = (WORD)(pend - (PCHAR)pmsgHead);
pMsg->fLastSendOpt = (pMsg->pPreOptEnd && (pend != pMsg->pPreOptEnd)); }
IF_DNSDBG( SEND ) { pMsg->MessageLength = sendLength; DnsDbg_Message( "Sending packet", pMsg ); }
//
// flip header count bytes
//
DNS_BYTE_FLIP_HEADER_COUNTS( pmsgHead );
//
// TCP -- send until all info transmitted
//
if ( pMsg->fTcp ) { PCHAR psend;
//
// TCP message always begins with bytes being sent
//
// - send length = message length plus two byte size
// - flip bytes in message length
// - send starting at message length
//
pMsg->MessageLength = htons( sendLength );
sendLength += sizeof(WORD);
psend = (PCHAR) &pMsg->MessageLength;
while ( sendLength ) { err = send( pMsg->Socket, psend, (INT) sendLength, 0 );
if ( err == 0 || err == SOCKET_ERROR ) { err = GetLastError();
//
// WSAESHUTDOWN is ok, client got timed out connection and
// closed
//
// WSAENOTSOCK may also occur if FIN recv'd and connection
// closed by TCP receive thread before the send
//
if ( err == WSAESHUTDOWN ) { IF_DNSDBG( ANY ) { DNS_PRINT(( "WARNING: send() failed on shutdown socket %d.\n" "\tpMsgInfo at %p\n", pMsg->Socket, pMsg )); } } else if ( err == WSAENOTSOCK ) { IF_DNSDBG( ANY ) { DNS_PRINT(( "ERROR: send() on closed socket %d.\n" "\tpMsgInfo at %p\n", pMsg->Socket, pMsg )); } } else { DNS_LOG_EVENT( DNS_EVENT_SEND_CALL_FAILED, 0, NULL, err );
IF_DNSDBG( ANY ) { DNS_PRINT(( "ERROR: TCP send() failed, err = %d.\n" )); } } goto Done; } sendLength -= (WORD)err; psend += err; } }
//
// UDP
//
else { DNS_ASSERT( sendLength <= DNS_RFC_MAX_UDP_PACKET_LENGTH );
err = sendto( pMsg->Socket, (PCHAR) pmsgHead, sendLength, 0, (PSOCKADDR) &pMsg->RemoteAddress, pMsg->RemoteAddressLength );
if ( err == SOCKET_ERROR ) { err = GetLastError();
DNS_LOG_EVENT( DNS_EVENT_SENDTO_CALL_FAILED, 0, NULL, err );
IF_DNSDBG( ANY ) { DNS_PRINT(( "ERROR: UDP sendto() failed.\n" ));
DnsDbg_SockaddrIn( "sendto() failed sockaddr\n", (PSOCKADDR_IN) &pMsg->RemoteAddress, pMsg->RemoteAddressLength );
DnsDbg_Message( "sendto() failed message", pMsg ); } goto Done; } }
err = ERROR_SUCCESS;
Done:
DNS_BYTE_FLIP_HEADER_COUNTS( pmsgHead );
// restore OPT in count if required
if ( fexcludedOpt ) { pmsgHead->AdditionalCount++; }
Trace_LogSendEvent( pMsg, err );
return( (DNS_STATUS)err ); }
DNS_STATUS Dns_SendEx( IN OUT PDNS_MSG_BUF pMsg, IN IP4_ADDRESS SendIp, OPTIONAL IN BOOL fNoOpt ) /*++
Routine Description:
Send a DNS packet.
This is the generic send routine used for ANY send of a DNS message.
It assumes nothing about the message type, but does assume: - pCurrent points at byte following end of desired data - RR count bytes are in HOST byte order
Note: EXPORTED function
DCR: Remove Dns_SendEx() from export when ICS fixed
Arguments:
pMsg - message info for message to send
SendIp - IP to send to; OPTIONAL, required only if UDP and message sockaddr not set
fNoOpt - TRUE if OPT send is forbidden
Return Value:
TRUE if successful. FALSE on send error.
--*/ { IP4_ADDRESS ip4 = SendIp;
return SendMessagePrivate( pMsg, (PCHAR) &ip4, TRUE, // sending IP4
fNoOpt ); }
//
// UDP routines
//
VOID Dns_SendMultipleUdp( IN OUT PDNS_MSG_BUF pMsg, IN PIP_ARRAY aipSendAddrs ) /*++
Routine Description:
Send a DNS packet to multiple destinations.
Assumes packet is in same state as normal send - host order count and XID - pCurrent pointing at byte after desired data
Arguments:
pMsg - message info for message to send and reuse
aipSendAddrs - IP array of addrs to send to
Return Value:
None.
--*/ { DWORD i;
//
// no targets
//
if ( !aipSendAddrs ) { return; }
//
// send the to each address specified in IP array
//
for ( i=0; i < aipSendAddrs->AddrCount; i++ ) { SendMessagePrivate( pMsg, (PCHAR) &aipSendAddrs->AddrArray[i], TRUE, // IP4
0 ); } }
DNS_STATUS Dns_RecvUdp( IN OUT PDNS_MSG_BUF pMsg ) /*++
Routine Description:
Receives DNS message
Arguments:
pMsg - message buffer for recv
Return Value:
ERROR_SUCCESS if successful. Error status on failure.
--*/ { SOCKET s; PDNS_HEADER pdnsHeader; LONG err = ERROR_SUCCESS; struct timeval selectTimeout; struct fd_set fdset;
DNSDBG( RECV, ( "Enter Dns_RecvUdp( %p )\n", pMsg ));
DNS_ASSERT( !pMsg->fTcp );
//
// verify socket
//
s = pMsg->Socket; if ( s == 0 || s == INVALID_SOCKET ) { return( ERROR_INVALID_PARAMETER ); }
FD_ZERO( &fdset ); FD_SET( s, &fdset );
if ( pMsg->Timeout > HARD_TIMEOUT_LIMIT && pMsg->Timeout != ONE_HOUR_TIMEOUT ) { DNSDBG( RECV, ( "ERROR: timeout %d, exceeded hard limit.\n", pMsg->Timeout ));
return( ERROR_TIMEOUT ); } selectTimeout.tv_usec = 0; selectTimeout.tv_sec = pMsg->Timeout;
pdnsHeader = &pMsg->MessageHead;
//
// wait for stack to indicate packet reception
//
err = select( 0, &fdset, NULL, NULL, &selectTimeout );
if ( err <= 0 ) { if ( err < 0 ) { // select error
err = WSAGetLastError(); DNS_PRINT(( "ERROR: select() error = %d\n", err )); return( err ); } else { DNS_PRINT(( "ERROR: timeout on response %p\n", pMsg )); return( ERROR_TIMEOUT ); } }
//
// receive
//
err = recvfrom( s, (PCHAR) pdnsHeader, DNS_MAX_UDP_PACKET_BUFFER_LENGTH, 0, (PSOCKADDR) &pMsg->RemoteAddress, &pMsg->RemoteAddressLength );
if ( err == SOCKET_ERROR ) { err = GetLastError();
Trace_LogRecvEvent( pMsg, err, FALSE // UDP
);
if ( err == WSAECONNRESET ) { //DNS_ASSERT( MSG_REMOTE_IP4(pMsg) != 0 );
DNSDBG( RECV, ( "Leave Dns_RecvUdp( %p ) with CONNRESET\n", pMsg )); return( err ); }
// message sent was too big
// sender was in error -- should have sent TCP
if ( err == WSAEMSGSIZE ) { pMsg->MessageLength = DNS_MAX_UDP_PACKET_BUFFER_LENGTH;
DnsDbg_Message( "ERROR: Recv UDP packet over 512 bytes.\n", pMsg ); } IF_DNSDBG( ANY ) { DnsDbg_Lock(); DNS_PRINT(( "ERROR: recvfrom(sock=%d) of UDP request failed.\n" "\tGetLastError() = 0x%08lx.\n", socket, err )); DnsDbg_SockaddrIn( "recvfrom failed sockaddr\n", &pMsg->RemoteAddress, pMsg->RemoteAddressLength ); DnsDbg_Unlock(); } return( err ); } else { //DNS_ASSERT( MSG_REMOTE_IP4(pMsg) != 0 );
DNS_ASSERT( err <= DNS_MAX_UDP_PACKET_BUFFER_LENGTH ); pMsg->MessageLength = (WORD)err; err = ERROR_SUCCESS; }
//
// put header fields in host order
//
DNS_BYTE_FLIP_HEADER_COUNTS( &pMsg->MessageHead ); //pMsg->fSwapped = FALSE;
Trace_LogRecvEvent( pMsg, 0, FALSE // UDP
);
IF_DNSDBG( RECV ) { DnsDbg_Message( "Received message", pMsg ); } return( err ); }
DNS_STATUS Dns_SendAndRecvUdp( IN OUT PDNS_MSG_BUF pMsgSend, OUT PDNS_MSG_BUF pMsgRecv, IN DWORD dwFlags, IN PIP_ARRAY aipServers, IN OUT PDNS_NETINFO pNetInfo ) /*++
Routine Description:
Sends to and waits to recv from remote DNS.
Arguments:
pMsgSend - message to send
ppMsgRecv - and reuse
dwFlags -- query flags
aipServers - servers to use; if no adapter info is specified this list is used
pNetInfo -- adapter list DNS server info
Return Value:
ERROR_SUCCESS if successful response. Error status for "best RCODE" response if rcode. ERROR_TIMEOUT on timeout. Error status on send\recv failure.
--*/ { SOCKET s; INT fcreatedSocket = FALSE; INT retry; DWORD timeout; DNS_STATUS status = ERROR_TIMEOUT; //IP6_ADDRESS recvIp = 0;
//PIP6_ADDRESS recvIp = 0;
IP4_ADDRESS recvIp = 0; DWORD rcode = 0; DWORD ignoredRcode = 0; DWORD sendCount; DWORD sentCount; DWORD sendTime; BOOL frecvRetry; BOOL fupdate = FALSE; // prefix
PDNS_NETINFO ptempNetInfo = NULL;
DNSDBG( SEND, ( "Enter Dns_SendAndRecvUdp()\n" "\ttime %d\n" "\tsend msg at %p\n" "\tsocket %d\n" "\trecv msg at %p\n" "\tflags %08x\n" "\tserver IP arrap %p\n" "\tadapter info at %p\n", Dns_GetCurrentTimeInSeconds(), pMsgSend, pMsgSend->Socket, pMsgRecv, dwFlags, aipServers, pNetInfo ));
// verify params
if ( !pMsgSend || !pMsgRecv || (!pNetInfo && !aipServers) ) { return( ERROR_INVALID_PARAMETER ); }
//
// server IP array? if given build temp adapter info
//
if ( aipServers ) { ptempNetInfo = NetInfo_CreateFromIpArray( aipServers, 0, // no single IP
FALSE, // no search info
NULL ); if ( !ptempNetInfo ) { return( DNS_ERROR_NO_MEMORY ); } pNetInfo = ptempNetInfo; }
// create socket if necessary
s = pMsgSend->Socket; if ( s == 0 || s == INVALID_SOCKET ) { s = Dns_GetUdpSocket(); if ( s == INVALID_SOCKET ) { status = GetLastError(); goto Done; } pMsgSend->Socket = s; pMsgSend->fTcp = FALSE; fcreatedSocket = TRUE; }
// if already have TCP socket -- invalid
//
// problem is we either leak TCP socket, or we close
// it here and may screw things up at higher level
else if ( pMsgSend->fTcp ) { status = ERROR_INVALID_PARAMETER; goto Done; }
pMsgRecv->Socket = s; pMsgRecv->fTcp = FALSE;
// determine UPDATE or standard QUERY
fupdate = ( pMsgSend->MessageHead.Opcode == DNS_OPCODE_UPDATE );
//
// loop sending until
// - receive successful response
// or
// - receive errors response from all servers
// or
// - reach final timeout on all servers
//
//
// DCR: should support setting of timeouts on individual queries
//
retry = 0; sendCount = 0;
while ( 1 ) { if ( fupdate ) { timeout = UpdateTimeouts[retry]; } else { if ( dwFlags & DNS_QUERY_USE_QUICK_TIMEOUTS ) { timeout = g_QuickQueryTimeouts[retry]; } else { timeout = g_QueryTimeouts[retry]; } }
//
// zero timeout indicates end of retries for this query type
//
if ( timeout == 0 ) { // save timeout for adapter?
//
// if multiple adapters and some timed out and some
// didn't then saving timeout is relevant
//
// DCR: this doesn't make much sense
// the actual test i moved inside the function
if ( pNetInfo && pNetInfo->AdapterCount > 1 && ignoredRcode && status == ERROR_TIMEOUT ) { resetOnFinalTimeout( pNetInfo ); } break; }
//
// send to best DNS servers in adapter list
// - servers sent to varies according to retry
// - send in previous timeout, if some servers did not respond
//
status = SendUdpToNextDnsServers( pMsgSend, pNetInfo, retry, sendCount ? pMsgRecv->Timeout : 0, & sendCount );
sentCount = sendCount;
if ( status != ERROR_SUCCESS ) { // if no more servers to send to, done
DNSDBG( RECV, ( "No more DNS servers to send message %p\n" "\tprevious RCODE = %d\n", pMsgSend, ignoredRcode )); goto ErrorReturn; } retry++; recvIp = 0; rcode = DNS_RCODE_NO_ERROR; pMsgRecv->Timeout = timeout; DNS_ASSERT( sendCount != 0 );
frecvRetry = FALSE; sendTime = GetCurrentTimeInSeconds();
//
// receive response
//
// note: the loop is strictly to allow us to drop back into
// receive if one server is misbehaving;
// in that case we go back into the receive without resending
// to allow other servers to respond
//
while ( sendCount ) { //
// if have to retry recv, adjust down timeout
// - note, one second granularity is handled by
// rounding up at zero so we wait 0-1s beyond official
// timeout value
//
// DCR: calculate timeouts in ms?
//
if ( frecvRetry ) { DWORD timeLeft;
timeLeft = timeout + sendTime - GetCurrentTimeInSeconds();
if ( (LONG)timeLeft < 0 ) { status = ERROR_TIMEOUT; break; } else if ( timeLeft == 0 ) { timeLeft = 1; } pMsgRecv->Timeout = timeLeft; } frecvRetry = TRUE;
status = Dns_RecvUdp( pMsgRecv );
recvIp = MSG_REMOTE_IP4(pMsgRecv);
// recv wait completed
// - if timeout, commence next retry
// - if CONNRESET
// - indicate NO server on IP
// - back to recv if more DNS servers outstanding,
// - if success, verify packet
if ( status != ERROR_SUCCESS ) { if ( status == ERROR_TIMEOUT ) { break; } if ( status == WSAECONNRESET ) { ResetDnsServerPriority( pNetInfo, recvIp, status ); #if 0
// old deal when didn't know about IP back in sockaddr
if ( sendCount == 1 ) { pMsgRecv->Timeout = NO_DNS_PRIORITY_DROP; status = ERROR_TIMEOUT; break; } #endif
sendCount--; continue; } } DNS_ASSERT( recvIp != 0 );
// check XID match
if ( pMsgRecv->MessageHead.Xid != pMsgSend->MessageHead.Xid ) { DNS_PRINT(( "WARNING: Incorrect XID in response. Ignoring.\n" )); continue; }
// check DNS server IP match
// this may or may NOT be desired
if ( fQueryIpMatching && sentCount == 1 && MSG_REMOTE_IP4(pMsgSend) != recvIp ) { DNS_PRINT(( "WARNING: Ignoring response from %08x to query %p\n" "\tIP does not match queried IP %08x\n", recvIp, pMsgSend, MSG_REMOTE_IP4(pMsgSend) )); continue; }
// valid receive, drop outstanding send count
sendCount--;
//
// check question match
// - this is "Maggs Bug" check
// - ASSERT here just to investigate issue locally
// and make sure check is not bogus
// - specifically doing after sendCount decrement
// as this server will NOT send us a valid response
// - some servers don't reply with question so ignore
// if QuestionCount == 0
//
if ( pMsgRecv->MessageHead.QuestionCount != 0 && !Dns_IsSamePacketQuestion( pMsgRecv, pMsgSend )) { DNS_PRINT(( "ERROR: Bad question response from server %08x!\n" "\tXID match, but question does not match question sent!\n", recvIp )); DNS_ASSERT( FALSE ); continue; }
// suck out RCODE
rcode = pMsgRecv->MessageHead.ResponseCode;
//
// good response?
//
// special case AUTH-EMPTY and delegations
//
// - AUTH-EMPTY gets similar treatment to name error
// (this adapter can be considered to be finished)
//
// - referrals can be treated like SERVER_FAILURE
// (avoid this server for rest of query; server may
// be fine for direct lookup, so don't drop priority)
//
//
// DCR_CLEANUP: functionalize packet-categorization
// this would be standard errors
// plus AUTH-EMPTY versus referral
// plus OPT issues, etc
// could be called from TCP side also
//
// then would have separate determination about whether
// packet was terminal (below)
//
if ( rcode == DNS_RCODE_NO_ERROR ) { if ( pMsgRecv->MessageHead.AnswerCount != 0 || fupdate ) { goto GoodRecv; }
//
// auth-empty
// - authoritative
// - or from cache, recursive response (hence not delegation)
//
// note: using dummy RCODE here as "ignored RCODE" serves
// the role of "best saved status" and roughly
// prioritizes in the way we want
//
// DCR: could change to "best saved status" as mapping is
// pretty much the same; would explicitly have to
// check
if ( pMsgRecv->MessageHead.Authoritative == 1 || ( pMsgRecv->MessageHead.RecursionAvailable == 1 && pMsgRecv->MessageHead.RecursionDesired ) ) { DNSDBG( RECV, ( "Recv AUTH-EMPTY response from %s\n", IP_STRING(recvIp) )); rcode = DNS_RCODE_AUTH_EMPTY_RESPONSE; status = DNS_INFO_NO_RECORDS; }
// referral
else if ( pMsgRecv->MessageHead.RecursionAvailable == 0 ) { DNSDBG( RECV, ( "Recv referral response from %s\n", IP_STRING(recvIp) ));
rcode = DNS_RCODE_SERVER_FAILURE; status = DNS_ERROR_REFERRAL_RESPONSE; }
// bogus (bad packet) response
else { rcode = DNS_RCODE_SERVER_FAILURE; status = DNS_ERROR_BAD_PACKET; } } else { status = Dns_MapRcodeToStatus( (UCHAR)rcode ); }
//
// OPT failure screening
//
// DCR: FORMERR overload on OPT for update
// unless we read result to see if OPT, no way
// to determine if this is update problem or
// OPT problem
//
// - note, that checking if in list doesn't work because
// of MT issue (another query adds setting)
//
// - could be fixed by setting flag in network info
//
if ( rcode == DNS_RCODE_FORMAT_ERROR && !fupdate ) { Dns_SetServerOptExclude( recvIp );
// redo send but explicitly force OPT excluse
Dns_SendEx( pMsgSend, recvIp, TRUE // exclude OPT
);
sendCount++; continue; }
//
// error RCODE do NOT terminate query
// - SERVER_FAILUREs
// - malfunctioning server
// - disjoint nets \ DNS namespace issues
//
// RCODE error removes particular server from further consideration
// during THIS query
//
// generally the higher RCODEs are more interesting
// NAME_ERROR > SERVER_FAILURE
// and
// update RCODEs > NAME_ERROR
// save the highest as return when no ERROR_SUCCESS response
//
// however for query NAME_ERROR is the highest RCODE,
// IF it is received on all adapters (if not REFUSED on one
// adapter may indicate that there really is a name)
//
// for UPDATE, REFUSED and higher are terminal RCODEs.
// downlevel servers (non-UPDATE-aware) servers would give
// FORMERR or NOTIMPL, so these are either valid responses or
// the zone has a completely busted server which must be detected
// and removed
//
//
// DCR_CLEANUP: functionalize packet-termination
// essentially is this type of packet terminal for
// this query;
// could be called from TCP side also
//
if ( rcode > ignoredRcode ) { ignoredRcode = rcode; }
//
// reset server priority for good recv
// - return ERROR_SUCCESS unless all adapters
// are
//
status = ResetDnsServerPriority( pNetInfo, recvIp, status );
//
// if all adapters are done (NAME_ERROR or NO_RECORDS)
// - return NAME_ERROR\NO_RECORDS rcode
// NO_RECORDS highest priority
// then NAME_ERROR
// then anything else
if ( status == DNS_ERROR_RCODE_NAME_ERROR ) { if ( !fupdate && ignoredRcode != DNS_RCODE_AUTH_EMPTY_RESPONSE ) { ignoredRcode = DNS_RCODE_NAME_ERROR; } goto ErrorReturn; }
//
// update RCODEs are terminal
//
if ( fupdate && rcode >= DNS_RCODE_REFUSED ) { goto ErrorReturn; }
// continue wait for any other outstanding servers
}
DNSDBG( RECV, ( "Failed retry = %d for message %p\n" "\tstatus = %d\n" "\ttimeout = %d\n" "\tservers out = %d\n" "\tlast rcode = %d\n" "\tignored RCODE = %d\n\n", (retry - 1), pMsgSend, status, timeout, sendCount, rcode, ignoredRcode )); continue;
} // end loop sending/recving packets
//
// falls here on retry exhausted
//
// note that any ignored RCODE takes precendence over failing
// status (which may be winsock error, timeout, or bogus
// NAME_ERROR from resetServerPriorities())
//
ErrorReturn:
// this can also hit on winsock error in DnsSend()
//
//DNS_ASSERT( ignoredRcode || status == ERROR_TIMEOUT );
//
// error responses from all servers or timeouts
//
DNSDBG( RECV, ( "Error or timeouts from all servers for message %p\n" "\treturning RCODE = %d\n", pMsgSend, ignoredRcode ));
if ( ignoredRcode ) { // empty-auth reponse is tracked with bogus RCODE,
// switch to status code -- DNS_INFO_NO_RECORDS
if ( !fupdate && ignoredRcode == DNS_RCODE_AUTH_EMPTY_RESPONSE ) { status = DNS_INFO_NO_RECORDS; } else { status = Dns_MapRcodeToStatus( (UCHAR)ignoredRcode ); } } goto Done;
GoodRecv:
ResetDnsServerPriority( pNetInfo, recvIp, rcode );
DNSDBG( RECV, ( "Recv'd response for query at %p from DNS %s\n", pMsgSend, IP_STRING(recvIp) ));
Done:
//
// if created socket -- close it
//
// DCR_ENHANCE: allow for possibility of keeping socket alive
//
if ( fcreatedSocket ) { DNSDBG( SEND, ( "Closing socket %d after recv.\n", s ));
Dns_ReturnUdpSocket( s ); pMsgSend->Socket = 0; pMsgRecv->Socket = 0; }
IF_DNSDBG( RECV ) { DNSDBG( SEND, ( "Leave Dns_SendAndRecvUdp()\n" "\tstatus = %d\n" "\ttime = %d\n" "\tsend msg = %p\n" "\trecv msg = %p\n", status, Dns_GetCurrentTimeInSeconds(), pMsgSend, pMsgRecv ));
DnsDbg_NetworkInfo( "Network info after UDP recv\n", pNetInfo ); }
// if allocated adapter list free it
if ( ptempNetInfo ) { NetInfo_Free( ptempNetInfo ); }
// should not return NXRRSET except on update
ASSERT( fupdate || status != DNS_ERROR_RCODE_NXRRSET );
return( status ); }
DNS_STATUS Dns_SendAndRecvMulticast( IN OUT PDNS_MSG_BUF pMsgSend, OUT PDNS_MSG_BUF pMsgRecv, IN OUT PDNS_NETINFO pNetInfo OPTIONAL ) /*++
Routine Description:
Sends to and waits to recv from remote DNS.
Arguments:
pMsgSend - message to send
ppMsgRecv - and reuse
pNetInfo -- adapter list DNS server info
DCR - pNetInfo parameter could be leveraged to identify specific networks to target a multicast query against. For example, there could be a multihomed machine that is configured to only multicast on one of many adapters, thus filtering out useless mDNS packets.
Return Value:
ERROR_SUCCESS if successful response. NAME_ERROR on timeout. Error status on send\recv failure.
--*/ { SOCKET s; INT fcreatedSocket = FALSE; INT retry; DWORD timeout; DNS_STATUS status = ERROR_TIMEOUT; IP_ADDRESS recvIp = 0; DWORD rcode = 0; DWORD ignoredRcode = 0;
DNSDBG( SEND, ( "Enter Dns_SendAndRecvMulticast()\n" "\tsend msg at %p\n" "\tsocket %d\n" "\trecv msg at %p\n", pMsgSend, pMsgSend->Socket, pMsgRecv ));
// verify params
if ( !pMsgSend || !pMsgRecv ) { return( ERROR_INVALID_PARAMETER ); }
if ( pMsgSend->MessageHead.Opcode == DNS_OPCODE_UPDATE ) { return( ERROR_INVALID_PARAMETER ); }
// create socket if necessary
s = pMsgSend->Socket; if ( s == 0 || s == INVALID_SOCKET ) { s = Dns_CreateSocket( SOCK_DGRAM, INADDR_ANY, 0 ); if ( s == INVALID_SOCKET ) { status = GetLastError(); goto Done; } pMsgSend->Socket = s; pMsgSend->fTcp = FALSE; fcreatedSocket = TRUE; }
// if already have TCP socket -- invalid
//
// problem is we either leak TCP socket, or we close
// it here and may screw things up at higher level
else if ( pMsgSend->fTcp ) { status = ERROR_INVALID_PARAMETER; goto Done; }
pMsgRecv->Socket = s; pMsgRecv->fTcp = FALSE;
//
// loop sending until
// - receive successful response
// or
// - receive errors response from all servers
// or
// - reach final timeout on all servers
//
retry = 0;
while ( 1 ) { timeout = g_MulticastQueryTimeouts[retry];
//
// zero timeout indicates end of retries for this query type
//
if ( timeout == 0 ) { break; }
//
// send to multicast DNS address
//
if ( retry == 0 ) { Dns_InitializeMsgRemoteSockaddr( pMsgSend, MULTICAST_DNS_RADDR ); Dns_Send( pMsgSend ); } else { Dns_Send( pMsgSend ); }
retry++; recvIp = 0; rcode = DNS_RCODE_NO_ERROR; pMsgRecv->Timeout = timeout;
//
// receive response
//
// note: the loop is strictly to allow us to drop back into
// receive if one server is misbehaving;
// in that case we go back into the receive without resending
// to allow other servers to respond
//
status = Dns_RecvUdp( pMsgRecv );
// recv wait completed
// - if timeout, commence next retry
// - if CONNRESET
// - back to recv if more DNS servers outstanding,
// - otherwise equivalent treat as timeout, except with
// very long timeout
// - if success, verify packet
if ( status != ERROR_SUCCESS ) { if ( status == ERROR_TIMEOUT ) { continue; } if ( status == WSAECONNRESET ) { pMsgRecv->Timeout = NO_DNS_PRIORITY_DROP; status = ERROR_TIMEOUT; continue; } goto Done; }
// check XID match
if ( pMsgRecv->MessageHead.Xid != pMsgSend->MessageHead.Xid ) { DNS_PRINT(( "WARNING: Incorrect XID in response. Ignoring.\n" )); continue; }
// suck out the IP of the machine that responded
recvIp = MSG_REMOTE_IP4(pMsgRecv);
// suck out RCODE
rcode = pMsgRecv->MessageHead.ResponseCode;
//
// good response?
//
// special case AUTH-EMPTY and delegations
//
// - AUTH-EMPTY gets similar treatment to name error
// (this adapter can be considered to be finished)
//
// - referrals can be treated like SERVER_FAILURE
// (avoid this server for rest of query; server may
// be fine for direct lookup, so don't drop priority)
//
if ( rcode == DNS_RCODE_NO_ERROR ) { if ( pMsgRecv->MessageHead.AnswerCount != 0 ) { goto Done; }
// auth-empty
if ( pMsgRecv->MessageHead.Authoritative == 1 ) { DNSDBG( RECV, ( "Recv AUTH-EMPTY response from %s\n", IP_STRING(recvIp) )); rcode = DNS_RCODE_AUTH_EMPTY_RESPONSE; } } } // end loop sending/recving packets
Done:
//
// if created socket -- close it
//
// DCR_ENHANCE: allow for possibility of keeping socket alive
//
if ( fcreatedSocket ) { DNSDBG( SEND, ( "Closing socket %d after recv.\n", s )); Dns_CloseSocket( s ); pMsgSend->Socket = 0; pMsgRecv->Socket = 0; }
IF_DNSDBG( RECV ) { DNSDBG( SEND, ( "Leave Dns_SendAndRecvMulticast()\n" "\tstatus = %d\n" "\ttime = %d\n" "\tsend msg at %p\n" "\trecv msg at %p\n", status, Dns_GetCurrentTimeInSeconds(), pMsgSend, pMsgRecv )); }
if ( status == ERROR_TIMEOUT ) { status = DNS_ERROR_RCODE_NAME_ERROR; }
return( status ); }
//
// TCP routines
//
DNS_STATUS Dns_OpenTcpConnectionAndSend( IN OUT PDNS_MSG_BUF pMsg, IN IP_ADDRESS ipServer, IN BOOL fBlocking ) /*++
Routine Description:
Connect via TCP to desired server.
Arguments:
pMsg -- message info to set with connection socket
ipServer -- IP of DNS server to connect to
fBlocking -- blocking connection
Return Value:
TRUE if successful. FALSE on connect error.
--*/ { SOCKET s; INT err;
// Note: currently blocking flag is unused and default to blocking
// connection; later want to allow async hence flag
UNREFERENCED_PARAMETER( fBlocking );
//
// setup a TCP socket
// - INADDR_ANY -- let stack select source IP
//
s = Dns_CreateSocket( SOCK_STREAM, INADDR_ANY, // any address
0 // any port
); if ( s == INVALID_SOCKET ) { DNS_PRINT(( "ERROR: unable to create TCP socket to create TCP" "\tconnection to %s.\n", IP_STRING( ipServer ) ));
pMsg->Socket = 0; err = WSAGetLastError(); if ( !err ) { DNS_ASSERT( FALSE ); err = WSAENOTSOCK; } return( err ); }
//
// set TCP params
// - do before connect(), so can directly use sockaddr buffer
//
pMsg->fTcp = TRUE; Dns_InitializeMsgRemoteSockaddr( pMsg, ipServer );
//
// connect
//
err = connect( s, (struct sockaddr *) &pMsg->RemoteAddress, sizeof( SOCKADDR_IN ) ); if ( err ) { PCHAR pchIpString;
err = GetLastError(); pchIpString = IP_STRING( MSG_REMOTE_IP4(pMsg) );
DNS_LOG_EVENT( DNS_EVENT_CANNOT_CONNECT_TO_SERVER, 1, &pchIpString, err );
DNSDBG( TCP, ( "Unable to establish TCP connection to %s.\n" "\tstatus = %d\n", pchIpString, err ));
Dns_CloseSocket( s ); pMsg->Socket = 0; if ( !err ) { err = WSAENOTCONN; } return( err ); }
DNSDBG( TCP, ( "Connected to %s for message at %p.\n" "\tsocket = %d\n", IP_STRING( MSG_REMOTE_IP4(pMsg) ), pMsg, s ));
pMsg->Socket = s;
//
// send desired packet
//
err = Dns_Send( pMsg );
return( (DNS_STATUS)err );
} // Dns_OpenTcpConnectionAndSend
DNS_STATUS Dns_RecvTcp( IN OUT PDNS_MSG_BUF pMsg ) /*++
Routine Description:
Receive TCP DNS message.
Arguments:
pMsg - message info buffer to receive packet; must contain connected TCP socket
Return Value:
ERROR_SUCCESS if successfully receive a message. Error code on failure.
--*/ { PCHAR pchrecv; // ptr to recv location
INT recvLength; // length left to recv()
SOCKET socket; INT err; WORD messageLength; struct timeval selectTimeout; struct fd_set fdset;
DNS_ASSERT( pMsg );
socket = pMsg->Socket;
DNSDBG( RECV, ( "Enter Dns_RecvTcp( %p )\n" "\tRecv on socket = %d.\n" "\tBytes left to receive = %d\n" "\tTimeout = %d\n", pMsg, socket, pMsg->BytesToReceive, pMsg->Timeout ));
//
// verify socket, setup fd_set and select timeout
//
if ( socket == 0 || socket == INVALID_SOCKET ) { return( ERROR_INVALID_PARAMETER ); }
FD_ZERO( &fdset ); FD_SET( socket, &fdset );
selectTimeout.tv_usec = 0; selectTimeout.tv_sec = pMsg->Timeout;
//
// new message -- set to receive message length
// - reusing buffer
// - new buffer
//
// continuing receive of message
//
if ( !pMsg->pchRecv ) { DNS_ASSERT( pMsg->fMessageComplete || pMsg->MessageLength == 0 );
pchrecv = (PCHAR) &pMsg->MessageLength; recvLength = (INT) sizeof( WORD ); } else { pchrecv = (PCHAR) pMsg->pchRecv; recvLength = (INT) pMsg->BytesToReceive; } DNS_ASSERT( recvLength );
//
// loop until receive the entire message
//
while ( 1 ) { //
// wait for stack to indicate packet reception
//
err = select( 0, &fdset, NULL, NULL, &selectTimeout ); if ( err <= 0 ) { if ( err < 0 ) { // select error
err = WSAGetLastError(); DNS_PRINT(( "ERROR: select() error = %p\n", err )); return( err ); } else { Trace_LogRecvEvent( pMsg, ERROR_TIMEOUT, TRUE // TCP
);
DNS_PRINT(( "ERROR: timeout on response %p\n", pMsg )); return( ERROR_TIMEOUT ); } }
//
// Only recv() exactly as much data as indicated.
// Another message could follow during zone transfer.
//
err = recv( socket, pchrecv, recvLength, 0 );
DNSDBG( TCP, ( "\nRecv'd %d bytes on TCP socket %d\n", err, socket ));
//
// TCP FIN received -- error in the middle of a message.
//
if ( err == 0 ) { goto FinReceived; }
//
// recv error
// - perfectly reasonable if shutting down
// - otherwise actual recv() error
//
if ( err == SOCKET_ERROR ) { goto SockError; }
//
// update buffer params
//
recvLength -= err; pchrecv += err;
DNS_ASSERT( recvLength >= 0 );
//
// received message or message length
//
if ( recvLength == 0 ) { // done receiving message
if ( pchrecv > (PCHAR)&pMsg->MessageHead ) { break; }
//
// recv'd message length, setup to recv() message
// - byte flip length
// - continue reception with this length
//
DNS_ASSERT( pchrecv == (PCHAR)&pMsg->MessageHead );
messageLength = pMsg->MessageLength; pMsg->MessageLength = messageLength = ntohs( messageLength ); if ( messageLength < sizeof(DNS_HEADER) ) { DNS_PRINT(( "ERROR: Received TCP message with bad message" " length %d.\n", messageLength ));
goto BadTcpMessage; } recvLength = messageLength;
DNSDBG( TCP, ( "Received TCP message length %d, on socket %d,\n" "\tfor msg at %p.\n", messageLength, socket, pMsg ));
// starting recv of valid message length
if ( messageLength <= pMsg->BufferLength ) { continue; }
// note: currently do not realloc
goto BadTcpMessage; #if 0
//
// DCR: allow TCP realloc
// - change call signature OR
// - return pMsg with ptr to realloced
// perhaps better to ignore and do 64K buffer all the time
//
// realloc, if existing message too small
//
pMsg = Dns_ReallocateTcpMessage( pMsg, messageLength ); if ( !pMsg ) { return( GetLastError() ); } #endif
} }
//
// Message received
// recv ptr serves as flag, clear to start new message on reuse
//
pMsg->fMessageComplete = TRUE; pMsg->pchRecv = NULL;
//
// return message information
// - flip count bytes
//
DNS_BYTE_FLIP_HEADER_COUNTS( &pMsg->MessageHead );
Trace_LogRecvEvent( pMsg, 0, TRUE // TCP
);
IF_DNSDBG( RECV ) { DnsDbg_Message( "Received TCP packet", pMsg ); } return( ERROR_SUCCESS );
SockError:
err = GetLastError();
#if 0
//
// note: want non-blocking sockets if doing full async
//
// WSAEWOULD block is NORMAL return for message not fully recv'd.
// - save state of message reception
//
// We use non-blocking sockets, so bad client (that fails to complete
// message) doesn't hang TCP receiver.
//
if ( err == WSAEWOULDBLOCK ) { pMsg->pchRecv = pchrecv; pMsg->BytesToReceive = recvLength;
DNSDBG( TCP, ( "Leave ReceiveTcpMessage() after WSAEWOULDBLOCK.\n" "\tSocket=%d, Msg=%p\n" "\tBytes left to receive = %d\n", socket, pMsg, pMsg->BytesToReceive )); goto CleanupConnection; } #endif
//
// cancelled connection
// -- perfectly legal, question is why
//
if ( pchrecv == (PCHAR) &pMsg->MessageLength && ( err == WSAESHUTDOWN || err == WSAECONNABORTED || err == WSAECONNRESET ) ) { DNSDBG( TCP, ( "WARNING: Recv RESET (%d) on socket %d\n", err, socket )); goto CleanupConnection; }
// anything else is our problem
DNS_LOG_EVENT( DNS_EVENT_RECV_CALL_FAILED, 0, NULL, err );
DNSDBG( ANY, ( "ERROR: recv() of TCP message failed.\n" "\t%d bytes recvd\n" "\t%d bytes left\n" "\tGetLastError = 0x%08lx.\n", pchrecv - (PCHAR)&pMsg->MessageLength, recvLength, err )); DNS_ASSERT( FALSE ); goto CleanupConnection;
FinReceived:
//
// valid FIN -- if recv'd between messages (before message length)
//
DNSDBG( TCP, ( "ERROR: Recv TCP FIN (0 bytes) on socket %d\n", socket, recvLength ));
if ( !pMsg->MessageLength && pchrecv == (PCHAR)&pMsg->MessageLength ) { err = DNS_ERROR_NO_PACKET; goto CleanupConnection; }
//
// FIN during message -- invalid message
//
DNSDBG( ANY, ( "ERROR: TCP message received has incorrect length.\n" "\t%d bytes left when recv'd FIN.\n", recvLength )); goto BadTcpMessage;
BadTcpMessage: { PCHAR pchServer = IP_STRING( MSG_REMOTE_IP4(pMsg) );
DNS_LOG_EVENT( DNS_EVENT_BAD_TCP_MESSAGE, 1, & pchServer, 0 ); } err = DNS_ERROR_BAD_PACKET;
CleanupConnection:
// close connection and socket, indicate this by zeroing socket
// in message buffer
Dns_CloseConnection( socket ); pMsg->Socket = 0; return( DNS_ERROR_BAD_PACKET ); }
DNS_STATUS Dns_SendAndRecvTcp( IN PDNS_MSG_BUF pMsgSend, OUT PDNS_MSG_BUF pMsgRecv, IN PIP_ARRAY aipServers, IN OUT PDNS_NETINFO pNetInfo ) /*++
Routine Description:
Sends to and waits to recv from remote DNS.
Arguments:
pMsgSend - message to send
pMsgRecv - and reuse
aipServers -- counted array of DNS server IP addrs
pNetInfo -- adapter info list; ignored if aipServers given
Return Value:
ERROR_SUCCESS if successful packet reception. Error status on failure.
--*/ { DWORD i; DNS_STATUS status; PIP_ARRAY pallocatedServerIpArray = NULL;
DNSDBG( SEND, ( "Enter Dns_SendAndRecvTcp()\n" "\tsend msg at %p\n" "\tsocket %d\n" "\trecv msg at %p\n" "\tserver IP array %p\n" "\tadapter info at %p\n", pMsgSend, pMsgSend->Socket, pMsgRecv, aipServers, pNetInfo ));
//
// verify params
//
if ( !pMsgSend || !pMsgRecv || (!pNetInfo && !aipServers) ) { return( ERROR_INVALID_PARAMETER ); }
//
// build server IP array?
//
if ( !aipServers ) { pallocatedServerIpArray = NetInfo_ConvertToIpArray( pNetInfo ); if ( !pallocatedServerIpArray ) { return( DNS_ERROR_NO_MEMORY ); } aipServers = pallocatedServerIpArray; }
// init remote sockaddr and socket
// setup receive buffer for TCP
Dns_InitializeMsgRemoteSockaddr( pMsgSend, aipServers->AddrArray[0] );
pMsgSend->Socket = 0; pMsgRecv->Socket = 0; pMsgSend->fTcp = TRUE; SET_MESSAGE_FOR_TCP_RECV( pMsgRecv );
//
// loop sending until
// - receive successful response
// or
// - receive errors response from all servers
// or
// - reach final timeout on all servers
//
if ( pMsgRecv->Timeout == 0 ) { pMsgRecv->Timeout = DEFAULT_TCP_TIMEOUT; }
for( i=0; i<aipServers->AddrCount; i++ ) { //
// close any previous connection
//
if ( pMsgSend->Socket ) { Dns_CloseConnection( pMsgSend->Socket ); pMsgSend->Socket = 0; pMsgRecv->Socket = 0; }
//
// connect and send to next server
//
status = Dns_OpenTcpConnectionAndSend( pMsgSend, aipServers->AddrArray[i], TRUE ); if ( status != ERROR_SUCCESS ) { continue; } DNS_ASSERT( pMsgSend->Socket != INVALID_SOCKET && pMsgSend->Socket != 0 );
//
// receive response
// - if successful receive, done
// - if timeout continue
// - other errors indicate some setup or system level problem
// note: Dns_RecvTcp will close and zero msg->socket on error!
//
pMsgRecv->Socket = pMsgSend->Socket;
status = Dns_RecvTcp( pMsgRecv );
if ( pMsgRecv->Socket == 0 ) { // socket error -> socket has been closed
pMsgSend->Socket = 0; }
//
// timed out (or error)
// - if end of timeout, quit
// - otherwise double timeout and resend
//
switch( status ) { case ERROR_SUCCESS: break;
case ERROR_TIMEOUT:
DNS_PRINT(( "ERROR: connected to server at %s\n" "\tbut no response to packet at %p\n", IP_STRING( MSG_REMOTE_IP4(pMsgSend) ), pMsgSend )); continue;
default:
DNS_PRINT(( "ERROR: connected to server at %s to send packet %p\n" "\tbut error %d encountered on receive.\n", IP_STRING( MSG_REMOTE_IP4(pMsgSend) ), pMsgSend, status )); continue; }
//
// verify XID match
//
if ( pMsgRecv->MessageHead.Xid != pMsgSend->MessageHead.Xid ) { DNS_PRINT(( "ERROR: Incorrect XID in response. Ignoring.\n" )); continue; }
//
// verify question match
// - this is "Maggs Bug" check
// - ASSERT here just to investigate issue locally
// and make sure check is not bogus
//
if ( !Dns_IsSamePacketQuestion( pMsgRecv, pMsgSend )) { DNS_PRINT(( "ERROR: Bad question response from server %08x!\n" "\tXID match, but question does not match question sent!\n", MSG_REMOTE_IP4(pMsgSend) ));
DNS_ASSERT( FALSE ); continue; }
//
// check response code
// - some move to next server, others terminal
//
// DCR_FIX1: bring TCP RCODE handling in-line with UDP
//
// DCR_FIX: save best TCP RCODE
// preserve RCODE (and message) for useless TCP response
// would need to then reset TIMEOUT to success at end
// or use these RCODEs as status returns
//
switch( pMsgRecv->MessageHead.ResponseCode ) { case DNS_RCODE_SERVER_FAILURE: case DNS_RCODE_NOT_IMPLEMENTED: case DNS_RCODE_REFUSED:
DNS_PRINT(( "WARNING: Servers have responded with failure.\n" )); continue;
default:
break; } break;
} // end loop sending/recving UPDATEs
//
// close up final connection
// unless set to keep open for reuse
//
Dns_CloseConnection( pMsgSend->Socket ); pMsgSend->Socket = 0; pMsgRecv->Socket = 0;
// if allocated adapter list free it
if ( pallocatedServerIpArray ) { FREE_HEAP( pallocatedServerIpArray ); }
DNSDBG( SEND, ( "Leaving Dns_SendAndRecvTcp()\n" "\tstatus = %d\n", status ));
return( status ); }
#if 0
DNS_STATUS Dns_AsyncRecv( IN OUT PDNS_MSG_BUF pMsgRecv ) /*++
Routine Description:
Drop recv on async socket.
Arguments:
pMsgRecv - message to receive; OPTIONAL, if NULL message buffer is allocated; in either case global pDnsAsyncRecvMsg points at buffer
Return Value:
ERROR_SUCCESS if successful. Error status on failure.
--*/ { WSABUF wsabuf; DWORD bytesRecvd; DWORD flags = 0;
IF_DNSDBG( RECV ) { DNS_PRINT(( "Enter Dns_AsyncRecv( %p )\n", pMsgRecv )); }
//
// allocate buffer if none given
//
if ( !pMsgRecv ) { pMsgRecv = Dns_AllocateMsgBuf( MAXWORD ); if ( !pMsgRecv ) { return( GetLastError() ): } } pDnsAsyncRecvMsg = pMsgRecv;
//
// reset i/o completion event
//
ResetEvent( hDnsSocketEvent ); DNS_ASSERT( hDnsSocketEvent == Dns_SocketOverlapped.hEvent );
//
// drop down recv
//
status = WSARecvFrom( DnsSocket, & wsabuf, 1, & bytesRecvd, // dummy
& flags, & pMsgRecv->RemoteAddress, & pMsgRecv->RemoteAddressLength, & DnsSocketOverlapped, NULL // no completion routine
);
return( ERROR_SUCCESS );
Failed:
return( status ); }
#endif
DNS_STATUS Dns_SendAndRecv( IN OUT PDNS_MSG_BUF pMsgSend, OUT PDNS_MSG_BUF * ppMsgRecv, OUT PDNS_RECORD * ppResponseRecords, IN DWORD dwFlags, IN PIP_ARRAY aipServers, IN OUT PDNS_NETINFO pNetInfo ) /*++
Routine Description:
Send message, receive response.
Arguments:
pMsgSend -- message to send
ppMsgResponse -- addr to recv ptr to response buffer; caller MUST free buffer
ppResponseRecord -- address to receive ptr to record list returned from query
dwFlags -- query flags
aipDnsServers -- specific DNS servers to query; OPTIONAL, if specified overrides normal list associated with machine
pDnsNetAdapters -- DNS servers to query; if NULL get current list
Return Value:
ERROR_SUCCESS if successful. Error code on failure.
--*/ { PDNS_MSG_BUF precvMsg = NULL; PDNS_MSG_BUF psavedUdpResponse = NULL; DNS_STATUS statusFromUdp = ERROR_SUCCESS; DNS_STATUS status = ERROR_TIMEOUT; DNS_STATUS parseStatus; BOOL ftcp; IP_ARRAY tempArray;
DNSDBG( QUERY, ( "Dns_SendAndRecv()\n" "\tsend msg %p\n" "\trecv msg %p\n" "\trecv records %p\n" "\tflags %08x\n" "\tserver %p\n" "\tadapter list %p\n", pMsgSend, ppMsgRecv, ppResponseRecords, dwFlags, aipServers, pNetInfo ));
// response buf passed in?
// if not allocate one -- big enough for TCP
if ( ppMsgRecv && *ppMsgRecv ) { precvMsg = *ppMsgRecv; } if ( !precvMsg ) { precvMsg = Dns_AllocateMsgBuf( DNS_TCP_DEFAULT_PACKET_LENGTH ); if ( !precvMsg ) { status = DNS_ERROR_NO_MEMORY; goto Cleanup; } }
//
// send packet and get response
// - try UDP first unless TCP only
//
ftcp = ( dwFlags & DNS_QUERY_USE_TCP_ONLY ) || ( DNS_MESSAGE_CURRENT_OFFSET(pMsgSend) >= DNS_RFC_MAX_UDP_PACKET_LENGTH );
if ( !ftcp ) { if ( dwFlags & DNS_QUERY_MULTICAST_ONLY ) { //
// If the multicast query fails, then ERROR_TIMEOUT will
// be returned
//
goto DoMulticast; }
status = Dns_SendAndRecvUdp( pMsgSend, precvMsg, dwFlags, aipServers, pNetInfo );
statusFromUdp = status;
if ( status != ERROR_SUCCESS && status != DNS_ERROR_RCODE_NAME_ERROR && status != DNS_INFO_NO_RECORDS ) { //
// DCR: this multicast ON_NAME_ERROR test is bogus
// this isn't NAME_ERROR this is pretty much any error
//
if ( pNetInfo && pNetInfo->InfoFlags & DNS_FLAG_MULTICAST_ON_NAME_ERROR ) { goto DoMulticast; } else { goto Cleanup; } }
// if truncated response switch to TCP
if ( precvMsg->MessageHead.Truncation && ! (dwFlags & DNS_QUERY_ACCEPT_PARTIAL_UDP) ) { ftcp = TRUE; tempArray.AddrCount = 1; tempArray.AddrArray[0] = MSG_REMOTE_IP4(precvMsg); aipServers = &tempArray;
psavedUdpResponse = precvMsg; precvMsg = NULL; } }
//
// TCP send
// - for TCP queries
// - or truncation on UDP unless accepting partial response
//
// DCR_FIX: this precvMsg Free is bad
// if message was passed in we shouldn't have it, we should
// just do our own thing and ignore this recv buffer somehow
// ideally that buffer action is at much higher level
//
if ( ftcp ) { if ( precvMsg && precvMsg->BufferLength < DNS_TCP_DEFAULT_PACKET_LENGTH ) { FREE_HEAP( precvMsg ); precvMsg = NULL; } if ( !precvMsg ) { precvMsg = Dns_AllocateMsgBuf( DNS_TCP_DEFAULT_PACKET_LENGTH ); if ( !precvMsg ) { status = DNS_ERROR_NO_MEMORY; goto Cleanup; } } pMsgSend->fTcp = TRUE; precvMsg->fTcp = TRUE;
#if 0
if ( dwFlags & DNS_QUERY_SOCKET_KEEPALIVE ) { precvMsg->fSocketKeepalive = TRUE; } #endif
status = Dns_SendAndRecvTcp( pMsgSend, precvMsg, aipServers, pNetInfo );
//
// if recursing following truncated UDP query, then
// must make sure we actually have better data
// - if successful, but RCODE is different and bad
// => use UDP response
// - if failed TCP => use UDP
// - successful with good RCODE => parse TCP response
//
if ( psavedUdpResponse ) { if ( status == ERROR_SUCCESS ) { DWORD rcode = precvMsg->MessageHead.ResponseCode;
if ( rcode == ERROR_SUCCESS || rcode == psavedUdpResponse->MessageHead.ResponseCode || ( rcode != DNS_RCODE_SERVER_FAILURE && rcode != DNS_RCODE_FORMAT_ERROR && rcode != DNS_RCODE_REFUSED ) ) { goto Parse; } }
// TCP failed or returned bum error code
FREE_HEAP( precvMsg ); precvMsg = psavedUdpResponse; psavedUdpResponse = NULL; }
// direct TCP query
// - cleanup if failed
else if ( status != ERROR_SUCCESS ) { goto Cleanup; } }
//
// DCR: this multicast test is bogus (too wide open)
// essentially ANY error sends us on to multicast
// even INFO_NO_RECORDS
//
// multicast test should be intelligent
// - adpater with no DNS servers, or NO_RESPONSE
// from any DNS server
//
if ( status == ERROR_SUCCESS ) { DWORD rcode = precvMsg->MessageHead.ResponseCode;
if ( rcode == ERROR_SUCCESS || ( rcode != DNS_RCODE_SERVER_FAILURE && rcode != DNS_RCODE_FORMAT_ERROR && rcode != DNS_RCODE_REFUSED ) ) { goto Parse; } }
//
// multicast?
//
DoMulticast:
if ( ( pNetInfo && pNetInfo->InfoFlags & DNS_FLAG_ALLOW_MULTICAST ) || ( ( dwFlags & DNS_QUERY_MULTICAST_ONLY ) && ! pNetInfo ) ) { if ( !pMsgSend || ( pMsgSend && ( pMsgSend->MessageHead.Opcode == DNS_OPCODE_UPDATE ) ) ) { if ( statusFromUdp ) { status = statusFromUdp; } else { status = DNS_ERROR_NO_DNS_SERVERS; } goto Cleanup; }
status = Dns_SendAndRecvMulticast( pMsgSend, precvMsg, pNetInfo );
if ( status != ERROR_SUCCESS && status != DNS_ERROR_RCODE_NAME_ERROR && status != DNS_INFO_NO_RECORDS ) { if ( statusFromUdp ) { status = statusFromUdp; } goto Cleanup; } }
//
// parse response (if desired)
//
Parse:
if ( ppResponseRecords ) { parseStatus = Dns_ExtractRecordsFromMessage( precvMsg, (dwFlags & DNSQUERY_UNICODE_OUT), ppResponseRecords );
if ( !(dwFlags & DNS_QUERY_DONT_RESET_TTL_VALUES ) ) { Dns_NormalizeAllRecordTtls( *ppResponseRecords ); } }
// not parsing -- just return RCODE as status
else { parseStatus = Dns_MapRcodeToStatus( precvMsg->MessageHead.ResponseCode ); }
//
// get "best" status
// - no-records response beats NAME_ERROR (or other error)
// dump bogus records from error response
//
// DCR: multi-adapter NXDOMAIN\no-records response broken
// note, here we'd give back a packet with NAME_ERROR
// or another error
//
if ( status != parseStatus ) { // save previous NO_RECORDS response, from underlying query
// this trumps other errors (FORMERR, SERVFAIL, NXDOMAIN);
//
// note, that parsed message shouldn't be HIGHER level RCODE
// as these should beat out NO_RECORDS in original parsing
if ( status == DNS_INFO_NO_RECORDS && parseStatus != ERROR_SUCCESS ) { ASSERT( precvMsg->MessageHead.ResponseCode <= DNS_RCODE_NAME_ERROR );
if ( *ppResponseRecords ) { Dns_RecordListFree( *ppResponseRecords ); *ppResponseRecords = NULL; } } else { status = parseStatus; } }
Cleanup:
// cleanup recv buffer?
if ( ppMsgRecv ) { if ( status == ERROR_SUCCESS || Dns_IsStatusRcode(status) ) { *ppMsgRecv = precvMsg; } else { *ppMsgRecv = NULL; FREE_HEAP( precvMsg ); } } else { FREE_HEAP( precvMsg ); } if ( psavedUdpResponse ) { FREE_HEAP( psavedUdpResponse ); }
DNSDBG( RECV, ( "Leaving Dns_SendAndRecv(), status = %s (%d)\n", Dns_StatusString(status), status ));
return( status ); }
VOID Dns_InitQueryTimeouts( VOID ) { HKEY hKey = NULL; DWORD status; DWORD dwType; DWORD ValueSize; LPSTR lpTimeouts = NULL;
DWORD sysVersion; DWORD winMajorVersion; BOOL fIsWin95 = FALSE;
g_QueryTimeouts = QueryTimeouts; g_QuickQueryTimeouts = QuickQueryTimeouts; g_MulticastQueryTimeouts = MulticastQueryTimeouts;
//
// determine what kind of OS it is. Win95 or NT
//
sysVersion = GetVersion(); winMajorVersion = (DWORD) (LOBYTE(LOWORD(sysVersion)));
if (sysVersion < 0x80000000) fIsWin95 = FALSE; else fIsWin95 = TRUE;
if ( fIsWin95 ) status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, WIN95_TCPIP_REG_LOCATION, 0, KEY_QUERY_VALUE, &hKey ); else status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, NT_TCPIP_REG_LOCATION, 0, KEY_QUERY_VALUE, &hKey );
if ( status ) return;
if ( !hKey ) return;
status = RegQueryValueEx( hKey, DNS_QUERY_TIMEOUTS, NULL, &dwType, NULL, &ValueSize );
if ( !status ) { if ( ValueSize == 0 ) { goto GetQuickQueryTimeouts; }
lpTimeouts = ALLOCATE_HEAP( ValueSize );
if ( lpTimeouts ) { LPSTR StringPtr; DWORD StringLen; DWORD Timeout; DWORD Count = 0;
status = RegQueryValueEx( hKey, DNS_QUERY_TIMEOUTS, NULL, &dwType, lpTimeouts, &ValueSize );
if ( status || dwType != REG_MULTI_SZ ) { FREE_HEAP( lpTimeouts ); goto GetQuickQueryTimeouts; }
StringPtr = lpTimeouts;
while ( ( StringLen = strlen( StringPtr ) ) != 0 && Count < DNS_MAX_QUERY_TIMEOUTS ) { Timeout = atoi( StringPtr );
if ( Timeout ) RegistryQueryTimeouts[Count++] = Timeout;
StringPtr += (StringLen + 1); }
RegistryQueryTimeouts[Count] = 0; g_QueryTimeouts = RegistryQueryTimeouts; FREE_HEAP( lpTimeouts ); } }
GetQuickQueryTimeouts:
status = RegQueryValueEx( hKey, DNS_QUICK_QUERY_TIMEOUTS, NULL, &dwType, NULL, &ValueSize );
if ( !status ) { if ( ValueSize == 0 ) { goto GetMulticastTimeouts; }
lpTimeouts = ALLOCATE_HEAP( ValueSize );
if ( lpTimeouts ) { LPSTR StringPtr; DWORD StringLen; DWORD Timeout; DWORD Count = 0;
status = RegQueryValueEx( hKey, DNS_QUICK_QUERY_TIMEOUTS, NULL, &dwType, lpTimeouts, &ValueSize );
if ( status || dwType != REG_MULTI_SZ ) { FREE_HEAP( lpTimeouts ); goto GetMulticastTimeouts; }
StringPtr = lpTimeouts;
while ( ( StringLen = strlen( StringPtr ) ) != 0 && Count < DNS_MAX_QUERY_TIMEOUTS ) { Timeout = atoi( StringPtr );
if ( Timeout ) RegistryQuickQueryTimeouts[Count++] = Timeout;
StringPtr += (StringLen + 1); }
RegistryQuickQueryTimeouts[Count] = 0; g_QuickQueryTimeouts = RegistryQuickQueryTimeouts; FREE_HEAP( lpTimeouts ); } }
GetMulticastTimeouts:
status = RegQueryValueEx( hKey, DNS_MULTICAST_QUERY_TIMEOUTS, NULL, &dwType, NULL, &ValueSize );
if ( !status ) { if ( ValueSize == 0 ) { RegCloseKey( hKey ); return; }
lpTimeouts = ALLOCATE_HEAP( ValueSize );
if ( lpTimeouts ) { LPSTR StringPtr; DWORD StringLen; DWORD Timeout; DWORD Count = 0;
status = RegQueryValueEx( hKey, DNS_MULTICAST_QUERY_TIMEOUTS, NULL, &dwType, lpTimeouts, &ValueSize );
if ( status || dwType != REG_MULTI_SZ ) { FREE_HEAP( lpTimeouts ); RegCloseKey( hKey ); return; }
StringPtr = lpTimeouts;
while ( ( StringLen = strlen( StringPtr ) ) != 0 && Count < DNS_MAX_QUERY_TIMEOUTS ) { Timeout = atoi( StringPtr );
if ( Timeout ) RegistryMulticastQueryTimeouts[Count++] = Timeout;
StringPtr += (StringLen + 1); }
RegistryMulticastQueryTimeouts[Count] = 0; g_MulticastQueryTimeouts = RegistryMulticastQueryTimeouts; FREE_HEAP( lpTimeouts ); } }
RegCloseKey( hKey ); }
//
// OPT selection
//
// These routines track DNS server OPT awareness.
//
// The paradigm here is to default to sending OPTs, then track
// OPT non-awareness.
//
// DCR: RPC over OPT config info
// - either two lists (local and from-resolver in process)
// OR
// - RPC back OPT failures to resolver
// OR
// - flag network info blobs to RPC back to resolver
//
// security wise, prefer not to get info back
//
//
// DCR: OPT info in network info
// - then don't have to traverse locks
// - save is identical to current
// - could exclude OPT on any non-cache sends to
// handle problem of not saving OPT failures
//
//
// Global IP array of OPT-failed DNS servers
//
PIP_ARRAY g_OptFailServerList = NULL;
// Allocation size for OptFail IP array.
// Ten servers nicely covers the typical case.
#define OPT_FAIL_LIST_SIZE 10
//
// Use global lock for OPT list
//
#define LOCK_OPT_LIST() LOCK_GENERAL()
#define UNLOCK_OPT_LIST() UNLOCK_GENERAL()
BOOL Dns_IsServerOptExclude( IN IP4_ADDRESS IpAddress ) /*++
Routine Description:
Determine if particular server is not OPT aware.
Arguments:
IpAddress -- IP address of DNS server
Return Value:
TRUE if server should NOT get OPT send. FALSE if server should can send OPT
--*/ { BOOL retval;
//
// zero IP -- meaning TCP connect to unknown
// => must exclude OPT to allow success, otherwise
// we can't retry non-OPT
//
if ( IpAddress == 0 ) { return TRUE; }
//
// no exclusions?
// - doing outside lock for perf once we get to
// the "fully-deployed" case
//
if ( !g_OptFailServerList ) { return FALSE; } //
// see if IP is in OPT list
// - only if found do we exclude
//
LOCK_OPT_LIST();
retval = FALSE;
if ( g_OptFailServerList && Dns_IsAddressInIpArray( g_OptFailServerList, IpAddress ) ) { retval = TRUE; } UNLOCK_OPT_LIST();
return retval; }
VOID Dns_SetServerOptExclude( IN IP4_ADDRESS IpAddress ) /*++
Routine Description:
Set server for OPT exclusion.
Arguments:
IpAddress -- IP address of DNS server that failed OPT
Return Value:
None
--*/ { //
// screen zero IP (TCP connect to unknown IP)
//
if ( IpAddress == 0 ) { return; }
//
// put IP in OPT-fail list
// - create if doesn't exist
// - resize if won't fit
// note: add failure means "won't fit"
//
// note: only safe to reset global if allocation successful
// note: only one retry to protect alloc failure looping
//
LOCK_OPT_LIST();
if ( ! g_OptFailServerList || ! Dns_AddIpToIpArray( g_OptFailServerList, IpAddress ) ) { PIP_ARRAY pnewList;
pnewList = Dns_CopyAndExpandIpArray( g_OptFailServerList, OPT_FAIL_LIST_SIZE, TRUE // free current
); if ( pnewList ) { g_OptFailServerList = pnewList;
Dns_AddIpToIpArray( g_OptFailServerList, IpAddress ); } }
UNLOCK_OPT_LIST(); }
//
// End send.c
//
|