/*++ Copyright (c) 2000-2000 Microsoft Corporation Module Name: ip.c Abstract: DNS Resolver Service. IP list and change notification. Author: Jim Gilroy (jamesg) November 2000 Revision History: --*/ #include "local.h" #include "iphlpapi.h" // // IP notify thread globals // HANDLE g_IpNotifyThread; DWORD g_IpNotifyThreadId; HANDLE g_IpNotifyEvent; HANDLE g_IpNotifyHandle; OVERLAPPED g_IpNotifyOverlapped; // // IP Address list values // PDNS_ADDR_ARRAY g_LocalAddrArray; PIP_ARRAY g_ClusterIpArray; IP_ADDRESS g_OldClusterIp; // // Cluster record TTLs -- use max TTL // #define CLUSTER_RECORD_TTL (g_MaxCacheTtl) // // Shutdown\close locking // // Since GQCS and GetOverlappedResult() don't directly wait // on StopEvent, we need to be able to close notification handle // and port in two different threads. // // Note: this should probably be some general CS that is // overloaded to do service control stuff. // I'm not using the server control CS because it's not clear // that the functions it does in dnsrslvr.c even need locking. // #define LOCK_IP_NOTIFY_HANDLE() EnterCriticalSection( &NetworkFailureCritSec ) #define UNLOCK_IP_NOTIFY_HANDLE() LeaveCriticalSection( &NetworkFailureCritSec ) CRITICAL_SECTION g_IpListCS; #define LOCK_IP_LIST() EnterCriticalSection( &g_IpListCS ); #define UNLOCK_IP_LIST() LeaveCriticalSection( &g_IpListCS ); // // Cluster Tag // #define CLUSTER_TAG 0xd734453d VOID CloseIpNotifyHandle( VOID ) /*++ Routine Description: Close IP notify handle. Wrapping up code since this close must be MT safe and is done in several places. Arguments: None Return Value: None --*/ { LOCK_IP_NOTIFY_HANDLE(); if ( g_IpNotifyHandle ) { //CloseHandle( g_IpNotifyHandle ); PostQueuedCompletionStatus( g_IpNotifyHandle, 0, // no bytes 0, // no key & g_IpNotifyOverlapped ); g_IpNotifyHandle = NULL; } UNLOCK_IP_NOTIFY_HANDLE(); } DNS_STATUS IpNotifyThread( IN LPVOID pvDummy ) /*++ Routine Description: IP notification thread. Arguments: pvDummy -- unused Return Value: NO_ERROR on normal service shutdown Win32 error on abnormal termination --*/ { DNS_STATUS status; DWORD bytesRecvd; BOOL fstartedNotify; BOOL fhaveIpChange = FALSE; BOOL fsleep = FALSE; HANDLE notifyHandle; DNSDBG( INIT, ( "\nStart IpNotifyThread.\n" )); g_IpNotifyHandle = NULL; // // wait in loop on notifications // while ( !g_StopFlag ) { // // spin protect // - if error in previous NotifyAddrChange or // GetOverlappedResult do short sleep to avoid // chance of hard spin // if ( fsleep ) { WaitForSingleObject( g_hStopEvent, 60000 ); fsleep = FALSE; continue; } // // start notification // // do this before checking result as we want notification // down BEFORE we read so we don't leave window where // IP change is not picked up // RtlZeroMemory( &g_IpNotifyOverlapped, sizeof(OVERLAPPED) ); if ( g_IpNotifyEvent ) { g_IpNotifyOverlapped.hEvent = g_IpNotifyEvent; ResetEvent( g_IpNotifyEvent ); } notifyHandle = 0; fstartedNotify = FALSE; status = NotifyAddrChange( & notifyHandle, & g_IpNotifyOverlapped ); if ( status == ERROR_IO_PENDING ) { DNSDBG( INIT, ( "NotifyAddrChange()\n" "\tstatus = %d\n" "\thandle = %d\n" "\toverlapped event = %d\n", status, notifyHandle, g_IpNotifyOverlapped.hEvent )); g_IpNotifyHandle = notifyHandle; fstartedNotify = TRUE; } else { DNSDBG( ANY, ( "NotifyAddrChange() FAILED\n" "\tstatus = %d\n" "\thandle = %d\n" "\toverlapped event = %d\n", status, notifyHandle, g_IpNotifyOverlapped.hEvent )); fsleep = TRUE; } if ( g_StopFlag ) { goto Done; } // // previous notification -- refresh data // // FlushCache currently include local IP list // sleep keeps us from spinning in this loop // // DCR: better spin protection; // if hit X times then sleep longer? // if ( fhaveIpChange ) { DNSDBG( ANY, ( "\nIP notification, flushing cache and restarting.\n" )); HandleConfigChange( "IP-notification", TRUE // flush cache ); fhaveIpChange = FALSE; } // // starting -- // clear list to force rebuild of IP list AFTER starting notify // so we can know that we don't miss any changes; // need this on startup, but also to protect against any // NotifyAddrChange failues // else if ( fstartedNotify ) { ClearLocalAddrArray(); } // // anti-spin protection // - 15 second sleep between any notifications // WaitForSingleObject( g_hStopEvent, 15000 ); if ( g_StopFlag ) { goto Done; } // // wait on notification // - save notification result // - sleep on error, but never if notification // if ( fstartedNotify ) { fhaveIpChange = GetOverlappedResult( g_IpNotifyHandle, & g_IpNotifyOverlapped, & bytesRecvd, TRUE // wait ); fsleep = !fhaveIpChange; status = NO_ERROR; if ( !fhaveIpChange ) { status = GetLastError(); } DNSDBG( ANY, ( "GetOverlappedResult() => %d.\n" "\t\tstatus = %d\n", fhaveIpChange, status )); } } Done: DNSDBG( ANY, ( "Stop IP Notify thread on service shutdown.\n" )); CloseIpNotifyHandle(); return( status ); } VOID ZeroInitIpListGlobals( VOID ) /*++ Routine Description: Zero-init IP globals just for failure protection. The reason to have this is there is some interaction with the cache from the notify thread. To avoid that being a problem we start the cache first. But just for safety we should at least zero init these globals first to protect us from cache touching them. Arguments: Return Value: NO_ERROR on normal service shutdown Win32 error on abnormal termination --*/ { // // clear out globals to smoothly handle failure cases // g_LocalAddrArray = NULL; g_ClusterIpArray = NULL; g_OldClusterIp = 0; g_IpNotifyThread = NULL; g_IpNotifyThreadId = 0; g_IpNotifyEvent = NULL; g_IpNotifyHandle = NULL; } DNS_STATUS InitIpListAndNotification( VOID ) /*++ Routine Description: Start IP notification thread. Arguments: None Globals: Initializes IP list and notify thread globals. Return Value: NO_ERROR on normal service shutdown Win32 error on abnormal termination --*/ { DNS_STATUS status = NO_ERROR; DNSDBG( TRACE, ( "InitIpListAndNotification()\n" )); // // CS for IP list access // InitializeCriticalSection( &g_IpListCS ); // // create event for overlapped I/O // g_IpNotifyEvent = CreateEvent( NULL, // no security descriptor TRUE, // manual reset event FALSE, // start not signalled NULL // no name ); if ( !g_IpNotifyEvent ) { status = GetLastError(); DNSDBG( ANY, ( "\nFAILED IpNotifyEvent create.\n" )); goto Done; } // // fire up IP notify thread // g_IpNotifyThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) IpNotifyThread, NULL, 0, & g_IpNotifyThreadId ); if ( !g_IpNotifyThread ) { status = GetLastError(); DNSDBG( ANY, ( "FAILED to create IP notify thread = %d\n", status )); } Done: // not currently stopping on init failure return( ERROR_SUCCESS ); } VOID ShutdownIpListAndNotify( VOID ) /*++ Routine Description: Stop IP notify thread. Note: currently this is blocking call, we'll wait until thread shuts down. Arguments: None. Return Value: None. --*/ { DNSDBG( TRACE, ( "ShutdownIpListAndNotify()\n" )); // // MUST be stopping // - if not thread won't wake // ASSERT( g_StopFlag ); g_StopFlag = TRUE; // // close IP notify handles -- waking thread if still running // if ( g_IpNotifyEvent ) { SetEvent( g_IpNotifyEvent ); } CloseIpNotifyHandle(); // // wait for thread to stop // ThreadShutdownWait( g_IpNotifyThread ); g_IpNotifyThread = NULL; // // cleanup IP lists // if ( g_LocalAddrArray ) { GENERAL_HEAP_FREE( g_LocalAddrArray ); g_LocalAddrArray = NULL; } if ( g_ClusterIpArray ) { GENERAL_HEAP_FREE( g_ClusterIpArray ); g_ClusterIpArray = NULL; g_OldClusterIp = 0; } // // close event // CloseHandle( g_IpNotifyEvent ); g_IpNotifyEvent = NULL; // // kill off CS // DeleteCriticalSection( &g_IpListCS ); } // // IP list routines // DWORD SizeForAddrArray( IN DWORD AddrCount ) /*++ Routine Description: Determine size for addr array of given count. Arguments: AddrCount -- count of addresses Return Value: Size in bytes required for addr array. --*/ { return sizeof(DNS_ADDR_ARRAY) + (AddrCount * sizeof(DNS_ADDR_INFO)); } PDNS_ADDR_ARRAY CopyAddrArray( IN PDNS_ADDR_ARRAY pAddrArray ) /*++ Routine Description: Create copy of address info array. Arguments: pAddrArray -- ptr to addr info array to copy Return Value: Ptr to new addr array. --*/ { PDNS_ADDR_ARRAY pnew; DWORD size; DNSDBG( TRACE, ( "CopyAddrArray()\n" )); // // alloc memory and copy // size = SizeForAddrArray( pAddrArray->AddrCount ); pnew = (PDNS_ADDR_ARRAY) GENERAL_HEAP_ALLOC( size ); if ( pnew ) { RtlCopyMemory( pnew, pAddrArray, size ); } return pnew; } VOID ClearLocalAddrArray( VOID ) /*++ Routine Description: Clear the IP list data. Arguments: None. Globals: Updates IP list globals. Return Value: None. --*/ { PDNS_ADDR_ARRAY poldArray; DNSDBG( TRACE, ( "ClearLocalAddrArray()\n" )); // // clear new list // // must go through lock even if no list, in case another // thread started building new list before our call // LOCK_IP_LIST(); poldArray = g_LocalAddrArray; g_LocalAddrArray = NULL; UNLOCK_IP_LIST(); // free old list if ( poldArray ) { GENERAL_HEAP_FREE( poldArray ); } } PDNS_ADDR_ARRAY GetLocalAddrArray( VOID ) /*++ Routine Description: Get copy of local IP address info list. Note: caller must free list. // // DCR: just direct return of counted array // - include IPv6 // Arguments: None Globals: May update IP list globals. Return Value: Ptr to local addr info array. Caller must free. NULL on allocation failure. --*/ { PDNS_ADDR_ARRAY pnew = NULL; DWORD size; DNSDBG( TRACE, ( "GetLocalAddrArray()\n" )); LOCK_IP_LIST(); // // if no current list -- try to get one // if ( !g_LocalAddrArray ) { g_LocalAddrArray = DnsGetLocalAddrArrayDirect(); if ( !g_LocalAddrArray ) { goto Unlock; } IF_DNSDBG( INIT ) { DnsDbg_DnsAddrArray( "New IP address info:", g_LocalAddrArray ); } } // // alloc memory and copy // pnew = CopyAddrArray( g_LocalAddrArray ); Unlock: UNLOCK_IP_LIST(); return pnew; } VOID RegisterClusterIp4Address( IN IP_ADDRESS ClusterIp, IN BOOL fAdd ) /*++ Routine Description: Register IP4 cluster IP. Arguments: ClusterIp -- IP of cluster fAdd -- TRUE if adding cluster IP, FALSE if deleting Return Value: None --*/ { DNSLOG_F1( "RegisterClusterIp4Address" ); DNSDBG( RPC, ( "RegisterClusterIp4Address\n" "\tClusterIp = %s\n" "\tfAdd = %d\n", IP_STRING( ClusterIp ), fAdd )); LOCK_IP_LIST(); // // adding, add to exclusion list // if ( fAdd ) { // put IP in cluster array // - if array is full (or nonexistant), allocate bigger one // and slap IP in again if ( ! Dns_AddIpToIpArray( g_ClusterIpArray, ClusterIp ) ) { g_ClusterIpArray = Dns_CopyAndExpandIpArray( g_ClusterIpArray, 25, // expand by 20 slots TRUE // delete existing copy ); Dns_AddIpToIpArray( g_ClusterIpArray, ClusterIp ); } } // // deleting remove from exclusion list // - but save old cluster IP as global until we are // sure it is gone // else { g_OldClusterIp = ClusterIp; if ( !g_ClusterIpArray ) { goto Refresh; } Dns_ClearIpFromIpArray( g_ClusterIpArray, ClusterIp ); } Refresh: // no need to clear local array // // IP help gives us notification of cluster IP change; since // cluster calls us after making change, we've probably already // been notified and done reread; since we don't keep a global // filtered list there's no need to dump it // // ClearLocalAddrArray(); IF_DNSDBG( INIT ) { DnsDbg_IpArray( "Cluster Exclusion Array:", NULL, g_ClusterIpArray ); DnsDbg_Printf( "Old Cluster IP = %s\n", IP_STRING( g_OldClusterIp ) ); } UNLOCK_IP_LIST(); } DWORD RemoveClusterIpFromAddrArray( IN OUT PDNS_ADDR_ARRAY pAddrArray ) /*++ Routine Description: Screen cluster addresses from IP list. Note, assumes caller has network info locked. Arguments: pAddrArray -- array with unscreened address info AddrCount -- count of addresses Return Value: Count after screening --*/ { DWORD loadIndex = 0; DWORD testIndex; IP_ADDRESS ip; BOOL fexclusion = FALSE; BOOL fexcludeOldCluster = FALSE; DNSDBG( TRACE, ( "RemoveClusterIpFromAddrArray()\n" )); // // screen IP address list for cluster IPs // // note, we do a push down here because we want to maintain order // or remaining IP addresses // // note, we could have this function do a copy from source to // destination, so we aren't copying twice in this case; // i don't see this as a big win // for ( testIndex = 0; testIndex < pAddrArray->AddrCount; testIndex++ ) { ip = pAddrArray->AddrArray[testIndex].IpAddr; if ( ip == g_OldClusterIp ) { DNSDBG( INIT, ( "Screened old cluster IP %s from local list.\n", IP_STRING(ip) )); fexclusion = TRUE; fexcludeOldCluster = TRUE; continue; } if ( Dns_IsAddressInIpArray( g_ClusterIpArray, ip ) ) { DNSDBG( INIT, ( "Screened cluster IP %s from local list.\n", IP_STRING(ip) )); fexclusion = TRUE; continue; } // once have exclusion must push down addresses if ( fexclusion ) { RtlCopyMemory( & pAddrArray->AddrArray[loadIndex], & pAddrArray->AddrArray[testIndex], sizeof(DNS_ADDR_INFO) ); } loadIndex++; } pAddrArray->AddrCount = loadIndex; // clear old cluster IP, if no longer in local list // - keep excluding until it doesn't show up anymore // if it shows up after that then it is legitimate address if ( !fexcludeOldCluster ) { g_OldClusterIp = 0; } return( loadIndex ); } PDNS_ADDR_ARRAY GetLocalAddrArrayFiltered( VOID ) /*++ Routine Description: Get filtered addr info array. Arguments: None. Globals: May update IP list globals. Return Value: Ptr to local addr info array. Caller must free. NULL on allocation failure. --*/ { PDNS_ADDR_ARRAY pnew = NULL; DWORD count; DNSDBG( TRACE, ( "GetLocalAddrArrayFiltered()\n" )); LOCK_IP_LIST(); // // create cluster filtered list // // - created copy of full list // - screen out cluster IPs // - note NET lock protects use of cluster list also // // note: if filter alloc fails, treats as no filtering avail // pnew = GetLocalAddrArray(); if ( !pnew ) { goto Unlock; } RemoveClusterIpFromAddrArray( pnew ); Unlock: UNLOCK_IP_LIST(); IF_DNSDBG( INIT ) { DnsDbg_DnsAddrArray( "Filtered IP address info:", pnew ); } return pnew; } // // Remote IP list routines // VOID R_ResolverGetLocalAddrInfoArray( IN DNS_RPC_HANDLE Reserved, OUT PDNS_ADDR_ARRAY * ppAddrArray, IN ENVAR_DWORD_INFO FilterInfo ) /*++ Routine Description: Get local IP address list from resolver. Arguments: Reserved -- RPC handle ppIpAddrList -- addr to recv ptr to array FilterInfo -- FilterClusterIp environment variable info Return Value: Count of IP addresses returned --*/ { PDNS_ADDR_ARRAY preturnList = NULL; DNSLOG_F1( "R_ResolverGetLocalAddrInfoArray()" ); DNSDBG( RPC, ( "R_ResolverGetLocalAddrInfoArray()\n" )); // // validate // if ( !ppAddrArray ) { return; // ERROR_INVALID_PARAMETER; } if ( ClientThreadNotAllowedAccess() ) { DNSLOG_F1( "R_ResolverGetLocalAddrInfoArray() - ERROR_ACCESS_DENIED" ); goto Done; } // // determine if need to filter // // default is don't filter unless environment variable is // explicitly set // if ( FilterInfo.fFound && FilterInfo.Value ) { preturnList = GetLocalAddrArrayFiltered(); } else { preturnList = GetLocalAddrArray(); } Done: *ppAddrArray = preturnList; DNSDBG( RPC, ( "Leave R_ResolverGetLocalAddrInfoArray()\n" )); } VOID R_ResolverRegisterClusterIp( IN DNS_RPC_HANDLE Handle, IN IP_ADDRESS ClusterIp, IN DWORD Tag, IN BOOL fAdd ) /*++ Routine Description: Make the query to remote DNS server. Arguments: Handle -- RPC handle ClusterIp -- IP of cluster fAdd -- TRUE if adding cluster IP, FALSE if deleting Return Value: None --*/ { DNSLOG_F1( "R_ResolverRegisterClusterIp" ); DNSDBG( RPC, ( "R_ResolverRegisterClusterIp\n" "\tClusterIp = %s\n" "\tfAdd = %d\n", IP_STRING( ClusterIp ), fAdd )); // // DCR: should have security // if ( ClientThreadNotAllowedAccess() ) { DNSLOG_F1( "R_ResolverFlushCache - ERROR_ACCESS_DENIED" ); return; } if ( Tag != CLUSTER_TAG ) { return; } RegisterClusterIp4Address( ClusterIp, fAdd ); } // // Cluster IP manipulation. // #if 0 BOOL Resolver_IsClusterIpCallback( IN PIP_UNION pIpUnion, IN BOOL fCopyName, OUT PWCHAR * ppName ) /*++ Routine Description: Detect cluster address. Arguments: pIpUnion -- address to test fCopyName -- want the name back ppName -- addr to recv ptr to name Return Value: TRUE if cluster IP FALSE if regular IP --*/ { IP4_ADDRESS ip; DNSDBG( TRACE, ( "Resolver_IsClusterIpCallback()\n" )); // // no IP6 support yet // *ppName = NULL; // // DCR: keep cluster as combo IP list with ptr to cluster name // if ( pIpUnion->IsIp6 ) { return FALSE; } ip = pIpUnion.Ip4; if ( ip == g_OldClusterIp ) { DNSDBG( INIT, ( "IP is old cluster IP %s.\n", IP_STRING(ip) )); return TRUE; } LOCK_IP_LIST(); fresult = Dns_IsAddressInIpArray( g_ClusterIpArray, ip ); UNLOCK_IP_LIST(); if ( fresult ) { DNSDBG( INIT, ( "IP is current cluster IP %s.\n", IP_STRING(ip) )); return TRUE; } // not cluster IP return FALSE; } #endif // // Cluster filtering callback routines // BOOL IsClusterIp4Addr( IN IP4_ADDRESS IpAddr ) /*++ Routine Description: Check if cluster address. Arguments: IpAddr -- IP4 address Return Value: TRUE if cluster IP. FALSE otherwise. --*/ { DNSDBG( TRACE, ( "IsClusterIp4Addr( %08x )\n", IpAddr )); if ( Dns_IsAddressInIpArray( g_ClusterIpArray, IpAddr ) ) { DNSDBG( INIT, ( "IP %s is cluster address.\n", IP_STRING(IpAddr) )); return TRUE; } return FALSE; } BOOL IsClusterAddress( IN OUT PQUERY_BLOB pBlob, IN PIP_UNION pIpUnion ) /*++ Routine Description: Cluster filtering callback. Arguments: Return Value: TRUE if cluster IP. FALSE otherwise. --*/ { IP4_ADDRESS ip4; DNSDBG( TRACE, ( "IsClusterAddress( %p, %p )\n", pBlob, pIpUnion )); if ( !g_ClusterIpArray ) { return FALSE; } // // determine if cluster address // // DCR: create cluster IP records // - match cluster name and create forward record // - match IP and create reverse record to cluster name // if ( IPUNION_IS_IP4(pIpUnion) ) { BOOL fresult; IP4_ADDRESS ip4; ip4 = IPUNION_GET_IP4( pIpUnion ); LOCK_IP_LIST(); fresult = Dns_IsAddressInIpArray( g_ClusterIpArray, ip4 ); UNLOCK_IP_LIST(); return fresult; } // DCR: no IP6 cluster support yet return FALSE; } DNS_STATUS R_ResolverRegisterCluster( IN DNS_RPC_HANDLE Handle, IN DWORD Tag, IN PWSTR pwsName, IN PRPC_IP_UNION pIpUnion, IN DWORD Flag ) /*++ Routine Description: Make the query to remote DNS server. Arguments: Handle -- RPC handle Tag -- RPC API tag pwsName -- name of cluster pIpUnion -- IP union Flag -- registration flag DNS_CLUSTER_ADD DNS_CLUSTER_DELETE_NAME DNS_CLUSTER_DELETE_IP Return Value: None --*/ { PDNS_RECORD prrAddr = NULL; PDNS_RECORD prrPtr = NULL; WORD wtype; PIP_UNION pip = (PIP_UNION) pIpUnion; DNS_STATUS status; BOOL fadd; DNSLOG_F1( "R_ResolverRegisterCluster" ); ASSERT( sizeof(IP_UNION) == sizeof(RPC_IP_UNION) ); DNSDBG( RPC, ( "R_ResolverRegisterCluster()\n" "\tpName = %s\n" "\tpIpUnion = %p\n" "\tFlag = %08x\n", pwsName, pip, Flag )); // // DCR: should have security // if ( ClientThreadNotAllowedAccess() ) { DNSLOG_F1( "R_ResolverRegisterClusterIp - ERROR_ACCESS_DENIED" ); return ERROR_ACCESS_DENIED; } if ( Tag != CLUSTER_TAG ) { return ERROR_ACCESS_DENIED; } // // get type // wtype = DNS_TYPE_A; if ( IPUNION_IS_IP6(pip) ) { wtype = DNS_TYPE_AAAA; } fadd = (Flag == DNS_CLUSTER_ADD); // // cluster add -- cache cluster records // - forward and reverse // if ( !pwsName ) { DNSDBG( ANY, ( "WARNING: no cluster name given!\n" )); goto AddrList; } // // build records // - address and corresponding PTR // prrAddr = Dns_CreateForwardRecord( pwsName, pip, CLUSTER_RECORD_TTL, DnsCharSetUnicode, DnsCharSetUnicode ); if ( !prrAddr ) { status = GetLastError(); if ( status == ERROR_SUCCESS ) { status = ERROR_INVALID_DATA; } return status; } SET_RR_CLUSTER( prrAddr ); prrPtr = Dns_CreatePtrRecordEx( pip, pwsName, CLUSTER_RECORD_TTL, DnsCharSetUnicode, DnsCharSetUnicode ); if ( prrPtr ) { SET_RR_CLUSTER( prrPtr ); } // // add records to cache // if ( Flag == DNS_CLUSTER_ADD ) { Cache_RecordSetAtomic( NULL, // record name 0, // record type prrAddr ); if ( prrPtr ) { Cache_RecordSetAtomic( NULL, // record name 0, // record type prrPtr ); } prrAddr = NULL; prrPtr = NULL; } // // if delete cluster, flush cache entries for name\type // // DCR: don't have way to STRONG flush a name and type // assume cluster name goes away wholesale // // DCR: need delete of cluster PTR record // must be name specific // // DCR: build reverse name independently so whack works // even without cluster name // else if ( Flag == DNS_CLUSTER_DELETE_NAME ) { Cache_FlushRecords( pwsName, FLUSH_LEVEL_STRONG, 0 ); // delete record matching PTR } else if ( Flag == DNS_CLUSTER_DELETE_IP ) { Cache_FlushRecords( pwsName, FLUSH_LEVEL_STRONG, 0 ); Cache_FlushRecords( prrPtr->pName, FLUSH_LEVEL_STRONG, 0 ); } // // IP4 address list backcompat processing // // note: could use sockaddr to IP6 routine here // if want list of IP6 addrs; if build combined // type then should have conversion routine // // my take here is that IP6 cluster addresses won't show // up period // AddrList: if ( wtype == DNS_TYPE_A ) { IP4_ADDRESS ip4 = IPUNION_GET_IP4(pip); if ( !fadd && Flag != DNS_CLUSTER_DELETE_IP ) { goto Done; } RegisterClusterIp4Address( ip4, fadd ); } Done: // cleanup uncached records Dns_RecordFree( prrAddr ); Dns_RecordFree( prrPtr ); return ERROR_SUCCESS; } // // End ip.c //