windows-nt/Source/XPSP1/NT/ds/dns/server/server/zonesec.c
2020-09-26 16:20:57 +08:00

4031 lines
98 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1995-1999 Microsoft Corporation
Module Name:
zonesec.c
Abstract:
Domain Name System (DNS) Server
Routines to handle zone transfer for secondary.
Author:
Jim Gilroy (jamesg) May 1995
Revision History:
jamesg Oct 1997 -- IXFR support
--*/
#include "dnssrv.h"
//
// Globals
//
BOOL g_fUsingSecondary;
// Notifies and SOA check responses queued by recv() threads
// DEVNOTE: it might simply things if we had a separate queue
// for notifies/SOA check responses/etc instead of sticking
// them all in the same queue.
#define DNS_SECONDARY_QUEUE_DEFAULT_TIMEOUT (5*60)
PPACKET_QUEUE g_SecondaryQueue;
// Zone transfer completion event
// Allows us to wake secondary control thread when transfer completes
HANDLE g_hWakeSecondaryEvent;
//
// List of masters with outstanding XFR connections.
// Keep this list, so don't attempt multiple connections to the same
// master. This will fail, as we always setup on DNS port to simplify
// router configuration.
//
PIP_ARRAY aipMastersOut;
//
// Do SOA check on empty zone every minute.
//
// Bracket zone check loop timeouts to reasonable values, so we
// neither "lose control" and fail to check regularly, nor waste
// cycles spinning.
//
#define DEFAULT_ZONE_RETRY_INTERVAL (60)
#define IXFR_RETRY_INTERVAL (60)
#define MIN_SOA_RETRY_TIME (15)
#define MAX_FAST_SOA_CHECKS (3)
#define MIN_SECONDARY_ZONE_CHECK_LOOP_TIME (60)
#define MAX_SECONDARY_ZONE_CHECK_LOOP_TIME (1200)
// Backoff to avoid spinning in AXFR attempts to failing master
#define MAX_BAD_MASTER_SUPPRESS_INTERVAL (10) // 10 seconds
// Timeout on non-responding primary
#define ZONE_TRANSFER_SELECT_TIMEOUT (30) // give up after 30 seconds
// Bogus type to indicate stub transfer
#define DNS_TYPE_STUBXFR 240
// Stub zone transfers are controlled by a simple state machine. Start an
// index at zero and iterate through the table below.
struct
{
WORD type;
BYTE recursionDesired : 1; // type matches DNS_HEADER for simplicity
} g_stubXfrData[] =
{
{ DNS_TYPE_SOA, 0 },
{ DNS_TYPE_NS, 1 },
{ DNS_TYPE_ZERO, 0 } // terminatator
};
//
// Private protos
//
PZONE_INFO
readZoneFromSoaAnswer(
IN OUT PDNS_MSGINFO pMsg,
IN WORD wType,
OUT PDWORD pdwMasterVersion,
OUT PDB_RECORD * ppSoaRR
);
VOID
processNotify(
IN OUT PDNS_MSGINFO pMsg
);
BOOL
processSoaCheckResponse(
IN OUT PDNS_MSGINFO pResponse
);
VOID
processIxfrUdpResponse(
IN OUT PDNS_MSGINFO pResponse
);
BOOL
startTcpXfr(
IN OUT PZONE_INFO pZone,
IN IP_ADDRESS ipMaster,
IN DWORD dwMasterVersion,
IN SOCKET Socket
);
#if 0
BOOLEAN
Zone_SerialNumber(
IN PZONE_INFO pZone,
OUT PDWORD pdwSerialNo
);
#endif
//
// Public secondary utilities.
//
// These are used by both secondary control thread and
// zone transfer reception threads.
//
BOOL
Xfr_RefreshZone(
IN OUT PZONE_INFO pZone
)
/*++
Routine Description:
Reset zone properties for successful refresh of zone.
May be called after matching SOA check, or transfer of zone.
Arguments:
pZone -- zone that has been refreshed
Return Value:
TRUE if successful.
FALSE on error.
--*/
{
PDB_RECORD prrSoa = NULL;
DWORD currentTime;
ASSERT( pZone );
//
// Find SOA RR for new zone
// - save direct ptr to it
// - save serial number in host order for faster compares
//
// DEVNOTE: should NOT need to find SOA unless
// - load
// - AXFR
// - IXFR
// in these cases should already have been loaded by standard "UpdateRoot"
// before this is called
// that processing also set serialNo
//
if ( pZone->pZoneRoot )
{
prrSoa = RR_FindNextRecord(
pZone->pZoneRoot,
DNS_TYPE_SOA,
NULL,
0 );
}
if ( !prrSoa )
{
DNS_PRINT(( "ERROR: No SOA RR at new zone root\n" ));
ASSERT( FALSE );
pZone->pSoaRR = NULL;
pZone->fEmpty = TRUE;
pZone->fStale = TRUE;
pZone->fShutdown = TRUE;
return( FALSE );
}
pZone->pSoaRR = prrSoa;
pZone->dwSerialNo = ntohl( prrSoa->Data.SOA.dwSerialNo );
//
// Reset refresh and expire timeouts
// - set zone expiration time
// - attempt next SOA check after refresh interval
// (or at expire if sooner)
//
currentTime = DNS_TIME();
pZone->dwExpireTime = ntohl( prrSoa->Data.SOA.dwExpire ) + currentTime;
pZone->dwNextSoaCheckTime = ntohl( prrSoa->Data.SOA.dwRefresh ) + currentTime;
if ( pZone->dwNextSoaCheckTime > pZone->dwExpireTime )
{
pZone->dwNextSoaCheckTime = pZone->dwExpireTime;
}
//
// Clear zone secondary state info
//
// Note this does not include TRANSFER flags.
// Send may be in progress during check, and RECV flag serves
// as ownership flag for zone during transfer.
//
REFRESH_ZONE(pZone);
return( TRUE );
}
VOID
Xfr_RetryZone(
IN OUT PZONE_INFO pZone
)
/*++
Routine Description:
Reset zone to retry SOA check.
May be called after failure to receive SOA check, or failure
during zone transfer attempt.
Arguments:
pZone -- zone that must retry
Return Value:
None.
--*/
{
DWORD dwRetryInterval = DEFAULT_ZONE_RETRY_INTERVAL;
DWORD currentTime = DNS_TIME();
DWORD nextTime;
ASSERT( pZone );
//
// We use UDP queries for SOA checks, which means that we need some
// limited fast retry. If a number of UDP SOA checks fail, assume that
// the connectivity problem is serious and drop back to the slow retry
// specified in the SOA for the zone.
//
if ( pZone->fSlowRetry || pZone->dwFastSoaChecks >= MAX_FAST_SOA_CHECKS )
{
DNS_DEBUG( XFR, (
"Xfr_RetryZone( %s ): using slow interval from SOA RR %p\n",
pZone->pszZoneName,
pZone->pSoaRR ));
if ( pZone->pSoaRR )
{
dwRetryInterval = ntohl( pZone->pSoaRR->Data.SOA.dwRetry );
}
pZone->fSlowRetry = FALSE;
pZone->dwFastSoaChecks = 0;
}
else
{
++pZone->dwFastSoaChecks;
}
DNS_DEBUG( XFR, (
"Xfr_RetryZone( %s ): interval=%d fSlow=%d dwChecks=%d\n",
pZone->pszZoneName,
dwRetryInterval,
( int ) pZone->fSlowRetry,
pZone->dwFastSoaChecks ));
//
// set next SOA check time at retry time
//
// but if expire time is closer, send next SOA at expiration
//
nextTime = dwRetryInterval + currentTime;
if ( pZone->dwExpireTime < nextTime
&&
pZone->dwExpireTime > currentTime )
{
ASSERT( ! pZone->fShutdown );
pZone->dwNextSoaCheckTime = pZone->dwExpireTime;
}
pZone->dwNextSoaCheckTime = nextTime;
}
VOID
Xfr_ForceZoneExpiration(
IN OUT PZONE_INFO pZone
)
/*++
Routine Description:
Force expiration of zone.
This can be called from admin RPC thread and forces ZoneControlThread
to expire the zone.
Arguments:
pZone -- zone to expire
Return Value:
None.
--*/
{
DNS_DEBUG( XFR, (
"Xfr_ForceZoneExpiration( %s )\n",
pZone->pszZoneName ));
// set expire time to NOW
// then wake up zone control thread to force expiration
pZone->dwExpireTime = DNS_TIME();
SetEvent( g_hWakeSecondaryEvent );
}
VOID
Xfr_ForceZoneRefresh(
IN OUT PZONE_INFO pZone
)
/*++
Routine Description:
Force immediate refresh on zone.
This can be called from admin RPC thread and forces ZoneControlThread
to send SOA query for the zone.
Arguments:
pZone -- zone to refresh
Return Value:
None.
--*/
{
DNS_DEBUG( XFR, (
"Xfr_ForceZoneRefresh( %s )",
pZone->pszZoneName ));
// set expire time to NOW
// then wake up zone control thread to force expiration
pZone->dwNextSoaCheckTime = DNS_TIME();
SetEvent( g_hWakeSecondaryEvent );
}
//
// Secondary control, public functions
//
// These routines are called by the main thread, or by
// main UDP reception threads.
//
BOOL
Xfr_InitializeSecondaryZoneControl(
VOID
)
/*++
Routine Description:
Initializes DNS to keep secondaries current with primary
using zone transfer.
Arguments:
None
Globals:
g_fUsingSecondary
g_SecondaryQueue
Return Value:
TRUE, if successful
FALSE otherwise, unable to create threads
--*/
{
//
// if previous initialization
// - wakeup secondary control thread to initiate transfer
// - but skip initialization
//
if ( g_fUsingSecondary )
{
SetEvent( g_hWakeSecondaryEvent );
return( TRUE );
}
//
// init globals to allow restart
//
aipMastersOut = NULL;
//
// create packet queue for SOA check responses and notifications
// - set event on queuing
// - discard expired packets on queuing, so don't back
// up queue (and bloat memory) if someone launches NOTIFY
// attack
//
g_SecondaryQueue = PQ_CreatePacketQueue(
"Secondary",
QUEUE_SET_EVENT |
QUEUE_DISCARD_EXPIRED,
DNS_SECONDARY_QUEUE_DEFAULT_TIMEOUT );
if ( !g_SecondaryQueue )
{
goto Failed;
}
//
// create zone transfer completion event
//
g_hWakeSecondaryEvent = CreateEvent(
NULL, // no security attributes
FALSE, // auto-reset
FALSE, // start non-signalled
NULL ); // no name
//
// create secondary version checking thread
//
if ( ! Thread_Create(
"Secondary Control",
Xfr_ZoneControlThread,
NULL,
DNS_EVENT_AXFR_INIT_FAILED ) )
{
goto Failed;
}
//
// indicate successful initialization
//
// no protection is required on setting this as it is done
// only during startup database parsing
//
g_fUsingSecondary = TRUE;
return( TRUE );
Failed:
DNS_DEBUG( INIT, ( "Xfr_InitializeSecondaryZoneControl() failed.\n" ));
return( FALSE );
} // Xfr_InitializeSecondaryZoneControl
VOID
Xfr_CleanupSecondaryZoneControl(
VOID
)
/*++
Routine Description:
Cleanup secondary control handles for restart.
Arguments:
None
Return Value:
None
--*/
{
// cleanup secondary packet queue
PQ_CleanupPacketQueueHandles( g_SecondaryQueue );
// cleanup secondary thread wakeup event
CloseHandle( g_hWakeSecondaryEvent );
g_hWakeSecondaryEvent = NULL;
} // Xfr_CleanupSecondaryZoneControl
VOID
Xfr_InitializeSecondaryZoneTimeouts(
IN OUT PZONE_INFO pZone
)
/*++
Routine Description:
Setup initial timeouts for secondary zone.
Note, this routine is called on creating secondary zone, giving
EVERY secondary zone the NO SOA initialization below. After file
load initialization may be reset to the have SOA version.
Arguments:
pZone - info structure for zone
Return Value:
None.
--*/
{
PDB_RECORD prrSoa = NULL;
DWORD currentTime;
//
// DEVNOTE: should pick use RefreshZone for most of this and just
// add LoadVersion
//
//
// DEVNOTE: this should be called after zone loaded, to reset values
//
pZone->dwFastSoaChecks = 0;
//
// get ptr to SOA -- if available
//
if ( pZone->pZoneRoot )
{
prrSoa = RR_FindNextRecord(
pZone->pZoneRoot,
DNS_TYPE_SOA,
NULL,
0 );
}
pZone->pSoaRR = prrSoa;
currentTime = GetCurrentTimeInSeconds();
//
// SOA exists -- set timeouts from SOA
//
// - load version, is THIS version
// - set to do IMMEDIATE SOA check
// - clear shutdown and stale flags set on zone creation
//
if ( prrSoa )
{
pZone->dwSerialNo = ntohl( prrSoa->Data.SOA.dwSerialNo );
pZone->dwLoadSerialNo = pZone->dwSerialNo;
pZone->dwExpireTime = ntohl(prrSoa->Data.SOA.dwExpire) + currentTime;
pZone->dwNextSoaCheckTime = 0;
pZone->fEmpty = FALSE;
pZone->fShutdown = FALSE;
pZone->fStale = FALSE;
IF_DEBUG( ZONEXFR )
{
Dbg_Zone(
"Startup of active secondary zone:\n",
pZone );
}
}
//
// NO SOA -- setup for zone starting shutdown
// - set shutdown and stale flags
// - check immediately
//
// note, this is the default initialization for ALL secondary zones
// on creation; effectively covers the case of loading secondary
// without datafile OR admin created zone; for normal file load
// this routine is called again after data load to read SOA above
//
else
{
pZone->fEmpty = TRUE;
pZone->fShutdown = TRUE;
pZone->fStale = TRUE;
pZone->dwExpireTime = 0;
pZone->dwNextSoaCheckTime = 0;
IF_DEBUG( ZONEXFR )
{
Dbg_Zone(
"Startup of shutdown secondary zone:\n",
pZone );
}
}
} // Xfr_InitializeSecondaryZoneTimeouts
VOID
Xfr_QueueSoaCheckResponse(
IN OUT PDNS_MSGINFO pMsg
)
/*++
Routine Description:
Queue SOA check response to secondary packet queue.
This routine is called by UDP receive thread.
Secondary zone control thread will dequeue and process.
Arguments:
pMsg - ptr to message info
Return Value:
None.
--*/
{
//
// DEVNOTE: prevent security attack on secondary queue
// DEVNOTE: validation before queuing to secondary queue
// - is valid zone
// - already NOTIFIED?
// - packet queue that screens for duplicates (same remote IP and zone)
//
if ( g_fUsingSecondary )
{
PQ_QueuePacketEx( g_SecondaryQueue, pMsg, FALSE );
DNS_DEBUG( ZONEXFR2, (
"Xfr_QueueSoaCheckResponse queued %p, new queue length %d\n",
pMsg,
g_SecondaryQueue->cLength ));
return;
}
//
// DEVNOTE-LOG: log warning to admin for bogus NOTIFY
//
// may get NOTIFIES from server that thinks this server secondary
// for one of its zones
//
IF_DEBUG( ANY )
{
Dbg_DnsMessage(
"WARNING: Notify or SOA response while NOT secondary",
pMsg );
}
Packet_Free( pMsg );
} // Xfr_QueueSoaCheckResponse
//
// Secondary (and DS) control thread
//
DNS_STATUS
Xfr_ZoneControlThread(
IN LPVOID pvDummy
)
/*++
Routine Description:
Thread to do version checking of all secondary zones.
Initiates zone transfer when necessary.
Arguments:
pvDummy - unused
Return Value:
Exit code.
Exit from DNS service terminating or error in wait call.
--*/
{
DBG_FN( "Xfr_ZoneControlThread" )
PZONE_INFO pzone; // zone info with nearest timeout
PDNS_MSGINFO pmsg; // SOA response or notify message
DNS_STATUS status;
DWORD timeout; // next timeout
DWORD currentTime; // current time in seconds
HANDLE waitHandleArray[3];
PCHAR pszeventArgs[2]; // logging strings
PPACKET_QUEUE ptempQueue;
//
// Create and lock temp queue. It will be held locked by this thread
// all the time since no other thread will need to use it.
//
ptempQueue = PQ_CreatePacketQueue( "ZoneXfrControl", 0, 0 );
if ( ptempQueue )
{
LOCK_QUEUE( ptempQueue );
}
else
{
DNS_DEBUG( ZONEXFR, (
"%s: unable to create temp queue\n"
"\twill not requeue unhandled soa checks!", fn ));
}
//
// initialize array of objects to wait on
// - shutdown
// - received SOA or notify packet
// - transfer completed
waitHandleArray[0] = hDnsShutdownEvent;
waitHandleArray[1] = g_SecondaryQueue->hEvent;
waitHandleArray[2] = g_hWakeSecondaryEvent;
//
// keep array of masters with outstanding transfers
//
// with this, we can avoid attempting multiple connections to
// same server (which will fail, since we use port 53 always)
//
aipMastersOut = DnsCreateIpArray( 10 );
//
// Initial sleep. This thread usually starts up before all zones have
// been created. Give the server some time to populate the zone list
// so that the first pass through the zone list takes all (or at
// least more) zones into account.
//
status = WaitForSingleObject( hDnsShutdownEvent, 15 * 1000 );
if ( status == WAIT_OBJECT_0 )
{
goto Cleanup;
}
//
// loop until service exit
//
// This loop is executed essentially everytime we receive
// an SOA check response or notify packet OR when a timeout
// expires.
//
// On each loop we send any SOA checks that are due (changing next
// timeout to retry interval), then wait for response or next timeout.
// On SOA response, timeouts reset for refresh, or zone transfer is
// initiated.
//
while ( TRUE )
{
DWORD dwTimeSlept;
//
// Check and possibly wait on service status
//
// Note, we MUST do this check BEFORE any processing to make
// sure all zones are loaded before we begin checks.
//
if ( ! Thread_ServiceCheck() )
{
DNS_DEBUG( ZONEXFR, (
"Terminating secondary zone control thread.\n" ));
goto Cleanup;
}
#if 0
//
// transfering multiple zones to same master
// - no need for master list
//
// clear masters outstanding list for rebuild
Dns_ClearIpArray( aipMastersOut );
#endif
//
// calculate next zone transfer timeout
//
// - loop through all secondary zones
// - get lowest "next time"
// - find offset from current time
// if a timeout has already elapsed, use zero
//
currentTime = UPDATE_DNS_TIME();
DNS_DEBUG( ZONEXFR, (
"Timeout check current time = %lu (s).\n",
currentTime ));
//
// loop through secondaries
// - inititate needed SOA checks or zone transfer
// - determine timeout to next check
//
// note: set no timeout at no less than a few second
// minimum, so don't loop pointlessly before any SOA
// responses come back
//
// DEVNOTE: may need a timeout max value to avoid
// long timeout locking things up while a
// zone is out for transfer
//
pzone = NULL;
timeout = MAXULONG;
while ( pzone = Zone_ListGetNextZone(pzone) )
{
// ignore cache
if ( IS_ZONE_CACHE(pzone) )
{
continue;
}
//
// ignore primaries and forwarding zones
//
else if ( IS_ZONE_PRIMARY( pzone ) || IS_ZONE_FORWARDER( pzone ) )
{
continue;
}
//
// secondary
//
// transfer in progress -- no SOA check
//
// - add master to outstanding list
// - include zone in timeout check below, so that
// we don't forget its next timeout
//
else if ( pzone->fXfrRecvLock )
{
DNS_DEBUG( XFR, (
"%s: zone %s still transfering from %s\n", fn,
pzone->pszZoneName,
IP_STRING( pzone->ipFreshMaster ) ));
}
//
// zone expiring -- shut it down
//
// note: to avoid continuous sends, while master is offline,
// send SOA query on expiration, then only at retry intervals
//
else if ( pzone->dwExpireTime <= currentTime
&&
! pzone->fShutdown )
{
IF_DEBUG( XFR )
{
Dbg_Zone(
"Expiring zone ",
pzone );
}
pzone->fShutdown = TRUE;
DNS_LOG_EVENT(
DNS_EVENT_ZONE_EXPIRATION,
1,
& pzone->pwsZoneName,
NULL,
0 );
// make immediate XFR attempt
pzone->dwNextSoaCheckTime = currentTime;
Xfr_SendSoaQuery( pzone );
}
//
// notified / refresh / retry => send SOA
//
// send SOA check when zone
// - at refresh
// - or in retry from NOTIFY (SOA or IXFR attempt)
// or failed refresh SOA query
//
// note: processNotify() sends immediately, so no need to
// check it's flag here; retries handled through timeout
//
else if ( pzone->dwNextSoaCheckTime < currentTime )
{
Xfr_SendSoaQuery( pzone );
}
ELSE_IF_DEBUG( ZONEXFR )
{
DNS_PRINT((
"%s: zone %s waiting for next timeout\n", fn,
pzone->pszZoneName ));
}
//
// find shortest timeout in zone list
//
if ( pzone->dwNextSoaCheckTime < timeout )
{
timeout = pzone->dwNextSoaCheckTime;
}
}
//
// time to next retry/refresh time
//
// a zone's next timeout may fall behind current time while
// zone is "owned" by transfer thread, producing a negative
// timeout interval;
//
// hence use a small minimum loop interval
// - allows reception of SOA check packets
// - prompt check when transfer reception thread returns
// zone to our control
// - prevent spinning and wasted cycles is zone improperly
// configured with zero retry
//
// for safety, fire up loop every hour or so to check things out
// convert to milliseconds for use in WaitForMultipleObjects()
//
timeout -= currentTime;
DNS_DEBUG( ZONEXFR2, (
"Min timeout found = %lu (s).\n",
timeout ));
if ( (LONG)timeout < MIN_SECONDARY_ZONE_CHECK_LOOP_TIME )
{
timeout = MIN_SECONDARY_ZONE_CHECK_LOOP_TIME;
}
else if ( timeout > (DWORD)MAX_SECONDARY_ZONE_CHECK_LOOP_TIME )
{
timeout = MAX_SECONDARY_ZONE_CHECK_LOOP_TIME;
}
//
// Wait for
// - SOA packet queued event
// - termination event
// with timeout to next zone SOA check.
//
WaitForTimeout:
dwTimeSlept = UPDATE_DNS_TIME();
DNS_DEBUG( ZONEXFR, (
"%s: sleeping for %lu seconds at %lu\n", fn,
timeout,
dwTimeSlept ));
status = WaitForMultipleObjects(
3,
waitHandleArray,
FALSE, // either event
( timeout * 1000 ) // timeout in ms
);
dwTimeSlept = UPDATE_DNS_TIME() - dwTimeSlept;
#if 0
//
// Adjust timeout by the amount of time we slept. Set it to
// zero to indicate that we slept as long as we need to.
//
if ( status != WAIT_OBJECT_0 + 2 || dwTimeSlept >= timeout )
{
//
// This event doesn't require us to go back to sleep or
// the time slept didn't make sense (could have wrapped).
//
timeout = 0;
}
else
{
timeout = timeout - dwTimeSlept;
}
#else
//
// There seems to be some confusion about event usage. For now
// always set timeout to zero. This forces us to jump back up to
// top of loop and perform SOA checks as necessary after processing
// any outstanding received packets.
//
timeout = 0;
#endif
DNS_DEBUG( ZONEXFR2, (
"%s: slept for %d seconds, timeout remaining %d, queue len %d\n", fn,
dwTimeSlept,
timeout,
g_SecondaryQueue->cLength ));
//
// Check and possibly wait on service status
//
if ( ! Thread_ServiceCheck() )
{
DNS_DEBUG( ZONEXFR, ( "%s: terminating\n", fn ));
goto Cleanup;
}
//
// Read queued SOA check responses and NOTIFY messages
//
// When indicated spawn the zone transfer threads
//
while ( pmsg = PQ_DequeueNextPacket( g_SecondaryQueue, FALSE ) )
{
DNS_DEBUG( ZONEXFR2, (
"%s: deqeued %p length now %d\n", fn,
pmsg,
g_SecondaryQueue->cLength ));
if ( pmsg->Head.Opcode == DNS_OPCODE_NOTIFY )
{
processNotify( pmsg );
}
else if ( IS_SOA_CHECK_XID(pmsg->Head.Xid) )
{
if ( !processSoaCheckResponse( pmsg ) )
{
if ( ptempQueue )
{
DNS_DEBUG( ZONEXFR2, (
"%s: SOA check resp %p not handled, requeue later", fn,
pmsg ));
PQ_QueuePacketEx( ptempQueue, pmsg, TRUE );
pmsg = NULL;
}
}
}
else
{
ASSERT( IS_IXFR_XID(pmsg->Head.Xid) );
processIxfrUdpResponse( pmsg );
}
if ( pmsg )
{
Packet_Free( pmsg );
}
}
//
// Requeue any messages not handled.
//
if ( ptempQueue && ptempQueue->cLength )
{
LOCK_QUEUE( g_SecondaryQueue );
while ( pmsg = PQ_DequeueNextPacket( ptempQueue, TRUE ) )
{
PQ_QueuePacketEx( g_SecondaryQueue, pmsg, TRUE );
}
UNLOCK_QUEUE( g_SecondaryQueue );
}
//
// If there is more time left to sleep, just back up to the wait
// otherwise continue to the top of the loop for zone expiry checks.
//
if ( timeout > 0 )
{
goto WaitForTimeout;
}
}
Cleanup:
if ( ptempQueue )
{
UNLOCK_QUEUE( ptempQueue );
PQ_DeletePacketQueue( ptempQueue );
}
return 1;
} // Xfr_ZoneControlThread
//
// SOA\IXFR request routines
//
PDNS_MSGINFO
Xfr_BuildXfrRequest(
IN OUT PZONE_INFO pZone,
IN WORD wType,
IN BOOL fTcp
)
/*++
Routine Description:
Build IXFR query.
Arguments:
pZone - info structure for zone
Return Value:
Ptr to IXFR message buffer.
--*/
{
PDNS_MSGINFO pmsg;
DWORD length;
// verify secondary
ASSERT( pZone );
ASSERT( IS_ZONE_SECONDARY(pZone) );
DNS_DEBUG( ZONEXFR, (
"buildIxfrRequest() for zone %s\n",
pZone->pszZoneName ));
//
// if on startup with no file, may not have SOA
// then can't do IXFR
//
if ( !pZone->pSoaRR && wType == DNS_TYPE_IXFR )
{
DNS_DEBUG( ZONEXFR, (
"Skipping IXFR, no SOA in zone %s\n",
pZone->pszZoneName ));
return( FALSE );
}
//
// create message info structure
//
length = 0;
if ( fTcp )
{
length = DNS_TCP_MAXIMUM_RECEIVE_LENGTH;
}
pmsg = Msg_CreateSendMessage( length );
IF_NOMEM( !pmsg )
{
return( NULL );
}
//
// write zone name question
//
if ( ! Msg_WriteQuestion(
pmsg,
pZone->pZoneTreeLink,
wType ) )
{
DNS_DEBUG( ANY, (
"ERROR: unable to write type=%d query for zone %s\n",
wType,
pZone->pszZoneName ));
ASSERT( FALSE );
goto Failed;
}
//
// for IXFR
// - current SOA in authority section
// - set XID to indicate IXFR check
//
if ( wType == DNS_TYPE_IXFR )
{
ASSERT( pZone->pZoneRoot && pZone->pSoaRR );
pmsg->Head.Xid = MAKE_IXFR_XID( pmsg->Head.Xid );
// write current SOA
pmsg->fDoAdditional = FALSE;
SET_TO_WRITE_AUTHORITY_RECORDS(pmsg);
if ( 1 != Wire_WriteRecordsAtNodeToMessage(
pmsg,
pZone->pZoneRoot,
DNS_TYPE_SOA,
DNS_OFFSET_TO_QUESTION_NAME,
0 ) )
{
DNS_DEBUG( ANY, (
"ERROR: Unable to write SOA to IXFR packet %s\n",
pZone->pszZoneName ));
ASSERT( FALSE );
goto Failed;
}
}
// write MS transfer tag
APPEND_MS_TRANSFER_TAG( pmsg );
return( pmsg );
Failed:
if ( pmsg )
{
Packet_Free( pmsg );
}
ASSERT( FALSE );
return( NULL );
}
BOOL
Xfr_SendUdpIxfrQuery(
IN OUT PZONE_INFO pZone,
IN IP_ADDRESS ipAddress
)
/*++
Routine Description:
Send IXFR query to master.
Arguments:
pZone - info structure for zone
ipMaster - IP of master to send IXFR to
Return Value:
None.
--*/
{
PDNS_MSGINFO pmsg;
// verify secondary
ASSERT( pZone );
ASSERT( IS_ZONE_SECONDARY(pZone) );
ASSERT( ZONE_MASTERS( pZone ) );
DNS_DEBUG( ZONEXFR, (
"Xfr_SendUdpIxfrQuery() for zone %s\n",
pZone->pszZoneName ));
//
// if zone has no SOA -- can't IXFR
//
if ( !pZone->pSoaRR )
{
ASSERT( IS_ZONE_EMPTY(pZone) );
return( FALSE );
}
ASSERT( pZone->pZoneRoot );
//
// create IXFR query
// - may be impossible if "fileless" zone on startup as no SOA to send
// - also possible to overrun UDP packet size with really long SOA name fields
//
pmsg = Xfr_BuildXfrRequest(
pZone,
DNS_TYPE_IXFR,
FALSE // use UDP
);
if( !pmsg )
{
return( FALSE );
}
//
// since UDP may lose query or non-IXFR-aware master may eat query
// or mangle response, set flag indicating IXFR tried and
// do short retry
//
pZone->dwNextSoaCheckTime = DNS_TIME() + IXFR_RETRY_INTERVAL;
pZone->cIxfrAttempts++;
pmsg->fDelete = TRUE;
pmsg->RemoteAddress.sin_addr.s_addr = ipAddress;
STAT_INC( SecondaryStats.IxfrUdpRequest );
Send_Msg( pmsg );
return( TRUE );
}
VOID
Xfr_SendSoaQuery(
IN OUT PZONE_INFO pZone
)
/*++
Routine Description:
Send SOA question to primary.
Arguments:
pZone - info structure for zone
Return Value:
None.
--*/
{
PDNS_MSGINFO pmsg;
PSOCKADDR_IN pSockaddr;
DWORD i;
// verify secondary
ASSERT( pZone );
ASSERT( IS_ZONE_SECONDARY(pZone) );
ASSERT( ZONE_MASTERS( pZone ) );
DNS_DEBUG( ZONEXFR, (
"Xfr_SendSoaQuery() to masters for zone %s\n",
pZone->pszZoneName ));
//
// if zone is in transfer then don't send
//
// generally should NOT be locked to process SOA or IXFR response as
// these should occur along with SoaQuery in zone control thread)
//
if ( IS_ZONE_LOCKED_FOR_WRITE(pZone) )
{
DNS_DEBUG( XFR, (
"Zone %s is write-locked, skipping SOA query!\n",
pZone->pszZoneName ));
return;
}
//
// if NOT required SOA send, avoid spin
//
// DEVNOTE: may be issue here with SOA query lockout
// - SOA, IXFR attempt (possibly lost other SOA responses), SOA rejected
//
if ( pZone->dwLastSoaCheckTime + MIN_SOA_RETRY_TIME > DNS_TIME() )
{
DNS_DEBUG( XFR, (
"Skipping SOA resend on zone %s.\n"
"\tLast SOA send within last %d(s).\n"
"\tlast send at %d\n"
"\tcurrent time %d\n",
pZone->pszZoneName,
MIN_SOA_RETRY_TIME,
pZone->dwLastSoaCheckTime,
DNS_TIME() ));
return;
}
//
// create message info structure
//
pmsg = Msg_CreateSendMessage( 0 );
IF_NOMEM( !pmsg )
{
Xfr_RetryZone( pZone );
return;
}
//
// build SOA query
// - set XID to indicate SOA check
if ( ! Msg_WriteQuestion(
pmsg,
pZone->pZoneTreeLink,
DNS_TYPE_SOA ) )
{
DNS_DEBUG( ANY, (
"ERROR: Unable to write SOA query for zone %s\n",
pZone->pszZoneName ));
Packet_Free( pmsg );
ASSERT( FALSE );
return;
}
pmsg->Head.Xid = MAKE_SOA_CHECK_XID( pmsg->Head.Xid );
//
// send query to each master in list
// - no need to lock, atomic creation\deletion of master list
//
ASSERT( !pmsg->fDelete );
pmsg->fDelete = TRUE;
Send_Multiple(
pmsg,
ZONE_MASTERS( pZone ),
& SecondaryStats.SoaRequest );
pZone->dwLastSuccessfulSoaCheckTime = ( DWORD ) time( NULL );
//
// reset timeouts for retry
//
Xfr_RetryZone( pZone );
//
// DEVNOTE: zone expiration\stale issues
// DEVNOTE: when IXFR fails, should be doing direct connect for AXFR
//
// DEVNOTE: need flags for each server in master list
// - sent SOA
// - sent IXFR
// - got response
// - has inferior version (if all masters hit this, REBUILD!)
// - has superior version (or reverse, "no-help" flag)
// - needs AXFR resposne to IXFR
// - invalid IXFR (primaryServer not valid)
// - refused IXFR (bad candidate for AXFR)
// - refused AXFR
//
// Permanent flags:
// - doesn't understand IXFR
//
// then can push through list trying IXFR until
// - success IXFR
// - known in ssync (all back are in ssync at least one at current)
// - need AXFR
// - can't get response from anyone, fall to default timeouts
//
} // Xfr_SendSoaQuery
//
// SOA\IXFR response routines
//
IP_ADDRESS
matchMessageToMaster(
IN PDNS_MSGINFO pMsg,
IN PZONE_INFO pZone
)
/*++
Routine Description:
Matches message sender to a zone master.
Arguments:
pMsg -- message received
pZone -- zone to find master in
Return Value:
IP address of matching master, if successful.
0 if no match.
--*/
{
IP_ADDRESS ip;
ASSERT( ZONE_MASTERS( pZone ) );
ip = pMsg->RemoteAddress.sin_addr.s_addr;
if ( DnsIsAddressInIpArray(
ZONE_MASTERS( pZone ),
ip ) )
{
return( ip );
}
return( 0 );
}
PZONE_INFO
readZoneFromSoaAnswer(
IN OUT PDNS_MSGINFO pMsg,
IN WORD wType,
OUT PDWORD pdwVersion,
OUT PDB_RECORD * ppSoaRR
)
/*++
Routine Description:
Read zone info from SOA response.
Arguments:
pMsg - ptr to response info
Return Value:
None.
--*/
{
register PCHAR pch;
PZONE_INFO pzone;
PDB_RECORD psoaRR = NULL;
DNS_DEBUG( ZONEXFR, ( "readZoneFromSoaAnswer(%p).\n", pMsg ));
//
// validate as response packet
// - also points pCurrent at response
//
// DEVNOTE: may want to handle no-answer case to catch authority empty
// responses from BIND servers that are not IXFR aware
//
if ( !Msg_ValidateResponse( pMsg, NULL, wType, 0 ) )
{
goto PacketError;
}
//
// find the zone
// - answer for node in database
// - must be zone root
// - zone must be secondary in list
// - sender IP, should be zone master
//
pzone = Lookup_ZoneForPacketName(
pMsg->MessageBody,
pMsg );
if ( ! pzone )
{
Dbg_MessageName(
"ERROR: received SOA\\IXFR\\NOTIFY for non-authoritative zone ",
pMsg->pCurrent,
pMsg );
//CLIENT_ASSERT( FALSE );
goto PacketError;
}
pMsg->pzoneCurrent = pzone;
//
// must be secondary (though may get NOTIFY to primary for DS-integrated)
//
if ( !IS_ZONE_SECONDARY(pzone) )
{
if ( pMsg->Head.Opcode == DNS_OPCODE_NOTIFY )
{
return( pzone );
}
//CLIENT_ASSERT( FALSE );
goto PacketError;
}
//
// if notify, may not have SOA
//
if ( pMsg->Head.AnswerCount == 0 )
{
if ( pMsg->Head.Opcode == DNS_OPCODE_NOTIFY )
{
return( pzone );
}
CLIENT_ASSERT( FALSE ); // no SOA in response
goto PacketError;
}
//
// parse SOA record
// - pull out master's version
//
pch = Wire_SkipPacketName( pMsg, pMsg->pCurrent );
if ( ! pch )
{
goto PacketError;
}
//
// build SOA
// - will always need to check version
// - sometimes will need to check primary server
//
psoaRR = Wire_CreateRecordFromWire(
pMsg,
NULL, // haven't parsed RR header
pch,
MEMTAG_RECORD_AXFR );
if ( !psoaRR )
{
goto PacketError;
}
if ( psoaRR->wType != DNS_TYPE_SOA )
{
goto PacketError;
}
*pdwVersion = ntohl( psoaRR->Data.SOA.dwSerialNo );
if ( ppSoaRR )
{
*ppSoaRR = psoaRR;
}
else
{
RR_Free( psoaRR );
}
return( pzone );
PacketError:
DNS_PRINT((
"ERROR: bogus SOA\\IXFR\\NOTIFY packet at %p from master %s\n",
pMsg,
IP_STRING( pMsg->RemoteAddress.sin_addr.s_addr )
));
if ( ppSoaRR )
{
*ppSoaRR = NULL;
}
if ( psoaRR )
{
RR_Free( psoaRR );
}
return( NULL );
}
BOOL
doesMasterHaveFreshVersion(
IN OUT PZONE_INFO pZone,
IN IP_ADDRESS ipMaster,
IN DWORD dwMasterVersion,
IN BOOL fIxfr
)
/*++
Routine Description:
Check if response is fresh version.
If master has fresh version, return.
If master has older version reset zone timeouts.
This exists simply to share code between IXFR and SOA query
responses.
Arguments:
pZone -- zone to transfer
ipMaster -- master IP address
dwMasterVersion -- master version
fIxfr -- TRUE if response from IXFR
Return Value:
TRUE if master has fresh (higher) version.
FALSE otherwise.
--*/
{
INT versionDiff;
//
// compare versions, if fresh return
//
versionDiff = Zone_SerialNoCompare( dwMasterVersion, pZone->dwSerialNo );
if ( versionDiff > 0 )
{
return( TRUE );
}
//
// DEVNOTE: need IN_SYNC_VERSION and LOWER_VERSION master flags
// then
// => refresh immediately if notifier
// otherwise
// -- all IN_SYNC => refresh
// -- all higher => log error
//
if ( versionDiff == 0 )
{
#if 0
//
// DEVNOTE: log to log, not event log
//
pszeventArgs[0] = pZone->pszZoneName;
pszeventArgs[1] = szdwMasterVersion;
pszeventArgs[2] = pszipMaster;
DNS_LOG_EVENT(
DNS_EVENT_ZONE_IN_SYNC,
3,
pszeventArgs,
EVENTARG_ALL_UTF8,
0 );
#endif
DNS_DEBUG( ZONEXFR, (
"Zone %s in sync with master, refresh timeouts.\n",
pZone->pszZoneName ));
if ( !pZone->ipNotifier || pZone->ipNotifier == ipMaster )
{
Xfr_RefreshZone( pZone );
}
else
{
pZone->fSlowRetry = TRUE;
Xfr_RetryZone( pZone );
}
}
//
// newer version than current?
//
// if contacting another secondary rather than primary, then
// might have a version that is actually older than our own
//
// test if difference < 0, this should not be true in any reasonable
// case IF not at startup of no file secondary
//
// instead test if difference is greater than half a DWORD,
// then the "New" version is actually older
//
// example:
// sNew = 5 sOld = 10 => transfer
// sNew = 0 sOld = 0xffffffff => transfer
// sNew = 0xffffffff sOld = 5 => hold onto old
//
else
{
ASSERT( (LONG)versionDiff < 0 );
#if 0
argArray[0] = pZone->pszZoneName;
argArray[1] = (PCHAR) pZone->dwSerialNo;
argArray[2] = (PCHAR) dwMasterVersion;
argArray[3] = (PCHAR) ipMaster;
typeArray[0] = EVENTARG_UTF8;
typeArray[1] = EVENTARG_DWORD;
typeArray[2] = EVENTARG_DWORD;
typeArray[3] = EVENTARG_IP_ADDRESS;
DNS_LOG_EVENT(
DNS_EVENT_ZONE_NEWER_THAN_SERVER_VERSION,
4,
argArray,
typeArray,
0 );
#endif
DNS_DEBUG( XFR, (
"WARNING: Secondary zone %s newer than master (%s) -- no transfer.\n"
"\tnew version = %08lx\n"
"\tcurrent version = %08lx\n",
pZone->pszZoneName,
IP_STRING( ipMaster ),
dwMasterVersion,
pZone->dwSerialNo ));
if ( !pZone->ipNotifier || pZone->ipNotifier == ipMaster )
{
Xfr_RefreshZone( pZone );
}
else
{
pZone->fSlowRetry = TRUE;
Xfr_RetryZone( pZone );
}
}
return( FALSE );
}
VOID
processNotify(
IN OUT PDNS_MSGINFO pMsg
)
/*++
Routine Description:
Process NOTIFY packet.
Find zone for notify, and setup zone for SOA check.
Note: Does NOT free NOTIFY packet -- callers responsibility.
Arguments:
pMsg -- NOTIFY packet
Return Value:
None.
--*/
{
PZONE_INFO pzone;
IP_ADDRESS ipnotifier;
DWORD masterVersion = 0;
INT versionDiff;
ASSERT( pMsg->Head.Opcode == DNS_OPCODE_NOTIFY );
DNS_DEBUG( ZONEXFR, ( "processNotify( %p ).\n", pMsg ));
STAT_INC( SecondaryStats.NotifyReceived );
//
// verify we have SOA notify, ignore other types
//
ipnotifier = pMsg->RemoteAddress.sin_addr.s_addr;
if ( pMsg->wQuestionType != DNS_TYPE_SOA )
{
DNS_PRINT(( "WARNING: message at %p, non-SOA NOTIFY.\n", pMsg ));
STAT_INC( SecondaryStats.NotifyInvalid );
return;
}
//
// verify packet
// - set to response since readZoneFromSoaAnswer will use ValidateResponse()
// - will extract SOA in notify if available
//
pMsg->Head.IsResponse = TRUE;
pzone = readZoneFromSoaAnswer(
pMsg,
DNS_TYPE_SOA,
& masterVersion,
NULL );
if ( !pzone )
{
Dbg_MessageName(
"ERROR: received notify for non-existent or non-root node.\n",
pMsg->MessageBody,
pMsg );
STAT_INC( SecondaryStats.NotifyInvalid );
return;
}
pMsg->Head.IsResponse = FALSE;
DNS_DEBUG( XFR, (
"Received notify for zone %s, at version %d\n"
"\tcurrent version = %d\n",
pzone->pszZoneName,
masterVersion,
pzone->dwSerialNo ));
//
// check if primary zone
//
// DEVNOTE: should log non-DS primary partner
// DEVNOTE: should we ACK notifies even if bogus?
//
if ( IS_ZONE_PRIMARY(pzone) )
{
//STAT_INC( PrivateStats.PrimaryNotifies );
if ( pzone->fDsIntegrated )
{
DNS_DEBUG( XFR, (
"Notify to primary-DS zone (presumably from partner).\n",
pzone->pszZoneName ));
}
//
// DEVNOTE-LOG: log someone sending NOTIFYs to primary
//
Dbg_MessageName(
"ERROR: received notify for PRIMARY zone.\n",
pMsg->MessageBody,
pMsg );
STAT_INC( SecondaryStats.NotifyPrimary );
return;
}
//
// Check if other zone type that does not want notifies.
//
if ( IS_ZONE_STUB( pzone ) || IS_ZONE_FORWARDER( pzone ) )
{
DNS_DEBUG( XFR, (
"Notify to non-primary zone %s zone type %d\n",
pzone->pszZoneName,
pzone->fZoneType ));
Dbg_MessageName(
"ERROR: received notify for non-primary zone\n",
pMsg->MessageBody,
pMsg );
STAT_INC( SecondaryStats.NotifyNonPrimary );
return;
}
//
// ACK notify packet
// - reset pCurrent to message length
// (readZoneFromSoaAnswer() leaves pCurrent at end of Question)
// - simply flip on IsResponse bit and send back
// - clear fDelete as message freed in main thread routine
//
pMsg->pCurrent = DNSMSG_END( pMsg );
pMsg->Head.IsResponse = TRUE;
pMsg->fDelete = FALSE;
Send_Msg( pMsg );
//
// no current version -- must send
//
if ( !pzone->pSoaRR )
{
STAT_INC( SecondaryStats.NotifyNoVersion );
goto Send;
}
//
// check serial
// - notify only interesting if larger serial
//
if ( masterVersion )
{
versionDiff = Zone_SerialNoCompare( masterVersion, pzone->dwSerialNo );
if ( versionDiff == 0 )
{
DNS_DEBUG( XFR, (
"Notified by %s at same as current zone version %d\n",
IP_STRING( ipnotifier ),
pzone->dwSerialNo ));
STAT_INC( SecondaryStats.NotifyCurrentVersion );
return;
}
else if ( versionDiff < 0 )
{
DNS_DEBUG( XFR, (
"Notified by %s at version %d less than current version %d\n",
IP_STRING( ipnotifier ),
masterVersion,
pzone->dwSerialNo ));
STAT_INC( SecondaryStats.NotifyOldVersion );
return;
}
else
{
DNS_DEBUG( XFR, (
"Notified by %s at version %d greater than current version %d\n",
IP_STRING( ipnotifier ),
masterVersion,
pzone->dwSerialNo ));
STAT_INC( SecondaryStats.NotifyNewVersion );
}
}
Send:
//
// DEVNOTE: pick best notifier (highest version), if already notified
//
// DEVNOTE: log complaint, if ONLY one master and it is behind in count
//
// DEVNOTE: notified, same version, different primary???
//
// DEVNOTE: fNotified flag is currently doing anything
//
//
// check if notifier is in zone's master list
// not necessarily a problem, as primary master may notify everyone
//
pzone->ipNotifier = 0;
pzone->fNotified = TRUE;
ipnotifier = pMsg->RemoteAddress.sin_addr.s_addr;
if ( DnsIsAddressInIpArray(
ZONE_MASTERS( pzone ),
ipnotifier ) )
{
pzone->ipNotifier = ipnotifier;
if ( Xfr_SendUdpIxfrQuery(
pzone,
ipnotifier ) )
{
return;
}
}
STAT_INC( SecondaryStats.NotifyMasterUnknown );
//
// notifier not in master list OR we can't build IXFR
//
DNS_DEBUG( ZONEXFR, (
"WARNING: Notify packet (%p), for zone %s, from %s\n"
"\tNOT a specified master for this secondary zone.\n",
pMsg,
pzone->pszZoneName,
IP_STRING(ipnotifier) ));
Xfr_SendSoaQuery( pzone );
//
// set NOTIFIED flag on zone -- forces SOA check request
//
// DEVNOTE: save notified version, keep trying until get IT
//
// DEVNOTE: save notify recv() socket
// this lets us send XFR on socket bound to this address
// either
// 1) send SOA query on this socket, so get SOA response on it
// and hence have existing code to XFR with SOA response socket binding
// 2) save notify recv socket binding IP, then can use it in XFR
// problem here is to know that notify binding() is correct
// - multiple notifies, second overwrites first
// - final responding server not original notifier
//
} // processNotify
BOOL
processSoaCheckResponse(
IN OUT PDNS_MSGINFO pMsg
)
/*++
Routine Description:
Process response to SOA query from primary.
Check that response is valid or determine whether serial number is
new for its zone.
Then refresh zone or initiate zone transfer.
Arguments:
pMsg - ptr to response info
Return Value:
Returns FALSE if no action was taken for this packet, for example if
a zone transfer thread was not created because too many threads are
already outstanding. The caller may will to requeue the msg and handle
it later.
--*/
{
DBG_FN( "processSoaCheckResponse" )
BOOL bMessageHandled = TRUE;
PZONE_INFO pzone = NULL;
PDB_NODE pnode = NULL;
DWORD i;
BOOL fzoneLocked = FALSE;
DWORD masterVersion = 0; // serial number in SOA response
DWORD currentVersion; // current serial number
DWORD versionDiff;
IP_ADDRESS masterIp;
PCHAR argArray[4]; // logging args
BYTE typeArray[4];
DNS_DEBUG( ZONEXFR, ( "%s: pMsg %p\n", fn, pMsg ));
STAT_INC( SecondaryStats.SoaResponse );
//
// Verify valid SOA response and extract zone. Note that AnswerCount
// can be greater than 1 because of SIGs.
//
if ( pMsg->Head.AnswerCount == 0 ||
pMsg->Head.ResponseCode != DNS_RCODE_NO_ERROR )
{
goto SoaPacketError;
}
pzone = readZoneFromSoaAnswer(
pMsg,
DNS_TYPE_SOA,
& masterVersion,
NULL );
if ( !pzone )
{
goto SoaPacketError;
}
ASSERT( IS_ZONE_SECONDARY(pzone) );
//
// match SOA sender to a zone master server
//
// don't do XID match, as may get caught always receiving
// and ignoring responses to previous SOA queries
//
//
// DEVNOTE: can not count on getting response from same master IP as sent
//
// DEVNOTE: needs protection from admin or else master list structure
// must be atomic update
//
masterIp = pMsg->RemoteAddress.sin_addr.s_addr;
if ( ! DnsIsAddressInIpArray(
ZONE_MASTERS( pzone ),
masterIp ) )
{
DNS_DEBUG( ZONEXFR, (
"ERROR: SOA response (%p), for zone %s, from %s\n"
"\tNOT from specified master for this secondary.\n",
pMsg,
pzone->pszZoneName,
IP_STRING(masterIp) ));
goto SoaPacketError;
}
//
// Reset the fast SOA checks counter on the zone. Since we got some kind
// of response the next time we want to start retries fresh.
//
pzone->dwFastSoaChecks = 0;
//
// lock zone
//
if ( !Zone_LockForXfrRecv(pzone) )
{
DNS_DEBUG( XFR, (
"%s: zone %s is locked ignoring SOA response\n", fn,
pzone->pszZoneName ));
goto Cleanup;
}
fzoneLocked = TRUE;
//
// get current serial number for zone
// - make string representation for logging
//
// if zone has no serial (i.e. no SOA) it is new unloaded zone
// (or broken somehow) and needs immediate transfer
//
if ( IS_ZONE_EMPTY(pzone) )
{
pzone->fNeedAxfr = TRUE;
goto TransferZone;
}
//
// compare versions
// - if unchanged or lower, reset timeouts and we're done
// - if higher then transfer
//
if ( ! doesMasterHaveFreshVersion(
pzone,
masterIp,
masterVersion,
FALSE // not IXFR response
) )
{
goto Cleanup;
}
//
// disable IXFR for stub zones - always use "customized" zone
// transfer (overridden in AXFR code).
//
if ( IS_ZONE_STUB( pzone ) )
{
pzone->fNeedAxfr = TRUE;
}
TransferZone:
DNS_DEBUG( XFR, (
"%s: attempting to start xfer for zone %s\n", fn,
pzone->pszZoneName ));
//
// indicate new version on master
// - if unable to transfer, we'll know to keep trying
//
pzone->fStale = TRUE;
//
// if haven't done IXFR, try it (if possible)
// otherwise do AXFR
//
// DEVNOTE: consider SOA response master to be different from notifier
// a notifier definitely WILL XFR with you; this one may not
//
// DEVNOTE: need to check if THIS server has failed IXFR
//
if ( !pzone->fNeedAxfr &&
!pzone->fSkipIxfr &&
pzone->cIxfrAttempts < 5 &&
(DWORD)pzone->cIxfrAttempts < ZONE_MASTERS( pzone )->AddrCount )
{
ASSERT( !IS_ZONE_EMPTY(pzone) );
if ( Xfr_SendUdpIxfrQuery( pzone, masterIp ) )
{
goto Cleanup;
}
}
pzone->fNeedAxfr = TRUE; // force AXFR
if ( !startTcpXfr(
pzone,
masterIp,
masterVersion,
pMsg->Socket ) )
{
bMessageHandled = FALSE;
}
return bMessageHandled;
SoaPacketError:
//
// bad response, problem with other server
// or
// server doesn't have zone (NAME_ERROR)
//
STAT_INC( SecondaryStats.SoaResponseInvalid );
#if 0
pszeventArgs[0] = pzone->pszZoneName;
pszeventArgs[1] = pszmasterIp;
DNS_LOG_EVENT(
DNS_EVENT_ZONE_SERVER_BAD_RESPONSE,
2,
pszeventArgs,
EVENTARG_ALL_UTF8,
GetLastError() );
#endif
IF_DEBUG( ZONEXFR )
{
Dbg_DnsMessage(
"ERROR: Bad SOA check response",
pMsg );
}
Cleanup:
if ( fzoneLocked )
{
Zone_UnlockAfterXfrRecv( pzone );
}
return bMessageHandled;
} // processSoaCheckResponse
VOID
processIxfrUdpResponse(
IN OUT PDNS_MSGINFO pMsg
)
/*++
Routine Description:
Process IXFR response.
Arguments:
pMsg - ptr to response info
Return Value:
None.
--*/
{
PZONE_INFO pzone = NULL;
PDB_RECORD psoaRR = NULL;
DNS_STATUS status;
DWORD masterVersion; // serial number in SOA response
DWORD currentVersion;
DWORD versionDiff;
IP_ADDRESS masterIp;
BOOL fzoneLocked = FALSE;
UPDATE_LIST ixfrUpdateList;
UPDATE_LIST passUpdateList;
DWORD eventId;
PVOID argArray[4]; // logging args
BYTE typeArray[4];
DNS_DEBUG( ZONEXFR, (
"processIxfrUdpMsg(%p).\n"
"\tfrom master %s\n",
pMsg,
IP_STRING( pMsg->RemoteAddress.sin_addr.s_addr )
));
IF_DEBUG( ZONEXFR2 )
{
Dbg_DnsMessage(
"IXFR response\n",
pMsg );
}
STAT_INC( SecondaryStats.IxfrUdpResponse );
// init update lists here, so we can call free routine in all cases
// both success and failure during parsing require free to avoid leak
Up_InitUpdateList( &ixfrUpdateList );
Up_InitUpdateList( &passUpdateList );
//
// verify packet
//
pzone = readZoneFromSoaAnswer(
pMsg,
DNS_TYPE_IXFR,
& masterVersion,
& psoaRR );
if ( !pzone )
{
// DEVNOTE: check for authoritative empty response here
// non-ixfr MS-DNS would FORMERR IXFR, but a BIND server might
// accept as and simply indicate no "IXFR records"
// when stop generally going to AXFR on packet errors, need to
// make sure this particular case gets handled that way
// also should set SkipIxfr flag if clearly detect valid master with
// IXFR support problem
DNS_DEBUG( ANY, (
"ERROR: IXFR response not for secondary zone!\n" ));
status = DNS_ERROR_ZONE_NOT_SECONDARY;
STAT_INC( SecondaryStats.IxfrUdpInvalid );
goto Cleanup;
}
ASSERT( IS_ZONE_SECONDARY(pzone) );
#if 0
//
// DEVNOTE: nice to do IXFR parsing outside of lock, but DOES NOT
// have much impact as really only affects locking out primary
// XFR; other secondary action (outside AXFR) is on this thread
//
// zone transfer lock to another server?
// - if so, dangerous to munge flags
//
// note: once verify zone not locked, then we can munge with
// impunity since ONLY zone control thread calls this function
// and starts transfers
// might lock zone on another thread to service an XFR as master
// but that wouldn't affect these secondary flags
//
if ( IS_ZONE_LOCKED(pzone) )
{
DNS_DEBUG( XFR, (
"IXFR response from %s, for currently LOCKED zone %s\n"
"\tignoring IXFR response\n",
IP_STRING(masterIp),
pzone->pszZoneName ));
goto Cleanup;
}
#endif
//
// lock zone
//
if ( !Zone_LockForXfrRecv(pzone) )
{
DNS_DEBUG( XFR, (
"Secondary Zone %s, locked -- unable to process UDP IXFR response.\n"
"\tipFreshMaster = %s\n"
"\tIXFR master = %s\n",
pzone->pszZoneName,
IP_STRING( pzone->ipFreshMaster ),
IP_STRING( pMsg->RemoteAddress.sin_addr.s_addr )
));
goto Cleanup;
}
fzoneLocked = TRUE;
//
// match IXFR sender to a zone master server
//
// DEVNOTE: can not count on getting response from same master IP as sent
//
// DEVNOTE: needs protection from admin or else master list structure
// must be atomic update
//
// DEVNOTE: use IXFR version info on IXFR from other server
// issues:
// - need to verify in list, or use just to force SOA requery
// - if in list, and higher than expected, try to get IXFR?
//
// DEVNOTE: can not count on getting response from same master IP as sent
//
// DEVNOTE: needs protection from admin or else master list structure
// must be atomic update
//
masterIp = pMsg->RemoteAddress.sin_addr.s_addr;
if ( ! DnsIsAddressInIpArray(
ZONE_MASTERS( pzone ),
masterIp ) )
{
DNS_DEBUG( ZONEXFR, (
"ERROR: IXFR response (%p), for zone %s, from %s\n"
"\tNOT from specified master for this secondary.\n",
pMsg,
pzone->pszZoneName,
IP_STRING(masterIp) ));
STAT_INC( SecondaryStats.IxfrUdpWrongServer );
goto Cleanup;
}
#if 0
//
// if do SOA query first and designate "fresh master" then this is correct
// approach. currently sending IXFR instead
//
// if not master IP we just sent to
// - then assume this is stale, toss away
//
masterIp = pMsg->RemoteAddress.sin_addr.s_addr;
if ( masterIp != pzone->ipFreshMaster && pzone->ipFreshMaster )
{
DNS_DEBUG( ZONEXFR, (
"WARNING: IXFR response (%p), for zone %s, version = %d, from %s\n"
"\tNOT from specified master %p for this secondary.\n",
pMsg,
pzone->pszZoneName,
IP_STRING(masterIp),
pzone->ipFreshMaster ));
STAT_INC( SecondaryStats.IxfrUdpWrongServer );
goto Cleanup;
}
#endif
//
// need AXFR
// - now sending IXFR requests sometimes like SOA requests
//
if ( !pzone->pSoaRR )
{
DNS_PRINT((
"ERROR: IXFR response to zone %s, with no SOA\n"
"\tshould have never sent IXFR!!!\n",
pzone->pszZoneName ));
goto TryAxfr;
}
//
// compare versions
// - if unchanged or lower, reset timeouts and we're done
// - if higher then continue to process IXFR
//
if ( ! doesMasterHaveFreshVersion(
pzone,
masterIp,
masterVersion,
TRUE // IXFR
) )
{
STAT_INC( SecondaryStats.IxfrUdpNoUpdate );
goto Cleanup;
}
//
// protect against bad IXFR to multi-DS-primary
// IXFR must satisfy one of two conditions
// - be from IP of last AXFR
// - have the same PrimaryServer field in the SOA
// indicating ultimate source is the same
//
// DEVNOTE: should keep trying last AXFR master until sure it's dead
// before pulling whole AXFR
//
if ( masterIp != pzone->ipLastAxfrMaster )
{
if ( ! Name_IsEqualDbaseNames(
& psoaRR->Data.SOA.namePrimaryServer,
& pzone->pSoaRR->Data.SOA.namePrimaryServer ) )
{
DNS_DEBUG( XFR, (
"WARNING: IXFR response with new primary, forcing AXFR!\n"
"\tIXFR master = %s\n"
"\tlast AXFR master = %s\n",
IP_STRING( masterIp ),
IP_STRING( pzone->ipLastAxfrMaster )
));
STAT_INC( SecondaryStats.IxfrUdpNewPrimary );
goto TryAxfr;
}
}
//
// parse IXFR packet
// - init update lists
// - init packet context
// - parse packet
//
XFR_MESSAGE_NUMBER(pMsg) = 1;
IXFR_CLIENT_VERSION(pMsg) = pzone->dwSerialNo;
IXFR_MASTER_VERSION(pMsg) = 0;
RECEIVED_XFR_STARTUP_SOA(pMsg) = FALSE;
pMsg->pzoneCurrent = pzone;
status = Xfr_ParseIxfrResponse(
pMsg,
& ixfrUpdateList, // full update list for transfer
& passUpdateList // update list for this pass
);
switch ( status )
{
case DNSSRV_STATUS_AXFR_COMPLETE:
DNS_DEBUG( ZONEXFR, (
"Recv'd valid UDP IXFR %p, from %s\n",
pMsg,
IP_STRING(masterIp) ));
goto Done;
case DNSSRV_STATUS_NEED_AXFR:
// the server has sent a single response SOA with its version
// this can be because
// - TCP necessary (IXFR does not fit in UDP message)
// - or AXFR required
DNS_DEBUG( ZONEXFR, (
"IXFR msg at %p indicates need TCP\n",
pMsg ));
STAT_INC( SecondaryStats.IxfrUdpUseTcp );
goto TryTcp;
case DNSSRV_STATUS_IXFR_UNSUPPORTED:
// master doesn't support IXFR
// set flag so always skipped
pzone->fSkipIxfr = TRUE;
DNS_DEBUG( ZONEXFR, (
"WARNING: IXFR msg at %p confused server %s, try AXFR\n",
pMsg ));
STAT_INC( SecondaryStats.IxfrUdpFormerr );
goto TryAxfr;
case DNS_ERROR_RCODE:
{
DNS_DEBUG( ZONEXFR, (
"IXFR msg at %p with RCODE %d\n",
pMsg,
pMsg->Head.ResponseCode ));
goto MasterFailure;
}
#if 0
case ERROR_SUCCESS:
// log this error -- malfunctioning IXFR implementation
DNS_DEBUG( ANY, (
"DNS server at %s, sent incomplete UDP IXFR %p\n",
IP_STRING(masterIp),
pMsg ));
CLIENT_ASSERT( FALSE );
goto MasterFailure;
case DNSSRV_STATUS_AXFR_IN_IXFR:
// log this error -- malfunctioning IXFR implementation
DNS_DEBUG( ANY, (
"ERROR: Recving AXFR in UDP IXFR message at %p\n",
pMsg ));
CLIENT_ASSERT( FALSE );
goto MasterFailure;
#endif
default:
DNS_DEBUG( ANY, (
"ERROR: Unknown status %p (%d) from ParseIxfrResponse() on packet %p\n",
status, status,
pMsg ));
//ASSERT( FALSE );
goto MasterFailure;
}
Done:
#if 0
// currently locking up above, before parsing message
//
// valid IXFR message, read records into zone
//
if ( !Zone_LockForXfrRecv(pzone) )
{
DNS_PRINT((
"ERROR: Zone %s, locked -- unable to read in UDP IXFR transfer.\n",
pzone->pszZoneName ));
goto ServerFailure;
}
fzoneLocked = TRUE;
#endif
//
// execute IXFR updates
// - leave zone locked until zone XFR flags reset
// - reinit update list to no-op global update list cleanup below
//
status = Up_ApplyUpdatesToDatabase(
& ixfrUpdateList,
pzone,
DNSUPDATE_IXFR |
DNSUPDATE_COMPLETE |
DNSUPDATE_NO_UNLOCK |
DNSUPDATE_NO_NOTIFY
);
if ( status != ERROR_SUCCESS )
{
goto ServerFailure;
}
Up_InitUpdateList( &ixfrUpdateList );
ASSERT( passUpdateList.pListHead == NULL );
ASSERT( ixfrUpdateList.pListHead == NULL );
//
// turn zone back on, reset its timeouts, reset failure count
//
// save this version as base version -- may not do incrementals to
// secondaries with lower versions
//
STAT_INC( SecondaryStats.IxfrUdpSuccess );
pzone->dwBadMasterCount = 0;
Xfr_RefreshZone( pzone );
pzone->dwLoadSerialNo = pzone->dwSerialNo;
//
// write zone back to file -- if any
// notify any secondaries
//
// DEVNOTE: push IXFR updates to disk
//
//File_WriteZoneToFile( pzone );
Xfr_SendNotify( pzone );
ASSERT( !pzone->fSkipIxfr );
ASSERT( !pzone->fNeedAxfr );
goto Cleanup;
MasterFailure:
if ( pMsg->Head.ResponseCode == DNS_RCODE_REFUSED )
{
STAT_INC( SecondaryStats.IxfrUdpRefused );
eventId = DNS_EVENT_AXFR_REFUSED;
}
else if ( pMsg->Head.ResponseCode == DNS_RCODE_FORMERR )
{
STAT_INC( SecondaryStats.IxfrUdpFormerr );
eventId = DNS_EVENT_IXFR_UNSUPPORTED;
}
else
{
STAT_INC( SecondaryStats.IxfrUdpInvalid );
DNS_DEBUG( ANY, (
"ERROR: %p (%d) parsing IXFR message %p\n",
status, status,
pMsg ));
IF_DEBUG( ZONEXFR )
{
Dbg_DnsMessage(
"ERROR: Bad UDP IXFR response",
pMsg );
}
eventId = DNS_EVENT_AXFR_BAD_RESPONSE;
CLIENT_ASSERT( FALSE );
}
//
// log master failure -- but avoid log spinning
//
if ( pzone->dwBadMasterCount < 3 )
{
argArray[0] = pzone->pwsZoneName;
argArray[1] = ( PCHAR ) ( DWORD_PTR ) masterIp;
typeArray[0] = EVENTARG_UNICODE;
typeArray[1] = EVENTARG_IP_ADDRESS;
DNS_LOG_EVENT(
eventId,
2,
argArray,
typeArray,
0 );
}
// Count successive failures so we can back off and avoid spinning
// when server(s) are off-line, broken or refusing AXFR
pzone->dwBadMasterCount++;
//
// DEVNOTE: separate bad server, from broken IXFR -> go to AXFR
// => wrong zone, misbehaving => try different server
// => broken IXFR -> goto AXFR
//
if ( pMsg->Head.ResponseCode == DNS_RCODE_REFUSED )
{
goto Cleanup;
}
goto TryAxfr;
TryAxfr:
DNS_DEBUG( XFR, (
"Recv() unuseable UDP IXFR (%p) from %s, forcing AXFR.\n",
pMsg,
IP_STRING( masterIp ) ));
STAT_INC( SecondaryStats.IxfrUdpUseAxfr );
pzone->fNeedAxfr = TRUE; // force AXFR
fzoneLocked = FALSE;
startTcpXfr( pzone, masterIp, masterVersion, pMsg->Socket );
goto Cleanup;
TryTcp:
ASSERT( !pzone->fSkipIxfr );
STAT_INC( SecondaryStats.IxfrUdpUseTcp );
pzone->fNeedAxfr = FALSE; // try IXFR first
startTcpXfr( pzone, masterIp, masterVersion, pMsg->Socket );
fzoneLocked = FALSE;
goto Cleanup;
ServerFailure:
//
// Retry on transfer errors:
// - failure to contact server
// - bad packet
// - connection aborted or packets stop coming
//
STAT_INC( SecondaryStats.IxfrUdpInvalid );
DNS_DEBUG( ANY, (
"ERROR: Server failure %p (%d) during transfer\n",
status, status ));
Xfr_RetryZone( pzone );
// fall through to thread exit
Cleanup:
//
// cleanup
// - release lock on zone
// - free update lists
// - free copy of master SOA from packet
//
if ( fzoneLocked )
{
Zone_UnlockAfterXfrRecv( pzone );
}
if ( psoaRR )
{
RR_Free( psoaRR );
}
Up_FreeUpdatesInUpdateList( &ixfrUpdateList );
Up_FreeUpdatesInUpdateList( &passUpdateList );
return;
} // processIxfrResponse
BOOL
startTcpXfr(
IN OUT PZONE_INFO pZone,
IN IP_ADDRESS ipMaster,
IN DWORD dwMasterVersion,
IN SOCKET Socket
)
/*++
Routine Description:
Start up AXFR transfer.
Arguments:
pZone -- zone to transfer
ipMaster -- master IP address
dwMasterVersion -- master version
Socket -- socket of previous contact (SOA query, IXFR query) to master
Return Value:
Return TRUE if transfer started, FALSE if not started.
--*/
{
HANDLE hthread;
DWORD waitTime;
DNS_DEBUG( ZONEXFR, ( "startTcpXfr( %s ).\n", pZone->pszZoneName ));
ASSERT( IS_ZONE_LOCKED_FOR_WRITE(pZone) );
//
// if no master given, use current fresh master
//
if ( !ipMaster )
{
ASSERT( FALSE );
goto Failed;
//ipMaster = pZone->ipFreshMaster;
}
//
// make sure we don't spin attempting transfer from master
// -- bogus misbehaving master
// -- master refusing this secondary
//
// if master is the same as previous attempt then will refuse retry
// for an interval that grows with each failed attempt
//
// if master is different then last attempt we based on count,
// no suppression for first 5 attempts so retries to other
// possible masters may succeeds, then limit all retries to
// a minute
//
if ( pZone->dwBadMasterCount )
{
waitTime = 0;
if ( pZone->ipFreshMaster == ipMaster )
{
waitTime = 60 + (pZone->dwBadMasterCount * 10);
if ( waitTime > MAX_BAD_MASTER_SUPPRESS_INTERVAL )
{
waitTime = MAX_BAD_MASTER_SUPPRESS_INTERVAL;
}
}
else if ( pZone->dwBadMasterCount > 5 )
{
waitTime = 60;
}
if ( pZone->dwZoneRecvStartTime + waitTime > DNS_TIME() )
{
DNS_DEBUG( XFR, (
"WARNING: Suppressing AXFR attempt on zone %s\n"
"\tto master = %p\n"
"\tprevious master = %p\n"
"\tbad master count = %d\n"
"\tlast recv() start time %d\n"
"\tcurrent time %d\n"
"\tsuppression lasts until %d\n",
pZone->pszZoneName,
ipMaster,
pZone->ipFreshMaster,
pZone->dwBadMasterCount,
pZone->dwZoneRecvStartTime,
DNS_TIME(),
pZone->dwZoneRecvStartTime + waitTime
));
goto Failed;
}
}
#if 0
//
// disable check for master in transfer, now using port > 53
//
// check for current connection to master?
//
// if so, we will be unable to connect to create new connection
// -- as we always use port 53 to simplify router configuration
//
if ( DnsIsAddressInIpArray(
aipMastersOut,
ipMaster ) )
{
IF_DEBUG( ZONEXFR )
{
DNS_PRINT((
"\nWARNING: Master %s currently busy with another zone.\n"
"\tNo attempt to transfer zone %s, until it is finished.\n",
IP_STRING(ipMaster),
pZone->pszZoneName ));
DnsDbg_IpArray(
"Masters with outstanding XFR connections:",
"master",
aipMastersOut );
}
goto Failed;
}
#endif
#if 0
// calling routines now locking while processing flags
//
// lock zone for transfer
//
// note: if switch to locking with CS held during transfer
// then test should move to recv thread
//
if ( ! Zone_LockForXfrRecv(pZone) )
{
DNS_PRINT((
"Zone %s, locked -- unable to transfer.\n",
pZone->pszZoneName ));
return FALSE;
}
#endif
//
// save master address
//
pZone->ipFreshMaster = ipMaster;
//pZone->dwMasterSerialNo = masterVersion;
//
// get binding address from previous contact with master
//
// in multi-homed case where not listening on all sockets
// we may be in a situation where INADDR_ANY binding would
// give us an non-DNS interface; this is ok, except that
// master may have secondary security set and only be listing
// addresses that secondary is configured to run DNS on;
// safest course is simply to bind() to IP address corresponding
// to UDP socket that we just reached the master with; obviously
// we can reach master from this address
//
pZone->ipXfrBind = INADDR_ANY;
if ( Socket )
{
pZone->ipXfrBind = Sock_GetAssociatedIpAddr( Socket );
if ( pZone->ipXfrBind == (SOCKET)(-1) )
{
DNS_DEBUG( ANY, (
"ERROR: UDP IXFR-SOA recv socket %d (%p), not found!\n"
"\tBinding TCP XFR INADDR_ANY\n",
Socket, Socket ));
pZone->ipXfrBind = INADDR_ANY;
}
}
//
// log zone transfer attempt
//
// DEVNOTE: log file for this, regular log for completion
// with dynamic update may not want even completion to be logged
//
// avoid spining when transfers are failing, by only logging first
// attempt
if ( !pZone->dwBadMasterCount )
{
PVOID argArray[3];
BYTE typeArray[3];
argArray[0] = ( PVOID ) ( DWORD_PTR ) dwMasterVersion;
argArray[1] = pZone->pwsZoneName;
argArray[2] = ( PVOID ) ( DWORD_PTR ) ipMaster;
typeArray[0] = EVENTARG_DWORD;
typeArray[1] = EVENTARG_UNICODE;
typeArray[2] = EVENTARG_IP_ADDRESS;
DNS_LOG_EVENT(
DNS_EVENT_ZONE_TRANSFER_IN_PROGRESS,
3,
argArray,
typeArray,
0 );
}
//
// setup to transfer write lock to XFR thread
//
Zone_TransferWriteLock( pZone );
//
// spawn zone transfer receptions thread
//
hthread = Thread_Create(
"Zone Transfer Receive",
Xfr_ReceiveThread,
(PVOID) pZone,
0 );
if ( hthread )
{
DNS_DEBUG( ZONEXFR, (
"Created XFR thread %p to recv zone %s\n",
hthread,
pZone->pszZoneName ));
return TRUE;
}
//
// failed to create thread
//
// - may well be unable to create thread, if many zones out
// for transfer at once
// - if can not start thread, then assume write lock ourselves
// to unlock before quit
//
// DEVNOTE-LOG: need logging for this error
//
DNS_DEBUG( ZONEXFR, (
"ERROR: unable to create thread to recv zone %s\n"
"\tfrom %s.\n",
pZone->pszZoneName,
IP_STRING(ipMaster) ));
Zone_AssumeWriteLock( pZone );
Failed:
// unlock and quit
Zone_UnlockAfterXfrRecv( pZone );
return FALSE;
} // startTcpXfr
//
// XFR recv thread
//
DWORD
Xfr_ReceiveThread(
IN LPVOID pvZone
)
/*++
Routine Description:
Zone transfer reception thread routine.
Arguments:
pvZone - ptr to zone info for zone being transfered
Return Value:
Exit code.
Exit from DNS service terminating or error in wait call.
--*/
{
DBG_FN( "Xfr_ReceiveThread" )
PZONE_INFO pzone = (PZONE_INFO) pvZone;
PDNS_MSGINFO pmsg = NULL;
PDB_NODE pzoneRoot = NULL;
DWORD currentTime;
DWORD eventId = 0;
DNS_STATUS status;
INT count;
WORD type = DNS_TYPE_IXFR;
WORD requestType;
BOOL fparseIxfr = TRUE;
BOOL finitialized = FALSE;
BOOL fupdateLists = FALSE;
FD_SET recvFdset;
struct timeval timeval;
PCHAR pszMasterIp;
UPDATE_LIST ixfrUpdateList;
UPDATE_LIST passUpdateList;
INT stubZoneXfrState = 0;
PIP_ARRAY pipMasters;
// verify secondary
ASSERT( pzone );
ASSERT( IS_ZONE_SECONDARY(pzone) );
ASSERT( pzone->fLocked && pzone->fXfrRecvLock );
ASSERT( pzone->ipFreshMaster );
// assume the zone write lock
Zone_AssumeWriteLock( pzone );
// save master IP string
pszMasterIp = inet_ntoa( *(struct in_addr *) &pzone->ipFreshMaster );
DNS_DEBUG( ZONEXFR, (
"%s starting for zone %s\n"
"\tReceive transfer from master at %s.\n", fn,
pzone->pszZoneName,
pszMasterIp ));
// force STUBXFR or AXFR
if ( IS_ZONE_STUB( pzone ) )
{
fparseIxfr = FALSE;
type = DNS_TYPE_STUBXFR;
}
else if ( pzone->fNeedAxfr || pzone->fSkipIxfr || !pzone->pSoaRR )
{
fparseIxfr = FALSE;
type = DNS_TYPE_AXFR;
}
//
// loop to receive zone transfer messages
// - build new zone outside database
//
// continue until
// - receive entire new zone
// - connection dies
// - service termination
//
while ( TRUE )
{
//
// if this is a stub zone check if we're done
//
if ( IS_ZONE_STUB( pzone ) &&
g_stubXfrData[ stubZoneXfrState ].type == DNS_TYPE_ZERO )
{
break;
}
//
// For stub zones:
// Send a query for the next type in the global state table.
// For other zones:
// Do init (we do this inside the loop, so we can retry server
// that fails IXFR with AXFR)
//
if ( finitialized && type == DNS_TYPE_STUBXFR )
{
//
// Format and send next STUBAXR request message.
// We already have a pmsg, so just clear the data in it
// and write a new question.
//
// Clear current pmsg contents.
RtlZeroMemory(
( PCHAR ) DNS_HEADER_PTR( pmsg ),
sizeof( DNS_HEADER ) );
pmsg->pCurrent = pmsg->MessageBody;
//
// Write new question to pmsg.
//
if ( IS_ZONE_STUB( pzone ) )
{
requestType = g_stubXfrData[ stubZoneXfrState ].type;
pmsg->Head.RecursionDesired =
g_stubXfrData[ stubZoneXfrState ].recursionDesired;
}
else
{
requestType = type;
}
if ( ! Msg_WriteQuestion(
pmsg,
pzone->pZoneTreeLink,
requestType ) )
{
DNS_DEBUG( ANY, (
"ERROR: Unable to write type=%d query for stub zone %s\n",
requestType,
pzone->pszZoneName ));
ASSERT( FALSE );
goto ServerFailure;
}
// Send pmsg to zone master server.
Send_Query( pmsg );
pmsg->fMessageComplete = TRUE;
pmsg->pzoneCurrent = pzone;
}
else if ( !finitialized )
{
finitialized = TRUE;
//
// create temp record store
// AXFR: database to hold new zone during transfer
// IXFR: update lists
//
// then build IXFR or AXFR request
//
if ( IS_ZONE_STUB( pzone ) )
{
status = Zone_PrepareForLoad( pzone );
if ( status != ERROR_SUCCESS )
{
DNS_DEBUG( XFR, (
"WARNING: Unable to init zone for STUBXFR for zone %s.\n"
"\tThis should only happen when have copy of zone in cleanup queue.\n",
pzone->pszZoneName ));
ASSERT( pzone->pOldTree );
goto ServerFailure;
}
STAT_INC( SecondaryStats.StubAxfrRequest );
PERF_INC( pcAxfrRequestSent ); // PerfMon hook JJW
}
else if ( fparseIxfr )
{
Up_InitUpdateList( &ixfrUpdateList );
Up_InitUpdateList( &passUpdateList );
fupdateLists = TRUE;
STAT_INC( SecondaryStats.IxfrTcpRequest );
PERF_INC( pcIxfrRequestSent ); // PerfMon hook
}
else
{
status = Zone_PrepareForLoad( pzone );
if ( status != ERROR_SUCCESS )
{
DNS_DEBUG( XFR, (
"WARNING: Unable to init zone for AXFR in TCP-IXFR for zone %s.\n"
"\tThis should only happen when have copy of zone in cleanup queue.\n",
pzone->pszZoneName ));
ASSERT( pzone->pOldTree );
goto ServerFailure;
}
STAT_INC( SecondaryStats.AxfrRequest );
PERF_INC( pcAxfrRequestSent ); // PerfMon hook
}
// flag indicates we are in transfer
pzone->fNeedAxfr = TRUE;
//
// create XFR request message
//
requestType = IS_ZONE_STUB( pzone ) ?
g_stubXfrData[ stubZoneXfrState ].type :
type;
pmsg = Xfr_BuildXfrRequest(
pzone,
requestType,
TRUE // TCP message buffer
);
if ( !pmsg )
{
goto ServerFailure;
}
//
// connect to primary and send
// use select to protect against hanging attempting to connect
//
pzone->dwZoneRecvStartTime = DNS_TIME();
if ( ! Msg_MakeTcpConnection(
pmsg,
pzone->ipFreshMaster,
pzone->ipXfrBind, // bind() address
0 // no flags, non-blocking socket
) )
{
DNS_DEBUG( ZONEXFR, (
"Zone transfer for %s failed connection to master at %s.\n",
pzone->pszZoneName,
pszMasterIp ));
eventId = DNS_EVENT_XFR_MASTER_UNAVAILABLE;
goto MasterFailure;
}
FD_ZERO( &recvFdset );
FD_SET( pmsg->Socket, &recvFdset );
timeval.tv_sec = SrvCfg_dwXfrConnectTimeout;
timeval.tv_usec = 0;
count = select( 0, NULL, &recvFdset, NULL, &timeval );
if ( count != 1 )
{
DNS_DEBUG( ZONEXFR, (
"Zone transfer for %s timed out connecting to master at %s.\n",
pzone->pszZoneName,
pszMasterIp ));
eventId = DNS_EVENT_XFR_MASTER_UNAVAILABLE;
goto MasterFailure;
}
Send_Query( pmsg );
//
// receive setup
// - mark message complete so Tcp_ReceiveMessage() will know
// that need to receive message length
//
pmsg->fMessageComplete = TRUE;
pmsg->pzoneCurrent = pzone;
XFR_MESSAGE_NUMBER(pmsg) = 0;
IXFR_CLIENT_VERSION(pmsg) = pzone->dwSerialNo;
IXFR_MASTER_VERSION(pmsg) = 0;
RECEIVED_XFR_STARTUP_SOA(pmsg) = FALSE;
}
//
// wait for indication
//
// DEVNOTE: better to have some hosed AXFR thread detection mechanism
// - could detect hosed thread and kill socket waking us up
// (detect by timeout on transfer or since last send, when go
// through secondaryControlThread and find zone locked for transfer)
//
count = select( 0, &recvFdset, NULL, NULL, &timeval );
if ( count != 1 )
{
DnsDebugLock();
DNS_DEBUG( ANY, (
"ERROR: timeout on select() receiving AXFR for zone %s (%p)\n"
"\tattempting transfer from %s\n",
pzone->pszZoneName,
pzone,
pszMasterIp ));
Dbg_DnsMessage(
"Current message of AXFR recv",
pmsg );
Dbg_Zone(
"Zone hanging in AXFR recv:",
pzone );
DnsDebugUnlock();
eventId = DNS_EVENT_XFR_ABORTED_BY_MASTER;
goto MasterFailure;
}
//
// Receive the packet
//
DNS_DEBUG( ZONEXFR, (
"Recv()ing zone transfer: time = %lu.\n",
pzone->dwZoneRecvStartTime ));
pmsg = Tcp_ReceiveMessage( pmsg );
//
// Check and possibly wait on service status
//
// Check before recv() error check, as shutdown will cause
// recv() failure.
//
if ( ! Thread_ServiceCheck() )
{
DNS_DEBUG( ZONEXFR, (
"Terminating zone transfer thread on service exit.\n" ));
goto ThreadExit;
}
//
// No message received indicates error -- abort zone transfer
// Message not complete, loop back for more
//
if ( !pmsg )
{
goto ServerFailure;
}
if ( !pmsg->fMessageComplete )
{
continue;
}
if ( XFR_MESSAGE_NUMBER(pmsg) == 0 )
{
if ( fparseIxfr )
{
STAT_INC( SecondaryStats.IxfrTcpResponse );
PERF_INC( pcIxfrResponseReceived ); //PerfMon hook
}
else if ( IS_ZONE_STUB( pzone ) )
{
STAT_INC( SecondaryStats.StubAxfrResponse );
PERF_INC( pcAxfrResponseReceived ); //PerfMon hook JJW
}
else
{
STAT_INC( SecondaryStats.AxfrResponse );
PERF_INC( pcAxfrResponseReceived ); //PerfMon hook
}
}
XFR_MESSAGE_NUMBER(pmsg)++;
IF_DEBUG( ZONEXFR )
{
Dbg_DnsMessage(
"Response from master:",
pmsg );
}
//
// Validate packet
//
requestType = IS_ZONE_STUB( pzone ) ?
g_stubXfrData[ stubZoneXfrState ].type :
type;
if ( ! Msg_ValidateResponse(
pmsg,
NULL,
requestType,
DNS_OPCODE_QUERY ) )
{
DNS_DEBUG( ANY, (
"ERROR: Invalid response from primary.\n" ));
goto MasterFailure;
}
//
// read IXFR packet
//
if ( fparseIxfr )
{
status = Xfr_ParseIxfrResponse(
pmsg,
& ixfrUpdateList, // full update list for transfer
& passUpdateList // update list for this pass
);
if ( status == ERROR_SUCCESS )
{
continue;
}
else if ( status == DNSSRV_STATUS_AXFR_COMPLETE )
{
DNS_DEBUG( ZONEXFR, ( "Recv'd last message zone transfer\n" ));
break;
}
else if ( status == DNSSRV_STATUS_AXFR_IN_IXFR )
{
DNS_DEBUG( ZONEXFR, (
"Recving AXFR in TCP IXFR message at %p\n",
pmsg ));
// drops through to AXFR recv
// note type stays IXFR, but parse remaining messages AXFR
status = Zone_PrepareForLoad( pzone );
if ( status != ERROR_SUCCESS )
{
DNS_DEBUG( XFR, (
"WARNING: Unable to init zone for AXFR in TCP-IXFR for zone %s.\n"
"\tThis should only happen when have copy of zone in cleanup queue.\n",
pzone->pszZoneName ));
ASSERT( pzone->pOldTree );
goto ServerFailure;
}
fparseIxfr = FALSE;
STAT_INC( SecondaryStats.IxfrTcpAxfr );
}
else if ( status == DNSSRV_STATUS_IXFR_UNSUPPORTED )
{
DNS_DEBUG( ZONEXFR, (
"WARNING: IXFR msg at %p confused server, try AXFR\n",
pmsg ));
shutdown( pmsg->Socket, 2 );
Sock_CloseSocket( pmsg->Socket );
Packet_FreeTcpMessage( pmsg );
// we reinitialize and try AXFR
fparseIxfr = FALSE;
type = DNS_TYPE_AXFR;
finitialized = FALSE;
STAT_INC( SecondaryStats.IxfrTcpFormerr );
continue;
}
else
{
DNS_DEBUG( ZONEXFR, (
"ERROR: ParseIxfrResponse() failure %p (%d).\n",
status, status ));
goto MasterFailure;
}
}
//
// read AXFR packet
// - not in else block as drop here if receiving AXFR in IXFR
//
status = Xfr_ReadXfrMesssageToDatabase(
pzone,
pmsg
);
if ( status == ERROR_SUCCESS )
{
if ( IS_ZONE_STUB( pzone ) )
{
++stubZoneXfrState; // Advance the stub state machine.
}
continue;
}
else if ( status == DNSSRV_STATUS_AXFR_COMPLETE )
{
DNS_DEBUG( ZONEXFR, ( "Recv'd last message zone transfer\n" ));
break;
}
DNS_DEBUG( ZONEXFR, ( "Error <%lu>: Xfr_ReadXfrMessageToDatabase() failed.\n" \
"\tTerminating zone xfr processing.\n",
status
) );
goto MasterFailure;
}
//
// IXFR transfer done, execute IXFR updates
// - leave zone locked until zone XFR flags reset
// - reinit update list to no-op global update list cleanup below
//
if ( fparseIxfr )
{
status = Up_ApplyUpdatesToDatabase(
& ixfrUpdateList,
pzone,
DNSUPDATE_IXFR |
DNSUPDATE_COMPLETE |
DNSUPDATE_NO_UNLOCK |
DNSUPDATE_NO_NOTIFY
);
if ( status != ERROR_SUCCESS )
{
goto ServerFailure;
}
Up_InitUpdateList( &ixfrUpdateList );
ASSERT( passUpdateList.pListHead == NULL );
ASSERT( ixfrUpdateList.pListHead == NULL );
STAT_INC( SecondaryStats.IxfrTcpSuccess );
PERF_INC( pcIxfrTcpSuccessReceived ); // PerfMon hook
PERF_INC( pcIxfrSuccessReceived ); // PerfMon hook
}
//
// AXFR transfer done
// - splice zone into database
// - delete temp database
//
else
{
status = Zone_ActivateLoadedZone( pzone );
if ( status != ERROR_SUCCESS )
{
DNS_DEBUG( ANY, (
"ERROR: Failed writing in new zone information for zone %s\n",
pzone->pszZoneName
));
ASSERT( FALSE );
goto ServerFailure;
}
// save last AXFR master, can not do incrementals from differing DS primaries// as given version does NOT mean indentical data across all DS primaries
pzone->ipLastAxfrMaster = pzone->ipFreshMaster;
if ( IS_ZONE_STUB( pzone ) )
{
STAT_INC( SecondaryStats.StubAxfrSuccess );
PERF_INC( pcAxfrSuccessReceived ); // PerfMon hook JJW
}
else
{
STAT_INC( SecondaryStats.AxfrSuccess );
PERF_INC( pcAxfrSuccessReceived ); // PerfMon hook
}
}
//
// refresh zone XFR info
//
pzone->dwBadMasterCount = 0;
pzone->ipFreshMaster = 0;
pzone->ipNotifier = 0;
Xfr_RefreshZone( pzone );
pzone->dwLoadSerialNo = pzone->dwSerialNo;
//
// write zone back to file -- if any
// notify any secondaries
//
File_WriteZoneToFile( pzone, NULL );
Xfr_SendNotify( pzone );
pzone->dwLastSuccessfulXfrTime = ( DWORD ) time( NULL );
DNS_DEBUG( ZONEXFR, (
"%s: success on zone %s at %lu\n", fn,
pzone->pszZoneName,
pzone->dwLastSuccessfulXfrTime ));
goto ThreadExit;
MasterFailure:
if ( eventId == 0 )
{
if ( pmsg->Head.ResponseCode == DNS_RCODE_REFUSED )
{
if ( type == DNS_TYPE_IXFR )
{
STAT_INC( SecondaryStats.IxfrTcpRefused );
}
else if ( IS_ZONE_STUB( pzone ) )
{
STAT_INC( SecondaryStats.StubAxfrRefused );
}
else
{
STAT_INC( SecondaryStats.AxfrRefused );
}
eventId = DNS_EVENT_AXFR_REFUSED;
}
else
{
DNS_DEBUG( ANY, (
"ERROR: %p (%d) parsing XFR message\n",
status, status,
pmsg ));
IF_DEBUG( ANY )
{
Dbg_DnsMessage(
"Bogus XFR message:\n",
pmsg );
}
eventId = DNS_EVENT_AXFR_BAD_RESPONSE;
if ( type == DNS_TYPE_IXFR )
{
STAT_INC( SecondaryStats.IxfrTcpInvalid );
}
else if ( IS_ZONE_STUB( pzone ) )
{
STAT_INC( SecondaryStats.StubAxfrInvalid );
}
else
{
STAT_INC( SecondaryStats.AxfrInvalid );
}
CLIENT_ASSERT( FALSE );
}
}
//
// log master failure -- but avoid log spinning
//
if ( pzone->dwBadMasterCount < 3 )
{
PVOID argArray[2];
BYTE typeArray[2];
argArray[0] = pzone->pwsZoneName;
argArray[1] = pszMasterIp;
typeArray[0] = EVENTARG_UNICODE;
typeArray[1] = EVENTARG_UTF8;
DNS_LOG_EVENT(
eventId,
2,
argArray,
typeArray,
GetLastError() );
}
// Count successive failures so we can back off and avoid spinning
// when server(s) are off-line, broken or refusing AXFR
//
// DEVNOTE: track bad masters and avoid (with long retry) using them
pzone->ipFreshMaster = 0;
pzone->dwBadMasterCount++;
goto ThreadExit;
ServerFailure:
//
// Retry on transfer errors:
// - failure to contact server
// - bad packet
// - connection aborted or packets stop coming
//
DNS_DEBUG( ANY, (
"ERROR: Server failure %p (%d) during transfer\n",
status, status ));
Xfr_RetryZone( pzone );
// fall through to thread exit
ThreadExit:
//
// shut connection
//
if ( pmsg )
{
// close transfer socket
if ( pmsg->Socket && pmsg->Socket != INVALID_SOCKET )
{
shutdown( pmsg->Socket, 2 );
Sock_CloseSocket( pmsg->Socket );
}
Packet_FreeTcpMessage( pmsg );
}
//
// reset zone flags
//
// always reset cIxfrAttempts, as can have failed IXFR because lost packet
//
// however don't reset fSkipIxfr unless multiple masters;
// if have just single non-IXFR aware master, then flag prevents
// unnecessary IXFR queries; with multiple masters
// if multiple masters, reset force AXFR flag as other masters
// might be IXFR aware
//
pzone->fNeedAxfr = FALSE;
pzone->cIxfrAttempts = 0;
pipMasters = ZONE_MASTERS( pzone );
if ( pipMasters && pipMasters->AddrCount > 1 )
{
pzone->fSkipIxfr = FALSE;
}
//
// cleanup
// - free temp database
// - free update lists
// - release lock on zone
// - signal transfer completion
// - clear this thread from global array
//
Zone_CleanupFailedLoad( pzone );
Zone_UnlockAfterXfrRecv( pzone );
if ( fupdateLists )
{
Up_FreeUpdatesInUpdateList( &ixfrUpdateList );
Up_FreeUpdatesInUpdateList( &passUpdateList );
}
//
// requery SOA if multiple masters
// this ensures we get the latest zone available
//
// DEVNOTE: better would be to save best SOA response
// then make sure we update from that version (or better)
//
// DEVNOTE: should probably eliminate this in favor of notify
//
ASSERT( ZONE_MASTERS( pzone ) );
if ( ZONE_MASTERS( pzone ) &&
ZONE_MASTERS( pzone )->AddrCount > 1 )
{
Xfr_SendSoaQuery( pzone );
}
//
// Signal the zone transfer master thread so that if other zones
// are ready to be transferred they can be started immediately.
// DEVNOTE: it would be cool to reuse this thread, but that would
// require getting the zone pointer from here. That would require
// some locking since two threads would be dequeuing responses,
// plus some responses are not supposed to be handled by this thread.
// So let's not bother with that for now.
//
if ( g_SecondaryQueue->cLength )
{
DNS_DEBUG( ZONEXFR2, (
"%s: setting event to wake secondary thread (%d queued)\n", fn,
g_SecondaryQueue->cLength ));
SetEvent( g_hWakeSecondaryEvent );
}
Thread_Close( TRUE );
return 0;
}
//
// End zonesec.c
//