mDNSMacOSX.c   [plain text]


/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2002-2011 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// ***************************************************************************
// mDNSMacOSX.c:
// Supporting routines to run mDNS on a CFRunLoop platform
// ***************************************************************************

// For debugging, set LIST_ALL_INTERFACES to 1 to display all found interfaces,
// including ones that mDNSResponder chooses not to use.
#define LIST_ALL_INTERFACES 0

// For enabling AAAA records over IPv4. Setting this to 0 sends only
// A records over IPv4 and AAAA over IPv6. Setting this to 1 sends both
// AAAA and A records over both IPv4 and IPv6.
#define AAAA_OVER_V4 1

// In Mac OS X 10.4 and earlier, to reduce traffic, we would send and receive using IPv6 only on interfaces that had no routable
// IPv4 address. Having a routable IPv4 address assigned is a reasonable indicator of being on a large configured network,
// which means there's a good chance that most or all the other devices on that network should also have IPv4.
// By doing this we lost the ability to talk to true IPv6-only devices on that link, but we cut the packet rate in half.
// At that time, reducing the packet rate was more important than v6-only devices on a large configured network,
// so were willing to make that sacrifice.
// In Mac OS X 10.5, in 2007, two things have changed:
// 1. IPv6-only devices are starting to become more common, so we can't ignore them.
// 2. Other efficiency improvements in the code mean that crude hacks like this should no longer be necessary.

#define USE_V6_ONLY_WHEN_NO_ROUTABLE_V4 0

#include "mDNSEmbeddedAPI.h"		// Defines the interface provided to the client layer above
#include "DNSCommon.h"
#include "uDNS.h"
#include "mDNSMacOSX.h"				// Defines the specific types needed to run mDNS on this platform
#include "dns_sd.h"					// For mDNSInterface_LocalOnly etc.
#include "PlatformCommon.h"
#include "uds_daemon.h"

#include <stdio.h>
#include <stdarg.h>                 // For va_list support
#include <stdlib.h>                 // For arc4random
#include <net/if.h>
#include <net/if_types.h>			// For IFT_ETHER
#include <net/if_dl.h>
#include <net/bpf.h>				// For BIOCSETIF etc.
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/event.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <time.h>                   // platform support for UTC time
#include <arpa/inet.h>              // for inet_aton
#include <pthread.h>
#include <netdb.h>					// for getaddrinfo

#include <netinet/in.h>             // For IP_RECVTTL
#ifndef IP_RECVTTL
#define IP_RECVTTL 24               // bool; receive reception TTL w/dgram
#endif

#include <netinet/in_systm.h>       // For n_long, required by <netinet/ip.h> below
#include <netinet/ip.h>             // For IPTOS_LOWDELAY etc.
#include <netinet6/in6_var.h>       // For IN6_IFF_NOTREADY etc.
#include <netinet6/nd6.h>           // For ND6_INFINITE_LIFETIME etc.

#if TARGET_OS_EMBEDDED
#define NO_SECURITYFRAMEWORK 1
#define NO_CFUSERNOTIFICATION 1
#endif

#ifndef NO_SECURITYFRAMEWORK
#include <Security/SecureTransport.h>
#include <Security/Security.h>
#endif /* NO_SECURITYFRAMEWORK */

#include <DebugServices.h>
#include "dnsinfo.h"

// Code contributed by Dave Heller:
// Define RUN_ON_PUMA_WITHOUT_IFADDRS to compile code that will
// work on Mac OS X 10.1, which does not have the getifaddrs call.
#define RUN_ON_PUMA_WITHOUT_IFADDRS 0
#if RUN_ON_PUMA_WITHOUT_IFADDRS
#include "mDNSMacOSXPuma.c"
#else
#include <ifaddrs.h>
#endif

#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
// This is currently defined in IOKit/PrivateHeaders/IOKitLibPrivate.h. Till it becomes an Public
// API, we will have our own declaration
void IONotificationPortSetDispatchQueue(IONotificationPortRef notify, dispatch_queue_t queue);
#endif

#if USE_IOPMCOPYACTIVEPMPREFERENCES
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/ps/IOPowerSourcesPrivate.h>
#endif

#include <mach/mach_error.h>
#include <mach/mach_port.h>
#include <mach/mach_time.h>
#include "helper.h"
#include "P2PPacketFilter.h"

#include <asl.h>

#include <SystemConfiguration/SCDynamicStorePrivate.h>

#if APPLE_OSX_mDNSResponder
#include <DeviceToDeviceManager/DeviceToDeviceManager.h>
#include <AWACS.h>

#if ! NO_D2D
D2DStatus D2DInitialize(CFRunLoopRef runLoop, D2DServiceCallback serviceCallback, void* userData) __attribute__((weak_import));
D2DStatus D2DTerminate() __attribute__((weak_import));
D2DStatus D2DStartAdvertisingPair(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize) __attribute__((weak_import));
D2DStatus D2DStopAdvertisingPair(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize) __attribute__((weak_import));
D2DStatus D2DStartBrowsingForKey(const Byte *key, const size_t keySize) __attribute__((weak_import));
D2DStatus D2DStopBrowsingForKey(const Byte *key, const size_t keySize) __attribute__((weak_import));
void D2DStartResolvingPair(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize) __attribute__((weak_import));
void D2DStopResolvingPair(const Byte *key, const size_t keySize, const Byte *value, const size_t valueSize) __attribute__((weak_import));
D2DStatus D2DRetain(D2DServiceInstance instanceHandle, D2DTransportType transportType) __attribute__((weak_import));
D2DStatus D2DRelease(D2DServiceInstance instanceHandle, D2DTransportType transportType) __attribute__((weak_import));

#define CHECK_D2D_FUNCTION(X) if (X)

#endif // ! NO_D2D

#else
#define NO_D2D 1
#define NO_AWACS 1
#endif // APPLE_OSX_mDNSResponder

#define kInterfaceSpecificOption "interface="

// ***************************************************************************
// Globals

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark - Globals
#endif

// By default we don't offer sleep proxy service
// If OfferSleepProxyService is set non-zero (typically via command-line switch),
// then we'll offer sleep proxy service on desktop Macs that are set to never sleep.
// We currently do not offer sleep proxy service on laptops, or on machines that are set to go to sleep.
mDNSexport int OfferSleepProxyService = 0;
mDNSexport int DisableSleepProxyClient = 0;
mDNSexport int UseInternalSleepProxy = 1;		// Set to non-zero to use internal (in-NIC) Sleep Proxy

// We disable inbound relay connection if this value is set to true (typically via command-line switch).
mDNSBool DisableInboundRelayConnection = mDNSfalse;
mDNSexport int OSXVers, iOSVers;
mDNSexport int KQueueFD;

#ifndef NO_SECURITYFRAMEWORK
static CFArrayRef ServerCerts;
OSStatus SSLSetAllowAnonymousCiphers(SSLContextRef context, Boolean enable);
#endif /* NO_SECURITYFRAMEWORK */

static CFStringRef NetworkChangedKey_IPv4;
static CFStringRef NetworkChangedKey_IPv6;
static CFStringRef NetworkChangedKey_Hostnames;
static CFStringRef NetworkChangedKey_Computername;
static CFStringRef NetworkChangedKey_DNS;
static CFStringRef NetworkChangedKey_DynamicDNS       = CFSTR("Setup:/Network/DynamicDNS");
static CFStringRef NetworkChangedKey_BackToMyMac      = CFSTR("Setup:/Network/BackToMyMac");
static CFStringRef NetworkChangedKey_BTMMConnectivity = CFSTR("State:/Network/Connectivity");
static CFStringRef NetworkChangedKey_PowerSettings    = CFSTR("State:/IOKit/PowerManagement/CurrentSettings");

static char  HINFO_HWstring_buffer[32];
static char *HINFO_HWstring = "Device";
static int   HINFO_HWstring_prefixlen = 6;

mDNSexport int WatchDogReportingThreshold = 250;

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
dispatch_queue_t SSLqueue;
#endif

#if APPLE_OSX_mDNSResponder
static mDNSu8 SPMetricPortability   = 99;
static mDNSu8 SPMetricMarginalPower = 99;
static mDNSu8 SPMetricTotalPower    = 99;
mDNSexport domainname ActiveDirectoryPrimaryDomain;
mDNSexport int        ActiveDirectoryPrimaryDomainLabelCount;
mDNSexport mDNSAddr   ActiveDirectoryPrimaryDomainServer;
#endif // APPLE_OSX_mDNSResponder

// Used by AutoTunnel
const char btmmprefix[] = "btmmdns:";
const char dnsprefix[] = "dns:";

// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - D2D Support
#endif

#if ! NO_D2D

// Name compression items for fake packet version number 1
static const mDNSu8 compression_packet_v1 = 0x01;

static DNSMessage compression_base_msg = { { {{0}}, {{0}}, 2, 0, 0, 0 }, "\x04_tcp\x05local\x00\x00\x0C\x00\x01\x04_udp\xC0\x11\x00\x0C\x00\x01" };
static mDNSu8 *const compression_limit = (mDNSu8 *) &compression_base_msg + sizeof(DNSMessage);
static mDNSu8 *const compression_lhs = (mDNSu8 *const) compression_base_msg.data + 27;

mDNSlocal void FreeD2DARElemCallback(mDNS *const m, AuthRecord *const rr, mStatus result);
mDNSlocal void PrintHex(mDNSu8 *data, mDNSu16 len);

static ARListElem *D2DRecords = NULL; // List of locally-generated PTR records to records found via D2D

typedef struct D2DBrowseListElem
	{
	struct D2DBrowseListElem *next;
	domainname name;
	mDNSu16 type;
	unsigned int refCount;
	} D2DBrowseListElem;

D2DBrowseListElem* D2DBrowseList = NULL;

mDNSlocal mDNSu8 *putVal16(mDNSu8 *ptr, mDNSu16 val)
	{
	ptr[0] = (mDNSu8)((val >> 8 ) & 0xFF);
	ptr[1] = (mDNSu8)((val      ) & 0xFF);
	return ptr + sizeof(mDNSu16);
	}

mDNSlocal mDNSu8 *putVal32(mDNSu8 *ptr, mDNSu32 val)
	{
	ptr[0] = (mDNSu8)((val >> 24) & 0xFF);
	ptr[1] = (mDNSu8)((val >> 16) & 0xFF);
	ptr[2] = (mDNSu8)((val >>  8) & 0xFF);
	ptr[3] = (mDNSu8)((val      ) & 0xFF);
	return ptr + sizeof(mDNSu32);
	}

mDNSlocal void DomainnameToLower(const domainname * const in, domainname * const out)
	{
	const mDNSu8 * const start = (const mDNSu8 * const)in;
	mDNSu8 *ptr = (mDNSu8*)start;
	while(*ptr)
		{
		mDNSu8 c = *ptr;
		out->c[ptr-start] = *ptr;
		ptr++;
		for (;c;c--,ptr++) out->c[ptr-start] = mDNSIsUpperCase(*ptr) ? (*ptr - 'A' + 'a') : *ptr;
		}
	out->c[ptr-start] = *ptr;
	}

mDNSlocal mStatus DNSNameCompressionParseBytes(mDNS *const m, const mDNSu8 *const lhs, const mDNSu16 lhs_len, const mDNSu8 *const rhs, const mDNSu16 rhs_len, AuthRecord *rr)
	{
	if (mDNS_LoggingEnabled)
		{
		LogInfo("%s", __func__);
		LogInfo("  Static Bytes: ");
		PrintHex((mDNSu8*)&compression_base_msg, compression_lhs - (mDNSu8*)&compression_base_msg);
		}
	
	mDNSu8 *ptr = compression_lhs; // pointer to the end of our fake packet

	// Check to make sure we're not going to go past the end of the DNSMessage data
	// 7 = 2 for CLASS (-1 for our version) + 4 for TTL + 2 for RDLENGTH
	if (ptr + lhs_len - 7 + rhs_len >= compression_limit) return mStatus_NoMemoryErr;
	
	// Copy the LHS onto our fake wire packet
	mDNSPlatformMemCopy(ptr, lhs, lhs_len);
	ptr += lhs_len - 1;
	
	// Check the 'fake packet' version number, to ensure that we know how to decompress this data
	if (*ptr != compression_packet_v1) return mStatus_Incompatible;
	
	// two bytes of CLASS
	ptr = putVal16(ptr, kDNSClass_IN | kDNSClass_UniqueRRSet);
	
	// four bytes of TTL
	ptr = putVal32(ptr, 120);
	
	// Copy the RHS length into the RDLENGTH of our fake wire packet
	ptr = putVal16(ptr, rhs_len);
	
	// Copy the RHS onto our fake wire packet
	mDNSPlatformMemCopy(ptr, rhs, rhs_len);
	ptr += rhs_len;
	
	if (mDNS_LoggingEnabled)
		{
		LogInfo("  Our Bytes %d: ", __LINE__);
		PrintHex(compression_lhs, ptr - compression_lhs);
		}

	ptr = (mDNSu8 *) GetLargeResourceRecord(m, &compression_base_msg, compression_lhs, ptr, mDNSInterface_Any, kDNSRecordTypePacketAns, &m->rec);
	if (!ptr || m->rec.r.resrec.RecordType == kDNSRecordTypePacketNegative)
		{ LogMsg("DNSNameCompressionParseBytes: failed to get large RR"); m->rec.r.resrec.RecordType = 0; return mStatus_UnknownErr; }
	else LogInfo("DNSNameCompressionParseBytes: got rr: %s", CRDisplayString(m, &m->rec.r));	

	mDNS_SetupResourceRecord(rr, mDNSNULL, mDNSInterface_P2P, m->rec.r.resrec.rrtype, 7200, kDNSRecordTypeShared, AuthRecordP2P, FreeD2DARElemCallback, NULL);
	AssignDomainName(&rr->namestorage, &m->rec.namestorage);
	rr->resrec.rdlength = m->rec.r.resrec.rdlength;
	rr->resrec.rdata->MaxRDLength = m->rec.r.resrec.rdlength;
	mDNSPlatformMemCopy(rr->resrec.rdata->u.data, m->rec.r.resrec.rdata->u.data, m->rec.r.resrec.rdlength);
	rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
	SetNewRData(&rr->resrec, mDNSNULL, 0);	// Sets rr->rdatahash for us
	
	m->rec.r.resrec.RecordType = 0; // Mark m->rec as no longer in use

	return mStatus_NoError;
	}

mDNSlocal mDNSu8 * DNSNameCompressionBuildLHS(const domainname const *typeDomain, DNS_TypeValues qtype)
	{
	mDNSu8 *ptr = putDomainNameAsLabels(&compression_base_msg, compression_lhs, compression_limit, typeDomain);
	if (!ptr) return ptr;
	*ptr = (qtype >> 8) & 0xff;
	ptr += 1;
	*ptr = qtype & 0xff;
	ptr += 1;
	*ptr = compression_packet_v1;
	return ptr + 1;
	}

mDNSlocal mDNSu8 * DNSNameCompressionBuildRHS(mDNSu8 *start, const ResourceRecord *const resourceRecord)
	{
	return putRData(&compression_base_msg, start, compression_limit, resourceRecord);
	}

mDNSlocal void PrintHex(mDNSu8 *data, mDNSu16 len)
	{
	mDNSu8 *end = data + len;
	char buffer[49] = {0};
	char *bufend = buffer + sizeof(buffer);
	while(data<end)
		{
		char *ptr = buffer;
		for(; data < end && ptr < bufend-1; ptr+=3,data++)
			mDNS_snprintf(ptr, bufend - ptr, "%02X ", *data);
		LogInfo("    %s", buffer);
		}
	}

mDNSlocal void PrintHelper(const char *const tag, mDNSu8 *lhs, mDNSu16 lhs_len, mDNSu8 *rhs, mDNSu16 rhs_len)
	{
	if (!mDNS_LoggingEnabled) return;
	
	LogInfo("%s:", tag);
	LogInfo("  LHS: ");
	PrintHex(lhs, lhs_len);
	
	if (!rhs) return;
	
	LogInfo("  RHS: ");
	PrintHex(rhs, rhs_len);
	}

mDNSlocal void FreeD2DARElemCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
	{
	(void)m;  // unused
	if (result == mStatus_MemFree)
		{
		ARListElem **ptr = &D2DRecords;
		ARListElem *tmp;
		while (*ptr && &(*ptr)->ar != rr) ptr = &(*ptr)->next;
		if (!*ptr) { LogMsg("FreeD2DARElemCallback: Could not find in D2DRecords: %s", ARDisplayString(m, rr)); return; }
		LogInfo("FreeD2DARElemCallback: Found in D2DRecords: %s", ARDisplayString(m, rr));
		tmp = *ptr;
		*ptr = (*ptr)->next;
		// Just because we stoppped browsing, doesn't mean we should tear down the PAN connection.
		mDNSPlatformMemFree(tmp);
		}
	}

mDNSlocal void xD2DClearCache(mDNS *const m, const domainname *regType)
	{
	ARListElem *ptr = D2DRecords;
	for ( ; ptr ; ptr = ptr->next)
		{
		if (SameDomainName(&ptr->ar.namestorage, regType)) 
			{
			char buffer[MAX_ESCAPED_DOMAIN_NAME];
			mDNS_Deregister(m, &ptr->ar);
			ConvertDomainNameToCString(regType, buffer);
			LogInfo("xD2DClearCache: Clearing cache record and deregistering %s", buffer);	
			}
		}
	}

mDNSlocal D2DBrowseListElem ** D2DFindInBrowseList(const domainname *const name, mDNSu16 type)
	{
	D2DBrowseListElem **ptr = &D2DBrowseList;

	for ( ; *ptr; ptr = &(*ptr)->next)
		if ((*ptr)->type == type && SameDomainName(&(*ptr)->name, name))
			break;
	
	return ptr;
	}

mDNSlocal unsigned int D2DBrowseListRefCount(const domainname *const name, mDNSu16 type)
	{
	D2DBrowseListElem **ptr = D2DFindInBrowseList(name, type);
	return *ptr ? (*ptr)->refCount : 0;
	}

mDNSlocal void D2DBrowseListRetain(const domainname *const name, mDNSu16 type)
	{
	D2DBrowseListElem **ptr = D2DFindInBrowseList(name, type);
	
	if (!*ptr)
	{
		*ptr = mDNSPlatformMemAllocate(sizeof(**ptr));
		mDNSPlatformMemZero(*ptr, sizeof(**ptr));
		(*ptr)->type = type;
		AssignDomainName(&(*ptr)->name, name);
	}
	(*ptr)->refCount += 1;
	
	LogInfo("D2DBrowseListRetain: %##s %s refcount now %u", (*ptr)->name.c, DNSTypeName((*ptr)->type), (*ptr)->refCount);	
	}

mDNSlocal void D2DBrowseListRelease(const domainname *const name, mDNSu16 type)
	{	
	D2DBrowseListElem **ptr = D2DFindInBrowseList(name, type);
	
	if (!*ptr) { LogMsg("D2DBrowseListRelease: Didn't find %##s %s in list", name->c, DNSTypeName(type)); return; }
	
	(*ptr)->refCount -= 1;
	
	LogInfo("D2DBrowseListRelease: %##s %s refcount now %u", (*ptr)->name.c, DNSTypeName((*ptr)->type), (*ptr)->refCount);
	
	if (!(*ptr)->refCount)
	{
		D2DBrowseListElem *tmp = *ptr;
		*ptr = (*ptr)->next;
		mDNSPlatformMemFree(tmp);
	}
	}

mDNSlocal mStatus xD2DParse(mDNS *const m, const mDNSu8 * const lhs, const mDNSu16 lhs_len, const mDNSu8 * const rhs, const mDNSu16 rhs_len, AuthRecord *rr)
	{
	if (*(lhs + (lhs_len - 1)) == compression_packet_v1)
		return DNSNameCompressionParseBytes(m, lhs, lhs_len, rhs, rhs_len, rr);
	else
		return mStatus_Incompatible;
	}

mDNSlocal void xD2DAddToCache(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
	{
	(void)transportType; // We don't care about this, yet.
	(void)instanceHandle; // We don't care about this, yet.
	
	if (result == kD2DSuccess)
		{
		if ( key == NULL || value == NULL || keySize == 0 || valueSize == 0) { LogMsg("xD2DAddToCache: NULL Byte * passed in or length == 0"); return; }
		
		mStatus err;
		ARListElem *ptr = mDNSPlatformMemAllocate(sizeof(ARListElem) + (valueSize < sizeof(RData) ? 0 : valueSize - sizeof(RData)));

		if (ptr == NULL) { LogMsg("xD2DAddToCache: memory allocation failure"); return; }

		err = xD2DParse(m, (const mDNSu8 * const)key, (const mDNSu16)keySize, (const mDNSu8 * const)value, (const mDNSu16)valueSize, &ptr->ar);
		if (err)
			{
			LogMsg("xD2DAddToCache: xD2DParse returned error: %d", err);
			PrintHelper(__func__, (mDNSu8 *)key, (mDNSu16)keySize, (mDNSu8 *)value, (mDNSu16)valueSize);
			mDNSPlatformMemFree(ptr);
			return;
			}
		err = mDNS_Register(m, &ptr->ar);
		if (err)
			{
			LogMsg("xD2DAddToCache: mDNS_Register returned error %d for %s", err, ARDisplayString(m, &ptr->ar));
			mDNSPlatformMemFree(ptr);
			return;
			}

		LogInfo("xD2DAddToCache: mDNS_Register succeeded for %s", ARDisplayString(m, &ptr->ar));
		ptr->next = D2DRecords;
		D2DRecords = ptr;
		}
	else
		LogMsg("xD2DAddToCache: Unexpected result %d", result);
	}

mDNSlocal ARListElem * xD2DFindInList(mDNS *const m, const Byte *const key, const size_t keySize, const Byte *const value, const size_t valueSize)
	{
	ARListElem *ptr = D2DRecords;
	ARListElem *arptr;
	
	if ( key == NULL || value == NULL || keySize == 0 || valueSize == 0) { LogMsg("xD2DFindInList: NULL Byte * passed in or length == 0"); return NULL; }

	arptr = mDNSPlatformMemAllocate(sizeof(ARListElem) + (valueSize < sizeof(RData) ? 0 : valueSize - sizeof(RData)));
	if (arptr == NULL) { LogMsg("xD2DFindInList: memory allocation failure"); return NULL; }

	if (xD2DParse(m, (const mDNSu8 *const)key, (const mDNSu16)keySize, (const mDNSu8 *const)value, (const mDNSu16)valueSize, &arptr->ar) != mStatus_NoError)
		{
		LogMsg("xD2DFindInList: xD2DParse failed for key: %p (%u) value: %p (%u)", key, keySize, value, valueSize);
		mDNSPlatformMemFree(arptr);
		return NULL;
		}
		
	while (ptr)
		{
		if (IdenticalResourceRecord(&arptr->ar.resrec, &ptr->ar.resrec)) break;
		ptr = ptr->next;
		}
		
	if (!ptr) LogMsg("xD2DFindInList: Could not find in D2DRecords: %s", ARDisplayString(m, &arptr->ar));
	mDNSPlatformMemFree(arptr);
	return ptr;
	}

mDNSlocal void xD2DRemoveFromCache(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
	{
	(void)transportType; // We don't care about this, yet.
	(void)instanceHandle; // We don't care about this, yet.
	
	if (result == kD2DSuccess)
		{
		ARListElem *ptr = xD2DFindInList(m, key, keySize, value, valueSize);
		if (ptr) 
			{
			LogInfo("xD2DRemoveFromCache: Remove from cache: %s", ARDisplayString(m, &ptr->ar));
			mDNS_Deregister(m, &ptr->ar);
			}
		}
	else
		LogMsg("xD2DRemoveFromCache: Unexpected result %d", result);
	}

mDNSlocal void xD2DServiceResolved(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
	{
	(void)m;
	(void)key;
	(void)keySize;
	(void)value;
	(void)valueSize;
	
	if (result == kD2DSuccess) 
		{
		LogInfo("xD2DServiceResolved: Starting up PAN connection for %p", instanceHandle);
		CHECK_D2D_FUNCTION(D2DRetain) D2DRetain(instanceHandle, transportType);
		}
	else LogMsg("xD2DServiceResolved: Unexpected result %d", result);
	}

mDNSlocal void xD2DRetainHappened(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
	{
	(void)m;
	(void)instanceHandle;
	(void)transportType;
	(void)key;
	(void)keySize;
	(void)value;
	(void)valueSize;
	
	if (result == kD2DSuccess) LogInfo("xD2DRetainHappened: Opening up PAN connection for %p", instanceHandle);
	else LogMsg("xD2DRetainHappened: Unexpected result %d", result);
	}

mDNSlocal void xD2DReleaseHappened(mDNS *const m, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize)
	{
	(void)m;
	(void)instanceHandle;
	(void)transportType;
	(void)key;
	(void)keySize;
	(void)value;
	(void)valueSize;
	
	if (result == kD2DSuccess) LogInfo("xD2DReleaseHappened: Closing PAN connection for %p", instanceHandle);
	else LogMsg("xD2DReleaseHappened: Unexpected result %d", result);
	}

mDNSlocal void xD2DServiceCallback(D2DServiceEvent event, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize, void *userData)
	{
	mDNS *m = (mDNS *) userData;
	const char *eventString = "unknown";
	
	KQueueLock(m);
	
	if (keySize   > 0xFFFF) LogMsg("xD2DServiceCallback: keySize too large: %u", keySize);
	if (valueSize > 0xFFFF) LogMsg("xD2DServiceCallback: valueSize too large: %u", valueSize);
		
	switch (event)
		{
		case D2DServiceFound:
			eventString = "D2DServiceFound";
			break;
		case D2DServiceLost:
			eventString = "D2DServiceLost";
			break;
		case D2DServiceResolved:
			eventString = "D2DServiceResolved";
			break;
		case D2DServiceRetained:
			eventString = "D2DServiceRetained";
			break;
		case D2DServiceReleased:
			eventString = "D2DServiceReleased";
			break;
		default:
			break;
		}
	
	LogInfo("xD2DServiceCallback: event=%s result=%d instanceHandle=%p transportType=%d LHS=%p (%u) RHS=%p (%u) userData=%p", eventString, result, instanceHandle, transportType, key, keySize, value, valueSize, userData);
	PrintHelper(__func__, (mDNSu8 *)key, (mDNSu16)keySize, (mDNSu8 *)value, (mDNSu16)valueSize);
	
	switch (event)
		{
		case D2DServiceFound:
			xD2DAddToCache(m, result, instanceHandle, transportType, key, keySize, value, valueSize);
			break;
		case D2DServiceLost:
			xD2DRemoveFromCache(m, result, instanceHandle, transportType, key, keySize, value, valueSize);
			break;
		case D2DServiceResolved:
			xD2DServiceResolved(m, result, instanceHandle, transportType, key, keySize, value, valueSize);
			break;
		case D2DServiceRetained:
			xD2DRetainHappened(m, result, instanceHandle, transportType, key, keySize, value, valueSize);
			break;
		case D2DServiceReleased:
			xD2DReleaseHappened(m, result, instanceHandle, transportType, key, keySize, value, valueSize);
			break;
		default:
			break;
		}
	
	// Need to tickle the main kqueue loop to potentially handle records we removed or added.
	KQueueUnlock(m, "xD2DServiceCallback");
	}

mDNSexport void external_start_browsing_for_service(mDNS *const m, const domainname *const typeDomain, DNS_TypeValues qtype)
	{
	(void)m;
	domainname lower;
	
	if (qtype == kDNSServiceType_A || qtype == kDNSServiceType_AAAA)
		{
		LogInfo("external_start_browsing_for_service: ignoring address record");
		return;
		}
	
	DomainnameToLower(typeDomain, &lower);
	
	if (!D2DBrowseListRefCount(&lower, qtype))
		{
		LogInfo("external_start_browsing_for_service: Starting browse for: %##s %s", lower.c, DNSTypeName(qtype));
		mDNSu8 *end = DNSNameCompressionBuildLHS(&lower, qtype);
		PrintHelper(__func__, compression_lhs, end - compression_lhs, mDNSNULL, 0);
		CHECK_D2D_FUNCTION(D2DStartBrowsingForKey) D2DStartBrowsingForKey(compression_lhs, end - compression_lhs);
		}
	D2DBrowseListRetain(&lower, qtype);
	}

mDNSexport void external_stop_browsing_for_service(mDNS *const m, const domainname *const typeDomain, DNS_TypeValues qtype)
	{
	domainname lower;
	
	if (qtype == kDNSServiceType_A || qtype == kDNSServiceType_AAAA)
 		{
		LogInfo("external_stop_browsing_for_service: ignoring address record");
		return;
		}
	
	DomainnameToLower(typeDomain, &lower);
	
	D2DBrowseListRelease(&lower, qtype);
	if (!D2DBrowseListRefCount(&lower, qtype))
		{
		LogInfo("external_stop_browsing_for_service: Stopping browse for: %##s %s", lower.c, DNSTypeName(qtype));
		mDNSu8 *end = DNSNameCompressionBuildLHS(&lower, qtype);
		PrintHelper(__func__, compression_lhs, end - compression_lhs, mDNSNULL, 0);
		CHECK_D2D_FUNCTION(D2DStopBrowsingForKey) D2DStopBrowsingForKey(compression_lhs, end - compression_lhs);
		xD2DClearCache(m, &lower);
		}
	}

mDNSexport void external_start_advertising_service(const ResourceRecord *const resourceRecord)
	{
	domainname lower;
	mDNSu8 *rhs = NULL;
	mDNSu8 *end = NULL;
	DomainnameToLower(resourceRecord->name, &lower);
	
	LogInfo("external_start_advertising_service: %s", RRDisplayString(&mDNSStorage, resourceRecord));
	// For SRV records, update packet filter if p2p interface already exists, otherwise,
	// if will be updated when we get the KEV_DL_IF_ATTACHED event for the interface.
	// Bonjour filter rules are removed when p2p interface KEV_DL_IF_DETACHED event is received.
	if (resourceRecord->rrtype == kDNSType_SRV)
		mDNSInitPacketFilter();

	if (resourceRecord->rrtype == kDNSServiceType_A || resourceRecord->rrtype == kDNSServiceType_AAAA)
		{
		LogInfo("external_start_advertising_service: ignoring address record");
		return;
		}
	rhs = DNSNameCompressionBuildLHS(&lower, resourceRecord->rrtype);
	end = DNSNameCompressionBuildRHS(rhs, resourceRecord);
	PrintHelper(__func__, compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	CHECK_D2D_FUNCTION(D2DStartAdvertisingPair) D2DStartAdvertisingPair(compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	}

mDNSexport void external_stop_advertising_service(const ResourceRecord *const resourceRecord)
	{
	domainname lower;
	mDNSu8 *rhs = NULL;
	mDNSu8 *end = NULL;
	DomainnameToLower(resourceRecord->name, &lower);
	
	LogInfo("external_stop_advertising_service: %s", RRDisplayString(&mDNSStorage, resourceRecord));
	if (resourceRecord->rrtype == kDNSServiceType_A || resourceRecord->rrtype == kDNSServiceType_AAAA)
		{
		LogInfo("external_stop_advertising_service: ignoring address record");
		return;
		}
	rhs = DNSNameCompressionBuildLHS(&lower, resourceRecord->rrtype);
	end = DNSNameCompressionBuildRHS(rhs, resourceRecord);
	PrintHelper(__func__, compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	CHECK_D2D_FUNCTION(D2DStopAdvertisingPair) D2DStopAdvertisingPair(compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	}

mDNSexport void external_start_resolving_service(const domainname *const fqdn)
	{
	domainname lower;
	mDNSu8 *rhs = NULL;
	mDNSu8 *end = NULL;
	DomainnameToLower(SkipLeadingLabels(fqdn, 1), &lower);
	
	LogInfo("external_start_resolving_service: %##s", fqdn->c);
	rhs = DNSNameCompressionBuildLHS(&lower, kDNSType_PTR);
	end = putDomainNameAsLabels(&compression_base_msg, rhs, compression_limit, fqdn);
	PrintHelper(__func__, compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	CHECK_D2D_FUNCTION(D2DStartResolvingPair) D2DStartResolvingPair(compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	}

mDNSexport void external_stop_resolving_service(const domainname *const fqdn) 
	{ 
	domainname lower;
	mDNSu8 *rhs = NULL;
	mDNSu8 *end = NULL;
	DomainnameToLower(SkipLeadingLabels(fqdn, 1), &lower);
	
	LogInfo("external_stop_resolving_service: %##s", fqdn->c);
	rhs = DNSNameCompressionBuildLHS(&lower, kDNSType_PTR);
	end = putDomainNameAsLabels(&compression_base_msg, rhs, compression_limit, fqdn);
	PrintHelper(__func__, compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	CHECK_D2D_FUNCTION(D2DStopResolvingPair) D2DStopResolvingPair(compression_lhs, rhs - compression_lhs, rhs, end - rhs);
	}

#elif APPLE_OSX_mDNSResponder

mDNSexport void external_start_browsing_for_service(mDNS *const m, const domainname *const type, DNS_TypeValues qtype) { (void)m; (void)type; (void)qtype; }
mDNSexport void external_stop_browsing_for_service(mDNS *const m, const domainname *const type, DNS_TypeValues qtype) { (void)m; (void)type; (void)qtype; }
mDNSexport void external_start_advertising_service(const ResourceRecord *const resourceRecord) { (void)resourceRecord; }
mDNSexport void external_stop_advertising_service(const ResourceRecord *const resourceRecord) { (void)resourceRecord; }
mDNSexport void external_start_resolving_service(const domainname *const fqdn)  { (void)fqdn; }
mDNSexport void external_stop_resolving_service(const domainname *const fqdn)  { (void)fqdn; }

#endif // ! NO_D2D

// ***************************************************************************
// Functions

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Utility Functions
#endif

// We only attempt to send and receive multicast packets on interfaces that are
// (a) flagged as multicast-capable
// (b) *not* flagged as point-to-point (e.g. modem)
// Typically point-to-point interfaces are modems (including mobile-phone pseudo-modems), and we don't want
// to run up the user's bill sending multicast traffic over a link where there's only a single device at the
// other end, and that device (e.g. a modem bank) is probably not answering Multicast DNS queries anyway.
#define MulticastInterface(i) (((i)->ifa_flags & IFF_MULTICAST) && !((i)->ifa_flags & IFF_POINTOPOINT))

mDNSexport void NotifyOfElusiveBug(const char *title, const char *msg)	// Both strings are UTF-8 text
	{
	static int notifyCount = 0;
	if (notifyCount) return;

	// If we display our alert early in the boot process, then it vanishes once the desktop appears.
	// To avoid this, we don't try to display alerts in the first three minutes after boot.
	if ((mDNSu32)(mDNSPlatformRawTime()) < (mDNSu32)(mDNSPlatformOneSecond * 180)) return;

	// Unless ForceAlerts is defined, we only show these bug report alerts on machines that have a 17.x.x.x address
	#if !ForceAlerts
		{
		// Determine if we're at Apple (17.*.*.*)
		NetworkInterfaceInfoOSX *i;
		for (i = mDNSStorage.p->InterfaceList; i; i = i->next)
			if (i->ifinfo.ip.type == mDNSAddrType_IPv4 && i->ifinfo.ip.ip.v4.b[0] == 17)
				break;
		if (!i) return;	// If not at Apple, don't show the alert
		}
	#endif

	LogMsg("%s", title);
	LogMsg("%s", msg);
	// Display a notification to the user
	notifyCount++;

#ifndef NO_CFUSERNOTIFICATION
	mDNSNotify(title, msg);
#endif /* NO_CFUSERNOTIFICATION */
	}

mDNSlocal struct ifaddrs *myGetIfAddrs(int refresh)
	{
	static struct ifaddrs *ifa = NULL;

	if (refresh && ifa)
		{
		freeifaddrs(ifa);
		ifa = NULL;
		}

	if (ifa == NULL) getifaddrs(&ifa);

	return ifa;
	}

// To match *either* a v4 or v6 instance of this interface name, pass AF_UNSPEC for type
mDNSlocal NetworkInterfaceInfoOSX *SearchForInterfaceByName(mDNS *const m, const char *ifname, int type)
	{
	NetworkInterfaceInfoOSX *i;
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->Exists && !strcmp(i->ifinfo.ifname, ifname) &&
			((type == AF_UNSPEC                                         ) ||
			 (type == AF_INET  && i->ifinfo.ip.type == mDNSAddrType_IPv4) ||
			 (type == AF_INET6 && i->ifinfo.ip.type == mDNSAddrType_IPv6))) return(i);
	return(NULL);
	}

mDNSlocal int myIfIndexToName(u_short ifindex, char *name)
	{
	struct ifaddrs *ifa;
	for (ifa = myGetIfAddrs(0); ifa; ifa = ifa->ifa_next)
		if (ifa->ifa_addr->sa_family == AF_LINK)
			if (((struct sockaddr_dl*)ifa->ifa_addr)->sdl_index == ifindex)
				{ strlcpy(name, ifa->ifa_name, IF_NAMESIZE); return 0; }
	return -1;
	}

mDNSexport NetworkInterfaceInfoOSX *IfindexToInterfaceInfoOSX(const mDNS *const m, mDNSInterfaceID ifindex)
{
	mDNSu32 scope_id = (mDNSu32)(uintptr_t)ifindex;
	NetworkInterfaceInfoOSX *i;

	// Don't get tricked by inactive interfaces
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->Registered && i->scope_id == scope_id) return(i);

	return mDNSNULL;
}

mDNSexport mDNSInterfaceID mDNSPlatformInterfaceIDfromInterfaceIndex(mDNS *const m, mDNSu32 ifindex)
	{
	if (ifindex == kDNSServiceInterfaceIndexLocalOnly) return(mDNSInterface_LocalOnly);
	if (ifindex == kDNSServiceInterfaceIndexP2P      ) return(mDNSInterface_P2P);
	if (ifindex == kDNSServiceInterfaceIndexAny      ) return(mDNSNULL);

	NetworkInterfaceInfoOSX* ifi = IfindexToInterfaceInfoOSX(m, (mDNSInterfaceID)(uintptr_t)ifindex);
	if (!ifi)
		{
		// Not found. Make sure our interface list is up to date, then try again.
		LogInfo("mDNSPlatformInterfaceIDfromInterfaceIndex: InterfaceID for interface index %d not found; Updating interface list", ifindex);
		mDNSMacOSXNetworkChanged(m);
		ifi = IfindexToInterfaceInfoOSX(m, (mDNSInterfaceID)(uintptr_t)ifindex);
		}

	if (!ifi) return(mDNSNULL);	
	
	return(ifi->ifinfo.InterfaceID);
	}


mDNSexport mDNSu32 mDNSPlatformInterfaceIndexfromInterfaceID(mDNS *const m, mDNSInterfaceID id, mDNSBool suppressNetworkChange)
	{
	NetworkInterfaceInfoOSX *i;
	if (id == mDNSInterface_LocalOnly) return(kDNSServiceInterfaceIndexLocalOnly);
	if (id == mDNSInterface_P2P      ) return(kDNSServiceInterfaceIndexP2P);
	if (id == mDNSInterface_Any      ) return(0);

	mDNSu32 scope_id = (mDNSu32)(uintptr_t)id;

	// Don't use i->Registered here, because we DO want to find inactive interfaces, which have no Registered set
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->scope_id == scope_id) return(i->scope_id);

	// If we are supposed to suppress network change, return "id" back 
	if (suppressNetworkChange) return scope_id;

	// Not found. Make sure our interface list is up to date, then try again.
	LogInfo("Interface index for InterfaceID %p not found; Updating interface list", id);
	mDNSMacOSXNetworkChanged(m);
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->scope_id == scope_id) return(i->scope_id);

	return(0);
	}

#if APPLE_OSX_mDNSResponder
mDNSexport void mDNSASLLog(uuid_t *uuid, const char *subdomain, const char *result, const char *signature, const char *fmt, ...)
	{
	if (OSXVers < OSXVers_10_6_SnowLeopard) return;	// Only do ASL on Mac OS X 10.6 and later (not on iOS)

	static char		buffer[512];
	aslmsg 			asl_msg = asl_new(ASL_TYPE_MSG);

	if (!asl_msg)	{ LogMsg("mDNSASLLog: asl_new failed"); return; }
	if (uuid)
		{
		char		uuidStr[37];
		uuid_unparse(*uuid, uuidStr);
		asl_set		(asl_msg, "com.apple.message.uuid", uuidStr);
		}

	static char 	domainBase[] = "com.apple.mDNSResponder.%s";
	mDNS_snprintf	(buffer, sizeof(buffer), domainBase, subdomain);
	asl_set			(asl_msg, "com.apple.message.domain", buffer);

	if (result)		asl_set(asl_msg, "com.apple.message.result", result);
	if (signature)	asl_set(asl_msg, "com.apple.message.signature", signature);

	va_list ptr;
	va_start(ptr,fmt);
	mDNS_vsnprintf(buffer, sizeof(buffer), fmt, ptr);
	va_end(ptr);

	int	old_filter = asl_set_filter(NULL,ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
	asl_log(NULL, asl_msg, ASL_LEVEL_DEBUG, "%s", buffer);
	asl_set_filter(NULL, old_filter);
	asl_free(asl_msg);
	}
#endif // APPLE_OSX_mDNSResponder

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - UDP & TCP send & receive
#endif

mDNSlocal mDNSBool AddrRequiresPPPConnection(const struct sockaddr *addr)
	{
	mDNSBool result = mDNSfalse;
	SCNetworkConnectionFlags flags;
	SCNetworkReachabilityRef ReachRef = NULL;

	ReachRef = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, addr);
	if (!ReachRef) { LogMsg("ERROR: RequiresConnection - SCNetworkReachabilityCreateWithAddress"); goto end; }
	if (!SCNetworkReachabilityGetFlags(ReachRef, &flags)) { LogMsg("ERROR: AddrRequiresPPPConnection - SCNetworkReachabilityGetFlags"); goto end; }
	result = flags & kSCNetworkFlagsConnectionRequired;

	end:
	if (ReachRef) CFRelease(ReachRef);
	return result;
	}

// Note: If InterfaceID is NULL, it means, "send this packet through our anonymous unicast socket"
// Note: If InterfaceID is non-NULL it means, "send this packet through our port 5353 socket on the specified interface"
// OR send via our primary v4 unicast socket
// UPDATE: The UDPSocket *src parameter now allows the caller to specify the source socket
mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const msg, const mDNSu8 *const end,
	mDNSInterfaceID InterfaceID, UDPSocket *src, const mDNSAddr *dst, mDNSIPPort dstPort)
	{
	NetworkInterfaceInfoOSX *info = mDNSNULL;
	struct sockaddr_storage to;
	int s = -1, err;
	mStatus result = mStatus_NoError;

	if (InterfaceID)
		{
		info = IfindexToInterfaceInfoOSX(m, InterfaceID);
		if (info == NULL)
			{
			LogMsg("mDNSPlatformSendUDP: Invalid interface index %p", InterfaceID);
			return mStatus_BadParamErr;
			}
		}

	char *ifa_name = InterfaceID ? info->ifinfo.ifname : "unicast";

	if (dst->type == mDNSAddrType_IPv4)
		{
		struct sockaddr_in *sin_to = (struct sockaddr_in*)&to;
		sin_to->sin_len            = sizeof(*sin_to);
		sin_to->sin_family         = AF_INET;
		sin_to->sin_port           = dstPort.NotAnInteger;
		sin_to->sin_addr.s_addr    = dst->ip.v4.NotAnInteger;
		s = (src ? src->ss : m->p->permanentsockets).sktv4;

		if (info)	// Specify outgoing interface
			{
			if (!mDNSAddrIsDNSMulticast(dst))
				{
				#ifdef IP_BOUND_IF
					if (info->scope_id == 0)
						LogInfo("IP_BOUND_IF socket option not set -- info %p (%s) scope_id is zero", info, ifa_name);
					else
						setsockopt(s, IPPROTO_IP, IP_BOUND_IF, &info->scope_id, sizeof(info->scope_id));
				#else
					{
					static int displayed = 0;
					if (displayed < 1000)
						{
						displayed++;
						LogInfo("IP_BOUND_IF socket option not defined -- cannot specify interface for unicast packets");
						}
					}
				#endif
				}
			else
				#ifdef IP_MULTICAST_IFINDEX
				{
				err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IFINDEX, &info->scope_id, sizeof(info->scope_id));
				// We get an error when we compile on a machine that supports this option and run the binary on
				// a different machine that does not support it
				if (err < 0)
					{
					if (errno != ENOPROTOOPT) LogInfo("mDNSPlatformSendUDP: setsockopt: IP_MUTLTICAST_IFINDEX returned %d", errno);
					err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &info->ifa_v4addr, sizeof(info->ifa_v4addr));
					if (err < 0 && !m->p->NetworkChanged)
						LogMsg("setsockopt - IP_MULTICAST_IF error %.4a %d errno %d (%s)", &info->ifa_v4addr, err, errno, strerror(errno));
					}
				}
				#else
				{
				err = setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, &info->ifa_v4addr, sizeof(info->ifa_v4addr));
				if (err < 0 && !m->p->NetworkChanged)
					LogMsg("setsockopt - IP_MULTICAST_IF error %.4a %d errno %d (%s)", &info->ifa_v4addr, err, errno, strerror(errno));

				}
				#endif
			}
		}
#ifndef NO_IPV6
	else if (dst->type == mDNSAddrType_IPv6)
		{
		struct sockaddr_in6 *sin6_to = (struct sockaddr_in6*)&to;
		sin6_to->sin6_len            = sizeof(*sin6_to);
		sin6_to->sin6_family         = AF_INET6;
		sin6_to->sin6_port           = dstPort.NotAnInteger;
		sin6_to->sin6_flowinfo       = 0;
		sin6_to->sin6_addr           = *(struct in6_addr*)&dst->ip.v6;
		sin6_to->sin6_scope_id       = info ? info->scope_id : 0;
		s = (src ? src->ss : m->p->permanentsockets).sktv6;
		if (info && mDNSAddrIsDNSMulticast(dst))	// Specify outgoing interface
			{
			err = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &info->scope_id, sizeof(info->scope_id));
			if (err < 0)
				{
				char name[IFNAMSIZ];
				if (if_indextoname(info->scope_id, name) != NULL)
					LogMsg("setsockopt - IPV6_MULTICAST_IF error %d errno %d (%s)", err, errno, strerror(errno));
				else
					LogInfo("setsockopt - IPV6_MUTLICAST_IF scopeid %d, not a valid interface", info->scope_id);
				}
			}
		}
#endif
	else
		{
		LogMsg("mDNSPlatformSendUDP: dst is not an IPv4 or IPv6 address!");
#if ForceAlerts
		*(long*)0 = 0;
#endif
		return mStatus_BadParamErr;
		}

	if (s >= 0)
		verbosedebugf("mDNSPlatformSendUDP: sending on InterfaceID %p %5s/%ld to %#a:%d skt %d",
			InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s);
	else
		verbosedebugf("mDNSPlatformSendUDP: NOT sending on InterfaceID %p %5s/%ld (socket of this type not available)",
			InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort));

	// Note: When sending, mDNSCore may often ask us to send both a v4 multicast packet and then a v6 multicast packet
	// If we don't have the corresponding type of socket available, then return mStatus_Invalid
	if (s < 0) return(mStatus_Invalid);

	err = sendto(s, msg, (UInt8*)end - (UInt8*)msg, 0, (struct sockaddr *)&to, to.ss_len);
	if (err < 0)
		{
		static int MessageCount = 0;
		// Don't report EHOSTDOWN (i.e. ARP failure), ENETDOWN, or no route to host for unicast destinations
		if (!mDNSAddressIsAllDNSLinkGroup(dst))
			if (errno == EHOSTDOWN || errno == ENETDOWN || errno == EHOSTUNREACH || errno == ENETUNREACH) return(mStatus_TransientErr);
		// Don't report EHOSTUNREACH in the first three minutes after boot
		// This is because mDNSResponder intentionally starts up early in the boot process (See <rdar://problem/3409090>)
		// but this means that sometimes it starts before configd has finished setting up the multicast routing entries.
		if (errno == EHOSTUNREACH && (mDNSu32)(mDNSPlatformRawTime()) < (mDNSu32)(mDNSPlatformOneSecond * 180)) return(mStatus_TransientErr);
		// Don't report EADDRNOTAVAIL ("Can't assign requested address") if we're in the middle of a network configuration change
		if (errno == EADDRNOTAVAIL && m->p->NetworkChanged) return(mStatus_TransientErr);
		if (MessageCount < 1000)
			{
			MessageCount++;
			if (errno == EHOSTUNREACH || errno == EADDRNOTAVAIL || errno == ENETDOWN)
				LogInfo("mDNSPlatformSendUDP sendto(%d) failed to send packet on InterfaceID %p %5s/%d to %#a:%d skt %d error %d errno %d (%s) %lu",
					s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, errno, strerror(errno), (mDNSu32)(m->timenow));
			else
				LogMsg("mDNSPlatformSendUDP sendto(%d) failed to send packet on InterfaceID %p %5s/%d to %#a:%d skt %d error %d errno %d (%s) %lu",
					s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, errno, strerror(errno), (mDNSu32)(m->timenow));
			}
		result = mStatus_UnknownErr;
		}

#ifdef IP_BOUND_IF
	if (dst->type == mDNSAddrType_IPv4 && info && !mDNSAddrIsDNSMulticast(dst))
		{
		static const mDNSu32 ifindex = 0;
		setsockopt(s, IPPROTO_IP, IP_BOUND_IF, &ifindex, sizeof(ifindex));
		}
#endif

	return(result);
	}

mDNSlocal ssize_t myrecvfrom(const int s, void *const buffer, const size_t max,
	struct sockaddr *const from, size_t *const fromlen, mDNSAddr *dstaddr, char ifname[IF_NAMESIZE], mDNSu8 *ttl)
	{
	static unsigned int numLogMessages = 0;
	struct iovec databuffers = { (char *)buffer, max };
	struct msghdr   msg;
	ssize_t         n;
	struct cmsghdr *cmPtr;
	char            ancillary[1024];

	*ttl = 255;  // If kernel fails to provide TTL data (e.g. Jaguar doesn't) then assume the TTL was 255 as it should be

	// Set up the message
	msg.msg_name       = (caddr_t)from;
	msg.msg_namelen    = *fromlen;
	msg.msg_iov        = &databuffers;
	msg.msg_iovlen     = 1;
	msg.msg_control    = (caddr_t)&ancillary;
	msg.msg_controllen = sizeof(ancillary);
	msg.msg_flags      = 0;

	// Receive the data
	n = recvmsg(s, &msg, 0);
	if (n<0)
		{
		if (errno != EWOULDBLOCK && numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) returned error %d errno %d", s, n, errno);
		return(-1);
		}
	if (msg.msg_controllen < (int)sizeof(struct cmsghdr))
		{
		if (numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) returned %d msg.msg_controllen %d < sizeof(struct cmsghdr) %lu",
			s, n, msg.msg_controllen, sizeof(struct cmsghdr));
		return(-1);
		}
	if (msg.msg_flags & MSG_CTRUNC)
		{
		if (numLogMessages++ < 100) LogMsg("mDNSMacOSX.c: recvmsg(%d) msg.msg_flags & MSG_CTRUNC", s);
		return(-1);
		}

	*fromlen = msg.msg_namelen;

	// Parse each option out of the ancillary data.
	for (cmPtr = CMSG_FIRSTHDR(&msg); cmPtr; cmPtr = CMSG_NXTHDR(&msg, cmPtr))
		{
		// debugf("myrecvfrom cmsg_level %d cmsg_type %d", cmPtr->cmsg_level, cmPtr->cmsg_type);
		if (cmPtr->cmsg_level == IPPROTO_IP && cmPtr->cmsg_type == IP_RECVDSTADDR)
			{
			dstaddr->type = mDNSAddrType_IPv4;
			dstaddr->ip.v4 = *(mDNSv4Addr*)CMSG_DATA(cmPtr);
			//LogMsg("mDNSMacOSX.c: recvmsg IP_RECVDSTADDR %.4a", &dstaddr->ip.v4);
			}
		if (cmPtr->cmsg_level == IPPROTO_IP && cmPtr->cmsg_type == IP_RECVIF)
			{
			struct sockaddr_dl *sdl = (struct sockaddr_dl *)CMSG_DATA(cmPtr);
			if (sdl->sdl_nlen < IF_NAMESIZE)
				{
				mDNSPlatformMemCopy(ifname, sdl->sdl_data, sdl->sdl_nlen);
				ifname[sdl->sdl_nlen] = 0;
				// debugf("IP_RECVIF sdl_index %d, sdl_data %s len %d", sdl->sdl_index, ifname, sdl->sdl_nlen);
				}
			}
		if (cmPtr->cmsg_level == IPPROTO_IP && cmPtr->cmsg_type == IP_RECVTTL)
			*ttl = *(u_char*)CMSG_DATA(cmPtr);
		if (cmPtr->cmsg_level == IPPROTO_IPV6 && cmPtr->cmsg_type == IPV6_PKTINFO)
			{
			struct in6_pktinfo *ip6_info = (struct in6_pktinfo*)CMSG_DATA(cmPtr);
			dstaddr->type = mDNSAddrType_IPv6;
			dstaddr->ip.v6 = *(mDNSv6Addr*)&ip6_info->ipi6_addr;
			myIfIndexToName(ip6_info->ipi6_ifindex, ifname);
			}
		if (cmPtr->cmsg_level == IPPROTO_IPV6 && cmPtr->cmsg_type == IPV6_HOPLIMIT)
			*ttl = *(int*)CMSG_DATA(cmPtr);
		}

	return(n);
	}

mDNSlocal void myKQSocketCallBack(int s1, short filter, void *context)
	{
	KQSocketSet *const ss = (KQSocketSet *)context;
	mDNS *const m = ss->m;
	int err = 0, count = 0, closed = 0;

	if (filter != EVFILT_READ)
		LogMsg("myKQSocketCallBack: Why is filter %d not EVFILT_READ (%d)?", filter, EVFILT_READ);

	if (s1 != ss->sktv4
#ifndef NO_IPV6
		&& s1 != ss->sktv6
#endif
		)
		{
		LogMsg("myKQSocketCallBack: native socket %d", s1);
		LogMsg("myKQSocketCallBack: sktv4 %d", ss->sktv4);
#ifndef NO_IPV6
		LogMsg("myKQSocketCallBack: sktv6 %d", ss->sktv6);
#endif
 		}

	while (!closed)
		{
		mDNSAddr senderAddr, destAddr;
		mDNSIPPort senderPort;
		struct sockaddr_storage from;
		size_t fromlen = sizeof(from);
		char packetifname[IF_NAMESIZE] = "";
		mDNSu8 ttl;
		err = myrecvfrom(s1, &m->imsg, sizeof(m->imsg), (struct sockaddr *)&from, &fromlen, &destAddr, packetifname, &ttl);
		if (err < 0) break;

		count++;
		if (from.ss_family == AF_INET)
			{
			struct sockaddr_in *s = (struct sockaddr_in*)&from;
			senderAddr.type = mDNSAddrType_IPv4;
			senderAddr.ip.v4.NotAnInteger = s->sin_addr.s_addr;
			senderPort.NotAnInteger = s->sin_port;
			//LogInfo("myKQSocketCallBack received IPv4 packet from %#-15a to %#-15a on skt %d %s", &senderAddr, &destAddr, s1, packetifname);
			}
		else if (from.ss_family == AF_INET6)
			{
			struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&from;
			senderAddr.type = mDNSAddrType_IPv6;
			senderAddr.ip.v6 = *(mDNSv6Addr*)&sin6->sin6_addr;
			senderPort.NotAnInteger = sin6->sin6_port;
			//LogInfo("myKQSocketCallBack received IPv6 packet from %#-15a to %#-15a on skt %d %s", &senderAddr, &destAddr, s1, packetifname);
			}
		else
			{
			LogMsg("myKQSocketCallBack from is unknown address family %d", from.ss_family);
			return;
			}

		// Note: When handling multiple packets in a batch, MUST reset InterfaceID before handling each packet
		mDNSInterfaceID InterfaceID = mDNSNULL;
		//NetworkInterfaceInfo *intf = m->HostInterfaces;
		//while (intf && strcmp(intf->ifname, packetifname)) intf = intf->next;

		NetworkInterfaceInfoOSX *intf = m->p->InterfaceList;
		while (intf && strcmp(intf->ifinfo.ifname, packetifname)) intf = intf->next;

		// When going to sleep we deregister all our interfaces, but if the machine
		// takes a few seconds to sleep we may continue to receive multicasts
		// during that time, which would confuse mDNSCoreReceive, because as far
		// as it's concerned, we should have no active interfaces any more.
		// Hence we ignore multicasts for which we can find no matching InterfaceID.
		if (intf) InterfaceID = intf->ifinfo.InterfaceID;
		else if (mDNSAddrIsDNSMulticast(&destAddr)) continue;

//		LogMsg("myKQSocketCallBack got packet from %#a to %#a on interface %#a/%s",
//			&senderAddr, &destAddr, &ss->info->ifinfo.ip, ss->info->ifinfo.ifname);

		// mDNSCoreReceive may close the socket we're reading from.  We must break out of our
		// loop when that happens, or we may try to read from an invalid FD.  We do this by
		// setting the closeFlag pointer in the socketset, so CloseSocketSet can inform us
		// if it closes the socketset.
		ss->closeFlag = &closed;

		mDNSCoreReceive(m, &m->imsg, (unsigned char*)&m->imsg + err, &senderAddr, senderPort, &destAddr, ss->port, InterfaceID);

		// if we didn't close, we can safely dereference the socketset, and should to
		// reset the closeFlag, since it points to something on the stack
		if (!closed) ss->closeFlag = mDNSNULL;
		}

	if (err < 0 && (errno != EWOULDBLOCK || count == 0))
		{
		// Something is busted here.
		// kqueue says there is a packet, but myrecvfrom says there is not.
		// Try calling select() to get another opinion.
		// Find out about other socket parameter that can help understand why select() says the socket is ready for read
		// All of this is racy, as data may have arrived after the call to select()
		static unsigned int numLogMessages = 0;
		int save_errno = errno;
		int so_error = -1;
		int so_nread = -1;
		int fionread = -1;
		socklen_t solen = sizeof(int);
		fd_set readfds;
		struct timeval timeout;
		int selectresult;
		FD_ZERO(&readfds);
		FD_SET(s1, &readfds);
		timeout.tv_sec  = 0;
		timeout.tv_usec = 0;
		selectresult = select(s1+1, &readfds, NULL, NULL, &timeout);
		if (getsockopt(s1, SOL_SOCKET, SO_ERROR, &so_error, &solen) == -1)
			LogMsg("myKQSocketCallBack getsockopt(SO_ERROR) error %d", errno);
		if (getsockopt(s1, SOL_SOCKET, SO_NREAD, &so_nread, &solen) == -1)
			LogMsg("myKQSocketCallBack getsockopt(SO_NREAD) error %d", errno);
		if (ioctl(s1, FIONREAD, &fionread) == -1)
			LogMsg("myKQSocketCallBack ioctl(FIONREAD) error %d", errno);
		if (numLogMessages++ < 100)
			LogMsg("myKQSocketCallBack recvfrom skt %d error %d errno %d (%s) select %d (%spackets waiting) so_error %d so_nread %d fionread %d count %d",
				s1, err, save_errno, strerror(save_errno), selectresult, FD_ISSET(s1, &readfds) ? "" : "*NO* ", so_error, so_nread, fionread, count);
		if (numLogMessages > 5)
			NotifyOfElusiveBug("Flaw in Kernel (select/recvfrom mismatch)",
				"Congratulations, you've reproduced an elusive bug.\r"
				"Please contact the current assignee of <rdar://problem/3375328>.\r"
				"Alternatively, you can send email to radar-3387020@group.apple.com. (Note number is different.)\r"
				"If possible, please leave your machine undisturbed so that someone can come to investigate the problem.");

		sleep(1);		// After logging this error, rate limit so we don't flood syslog
		}
	}

// TCP socket support

typedef enum
	{
	handshake_required,
	handshake_in_progress,
	handshake_completed,
	handshake_to_be_closed
	} handshakeStatus;
	
struct TCPSocket_struct
	{
	TCPSocketFlags flags;		// MUST BE FIRST FIELD -- mDNSCore expects every TCPSocket_struct to begin with TCPSocketFlags flags
	TCPConnectionCallback callback;
	int fd;
	KQueueEntry *kqEntry;	
	KQSocketSet ss;
#ifndef NO_SECURITYFRAMEWORK
	SSLContextRef tlsContext;
	pthread_t handshake_thread;
#endif /* NO_SECURITYFRAMEWORK */
	domainname hostname;
	void *context;
	mDNSBool setup;
	mDNSBool connected;
	handshakeStatus handshake;
	mDNS *m; // So we can call KQueueLock from the SSLHandshake thread
	mStatus err;
	};

mDNSlocal void doTcpSocketCallback(TCPSocket *sock)
	{
	mDNSBool c = !sock->connected;
	sock->connected = mDNStrue;
	sock->callback(sock, sock->context, c, sock->err);
	// Note: the callback may call CloseConnection here, which frees the context structure!
	}

#ifndef NO_SECURITYFRAMEWORK

mDNSlocal OSStatus tlsWriteSock(SSLConnectionRef connection, const void *data, size_t *dataLength)
	{
	int ret = send(((TCPSocket *)connection)->fd, data, *dataLength, 0);
	if (ret >= 0 && (size_t)ret < *dataLength) { *dataLength = ret; return(errSSLWouldBlock); }
	if (ret >= 0)                              { *dataLength = ret; return(noErr); }
	*dataLength = 0;
	if (errno == EAGAIN                      ) return(errSSLWouldBlock);
	if (errno == ENOENT                      ) return(errSSLClosedGraceful);
	if (errno == EPIPE || errno == ECONNRESET) return(errSSLClosedAbort);
	LogMsg("ERROR: tlsWriteSock: %d error %d (%s)\n", ((TCPSocket *)connection)->fd, errno, strerror(errno));
	return(errSSLClosedAbort);
	}

mDNSlocal OSStatus tlsReadSock(SSLConnectionRef connection, void *data, size_t *dataLength)
	{
	int ret = recv(((TCPSocket *)connection)->fd, data, *dataLength, 0);
	if (ret > 0 && (size_t)ret < *dataLength) { *dataLength = ret; return(errSSLWouldBlock); }
	if (ret > 0)                              { *dataLength = ret; return(noErr); }
	*dataLength = 0;
	if (ret == 0 || errno == ENOENT    ) return(errSSLClosedGraceful);
	if (            errno == EAGAIN    ) return(errSSLWouldBlock);
	if (            errno == ECONNRESET) return(errSSLClosedAbort);
	LogMsg("ERROR: tlsSockRead: error %d (%s)\n", errno, strerror(errno));
	return(errSSLClosedAbort);
	}

mDNSlocal OSStatus tlsSetupSock(TCPSocket *sock, mDNSBool server)
	{
	char domname_cstr[MAX_ESCAPED_DOMAIN_NAME];

	mStatus err = SSLNewContext(server, &sock->tlsContext);
	if (err) { LogMsg("ERROR: tlsSetupSock: SSLNewContext failed with error code: %d", err); return(err); }

	err = SSLSetIOFuncs(sock->tlsContext, tlsReadSock, tlsWriteSock);
	if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetIOFuncs failed with error code: %d", err); return(err); }

	err = SSLSetConnection(sock->tlsContext, (SSLConnectionRef) sock);
	if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetConnection failed with error code: %d", err); return(err); }

	// Instead of listing all the acceptable ciphers, we just disable the bad ciphers. It does not disable
	// all the bad ciphers like RC4_MD5, but it assumes that the servers don't offer them.
	err = SSLSetAllowAnonymousCiphers(sock->tlsContext, 0);
	if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetAllowAnonymousCiphers failed with error code: %d", err); return(err); }

	// We already checked for NULL in hostname and this should never happen. Hence, returning -1
	// (error not in OSStatus space) is okay.
	if (!sock->hostname.c[0]) {LogMsg("ERROR: tlsSetupSock: hostname NULL"); return -1; }

	ConvertDomainNameToCString(&sock->hostname, domname_cstr);
	err = SSLSetPeerDomainName(sock->tlsContext, domname_cstr, strlen(domname_cstr));
	if (err) { LogMsg("ERROR: tlsSetupSock: SSLSetPeerDomainname: %s failed with error code: %d", domname_cstr, err); return(err); }

	return(err);
	}

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
mDNSlocal void doSSLHandshake(void *ctx)
	{
	TCPSocket *sock = (TCPSocket*)ctx;
	mStatus err = SSLHandshake(sock->tlsContext);
	
	//Can't have multiple threads in mDNS core. When MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM is
	//defined, KQueueLock is a noop. Hence we need to serialize here
	//
	//NOTE: We just can't serialize doTcpSocketCallback alone on the main queue.
	//We need the rest of the logic also. Otherwise, we can enable the READ
	//events below, dispatch a doTcpSocketCallback on the main queue. Assume it is
	//ConnFailed which means we are going to free the tcpInfo. While it
	//is waiting to be dispatched, another read event can come into tcpKQSocketCallback
	//and potentially call doTCPCallback with error which can close the fd and free the
	//tcpInfo. Later when the thread gets dispatched it will crash because the tcpInfo
	//is already freed.

	dispatch_async(dispatch_get_main_queue(), ^{

		LogInfo("doSSLHandshake %p: got lock", sock); // Log *after* we get the lock

		if (sock->handshake == handshake_to_be_closed)
			{
			LogInfo("SSLHandshake completed after close");
			mDNSPlatformTCPCloseConnection(sock);
			}
		else
			{
			if (sock->fd != -1) KQueueSet(sock->fd, EV_ADD, EVFILT_READ, sock->kqEntry);
			else LogMsg("doSSLHandshake: sock->fd is -1");
	
			if (err == errSSLWouldBlock)
				sock->handshake = handshake_required;
			else
				{
				if (err)
					{
					LogMsg("SSLHandshake failed: %d%s", err, err == errSSLPeerInternalError ? " (server busy)" : "");
					SSLDisposeContext(sock->tlsContext);
					sock->tlsContext = NULL;
					}
				
				sock->err = err ? mStatus_ConnFailed : 0;
				sock->handshake = handshake_completed;
				
				LogInfo("doSSLHandshake: %p calling doTcpSocketCallback fd %d", sock, sock->fd);
				doTcpSocketCallback(sock);
				}
			}
	
		LogInfo("SSLHandshake %p: dropping lock for fd %d", sock, sock->fd);
		return;
		});
	}
#else
mDNSlocal void *doSSLHandshake(void *ctx)
	{
	// Warning: Touching sock without the kqueue lock!
	// We're protected because sock->handshake == handshake_in_progress
	TCPSocket *sock = (TCPSocket*)ctx;
	mDNS * const m = sock->m; // Get m now, as we may free sock if marked to be closed while we're waiting on SSLHandshake
	mStatus err = SSLHandshake(sock->tlsContext);
	
	KQueueLock(m);
	debugf("doSSLHandshake %p: got lock", sock); // Log *after* we get the lock

	if (sock->handshake == handshake_to_be_closed)
		{
		LogInfo("SSLHandshake completed after close");
		mDNSPlatformTCPCloseConnection(sock);
		}
	else
		{
		if (sock->fd != -1) KQueueSet(sock->fd, EV_ADD, EVFILT_READ, sock->kqEntry);
		else LogMsg("doSSLHandshake: sock->fd is -1");

		if (err == errSSLWouldBlock)
			sock->handshake = handshake_required;
		else
			{
			if (err)
				{
				LogMsg("SSLHandshake failed: %d%s", err, err == errSSLPeerInternalError ? " (server busy)" : "");
				SSLDisposeContext(sock->tlsContext);
				sock->tlsContext = NULL;
				}
			
			sock->err = err ? mStatus_ConnFailed : 0;
			sock->handshake = handshake_completed;
			
			debugf("doSSLHandshake: %p calling doTcpSocketCallback fd %d", sock, sock->fd);
			doTcpSocketCallback(sock);
			}
		}
	
	debugf("SSLHandshake %p: dropping lock for fd %d", sock, sock->fd);
	KQueueUnlock(m, "doSSLHandshake");
	return NULL;
	}
#endif

mDNSlocal mStatus spawnSSLHandshake(TCPSocket* sock)
	{
	debugf("spawnSSLHandshake %p: entry", sock);
	mStatus err;

	if (sock->handshake != handshake_required) LogMsg("spawnSSLHandshake: handshake status not required: %d", sock->handshake);
	sock->handshake = handshake_in_progress;
	KQueueSet(sock->fd, EV_DELETE, EVFILT_READ, sock->kqEntry);
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM

	// Dispatch it on a separate serial queue to avoid deadlocks with threads running on main queue
	dispatch_async(SSLqueue, ^{doSSLHandshake(sock);});
	err = 0;
#else
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	err = pthread_create(&sock->handshake_thread, &attr, doSSLHandshake, sock);
	pthread_attr_destroy(&attr);
	if (err)
		{
		LogMsg("Could not start SSLHandshake thread: (%d) %s", err, strerror(err));
		sock->handshake = handshake_completed;
		sock->err = err;
		KQueueSet(sock->fd, EV_ADD, EVFILT_READ, sock->kqEntry);
		}
#endif
	debugf("spawnSSLHandshake %p: done for %d", sock, sock->fd);
	return err;
	}

mDNSlocal mDNSBool IsTunnelModeDomain(const domainname *d)
	{
	static const domainname *mmc = (const domainname*) "\x7" "members" "\x3" "mac" "\x3" "com";
	const domainname *d1 = mDNSNULL;	// TLD
	const domainname *d2 = mDNSNULL;	// SLD
	const domainname *d3 = mDNSNULL;
	while (d->c[0]) { d3 = d2; d2 = d1; d1 = d; d = (const domainname*)(d->c + 1 + d->c[0]); }
	return(d3 && SameDomainName(d3, mmc));
	}

#endif /* NO_SECURITYFRAMEWORK */

mDNSlocal void tcpKQSocketCallback(__unused int fd, short filter, void *context)
	{
	TCPSocket *sock = context;
	sock->err = mStatus_NoError;

	//if (filter == EVFILT_READ ) LogMsg("myKQSocketCallBack: tcpKQSocketCallback %d is EVFILT_READ", filter);
	//if (filter == EVFILT_WRITE) LogMsg("myKQSocketCallBack: tcpKQSocketCallback %d is EVFILT_WRITE", filter);
	// EV_ONESHOT doesn't seem to work, so we add the filter with EV_ADD, and explicitly delete it here with EV_DELETE
	if (filter == EVFILT_WRITE) KQueueSet(sock->fd, EV_DELETE, EVFILT_WRITE, sock->kqEntry);

	if (sock->flags & kTCPSocketFlags_UseTLS)
		{
#ifndef NO_SECURITYFRAMEWORK
		if (!sock->setup) { sock->setup = mDNStrue; tlsSetupSock(sock, mDNSfalse); }
		
		if (sock->handshake == handshake_required) { if (spawnSSLHandshake(sock) == 0) return; }
		else if (sock->handshake == handshake_in_progress || sock->handshake == handshake_to_be_closed) return;
		else if (sock->handshake != handshake_completed)
			{
			if (!sock->err) sock->err = mStatus_UnknownErr;
			LogMsg("tcpKQSocketCallback called with unexpected SSLHandshake status: %d", sock->handshake);
			}
#else
		sock->err = mStatus_UnsupportedErr;
#endif /* NO_SECURITYFRAMEWORK */
		}
	
	doTcpSocketCallback(sock);
	}

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
mDNSexport int KQueueSet(int fd, u_short flags, short filter, KQueueEntry *const entryRef)
	{
	dispatch_queue_t queue = dispatch_get_main_queue();
	dispatch_source_t source;
	if (flags == EV_DELETE)
		{
		if (filter == EVFILT_READ)
			{
			dispatch_source_cancel(entryRef->readSource);
			dispatch_release(entryRef->readSource);
			entryRef->readSource = mDNSNULL;
			debugf("KQueueSet: source cancel for read %p, %p", entryRef->readSource, entryRef->writeSource);
			}
		else if (filter == EVFILT_WRITE)
			{
			dispatch_source_cancel(entryRef->writeSource);
			dispatch_release(entryRef->writeSource);
			entryRef->writeSource = mDNSNULL;
			debugf("KQueueSet: source cancel for write %p, %p", entryRef->readSource, entryRef->writeSource);
			}
		else
			LogMsg("KQueueSet: ERROR: Wrong filter value %d for EV_DELETE", filter);
		return 0;
		}
	if (flags != EV_ADD) LogMsg("KQueueSet: Invalid flags %d", flags);

	if (filter == EVFILT_READ)
		{
		source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
		}
	else if (filter == EVFILT_WRITE)
		{
		source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue);
		}
	else 
		{
		LogMsg("KQueueSet: ERROR: Wrong filter value %d for EV_ADD", filter);
		return -1;
		}
	if (!source) return -1;
	dispatch_source_set_event_handler(source, ^{

		mDNSs32 stime = mDNSPlatformRawTime();
		entryRef->KQcallback(fd, filter, entryRef->KQcontext);
		mDNSs32 etime = mDNSPlatformRawTime();
		if (etime - stime >= WatchDogReportingThreshold)
			LogInfo("KQEntryCallback Block: WARNING: took %dms to complete", etime - stime);

		// Trigger the event delivery to the application. Even though we trigger the
		// event completion after handling every event source, these all will hopefully
		// get merged
		TriggerEventCompletion();

		});
    dispatch_source_set_cancel_handler(source, ^{
		if (entryRef->fdClosed)
			{
			//LogMsg("CancelHandler: closing fd %d", fd);
			close(fd);
			}
		});
	dispatch_resume(source);
	if (filter == EVFILT_READ)
		entryRef->readSource = source;
	else
		entryRef->writeSource = source;
		
	return 0;
	}

mDNSexport void KQueueLock(mDNS *const m)
	{
	(void)m; //unused
	}
mDNSexport void KQueueUnlock(mDNS *const m, const char const *task)
	{
	(void)m; //unused
	(void)task; //unused
	}
#else
mDNSexport int KQueueSet(int fd, u_short flags, short filter, const KQueueEntry *const entryRef)
	{
	struct kevent new_event;
	EV_SET(&new_event, fd, filter, flags, 0, 0, (void*)entryRef);
	return (kevent(KQueueFD, &new_event, 1, NULL, 0, NULL) < 0) ? errno : 0;
	}

mDNSexport void KQueueLock(mDNS *const m)
	{
	pthread_mutex_lock(&m->p->BigMutex);
	m->p->BigMutexStartTime = mDNSPlatformRawTime();
	}

mDNSexport void KQueueUnlock(mDNS *const m, const char const *task)
	{
	mDNSs32 end = mDNSPlatformRawTime();
	(void)task;
	if (end - m->p->BigMutexStartTime >= WatchDogReportingThreshold)
		LogInfo("WARNING: %s took %dms to complete", task, end - m->p->BigMutexStartTime);

	pthread_mutex_unlock(&m->p->BigMutex);

	char wake = 1;
	if (send(m->p->WakeKQueueLoopFD, &wake, sizeof(wake), 0) == -1)
		LogMsg("ERROR: KQueueWake: send failed with error code: %d (%s)", errno, strerror(errno));
	}
#endif

mDNSexport void mDNSPlatformCloseFD(KQueueEntry *kq, int fd)
	{
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	(void)fd; //unused
	if (kq->readSource)
		{
		dispatch_source_cancel(kq->readSource);
		kq->readSource = mDNSNULL;
		}
	if (kq->writeSource)
		{	
		dispatch_source_cancel(kq->writeSource);
		kq->writeSource = mDNSNULL;
		}
	// Close happens in the cancellation handler
	debugf("mDNSPlatformCloseFD: resetting sources for %d", fd);
	kq->fdClosed = mDNStrue;
#else
	(void)kq; //unused
	close(fd);
#endif
	}

mDNSlocal mStatus SetupTCPSocket(TCPSocket *sock, u_short sa_family, mDNSIPPort *port)
	{
	KQSocketSet *cp = &sock->ss;
#ifndef NO_IPV6
    int         *s        = (sa_family == AF_INET) ? &cp->sktv4 : &cp->sktv6;
    KQueueEntry *k        = (sa_family == AF_INET) ? &cp->kqsv4 : &cp->kqsv6;
#else
    int         *s        = &cp->sktv4;
    KQueueEntry *k        = &cp->kqsv4;
#endif
	const int on = 1;  // "on" for setsockopt
	mStatus err;

    int skt = socket(sa_family, SOCK_STREAM, IPPROTO_TCP);
    if (skt < 3) { if (errno != EAFNOSUPPORT) LogMsg("SetupTCPSocket: socket error %d errno %d (%s)", skt, errno, strerror(errno)); return(skt); }
	if (sa_family == AF_INET)
		{
		// Bind it
		struct sockaddr_in addr;
		mDNSPlatformMemZero(&addr, sizeof(addr));
		addr.sin_family = AF_INET;
		addr.sin_port = port->NotAnInteger;
		err = bind(skt, (struct sockaddr*) &addr, sizeof(addr));
		if (err < 0) { LogMsg("ERROR: bind %s", strerror(errno)); return err; }
	
		// Receive interface identifiers
		err = setsockopt(skt, IPPROTO_IP, IP_RECVIF, &on, sizeof(on));
		if (err < 0) { LogMsg("setsockopt IP_RECVIF - %s", strerror(errno)); return err; }
	
		mDNSPlatformMemZero(&addr, sizeof(addr));
		socklen_t len = sizeof(addr);
		err = getsockname(skt, (struct sockaddr*) &addr, &len);
		if (err < 0) { LogMsg("getsockname - %s", strerror(errno)); return err; }
	
		port->NotAnInteger = addr.sin_port;
		}
	else
		{
		// Bind it
		struct sockaddr_in6 addr6;
		mDNSPlatformMemZero(&addr6, sizeof(addr6));
		addr6.sin6_family = AF_INET6;
		addr6.sin6_port = port->NotAnInteger;
		err = bind(skt, (struct sockaddr*) &addr6, sizeof(addr6));
		if (err < 0) { LogMsg("ERROR: bind6 %s", strerror(errno)); return err; }

		// We want to receive destination addresses and receive interface identifiers
        err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
		if (err < 0) { LogMsg("ERROR: setsockopt IPV6_RECVPKTINFO %s", strerror(errno)); return err; }

		mDNSPlatformMemZero(&addr6, sizeof(addr6));
		socklen_t len = sizeof(addr6);
		err = getsockname(skt, (struct sockaddr *) &addr6, &len);
		if (err < 0) { LogMsg("getsockname6 - %s", strerror(errno)); return err; }
	
		port->NotAnInteger = addr6.sin6_port;

		}
	*s = skt;
	k->KQcallback = tcpKQSocketCallback;
	k->KQcontext  = sock;
	k->KQtask     = "mDNSPlatformTCPSocket";
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	k->readSource = mDNSNULL;
	k->writeSource = mDNSNULL;
	k->fdClosed = mDNSfalse;
#endif
	return mStatus_NoError;
	}

mDNSexport TCPSocket *mDNSPlatformTCPSocket(mDNS *const m, TCPSocketFlags flags, mDNSIPPort *port)
	{
	mStatus err;
	(void) m;

	TCPSocket *sock = mallocL("TCPSocket/mDNSPlatformTCPSocket", sizeof(TCPSocket));
	if (!sock) { LogMsg("mDNSPlatformTCPSocket: memory allocation failure"); return(mDNSNULL); }

	mDNSPlatformMemZero(sock, sizeof(TCPSocket));

	sock->ss.m     = m;
	sock->ss.sktv4 = -1;
#ifndef NO_IPV6
	sock->ss.sktv6 = -1;
#endif
	err = SetupTCPSocket(sock, AF_INET, port);
#ifndef NO_IPV6
	if (!err)
		{
		err = SetupTCPSocket(sock, AF_INET6, port);
		if (err) { mDNSPlatformCloseFD(&sock->ss.kqsv4, sock->ss.sktv4); sock->ss.sktv4 = -1; }
		}
#endif
	if (err)
		{
		LogMsg("mDNSPlatformTCPSocket: socket error %d errno %d (%s)", sock->fd, errno, strerror(errno));
		freeL("TCPSocket/mDNSPlatformTCPSocket", sock);
		return(mDNSNULL);
		}

	sock->callback          = mDNSNULL;
	sock->flags             = flags;
	sock->context           = mDNSNULL;
	sock->setup             = mDNSfalse;
	sock->connected         = mDNSfalse;
	sock->handshake         = handshake_required;
	sock->m                 = m;
	sock->err               = mStatus_NoError;
	
	return sock;
	}

mDNSexport mStatus mDNSPlatformTCPConnect(TCPSocket *sock, const mDNSAddr *dst, mDNSOpaque16 dstport, domainname *hostname, mDNSInterfaceID InterfaceID, TCPConnectionCallback callback, void *context)
	{
	KQSocketSet *cp = &sock->ss;
#ifndef NO_IPV6
    int         *s        = (dst->type == mDNSAddrType_IPv4) ? &cp->sktv4 : &cp->sktv6;
    KQueueEntry *k        = (dst->type == mDNSAddrType_IPv4) ? &cp->kqsv4 : &cp->kqsv6;
#else
    int         *s        = &cp->sktv4;
    KQueueEntry *k        = &cp->kqsv4;
#endif
	mStatus err = mStatus_NoError;
	struct sockaddr_storage ss;

	sock->callback          = callback;
	sock->context           = context;
	sock->setup             = mDNSfalse;
	sock->connected         = mDNSfalse;
	sock->handshake         = handshake_required;
	sock->err               = mStatus_NoError;

	if (hostname) { debugf("mDNSPlatformTCPConnect: hostname %##s", hostname->c); AssignDomainName(&sock->hostname, hostname); }

	if (dst->type == mDNSAddrType_IPv4)
		{
		struct sockaddr_in *saddr = (struct sockaddr_in *)&ss;
		mDNSPlatformMemZero(saddr, sizeof(*saddr));
		saddr->sin_family      = AF_INET;
		saddr->sin_port        = dstport.NotAnInteger;
		saddr->sin_len         = sizeof(*saddr);
		saddr->sin_addr.s_addr = dst->ip.v4.NotAnInteger;
		}
	else
		{
		struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&ss;
		mDNSPlatformMemZero(saddr6, sizeof(*saddr6));
		saddr6->sin6_family      = AF_INET6;
		saddr6->sin6_port        = dstport.NotAnInteger;
		saddr6->sin6_len         = sizeof(*saddr6);
		saddr6->sin6_addr        = *(struct in6_addr *)&dst->ip.v6;
		}

	// Watch for connect complete (write is ready)
	// EV_ONESHOT doesn't seem to work, so we add the filter with EV_ADD, and explicitly delete it in tcpKQSocketCallback using EV_DELETE
	if (KQueueSet(*s, EV_ADD /* | EV_ONESHOT */, EVFILT_WRITE, k))
		{
		LogMsg("ERROR: mDNSPlatformTCPConnect - KQueueSet failed");
		return errno;
		}

	// Watch for incoming data
	if (KQueueSet(*s, EV_ADD, EVFILT_READ, k))
		{
		LogMsg("ERROR: mDNSPlatformTCPConnect - KQueueSet failed");
		return errno;
 		}

	if (fcntl(*s, F_SETFL, fcntl(*s, F_GETFL, 0) | O_NONBLOCK) < 0) // set non-blocking
		{
		LogMsg("ERROR: setsockopt O_NONBLOCK - %s", strerror(errno));
		return mStatus_UnknownErr;
		}

	// We bind to the interface and all subsequent packets including the SYN will be sent out
	// on this interface
	//
	// Note: If we are in Active Directory domain, we may try TCP (if the response can't fit in
	// UDP). mDNSInterface_Unicast indicates this case and not a valid interface.
	if (InterfaceID && InterfaceID != mDNSInterface_Unicast)
		{
		NetworkInterfaceInfoOSX *info = IfindexToInterfaceInfoOSX(&mDNSStorage, InterfaceID);
		if (dst->type == mDNSAddrType_IPv4)
			{
		#ifdef IP_BOUND_IF
			if (info) setsockopt(*s, IPPROTO_IP, IP_BOUND_IF, &info->scope_id, sizeof(info->scope_id));
			else { LogMsg("mDNSPlatformTCPConnect: Invalid interface index %p", InterfaceID); return mStatus_BadParamErr; }
		#else
			(void)InterfaceID; // Unused
			(void)info; // Unused
		#endif
			}
		else
			{
		#ifdef IPV6_BOUND_IF
			if (info) setsockopt(*s, IPPROTO_IPV6, IPV6_BOUND_IF, &info->scope_id, sizeof(info->scope_id));
			else { LogMsg("mDNSPlatformTCPConnect: Invalid interface index %p", InterfaceID); return mStatus_BadParamErr; }
		#else
			(void)InterfaceID; // Unused
			(void)info; // Unused
		#endif
			}
		}

	// mDNSPlatformReadTCP/WriteTCP (unlike the UDP counterpart) does not provide the destination address
	// from which we can infer the destination address family. Hence we need to remember that here.
	// Instead of remembering the address family, we remember the right fd.
	sock->fd = *s;
	sock->kqEntry = k;
	// initiate connection wth peer
	if (connect(*s, (struct sockaddr *)&ss, ss.ss_len) < 0)
		{
		if (errno == EINPROGRESS) return mStatus_ConnPending;
		if (errno == EHOSTUNREACH || errno == EADDRNOTAVAIL || errno == ENETDOWN)
			LogInfo("ERROR: mDNSPlatformTCPConnect - connect failed: socket %d: Error %d (%s)", sock->fd, errno, strerror(errno));
		else
			LogMsg("ERROR: mDNSPlatformTCPConnect - connect failed: socket %d: Error %d (%s) length %d", sock->fd, errno, strerror(errno), ss.ss_len);
		return mStatus_ConnFailed;
		}

	LogMsg("NOTE: mDNSPlatformTCPConnect completed synchronously");
	// kQueue should notify us, but this LogMsg is to help track down if it doesn't
	return err;
	}

// Why doesn't mDNSPlatformTCPAccept actually call accept() ?
mDNSexport TCPSocket *mDNSPlatformTCPAccept(TCPSocketFlags flags, int fd)
	{
	mStatus err = mStatus_NoError;

	TCPSocket *sock = mallocL("TCPSocket/mDNSPlatformTCPAccept", sizeof(TCPSocket));
	if (!sock) return(mDNSNULL);

	mDNSPlatformMemZero(sock, sizeof(*sock));
	sock->fd = fd;
	sock->flags = flags;

	if (flags & kTCPSocketFlags_UseTLS)
		{
#ifndef NO_SECURITYFRAMEWORK
		if (!ServerCerts) { LogMsg("ERROR: mDNSPlatformTCPAccept: unable to find TLS certificates"); err = mStatus_UnknownErr; goto exit; }

		err = tlsSetupSock(sock, mDNStrue);
		if (err) { LogMsg("ERROR: mDNSPlatformTCPAccept: tlsSetupSock failed with error code: %d", err); goto exit; }

		err = SSLSetCertificate(sock->tlsContext, ServerCerts);
		if (err) { LogMsg("ERROR: mDNSPlatformTCPAccept: SSLSetCertificate failed with error code: %d", err); goto exit; }
#else
		err = mStatus_UnsupportedErr;
#endif /* NO_SECURITYFRAMEWORK */
		}
#ifndef NO_SECURITYFRAMEWORK
exit:
#endif

	if (err) { freeL("TCPSocket/mDNSPlatformTCPAccept", sock); return(mDNSNULL); }
	return(sock);
	}

mDNSlocal void CloseSocketSet(KQSocketSet *ss)
	{
	if (ss->sktv4 != -1)
		{
		mDNSPlatformCloseFD(&ss->kqsv4,  ss->sktv4);
		ss->sktv4 = -1;
		}
#ifndef NO_IPV6
	if (ss->sktv6 != -1)
		{
		mDNSPlatformCloseFD(&ss->kqsv6,  ss->sktv6);
		ss->sktv6 = -1;
		}
#endif
	if (ss->closeFlag) *ss->closeFlag = 1;
	}

mDNSexport void mDNSPlatformTCPCloseConnection(TCPSocket *sock)
	{
	if (sock)
		{	
#ifndef NO_SECURITYFRAMEWORK
		if (sock->tlsContext)
			{
			if (sock->handshake == handshake_in_progress) // SSLHandshake thread using this sock (esp. tlsContext)
				{
				LogInfo("mDNSPlatformTCPCloseConnection: called while handshake in progress");
				// When we come back from SSLHandshake, we will notice that a close was here and
				// call this function again which will do the cleanup then.
				sock->handshake = handshake_to_be_closed;
				return;
				}

			SSLClose(sock->tlsContext);
			SSLDisposeContext(sock->tlsContext);
			sock->tlsContext = NULL;
			}
#endif /* NO_SECURITYFRAMEWORK */
			if (sock->ss.sktv4 != -1) shutdown(sock->ss.sktv4, 2);
#ifndef NO_IPV6
			if (sock->ss.sktv6 != -1) shutdown(sock->ss.sktv6, 2);
#endif
			CloseSocketSet(&sock->ss);
			sock->fd = -1;

		freeL("TCPSocket/mDNSPlatformTCPCloseConnection", sock);
		}
	}

mDNSexport long mDNSPlatformReadTCP(TCPSocket *sock, void *buf, unsigned long buflen, mDNSBool *closed)
	{
	size_t nread = 0;
	*closed = mDNSfalse;

	if (sock->flags & kTCPSocketFlags_UseTLS)
		{
#ifndef NO_SECURITYFRAMEWORK
		if (sock->handshake == handshake_required) { LogMsg("mDNSPlatformReadTCP called while handshake required"); return 0; }
		else if (sock->handshake == handshake_in_progress) return 0;
		else if (sock->handshake != handshake_completed) LogMsg("mDNSPlatformReadTCP called with unexpected SSLHandshake status: %d", sock->handshake);

		//LogMsg("Starting SSLRead %d %X", sock->fd, fcntl(sock->fd, F_GETFL, 0));
		mStatus err = SSLRead(sock->tlsContext, buf, buflen, &nread);
		//LogMsg("SSLRead returned %d (%d) nread %d buflen %d", err, errSSLWouldBlock, nread, buflen);
		if (err == errSSLClosedGraceful) { nread = 0; *closed = mDNStrue; }
		else if (err && err != errSSLWouldBlock)
			{ LogMsg("ERROR: mDNSPlatformReadTCP - SSLRead: %d", err); nread = -1; *closed = mDNStrue; }
#else
		nread = -1;
		*closed = mDNStrue;
#endif /* NO_SECURITYFRAMEWORK */
		}
	else
		{
		static int CLOSEDcount = 0;
		static int EAGAINcount = 0;
		nread = recv(sock->fd, buf, buflen, 0);

		if (nread > 0) { CLOSEDcount = 0; EAGAINcount = 0; } // On success, clear our error counters
		else if (nread == 0)
			{
			*closed = mDNStrue;
			if ((++CLOSEDcount % 1000) == 0) { LogMsg("ERROR: mDNSPlatformReadTCP - recv %d got CLOSED %d times", sock->fd, CLOSEDcount); sleep(1); }
			}
		// else nread is negative -- see what kind of error we got
		else if (errno == ECONNRESET) { nread = 0; *closed = mDNStrue; }
		else if (errno != EAGAIN) { LogMsg("ERROR: mDNSPlatformReadTCP - recv: %d (%s)", errno, strerror(errno)); nread = -1; }
		else // errno is EAGAIN (EWOULDBLOCK) -- no data available
			{
			nread = 0;
			if ((++EAGAINcount % 1000) == 0) { LogMsg("ERROR: mDNSPlatformReadTCP - recv %d got EAGAIN %d times", sock->fd, EAGAINcount); sleep(1); }
			}
		}

	return nread;
	}

mDNSexport long mDNSPlatformWriteTCP(TCPSocket *sock, const char *msg, unsigned long len)
	{
	int nsent;

	if (sock->flags & kTCPSocketFlags_UseTLS)
		{
#ifndef NO_SECURITYFRAMEWORK
		size_t	processed;
		if (sock->handshake == handshake_required) { LogMsg("mDNSPlatformWriteTCP called while handshake required"); return 0; }
		if (sock->handshake == handshake_in_progress) return 0;
		else if (sock->handshake != handshake_completed) LogMsg("mDNSPlatformWriteTCP called with unexpected SSLHandshake status: %d", sock->handshake);
		
		mStatus	err = SSLWrite(sock->tlsContext, msg, len, &processed);

		if (!err) nsent = (int) processed;
		else if (err == errSSLWouldBlock) nsent = 0;
		else { LogMsg("ERROR: mDNSPlatformWriteTCP - SSLWrite returned %d", err); nsent = -1; }
#else
		nsent = -1;
#endif /* NO_SECURITYFRAMEWORK */
		}
	else
		{
		nsent = send(sock->fd, msg, len, 0);
		if (nsent < 0)
			{
			if (errno == EAGAIN) nsent = 0;
			else { LogMsg("ERROR: mDNSPlatformWriteTCP - send %s", strerror(errno)); nsent = -1; }
			}
		}

	return nsent;
	}

mDNSexport int mDNSPlatformTCPGetFD(TCPSocket *sock)
	{
	return sock->fd;
	}

// If mDNSIPPort port is non-zero, then it's a multicast socket on the specified interface
// If mDNSIPPort port is zero, then it's a randomly assigned port number, used for sending unicast queries
mDNSlocal mStatus SetupSocket(KQSocketSet *cp, const mDNSIPPort port, u_short sa_family, mDNSIPPort *const outport)
	{
#ifndef NO_IPV6
	int         *s        = (sa_family == AF_INET) ? &cp->sktv4 : &cp->sktv6;
	KQueueEntry	*k        = (sa_family == AF_INET) ? &cp->kqsv4 : &cp->kqsv6;
#else
	int         *s        = &cp->sktv4;
	KQueueEntry	*k        = &cp->kqsv4;
#endif
	const int on = 1;
	const int twofivefive = 255;
	mStatus err = mStatus_NoError;
	char *errstr = mDNSNULL;

#ifdef NO_IPV6
	if (sa_family != AF_INET) return -1;
#endif
	
	cp->closeFlag = mDNSNULL;

	int skt = socket(sa_family, SOCK_DGRAM, IPPROTO_UDP);
	if (skt < 3) { if (errno != EAFNOSUPPORT) LogMsg("SetupSocket: socket error %d errno %d (%s)", skt, errno, strerror(errno)); return(skt); }

	// ... with a shared UDP port, if it's for multicast receiving
	if (mDNSSameIPPort(port, MulticastDNSPort) || mDNSSameIPPort(port, NATPMPAnnouncementPort)) err = setsockopt(skt, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
	if (err < 0) { errstr = "setsockopt - SO_REUSEPORT"; goto fail; }

	if (sa_family == AF_INET)
		{
		// We want to receive destination addresses
		err = setsockopt(skt, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on));
		if (err < 0) { errstr = "setsockopt - IP_RECVDSTADDR"; goto fail; }

		// We want to receive interface identifiers
		err = setsockopt(skt, IPPROTO_IP, IP_RECVIF, &on, sizeof(on));
		if (err < 0) { errstr = "setsockopt - IP_RECVIF"; goto fail; }

		// We want to receive packet TTL value so we can check it
		err = setsockopt(skt, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on));
		// We ignore errors here -- we already know Jaguar doesn't support this, but we can get by without it

		// Send unicast packets with TTL 255
		err = setsockopt(skt, IPPROTO_IP, IP_TTL, &twofivefive, sizeof(twofivefive));
		if (err < 0) { errstr = "setsockopt - IP_TTL"; goto fail; }

		// And multicast packets with TTL 255 too
		err = setsockopt(skt, IPPROTO_IP, IP_MULTICAST_TTL, &twofivefive, sizeof(twofivefive));
		if (err < 0) { errstr = "setsockopt - IP_MULTICAST_TTL"; goto fail; }

		// And start listening for packets
		struct sockaddr_in listening_sockaddr;
		listening_sockaddr.sin_family      = AF_INET;
		listening_sockaddr.sin_port        = port.NotAnInteger;		// Pass in opaque ID without any byte swapping
		listening_sockaddr.sin_addr.s_addr = mDNSSameIPPort(port, NATPMPAnnouncementPort) ? AllHosts_v4.NotAnInteger : 0;
		err = bind(skt, (struct sockaddr *) &listening_sockaddr, sizeof(listening_sockaddr));
		if (err) { errstr = "bind"; goto fail; }
		if (outport) outport->NotAnInteger = listening_sockaddr.sin_port;
		}
#ifndef NO_IPV6
	else if (sa_family == AF_INET6)
		{
		// NAT-PMP Announcements make no sense on IPv6, so bail early w/o error
		if (mDNSSameIPPort(port, NATPMPAnnouncementPort)) { if (outport) *outport = zeroIPPort; return mStatus_NoError; }
		
		// We want to receive destination addresses and receive interface identifiers
		err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
		if (err < 0) { errstr = "setsockopt - IPV6_RECVPKTINFO"; goto fail; }

		// We want to receive packet hop count value so we can check it
		err = setsockopt(skt, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on));
		if (err < 0) { errstr = "setsockopt - IPV6_RECVHOPLIMIT"; goto fail; }

		// We want to receive only IPv6 packets. Without this option we get IPv4 packets too,
		// with mapped addresses of the form 0:0:0:0:0:FFFF:xxxx:xxxx, where xxxx:xxxx is the IPv4 address
		err = setsockopt(skt, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
		if (err < 0) { errstr = "setsockopt - IPV6_V6ONLY"; goto fail; }

		// Send unicast packets with TTL 255
		err = setsockopt(skt, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &twofivefive, sizeof(twofivefive));
		if (err < 0) { errstr = "setsockopt - IPV6_UNICAST_HOPS"; goto fail; }

		// And multicast packets with TTL 255 too
		err = setsockopt(skt, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &twofivefive, sizeof(twofivefive));
		if (err < 0) { errstr = "setsockopt - IPV6_MULTICAST_HOPS"; goto fail; }

		// Want to receive our own packets
		err = setsockopt(skt, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on));
		if (err < 0) { errstr = "setsockopt - IPV6_MULTICAST_LOOP"; goto fail; }

		// And start listening for packets
		struct sockaddr_in6 listening_sockaddr6;
		mDNSPlatformMemZero(&listening_sockaddr6, sizeof(listening_sockaddr6));
		listening_sockaddr6.sin6_len         = sizeof(listening_sockaddr6);
		listening_sockaddr6.sin6_family      = AF_INET6;
		listening_sockaddr6.sin6_port        = port.NotAnInteger;		// Pass in opaque ID without any byte swapping
		listening_sockaddr6.sin6_flowinfo    = 0;
		listening_sockaddr6.sin6_addr        = in6addr_any; // Want to receive multicasts AND unicasts on this socket
		listening_sockaddr6.sin6_scope_id    = 0;
		err = bind(skt, (struct sockaddr *) &listening_sockaddr6, sizeof(listening_sockaddr6));
		if (err) { errstr = "bind"; goto fail; }
		if (outport) outport->NotAnInteger = listening_sockaddr6.sin6_port;
		}
#endif

	fcntl(skt, F_SETFL, fcntl(skt, F_GETFL, 0) | O_NONBLOCK); // set non-blocking
	fcntl(skt, F_SETFD, 1); // set close-on-exec
	*s = skt;
	k->KQcallback = myKQSocketCallBack;
	k->KQcontext  = cp;
	k->KQtask     = "UDP packet reception";
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	k->readSource = mDNSNULL;
	k->writeSource = mDNSNULL;
	k->fdClosed = mDNSfalse;
#endif
	KQueueSet(*s, EV_ADD, EVFILT_READ, k);

	return(err);

	fail:
	// For "bind" failures, only write log messages for our shared mDNS port, or for binding to zero
	if (strcmp(errstr, "bind") || mDNSSameIPPort(port, MulticastDNSPort) || mDNSIPPortIsZero(port))
		LogMsg("%s skt %d port %d error %d errno %d (%s)", errstr, skt, mDNSVal16(port), err, errno, strerror(errno));

	// If we got a "bind" failure of EADDRINUSE, inform the caller as it might need to try another random port
	if (!strcmp(errstr, "bind") && errno == EADDRINUSE)
		{
		err = EADDRINUSE;
		if (mDNSSameIPPort(port, MulticastDNSPort))
			NotifyOfElusiveBug("Setsockopt SO_REUSEPORT failed",
				"Congratulations, you've reproduced an elusive bug.\r"
				"Please contact the current assignee of <rdar://problem/3814904>.\r"
				"Alternatively, you can send email to radar-3387020@group.apple.com. (Note number is different.)\r"
				"If possible, please leave your machine undisturbed so that someone can come to investigate the problem.");
		}

	mDNSPlatformCloseFD(k, skt);
	return(err);
	}

mDNSexport UDPSocket *mDNSPlatformUDPSocket(mDNS *const m, const mDNSIPPort requestedport)
	{
	mStatus err;
	mDNSIPPort port = requestedport;
	mDNSBool randomizePort = mDNSIPPortIsZero(requestedport);
	int i = 10000; // Try at most 10000 times to get a unique random port
	UDPSocket *p = mallocL("UDPSocket", sizeof(UDPSocket));
	if (!p) { LogMsg("mDNSPlatformUDPSocket: memory exhausted"); return(mDNSNULL); }
	mDNSPlatformMemZero(p, sizeof(UDPSocket));
	p->ss.port  = zeroIPPort;
	p->ss.m     = m;
	p->ss.sktv4 = -1;
#ifndef NO_IPV6
	p->ss.sktv6 = -1;
#endif

	do
		{
		// The kernel doesn't do cryptographically strong random port allocation, so we do it ourselves here
		if (randomizePort) port = mDNSOpaque16fromIntVal(0xC000 + mDNSRandom(0x3FFF));
		err = SetupSocket(&p->ss, port, AF_INET, &p->ss.port);
#ifndef NO_IPV6
		if (!err)
			{
			err = SetupSocket(&p->ss, port, AF_INET6, &p->ss.port);
			if (err) { mDNSPlatformCloseFD(&p->ss.kqsv4, p->ss.sktv4); p->ss.sktv4 = -1; }
			}
#endif
		i--;
		} while (err == EADDRINUSE && randomizePort && i);

	if (err)
		{
		// In customer builds we don't want to log failures with port 5351, because this is a known issue
		// of failing to bind to this port when Internet Sharing has already bound to it
		// We also don't want to log about port 5350, due to a known bug when some other
		// process is bound to it.
		if (mDNSSameIPPort(requestedport, NATPMPPort) || mDNSSameIPPort(requestedport, NATPMPAnnouncementPort))
			LogInfo("mDNSPlatformUDPSocket: SetupSocket %d failed error %d errno %d (%s)", mDNSVal16(requestedport), err, errno, strerror(errno));
		else LogMsg("mDNSPlatformUDPSocket: SetupSocket %d failed error %d errno %d (%s)", mDNSVal16(requestedport), err, errno, strerror(errno));
		freeL("UDPSocket", p);
		return(mDNSNULL);
		}
	return(p);
	}

mDNSexport void mDNSPlatformUDPClose(UDPSocket *sock)
	{
	CloseSocketSet(&sock->ss);
	freeL("UDPSocket", sock);
	}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - BPF Raw packet sending/receiving
#endif

#if APPLE_OSX_mDNSResponder

mDNSexport void mDNSPlatformSendRawPacket(const void *const msg, const mDNSu8 *const end, mDNSInterfaceID InterfaceID)
	{
	if (!InterfaceID) { LogMsg("mDNSPlatformSendRawPacket: No InterfaceID specified"); return; }
	NetworkInterfaceInfoOSX *info;

	info = IfindexToInterfaceInfoOSX(&mDNSStorage, InterfaceID);
	if (info == NULL)
		{
		LogMsg("mDNSPlatformSendUDP: Invalid interface index %p", InterfaceID);
		return;
		}
	if (info->BPF_fd < 0)
		LogMsg("mDNSPlatformSendRawPacket: %s BPF_fd %d not ready", info->ifinfo.ifname, info->BPF_fd);
	else
		{
		//LogMsg("mDNSPlatformSendRawPacket %d bytes on %s", end - (mDNSu8 *)msg, info->ifinfo.ifname);
		if (write(info->BPF_fd, msg, end - (mDNSu8 *)msg) < 0)
			LogMsg("mDNSPlatformSendRawPacket: BPF write(%d) failed %d (%s)", info->BPF_fd, errno, strerror(errno));
		}
	}

mDNSexport void mDNSPlatformSetLocalAddressCacheEntry(mDNS *const m, const mDNSAddr *const tpa, const mDNSEthAddr *const tha, mDNSInterfaceID InterfaceID)
	{
	if (!InterfaceID) { LogMsg("mDNSPlatformSetLocalAddressCacheEntry: No InterfaceID specified"); return; }
	NetworkInterfaceInfoOSX *info;
	info = IfindexToInterfaceInfoOSX(m, InterfaceID);
	if (info == NULL) { LogMsg("mDNSPlatformSetLocalAddressCacheEntry: Invalid interface index %p", InterfaceID); return; }
	// Manually inject an entry into our local ARP cache.
	// (We can't do this by sending an ARP broadcast, because the kernel only pays attention to incoming ARP packets, not outgoing.)
	if (!mDNS_AddressIsLocalSubnet(m, InterfaceID, tpa))
		LogSPS("Don't need address cache entry for %s %#a %.6a",            info->ifinfo.ifname, tpa, tha);
	else
		{
		int result = mDNSSetLocalAddressCacheEntry(info->scope_id, tpa->type, tpa->ip.v6.b, tha->b);
		if (result) LogMsg("Set local address cache entry for %s %#a %.6a failed: %d", info->ifinfo.ifname, tpa, tha, result);
		else        LogSPS("Set local address cache entry for %s %#a %.6a",            info->ifinfo.ifname, tpa, tha);
		}
	}

mDNSlocal void CloseBPF(NetworkInterfaceInfoOSX *const i)
	{
	LogSPS("%s closing BPF fd %d", i->ifinfo.ifname, i->BPF_fd);
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	// close will happen in the cancel handler
	dispatch_source_cancel(i->BPF_source);
#else

	// Note: MUST NOT close() the underlying native BSD sockets.
	// CFSocketInvalidate() will do that for us, in its own good time, which may not necessarily be immediately, because
	// it first has to unhook the sockets from its select() call on its other thread, before it can safely close them.
	CFRunLoopRemoveSource(i->m->p->CFRunLoop, i->BPF_rls, kCFRunLoopDefaultMode);
	CFRelease(i->BPF_rls);
	CFSocketInvalidate(i->BPF_cfs);
	CFRelease(i->BPF_cfs);
#endif
	i->BPF_fd = -1;
	if (i->BPF_mcfd >= 0) { close(i->BPF_mcfd); i->BPF_mcfd = -1; }
	}

mDNSlocal void bpf_callback_common(NetworkInterfaceInfoOSX *info)
	{
	KQueueLock(info->m);

	// Now we've got the lock, make sure the kqueue thread didn't close the fd out from under us (will not be a problem once the OS X
	// kernel has a mechanism for dispatching all events to a single thread, but for now we have to guard against this race condition).
	if (info->BPF_fd < 0) goto exit;

	ssize_t n = read(info->BPF_fd, &info->m->imsg, info->BPF_len);
	const mDNSu8 *ptr = (const mDNSu8 *)&info->m->imsg;
	const mDNSu8 *end = (const mDNSu8 *)&info->m->imsg + n;
	debugf("%3d: bpf_callback got %d bytes on %s", info->BPF_fd, n, info->ifinfo.ifname);

	if (n<0)
		{
		LogMsg("Closing %s BPF fd %d due to error %d (%s)", info->ifinfo.ifname, info->BPF_fd, errno, strerror(errno));
		CloseBPF(info);
		goto exit;
		}

	while (ptr < end)
		{
		const struct bpf_hdr *const bh = (const struct bpf_hdr *)ptr;
		debugf("%3d: bpf_callback ptr %p bh_hdrlen %d data %p bh_caplen %4d bh_datalen %4d next %p remaining %4d",
			info->BPF_fd, ptr, bh->bh_hdrlen, ptr + bh->bh_hdrlen, bh->bh_caplen, bh->bh_datalen,
			ptr + BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen), end - (ptr + BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen)));
		// Note that BPF guarantees that the NETWORK LAYER header will be word aligned, not the link-layer header.
		// Given that An Ethernet header is 14 bytes, this means that if the network layer header (e.g. IP header,
		// ARP message, etc.) is 4-byte aligned, then necessarily the Ethernet header will be NOT be 4-byte aligned.
		mDNSCoreReceiveRawPacket(info->m, ptr + bh->bh_hdrlen, ptr + bh->bh_hdrlen + bh->bh_caplen, info->ifinfo.InterfaceID);
		ptr += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
		}
exit:
	KQueueUnlock(info->m, "bpf_callback");
	}
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
mDNSlocal void bpf_callback_dispatch(NetworkInterfaceInfoOSX *const info)
	{
	bpf_callback_common(info);
	}
#else
mDNSlocal void bpf_callback(const CFSocketRef cfs, const CFSocketCallBackType CallBackType, const CFDataRef address, const void *const data, void *const context)
	{
	(void)cfs;
	(void)CallBackType;
	(void)address;
	(void)data;
	bpf_callback_common((NetworkInterfaceInfoOSX *)context);
	}
#endif

#define BPF_SetOffset(from, cond, to) (from)->cond = (to) - 1 - (from)

mDNSlocal int CountProxyTargets(mDNS *const m, NetworkInterfaceInfoOSX *x, int *p4, int *p6)
	{
	int numv4 = 0, numv6 = 0;
	AuthRecord *rr;

	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.InterfaceID == x->ifinfo.InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv4)
			{
			if (p4) LogSPS("CountProxyTargets: fd %d %-7s IP%2d %.4a", x->BPF_fd, x->ifinfo.ifname, numv4, &rr->AddressProxy.ip.v4);
			numv4++;
			}

	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.InterfaceID == x->ifinfo.InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv6)
			{
			if (p6) LogSPS("CountProxyTargets: fd %d %-7s IP%2d %.16a", x->BPF_fd, x->ifinfo.ifname, numv6, &rr->AddressProxy.ip.v6);
			numv6++;
			}

	if (p4) *p4 = numv4;
	if (p6) *p6 = numv6;
	return(numv4 + numv6);
	}

mDNSexport void mDNSPlatformUpdateProxyList(mDNS *const m, const mDNSInterfaceID InterfaceID)
	{
	NetworkInterfaceInfoOSX *x;

	// Note: We can't use IfIndexToInterfaceInfoOSX because that looks for Registered also.
	for (x = m->p->InterfaceList; x; x = x->next) if (x->ifinfo.InterfaceID == InterfaceID) break;

	if (!x) { LogMsg("mDNSPlatformUpdateProxyList: ERROR InterfaceID %p not found", InterfaceID); return; }

	#define MAX_BPF_ADDRS 250
	int numv4 = 0, numv6 = 0;

	if (CountProxyTargets(m, x, &numv4, &numv6) > MAX_BPF_ADDRS)
		{
		LogMsg("mDNSPlatformUpdateProxyList: ERROR Too many address proxy records v4 %d v6 %d", numv4, numv6);
		if (numv4 > MAX_BPF_ADDRS) numv4 = MAX_BPF_ADDRS;
		numv6 = MAX_BPF_ADDRS - numv4;
		}

	LogSPS("mDNSPlatformUpdateProxyList: fd %d %-7s MAC  %.6a %d v4 %d v6", x->BPF_fd, x->ifinfo.ifname, &x->ifinfo.MAC, numv4, numv6);

	// Caution: This is a static structure, so we need to be careful that any modifications we make to it
	// are done in such a way that they work correctly when mDNSPlatformUpdateProxyList is called multiple times
	static struct bpf_insn filter[17 + MAX_BPF_ADDRS] =
		{
		BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, 12),				// 0 Read Ethertype (bytes 12,13)

		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0806, 0, 1),		// 1 If Ethertype == ARP goto next, else 3
		BPF_STMT(BPF_RET + BPF_K,             42),				// 2 Return 42-byte ARP

		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0800, 4, 0),		// 3 If Ethertype == IPv4 goto 8 (IPv4 address list check) else next

		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x86DD, 0, 9),		// 4 If Ethertype == IPv6 goto next, else exit
		BPF_STMT(BPF_LD  + BPF_H   + BPF_ABS, 20),				// 5 Read Protocol and Hop Limit (bytes 20,21)
		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x3AFF, 0, 9),		// 6 If (Prot,TTL) == (3A,FF) goto next, else IPv6 address list check
		BPF_STMT(BPF_RET + BPF_K,             86),				// 7 Return 86-byte ND

		// Is IPv4 packet; check if it's addressed to any IPv4 address we're proxying for
		BPF_STMT(BPF_LD  + BPF_W   + BPF_ABS, 30),				// 8 Read IPv4 Dst (bytes 30,31,32,33)
		};

	struct bpf_insn *pc   = &filter[9];
	struct bpf_insn *chk6 = pc   + numv4 + 1;	// numv4 address checks, plus a "return 0"
	struct bpf_insn *fail = chk6 + 1 + numv6;	// Get v6 Dst LSW, plus numv6 address checks
	struct bpf_insn *ret4 = fail + 1;
	struct bpf_insn *ret6 = ret4 + 4;

	static const struct bpf_insn rf  = BPF_STMT(BPF_RET + BPF_K, 0);				// No match: Return nothing

	static const struct bpf_insn g6  = BPF_STMT(BPF_LD  + BPF_W   + BPF_ABS, 50);	// Read IPv6 Dst LSW (bytes 50,51,52,53)

	static const struct bpf_insn r4a = BPF_STMT(BPF_LDX + BPF_B   + BPF_MSH, 14);	// Get IP Header length (normally 20)
	static const struct bpf_insn r4b = BPF_STMT(BPF_LD  + BPF_IMM,           54);	// A = 54 (14-byte Ethernet plus 20-byte TCP + 20 bytes spare)
	static const struct bpf_insn r4c = BPF_STMT(BPF_ALU + BPF_ADD + BPF_X,    0);	// A += IP Header length
	static const struct bpf_insn r4d = BPF_STMT(BPF_RET + BPF_A, 0);				// Success: Return Ethernet + IP + TCP + 20 bytes spare (normally 74)

	static const struct bpf_insn r6a = BPF_STMT(BPF_RET + BPF_K, 94);				// Success: Return Eth + IPv6 + TCP + 20 bytes spare

	BPF_SetOffset(&filter[4], jf, fail);	// If Ethertype not ARP, IPv4, or IPv6, fail
	BPF_SetOffset(&filter[6], jf, chk6);	// If IPv6 but not ICMPv6, go to IPv6 address list check

	// BPF Byte-Order Note
	// The BPF API designers apparently thought that programmers would not be smart enough to use htons
	// and htonl correctly to convert numeric values to network byte order on little-endian machines,
	// so instead they chose to make the API implicitly byte-swap *ALL* values, even literal byte strings
	// that shouldn't be byte-swapped, like ASCII text, Ethernet addresses, IP addresses, etc.
	// As a result, if we put Ethernet addresses and IP addresses in the right byte order, the BPF API
	// will byte-swap and make them backwards, and then our filter won't work. So, we have to arrange
	// that on little-endian machines we deliberately put addresses in memory with the bytes backwards,
	// so that when the BPF API goes through and swaps them all, they end up back as they should be.
	// In summary, if we byte-swap all the non-numeric fields that shouldn't be swapped, and we *don't*
	// swap any of the numeric values that *should* be byte-swapped, then the filter will work correctly.

	// IPSEC capture size notes:
	//  8 bytes UDP header
	//  4 bytes Non-ESP Marker
	// 28 bytes IKE Header
	// --
	// 40 Total. Capturing TCP Header + 20 gets us enough bytes to receive the IKE Header in a UDP-encapsulated IKE packet.

	AuthRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.InterfaceID == InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv4)
			{
			mDNSv4Addr a = rr->AddressProxy.ip.v4;
			pc->code = BPF_JMP + BPF_JEQ + BPF_K;
			BPF_SetOffset(pc, jt, ret4);
			pc->jf   = 0;
			pc->k    = (bpf_u_int32)a.b[0] << 24 | (bpf_u_int32)a.b[1] << 16 | (bpf_u_int32)a.b[2] << 8 | (bpf_u_int32)a.b[3];
			pc++;
			}
	*pc++ = rf;

	if (pc != chk6) LogMsg("mDNSPlatformUpdateProxyList: pc %p != chk6 %p", pc, chk6);
	*pc++ = g6;	// chk6 points here

	// First cancel any previous ND group memberships we had, then create a fresh socket
	if (x->BPF_mcfd >= 0) close(x->BPF_mcfd);
	x->BPF_mcfd = socket(AF_INET6, SOCK_DGRAM, 0);

	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.InterfaceID == InterfaceID && rr->AddressProxy.type == mDNSAddrType_IPv6)
			{
			const mDNSv6Addr *const a = &rr->AddressProxy.ip.v6;
			pc->code = BPF_JMP + BPF_JEQ + BPF_K;
			BPF_SetOffset(pc, jt, ret6);
			pc->jf   = 0;
			pc->k    = (bpf_u_int32)a->b[0x0C] << 24 | (bpf_u_int32)a->b[0x0D] << 16 | (bpf_u_int32)a->b[0x0E] << 8 | (bpf_u_int32)a->b[0x0F];
			pc++;

			struct ipv6_mreq i6mr;
			i6mr.ipv6mr_interface = x->scope_id;
			i6mr.ipv6mr_multiaddr = *(const struct in6_addr*)&NDP_prefix;
			i6mr.ipv6mr_multiaddr.s6_addr[0xD] = a->b[0xD];
			i6mr.ipv6mr_multiaddr.s6_addr[0xE] = a->b[0xE];
			i6mr.ipv6mr_multiaddr.s6_addr[0xF] = a->b[0xF];

			// Do precautionary IPV6_LEAVE_GROUP first, necessary to clear stale kernel state
			mStatus err = setsockopt(x->BPF_mcfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr));
			if (err < 0 && (errno != EADDRNOTAVAIL))
				LogMsg("mDNSPlatformUpdateProxyList: IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);

			err = setsockopt(x->BPF_mcfd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &i6mr, sizeof(i6mr));
			if (err < 0 && (errno != EADDRINUSE))	// Joining same group twice can give "Address already in use" error -- no need to report that
				LogMsg("mDNSPlatformUpdateProxyList: IPV6_JOIN_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
			
			LogSPS("Joined IPv6 ND multicast group %.16a for %.16a", &i6mr.ipv6mr_multiaddr, a);
			}

	if (pc != fail) LogMsg("mDNSPlatformUpdateProxyList: pc %p != fail %p", pc, fail);
	*pc++ = rf;		// fail points here

	if (pc != ret4) LogMsg("mDNSPlatformUpdateProxyList: pc %p != ret4 %p", pc, ret4);
	*pc++ = r4a;	// ret4 points here
	*pc++ = r4b;
	*pc++ = r4c;
	*pc++ = r4d;

	if (pc != ret6) LogMsg("mDNSPlatformUpdateProxyList: pc %p != ret6 %p", pc, ret6);
	*pc++ = r6a;	// ret6 points here

	struct bpf_program prog = { pc - filter, filter };

#if 0
	// For debugging BPF filter program
	unsigned int q;
	for (q=0; q<prog.bf_len; q++)
		LogSPS("mDNSPlatformUpdateProxyList: %2d { 0x%02x, %d, %d, 0x%08x },", q, prog.bf_insns[q].code, prog.bf_insns[q].jt, prog.bf_insns[q].jf, prog.bf_insns[q].k);
#endif

	if (!numv4 && !numv6)
		{
		LogSPS("mDNSPlatformUpdateProxyList: No need for filter");
		if (m->timenow == 0) LogMsg("mDNSPlatformUpdateProxyList: m->timenow == 0");
		// Schedule check to see if we can close this BPF_fd now
		if (!m->p->NetworkChanged) m->p->NetworkChanged = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 2);
		// prog.bf_len = 0; This seems to panic the kernel
		if (x->BPF_fd < 0) return;		// If we've already closed our BPF_fd, no need to generate an error message below
		}

	if (ioctl(x->BPF_fd, BIOCSETF, &prog) < 0) LogMsg("mDNSPlatformUpdateProxyList: BIOCSETF(%d) failed %d (%s)", prog.bf_len, errno, strerror(errno));
	else LogSPS("mDNSPlatformUpdateProxyList: BIOCSETF(%d) successful", prog.bf_len);
	}

mDNSexport void mDNSPlatformReceiveBPF_fd(mDNS *const m, int fd)
	{
	mDNS_Lock(m);
	
	NetworkInterfaceInfoOSX *i;
	for (i = m->p->InterfaceList; i; i = i->next) if (i->BPF_fd == -2) break;
	if (!i) { LogSPS("mDNSPlatformReceiveBPF_fd: No Interfaces awaiting BPF fd %d; closing", fd); close(fd); }
	else
		{
		LogSPS("%s using   BPF fd %d", i->ifinfo.ifname, fd);
	
		struct bpf_version v;
		if (ioctl(fd, BIOCVERSION, &v) < 0)
			LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCVERSION failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno));
		else if (BPF_MAJOR_VERSION != v.bv_major || BPF_MINOR_VERSION != v.bv_minor)
			LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCVERSION header %d.%d kernel %d.%d",
				fd, i->ifinfo.ifname, BPF_MAJOR_VERSION, BPF_MINOR_VERSION, v.bv_major, v.bv_minor);
	
		if (ioctl(fd, BIOCGBLEN, &i->BPF_len) < 0)
			LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCGBLEN failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno));
	
		if (i->BPF_len > sizeof(m->imsg))
			{
			i->BPF_len = sizeof(m->imsg);
			if (ioctl(fd, BIOCSBLEN, &i->BPF_len) < 0)
				LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCSBLEN failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno));
			else 
				LogSPS("mDNSPlatformReceiveBPF_fd: %d %s BIOCSBLEN %d", fd, i->ifinfo.ifname, i->BPF_len);
			}
	
		static const u_int opt_one = 1;
		if (ioctl(fd, BIOCIMMEDIATE, &opt_one) < 0)
			LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCIMMEDIATE failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno));
	
		//if (ioctl(fd, BIOCPROMISC, &opt_one) < 0)
		//	LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCPROMISC failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno));
	
		//if (ioctl(fd, BIOCSHDRCMPLT, &opt_one) < 0)
		//	LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCSHDRCMPLT failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno));
	
		struct ifreq ifr;
		mDNSPlatformMemZero(&ifr, sizeof(ifr));
		strlcpy(ifr.ifr_name, i->ifinfo.ifname, sizeof(ifr.ifr_name));
		if (ioctl(fd, BIOCSETIF, &ifr) < 0)
			{ LogMsg("mDNSPlatformReceiveBPF_fd: %d %s BIOCSETIF failed %d (%s)", fd, i->ifinfo.ifname, errno, strerror(errno)); i->BPF_fd = -3; }
		else
			{
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
			i->BPF_fd  = fd;
			i->BPF_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatch_get_main_queue());
			if (!i->BPF_source) {LogMsg("mDNSPlatformReceiveBPF_fd: dispatch source create failed");return;}
			dispatch_source_set_event_handler(i->BPF_source, ^{bpf_callback_dispatch(i);});
			dispatch_source_set_cancel_handler(i->BPF_source, ^{close(fd);});
			dispatch_resume(i->BPF_source);
#else
			CFSocketContext myCFSocketContext = { 0, i, NULL, NULL, NULL };
			i->BPF_fd  = fd;
			i->BPF_cfs = CFSocketCreateWithNative(kCFAllocatorDefault, fd, kCFSocketReadCallBack, bpf_callback, &myCFSocketContext);
			i->BPF_rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, i->BPF_cfs, 0);
			CFRunLoopAddSource(i->m->p->CFRunLoop, i->BPF_rls, kCFRunLoopDefaultMode);
#endif
			mDNSPlatformUpdateProxyList(m, i->ifinfo.InterfaceID);
			}
		}

	mDNS_Unlock(m);
	}

#endif // APPLE_OSX_mDNSResponder

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Key Management
#endif

#ifndef NO_SECURITYFRAMEWORK
mDNSlocal CFArrayRef GetCertChain(SecIdentityRef identity)
	{
	CFMutableArrayRef certChain = NULL;
	if (!identity) { LogMsg("getCertChain: identity is NULL"); return(NULL); }
	SecCertificateRef cert;
	OSStatus err = SecIdentityCopyCertificate(identity, &cert);
	if (err || !cert) LogMsg("getCertChain: SecIdentityCopyCertificate() returned %d", (int) err);
	else
		{
		SecPolicySearchRef searchRef;
		err = SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_X509_BASIC, NULL, &searchRef);
		if (err || !searchRef) LogMsg("getCertChain: SecPolicySearchCreate() returned %d", (int) err);
		else
			{
			SecPolicyRef policy;
			err = SecPolicySearchCopyNext(searchRef, &policy);
			if (err || !policy) LogMsg("getCertChain: SecPolicySearchCopyNext() returned %d", (int) err);
			else
				{
				CFArrayRef wrappedCert = CFArrayCreate(NULL, (const void**) &cert, 1, &kCFTypeArrayCallBacks);
				if (!wrappedCert) LogMsg("getCertChain: wrappedCert is NULL");
				else
					{
					SecTrustRef trust;
					err = SecTrustCreateWithCertificates(wrappedCert, policy, &trust);
					if (err || !trust) LogMsg("getCertChain: SecTrustCreateWithCertificates() returned %d", (int) err);
					else
						{
						err = SecTrustEvaluate(trust, NULL);
						if (err) LogMsg("getCertChain: SecTrustEvaluate() returned %d", (int) err);
						else
							{
							CFArrayRef rawCertChain;
							CSSM_TP_APPLE_EVIDENCE_INFO *statusChain = NULL;
							err = SecTrustGetResult(trust, NULL, &rawCertChain, &statusChain);
							if (err || !rawCertChain || !statusChain) LogMsg("getCertChain: SecTrustGetResult() returned %d", (int) err);
							else
								{
								certChain = CFArrayCreateMutableCopy(NULL, 0, rawCertChain);
								if (!certChain) LogMsg("getCertChain: certChain is NULL");
								else
									{
									// Replace the SecCertificateRef at certChain[0] with a SecIdentityRef per documentation for SSLSetCertificate:
									// <http://devworld.apple.com/documentation/Security/Reference/secureTransportRef/index.html>
									CFArraySetValueAtIndex(certChain, 0, identity);
									// Remove root from cert chain, but keep any and all intermediate certificates that have been signed by the root certificate
									if (CFArrayGetCount(certChain) > 1) CFArrayRemoveValueAtIndex(certChain, CFArrayGetCount(certChain) - 1);
									}
								CFRelease(rawCertChain);
								// Do not free statusChain:
								// <http://developer.apple.com/documentation/Security/Reference/certifkeytrustservices/Reference/reference.html> says:
								// certChain: Call the CFRelease function to release this object when you are finished with it.
								// statusChain: Do not attempt to free this pointer; it remains valid until the trust management object is released...
								}
							}
						CFRelease(trust);
						}
					CFRelease(wrappedCert);
					}
				CFRelease(policy);
				}
			CFRelease(searchRef);
			}
		CFRelease(cert);
		}
	return certChain;
	}
#endif /* NO_SECURITYFRAMEWORK */

mDNSexport mStatus mDNSPlatformTLSSetupCerts(void)
	{
#ifdef NO_SECURITYFRAMEWORK
	return mStatus_UnsupportedErr;
#else
	SecIdentityRef			identity = nil;
	SecIdentitySearchRef	srchRef = nil;
	OSStatus				err;

	// search for "any" identity matching specified key use
	// In this app, we expect there to be exactly one
	err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_DECRYPT, &srchRef);
	if (err) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: SecIdentitySearchCreate returned %d", (int) err); return err; }

	err = SecIdentitySearchCopyNext(srchRef, &identity);
	if (err) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: SecIdentitySearchCopyNext returned %d", (int) err); return err; }

	if (CFGetTypeID(identity) != SecIdentityGetTypeID())
		{ LogMsg("ERROR: mDNSPlatformTLSSetupCerts: SecIdentitySearchCopyNext CFTypeID failure"); return mStatus_UnknownErr; }

	// Found one. Call getCertChain to create the correct certificate chain.
	ServerCerts = GetCertChain(identity);
	if (ServerCerts == nil) { LogMsg("ERROR: mDNSPlatformTLSSetupCerts: getCertChain error"); return mStatus_UnknownErr; }

	return mStatus_NoError;
#endif /* NO_SECURITYFRAMEWORK */
	}

mDNSexport  void  mDNSPlatformTLSTearDownCerts(void)
	{
#ifndef NO_SECURITYFRAMEWORK
	if (ServerCerts) { CFRelease(ServerCerts); ServerCerts = NULL; }
#endif /* NO_SECURITYFRAMEWORK */
	}

// This gets the text of the field currently labelled "Computer Name" in the Sharing Prefs Control Panel
mDNSlocal void GetUserSpecifiedFriendlyComputerName(domainlabel *const namelabel)
	{
	CFStringEncoding encoding = kCFStringEncodingUTF8;
	CFStringRef cfs = SCDynamicStoreCopyComputerName(NULL, &encoding);
	if (cfs)
		{
		CFStringGetPascalString(cfs, namelabel->c, sizeof(*namelabel), kCFStringEncodingUTF8);
		CFRelease(cfs);
		}
	}

// This gets the text of the field currently labelled "Local Hostname" in the Sharing Prefs Control Panel
mDNSlocal void GetUserSpecifiedLocalHostName(domainlabel *const namelabel)
	{
	CFStringRef cfs = SCDynamicStoreCopyLocalHostName(NULL);
	if (cfs)
		{
		CFStringGetPascalString(cfs, namelabel->c, sizeof(*namelabel), kCFStringEncodingUTF8);
		CFRelease(cfs);
		}
	}

mDNSexport mDNSBool DictionaryIsEnabled(CFDictionaryRef dict)
	{
	mDNSs32 val;
	CFNumberRef state = (CFNumberRef)CFDictionaryGetValue(dict, CFSTR("Enabled"));
	if (!state) return mDNSfalse;
	if (!CFNumberGetValue(state, kCFNumberSInt32Type, &val))
		{ LogMsg("ERROR: DictionaryIsEnabled - CFNumberGetValue"); return mDNSfalse; }
	return val ? mDNStrue : mDNSfalse;
	}

mDNSlocal mStatus SetupAddr(mDNSAddr *ip, const struct sockaddr *const sa)
	{
	if (!sa) { LogMsg("SetupAddr ERROR: NULL sockaddr"); return(mStatus_Invalid); }

	if (sa->sa_family == AF_INET)
		{
		struct sockaddr_in *ifa_addr = (struct sockaddr_in *)sa;
		ip->type = mDNSAddrType_IPv4;
		ip->ip.v4.NotAnInteger = ifa_addr->sin_addr.s_addr;
		return(mStatus_NoError);
		}

	if (sa->sa_family == AF_INET6)
		{
		struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6 *)sa;
		// Inside the BSD kernel they use a hack where they stuff the sin6->sin6_scope_id
		// value into the second word of the IPv6 link-local address, so they can just
		// pass around IPv6 address structures instead of full sockaddr_in6 structures.
		// Those hacked IPv6 addresses aren't supposed to escape the kernel in that form, but they do.
		// To work around this we always whack the second word of any IPv6 link-local address back to zero.
		if (IN6_IS_ADDR_LINKLOCAL(&ifa_addr->sin6_addr)) ifa_addr->sin6_addr.__u6_addr.__u6_addr16[1] = 0;
		ip->type = mDNSAddrType_IPv6;
		ip->ip.v6 = *(mDNSv6Addr*)&ifa_addr->sin6_addr;
		return(mStatus_NoError);
		}

	LogMsg("SetupAddr invalid sa_family %d", sa->sa_family);
	return(mStatus_Invalid);
	}

mDNSlocal mDNSEthAddr GetBSSID(char *ifa_name)
	{
	mDNSEthAddr eth = zeroEthAddr;
	SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:GetBSSID"), NULL, NULL);
	if (!store)
		LogMsg("GetBSSID: SCDynamicStoreCreate failed: %s", SCErrorString(SCError()));
	else
		{
		CFStringRef entityname = CFStringCreateWithFormat(NULL, NULL, CFSTR("State:/Network/Interface/%s/AirPort"), ifa_name);
		if (entityname)
			{
			CFDictionaryRef dict = SCDynamicStoreCopyValue(store, entityname);
			if (dict)
				{
				CFRange range = { 0, 6 };		// Offset, length
				CFDataRef data = CFDictionaryGetValue(dict, CFSTR("BSSID"));
				if (data && CFDataGetLength(data) == 6) CFDataGetBytes(data, range, eth.b);
				CFRelease(dict);
				}
			CFRelease(entityname);
			}
		CFRelease(store);
		}
	return(eth);
	}

mDNSlocal int GetMAC(mDNSEthAddr *eth, u_short ifindex)
	{
	struct ifaddrs *ifa;
	for (ifa = myGetIfAddrs(0); ifa; ifa = ifa->ifa_next)
		if (ifa->ifa_addr->sa_family == AF_LINK)
			{
			const struct sockaddr_dl *const sdl = (const struct sockaddr_dl *)ifa->ifa_addr;
			if (sdl->sdl_index == ifindex)
				{ mDNSPlatformMemCopy(eth->b, sdl->sdl_data + sdl->sdl_nlen, 6); return 0; }
			}
	*eth = zeroEthAddr;
	return -1;
	}

#ifndef SIOCGIFWAKEFLAGS
#define SIOCGIFWAKEFLAGS _IOWR('i', 136, struct ifreq) /* get interface wake property flags */
#endif

#ifndef IF_WAKE_ON_MAGIC_PACKET
#define IF_WAKE_ON_MAGIC_PACKET 0x01
#endif

#ifndef ifr_wake_flags
#define ifr_wake_flags ifr_ifru.ifru_intval
#endif

mDNSlocal mDNSBool NetWakeInterface(NetworkInterfaceInfoOSX *i)
	{
	if (!MulticastInterface(i)     ) return(mDNSfalse);	// We only use Sleep Proxy Service on multicast-capable interfaces
	if (i->ifa_flags & IFF_LOOPBACK) return(mDNSfalse);	// except loopback

	int s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) { LogMsg("NetWakeInterface socket failed %s error %d errno %d (%s)", i->ifinfo.ifname, s, errno, strerror(errno)); return(mDNSfalse); }

	struct ifreq ifr;
	strlcpy(ifr.ifr_name, i->ifinfo.ifname, sizeof(ifr.ifr_name));
	if (ioctl(s, SIOCGIFWAKEFLAGS, &ifr) < 0)
		{
		// For some strange reason, in /usr/include/sys/errno.h, EOPNOTSUPP is defined to be
		// 102 when compiling kernel code, and 45 when compiling user-level code. Since this
		// error code is being returned from the kernel, we need to use the kernel version.
		#define KERNEL_EOPNOTSUPP 102
		if (errno != KERNEL_EOPNOTSUPP)	// "Operation not supported on socket", the expected result on Leopard and earlier
			LogMsg("NetWakeInterface SIOCGIFWAKEFLAGS %s errno %d (%s)", i->ifinfo.ifname, errno, strerror(errno));
		// If on Leopard or earlier, we get EOPNOTSUPP, so in that case
		// we enable WOL if this interface is not AirPort and "Wake for Network access" is turned on.
		ifr.ifr_wake_flags = (errno == KERNEL_EOPNOTSUPP && !(i)->BSSID.l[0] && i->m->SystemWakeOnLANEnabled) ? IF_WAKE_ON_MAGIC_PACKET : 0;
		}

	close(s);

	// ifr.ifr_wake_flags = IF_WAKE_ON_MAGIC_PACKET;	// For testing with MacBook Air, using a USB dongle that doesn't actually support Wake-On-LAN

	LogSPS("%-6s %#-14a %s WOMP", i->ifinfo.ifname, &i->ifinfo.ip, (ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) ? "supports" : "no");

	return((ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) != 0);
	}

// Returns pointer to newly created NetworkInterfaceInfoOSX object, or
// pointer to already-existing NetworkInterfaceInfoOSX object found in list, or
// may return NULL if out of memory (unlikely) or parameters are invalid for some reason
// (e.g. sa_family not AF_INET or AF_INET6)
mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(mDNS *const m, struct ifaddrs *ifa, mDNSs32 utc)
	{
	mDNSu32 scope_id  = if_nametoindex(ifa->ifa_name);
	mDNSEthAddr bssid = GetBSSID(ifa->ifa_name);

	mDNSAddr ip, mask;
	if (SetupAddr(&ip,   ifa->ifa_addr   ) != mStatus_NoError) return(NULL);
	if (SetupAddr(&mask, ifa->ifa_netmask) != mStatus_NoError) return(NULL);

	NetworkInterfaceInfoOSX **p;
	for (p = &m->p->InterfaceList; *p; p = &(*p)->next)
		if (scope_id == (*p)->scope_id &&
			mDNSSameAddress(&ip, &(*p)->ifinfo.ip) &&
			mDNSSameEthAddress(&bssid, &(*p)->BSSID))
			{
			debugf("AddInterfaceToList: Found existing interface %lu %.6a with address %#a at %p, ifname before %s, after %s", scope_id, &bssid, &ip, *p, (*p)->ifinfo.ifname, ifa->ifa_name);
			// The name should be updated to the new name so that we don't report a wrong name in our SIGINFO output.
			// When interfaces are created with same MAC address, kernel resurrects the old interface.
			// Even though the interface index is the same (which should be sufficient), when we receive a UDP packet
			// we get the corresponding name for the interface index on which the packet was received and check against
			// the InterfaceList for a matching name. So, keep the name in sync
			strlcpy((*p)->ifinfo.ifname, ifa->ifa_name, sizeof((*p)->ifinfo.ifname));
			(*p)->Exists = mDNStrue;
			// If interface was not in getifaddrs list last time we looked, but it is now, update 'AppearanceTime' for this record
			if ((*p)->LastSeen != utc) (*p)->AppearanceTime = utc;

			// If Wake-on-LAN capability of this interface has changed (e.g. because power cable on laptop has been disconnected)
			// we may need to start or stop or sleep proxy browse operation
			const mDNSBool NetWake = NetWakeInterface(*p);
			if ((*p)->ifinfo.NetWake != NetWake)
				{
				(*p)->ifinfo.NetWake = NetWake;
				// If this interface is already registered with mDNSCore, then we need to start or stop its NetWake browse on-the-fly.
				// If this interface is not already registered (i.e. it's a dormant interface we had in our list
				// from when we previously saw it) then we mustn't do that, because mDNSCore doesn't know about it yet.
				// In this case, the mDNS_RegisterInterface() call will take care of starting the NetWake browse if necessary.
				if ((*p)->Registered)
					{
					mDNS_Lock(m);
					if (NetWake) mDNS_ActivateNetWake_internal  (m, &(*p)->ifinfo);
					else         mDNS_DeactivateNetWake_internal(m, &(*p)->ifinfo);
					mDNS_Unlock(m);
					}
				}

			return(*p);
			}

	NetworkInterfaceInfoOSX *i = (NetworkInterfaceInfoOSX *)mallocL("NetworkInterfaceInfoOSX", sizeof(*i));
	debugf("AddInterfaceToList: Making   new   interface %lu %.6a with address %#a at %p", scope_id, &bssid, &ip, i);
	if (!i) return(mDNSNULL);
	mDNSPlatformMemZero(i, sizeof(NetworkInterfaceInfoOSX));
	i->ifinfo.InterfaceID = (mDNSInterfaceID)(uintptr_t)scope_id;
	i->ifinfo.ip          = ip;
	i->ifinfo.mask        = mask;
	strlcpy(i->ifinfo.ifname, ifa->ifa_name, sizeof(i->ifinfo.ifname));
	i->ifinfo.ifname[sizeof(i->ifinfo.ifname)-1] = 0;
	// We can be configured to disable multicast advertisement, but we want to to support
	// local-only services, which need a loopback address record.
	i->ifinfo.Advertise   = m->DivertMulticastAdvertisements ? ((ifa->ifa_flags & IFF_LOOPBACK) ? mDNStrue : mDNSfalse) : m->AdvertiseLocalAddresses;
	i->ifinfo.McastTxRx   = mDNSfalse; // For now; will be set up later at the end of UpdateInterfaceList
	i->ifinfo.Loopback    = ((ifa->ifa_flags & IFF_LOOPBACK) != 0) ? mDNStrue : mDNSfalse;

	i->next            = mDNSNULL;
	i->m               = m;
	i->Exists          = mDNStrue;
	i->Flashing        = mDNSfalse;
	i->Occulting       = mDNSfalse;
	i->AppearanceTime  = utc;		// Brand new interface; AppearanceTime is now
	i->LastSeen        = utc;
	i->ifa_flags       = ifa->ifa_flags;
	i->scope_id        = scope_id;
	i->BSSID           = bssid;
	i->sa_family       = ifa->ifa_addr->sa_family;
	i->BPF_fd          = -1;
	i->BPF_mcfd        = -1;
	i->BPF_len         = 0;
	i->Registered	   = mDNSNULL;

	// Do this AFTER i->BSSID has been set up
	i->ifinfo.NetWake  = NetWakeInterface(i);
	GetMAC(&i->ifinfo.MAC, scope_id);
	if (i->ifinfo.NetWake && !i->ifinfo.MAC.l[0])
		LogMsg("AddInterfaceToList: Bad MAC address %.6a for %d %s %#a", &i->ifinfo.MAC, scope_id, i->ifinfo.ifname, &ip);

	*p = i;
	return(i);
	}

#if USE_V6_ONLY_WHEN_NO_ROUTABLE_V4
mDNSlocal NetworkInterfaceInfoOSX *FindRoutableIPv4(mDNS *const m, mDNSu32 scope_id)
	{
	NetworkInterfaceInfoOSX *i;
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->Exists && i->scope_id == scope_id && i->ifinfo.ip.type == mDNSAddrType_IPv4)
			if (!mDNSv4AddressIsLinkLocal(&i->ifinfo.ip.ip.v4))
				return(i);
	return(mDNSNULL);
	}
#endif

#if APPLE_OSX_mDNSResponder

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - AutoTunnel
#endif

#define kRacoonPort 4500

static DomainAuthInfo* AnonymousRacoonConfig = mDNSNULL;

#ifndef NO_SECURITYFRAMEWORK

static CFMutableDictionaryRef domainStatusDict = NULL;

// MUST be called with lock held
mDNSlocal void RemoveAutoTunnelDomainStatus(const mDNS *const m, const DomainAuthInfo *const info)
	{
	char buffer[1024];
	mDNSu32 buflen;
	CFStringRef domain;

	LogInfo("RemoveAutoTunnelDomainStatus: %##s", info->domain.c);

	if (!domainStatusDict) { LogMsg("RemoveAutoTunnelDomainStatus: No domainStatusDict"); return; }
	
	buflen = mDNS_snprintf(buffer, sizeof(buffer), "%##s", info->domain.c);
	if (info->AutoTunnel == dnsprefix) buffer[buflen-1] = 0; // Strip the trailing dot for Classic
	domain = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
	if (!domain) { LogMsg("RemoveAutoTunnelDomainStatus: Could not create CFString domain"); return; }
	
	if (CFDictionaryContainsKey(domainStatusDict, domain))
		{
		CFDictionaryRemoveValue(domainStatusDict, domain);
		if (!m->ShutdownTime) mDNSDynamicStoreSetConfig(kmDNSBackToMyMacConfig, mDNSNULL, domainStatusDict);
		}
	CFRelease(domain);
	}

mDNSlocal mStatus CheckQuestionForStatus(const DNSQuestion *const q)
	{
	if (q->LongLived)
		{
		if (q->servAddr.type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsOnes(q->servAddr.ip.v4))
			return mStatus_NoSuchRecord;
		else if (q->state == LLQ_Poll)
			return mStatus_PollingMode;
		else if (q->state != LLQ_Established && !q->DuplicateOf)
			return mStatus_TransientErr;
		}
	
	return mStatus_NoError;
}

mDNSlocal mStatus UpdateLLQStatus(const mDNS *const m, char *buffer, int bufsz, const DomainAuthInfo *const info)
	{
	mStatus status = mStatus_NoError;
	DNSQuestion* q, *worst_q = mDNSNULL;
	for (q = m->Questions; q; q=q->next)
		if (q->AuthInfo == info)
			{
			mStatus newStatus = CheckQuestionForStatus(q);
			if 		(newStatus == mStatus_NoSuchRecord) { status = newStatus; worst_q = q; break; }
			else if (newStatus == mStatus_PollingMode)  { status = newStatus; worst_q = q; }
			else if (newStatus == mStatus_TransientErr && status == mStatus_NoError) { status = newStatus; worst_q = q; }
			}
	
	if      (status == mStatus_NoError)      mDNS_snprintf(buffer, bufsz, "Success");
	else if (status == mStatus_NoSuchRecord) mDNS_snprintf(buffer, bufsz, "GetZoneData %s: %##s", worst_q->nta ? "not yet complete" : "failed", worst_q->qname.c);
	else if (status == mStatus_PollingMode)  mDNS_snprintf(buffer, bufsz, "Query polling %##s", worst_q->qname.c);
	else if (status == mStatus_TransientErr) mDNS_snprintf(buffer, bufsz, "Query not yet established %##s", worst_q->qname.c);
	return status;
	}

mDNSlocal mStatus UpdateRRStatus(const mDNS *const m, char *buffer, int bufsz, const DomainAuthInfo *const info)
	{
	AuthRecord *r;

	if (info->deltime) return mStatus_NoError;
	for (r = m->ResourceRecords; r; r = r->next)
		{
		// This function is called from UpdateAutoTunnelDomainStatus which in turn may be called from
		// a callback e.g., CheckNATMappings. GetAuthInfoFor_internal does not like that (reentrancy being 1),
		// hence we inline the code here. We just need the lock to walk the list of AuthInfos which the caller
		// has already checked
		const domainname *n = r->resrec.name;
		while (n->c[0])
			{
			DomainAuthInfo *ptr;
			for (ptr = m->AuthInfoList; ptr; ptr = ptr->next)
				if (SameDomainName(&ptr->domain, n))
					{
					if (ptr == info && r->updateError == mStatus_BadSig)
						{
						mDNS_snprintf(buffer, bufsz, "Resource record update failed for %##s", r->resrec.name);
						return r->updateError;
						}
					}
			n = (const domainname *)(n->c + 1 + n->c[0]);
			}
		}
	return mStatus_NoError;
	}

#endif // ndef NO_SECURITYFRAMEWORK

// MUST be called with lock held
mDNSlocal void UpdateAutoTunnelDomainStatus(const mDNS *const m, const DomainAuthInfo *const info)
	{
#ifdef NO_SECURITYFRAMEWORK
	(void)m;
	(void)info;
#else
	const NATTraversalInfo *const llq = m->LLQNAT.clientContext ? &m->LLQNAT : mDNSNULL;
	const NATTraversalInfo *const tun = info->AutoTunnelNAT.clientContext ? &info->AutoTunnelNAT : mDNSNULL;
	char buffer[1024];
	mDNSu32 buflen = 0;
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	CFStringRef domain = NULL;
	CFStringRef tmp = NULL;
	CFNumberRef num = NULL;
	mStatus status = mStatus_NoError;
	mStatus llqStatus = mStatus_NoError;
	char llqBuffer[1024];
	
	if (!m->mDNS_busy) LogMsg("UpdateAutoTunnelDomainStatus: ERROR!! Lock not held");
	if (!domainStatusDict)
		{
		domainStatusDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		if (!domainStatusDict) { LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFDictionary domainStatusDict"); return; }
		}
	
	if (!dict) { LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFDictionary dict"); return; }

	buflen = mDNS_snprintf(buffer, sizeof(buffer), "%##s", info->domain.c);
	if (info->AutoTunnel == dnsprefix) buffer[buflen-1] = 0; // Strip the trailing dot for Classic
	domain = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
	if (!domain) { LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString domain"); return; }

	mDNS_snprintf(buffer, sizeof(buffer), "%#a", &m->Router);
	tmp = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
	if (!tmp)
		LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString RouterAddress");
	else
		{
		CFDictionarySetValue(dict, CFSTR("RouterAddress"), tmp);
		CFRelease(tmp);
		}
	
	mDNS_snprintf(buffer, sizeof(buffer), "%.4a", &m->ExternalAddress);
	tmp = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
	if (!tmp)
		LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString ExternalAddress");
	else
		{
		CFDictionarySetValue(dict, CFSTR("ExternalAddress"), tmp);
		CFRelease(tmp);
		}
	
	if (llq)
		{
		mDNSu32 port = mDNSVal16(llq->ExternalPort);
		
		num = CFNumberCreate(NULL, kCFNumberSInt32Type, &port);
		if (!num)
			LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber LLQExternalPort");
		else
			{
			CFDictionarySetValue(dict, CFSTR("LLQExternalPort"), num);
			CFRelease(num);
			}

		if (llq->Result)
			{
			num = CFNumberCreate(NULL, kCFNumberSInt32Type, &llq->Result);
			if (!num)
				LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber LLQNPMStatus");
			else
				{
				CFDictionarySetValue(dict, CFSTR("LLQNPMStatus"), num);
				CFRelease(num);
				}
			}
		}
	
	if (tun)
		{
		mDNSu32 port = mDNSVal16(tun->ExternalPort);
		
		num = CFNumberCreate(NULL, kCFNumberSInt32Type, &port);
		if (!num)
			LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber AutoTunnelExternalPort");
		else
			{
			CFDictionarySetValue(dict, CFSTR("AutoTunnelExternalPort"), num);
			CFRelease(num);
			}

		if (tun->Result)
			{
			num = CFNumberCreate(NULL, kCFNumberSInt32Type, &tun->Result);
			if (!num)
				LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber AutoTunnelNPMStatus");
			else
				{
				CFDictionarySetValue(dict, CFSTR("AutoTunnelNPMStatus"), num);
				CFRelease(num);
				}
			}
		}
	if (tun || llq)
		{
		mDNSu32 code = m->LastNATMapResultCode;
		
		num = CFNumberCreate(NULL, kCFNumberSInt32Type, &code);
		if (!num)
			LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber LastNATMapResultCode");
		else
			{
			CFDictionarySetValue(dict, CFSTR("LastNATMapResultCode"), num);
			CFRelease(num);
			}
		}
	
	mDNS_snprintf(buffer, sizeof(buffer), "Success");
	llqStatus = UpdateLLQStatus(m, llqBuffer, sizeof(llqBuffer), info);
	status = UpdateRRStatus(m, buffer, sizeof(buffer), info);

	// If we have a bad signature error updating a RR, it overrides any error as it needs to be
	// reported so that it can be fixed automatically (or the user needs to be notified)
	if (status != mStatus_NoError)
		{
		LogInfo("UpdateAutoTunnelDomainStatus: RR Status %d, %s", status, buffer);
		}
	else if (m->Router.type == mDNSAddrType_None)
		{
		status = mStatus_NoRouter;
		mDNS_snprintf(buffer, sizeof(buffer), "No network connection - none");
		}
	else if (m->Router.type == mDNSAddrType_IPv4 && mDNSIPv4AddressIsZero(m->Router.ip.v4))
		{
		status = mStatus_NoRouter;
		mDNS_snprintf(buffer, sizeof(buffer), "No network connection - v4 zero");
		}
	else if (mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddrOut))
		{
		status = info->AutoTunnel == btmmprefix ? mStatus_ServiceNotRunning : mStatus_PollingMode;
		mDNS_snprintf(buffer, sizeof(buffer), "No relay connection");
		}
	else if (!llq && !tun)
		{
		status = mStatus_NotInitializedErr;
		mDNS_snprintf(buffer, sizeof(buffer), "Neither LLQ nor AutoTunnel NAT port mapping is currently active");
		}
	else if (llqStatus == mStatus_NoSuchRecord)
		{
		status = llqStatus;
		mDNS_snprintf(buffer, sizeof(buffer), llqBuffer);
		}
	else if (info->AutoTunnel == btmmprefix && ((llq && llq->Result == mStatus_DoubleNAT) || (tun && tun->Result == mStatus_DoubleNAT)))
		{
		status = mStatus_DoubleNAT;
		mDNS_snprintf(buffer, sizeof(buffer), "Double NAT: Router is reporting an external address");
		}
	else if (info->AutoTunnel == btmmprefix && ((llq && llq->Result == mStatus_NATPortMappingDisabled) || (tun && tun->Result == mStatus_NATPortMappingDisabled) ||
	         (m->LastNATMapResultCode == NATErr_Refused && ((llq && !llq->Result && mDNSIPPortIsZero(llq->ExternalPort)) || (tun && !tun->Result && mDNSIPPortIsZero(tun->ExternalPort))))))
		{
		status = mStatus_NATPortMappingDisabled;
		mDNS_snprintf(buffer, sizeof(buffer), "NAT-PMP is disabled on the router");
		}
	else if (info->AutoTunnel == btmmprefix && ((llq && llq->Result) || (tun && tun->Result)))
		{
		status = mStatus_NATTraversal;
		mDNS_snprintf(buffer, sizeof(buffer), "Error obtaining NAT port mapping from router");
		}
	else if (info->AutoTunnel == btmmprefix && ((llq && mDNSIPPortIsZero(llq->ExternalPort)) || (tun && mDNSIPPortIsZero(tun->ExternalPort))))
		{
		status = mStatus_NATTraversal;
		mDNS_snprintf(buffer, sizeof(buffer), "Unable to obtain NAT port mapping from router");
		}
	else if (info->AutoTunnel == btmmprefix || llqStatus != mStatus_PollingMode)
		{
		status = llqStatus;
		mDNS_snprintf(buffer, sizeof(buffer), llqBuffer);		
		LogInfo("UpdateAutoTunnelDomainStatus: LLQ Status %d, %s", status, buffer);
		}
	else
		{
		mDNS_snprintf(buffer, sizeof(buffer), "Polling success");		
		}
	
	num = CFNumberCreate(NULL, kCFNumberSInt32Type, &status);
	if (!num)
		LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFNumber StatusCode");
	else
		{
		CFDictionarySetValue(dict, CFSTR("StatusCode"), num);
		CFRelease(num);
		}
		
	tmp = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
	if (!tmp)
		LogMsg("UpdateAutoTunnelDomainStatus: Could not create CFString StatusMessage");
	else
		{
		CFDictionarySetValue(dict, CFSTR("StatusMessage"), tmp);
		CFRelease(tmp);
		}

	if (!CFDictionaryContainsKey(domainStatusDict, domain) ||
	    !CFEqual(dict, (CFMutableDictionaryRef)CFDictionaryGetValue(domainStatusDict, domain)))
	    {
		CFDictionarySetValue(domainStatusDict, domain, dict);
		if (!m->ShutdownTime) 
			{
			static char statusBuf[16];
			mDNS_snprintf(statusBuf, sizeof(statusBuf), "%d", (int)status);
			mDNSASLLog((uuid_t *)&m->asl_uuid, "autotunnel.domainstatus", status ? "failure" : "success", statusBuf, "");
			mDNSDynamicStoreSetConfig(kmDNSBackToMyMacConfig, mDNSNULL, domainStatusDict);
			}
		}
		
	CFRelease(domain);
	CFRelease(dict);

	debugf("UpdateAutoTunnelDomainStatus: %s", buffer);
#endif // def NO_SECURITYFRAMEWORK
	}

// MUST be called with lock held
mDNSexport void UpdateAutoTunnelDomainStatuses(const mDNS *const m)
	{
#ifdef NO_SECURITYFRAMEWORK
	(void)m;
#else
	if (!m->mDNS_busy) LogMsg("UpdateAutoTunnelDomainStatuses: ERROR!! Lock not held");
	DomainAuthInfo* info;
	for (info = m->AuthInfoList; info; info = info->next)
		if (info->AutoTunnel && !info->deltime)
			UpdateAutoTunnelDomainStatus(m, info);
#endif // def NO_SECURITYFRAMEWORK
	}
	
// MUST be called with lock held
mDNSlocal mDNSBool TunnelServers(mDNS *const m)
	{
	AuthRecord *r;
	for (r = m->ResourceRecords; r; r = r->next)
		if (r->resrec.rrtype == kDNSType_SRV)
			{
			DomainAuthInfo *AuthInfo = GetAuthInfoForName_internal(m, r->resrec.name);
			if (AuthInfo && AuthInfo->AutoTunnel && !AuthInfo->deltime) return(mDNStrue);
			}

	return(mDNSfalse);
	}

// MUST be called with lock held
mDNSlocal mDNSBool TunnelClients(mDNS *const m)
	{
	ClientTunnel *p;
	for (p = m->TunnelClients; p; p = p->next)
		if (p->q.ThisQInterval < 0)
			return(mDNStrue);
	return(mDNSfalse);
	}

mDNSlocal void UpdateAnonymousRacoonConfig(mDNS *m)		// Determine whether we need racoon to accept incoming connections
	{
	DomainAuthInfo *info;
	
	for (info = m->AuthInfoList; info; info = info->next)
		if (info->AutoTunnel && !info->deltime && (!mDNSIPPortIsZero(info->AutoTunnelNAT.ExternalPort) || !mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddrIn)))
			break;
	
	if (info != AnonymousRacoonConfig)
		{
		AnonymousRacoonConfig = info;
		// Create or revert configuration file, and start (or SIGHUP) Racoon
		(void)mDNSConfigureServer(AnonymousRacoonConfig ? kmDNSUp : kmDNSDown, AnonymousRacoonConfig ? AnonymousRacoonConfig->AutoTunnel : mDNSNULL, AnonymousRacoonConfig ? &AnonymousRacoonConfig->domain : mDNSNULL);
		}
	}

// Caller should hold the lock. We don't call mDNS_Register (which acquires the lock) in this function because
// sometimes the caller may already be holding the lock e.g., RegisterAutoTunnel6Record and sometimes
// not e.g., RegisterAutoTunnelServiceRecord
mDNSlocal void RegisterAutoTunnelHostRecord(mDNS *m, DomainAuthInfo *info)
	{
	mStatus err;
	mDNSBool NATProblem;

	if (!m->mDNS_busy) LogMsg("RegisterAutoTunnelHostRecord: ERROR!! Lock not held");

	// We use AutoTunnelNAT.clientContext to infer that SetupLocalAutoTunnelInterface_internal has been
	// called at least once with some Services/Records in the domain and hence it is safe to register
	// records when this function is called.
	if (!info->AutoTunnelNAT.clientContext) { LogInfo("RegisterAutoTunnelHostRecord: No services registered, not registering the record\n"); return; }

	// Are we behind a NAT with no NAT-PMP support or behind a Double NAT ? Double NATs may have
	// NAT-PMP support but it still does not provide inbound connectivity. If there is no NAT-PMP
	// support, ExternalPort is zero. If we are behind a Double NAT, then the NATResult is non-zero.

	NATProblem = mDNSIPPortIsZero(info->AutoTunnelNAT.ExternalPort) || info->AutoTunnelNAT.Result;

	if (mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddrIn))
		{
		// If we don't have a relay address, check to see if we are behind a Double NAT or NAT with no NAT-PMP
		// support.  
		if (NATProblem)
			{
			LogInfo("RegisterAutoTunnelHostRecord %##s, not registering the Host Record, Neither AutoTunnel6 nor NAT is available, ClientContext %p, ExternalPort %d, NAT Result %d", info->domain.c, info->AutoTunnelNAT.clientContext, mDNSVal16(info->AutoTunnelNAT.ExternalPort), info->AutoTunnelNAT.Result);
			return;
			}
		}
	else
		{
		// Relay address may be non-zero but we might be going to sleep as the utun interface is not removed
		// when going to sleep. If we are awake, we don't care about the NATProblem as the relay connnection
		// is up. If we are going to sleep, we should not register the host record if we have a NAT problem.
		if (m->SleepState != SleepState_Awake && NATProblem)
			{
			LogInfo("RegisterAutoTunnelHostRecord %##s, not registering the Host Record, Not in awake state(%d), and some NAT Problem, ClientContext %p, ExternalPort %d, NAT Result %d", info->domain.c, m->SleepState, info->AutoTunnelNAT.clientContext, mDNSVal16(info->AutoTunnelNAT.ExternalPort), info->AutoTunnelNAT.Result);
			return;
			}
		}

	// Note:
	//
	// We use zero Requested port to infer that we should not be calling Register anymore as it might
	// be shutdown or the DomainAuthInfo is going away.
	//
	// We can use a different set of state variables to track the above as the records registered in
	// this function is not dependent on NAT traversal info. For the sake of simplicity, we just
	// reuse the NAT variables.

	// Set up our address record for the internal tunnel address
	// (User-visible user-friendly host name, used as target in AutoTunnel SRV records)
	if (!mDNSIPPortIsZero(info->AutoTunnelNAT.RequestedPort) && info->AutoTunnelHostRecord.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		info->AutoTunnelHostRecord.namestorage.c[0] = 0;
		AppendDomainLabel(&info->AutoTunnelHostRecord.namestorage, &m->hostlabel);
		AppendDomainName (&info->AutoTunnelHostRecord.namestorage, &info->domain);
		info->AutoTunnelHostRecord.resrec.rdata->u.ipv6 = m->AutoTunnelHostAddr;
		info->AutoTunnelHostRecord.resrec.RecordType = kDNSRecordTypeKnownUnique;

		err = mDNS_Register_internal(m, &info->AutoTunnelHostRecord);

		if (err) LogMsg("RegisterAutoTunnelHostRecord error %d registering AutoTunnelHostRecord %##s", err, info->AutoTunnelHostRecord.namestorage.c);
		else
			{
			// Make sure we trigger the registration of all SRV records in regState_NoTarget again
			m->NextSRVUpdate = NonZeroTime(m->timenow);
			LogInfo("RegisterAutoTunnelHostRecord registering AutoTunnelHostRecord %##s", info->AutoTunnelHostRecord.namestorage.c);
			}
		}
	else LogInfo("RegisterAutoTunnelHostRecord: Not registering Context %p Port %d Type %d", info->AutoTunnelNAT.clientContext, mDNSVal16(info->AutoTunnelNAT.RequestedPort), info->AutoTunnelHostRecord.resrec.RecordType);
	}

mDNSlocal void DeregisterAutoTunnelHostRecord(mDNS *m, DomainAuthInfo *info)
	{
	LogInfo("DeregisterAutoTunnelHostRecord %##s", info->domain.c);

	// Don't deregister if we have the AutoTunnel6 or AutoTunnelService records are registered.
	// They indicate that BTMM is working
	if (info->AutoTunnel6Record.resrec.RecordType > kDNSRecordTypeDeregistering ||
	    info->AutoTunnelService.resrec.RecordType > kDNSRecordTypeDeregistering)
		{
		LogInfo("DeregisterAutoTunnelHostRecord %##s, not deregistering the Host Record AutoTunnel6 RecordType:%d AutoTunnel RecordType: %d", info->domain.c,
			info->AutoTunnel6Record.resrec.RecordType, info->AutoTunnelService.resrec.RecordType);
		return;
		}

	if (info->AutoTunnelHostRecord.resrec.RecordType > kDNSRecordTypeDeregistering)
		{
		mStatus err = mDNS_Deregister(m, &info->AutoTunnelHostRecord);
		if (err)
			{
			info->AutoTunnelHostRecord.resrec.RecordType = kDNSRecordTypeUnregistered;
			LogMsg("DeregisterAutoTunnelHostRecord error %d deregistering AutoTunnelHostRecord %##s", err, info->AutoTunnelHostRecord.namestorage.c);
			}
		else LogInfo("DeregisterAutoTunnelHostRecord: Deregistered AutoTunnel Host Record");
		}
	else LogInfo("DeregisterAutoTunnelHostRecord: Not deregistering Host Record state:%d", info->AutoTunnelHostRecord.resrec.RecordType);
	}

mDNSlocal void RegisterAutoTunnelServiceRecords(mDNS *m, DomainAuthInfo *info)
	{
	mStatus err;

	//if (m->mDNS_busy) LogMsg("RegisterAutoTunnelServiceRecords: ERROR!! Lock already held");

	if (info->AutoTunnelNAT.clientContext && !info->AutoTunnelNAT.Result && !mDNSIPPortIsZero(info->AutoTunnelNAT.ExternalPort) && info->AutoTunnelTarget.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		LogInfo("RegisterAutoTunnelServiceRecords %##s (%#s)", info->domain.c, m->hostlabel.c);

		// 1. Set up our address record for the external tunnel address
		// (Constructed name, not generally user-visible, used as target in IKE tunnel's SRV record)
		info->AutoTunnelTarget.namestorage.c[0] = 0;
		AppendDomainLabel(&info->AutoTunnelTarget.namestorage, &m->AutoTunnelLabel);
		AppendDomainName (&info->AutoTunnelTarget.namestorage, &info->domain);
		info->AutoTunnelTarget.resrec.rdata->u.ipv4 = info->AutoTunnelNAT.ExternalAddress;
		info->AutoTunnelTarget.resrec.RecordType = kDNSRecordTypeKnownUnique;

		err = mDNS_Register(m, &info->AutoTunnelTarget);
		if (err) LogMsg("RegisterAutoTunnelServiceRecords error %d registering AutoTunnelTarget %##s", err, info->AutoTunnelTarget.namestorage.c);
		else LogInfo("RegisterAutoTunnelServiceRecords registering AutoTunnelTarget %##s", info->AutoTunnelTarget.namestorage.c);

		}

	if (info->AutoTunnelNAT.clientContext && !info->AutoTunnelNAT.Result && !mDNSIPPortIsZero(info->AutoTunnelNAT.ExternalPort) && info->AutoTunnelService.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		// 2. Set up IKE tunnel's SRV record: "AutoTunnelHostRecord SRV 0 0 port AutoTunnelTarget"
		AssignDomainName (&info->AutoTunnelService.namestorage, (const domainname*) "\x0B" "_autotunnel" "\x04" "_udp");
		AppendDomainLabel(&info->AutoTunnelService.namestorage, &m->hostlabel);
		AppendDomainName (&info->AutoTunnelService.namestorage, &info->domain);
		info->AutoTunnelService.resrec.rdata->u.srv.priority = 0;
		info->AutoTunnelService.resrec.rdata->u.srv.weight   = 0;
		info->AutoTunnelService.resrec.rdata->u.srv.port     = info->AutoTunnelNAT.ExternalPort;
		AssignDomainName(&info->AutoTunnelService.resrec.rdata->u.srv.target, &info->AutoTunnelTarget.namestorage);
		info->AutoTunnelService.resrec.RecordType = kDNSRecordTypeKnownUnique;
		err = mDNS_Register(m, &info->AutoTunnelService);
		if (err) LogMsg("RegisterAutoTunnelServiceRecords error %d registering AutoTunnelService %##s", err, info->AutoTunnelService.namestorage.c);
		else LogInfo("RegisterAutoTunnelServiceRecords registering AutoTunnelService %##s", info->AutoTunnelService.namestorage.c);

		LogInfo("AutoTunnel server listening for connections on %##s[%.4a]:%d:%##s[%.16a]",
			info->AutoTunnelTarget.namestorage.c,     &m->AdvertisedV4.ip.v4, mDNSVal16(info->AutoTunnelNAT.IntPort),
			info->AutoTunnelHostRecord.namestorage.c, &m->AutoTunnelHostAddr);
		}
	mDNS_Lock(m);
	RegisterAutoTunnelHostRecord(m, info);
	mDNS_Unlock(m);
	}

mDNSlocal void DeregisterAutoTunnelServiceRecords(mDNS *m, DomainAuthInfo *info)
	{
	LogInfo("DeregisterAutoTunnelServiceRecords %##s", info->domain.c);
	if (info->AutoTunnelTarget.resrec.RecordType > kDNSRecordTypeDeregistering)
		{
		mStatus err = mDNS_Deregister(m, &info->AutoTunnelTarget);
		if (err)
			{
			info->AutoTunnelTarget.resrec.RecordType = kDNSRecordTypeUnregistered;
			LogMsg("DeregisterAutoTunnelServiceRecords error %d deregistering AutoTunnelTarget %##s", err, info->AutoTunnelTarget.namestorage.c);
			}
		else LogInfo("DeregisterAutoTunnelServiceRecords: Deregistered AutoTunnel Target  Record");

		}
	else LogInfo("DeregisterAutoTunnelServiceRecords: Not deregistering Target record state:%d", info->AutoTunnelService.resrec.RecordType);

	if (info->AutoTunnelService.resrec.RecordType > kDNSRecordTypeDeregistering)
		{
		mStatus err = mDNS_Deregister(m, &info->AutoTunnelService);
		if (err)
			{
			info->AutoTunnelService.resrec.RecordType = kDNSRecordTypeUnregistered;
			LogMsg("DeregisterAutoTunnelServiceRecords error %d deregistering AutoTunnelService %##s", err, info->AutoTunnelService.namestorage.c);
			}
		else LogInfo("DeregisterAutoTunnelServiceRecords: Deregistered AutoTunnel Service Record");

		}
	else LogInfo("DeregisterAutoTunnelServiceRecords: Not deregistering service records state:%d", info->AutoTunnelService.resrec.RecordType);

	DeregisterAutoTunnelHostRecord(m, info);
	}

// Caller should hold the lock. We don't call mDNS_Register (which acquires the lock) in this function because
// sometimes the caller may already be holding the lock e.g., SetupLocalAutoTunnelInterface_internal and sometimes
// not e.g., AutoTunnelHostNameChanged
mDNSlocal void RegisterAutoTunnelDevInfoRecord(mDNS *m, DomainAuthInfo *info)
	{
	mStatus err;

	if (!m->mDNS_busy) LogMsg("RegisterAutoTunnelDevInfoRecord: Lock not held");
	// Note:
	// a. We use AutoTunnelNAT.clientContext to infer that SetupLocalAutoTunnelInterface_internal has been
	//    called at least once with some Services/Records in the domain and hence it is safe to register
	//    records when this function is called.
	//
	// b. We use zero Requested port to infer that we should not be calling Register anymore as it might
	//    be shutdown or the DomainAuthInfo is going away.
	//
	// We can use a different set of state variables to track the above as the records registered in
	// this function is not dependent on NAT traversal info. For the sake of simplicity, we just
	// reuse the NAT variables.

	// Set up device info record
	if (info->AutoTunnelNAT.clientContext && !mDNSIPPortIsZero(info->AutoTunnelNAT.RequestedPort) && info->AutoTunnelDeviceInfo.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		ConstructServiceName(&info->AutoTunnelDeviceInfo.namestorage, &m->nicelabel, &DeviceInfoName, &info->domain);
		mDNSu8 len = m->HIHardware.c[0] < 255 - 6 ? m->HIHardware.c[0] : 255 - 6;
		mDNSPlatformMemCopy(info->AutoTunnelDeviceInfo.resrec.rdata->u.data + 1, "model=", 6);
		mDNSPlatformMemCopy(info->AutoTunnelDeviceInfo.resrec.rdata->u.data + 7, m->HIHardware.c + 1, len);
		info->AutoTunnelDeviceInfo.resrec.rdata->u.data[0] = 6 + len;	// "model=" plus the device string
		info->AutoTunnelDeviceInfo.resrec.rdlength         = 7 + len;	// One extra for the length byte at the start of the string
		info->AutoTunnelDeviceInfo.resrec.RecordType = kDNSRecordTypeKnownUnique;

		err = mDNS_Register_internal(m, &info->AutoTunnelDeviceInfo);
		if (err) LogMsg("RegisterAutoTunnelDevInfoRecord error %d registering AutoTunnelDeviceInfo %##s", err, info->AutoTunnelDeviceInfo.namestorage.c);
		else LogInfo("RegisterAutoTunnelDevInfoRecord registering AutoTunnelDeviceInfo %##s", info->AutoTunnelDeviceInfo.namestorage.c);
		}
	}

#ifndef NO_SECURITYFRAMEWORK
mDNSlocal void DeregisterAutoTunnelDevInfoRecord(mDNS *m, DomainAuthInfo *info)
	{
	LogInfo("DeregisterAutoTunnelDevInfoRecord %##s", info->domain.c);

	if (info->AutoTunnelDeviceInfo.resrec.RecordType > kDNSRecordTypeDeregistering)
		{
		mStatus err = mDNS_Deregister(m, &info->AutoTunnelDeviceInfo);
		if (err)
			{
			info->AutoTunnelDeviceInfo.resrec.RecordType = kDNSRecordTypeUnregistered;
			LogMsg("DeregisterAutoTunnelDevInfoRecord error %d deregistering AutoTunnelDeviceInfo %##s", err, info->AutoTunnelDeviceInfo.namestorage.c);
			}
		else LogInfo("DeregisterAutoTunnelDevInfoRecord: Deregistered AutoTunnel Device Info");
		}
	else LogInfo("DeregisterAutoTunnelDevInfoRecord: Not deregistering DeviceInfo  Record state:%d", info->AutoTunnelDeviceInfo.resrec.RecordType);
	}
#endif


// Caller should hold the lock. We don't call mDNS_Register (which acquires the lock) in this function because
// sometimes the caller may already be holding the lock e.g., SetupLocalAutoTunnelInterface_internal and sometimes
// not e.g., AutoTunnelHostNameChanged
mDNSlocal void RegisterAutoTunnel6Record(mDNS *m, DomainAuthInfo *info)
	{
	mStatus err;

	if (!m->mDNS_busy) LogMsg("RegisterAutoTunnel6Record: ERROR!! Lock not held");

	// We deregister the AutoTunnel6Record during sleep and come back here (AutoTunnelRecordCallback) to
	// register the address if needed. During that time, we might get a network change event which finds
	// that the utun interface exists and tries to register the AutoTunnel6Record which should be stopped.
	// Also the RelayAddress is reinitialized during that process which in turn causes the AutoTunnelRecordCallback
	// to re-register again. To stop these, we check for the SleepState and register only if we are awake.
	if (m->SleepState != SleepState_Awake)
		{
		LogInfo("RegisterAutoTunnel6Record: Not in awake state, SleepState %d", m->SleepState);
		return;
		}

	// if disabled administratively, don't register
	if (!m->RegisterAutoTunnel6 || DisableInboundRelayConnection)
		{
		LogInfo("RegisterAutoTunnel6Record: registration Disabled RegisterAutoTunnel6 %d, DisableInbound %d",
			m->RegisterAutoTunnel6, DisableInboundRelayConnection);
		return;
		}
	//
	// If we have a valid Relay address, we need to register it now. When we got a valid address, we may not
	// have registered it because it was waiting for at least one service to become active in the BTMM domain.
	// During network change event, we might be called multiple times while the "Connectivity" key did not
	// change, so check to see if the value has changed. This can also be zero when we are deregistering and
	// getting called from the AutoTunnelRecordCallback

	if (mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddrIn))
		{
		LogInfo("RegisterAutoTunnel6Record: Relay address is zero, not registering");
		return;
		}

	if ((info->AutoTunnel6Record.resrec.RecordType > kDNSRecordTypeDeregistering) &&
		(mDNSSameIPv6Address(info->AutoTunnel6Record.resrec.rdata->u.ipv6, m->AutoTunnelRelayAddrIn)))
		{
		LogInfo("RegisterAutoTunnel6Record: Relay address %.16a same, not registering", &m->AutoTunnelRelayAddrIn);
		return;
		}

	// Note:
	// a. We use AutoTunnelNAT.clientContext to infer that SetupLocalAutoTunnelInterface_internal has been
	//    called at least once with some Services/Records in the domain and hence it is safe to register
	//    records when this function is called.
	//
	// b. We use zero Requested port to infer that we should not be calling Register anymore as it might
	//    be shutdown or the DomainAuthInfo is going away.
	//
	// We can use a different set of state variables to track the above as the records registered in
	// this function is not dependent on NAT traversal info. For the sake of simplicity, we just
	// reuse the NAT variables.

	if (info->AutoTunnelNAT.clientContext && !mDNSIPPortIsZero(info->AutoTunnelNAT.RequestedPort) &&
	    info->AutoTunnel6Record.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		AssignDomainName (&info->AutoTunnel6Record.namestorage, (const domainname*) "\x0C" "_autotunnel6");
		AppendDomainLabel(&info->AutoTunnel6Record.namestorage, &m->hostlabel);
		AppendDomainName (&info->AutoTunnel6Record.namestorage, &info->domain);
		info->AutoTunnel6Record.resrec.rdata->u.ipv6 = m->AutoTunnelRelayAddrIn;
		info->AutoTunnel6Record.resrec.RecordType = kDNSRecordTypeKnownUnique;

		err = mDNS_Register_internal(m, &info->AutoTunnel6Record);
		if (err) LogMsg("RegisterAutoTunnel6Record error %d registering AutoTunnel6 Record %##s", err, info->AutoTunnel6Record.namestorage.c);
		else LogInfo("RegisterAutoTunnel6Record registering AutoTunnel6 Record %##s", info->AutoTunnel6Record.namestorage.c);

		LogInfo("AutoTunnel6 server listening for connections on %##s[%.16a] :%##s[%.16a]",
			info->AutoTunnel6Record.namestorage.c,     &m->AutoTunnelRelayAddrIn,
			info->AutoTunnelHostRecord.namestorage.c, &m->AutoTunnelHostAddr);

		} else {LogInfo("RegisterAutoTunnel6Record: client context %p, RequestedPort %d, Address %.16a, record type %d", info->AutoTunnelNAT.clientContext, info->AutoTunnelNAT.RequestedPort, &m->AutoTunnelRelayAddrIn, info->AutoTunnel6Record.resrec.RecordType);}

	RegisterAutoTunnelHostRecord(m, info);
	// When the AutoTunnel6 record comes up, we need to kick racoon and update the status.
	// If we had a port mapping, we would have done it in RegisterAutoTunnelServiceRecords.
	// If we don't have a port mapping, we need to do it here.
	UpdateAnonymousRacoonConfig(m);		// Determine whether we need racoon to accept incoming connections
	UpdateAutoTunnelDomainStatus(m, info);
	}

mDNSlocal void DeregisterAutoTunnel6Record(mDNS *m, DomainAuthInfo *info)
	{
	LogInfo("DeregisterAutoTunnel6Record %##s", info->domain.c);

	if (info->AutoTunnel6Record.resrec.RecordType > kDNSRecordTypeDeregistering)
		{
		mStatus err = mDNS_Deregister(m, &info->AutoTunnel6Record);
		if (err)
			{
			info->AutoTunnel6Record.resrec.RecordType = kDNSRecordTypeUnregistered;
			info->AutoTunnel6Record.resrec.rdata->u.ipv6 = zerov6Addr;
			LogMsg("DeregisterAutoTunnel6Record error %d deregistering AutoTunnel6Record %##s", err, info->AutoTunnel6Record.namestorage.c);
			}
		else LogInfo("DeregisterAutoTunnel6Record: Deregistered AutoTunnel6 Record");
		}
	else LogInfo("DeregisterAutoTunnel6Record: Not deregistering AuoTunnel6 record state:%d", info->AutoTunnel6Record.resrec.RecordType);

	DeregisterAutoTunnelHostRecord(m, info);
	// UpdateAutoTunnelDomainStatus is careful enough not to turn it on if we don't have
	// a external port mapping. Otherwise, it will be turned off.
	mDNS_Lock(m);
	UpdateAutoTunnelDomainStatus(m, info);
	mDNS_Unlock(m);
	}

mDNSlocal void AutoTunnelRecordCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
	{
	DomainAuthInfo *info = (DomainAuthInfo *)rr->RecordContext;
	if (result == mStatus_MemFree)
		{
		LogInfo("AutoTunnelRecordCallback MemFree %s", ARDisplayString(m, rr));
		// Reset the host record namestorage to force high-level PTR/SRV/TXT to deregister
		if (rr == &info->AutoTunnelHostRecord)
			{
			rr->namestorage.c[0] = 0;
			m->NextSRVUpdate = NonZeroTime(m->timenow);
			LogInfo("AutoTunnelRecordCallback: NextSRVUpdate in %d %d", m->NextSRVUpdate - m->timenow, m->timenow);
			}
		if (m->ShutdownTime) {LogInfo("AutoTunnelRecordCallback: Shutdown, returning");return;}
		if (rr == &info->AutoTunnelHostRecord)
			{
			LogInfo("AutoTunnelRecordCallback: calling RegisterAutoTunnelHostRecord");
			RegisterAutoTunnelHostRecord(m,info);
			}
		else if (rr == &info->AutoTunnelDeviceInfo)
			{
			LogInfo("AutoTunnelRecordCallback: Calling RegisterAutoTunnelDevInfoRecord");
			RegisterAutoTunnelDevInfoRecord(m,info);
			}
		else if (rr == &info->AutoTunnelService || rr == &info->AutoTunnelTarget)
			{
			LogInfo("AutoTunnelRecordCallback: Calling RegisterAutoTunnelServiceRecords");
			RegisterAutoTunnelServiceRecords(m,info);
			}
		else if (rr == &info->AutoTunnel6Record)
			{
			LogInfo("AutoTunnelRecordCallback: Calling RegisterAutoTunnel6Record");
			info->AutoTunnel6Record.resrec.rdata->u.ipv6 = zerov6Addr;
			RegisterAutoTunnel6Record(m,info);
			}
		}
	}

#ifndef NO_SECURITYFRAMEWORK
mDNSlocal void AutoTunnelDeleteAuthInfoState(mDNS *m, DomainAuthInfo *info)
	{
	LogInfo("AutoTunnelDeleteAuthInfoState: Cleaning up state releated to Domain AuthInfo %##s", info->domain.c);

	m->NextSRVUpdate = NonZeroTime(m->timenow);
	DeregisterAutoTunnelDevInfoRecord(m, info);
	DeregisterAutoTunnelServiceRecords(m, info);
	DeregisterAutoTunnel6Record(m, info);
	UpdateAnonymousRacoonConfig(m);		// Determine whether we need racoon to accept incoming connections
	UpdateAutoTunnelDomainStatus(m, info);
	}
#endif // ndef NO_SECURITYFRAMEWORK

mDNSlocal void AutoTunnelNATCallback(mDNS *m, NATTraversalInfo *n)
	{
	DomainAuthInfo *info = (DomainAuthInfo *)n->clientContext;
	LogInfo("AutoTunnelNATCallback Result %d %.4a Internal %d External %d %#s.%##s",
		n->Result, &n->ExternalAddress, mDNSVal16(n->IntPort), mDNSVal16(n->ExternalPort), m->hostlabel.c, info->domain.c);

	m->NextSRVUpdate = NonZeroTime(m->timenow);
	LogInfo("AutoTunnelNATCallback: NextSRVUpdate in %d %d", m->NextSRVUpdate - m->timenow, m->timenow);

	DeregisterAutoTunnelServiceRecords(m, info);
	RegisterAutoTunnelServiceRecords(m, info);
	
	UpdateAnonymousRacoonConfig(m);		// Determine whether we need racoon to accept incoming connections

	UpdateAutoTunnelDomainStatus(m, (DomainAuthInfo *)n->clientContext);
	}

mDNSlocal void AbortDeregistration(mDNS *const m, AuthRecord *rr)
	{
	if (rr->resrec.RecordType == kDNSRecordTypeDeregistering)
		{
		LogInfo("Aborting deregistration of %s", ARDisplayString(m, rr));
		CompleteDeregistration(m, rr);
		}
	else if (rr->resrec.RecordType != kDNSRecordTypeUnregistered)
		LogMsg("AbortDeregistration ERROR RecordType %02X for %s", ARDisplayString(m, rr));
	}

mDNSlocal void AutoTunnelHostNameChanged(mDNS *m, DomainAuthInfo *info)
	{
	LogInfo("AutoTunnelHostNameChanged %#s.%##s", m->hostlabel.c, info->domain.c);

#ifndef NO_SECURITYFRAMEWORK
	DeregisterAutoTunnelDevInfoRecord(m, info);
#endif
	DeregisterAutoTunnelServiceRecords(m, info);
	DeregisterAutoTunnel6Record(m, info);
	RegisterAutoTunnelServiceRecords(m, info);

	mDNS_Lock(m);
	RegisterAutoTunnelDevInfoRecord(m, info);
	RegisterAutoTunnel6Record(m, info);
	m->NextSRVUpdate = NonZeroTime(m->timenow);
	mDNS_Unlock(m);
	}

mDNSlocal void SetupLocalAutoTunnel6Records(mDNS *const m, DomainAuthInfo *info)
	{
	AbortDeregistration(m, &info->AutoTunnelDeviceInfo);
	AbortDeregistration(m, &info->AutoTunnel6Record);

	// When the BTMM is turned on/off too quickly, following things happen.
	//
	// 1. Turning off BTMM triggers deregistration of the DevInfo/AutoTunnel6 etc. records
	// 2. While (1) is in progress, the BTMM is turned on
	//
	// At step (2), mDNS_SetSecretForDomain clears info->deltime indicating that the domain is valid
	// while we have not processed the turning off BTMM completely. Hence, we end up calling this
	// function to re-register the records. AbortDeregistration above aborts the Deregistration as the
	// records are still in Deregistering state and in AutoTunnelRecordCallback we end up registering
	// again. So, we have to be careful below to not call mDNS_SetupResourceRecord again which will
	// reset the state to Unregistered and registering again will lead to error as it is registered
	// and already in the list. Hence, register below only if needed.

	if (info->AutoTunnelDeviceInfo.resrec.RecordType != kDNSRecordTypeUnregistered ||
	    info->AutoTunnel6Record.resrec.RecordType != kDNSRecordTypeUnregistered)
		{
		LogInfo("SetupLocalAutoTunnel6Records: AutoTunnel Records not in Unregistered state: Device: %d, AutoTunnel6:%d",
			info->AutoTunnelDeviceInfo.resrec.RecordType, info->AutoTunnel6Record.resrec.RecordType);
		}

	if (info->AutoTunnelDeviceInfo.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		mDNS_SetupResourceRecord(&info->AutoTunnelDeviceInfo, mDNSNULL, mDNSInterface_Any, kDNSType_TXT,  kStandardTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info);
		RegisterAutoTunnelDevInfoRecord(m, info);
		}
	if (info->AutoTunnel6Record.resrec.RecordType == kDNSRecordTypeUnregistered)
		{
		mDNS_SetupResourceRecord(&info->AutoTunnel6Record, mDNSNULL, mDNSInterface_Any, kDNSType_AAAA, kHostNameTTL, kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info);
		RegisterAutoTunnel6Record(m, info);
		}

	UpdateAnonymousRacoonConfig(m);		// Determine whether we need racoon to accept incoming connections
	
	UpdateAutoTunnelDomainStatus(m, info);
	m->NextSRVUpdate = NonZeroTime(m->timenow);
	}

// Before SetupLocalAutoTunnelInterface_internal is called,
// m->AutoTunnelHostAddr.b[0] must be non-zero, and there must be at least one TunnelClient or TunnelServer
// Must be called with the lock held
mDNSexport void SetupLocalAutoTunnelInterface_internal(mDNS *const m, mDNSBool servicesStarting)
	{
	// 1. Configure the local IPv6 ULA BTMM address
	if (!m->AutoTunnelHostAddrActive)
		{
		m->AutoTunnelHostAddrActive = mDNStrue;
		LogInfo("SetupLocalAutoTunnelInterface_internal: Setting up AutoTunnel address %.16a", &m->AutoTunnelHostAddr);
		(void)mDNSAutoTunnelInterfaceUpDown(kmDNSUp, m->AutoTunnelHostAddr.b);
		}

	// 2. If we have at least one server (pending) listening, publish our records
	// The services may not be in the list when it is first trying to resolve the target of the SRV record.
	// servicesStarting is an indication of that. Use that instead of looking up in the list of Services/Records.
	if (servicesStarting || TunnelServers(m))
		{
		DomainAuthInfo *info;
		for (info = m->AuthInfoList; info; info = info->next)
			{
			if (info->AutoTunnel && !info->deltime && !info->AutoTunnelNAT.clientContext)
				{
				// If we just resurrected a DomainAuthInfo that is still deregistering, we need to abort the
				// deregistration process before re-using the AuthRecord memory
				//
				// Note: We don't need the same caution as in SetupLocalAutoTunnel6Records (see the comments there)
				// as AutoTunnelRecordCallback (called as a result of AbortDeregistration) will not end up registering
				// the records because clientContext is still NULL

				AbortDeregistration(m, &info->AutoTunnelTarget);
				AbortDeregistration(m, &info->AutoTunnelService);
				AbortDeregistration(m, &info->AutoTunnelHostRecord);

				mDNS_SetupResourceRecord(&info->AutoTunnelTarget,     mDNSNULL, mDNSInterface_Any, kDNSType_A,    kHostNameTTL,
					kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info);
				mDNS_SetupResourceRecord(&info->AutoTunnelService,    mDNSNULL, mDNSInterface_Any, kDNSType_SRV,  kHostNameTTL,
					kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info);
				mDNS_SetupResourceRecord(&info->AutoTunnelHostRecord, mDNSNULL, mDNSInterface_Any, kDNSType_AAAA, kHostNameTTL,
					kDNSRecordTypeUnregistered, AuthRecordAny, AutoTunnelRecordCallback, info);

				// Try to get a NAT port mapping for the AutoTunnelService
				info->AutoTunnelNAT.clientCallback   = AutoTunnelNATCallback;
				info->AutoTunnelNAT.clientContext    = info;
				info->AutoTunnelNAT.Protocol         = NATOp_MapUDP;
				info->AutoTunnelNAT.IntPort          = IPSECPort;
				info->AutoTunnelNAT.RequestedPort    = IPSECPort;
				info->AutoTunnelNAT.NATLease         = 0;
				mStatus err = mDNS_StartNATOperation_internal(m, &info->AutoTunnelNAT);
				if (err) LogMsg("SetupLocalAutoTunnelInterface_internal: error %d starting NAT mapping", err);

				// Register the records that can be done without communicating with the NAT
				//
				// Note: This should be done after we setup the AutoTunnelNAT information
				// as some of the fields in that structure are used to infer other information
				// e.g., is it okay to register now ?

				SetupLocalAutoTunnel6Records(m, info);

				}
			}
		}
	}

mDNSlocal mStatus AutoTunnelSetKeys(ClientTunnel *tun, mDNSBool AddNew)
	{
	mDNSv6Addr loc_outer6;
	mDNSv6Addr rmt_outer6;

	// When we are tunneling over IPv6 Relay address, the port number is zero
	if (mDNSIPPortIsZero(tun->rmt_outer_port))
		{
		loc_outer6 = tun->loc_outer6;
		rmt_outer6 = tun->rmt_outer6;
		}
	else
		{
		loc_outer6 = zerov6Addr;
		loc_outer6.b[0] = tun->loc_outer.b[0];
		loc_outer6.b[1] = tun->loc_outer.b[1];
		loc_outer6.b[2] = tun->loc_outer.b[2];
		loc_outer6.b[3] = tun->loc_outer.b[3];

		rmt_outer6 = zerov6Addr;
		rmt_outer6.b[0] = tun->rmt_outer.b[0];
		rmt_outer6.b[1] = tun->rmt_outer.b[1];
		rmt_outer6.b[2] = tun->rmt_outer.b[2];
		rmt_outer6.b[3] = tun->rmt_outer.b[3];
		}

	return(mDNSAutoTunnelSetKeys(AddNew ? kmDNSAutoTunnelSetKeysReplace : kmDNSAutoTunnelSetKeysDelete, tun->loc_inner.b, loc_outer6.b, kRacoonPort, tun->rmt_inner.b, rmt_outer6.b, mDNSVal16(tun->rmt_outer_port), tun->prefix, SkipLeadingLabels(&tun->dstname, 1)));
	}

// If the EUI-64 part of the IPv6 ULA matches, then that means the two addresses point to the same machine
#define mDNSSameClientTunnel(A,B) ((A)->l[2] == (B)->l[2] && (A)->l[3] == (B)->l[3])

mDNSlocal void ReissueBlockedQuestionWithType(mDNS *const m, domainname *d, mDNSBool success, mDNSu16 qtype)
	{
	DNSQuestion *q = m->Questions;
	while (q)
		{
		if (q->NoAnswer == NoAnswer_Suspended && q->qtype == qtype && q->AuthInfo && q->AuthInfo->AutoTunnel && SameDomainName(&q->qname, d))
			{
			LogInfo("Restart %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
			mDNSQuestionCallback *tmp = q->QuestionCallback;
			q->QuestionCallback = AutoTunnelCallback;	// Set QuestionCallback to suppress another call back to AddNewClientTunnel
			mDNS_StopQuery(m, q);
			mDNS_StartQuery(m, q);
			q->QuestionCallback = tmp;					// Restore QuestionCallback back to the real value
			if (!success) q->NoAnswer = NoAnswer_Fail;
			// When we call mDNS_StopQuery, it's possible for other subordinate questions like the GetZoneData query to be cancelled too.
			// In general we have to assume that the question list might have changed in arbitrary ways.
			// This code is itself called from a question callback, so the m->CurrentQuestion mechanism is
			// already in use. The safest solution is just to go back to the start of the list and start again.
			// In principle this sounds like an n^2 algorithm, but in practice we almost always activate
			// just one suspended question, so it's really a 2n algorithm.
			q = m->Questions;
			}
		else
			q = q->next;
		}
	}

mDNSlocal void ReissueBlockedQuestions(mDNS *const m, domainname *d, mDNSBool success)
	{
	// 1. We deliberately restart AAAA queries before A queries, because in the common case where a BTTM host has
	//    a v6 address but no v4 address, we prefer the caller to get the positive AAAA response before the A NXDOMAIN.
	// 2. In the case of AAAA queries, if our tunnel setup failed, then we return a deliberate failure indication to the caller --
	//    even if the name does have a valid AAAA record, we don't want clients trying to connect to it without a properly encrypted tunnel.
	// 3. For A queries we never fabricate failures -- if a BTTM service is really using raw IPv4, then it doesn't need the IPv6 tunnel.
	ReissueBlockedQuestionWithType(m, d, success, kDNSType_AAAA);
	ReissueBlockedQuestionWithType(m, d, mDNStrue, kDNSType_A);
	}

mDNSlocal void UnlinkAndReissueBlockedQuestions(mDNS *const m, ClientTunnel *tun, mDNSBool success)
	{
	ClientTunnel **p = &m->TunnelClients;
	while (*p != tun && *p) p = &(*p)->next;
	if (*p) *p = tun->next;
	ReissueBlockedQuestions(m, &tun->dstname, success);
	LogInfo("UnlinkAndReissueBlockedQuestions: Disposing ClientTunnel %p", tun);
	freeL("ClientTunnel", tun);
	}

mDNSlocal mDNSBool TunnelClientDeleteMatching(mDNS *const m, ClientTunnel *tun, mDNSBool v6Tunnel)
	{
	ClientTunnel **p;
	mDNSBool needSetKeys = mDNStrue;

	p = &tun->next;
	while (*p)
		{
		// Is this a tunnel to the same host that we are trying to setup now?
		if (!mDNSSameClientTunnel(&(*p)->rmt_inner, &tun->rmt_inner)) p = &(*p)->next;
		else
			{
			ClientTunnel *old = *p;
			if (v6Tunnel)
				{
				if (!mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue; }
				LogInfo("TunnelClientDeleteMatching: Found existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
				if (old->q.ThisQInterval >= 0)
					{
					LogInfo("TunnelClientDeleteMatching: Stopping query on IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
					mDNS_StopQuery(m, &old->q);
					}
				else if (!mDNSSameIPv6Address((*p)->rmt_inner, tun->rmt_inner) ||
						!mDNSSameIPv6Address(old->loc_inner, tun->loc_inner)   ||
					 	!mDNSSameIPv6Address(old->loc_outer6, tun->loc_outer6) ||
					 	!mDNSSameIPv6Address(old->rmt_outer6, tun->rmt_outer6))
					{
					// Delete the old tunnel if the current tunnel to the same host does not have the same ULA or
					// the other parameters of the tunnel are different
					LogInfo("TunnelClientDeleteMatching: Deleting existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
					AutoTunnelSetKeys(old, mDNSfalse);
					}
				else
					{
					// Reusing the existing tunnel means that we reuse the IPsec SAs and the policies. We delete the old
					// as "tun" and "old" are identical
					LogInfo("TunnelClientDeleteMatching: Reusing the existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c,
						&old->rmt_inner);
					needSetKeys = mDNSfalse;
					}
				}
			else 
				{
				if (mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue; }
				LogInfo("TunnelClientDeleteMatching: Found existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
				if (old->q.ThisQInterval >= 0)
					{
					LogInfo("TunnelClientDeleteMatching: Stopping query on IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
					mDNS_StopQuery(m, &old->q);
					}
				else if (!mDNSSameIPv6Address((*p)->rmt_inner, tun->rmt_inner) ||
						!mDNSSameIPv6Address(old->loc_inner, tun->loc_inner)   ||
					 	!mDNSSameIPv4Address(old->loc_outer, tun->loc_outer)   ||
					 	!mDNSSameIPv4Address(old->rmt_outer, tun->rmt_outer)   ||
					 	!mDNSSameIPPort(old->rmt_outer_port, tun->rmt_outer_port))
					{
					// Delete the old tunnel if the current tunnel to the same host does not have the same ULA or
					// the other parameters of the tunnel are different
					LogInfo("TunnelClientDeleteMatching: Deleting existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
					AutoTunnelSetKeys(old, mDNSfalse);
					}
				else
					{
					// Reusing the existing tunnel means that we reuse the IPsec SAs and the policies. We delete the old
					// as "tun" and "old" are identical
					LogInfo("TunnelClientDeleteMatching: Reusing the existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c,
						&old->rmt_inner);
					needSetKeys = mDNSfalse;
					}
				}

			*p = old->next;
			LogInfo("TunnelClientDeleteMatching: Disposing ClientTunnel %p", old);
			freeL("ClientTunnel", old);
			}
		}
		return needSetKeys;
	}

// v6Tunnel indicates whether to delete a tunnel whose outer header is IPv6. If false, outer IPv4
// tunnel will be deleted
mDNSlocal void TunnelClientDeleteAny(mDNS *const m, ClientTunnel *tun, mDNSBool v6Tunnel)
	{
	ClientTunnel **p;

	p = &tun->next;
	while (*p)
		{
		// If there is more than one client tunnel to the same host, delete all of them.
		// We do this by just checking against the EUI64 rather than the full address
		if (!mDNSSameClientTunnel(&(*p)->rmt_inner, &tun->rmt_inner)) p = &(*p)->next;
		else
			{
			ClientTunnel *old = *p;
			if (v6Tunnel)
				{
				if (!mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue;}
				LogInfo("TunnelClientDeleteAny: Found existing IPv6 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
				}
			else 
				{
				if (mDNSIPPortIsZero(old->rmt_outer_port)) { p = &old->next; continue;}
				LogInfo("TunnelClientDeleteAny: Found existing IPv4 AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
				}
			if (old->q.ThisQInterval >= 0)
				{
				LogInfo("TunnelClientDeleteAny: Stopping query on AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
				mDNS_StopQuery(m, &old->q);
				}
			else 
				{
				LogInfo("TunnelClientDeleteAny: Deleting existing AutoTunnel for %##s %.16a", old->dstname.c, &old->rmt_inner);
				AutoTunnelSetKeys(old, mDNSfalse);
				}
			*p = old->next;
			LogInfo("TunnelClientDeleteAny: Disposing ClientTunnel %p", old);
			freeL("ClientTunnel", old);
			}
		}
	}

mDNSlocal void TunnelClientFinish(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer)
	{
	mDNSBool needSetKeys = mDNStrue;
	ClientTunnel *tun = (ClientTunnel *)question->QuestionContext;
	mDNSBool v6Tunnel = mDNSfalse;

	// If the port is zero, then we have a relay address of the peer
	if (mDNSIPPortIsZero(tun->rmt_outer_port))
		v6Tunnel = mDNStrue;

	if (v6Tunnel)
		{
		LogInfo("TunnelClientFinish: Relay address %.16a", &answer->rdata->u.ipv6);
		tun->rmt_outer6 = answer->rdata->u.ipv6;
		tun->loc_outer6 = m->AutoTunnelRelayAddrOut;
		}
	else
		{
		LogInfo("TunnelClientFinish: SRV target address %.4a", &answer->rdata->u.ipv4);
		tun->rmt_outer = answer->rdata->u.ipv4;
		mDNSAddr tmpDst = { mDNSAddrType_IPv4, {{{0}}} };
		tmpDst.ip.v4 = tun->rmt_outer;
		mDNSAddr tmpSrc = zeroAddr;
		mDNSPlatformSourceAddrForDest(&tmpSrc, &tmpDst);
		if (tmpSrc.type == mDNSAddrType_IPv4) tun->loc_outer = tmpSrc.ip.v4;
		else tun->loc_outer = m->AdvertisedV4.ip.v4;
		}

	question->ThisQInterval = -1;		// So we know this tunnel setup has completed
	tun->loc_inner = m->AutoTunnelHostAddr;

	// If we found a v6Relay address for our peer, delete all the v4Tunnels for our peer and
	// look for existing tunnels to see whether they have the same information for our peer.
	// If not, delete them and need to create a new tunnel. If they are same, just use the
	// same tunnel. Do the similar thing if we found a v4Tunnel end point for our peer.
	TunnelClientDeleteAny(m, tun, !v6Tunnel);
	needSetKeys = TunnelClientDeleteMatching(m, tun, v6Tunnel);

	if (needSetKeys) LogInfo("TunnelClientFinish: New %s AutoTunnel for %##s %.16a", (v6Tunnel ? "IPv6" : "IPv4"), tun->dstname.c, &tun->rmt_inner);
	else LogInfo("TunnelClientFinish: Reusing exiting %s AutoTunnel for %##s %.16a", (v6Tunnel ? "IPv6" : "IPv4"), tun->dstname.c, &tun->rmt_inner);

	if (m->AutoTunnelHostAddr.b[0]) { mDNS_Lock(m); SetupLocalAutoTunnelInterface_internal(m, mDNSfalse); mDNS_Unlock(m); };

	mStatus result = needSetKeys ? AutoTunnelSetKeys(tun, mDNStrue) : mStatus_NoError;
	static char msgbuf[32];
	mDNS_snprintf(msgbuf, sizeof(msgbuf), "Tunnel setup - %d", result);
	mDNSASLLog((uuid_t *)&m->asl_uuid, "autotunnel.config", result ? "failure" : "success", msgbuf, "");
	// Kick off any questions that were held pending this tunnel setup
	ReissueBlockedQuestions(m, &tun->dstname, (result == mStatus_NoError) ? mDNStrue : mDNSfalse);
	}

mDNSexport void AutoTunnelCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
	{
	ClientTunnel *tun = (ClientTunnel *)question->QuestionContext;

	LogInfo("AutoTunnelCallback tun %p AddRecord %d rdlength %d qtype %d", tun, AddRecord, answer->rdlength, question->qtype);

	if (!AddRecord) return;
	mDNS_StopQuery(m, question);

	// If we are looking up the AAAA record for _autotunnel6, don't consider it as failure.
	// The code below will look for _autotunnel._udp SRV record followed by A record
	if (tun->tc_state != TC_STATE_AAAA_PEER_RELAY && !answer->rdlength)
		{
		LogInfo("AutoTunnelCallback NXDOMAIN %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
		static char msgbuf[16];
		mDNS_snprintf(msgbuf, sizeof(msgbuf), "%s lookup", DNSTypeName(question->qtype));
		mDNSASLLog((uuid_t *)&m->asl_uuid, "autotunnel.config", "failure", msgbuf, "");
		UnlinkAndReissueBlockedQuestions(m, tun, mDNSfalse);
		return;
		}

	switch (tun->tc_state)
		{
		case TC_STATE_AAAA_PEER:
			if (question->qtype != kDNSType_AAAA)
				{
				LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_AAAA_PEER", question->qtype);
				}
			if (mDNSSameIPv6Address(answer->rdata->u.ipv6, m->AutoTunnelHostAddr))
				{
				LogInfo("AutoTunnelCallback: suppressing tunnel to self %.16a", &answer->rdata->u.ipv6);
				UnlinkAndReissueBlockedQuestions(m, tun, mDNStrue);
				return;
				}
			tun->rmt_inner = answer->rdata->u.ipv6;
			LogInfo("AutoTunnelCallback:TC_STATE_AAAA_PEER: dst host %.16a", &tun->rmt_inner);
			if (!mDNSIPv6AddressIsZero(m->AutoTunnelRelayAddrOut))
				{
				LogInfo("AutoTunnelCallback: Looking up _autotunnel6 AAAA");
				tun->tc_state = TC_STATE_AAAA_PEER_RELAY;
				question->qtype = kDNSType_AAAA;
				AssignDomainName(&question->qname, (const domainname*) "\x0C" "_autotunnel6");
				}
			else
				{
				LogInfo("AutoTunnelCallback: Looking up _autotunnel._udp SRV");
				tun->tc_state = TC_STATE_SRV_PEER;
				question->qtype = kDNSType_SRV;
				AssignDomainName(&question->qname, (const domainname*) "\x0B" "_autotunnel" "\x04" "_udp");
				}
			AppendDomainName(&question->qname, &tun->dstname);
			mDNS_StartQuery(m, &tun->q);
			return;
		case TC_STATE_AAAA_PEER_RELAY:
			if (question->qtype != kDNSType_AAAA)
				{
				LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_AAAA_PEER_RELAY", question->qtype);
				}
			// If it failed, look for the SRV record.
			if (!answer->rdlength)
				{
				LogInfo("AutoTunnelCallback: Looking up _autotunnel6 AAAA failed, trying SRV");
				tun->tc_state = TC_STATE_SRV_PEER;
				AssignDomainName(&question->qname, (const domainname*) "\x0B" "_autotunnel" "\x04" "_udp");
				AppendDomainName(&question->qname, &tun->dstname);
				question->qtype = kDNSType_SRV;
				mDNS_StartQuery(m, &tun->q);
				return;
				}
			TunnelClientFinish(m, question, answer);
			return;
		case TC_STATE_SRV_PEER:
			if (question->qtype != kDNSType_SRV)
				{
				LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_SRV_PEER", question->qtype);
				}
			LogInfo("AutoTunnelCallback: SRV target name %##s", answer->rdata->u.srv.target.c);
			tun->tc_state = TC_STATE_ADDR_PEER;
			AssignDomainName(&tun->q.qname, &answer->rdata->u.srv.target);
			tun->rmt_outer_port = answer->rdata->u.srv.port;
			question->qtype = kDNSType_A;
			mDNS_StartQuery(m, &tun->q);
			return;
		case TC_STATE_ADDR_PEER:
			if (question->qtype != kDNSType_A)
				{
				LogMsg("AutoTunnelCallback: Bad question type %d in TC_STATE_ADDR_PEER", question->qtype);
				}
			TunnelClientFinish(m, question, answer);
			return;
		default:
			LogMsg("AutoTunnelCallback: Unknown question %p", question);
		}
	}

// Must be called with the lock held
mDNSexport void AddNewClientTunnel(mDNS *const m, DNSQuestion *const q)
	{
	ClientTunnel *p = mallocL("ClientTunnel", sizeof(ClientTunnel));
	if (!p) return;
	p->prefix = q->AuthInfo->AutoTunnel;
	AssignDomainName(&p->dstname, &q->qname);
	p->MarkedForDeletion = mDNSfalse;
	p->loc_inner      = zerov6Addr;
	p->loc_outer      = zerov4Addr;
	p->loc_outer6     = zerov6Addr;
	p->rmt_inner      = zerov6Addr;
	p->rmt_outer      = zerov4Addr;
	p->rmt_outer6     = zerov6Addr;
	p->rmt_outer_port = zeroIPPort;
	p->tc_state = TC_STATE_AAAA_PEER;
	p->next = m->TunnelClients;
	m->TunnelClients = p;		// We intentionally build list in reverse order

	p->q.InterfaceID      = mDNSInterface_Any;
	p->q.Target           = zeroAddr;
	AssignDomainName(&p->q.qname, &q->qname);
	p->q.qtype            = kDNSType_AAAA;
	p->q.qclass           = kDNSClass_IN;
	p->q.LongLived        = mDNSfalse;
	p->q.ExpectUnique     = mDNStrue;
	p->q.ForceMCast       = mDNSfalse;
	p->q.ReturnIntermed   = mDNStrue;
	p->q.SuppressUnusable = mDNSfalse;
	p->q.SearchListIndex  = 0;
	p->q.AppendSearchDomains = 0;
	p->q.RetryWithSearchDomains = mDNSfalse;
	p->q.TimeoutQuestion  = 0;
	p->q.WakeOnResolve    = 0;
	p->q.qnameOrig        = mDNSNULL;
	p->q.QuestionCallback = AutoTunnelCallback;
	p->q.QuestionContext  = p;

	LogInfo("AddNewClientTunnel start tun %p %##s (%s)%s", p, &q->qname.c, DNSTypeName(q->qtype), q->LongLived ? " LongLived" : "");
	mDNS_StartQuery_internal(m, &p->q);
	}

#endif // APPLE_OSX_mDNSResponder

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Power State & Configuration Change Management
#endif

mDNSlocal mStatus UpdateInterfaceList(mDNS *const m, mDNSs32 utc)
	{
	mDNSBool foundav4           = mDNSfalse;
	mDNSBool foundav6           = mDNSfalse;
	struct ifaddrs *ifa         = myGetIfAddrs(1);
	struct ifaddrs *v4Loopback  = NULL;
	struct ifaddrs *v6Loopback  = NULL;
	char defaultname[64];
#ifndef NO_IPV6
	int InfoSocket              = socket(AF_INET6, SOCK_DGRAM, 0);
	if (InfoSocket < 3 && errno != EAFNOSUPPORT) LogMsg("UpdateInterfaceList: InfoSocket error %d errno %d (%s)", InfoSocket, errno, strerror(errno));
#endif

	// During wakeup, we may get a network change notification e.g., new addresses, before we get
	// a wake notification. This means that we have not set AnnounceOwner. Registering interfaces with
	// core would cause us to probe again which will conflict with the sleep proxy server, if we had
	// registered with it when going to sleep. Hence, need to delay until we get the wake notification

	if (m->SleepState == SleepState_Sleeping) ifa = NULL;

	while (ifa)
		{
#if LIST_ALL_INTERFACES
		if (ifa->ifa_addr->sa_family == AF_APPLETALK)
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d is AF_APPLETALK",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
		else if (ifa->ifa_addr->sa_family == AF_LINK)
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d is AF_LINK",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
		else if (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6)
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d not AF_INET (2) or AF_INET6 (30)",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
		if (!(ifa->ifa_flags & IFF_UP))
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface not IFF_UP",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
		if (!(ifa->ifa_flags & IFF_MULTICAST))
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface not IFF_MULTICAST",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
		if (ifa->ifa_flags & IFF_POINTOPOINT)
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface IFF_POINTOPOINT",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
		if (ifa->ifa_flags & IFF_LOOPBACK)
			LogMsg("UpdateInterfaceList: %5s(%d) Flags %04X Family %2d Interface IFF_LOOPBACK",
				ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family);
#endif

		if (ifa->ifa_addr->sa_family == AF_LINK)
			{
			struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
			if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == sizeof(m->PrimaryMAC) && mDNSSameEthAddress(&m->PrimaryMAC, &zeroEthAddr))
				mDNSPlatformMemCopy(m->PrimaryMAC.b, sdl->sdl_data + sdl->sdl_nlen, 6);
			}

		if (ifa->ifa_flags & IFF_UP && ifa->ifa_addr)
			if (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6)
				{
				if (!ifa->ifa_netmask)
					{
					mDNSAddr ip;
					SetupAddr(&ip, ifa->ifa_addr);
					LogMsg("getifaddrs: ifa_netmask is NULL for %5s(%d) Flags %04X Family %2d %#a",
						ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family, &ip);
					}
				// Apparently it's normal for the sa_family of an ifa_netmask to sometimes be zero, so we don't complain about that
				// <rdar://problem/5492035> getifaddrs is returning invalid netmask family for fw0 and vmnet
				else if (ifa->ifa_netmask->sa_family != ifa->ifa_addr->sa_family && ifa->ifa_netmask->sa_family != 0)
					{
					mDNSAddr ip;
					SetupAddr(&ip, ifa->ifa_addr);
					LogMsg("getifaddrs ifa_netmask for %5s(%d) Flags %04X Family %2d %#a has different family: %d",
						ifa->ifa_name, if_nametoindex(ifa->ifa_name), ifa->ifa_flags, ifa->ifa_addr->sa_family, &ip, ifa->ifa_netmask->sa_family);
					}
					// Currently we use a few internal ones like mDNSInterfaceID_LocalOnly etc. that are negative values (0, -1, -2).
				else if ((int)if_nametoindex(ifa->ifa_name) <= 0)
					{
					LogMsg("UpdateInterfaceList: if_nametoindex returned zero/negative value for %5s(%d)", ifa->ifa_name, if_nametoindex(ifa->ifa_name));
					}
				else
					{
					// Make sure ifa_netmask->sa_family is set correctly
					// <rdar://problem/5492035> getifaddrs is returning invalid netmask family for fw0 and vmnet
					ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family;
					int ifru_flags6 = 0;
#ifndef NO_IPV6
					struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
					if (ifa->ifa_addr->sa_family == AF_INET6 && InfoSocket >= 0)
						{
						struct in6_ifreq ifr6;
						mDNSPlatformMemZero((char *)&ifr6, sizeof(ifr6));
						strlcpy(ifr6.ifr_name, ifa->ifa_name, sizeof(ifr6.ifr_name));
						ifr6.ifr_addr = *sin6;
						if (ioctl(InfoSocket, SIOCGIFAFLAG_IN6, &ifr6) != -1)
							ifru_flags6 = ifr6.ifr_ifru.ifru_flags6;
						verbosedebugf("%s %.16a %04X %04X", ifa->ifa_name, &sin6->sin6_addr, ifa->ifa_flags, ifru_flags6);
						}
#endif
					if (!(ifru_flags6 & (IN6_IFF_NOTREADY | IN6_IFF_DETACHED | IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY)))
						{
						if (ifa->ifa_flags & IFF_LOOPBACK)
							{
							if (ifa->ifa_addr->sa_family == AF_INET)     v4Loopback = ifa;
#ifndef NO_IPV6
							else if (sin6->sin6_addr.s6_addr[0] != 0xFD) v6Loopback = ifa;
#endif
							}
						else
							{
							NetworkInterfaceInfoOSX *i = AddInterfaceToList(m, ifa, utc);
							if (i && MulticastInterface(i) && i->ifinfo.Advertise)
								{
								if (ifa->ifa_addr->sa_family == AF_INET) foundav4 = mDNStrue;
								else                                     foundav6 = mDNStrue;
								}
							}
						}
					}
				}
		ifa = ifa->ifa_next;
		}

	// For efficiency, we don't register a loopback interface when other interfaces of that family are available and advertising
	if (!foundav4 && v4Loopback) AddInterfaceToList(m, v4Loopback, utc);
	if (!foundav6 && v6Loopback) AddInterfaceToList(m, v6Loopback, utc);

	// Now the list is complete, set the McastTxRx setting for each interface.
	NetworkInterfaceInfoOSX *i;
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->Exists)
			{
			mDNSBool txrx = MulticastInterface(i);
#if USE_V6_ONLY_WHEN_NO_ROUTABLE_V4
			txrx = txrx && ((i->ifinfo.ip.type == mDNSAddrType_IPv4) || !FindRoutableIPv4(m, i->scope_id));
#endif
			if (i->ifinfo.McastTxRx != txrx)
				{
				i->ifinfo.McastTxRx = txrx;
				i->Exists = 2; // State change; need to deregister and reregister this interface
				}
			}

#ifndef NO_IPV6
	if (InfoSocket >= 0) close(InfoSocket);
#endif

	// If we haven't set up AutoTunnelHostAddr yet, do it now
	if (!mDNSSameEthAddress(&m->PrimaryMAC, &zeroEthAddr) && m->AutoTunnelHostAddr.b[0] == 0)
		{
		m->AutoTunnelHostAddr.b[0x0] = 0xFD;		// Required prefix for "locally assigned" ULA (See RFC 4193)
		m->AutoTunnelHostAddr.b[0x1] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x2] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x3] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x4] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x5] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x6] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x7] = mDNSRandom(255);
		m->AutoTunnelHostAddr.b[0x8] = m->PrimaryMAC.b[0] ^ 0x02;	// See RFC 3513, Appendix A for explanation
		m->AutoTunnelHostAddr.b[0x9] = m->PrimaryMAC.b[1];
		m->AutoTunnelHostAddr.b[0xA] = m->PrimaryMAC.b[2];
		m->AutoTunnelHostAddr.b[0xB] = 0xFF;
		m->AutoTunnelHostAddr.b[0xC] = 0xFE;
		m->AutoTunnelHostAddr.b[0xD] = m->PrimaryMAC.b[3];
		m->AutoTunnelHostAddr.b[0xE] = m->PrimaryMAC.b[4];
		m->AutoTunnelHostAddr.b[0xF] = m->PrimaryMAC.b[5];
		m->AutoTunnelLabel.c[0] = mDNS_snprintf((char*)m->AutoTunnelLabel.c+1, 254, "AutoTunnel-%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X",
			m->AutoTunnelHostAddr.b[0x8], m->AutoTunnelHostAddr.b[0x9], m->AutoTunnelHostAddr.b[0xA], m->AutoTunnelHostAddr.b[0xB],
			m->AutoTunnelHostAddr.b[0xC], m->AutoTunnelHostAddr.b[0xD], m->AutoTunnelHostAddr.b[0xE], m->AutoTunnelHostAddr.b[0xF]);
		LogInfo("m->AutoTunnelLabel %#s", m->AutoTunnelLabel.c);
		}
	
	mDNS_snprintf(defaultname, sizeof(defaultname), "%.*s-%02X%02X%02X%02X%02X%02X", HINFO_HWstring_prefixlen, HINFO_HWstring,
		m->PrimaryMAC.b[0], m->PrimaryMAC.b[1], m->PrimaryMAC.b[2], m->PrimaryMAC.b[3], m->PrimaryMAC.b[4], m->PrimaryMAC.b[5]);

	// Set up the nice label
	domainlabel nicelabel;
	nicelabel.c[0] = 0;
	GetUserSpecifiedFriendlyComputerName(&nicelabel);
	if (nicelabel.c[0] == 0)
		{
		debugf("Couldn’t read user-specified Computer Name; using default “%s” instead", defaultname);
		MakeDomainLabelFromLiteralString(&nicelabel, defaultname);
		}

	// Set up the RFC 1034-compliant label
	domainlabel hostlabel;
	hostlabel.c[0] = 0;
	GetUserSpecifiedLocalHostName(&hostlabel);
	if (hostlabel.c[0] == 0)
		{
		debugf("Couldn’t read user-specified Local Hostname; using default “%s.local” instead", defaultname);
		MakeDomainLabelFromLiteralString(&hostlabel, defaultname);
		}

	mDNSBool namechange = mDNSfalse;

	// We use a case-sensitive comparison here because even though changing the capitalization
	// of the name alone is not significant to DNS, it's still a change from the user's point of view
	if (SameDomainLabelCS(m->p->usernicelabel.c, nicelabel.c))
		debugf("Usernicelabel (%#s) unchanged since last time; not changing m->nicelabel (%#s)", m->p->usernicelabel.c, m->nicelabel.c);
	else
		{
		if (m->p->usernicelabel.c[0])	// Don't show message first time through, when we first read name from prefs on boot
			LogMsg("User updated Computer Name from “%#s” to “%#s”", m->p->usernicelabel.c, nicelabel.c);
		m->p->usernicelabel = m->nicelabel = nicelabel;
		namechange = mDNStrue;
		}

	if (SameDomainLabelCS(m->p->userhostlabel.c, hostlabel.c))
		debugf("Userhostlabel (%#s) unchanged since last time; not changing m->hostlabel (%#s)", m->p->userhostlabel.c, m->hostlabel.c);
	else
		{
		if (m->p->userhostlabel.c[0])	// Don't show message first time through, when we first read name from prefs on boot
			LogMsg("User updated Local Hostname from “%#s” to “%#s”", m->p->userhostlabel.c, hostlabel.c);
		m->p->userhostlabel = m->hostlabel = hostlabel;
		mDNS_SetFQDN(m);
		namechange = mDNStrue;
		}

#if APPLE_OSX_mDNSResponder
	if (namechange)		// If either name has changed, we need to tickle our AutoTunnel state machine to update its registered records
		{
		DomainAuthInfo *info;
		for (info = m->AuthInfoList; info; info = info->next)
				if (info->AutoTunnel) AutoTunnelHostNameChanged(m, info);
		}
#endif // APPLE_OSX_mDNSResponder

	return(mStatus_NoError);
	}

// Returns number of leading one-bits in mask: 0-32 for IPv4, 0-128 for IPv6
// Returns -1 if all the one-bits are not contiguous
mDNSlocal int CountMaskBits(mDNSAddr *mask)
	{
	int i = 0, bits = 0;
	int bytes = mask->type == mDNSAddrType_IPv4 ? 4 : mask->type == mDNSAddrType_IPv6 ? 16 : 0;
	while (i < bytes)
		{
		mDNSu8 b = mask->ip.v6.b[i++];
		while (b & 0x80) { bits++; b <<= 1; }
		if (b) return(-1);
		}
	while (i < bytes) if (mask->ip.v6.b[i++]) return(-1);
	return(bits);
	}

// returns count of non-link local V4 addresses registered
mDNSlocal int SetupActiveInterfaces(mDNS *const m, mDNSs32 utc)
	{
	NetworkInterfaceInfoOSX *i;
	int count = 0;
	for (i = m->p->InterfaceList; i; i = i->next)
		if (i->Exists)
			{
			NetworkInterfaceInfo *const n = &i->ifinfo;
			NetworkInterfaceInfoOSX *primary = SearchForInterfaceByName(m, i->ifinfo.ifname, AAAA_OVER_V4 ? AF_UNSPEC : i->sa_family);
			if (!primary) LogMsg("SetupActiveInterfaces ERROR! SearchForInterfaceByName didn't find %s", i->ifinfo.ifname);

			if (i->Registered && i->Registered != primary)	// Sanity check
				{
				LogMsg("SetupActiveInterfaces ERROR! n->Registered %p != primary %p", i->Registered, primary);
				i->Registered = mDNSNULL;
				}

			if (!i->Registered)
				{
				// Note: If i->Registered is set, that means we've called mDNS_RegisterInterface() for this interface,
				// so we need to make sure we call mDNS_DeregisterInterface() before disposing it.
				// If i->Registered is NOT set, then we haven't registered it and we should not try to deregister it
				//

				i->Registered = primary;

				// If i->LastSeen == utc, then this is a brand-new interface, just created, or an interface that never went away.
				// If i->LastSeen != utc, then this is an old interface, previously seen, that went away for (utc - i->LastSeen) seconds.
				// If the interface is an old one that went away and came back in less than a minute, then we're in a flapping scenario.
				i->Occulting = !(i->ifa_flags & IFF_LOOPBACK) && (utc - i->LastSeen > 0 && utc - i->LastSeen < 60);

				// Temporary fix to handle P2P flapping. P2P reuses the scope-id, mac address and the IP address
				// everytime it creates a new interface. We think it is a duplicate and hence consider it
				// as flashing and occulting, that is, flapping. If an interface is marked as flapping, 
				// mDNS_RegisterInterface() changes the probe delay from 1/2 second to 5 seconds and
				// logs a warning message to system.log noting frequent interface transitions.
				if (strncmp(i->ifinfo.ifname, "p2p", 3) == 0)
					{
					LogInfo("SetupActiveInterfaces: P2P %s interface registering %s %s", i->ifinfo.ifname,
						i->Flashing               ? " (Flashing)"  : "",
						i->Occulting              ? " (Occulting)" : "");
					mDNS_RegisterInterface(m, n, 0);
					}
				else
					{
					mDNS_RegisterInterface(m, n, i->Flashing && i->Occulting);
					}

				if (!mDNSAddressIsLinkLocal(&n->ip)) count++;
				LogInfo("SetupActiveInterfaces:   Registered    %5s(%lu) %.6a InterfaceID %p(%p), primary %p, %#a/%d%s%s%s",
					i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i, primary, &n->ip, CountMaskBits(&n->mask),
					i->Flashing        ? " (Flashing)"  : "",
					i->Occulting       ? " (Occulting)" : "",
					n->InterfaceActive ? " (Primary)"   : "");

				if (!n->McastTxRx)
					debugf("SetupActiveInterfaces:   No Tx/Rx on   %5s(%lu) %.6a InterfaceID %p %#a", i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, &n->ip);
				else
					{
					if (i->sa_family == AF_INET)
						{
						struct ip_mreq imr;
						primary->ifa_v4addr.s_addr = n->ip.ip.v4.NotAnInteger;
						imr.imr_multiaddr.s_addr = AllDNSLinkGroup_v4.ip.v4.NotAnInteger;
						imr.imr_interface        = primary->ifa_v4addr;
	
						// If this is our *first* IPv4 instance for this interface name, we need to do a IP_DROP_MEMBERSHIP first,
						// before trying to join the group, to clear out stale kernel state which may be lingering.
						// In particular, this happens with removable network interfaces like USB Ethernet adapters -- the kernel has stale state
						// from the last time the USB Ethernet adapter was connected, and part of the kernel thinks we've already joined the group
						// on that interface (so we get EADDRINUSE when we try to join again) but a different part of the kernel thinks we haven't
						// joined the group (so we receive no multicasts). Doing an IP_DROP_MEMBERSHIP before joining seems to flush the stale state.
						// Also, trying to make the code leave the group when the adapter is removed doesn't work either,
						// because by the time we get the configuration change notification, the interface is already gone,
						// so attempts to unsubscribe fail with EADDRNOTAVAIL (errno 49 "Can't assign requested address").
						// <rdar://problem/5585972> IP_ADD_MEMBERSHIP fails for previously-connected removable interfaces
						if (SearchForInterfaceByName(m, i->ifinfo.ifname, AF_INET) == i)
							{
							LogInfo("SetupActiveInterfaces: %5s(%lu) Doing precautionary IP_DROP_MEMBERSHIP for %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface);
							mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_DROP_MEMBERSHIP, &imr, sizeof(imr));
							if (err < 0 && (errno != EADDRNOTAVAIL))
								LogMsg("setsockopt - IP_DROP_MEMBERSHIP error %d errno %d (%s)", err, errno, strerror(errno));
							}
	
						LogInfo("SetupActiveInterfaces: %5s(%lu) joining IPv4 mcast group %.4a on %.4a", i->ifinfo.ifname, i->scope_id, &imr.imr_multiaddr, &imr.imr_interface);
						mStatus err = setsockopt(m->p->permanentsockets.sktv4, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr));
						// Joining same group twice can give "Address already in use" error -- no need to report that
						if (err < 0 && (errno != EADDRINUSE))
							LogMsg("setsockopt - IP_ADD_MEMBERSHIP error %d errno %d (%s) group %.4a on %.4a", err, errno, strerror(errno), &imr.imr_multiaddr, &imr.imr_interface);
						}
#ifndef NO_IPV6
					if (i->sa_family == AF_INET6)
						{
						struct ipv6_mreq i6mr;
						i6mr.ipv6mr_interface = primary->scope_id;
						i6mr.ipv6mr_multiaddr = *(struct in6_addr*)&AllDNSLinkGroup_v6.ip.v6;
	
						if (SearchForInterfaceByName(m, i->ifinfo.ifname, AF_INET6) == i)
							{
							LogInfo("SetupActiveInterfaces: %5s(%lu) Doing precautionary IPV6_LEAVE_GROUP for %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
							mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &i6mr, sizeof(i6mr));
							if (err < 0 && (errno != EADDRNOTAVAIL))
								LogMsg("setsockopt - IPV6_LEAVE_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
							}
	
						LogInfo("SetupActiveInterfaces: %5s(%lu) joining IPv6 mcast group %.16a on %u", i->ifinfo.ifname, i->scope_id, &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
						mStatus err = setsockopt(m->p->permanentsockets.sktv6, IPPROTO_IPV6, IPV6_JOIN_GROUP, &i6mr, sizeof(i6mr));
						// Joining same group twice can give "Address already in use" error -- no need to report that
						if (err < 0 && (errno != EADDRINUSE))
							LogMsg("setsockopt - IPV6_JOIN_GROUP error %d errno %d (%s) group %.16a on %u", err, errno, strerror(errno), &i6mr.ipv6mr_multiaddr, i6mr.ipv6mr_interface);
						}
#endif
					}
				}
			}

	return count;
	}

mDNSlocal void MarkAllInterfacesInactive(mDNS *const m, mDNSs32 utc)
	{
	NetworkInterfaceInfoOSX *i;
	for (i = m->p->InterfaceList; i; i = i->next)
		{
		if (i->Exists) i->LastSeen = utc;
		i->Exists = mDNSfalse;
		}
	}

// returns count of non-link local V4 addresses deregistered
mDNSlocal int ClearInactiveInterfaces(mDNS *const m, mDNSs32 utc)
	{
	// First pass:
	// If an interface is going away, then deregister this from the mDNSCore.
	// We also have to deregister it if the primary interface that it's using for its InterfaceID is going away.
	// We have to do this because mDNSCore will use that InterfaceID when sending packets, and if the memory
	// it refers to has gone away we'll crash.
	NetworkInterfaceInfoOSX *i;
	int count = 0;
	for (i = m->p->InterfaceList; i; i = i->next)
		{
		// If this interface is no longer active, or its InterfaceID is changing, deregister it
		NetworkInterfaceInfoOSX *primary = SearchForInterfaceByName(m, i->ifinfo.ifname, AAAA_OVER_V4 ? AF_UNSPEC : i->sa_family);
		if (i->Registered)
			if (i->Exists == 0 || i->Exists == 2 || i->Registered != primary)
				{
				i->Flashing = !(i->ifa_flags & IFF_LOOPBACK) && (utc - i->AppearanceTime < 60);
				LogInfo("ClearInactiveInterfaces: Deregistering %5s(%lu) %.6a InterfaceID %p(%p), primary %p, %#a/%d%s%s%s",
					i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i, primary,
					&i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask),
					i->Flashing               ? " (Flashing)"  : "",
					i->Occulting              ? " (Occulting)" : "",
					i->ifinfo.InterfaceActive ? " (Primary)"   : "");

				// Temporary fix to handle P2P flapping. P2P reuses the scope-id, mac address and the IP address
				// everytime it creates a new interface. We think it is a duplicate and hence consider it
				// as flashing and occulting. The "core" does not flush the cache for this case. This leads to
				// stale data returned to the application even after the interface is removed. The application
				// then starts to send data but the new interface is not yet created.
				if (strncmp(i->ifinfo.ifname, "p2p", 3) == 0)
					{
					LogInfo("ClearInactiveInterfaces: P2P %s interface deregistering %s %s", i->ifinfo.ifname,
						i->Flashing               ? " (Flashing)"  : "",
						i->Occulting              ? " (Occulting)" : "");
					mDNS_DeregisterInterface(m, &i->ifinfo, 0);
					}
				else
					{
					mDNS_DeregisterInterface(m, &i->ifinfo, i->Flashing && i->Occulting);
					}
				if (!mDNSAddressIsLinkLocal(&i->ifinfo.ip)) count++;
				i->Registered = mDNSNULL;
				// Note: If i->Registered is set, that means we've called mDNS_RegisterInterface() for this interface,
				// so we need to make sure we call mDNS_DeregisterInterface() before disposing it.
				// If i->Registered is NOT set, then it's not registered and we should not call mDNS_DeregisterInterface() on it.

				// Caution: If we ever decide to add code here to leave the multicast group, we need to make sure that this
				// is the LAST representative of this physical interface, or we'll unsubscribe from the group prematurely.
				}
		}

	// Second pass:
	// Now that everything that's going to deregister has done so, we can clean up and free the memory
	NetworkInterfaceInfoOSX **p = &m->p->InterfaceList;
	while (*p)
		{
		i = *p;
		// If no longer active, delete interface from list and free memory
		if (!i->Exists)
			{
			if (i->LastSeen == utc) i->LastSeen = utc - 1;
			mDNSBool delete = (NumCacheRecordsForInterfaceID(m, i->ifinfo.InterfaceID) == 0) && (utc - i->LastSeen >= 60);
			LogInfo("ClearInactiveInterfaces: %-13s %5s(%lu) %.6a InterfaceID %p(%p) %#a/%d Age %d%s", delete ? "Deleting" : "Holding",
				i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i,
				&i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask), utc - i->LastSeen,
				i->ifinfo.InterfaceActive ? " (Primary)" : "");
#if APPLE_OSX_mDNSResponder
			if (i->BPF_fd >= 0) CloseBPF(i);
#endif // APPLE_OSX_mDNSResponder
			if (delete)
				{
				*p = i->next;
				freeL("NetworkInterfaceInfoOSX", i);
				continue;	// After deleting this object, don't want to do the "p = &i->next;" thing at the end of the loop
				}
			}
		p = &i->next;
		}
	return count;
	}

mDNSlocal void AppendDNameListElem(DNameListElem ***List, mDNSu32 uid, domainname *name)
	{
	DNameListElem *dnle = (DNameListElem*) mallocL("DNameListElem/AppendDNameListElem", sizeof(DNameListElem));
	if (!dnle) LogMsg("ERROR: AppendDNameListElem: memory exhausted");
	else
		{
		dnle->next = mDNSNULL;
		dnle->uid  = uid;
		AssignDomainName(&dnle->name, name);
		**List = dnle;
		*List = &dnle->next;
		}
	}

mDNSlocal int compare_dns_configs(const void *aa, const void *bb)
	{
	dns_resolver_t *a = *(dns_resolver_t**)aa;
	dns_resolver_t *b = *(dns_resolver_t**)bb;
	
	return (a->search_order < b->search_order) ? -1 : (a->search_order == b->search_order) ? 0 : 1;
	}

// ConfigResolvers is called twice - once to parse the "scoped_resolver" list and second time to parse the "resolver" list.
// "scoped_resolver" has entries that should only be used for "scoped_questions" (for questions that specify an interface index
// q->InterfaceID) and "resolver" entries should only be used for non-scoped questions. Entries in either of the list can specify
// an ifindex. This means that the dns query should be scoped to that interface when sent out on the wire. The flag value
// "DNS_RESOLVER_FLAGS_SCOPED" itself appears only in "scoped" list of resolvers.
//
// Before "scoped_resolver" was introduced, the entries in "resolver" list can contain options like "interface=en0" which
// was meant to scope the query (non-scoped queries) to a specific interface. We still support this option. On top of that,
// we also support a new way of specifying the interface index as described above.
mDNSlocal void ConfigResolvers(mDNS *const m, dns_config_t *config, mDNSBool scope, mDNSBool setsearch, mDNSBool setservers)
	{
	int i, j;
	domainname d;
#if DNSINFO_VERSION >= 20091104
	dns_resolver_t **resolver = scope ? config->scoped_resolver : config->resolver;
	int nresolvers = scope ? config->n_scoped_resolver : config->n_resolver;
#else
	(void) scope; // unused
	dns_resolver_t **resolver = config->resolver;
	int nresolvers = config->n_resolver;
#endif

	if (setsearch && !scope && nresolvers)
		{
		// Due to the vagaries of Apple's SystemConfiguration and dnsinfo.h APIs, if there are no search domains
		// listed, then you're supposed to interpret the "domain" field as also being the search domain, but if
		// there *are* search domains listed, then you're supposed to ignore the "domain" field completely and
		// instead use the search domain list as the sole authority for what domains to search and in what order
		// (and the domain from the "domain" field will also appear somewhere in that list).
		// Also, all search domains get added to the search list for resolver[0], so the domains and/or
		// search lists for other resolvers in the list need to be ignored.
		//
		// Note: Starting DNSINFO_VERSION 20091104, search list is present only in the first resolver (resolver 0).
		// i.e., n_search for the first resolver is always non-zero. We don't guard it with #ifs for better readability

		if (resolver[0]->n_search == 0)
			{
			LogInfo("ConfigResolvers: (%s) configuring zeroth domain as search list %s", scope ? "Scoped" : "Non-scoped", resolver[0]->domain);
			mDNS_AddSearchDomain_CString(resolver[0]->domain, mDNSNULL);
			}
		else
			{
			for (i = 0; i < resolver[0]->n_search; i++)
				{
				LogInfo("ConfigResolvers: (%s) configuring search list %s", scope ? "Scoped" : "Non-scoped", resolver[0]->search[i]);
				mDNS_AddSearchDomain_CString(resolver[0]->search[i], mDNSNULL);
				}
			}
		}

	// scoped search domains are set below. If neither scoped nor setting servers, we have nothing to do
	if (!scope && !setservers) return;

	// For the "default" resolver ("resolver #1") the "domain" value is bogus and we need to ignore it.
	// e.g. the default resolver's "domain" value might say "apple.com", which indicates that this resolver
	// is only for names that fall under "apple.com", but that's not correct. Actually the default resolver is
	// for all names not covered by a more specific resolver (i.e. its domain should be ".", the root domain).
	//
	// Note: Starting DNSINFO_VERSION 20091104, domain value of this first resolver (resolver 0) is always NULL.
	// We don't guard it with #ifs for better readability
	// 
	if ((nresolvers != 0) && resolver[0]->domain)
		resolver[0]->domain[0] = 0; // don't stop pointing at the memory, just change the first byte

	qsort(resolver, nresolvers, sizeof(dns_resolver_t*), compare_dns_configs);
				
	for (i = 0; i < nresolvers; i++)
		{	
		int n;
		dns_resolver_t *r = resolver[i];
		mDNSInterfaceID interface = mDNSInterface_Any;
		int disabled = 0;
					
		LogInfo("ConfigResolvers: %s resolver[%d] domain %s n_nameserver %d", scope ? "Scoped" : "", i, r->domain, r->n_nameserver);

		// On Tiger, dnsinfo entries for mDNS domains have port 5353, the mDNS port.  Ignore them.
		// Note: Unlike the BSD Sockets APIs (where TCP and UDP port numbers are universally in network byte order)
		// in Apple's "dnsinfo.h" API the port number is declared to be a "uint16_t in host byte order"
		// We also don't need to do any more work if there are no nameserver addresses
		if (r->port == 5353 || r->n_nameserver == 0)
			{
			char *opt = r->options;
			if (opt && !strncmp(opt, "mdns", strlen(opt)))
				{
				if (!MakeDomainNameFromDNSNameString(&d, r->domain))
					{ LogMsg("ConfigResolvers: config->resolver[%d] bad domain %s", i, r->domain); continue; }
				mDNS_AddMcastResolver(m, &d, interface, r->timeout);
				}
			continue;
			}

					
		if (!r->domain || !*r->domain) d.c[0] = 0;
		else if (!MakeDomainNameFromDNSNameString(&d, r->domain))
			{ LogMsg("ConfigResolvers: config->resolver[%d] bad domain %s", i, r->domain); continue; }

		// DNS server option parsing
		if (r->options != NULL)
			{
			char *nextOption = r->options;
			char *currentOption = NULL;
			while ((currentOption = strsep(&nextOption, " ")) != NULL && currentOption[0] != 0)
				{
				// The option may be in the form of interface=xxx where xxx is an interface name.
				if (strncmp(currentOption, kInterfaceSpecificOption, sizeof(kInterfaceSpecificOption) - 1) == 0)
					{
					NetworkInterfaceInfoOSX *ni;
					char	ifname[IF_NAMESIZE+1];
					mDNSu32	ifindex = 0;
					// If something goes wrong finding the interface, create the server entry anyhow but mark it as disabled.
					// This allows us to block these special queries from going out on the wire.
					strlcpy(ifname, currentOption + sizeof(kInterfaceSpecificOption)-1, sizeof(ifname));
					ifindex = if_nametoindex(ifname);
					if (ifindex == 0) { disabled = 1; LogMsg("ConfigResolvers: RegisterSplitDNS interface specific - interface %s not found", ifname); continue; }
					LogInfo("ConfigResolvers: interface specific entry: %s on %s (%d)", r->domain, ifname, ifindex);
					// Find the interface. Can't use mDNSPlatformInterfaceIDFromInterfaceIndex
					// because that will call mDNSMacOSXNetworkChanged if the interface doesn't exist
					for (ni = m->p->InterfaceList; ni; ni = ni->next)
						if (ni->ifinfo.InterfaceID && ni->scope_id == ifindex) break;
					if (ni != NULL) interface = ni->ifinfo.InterfaceID;
					if (interface == mDNSNULL)
						{
						disabled = 1;
						LogMsg("ConfigResolvers: RegisterSplitDNS interface specific - index %d (%s) not found", ifindex, ifname);
						continue;
						}
					}
				}
			}

		// flags and if_index are defined only from this DNSINFO_VERSION onwards.
		// Parse the interface index if we have not already parsed one above.
#if DNSINFO_VERSION >= 20091104
		if ((interface == mDNSInterface_Any) && (r->if_index != 0))
			{
			NetworkInterfaceInfoOSX *ni;
			interface = mDNSNULL;
			for (ni = m->p->InterfaceList; ni; ni = ni->next)
				if (ni->ifinfo.InterfaceID && ni->scope_id == r->if_index) break;
			if (ni != NULL) interface = ni->ifinfo.InterfaceID;
			if (interface == mDNSNULL)
				{
				disabled = 1;
				LogMsg("ConfigResolvers: interface specific index %d not found", r->if_index);
				continue;
				}
			}
#endif

		if (setsearch)
			{
			// For non-scoped resolvers unlike scoped resolvers, only zeroth resolver has search lists if any. For scoped
			// resolvers, we need to parse all the entries.
			if (scope)
				{
				for (j = 0; j < resolver[i]->n_search; j++)
					{
					LogInfo("ConfigResolvers: (%s) configuring search list %s, Interface %p", scope ? "Scoped" : "Non-scoped", resolver[i]->search[j], interface);
					mDNS_AddSearchDomain_CString(resolver[i]->search[j], interface);
					}
				// Parse other scoped resolvers for search lists
				if (!setservers) continue;
				}
			}

		for (n = 0; n < r->n_nameserver; n++)
			if (r->nameserver[n]->sa_family == AF_INET || r->nameserver[n]->sa_family == AF_INET6)
				{
				mDNSAddr saddr;
				// mDNSAddr saddr = { mDNSAddrType_IPv4, { { { 192, 168, 1, 1 } } } }; // for testing
				if (SetupAddr(&saddr, r->nameserver[n])) LogMsg("RegisterSplitDNS: bad IP address");
				else
					{
					mDNSBool cellIntf = mDNSfalse;
					mDNSBool scopedDNS = mDNSfalse;
					DNSServer *s;
#if DNSINFO_VERSION >= 20091104
					// By setting scoped, this DNSServer can only be picked if the right interfaceID
					// is given in the question. 
					if (scope && (r->flags & DNS_RESOLVER_FLAGS_SCOPED) && (interface == mDNSNULL))
						LogMsg("ConfigResolvers: ERROR: scoped is set but if_index %d is invalid for DNSServer %#a:%d",
							r->if_index, &saddr, mDNSVal16(r->port ? mDNSOpaque16fromIntVal(r->port) : UnicastDNSPort));
					else
						scopedDNS = (scope && (r->flags & DNS_RESOLVER_FLAGS_SCOPED)) ? mDNStrue : mDNSfalse;
#endif
#if DNSINFO_VERSION >= 20110420
					cellIntf = r->reach_flags & kSCNetworkReachabilityFlagsIsWWAN;
#endif
					// The timeout value is for all the DNS servers in a given resolver, hence we pass
					// the timeout value only for the first DNSServer. If we don't have a value in the
					// resolver, then use the core's default value
					//
					// Note: this assumes that when the core picks a list of DNSServers for a question,
					// it takes the sum of all the timeout values for all DNS servers. By doing this, it
					// tries all the DNS servers in a specified timeout
					s = mDNS_AddDNSServer(m, &d, interface, &saddr, r->port ? mDNSOpaque16fromIntVal(r->port) : UnicastDNSPort, scopedDNS,
						(n == 0 ? (r->timeout ? r->timeout : DEFAULT_UDNS_TIMEOUT) : 0), cellIntf);
					if (s)
						{
						if (disabled) s->teststate = DNSServer_Disabled;
						LogInfo("ConfigResolvers: DNS server %#a:%d for domain %##s from slot %d, %d",
							&s->addr, mDNSVal16(s->port), d.c, i, n);
						}
					}
				}
		}
	}

mDNSexport void mDNSPlatformSetDNSConfig(mDNS *const m, mDNSBool setservers, mDNSBool setsearch, domainname *const fqdn, DNameListElem **RegDomains, DNameListElem **BrowseDomains)
	{
	int i;
	char buf[MAX_ESCAPED_DOMAIN_NAME];	// Max legal C-string name, including terminating NUL
	domainname d;

	// Need to set these here because we need to do this even if SCDynamicStoreCreate() or SCDynamicStoreCopyValue() below don't succeed
	if (fqdn)          fqdn->c[0]      = 0;
	if (RegDomains   ) *RegDomains     = NULL;
	if (BrowseDomains) *BrowseDomains  = NULL;

	LogInfo("mDNSPlatformSetDNSConfig:%s%s%s%s%s",
	        setservers    ? " setservers"    : "",
	        setsearch     ? " setsearch"     : "",
	        fqdn          ? " fqdn"          : "",
	        RegDomains    ? " RegDomains"    : "",
	        BrowseDomains ? " BrowseDomains" : "");

	// Add the inferred address-based configuration discovery domains
	// (should really be in core code I think, not platform-specific)
	if (setsearch)
		{
		struct ifaddrs *ifa = mDNSNULL;
		struct sockaddr_in saddr;
		mDNSPlatformMemZero(&saddr, sizeof(saddr));
		saddr.sin_len = sizeof(saddr);
		saddr.sin_family = AF_INET;
		saddr.sin_port = 0;
		saddr.sin_addr.s_addr = *(in_addr_t *)&m->Router.ip.v4;

		// Don't add any reverse-IP search domains if doing the WAB bootstrap queries would cause dial-on-demand connection initiation
		if (!AddrRequiresPPPConnection((struct sockaddr *)&saddr)) ifa =  myGetIfAddrs(1);
		
		while (ifa)
			{
			mDNSAddr a, n;
			if (ifa->ifa_addr->sa_family == AF_INET	&&
				ifa->ifa_netmask                    &&
				!(ifa->ifa_flags & IFF_LOOPBACK)	&&
				!SetupAddr(&a, ifa->ifa_addr)		&&
				!mDNSv4AddressIsLinkLocal(&a.ip.v4)  )
				{
				// Apparently it's normal for the sa_family of an ifa_netmask to sometimes be incorrect, so we explicitly fix it here before calling SetupAddr
				// <rdar://problem/5492035> getifaddrs is returning invalid netmask family for fw0 and vmnet
				ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family;		// Make sure ifa_netmask->sa_family is set correctly
				SetupAddr(&n, ifa->ifa_netmask);
				// Note: This is reverse order compared to a normal dotted-decimal IP address, so we can't use our customary "%.4a" format code
				mDNS_snprintf(buf, sizeof(buf), "%d.%d.%d.%d.in-addr.arpa.", a.ip.v4.b[3] & n.ip.v4.b[3],
																			 a.ip.v4.b[2] & n.ip.v4.b[2],
																			 a.ip.v4.b[1] & n.ip.v4.b[1],
																			 a.ip.v4.b[0] & n.ip.v4.b[0]);
				mDNS_AddSearchDomain_CString(buf, mDNSNULL);
				}
			ifa = ifa->ifa_next;
			}
		}

#ifndef MDNS_NO_DNSINFO
	if (setservers || setsearch)
		{
		dns_config_t *config = dns_configuration_copy();
		if (!config)
			{
			// When running on 10.3 (build 7xxx) and earlier, we don't expect dns_configuration_copy() to succeed
			// On 10.4, calls to dns_configuration_copy() early in the boot process often fail.
			// Apparently this is expected behaviour -- "not a bug".
			// Accordingly, we suppress syslog messages for the first three minutes after boot.
			// If we are still getting failures after three minutes, then we log them.
			if ((mDNSu32)mDNSPlatformRawTime() > (mDNSu32)(mDNSPlatformOneSecond * 180))
				LogMsg("mDNSPlatformSetDNSConfig: Error: dns_configuration_copy returned NULL");
			}
		else
			{
			LogInfo("mDNSPlatformSetDNSConfig: config->n_resolver = %d", config->n_resolver);

#if APPLE_OSX_mDNSResponder
				// Record the so-called "primary" domain, which we use as a hint to tell if the user is on a network set up
				// by someone using Microsoft Active Directory using "local" as a private internal top-level domain
				if (config->n_resolver && config->resolver[0]->domain && config->resolver[0]->n_nameserver && config->resolver[0]->nameserver[0])
					MakeDomainNameFromDNSNameString(&ActiveDirectoryPrimaryDomain, config->resolver[0]->domain);
				else ActiveDirectoryPrimaryDomain.c[0] = 0;
				//MakeDomainNameFromDNSNameString(&ActiveDirectoryPrimaryDomain, "test.local");
				ActiveDirectoryPrimaryDomainLabelCount = CountLabels(&ActiveDirectoryPrimaryDomain);
				if (config->n_resolver && config->resolver[0]->n_nameserver && SameDomainName(SkipLeadingLabels(&ActiveDirectoryPrimaryDomain, ActiveDirectoryPrimaryDomainLabelCount - 1), &localdomain))
					SetupAddr(&ActiveDirectoryPrimaryDomainServer, config->resolver[0]->nameserver[0]);
				else
					{
					AssignDomainName(&ActiveDirectoryPrimaryDomain, (const domainname *)"");
					ActiveDirectoryPrimaryDomainLabelCount = 0;
					ActiveDirectoryPrimaryDomainServer = zeroAddr;
					}
#endif

			ConfigResolvers(m, config, mDNSfalse, setsearch, setservers);
#if DNSINFO_VERSION >= 20091104
			ConfigResolvers(m, config, mDNStrue, setsearch, setservers);
#endif
			dns_configuration_free(config);
			if (setsearch) RetrySearchDomainQuestions(m);
			setservers = mDNSfalse;  // Done these now -- no need to fetch the same data from SCDynamicStore
			setsearch  = mDNSfalse;
			}
		}
#endif // MDNS_NO_DNSINFO

	SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:mDNSPlatformSetDNSConfig"), NULL, NULL);
	if (!store)
		LogMsg("mDNSPlatformSetDNSConfig: SCDynamicStoreCreate failed: %s", SCErrorString(SCError()));
	else
		{
		CFDictionaryRef ddnsdict = SCDynamicStoreCopyValue(store, NetworkChangedKey_DynamicDNS);
		if (ddnsdict)
			{
			if (fqdn)
				{
				CFArrayRef fqdnArray = CFDictionaryGetValue(ddnsdict, CFSTR("HostNames"));
				if (fqdnArray && CFArrayGetCount(fqdnArray) > 0)
					{
					// for now, we only look at the first array element.  if we ever support multiple configurations, we will walk the list
					CFDictionaryRef fqdnDict = CFArrayGetValueAtIndex(fqdnArray, 0);
					if (fqdnDict && DictionaryIsEnabled(fqdnDict))
						{
						CFStringRef name = CFDictionaryGetValue(fqdnDict, CFSTR("Domain"));
						if (name)
							{
							if (!CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) ||
								!MakeDomainNameFromDNSNameString(fqdn, buf) || !fqdn->c[0])
								LogMsg("GetUserSpecifiedDDNSConfig SCDynamicStore bad DDNS host name: %s", buf[0] ? buf : "(unknown)");
							else debugf("GetUserSpecifiedDDNSConfig SCDynamicStore DDNS host name: %s", buf);
							}
						}
					}
				}

			if (RegDomains)
				{
				CFArrayRef regArray = CFDictionaryGetValue(ddnsdict, CFSTR("RegistrationDomains"));
				if (regArray && CFArrayGetCount(regArray) > 0)
					{
					CFDictionaryRef regDict = CFArrayGetValueAtIndex(regArray, 0);
					if (regDict && DictionaryIsEnabled(regDict))
						{
						CFStringRef name = CFDictionaryGetValue(regDict, CFSTR("Domain"));
						if (name)
							{
							if (!CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) ||
								!MakeDomainNameFromDNSNameString(&d, buf) || !d.c[0])
								LogMsg("GetUserSpecifiedDDNSConfig SCDynamicStore bad DDNS registration domain: %s", buf[0] ? buf : "(unknown)");
							else
								{
								debugf("GetUserSpecifiedDDNSConfig SCDynamicStore DDNS registration domain: %s", buf);
								AppendDNameListElem(&RegDomains, 0, &d);
								}
							}
						}
					}
				}

			if (BrowseDomains)
				{
				CFArrayRef browseArray = CFDictionaryGetValue(ddnsdict, CFSTR("BrowseDomains"));
				if (browseArray)
					{
					for (i = 0; i < CFArrayGetCount(browseArray); i++)
						{
						CFDictionaryRef browseDict = CFArrayGetValueAtIndex(browseArray, i);
						if (browseDict && DictionaryIsEnabled(browseDict))
							{
							CFStringRef name = CFDictionaryGetValue(browseDict, CFSTR("Domain"));
							if (name)
								{
								if (!CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) ||
									!MakeDomainNameFromDNSNameString(&d, buf) || !d.c[0])
									LogMsg("GetUserSpecifiedDDNSConfig SCDynamicStore bad DDNS browsing domain: %s", buf[0] ? buf : "(unknown)");
								else
									{
									debugf("GetUserSpecifiedDDNSConfig SCDynamicStore DDNS browsing domain: %s", buf);
									AppendDNameListElem(&BrowseDomains, 0, &d);
									}
								}
							}
						}
					}
				}
			CFRelease(ddnsdict);
			}

		if (RegDomains)
			{
			CFDictionaryRef btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
			if (btmm)
				{
				CFIndex size = CFDictionaryGetCount(btmm);
				const void *key[size];
				const void *val[size];
				CFDictionaryGetKeysAndValues(btmm, key, val);
				for (i = 0; i < size; i++)
					{
					LogInfo("BackToMyMac %d", i);
					if (!CFStringGetCString(key[i], buf, sizeof(buf), kCFStringEncodingUTF8))
						LogMsg("Can't read BackToMyMac %d key %s", i, buf);
					else
						{
						mDNSu32 uid = atoi(buf);
						if (!CFStringGetCString(val[i], buf, sizeof(buf), kCFStringEncodingUTF8))
							LogMsg("Can't read BackToMyMac %d val %s", i, buf);
						else if (MakeDomainNameFromDNSNameString(&d, buf) && d.c[0])
							{
							LogInfo("BackToMyMac %d %d %##s", i, uid, d.c);
							AppendDNameListElem(&RegDomains, uid, &d);
							}
						}
					}
				CFRelease(btmm);
				}
			}

		if (setservers || setsearch)
			{
			CFDictionaryRef dict = SCDynamicStoreCopyValue(store, NetworkChangedKey_DNS);
			if (dict)
				{
				if (setservers)
					{
					CFArrayRef values = CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses);
					if (values)
						{
						LogInfo("DNS Server Address values: %d", (int)CFArrayGetCount(values));
						for (i = 0; i < CFArrayGetCount(values); i++)
							{
							CFStringRef s = CFArrayGetValueAtIndex(values, i);
							mDNSAddr addr = { mDNSAddrType_IPv4, { { { 0 } } } };
							if (s && CFStringGetCString(s, buf, 256, kCFStringEncodingUTF8) &&
								inet_aton(buf, (struct in_addr *) &addr.ip.v4))
								{
								LogInfo("Adding DNS server from dict: %s", buf);
								mDNS_AddDNSServer(m, mDNSNULL, mDNSInterface_Any, &addr, UnicastDNSPort, mDNSfalse, 0, mDNSfalse);
								}
							}
						}
					else LogInfo("No DNS Server Address values");
					}
				if (setsearch)
					{
					// Add the manual and/or DHCP-dicovered search domains
					CFArrayRef searchDomains = CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains);
					if (searchDomains)
						{
						for (i = 0; i < CFArrayGetCount(searchDomains); i++)
							{
							CFStringRef s = CFArrayGetValueAtIndex(searchDomains, i);
							if (s && CFStringGetCString(s, buf, sizeof(buf), kCFStringEncodingUTF8))
								mDNS_AddSearchDomain_CString(buf, mDNSNULL);
							}
						}
					else	// No kSCPropNetDNSSearchDomains, so use kSCPropNetDNSDomainName
						{
						// Due to the vagaries of Apple's SystemConfiguration and dnsinfo.h APIs, if there are no search domains
						// listed, then you're supposed to interpret the "domain" field as also being the search domain, but if
						// there *are* search domains listed, then you're supposed to ignore the "domain" field completely and
						// instead use the search domain list as the sole authority for what domains to search and in what order
						// (and the domain from the "domain" field will also appear somewhere in that list).
						CFStringRef string = CFDictionaryGetValue(dict, kSCPropNetDNSDomainName);
						if (string && CFStringGetCString(string, buf, sizeof(buf), kCFStringEncodingUTF8))
							mDNS_AddSearchDomain_CString(buf, mDNSNULL);
						}
					RetrySearchDomainQuestions(m);
					}
				CFRelease(dict);
				}
			}
		CFRelease(store);
		}
	}

mDNSexport mStatus mDNSPlatformGetPrimaryInterface(mDNS *const m, mDNSAddr *v4, mDNSAddr *v6, mDNSAddr *r)
	{
	char				buf[256];
	(void)m; // Unused

	SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:mDNSPlatformGetPrimaryInterface"), NULL, NULL);
	if (!store)
		LogMsg("mDNSPlatformGetPrimaryInterface: SCDynamicStoreCreate failed: %s", SCErrorString(SCError()));
	else
		{
		CFDictionaryRef dict = SCDynamicStoreCopyValue(store, NetworkChangedKey_IPv4);
		if (dict)
			{
			r->type  = mDNSAddrType_IPv4;
			r->ip.v4 = zerov4Addr;
			CFStringRef string = CFDictionaryGetValue(dict, kSCPropNetIPv4Router);
			if (string)
				{
				if (!CFStringGetCString(string, buf, 256, kCFStringEncodingUTF8))
					LogMsg("Could not convert router to CString");
				else
					{
					struct sockaddr_in saddr;
					saddr.sin_len = sizeof(saddr);
					saddr.sin_family = AF_INET;
					saddr.sin_port = 0;
					inet_aton(buf, &saddr.sin_addr);
		
					*(in_addr_t *)&r->ip.v4 = saddr.sin_addr.s_addr;
					}
				}
		
			string = CFDictionaryGetValue(dict, kSCDynamicStorePropNetPrimaryInterface);
			if (string)
				{
				mDNSBool HavePrimaryGlobalv6 = mDNSfalse;  // does the primary interface have a global v6 address?
				struct ifaddrs *ifa = myGetIfAddrs(1);
		
				*v4 = *v6 = zeroAddr;
		
				if (!CFStringGetCString(string, buf, 256, kCFStringEncodingUTF8)) { LogMsg("Could not convert router to CString"); goto exit; }
		
				// find primary interface in list
				while (ifa && (mDNSIPv4AddressIsZero(v4->ip.v4) || mDNSv4AddressIsLinkLocal(&v4->ip.v4) || !HavePrimaryGlobalv6))
					{
					mDNSAddr tmp6 = zeroAddr;
					if (!strcmp(buf, ifa->ifa_name))
						{
						if (ifa->ifa_addr->sa_family == AF_INET)
							{
							if (mDNSIPv4AddressIsZero(v4->ip.v4) || mDNSv4AddressIsLinkLocal(&v4->ip.v4)) SetupAddr(v4, ifa->ifa_addr);
							}
						else if (ifa->ifa_addr->sa_family == AF_INET6)
							{
							SetupAddr(&tmp6, ifa->ifa_addr);
							if (tmp6.ip.v6.b[0] >> 5 == 1)   // global prefix: 001
								{ HavePrimaryGlobalv6 = mDNStrue; *v6 = tmp6; }
							}
						}
					else
						{
						// We'll take a V6 address from the non-primary interface if the primary interface doesn't have a global V6 address
						if (!HavePrimaryGlobalv6 && ifa->ifa_addr->sa_family == AF_INET6 && !v6->ip.v6.b[0])
							{
							SetupAddr(&tmp6, ifa->ifa_addr);
							if (tmp6.ip.v6.b[0] >> 5 == 1) *v6 = tmp6;
							}
						}
					ifa = ifa->ifa_next;
					}
		
				// Note that while we advertise v6, we still require v4 (possibly NAT'd, but not link-local) because we must use
				// V4 to communicate w/ our DNS server
				}
		
			exit:
			CFRelease(dict);
			}
		CFRelease(store);
		}
	return mStatus_NoError;
	}

mDNSexport void mDNSPlatformDynDNSHostNameStatusChanged(const domainname *const dname, const mStatus status)
	{
	LogInfo("mDNSPlatformDynDNSHostNameStatusChanged %d %##s", status, dname->c);
	char uname[MAX_ESCAPED_DOMAIN_NAME];	// Max legal C-string name, including terminating NUL
	ConvertDomainNameToCString(dname, uname);

	char *p = uname;
	while (*p)
		{
		*p = tolower(*p);
		if (!(*(p+1)) && *p == '.') *p = 0; // if last character, strip trailing dot
		p++;
		}

	// We need to make a CFDictionary called "State:/Network/DynamicDNS" containing (at present) a single entity.
	// That single entity is a CFDictionary with name "HostNames".
	// The "HostNames" CFDictionary contains a set of name/value pairs, where the each name is the FQDN
	// in question, and the corresponding value is a CFDictionary giving the state for that FQDN.
	// (At present we only support a single FQDN, so this dictionary holds just a single name/value pair.)
	// The CFDictionary for each FQDN holds (at present) a single name/value pair,
	// where the name is "Status" and the value is a CFNumber giving an errror code (with zero meaning success).

	const CFStringRef StateKeys [1] = { CFSTR("HostNames") };
	const CFStringRef HostKeys  [1] = { CFStringCreateWithCString(NULL, uname, kCFStringEncodingUTF8) };
	const CFStringRef StatusKeys[1] = { CFSTR("Status") };
	if (!HostKeys[0]) LogMsg("SetDDNSNameStatus: CFStringCreateWithCString(%s) failed", uname);
	else
		{
		const CFNumberRef StatusVals[1] = { CFNumberCreate(NULL, kCFNumberSInt32Type, &status) };
		if (!StatusVals[0]) LogMsg("SetDDNSNameStatus: CFNumberCreate(%d) failed", status);
		else
			{
			const CFDictionaryRef HostVals[1] = { CFDictionaryCreate(NULL, (void*)StatusKeys, (void*)StatusVals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) };
			if (HostVals[0])
				{
				const CFDictionaryRef StateVals[1] = { CFDictionaryCreate(NULL, (void*)HostKeys, (void*)HostVals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) };
				if (StateVals[0])
					{
					CFDictionaryRef StateDict = CFDictionaryCreate(NULL, (void*)StateKeys, (void*)StateVals, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
					if (StateDict)
						{
						mDNSDynamicStoreSetConfig(kmDNSDynamicConfig, mDNSNULL, StateDict);
						CFRelease(StateDict);
						}
					CFRelease(StateVals[0]);
					}
				CFRelease(HostVals[0]);
				}
			CFRelease(StatusVals[0]);
			}
		CFRelease(HostKeys[0]);
		}
	}

#if APPLE_OSX_mDNSResponder
#if ! NO_AWACS

// checks whether a domain is present in Setup:/Network/BackToMyMac. Just because there is a key in the
// keychain for a domain, it does not become a valid BTMM domain. If things get inconsistent, this will
// help catch it
mDNSlocal mDNSBool IsBTMMDomain(domainname *d)
	{
	SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:IsBTMMDomain"), NULL, NULL);
	if (!store)
		{
		LogMsg("IsBTMMDomain: SCDynamicStoreCreate failed: %s", SCErrorString(SCError()));
		return mDNSfalse;
		}
	CFDictionaryRef btmm = SCDynamicStoreCopyValue(store, NetworkChangedKey_BackToMyMac);
	if (btmm)
		{
		CFIndex size = CFDictionaryGetCount(btmm);
		char buf[MAX_ESCAPED_DOMAIN_NAME];	// Max legal C-string name, including terminating NUL
		const void *key[size];
		const void *val[size];
		domainname dom;
		int i;
		CFDictionaryGetKeysAndValues(btmm, key, val);
		for (i = 0; i < size; i++)
			{
			LogInfo("BackToMyMac %d", i);
			if (!CFStringGetCString(key[i], buf, sizeof(buf), kCFStringEncodingUTF8))
				LogMsg("IsBTMMDomain: ERROR!! Can't read BackToMyMac %d key %s", i, buf);
			else
				{
				mDNSu32 uid = atoi(buf);
				if (!CFStringGetCString(val[i], buf, sizeof(buf), kCFStringEncodingUTF8))
					LogMsg("IsBTMMDomain: Can't read BackToMyMac %d val %s", i, buf);
				else if (MakeDomainNameFromDNSNameString(&dom, buf) && dom.c[0])
					{
					if (SameDomainName(&dom, d))
						{
						LogInfo("IsBTMMDomain: Domain %##s is a btmm domain, uid %u", d->c, uid);
						CFRelease(btmm);
						CFRelease(store);
						return mDNStrue;
						}
					}
				}
			}
		CFRelease(btmm);
		}
	CFRelease(store);
	LogInfo("IsBTMMDomain: Domain %##s not a btmm domain", d->c);
	return mDNSfalse;
	}

// Appends data to the buffer
mDNSlocal int AddOneItem(char *buf, int bufsz, char *data, int *currlen)
	{
	int len;

	len = strlcpy(buf + *currlen, data, bufsz - *currlen);
	if (len >= (bufsz - *currlen))
		{
		// if we have exceeded the space in buf, it has already been NULL terminated
		// and we have nothing more to do. Set currlen to the last byte so that the caller
		// knows to do the right thing
		LogMsg("AddOneItem: Exceeded the max buffer size currlen %d, len %d", *currlen, len);
		*currlen = bufsz - 1;
		return -1;
		}
	else { (*currlen) += len; }

	buf[*currlen] = ',';
	if (*currlen >= bufsz)
		{
		LogMsg("AddOneItem: ERROR!! How can currlen be %d", *currlen);
		*currlen = bufsz - 1;
		buf[*currlen] = 0;
		return -1;
		}
	// if we have filled up the buffer exactly, then there is no more work to do
	if (*currlen == bufsz - 1) { buf[*currlen] = 0; return -1; }
	(*currlen)++;
	return *currlen;
	}

// If we have at least one BTMM domain, then trigger the connection to the relay. If we have no
// BTMM domains, then bring down the connection to the relay.
mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m)
	{
	DomainAuthInfo *BTMMDomain = mDNSNULL;
	DomainAuthInfo *FoundInList;
	static mDNSBool AWACSDConnected = mDNSfalse;
	char AllUsers[1024];	// maximum size of mach message
	char AllPass[1024];  	// maximum size of mach message
	char username[MAX_DOMAIN_LABEL + 1];
	int currulen = 0;
	int currplen = 0;

	// if a domain is being deleted, we want to send a disconnect. If we send a disconnect now,
	// we may not be able to send the dns queries over the relay connection which may be needed
	// for sending the deregistrations. Hence, we need to delay sending the disconnect. But we
	// need to make sure that we send the disconnect before attempting the next connect as the
	// awacs connections are redirected based on usernames.
	//
	// For now we send a disconnect immediately. When we start sending dns queries over the relay
	// connection, we will need to fix this.

	for (FoundInList = m->AuthInfoList; FoundInList; FoundInList = FoundInList->next)
		if (!FoundInList->deltime && FoundInList->AutoTunnel && IsBTMMDomain(&FoundInList->domain))
			{
			// We need the passwd from the first domain.
			BTMMDomain = FoundInList;
			ConvertDomainLabelToCString_unescaped((domainlabel *)BTMMDomain->domain.c, username);
			LogInfo("UpdateBTMMRelayConnection: user %s for domain %##s", username, BTMMDomain->domain.c);
			if (AddOneItem(AllUsers, sizeof(AllUsers), username, &currulen) == -1) break;
			if (AddOneItem(AllPass, sizeof(AllPass), BTMMDomain->b64keydata, &currplen) == -1) break;
			}

	if (BTMMDomain) 
		{
		// In the normal case (where we neither exceed the buffer size nor write bytes that
		// fit exactly into the buffer), currulen/currplen should be a different size than
		// (AllUsers - 1) / (AllPass - 1). In that case, we need to override the "," with a NULL byte.

		if (currulen != (int)(sizeof(AllUsers) - 1)) AllUsers[currulen - 1] = 0;
		if (currplen != (int)(sizeof(AllPass) - 1)) AllPass[currplen - 1] = 0;

		LogInfo("UpdateBTMMRelayConnection: AWS_Connect for user %s", AllUsers);
		AWACS_Connect(AllUsers, AllPass, "hello.connectivity.me.com");
		AWACSDConnected = mDNStrue;
		}
	else
		{
		// Disconnect only if we connected previously
		if (AWACSDConnected)
			{
			LogInfo("UpdateBTMMRelayConnection: AWS_Disconnect");
			AWACS_Disconnect();
			AWACSDConnected = mDNSfalse;
			}
		else LogInfo("UpdateBTMMRelayConnection: Not calling AWS_Disconnect");
		}
	}
#else
mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m)
	{
	(void) m; // Unused
	LogInfo("UpdateBTMMRelayConnection: AWACS connection not started, no AWACS library");
	}
#endif // ! NO_AWACS
#endif // APPLE_OSX_mDNSResponder

// MUST be called holding the lock -- this routine calls SetupLocalAutoTunnelInterface_internal()
mDNSexport void SetDomainSecrets(mDNS *m)
	{
#ifdef NO_SECURITYFRAMEWORK
	(void)m;
	LogMsg("Note: SetDomainSecrets: no keychain support");
#else
	mDNSBool	haveAutoTunnels = mDNSfalse;

	LogInfo("SetDomainSecrets");

	// Rather than immediately deleting all keys now, we mark them for deletion in ten seconds.
	// In the case where the user simultaneously removes their DDNS host name and the key
	// for it, this gives mDNSResponder ten seconds to gracefully delete the name from the
	// server before it loses access to the necessary key. Otherwise, we'd leave orphaned
	// address records behind that we no longer have permission to delete.
	DomainAuthInfo *ptr;
	for (ptr = m->AuthInfoList; ptr; ptr = ptr->next)
		ptr->deltime = NonZeroTime(m->timenow + mDNSPlatformOneSecond*10);

#if APPLE_OSX_mDNSResponder
	{
	// Mark all TunnelClients for deletion
	ClientTunnel *client;
	for (client = m->TunnelClients; client; client = client->next)
		{
		LogInfo("SetDomainSecrets: tunnel to %##s marked for deletion", client->dstname.c);
		client->MarkedForDeletion = mDNStrue;
		}
	}
#endif // APPLE_OSX_mDNSResponder

	// String Array used to write list of private domains to Dynamic Store
	CFMutableArrayRef sa = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	if (!sa) { LogMsg("SetDomainSecrets: CFArrayCreateMutable failed"); return; }
	CFIndex i;
	CFDataRef data = NULL;
	const int itemsPerEntry = 4; // domain name, key name, key value, Name value
	CFArrayRef secrets = NULL;
	int err = mDNSKeychainGetSecrets(&secrets);
	if (err || !secrets)
		LogMsg("SetDomainSecrets: mDNSKeychainGetSecrets failed error %d CFArrayRef %p", err, secrets);
	else
		{
		CFIndex ArrayCount = CFArrayGetCount(secrets);
		// Iterate through the secrets
		for (i = 0; i < ArrayCount; ++i)
			{
			mDNSBool AutoTunnel;
			int j, offset;
			CFArrayRef entry = CFArrayGetValueAtIndex(secrets, i);
			if (CFArrayGetTypeID() != CFGetTypeID(entry) || itemsPerEntry != CFArrayGetCount(entry))
				{ LogMsg("SetDomainSecrets: malformed entry %d, itemsPerEntry %d", i, itemsPerEntry); continue; }
			for (j = 0; j < CFArrayGetCount(entry); ++j)
				if (CFDataGetTypeID() != CFGetTypeID(CFArrayGetValueAtIndex(entry, j)))
					{ LogMsg("SetDomainSecrets: malformed entry item %d", j); continue; }

			// The names have already been vetted by the helper, but checking them again here helps humans and automated tools verify correctness

			// Max legal domainname as C-string, including space for btmmprefix and terminating NUL
			// Get DNS domain this key is for (kmDNSKcWhere)
			char stringbuf[MAX_ESCAPED_DOMAIN_NAME + sizeof(btmmprefix)];
			data = CFArrayGetValueAtIndex(entry, kmDNSKcWhere);
			if (CFDataGetLength(data) >= (int)sizeof(stringbuf))
				{ LogMsg("SetDomainSecrets: Bad kSecServiceItemAttr length %d", CFDataGetLength(data)); continue; }
			CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), (UInt8 *)stringbuf);
			stringbuf[CFDataGetLength(data)] = '\0';

			AutoTunnel = mDNSfalse;
			offset = 0;
			if (!strncmp(stringbuf, dnsprefix, strlen(dnsprefix)))
				offset = strlen(dnsprefix);
			else if (!strncmp(stringbuf, btmmprefix, strlen(btmmprefix)))
				{
				AutoTunnel = mDNStrue;
				offset = strlen(btmmprefix);
				}
			domainname domain;
			if (!MakeDomainNameFromDNSNameString(&domain, stringbuf + offset)) { LogMsg("SetDomainSecrets: bad key domain %s", stringbuf); continue; }

			// Get key name (kmDNSKcAccount)
			data = CFArrayGetValueAtIndex(entry, kmDNSKcAccount);
			if (CFDataGetLength(data) >= (int)sizeof(stringbuf))
				{ LogMsg("SetDomainSecrets: Bad kSecAccountItemAttr length %d", CFDataGetLength(data)); continue; }
			CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8 *)stringbuf);
			stringbuf[CFDataGetLength(data)] = '\0';

			domainname keyname;
			if (!MakeDomainNameFromDNSNameString(&keyname, stringbuf)) { LogMsg("SetDomainSecrets: bad key name %s", stringbuf); continue; }

			// Get key data (kmDNSKcKey)
			data = CFArrayGetValueAtIndex(entry, kmDNSKcKey);
			if (CFDataGetLength(data) >= (int)sizeof(stringbuf))
				{ LogMsg("SetDomainSecrets: Shared secret too long: %d", CFDataGetLength(data)); continue; }
			CFDataGetBytes(data, CFRangeMake(0, CFDataGetLength(data)), (UInt8 *)stringbuf);
			stringbuf[CFDataGetLength(data)] = '\0';	// mDNS_SetSecretForDomain requires NULL-terminated C string for key

			// Get the Name of the keychain entry (kmDNSKcName) host or host:port
			// The hostname also has the port number and ":". It should take a maximum of 6 bytes.
			char hostbuf[MAX_ESCAPED_DOMAIN_NAME + 6];	// Max legal domainname as C-string, including terminating NUL
			data = CFArrayGetValueAtIndex(entry, kmDNSKcName);
			if (CFDataGetLength(data) >= (int)sizeof(hostbuf))
				{ LogMsg("SetDomainSecrets: Shared secret too long: %d", CFDataGetLength(data)); continue; }
			CFDataGetBytes(data, CFRangeMake(0,CFDataGetLength(data)), (UInt8 *)hostbuf);
			hostbuf[CFDataGetLength(data)] = '\0';

			domainname hostname;
			mDNSIPPort port;
			char *hptr;
			hptr = strchr(hostbuf, ':');
			
			port.NotAnInteger = 0;
			if (hptr)
				{
				mDNSu8 *p;
				mDNSu16 val = 0;

				*hptr++ = '\0'; 
				while(hptr && *hptr != 0)
					{
					if (*hptr < '0' || *hptr > '9')
						{ LogMsg("SetDomainSecrets: Malformed Port number %d, val %d", *hptr, val); val = 0; break;}
					val = val * 10 + *hptr - '0';
					hptr++;
					}
				if (!val) continue;
				p = (mDNSu8 *)&val;
				port.NotAnInteger = p[0] << 8 | p[1];
				}
			// The hostbuf is of the format dsid@hostname:port. We don't care about the dsid. 
			hptr = strchr(hostbuf, '@');
			if (hptr)
				hptr++;
			else
				hptr = hostbuf;
			if (!MakeDomainNameFromDNSNameString(&hostname, hptr)) { LogMsg("SetDomainSecrets: bad host name %s", hptr); continue; }

			DomainAuthInfo *FoundInList;
			for (FoundInList = m->AuthInfoList; FoundInList; FoundInList = FoundInList->next)
				if (SameDomainName(&FoundInList->domain, &domain)) break;

#if APPLE_OSX_mDNSResponder
			if (FoundInList)
				{
				// If any client tunnel destination is in this domain, set deletion flag to false
				ClientTunnel *client;
				for (client = m->TunnelClients; client; client = client->next)
					if (FoundInList == GetAuthInfoForName_internal(m, &client->dstname))
					{
					LogInfo("SetDomainSecrets: tunnel to %##s no longer marked for deletion", client->dstname.c);
					client->MarkedForDeletion = mDNSfalse;
					}
				}

#endif // APPLE_OSX_mDNSResponder

			// Uncomment the line below to view the keys as they're read out of the system keychain
			// DO NOT SHIP CODE THIS WAY OR YOU'LL LEAK SECRET DATA INTO A PUBLICLY READABLE FILE!
			//LogInfo("SetDomainSecrets: domain %##s keyname %##s key %s hostname %##s port %d", &domain.c, &keyname.c, stringbuf, hostname.c, (port.b[0] << 8 | port.b[1]));
			LogInfo("SetDomainSecrets: domain %##s keyname %##s hostname %##s port %d", &domain.c, &keyname.c, hostname.c, (port.b[0] << 8 | port.b[1]));

			// If didn't find desired domain in the list, make a new entry
			ptr = FoundInList;
			if (FoundInList && FoundInList->AutoTunnel && haveAutoTunnels == mDNSfalse) haveAutoTunnels = mDNStrue;
			if (!FoundInList)
				{
				ptr = (DomainAuthInfo*)mallocL("DomainAuthInfo", sizeof(*ptr));
				if (!ptr) { LogMsg("SetDomainSecrets: No memory"); continue; }
				}

			//LogInfo("SetDomainSecrets: %d of %d %##s", i, ArrayCount, &domain);

			// It is an AutoTunnel if the keychains tells us so (with btmm prefix) or if it is a TunnelModeDomain
			if (mDNS_SetSecretForDomain(m, ptr, &domain, &keyname, stringbuf, &hostname, &port, (AutoTunnel ? btmmprefix : (IsTunnelModeDomain(&domain) ? dnsprefix : NULL))) == mStatus_BadParamErr)
				{
				if (!FoundInList) mDNSPlatformMemFree(ptr);		// If we made a new DomainAuthInfo here, and it turned out bad, dispose it immediately
				continue;
				}

#if APPLE_OSX_mDNSResponder
			if (ptr->AutoTunnel) UpdateAutoTunnelDomainStatus(m, ptr);
#endif // APPLE_OSX_mDNSResponder

			ConvertDomainNameToCString(&domain, stringbuf);
			CFStringRef cfs = CFStringCreateWithCString(NULL, stringbuf, kCFStringEncodingUTF8);
			if (cfs) { CFArrayAppendValue(sa, cfs); CFRelease(cfs); }
			}
		CFRelease(secrets);
		}
	mDNSDynamicStoreSetConfig(kmDNSPrivateConfig, mDNSNULL, sa);
	CFRelease(sa);

#if APPLE_OSX_mDNSResponder
		{
		// clean up ClientTunnels
		ClientTunnel **pp = &m->TunnelClients;
		while (*pp)
			{
			if ((*pp)->MarkedForDeletion)
				{
				ClientTunnel *cur = *pp;
				LogInfo("SetDomainSecrets: removing client %p %##s from list", cur, cur->dstname.c);
				if (cur->q.ThisQInterval >= 0) mDNS_StopQuery(m, &cur->q);
				AutoTunnelSetKeys(cur, mDNSfalse);
				*pp = cur->next;
				freeL("ClientTunnel", cur);
				}
			else
				pp = &(*pp)->next;
			}

		DomainAuthInfo *info = m->AuthInfoList;
		while (info)
			{
			if (info->AutoTunnel && info->deltime)
				{
				if (info->AutoTunnelNAT.clientContext)
					{
					// stop the NAT operation
					mDNS_StopNATOperation_internal(m, &info->AutoTunnelNAT);
					if (info->AutoTunnelNAT.clientCallback)
						{
						// Reset port and cleanup the state
						info->AutoTunnelNAT.ExternalAddress = m->ExternalAddress;
						info->AutoTunnelNAT.ExternalPort    = zeroIPPort;
						info->AutoTunnelNAT.RequestedPort    = zeroIPPort;
						info->AutoTunnelNAT.Lifetime        = 0;
						info->AutoTunnelNAT.Result          = mStatus_NoError;
						mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
						AutoTunnelDeleteAuthInfoState(m, info);
						mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
						}
					info->AutoTunnelNAT.clientContext = mDNSNULL;
					}
				RemoveAutoTunnelDomainStatus(m, info);
				}
			info = info->next;
			}

		if (!haveAutoTunnels && !m->TunnelClients && m->AutoTunnelHostAddrActive)
			{
			// remove interface if no autotunnel servers and no more client tunnels
			LogInfo("SetDomainSecrets: Bringing tunnel interface DOWN");
			m->AutoTunnelHostAddrActive = mDNSfalse;
			(void)mDNSAutoTunnelInterfaceUpDown(kmDNSDown, m->AutoTunnelHostAddr.b);
			mDNSPlatformMemZero(m->AutoTunnelHostAddr.b, sizeof(m->AutoTunnelHostAddr.b));
			}
		
		if (m->AutoTunnelHostAddr.b[0])
			if (TunnelClients(m) || TunnelServers(m))
				SetupLocalAutoTunnelInterface_internal(m, mDNSfalse);

		UpdateAnonymousRacoonConfig(m);		// Determine whether we need racoon to accept incoming connections
		UpdateBTMMRelayConnection(m);
		}
#endif // APPLE_OSX_mDNSResponder

	CheckSuppressUnusableQuestions(m);

#endif /* NO_SECURITYFRAMEWORK */
	}

mDNSlocal void SetLocalDomains(void)
	{
	CFMutableArrayRef sa = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	if (!sa) { LogMsg("SetLocalDomains: CFArrayCreateMutable failed"); return; }

	CFArrayAppendValue(sa, CFSTR("local"));
	CFArrayAppendValue(sa, CFSTR("254.169.in-addr.arpa"));
	CFArrayAppendValue(sa, CFSTR("8.e.f.ip6.arpa"));
	CFArrayAppendValue(sa, CFSTR("9.e.f.ip6.arpa"));
	CFArrayAppendValue(sa, CFSTR("a.e.f.ip6.arpa"));
	CFArrayAppendValue(sa, CFSTR("b.e.f.ip6.arpa"));

	mDNSDynamicStoreSetConfig(kmDNSMulticastConfig, mDNSNULL, sa);
	CFRelease(sa);
	}

mDNSlocal void GetCurrentPMSetting(const CFStringRef name, mDNSs32 *val)
	{
#if USE_IOPMCOPYACTIVEPMPREFERENCES
	CFTypeRef blob = NULL;
	CFStringRef str = NULL;
	CFDictionaryRef odict = NULL;
	CFDictionaryRef idict = NULL;
	CFNumberRef number = NULL;

	blob = IOPSCopyPowerSourcesInfo();
	if (!blob) { LogMsg("GetCurrentPMSetting: IOPSCopyPowerSourcesInfo failed!"); goto end; }

	odict = IOPMCopyActivePMPreferences();
	if (!odict) { LogMsg("GetCurrentPMSetting: IOPMCopyActivePMPreferences failed!"); goto end; }

	str = IOPSGetProvidingPowerSourceType(blob);
	if (!str) { LogMsg("GetCurrentPMSetting: IOPSGetProvidingPowerSourceType failed!"); goto end; }

	idict = CFDictionaryGetValue(odict, str);
	if (!idict)
		{
		char buf[256];
		if (!CFStringGetCString(str, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
		LogMsg("GetCurrentPMSetting: CFDictionaryGetValue (%s) failed!", buf);
		goto end;
		}
	
	number = CFDictionaryGetValue(idict, name);
	if (!number || CFGetTypeID(number) != CFNumberGetTypeID() || !CFNumberGetValue(number, kCFNumberSInt32Type, val))
		*val = 0;
end:
	if (blob) CFRelease(blob);
	if (odict) CFRelease(odict);

#else

	SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:GetCurrentPMSetting"), NULL, NULL);
	if (!store) LogMsg("GetCurrentPMSetting: SCDynamicStoreCreate failed: %s", SCErrorString(SCError()));
	else
		{
		CFDictionaryRef dict = SCDynamicStoreCopyValue(store, NetworkChangedKey_PowerSettings);
		if (!dict) LogSPS("GetCurrentPMSetting: Could not get IOPM CurrentSettings dict");
		else
			{
			CFNumberRef number = CFDictionaryGetValue(dict, name);
			if (!number || CFGetTypeID(number) != CFNumberGetTypeID() || !CFNumberGetValue(number, kCFNumberSInt32Type, val))
				*val = 0;
			CFRelease(dict);
			}
		CFRelease(store);
		}
	
#endif
	}

#if APPLE_OSX_mDNSResponder

static CFMutableDictionaryRef spsStatusDict = NULL;
static const CFStringRef kMetricRef = CFSTR("Metric");

mDNSlocal void SPSStatusPutNumber(CFMutableDictionaryRef dict, const mDNSu8* const ptr, CFStringRef key)
	{
	mDNSu8 tmp = (ptr[0] - '0') * 10 + ptr[1] - '0';
	CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt8Type, &tmp);
	if (!num)
		LogMsg("SPSStatusPutNumber: Could not create CFNumber");
	else
		{
		CFDictionarySetValue(dict, key, num);
		CFRelease(num);
		}
	}

mDNSlocal CFMutableDictionaryRef SPSCreateDict(const mDNSu8* const ptr)
	{
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	if (!dict) { LogMsg("SPSCreateDict: Could not create CFDictionary dict"); return dict; }
	
	char buffer[1024];
	buffer[mDNS_snprintf(buffer, sizeof(buffer), "%##s", ptr) - 1] = 0;
	CFStringRef spsname = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
	if (!spsname) { LogMsg("SPSCreateDict: Could not create CFString spsname full"); CFRelease(dict); return NULL; }
	CFDictionarySetValue(dict, CFSTR("FullName"), spsname);
	CFRelease(spsname);

	if (ptr[0] >=  2) SPSStatusPutNumber(dict, ptr + 1, CFSTR("Type"));
	if (ptr[0] >=  5) SPSStatusPutNumber(dict, ptr + 4, CFSTR("Portability"));
	if (ptr[0] >=  8) SPSStatusPutNumber(dict, ptr + 7, CFSTR("MarginalPower"));
	if (ptr[0] >= 11) SPSStatusPutNumber(dict, ptr +10, CFSTR("TotalPower"));

	mDNSu32 tmp = SPSMetric(ptr);
	CFNumberRef num = CFNumberCreate(NULL, kCFNumberSInt32Type, &tmp);
	if (!num)
		LogMsg("SPSCreateDict: Could not create CFNumber");
	else
		{
		CFDictionarySetValue(dict, kMetricRef, num);
		CFRelease(num);
		}

	if (ptr[0] >= 12)
		{
		memcpy(buffer, ptr + 13, ptr[0] - 12);
		buffer[ptr[0] - 12] = 0;
		spsname = CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8);
		if (!spsname) { LogMsg("SPSCreateDict: Could not create CFString spsname"); CFRelease(dict); return NULL; }
		else
			{
			CFDictionarySetValue(dict, CFSTR("PrettyName"), spsname);
			CFRelease(spsname);
			}
		}
	
	return dict;
	}
	
mDNSlocal CFComparisonResult CompareSPSEntries(const void *val1, const void *val2, void *context)
	{
	(void)context;
	return CFNumberCompare((CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)val1, kMetricRef),
						   (CFNumberRef)CFDictionaryGetValue((CFDictionaryRef)val2, kMetricRef),
						   NULL);
	}

mDNSlocal void UpdateSPSStatus(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
	{
	NetworkInterfaceInfo* info = (NetworkInterfaceInfo*)question->QuestionContext;
	debugf("UpdateSPSStatus: %s %##s %s %s", info->ifname, question->qname.c, AddRecord ? "Add" : "Rmv", answer ? RRDisplayString(m, answer) : "<null>");

	mDNS_Lock(m);
	mDNS_UpdateAllowSleep(m);
	mDNS_Unlock(m);

	if (answer && SPSMetric(answer->rdata->u.name.c) > 999999) return;	// Ignore instances with invalid names

	if (!spsStatusDict)
		{
		spsStatusDict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
		if (!spsStatusDict) { LogMsg("UpdateSPSStatus: Could not create CFDictionary spsStatusDict"); return; }
		}
	
	CFStringRef ifname = CFStringCreateWithCString(NULL, info->ifname, kCFStringEncodingUTF8);
	if (!ifname) { LogMsg("UpdateSPSStatus: Could not create CFString ifname"); return; }
	
	CFMutableArrayRef array = NULL;
	
	if (!CFDictionaryGetValueIfPresent(spsStatusDict, ifname, (const void**) &array))
		{
		array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
		if (!array) { LogMsg("UpdateSPSStatus: Could not create CFMutableArray"); CFRelease(ifname); return; }
		CFDictionarySetValue(spsStatusDict, ifname, array);
		CFRelease(array); // let go of our reference, now that the dict has one
		}
	else
		if (!array) { LogMsg("UpdateSPSStatus: Could not get CFMutableArray for %s", info->ifname); CFRelease(ifname); return; }
	
	if (!answer) // special call that means the question has been stopped (because the interface is going away)
		CFArrayRemoveAllValues(array);
	else
		{
		CFMutableDictionaryRef dict = SPSCreateDict(answer->rdata->u.name.c);
		if (!dict) { CFRelease(ifname); return; }
		
		if (AddRecord)
			{
			if (!CFArrayContainsValue(array, CFRangeMake(0, CFArrayGetCount(array)), dict))
				{
				int i=0;
				for (i=0; i<CFArrayGetCount(array); i++)
					if (CompareSPSEntries(CFArrayGetValueAtIndex(array, i), dict, NULL) != kCFCompareLessThan)
						break;
				CFArrayInsertValueAtIndex(array, i, dict);
				}
			else LogMsg("UpdateSPSStatus: %s array already contains %##s", info->ifname, answer->rdata->u.name.c);
			}
		else
			{
			CFIndex i = CFArrayGetFirstIndexOfValue(array, CFRangeMake(0, CFArrayGetCount(array)), dict);
			if (i != -1) CFArrayRemoveValueAtIndex(array, i);
			else LogMsg("UpdateSPSStatus: %s array does not contain %##s", info->ifname, answer->rdata->u.name.c);
			}
			
		CFRelease(dict);
		}

	if (!m->ShutdownTime) mDNSDynamicStoreSetConfig(kmDNSSleepProxyServersState, info->ifname, array);
	
	CFRelease(ifname);
	}

mDNSlocal mDNSs32 GetSystemSleepTimerSetting(void)
	{
	mDNSs32 val = -1;
	SCDynamicStoreRef store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:GetSystemSleepTimerSetting"), NULL, NULL);
	if (!store)
		LogMsg("GetSystemSleepTimerSetting: SCDynamicStoreCreate failed: %s", SCErrorString(SCError()));
	else
		{
		CFDictionaryRef dict = SCDynamicStoreCopyValue(store, NetworkChangedKey_PowerSettings);
		if (dict)
			{
			CFNumberRef number = CFDictionaryGetValue(dict, CFSTR("System Sleep Timer"));
			if (number) CFNumberGetValue(number, kCFNumberSInt32Type, &val);
			CFRelease(dict);
			}
		CFRelease(store);
		}
	return val;
	}

mDNSlocal void SetSPS(mDNS *const m)
	{
	SCPreferencesSynchronize(m->p->SCPrefs);
	CFDictionaryRef dict = SCPreferencesGetValue(m->p->SCPrefs, CFSTR("NAT"));
	mDNSBool natenabled = (dict && (CFGetTypeID(dict) == CFDictionaryGetTypeID()) && DictionaryIsEnabled(dict));
	mDNSu8 sps = natenabled ? mDNSSleepProxyMetric_PrimarySoftware :
		(OfferSleepProxyService && GetSystemSleepTimerSetting() == 0) ? mDNSSleepProxyMetric_IncidentalSoftware : 0;

	// For devices that are not running NAT, but are set to never sleep, we may choose to act
	// as a Sleep Proxy, but only for non-portable Macs (Portability > 35 means nominal weight < 3kg)
	//if (sps > mDNSSleepProxyMetric_PrimarySoftware && SPMetricPortability > 35) sps = 0;

	// If we decide to let laptops act as Sleep Proxy, we should do it only when running on AC power, not on battery

	// For devices that are unable to sleep at all to save power, or save 1W or less by sleeping,
	// it makes sense for them to offer low-priority Sleep Proxy service on the network.
	// We rate such a device as metric 70 ("Incidentally Available Hardware")
	if (SPMetricMarginalPower <= 60 && !sps) sps = mDNSSleepProxyMetric_IncidentalHardware;

	// If the launchd plist specifies an explicit value for the Intent Metric, then use that instead of the
	// computed value (currently 40 "Primary Network Infrastructure Software" or 80 "Incidentally Available Software")
	if (sps && OfferSleepProxyService && OfferSleepProxyService < 100) sps = OfferSleepProxyService;

	mDNSCoreBeSleepProxyServer(m, sps, SPMetricPortability, SPMetricMarginalPower, SPMetricTotalPower);
	}

mDNSlocal void InternetSharingChanged(SCPreferencesRef prefs, SCPreferencesNotification notificationType, void *context)
	{
	(void)prefs;             // Parameter not used
	(void)notificationType;  // Parameter not used
	mDNS *const m = (mDNS *const)context;
	KQueueLock(m);
	mDNS_Lock(m);

	// Tell platform layer to open or close its BPF fds
	if (!m->p->NetworkChanged ||
		m->p->NetworkChanged - NonZeroTime(m->timenow + mDNSPlatformOneSecond * 2) < 0)
		{
		m->p->NetworkChanged = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 2);
		LogInfo("InternetSharingChanged: Set NetworkChanged to %d (%d)", m->p->NetworkChanged - m->timenow, m->p->NetworkChanged);
		}

	mDNS_Unlock(m);
	KQueueUnlock(m, "InternetSharingChanged");
	}

mDNSlocal mStatus WatchForInternetSharingChanges(mDNS *const m)
	{
	SCPreferencesRef SCPrefs = SCPreferencesCreate(NULL, CFSTR("mDNSResponder:WatchForInternetSharingChanges"), CFSTR("com.apple.nat.plist"));
	if (!SCPrefs) { LogMsg("SCPreferencesCreate failed: %s", SCErrorString(SCError())); return(mStatus_NoMemoryErr); }

	SCPreferencesContext context = { 0, m, NULL, NULL, NULL };
	if (!SCPreferencesSetCallback(SCPrefs, InternetSharingChanged, &context))
		{ LogMsg("SCPreferencesSetCallback failed: %s", SCErrorString(SCError())); CFRelease(SCPrefs); return(mStatus_NoMemoryErr); }

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	if (!SCPreferencesSetDispatchQueue( SCPrefs, dispatch_get_main_queue()))
		{ LogMsg("SCPreferencesSetDispatchQueue failed: %s", SCErrorString(SCError())); return(mStatus_NoMemoryErr); }
#else
	if (!SCPreferencesScheduleWithRunLoop(SCPrefs, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
		{ LogMsg("SCPreferencesScheduleWithRunLoop failed: %s", SCErrorString(SCError())); CFRelease(SCPrefs); return(mStatus_NoMemoryErr); }
#endif

	m->p->SCPrefs = SCPrefs;
	return(mStatus_NoError);
	}

// The definitions below should eventually come from some externally-supplied header file.
// However, since these definitions can't really be changed without breaking binary compatibility,
// they should never change, so in practice it should not be a big problem to have them defined here.

#define mDNS_IOREG_KEY               "mDNS_KEY"
#define mDNS_IOREG_VALUE             "2009-07-30"
#define mDNS_USER_CLIENT_CREATE_TYPE 'mDNS'

enum
	{							// commands from the daemon to the driver
    cmd_mDNSOffloadRR = 21,		// give the mdns update buffer to the driver
	};

typedef union { void *ptr; mDNSOpaque64 sixtyfourbits; } FatPtr;

typedef struct
	{                                   // cmd_mDNSOffloadRR structure
    uint32_t  command;                // set to OffloadRR
    uint32_t  rrBufferSize;           // number of bytes of RR records
    uint32_t  numUDPPorts;            // number of SRV UDP ports
    uint32_t  numTCPPorts;            // number of SRV TCP ports 
    uint32_t  numRRRecords;           // number of RR records
    uint32_t  compression;            // rrRecords - compression is base for compressed strings
    FatPtr    rrRecords;              // address of array of pointers to the rr records
    FatPtr    udpPorts;               // address of udp port list (SRV)
    FatPtr    tcpPorts;               // address of tcp port list (SRV)
	} mDNSOffloadCmd;

#include <IOKit/IOKitLib.h>
#include <dns_util.h>

mDNSlocal mDNSu16 GetPortArray(mDNS *const m, int trans, mDNSIPPort *portarray)
	{
	const domainlabel *const tp = (trans == mDNSTransport_UDP) ? (const domainlabel *)"\x4_udp" : (const domainlabel *)"\x4_tcp";
	int count = 0;
	AuthRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.rrtype == kDNSType_SRV && SameDomainLabel(ThirdLabel(rr->resrec.name)->c, tp->c))
			{
			if (portarray) portarray[count] = rr->resrec.rdata->u.srv.port;
			count++;
			}

	// If Back to My Mac is on, also wake for packets to the IPSEC UDP port (4500)
	if (trans == mDNSTransport_UDP && TunnelServers(m))
		{
		LogSPS("GetPortArray Back to My Mac at %d", count);
		if (portarray) portarray[count] = IPSECPort;
		count++;
		}
	return(count);
	}

#define TfrRecordToNIC(RR) \
	((!(RR)->resrec.InterfaceID && ((RR)->ForceMCast || IsLocalDomain((RR)->resrec.name))))

mDNSlocal mDNSu32 CountProxyRecords(mDNS *const m, uint32_t *const numbytes)
	{
	*numbytes = 0;
	int count = 0;
	AuthRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.RecordType > kDNSRecordTypeDeregistering)
			if (TfrRecordToNIC(rr))
				{
				*numbytes += DomainNameLength(rr->resrec.name) + 10 + rr->resrec.rdestimate;
				LogSPS("CountProxyRecords: %3d size %5d total %5d %s",
					count, DomainNameLength(rr->resrec.name) + 10 + rr->resrec.rdestimate, *numbytes, ARDisplayString(m,rr));
				count++;
				}
	return(count);
	}

mDNSlocal void GetProxyRecords(mDNS *const m, DNSMessage *const msg, uint32_t *const numbytes, FatPtr *const records)
	{
	mDNSu8 *p = msg->data;
	const mDNSu8 *const limit = p + *numbytes;
	InitializeDNSMessage(&msg->h, zeroID, zeroID);

	int count = 0;
	AuthRecord *rr;
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		if (rr->resrec.RecordType > kDNSRecordTypeDeregistering)
			if (TfrRecordToNIC(rr))
				{
				records[count].sixtyfourbits = zeroOpaque64;
				records[count].ptr = p;
				if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
					rr->resrec.rrclass |= kDNSClass_UniqueRRSet;	// Temporarily set the 'unique' bit so PutResourceRecord will set it
				p = PutResourceRecordTTLWithLimit(msg, p, &msg->h.mDNS_numUpdates, &rr->resrec, rr->resrec.rroriginalttl, limit);
				rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet;		// Make sure to clear 'unique' bit back to normal state
				LogSPS("GetProxyRecords: %3d start %p end %p size %5d total %5d %s",
					count, records[count].ptr, p, p - (mDNSu8 *)records[count].ptr, p - msg->data, ARDisplayString(m,rr));
				count++;
				}
	*numbytes = p - msg->data;
	}

// If compiling with old headers and libraries (pre 10.5) that don't include IOConnectCallStructMethod
// then we declare a dummy version here so that the code at least compiles
#ifndef AVAILABLE_MAC_OS_X_VERSION_10_5_AND_LATER
static kern_return_t
IOConnectCallStructMethod(
	mach_port_t	 connection,		// In
	uint32_t	 selector,			// In
	const void	*inputStruct,		// In
	size_t		 inputStructCnt,	// In
	void		*outputStruct,		// Out
	size_t		*outputStructCnt)	// In/Out
	{
	(void)connection;
	(void)selector;
	(void)inputStruct;
	(void)inputStructCnt;
	(void)outputStruct;
	(void)outputStructCnt;
	LogMsg("Compiled without IOConnectCallStructMethod");
	return(KERN_FAILURE);
	}
#endif

mDNSexport mStatus ActivateLocalProxy(mDNS *const m, char *ifname)	// Called with the lock held
	{
	mStatus result = mStatus_UnknownErr;
	io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, ifname));
	if (!service) { LogMsg("ActivateLocalProxy: No service for interface %s", ifname); return(mStatus_UnknownErr); }

	io_name_t n1, n2;
	IOObjectGetClass(service, n1);
	io_object_t parent;
	kern_return_t kr = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent);
	if (kr != KERN_SUCCESS) LogMsg("ActivateLocalProxy: IORegistryEntryGetParentEntry for %s/%s failed %d", ifname, n1, kr);
	else
		{
		IOObjectGetClass(parent, n2);
		LogSPS("ActivateLocalProxy: Interface %s service %s parent %s", ifname, n1, n2);
		const CFTypeRef ref = IORegistryEntryCreateCFProperty(parent, CFSTR(mDNS_IOREG_KEY), kCFAllocatorDefault, mDNSNULL);
		if (!ref) LogSPS("ActivateLocalProxy: No mDNS_IOREG_KEY for interface %s/%s/%s", ifname, n1, n2);
		else
			{
			if (CFGetTypeID(ref) != CFStringGetTypeID() || !CFEqual(ref, CFSTR(mDNS_IOREG_VALUE)))
				LogMsg("ActivateLocalProxy: mDNS_IOREG_KEY for interface %s/%s/%s value %s != %s",
					ifname, n1, n2, CFStringGetCStringPtr(ref, mDNSNULL), mDNS_IOREG_VALUE);
			else if (!UseInternalSleepProxy)
				LogSPS("ActivateLocalProxy: Not using internal (NIC) sleep proxy for interface %s", ifname);
			else
				{
				io_connect_t conObj;
				kr = IOServiceOpen(parent, mach_task_self(), mDNS_USER_CLIENT_CREATE_TYPE, &conObj);
				if (kr != KERN_SUCCESS) LogMsg("ActivateLocalProxy: IOServiceOpen for %s/%s/%s failed %d", ifname, n1, n2, kr);
				else
					{
					mDNSOffloadCmd cmd;
					mDNSPlatformMemZero(&cmd, sizeof(cmd)); // When compiling 32-bit, make sure top 32 bits of 64-bit pointers get initialized to zero
					cmd.command       = cmd_mDNSOffloadRR;
					cmd.numUDPPorts   = GetPortArray(m, mDNSTransport_UDP, mDNSNULL);
					cmd.numTCPPorts   = GetPortArray(m, mDNSTransport_TCP, mDNSNULL);
					cmd.numRRRecords  = CountProxyRecords(m, &cmd.rrBufferSize);
					cmd.compression   = sizeof(DNSMessageHeader);

					DNSMessage *msg = (DNSMessage *)mallocL("mDNSOffloadCmd msg", sizeof(DNSMessageHeader) + cmd.rrBufferSize);
					cmd.rrRecords.ptr = mallocL("mDNSOffloadCmd rrRecords", cmd.numRRRecords * sizeof(FatPtr));
					cmd.udpPorts .ptr = mallocL("mDNSOffloadCmd udpPorts",  cmd.numUDPPorts  * sizeof(mDNSIPPort));
					cmd.tcpPorts .ptr = mallocL("mDNSOffloadCmd tcpPorts",  cmd.numTCPPorts  * sizeof(mDNSIPPort));

					LogSPS("ActivateLocalProxy: msg %p %d RR %p %d, UDP %p %d, TCP %p %d",
						msg, cmd.rrBufferSize,
						cmd.rrRecords.ptr, cmd.numRRRecords,
						cmd.udpPorts .ptr, cmd.numUDPPorts,
						cmd.tcpPorts .ptr, cmd.numTCPPorts);

					if (!msg || !cmd.rrRecords.ptr || !cmd.udpPorts.ptr || !cmd.tcpPorts.ptr)
						LogMsg("ActivateLocalProxy: Failed to allocate memory: msg %p %d RR %p %d, UDP %p %d, TCP %p %d",
						msg, cmd.rrBufferSize,
						cmd.rrRecords.ptr, cmd.numRRRecords,
						cmd.udpPorts .ptr, cmd.numUDPPorts,
						cmd.tcpPorts .ptr, cmd.numTCPPorts);
					else
						{
						GetProxyRecords(m, msg, &cmd.rrBufferSize, cmd.rrRecords.ptr);
						GetPortArray(m, mDNSTransport_UDP, cmd.udpPorts.ptr);
						GetPortArray(m, mDNSTransport_TCP, cmd.tcpPorts.ptr);
						char outputData[2];
						size_t outputDataSize = sizeof(outputData);
						kr = IOConnectCallStructMethod(conObj, 0, &cmd, sizeof(cmd), outputData, &outputDataSize);
						LogSPS("ActivateLocalProxy: IOConnectCallStructMethod for %s/%s/%s %d", ifname, n1, n2, kr);
						if (kr == KERN_SUCCESS) result = mStatus_NoError;
						}

				 	if (cmd.tcpPorts. ptr) freeL("mDNSOffloadCmd udpPorts",  cmd.tcpPorts .ptr);
					if (cmd.udpPorts. ptr) freeL("mDNSOffloadCmd tcpPorts",  cmd.udpPorts .ptr);
					if (cmd.rrRecords.ptr) freeL("mDNSOffloadCmd rrRecords", cmd.rrRecords.ptr);
					if (msg)               freeL("mDNSOffloadCmd msg",       msg);
					IOServiceClose(conObj);
					}
				}
			CFRelease(ref);
			}
		IOObjectRelease(parent);
		}
	IOObjectRelease(service);
	return result;
	}

#endif // APPLE_OSX_mDNSResponder

static io_service_t g_rootdomain = MACH_PORT_NULL;

mDNSlocal mDNSBool SystemWakeForNetworkAccess(void)
	{
	mDNSs32 val = 0;
	CFBooleanRef clamshellStop = NULL;
	mDNSBool retnow = mDNSfalse;

	if (DisableSleepProxyClient) { LogSPS("SystemWakeForNetworkAccess: Sleep Proxy Client disabled by command-line option"); return mDNSfalse; }

	GetCurrentPMSetting(CFSTR("Wake On LAN"), &val);
	LogSPS("SystemWakeForNetworkAccess: Wake On LAN: %d", val);
	if (!val) return mDNSfalse;
	
	if (!g_rootdomain) g_rootdomain = IORegistryEntryFromPath(MACH_PORT_NULL, kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
	if (!g_rootdomain) { LogMsg("SystemWakeForNetworkAccess: IORegistryEntryFromPath failed; assuming no clamshell so can WOMP"); return mDNStrue; }
	
	clamshellStop = (CFBooleanRef)IORegistryEntryCreateCFProperty(g_rootdomain, CFSTR(kAppleClamshellStateKey), kCFAllocatorDefault, 0);
	if (!clamshellStop) { LogSPS("SystemWakeForNetworkAccess: kAppleClamshellStateKey does not exist; assuming no clamshell so can WOMP"); return mDNStrue; }
	retnow = clamshellStop == kCFBooleanFalse;
	CFRelease(clamshellStop);
	if (retnow) { LogSPS("SystemWakeForNetworkAccess: kAppleClamshellStateKey is false; clamshell is open so can WOMP"); return mDNStrue; }
	
	clamshellStop = (CFBooleanRef)IORegistryEntryCreateCFProperty(g_rootdomain, CFSTR(kAppleClamshellCausesSleepKey), kCFAllocatorDefault, 0);
	if (!clamshellStop) { LogSPS("SystemWakeForNetworkAccess: kAppleClamshellCausesSleepKey does not exist; assuming no clamshell so can WOMP"); return mDNStrue; }
	retnow = (clamshellStop == kCFBooleanFalse);
	CFRelease(clamshellStop);
	if (retnow) { LogSPS("SystemWakeForNetworkAccess: kAppleClamshellCausesSleepKey is false; clamshell is closed but can WOMP"); return mDNStrue; }
	
	LogSPS("SystemWakeForNetworkAccess: clamshell is closed and can't WOMP");
	return mDNSfalse;
	}

mDNSlocal mDNSBool SystemSleepOnlyIfWakeOnLAN(void)
	{
	mDNSs32 val = 0;
	GetCurrentPMSetting(CFSTR("PrioritizeNetworkReachabilityOverSleep"), &val);
	return val != 0 ? mDNStrue : mDNSfalse;
	}

#if APPLE_OSX_mDNSResponder
// If the _autotunnel6 record is still there in the list, we are waiting for the ack from
// the server.
//
// If we are behind a double-NAT or NAT with no NAT-PMP support, we should make sure that all our
// BTMM records are deregistered so that it does not appear on the Finder sidebar of our peers
// when we go to sleep. First _autotunnel6 and the host record gets deregistered, then SRV
// (UpdateAllSrvRecords) and then PTR and TXT
//
// Note: We wait up to a maximum of 10 seconds before we ack the sleep. So, returning "false"
// here does not necessarily mean that it will be honored.
mDNSexport mDNSBool RecordReadyForSleep(mDNS *const m, AuthRecord *rr)
	{
	if (!AuthRecord_uDNS(rr)) return mDNStrue;

	if (SameDomainLabel(rr->namestorage.c, (const mDNSu8 *)"\x0c_autotunnel6"))
		{
		LogInfo("RecordReadyForSleep: %s not ready for sleep", ARDisplayString(m, rr));
		return mDNSfalse;
		}
	// Just check for the SRV record alone as the PTR and TXT records are dependent on SRV
	// and will get deregistered together in a single update. We also don't check for TXT
	// records  as _kerberos TXT record is always there even when there are no services
	// and we don't want to delay the sleep in that case.
	if (mDNSIPPortIsZero(m->LLQNAT.ExternalPort) || m->LLQNAT.Result)
		{
		if ((rr->resrec.rrtype == kDNSType_SRV) && rr->state != regState_NoTarget && rr->zone)
			{
			DomainAuthInfo *info = GetAuthInfoForName_internal(m, rr->zone);
			if (info && info->AutoTunnel)
				{	
				LogInfo("RecordReadyForSleep: %s not ready for sleep", ARDisplayString(m, rr));
				return mDNSfalse;
				}
			}
		}
	return mDNStrue;
	}

// Note: BTMMDict needs to be retained by the caller if needed
mDNSlocal CFDictionaryRef ParseBackToMyMacKey(CFDictionaryRef connd)
	{
	CFDictionaryRef BTMMDict = CFDictionaryGetValue(connd, CFSTR("BackToMyMac"));
	if (!BTMMDict)
		{
		LogInfo("ParseBackToMyMacKey: CFDictionaryGetValue No value for BackToMyMac");
		return NULL;
		}

	// Non-dictionary is treated as non-existent dictionary
	if (CFGetTypeID(BTMMDict) != CFDictionaryGetTypeID())
		{LogMsg("ERROR: ParseBackToMyMacKey: CFDictionaryGetValue BackToMyMac not a dictionary"); CFRelease(BTMMDict); return NULL;}

	return BTMMDict;
	}

mDNSlocal void ParseBTMMInterfaceKey(CFDictionaryRef BTMMDict, char *buf, int buflen)
	{
	CFTypeRef string;
	mDNSBool ifExists;

	ifExists = CFDictionaryGetValueIfPresent(BTMMDict, CFSTR("Interface"), &string);
	if (ifExists)
		{
		if (!CFStringGetCString(string, buf, buflen, kCFStringEncodingUTF8))
			{
			LogMsg("ERROR: ParseBTMMInterfaceKey: Could not convert Interface to CString");
			if (buflen) buf[0] = 0;
			return;
			}
		else
			debugf("ParseBTMMInterfaceKey: Interface Key exists %s", buf);
		}
	else
		{
		if (buflen) buf[0] = 0;
		debugf("ParseBTMMInterfaceKey: Interface Key does not exist");
		}
	}

mDNSexport void RemoveAutoTunnel6Record(mDNS *const m)
	{
	DomainAuthInfo *info;
	char buf[IFNAMSIZ];

	// Did we parse a non-empty dictionary before ?
	if (!m->p->ConndBTMMDict || (CFDictionaryGetCount(m->p->ConndBTMMDict) == 0))
		{
		LogInfo("RemoveAutoTunnel6Record: Never registered any records before, not deregistering %p", m->p->ConndBTMMDict);
		return;
		}

	// Did we have a non-NULL Interface name before ?
	ParseBTMMInterfaceKey(m->p->ConndBTMMDict, buf, sizeof(buf));
	if (!strlen(buf))
		{
		LogInfo("RemoveAutoTunnel6Record: Interface name already NULL, not deregistering");
		return;
		}
		
	// Set the address to zero before calling DeregisterAutoTunnel6Record. If we call
	// Deregister too quickly before the previous Register completed (just scheduled
	// to be sent out) and when DeregisterAutoTunnel6Record calls mDNS_Register_internal,
	// it invokes the AutoTunnelRecordCallback immediately and AutoTunnelRelayAddrIn should
	// be zero so that we don't register again.
	m->AutoTunnelRelayAddrIn = zerov6Addr;
	if (!m->AuthInfoList) LogInfo("RemoveAutoTunnel6Record: No Domain AuthInfo");
	for (info = m->AuthInfoList; info; info = info->next)
		{
		if (!info->AutoTunnel) { LogInfo("RemoveAutoTunnel6Record: Domain %##s not an AutoTunnel", info->domain.c); continue;}

		if (info->deltime) {LogInfo("RemoveAutoTunnel6Record: Domain %##s about to be deleted", info->domain.c); continue;}

		LogInfo("RemoveAutoTunnel6Record: Deregistering records for domain %##s", info->domain.c);
		DeregisterAutoTunnel6Record(m, info);
		}
	CFRelease(m->p->ConndBTMMDict);
	m->p->ConndBTMMDict = NULL;
	}

// Returns zero on success
mDNSlocal int GetIPv6AddressForIfname(char *ifname, mDNSv6Addr *ipv6Addr)
	{
	struct ifaddrs  *ifa;
	struct ifaddrs  *ifaddrs;
	mDNSAddr addr;

	if (if_nametoindex(ifname) == 0) {LogInfo("GetIPv6AddressForIfname: Invalid name %s", ifname); return (-1);}

	if (getifaddrs(&ifaddrs) < 0) {LogInfo("GetIPv6AddressForIfname: getifaddrs failed"); return (-1);}

	/*
	 * Find the ifaddr entry corresponding to the interface name,
	 * and return the first matching non-linklocal IPv6 address.
	 */
	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next)
		{
		if (strncmp(ifa->ifa_name, ifname, IFNAMSIZ) != 0)
			continue;
		if (ifa->ifa_flags & IFF_UP && ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6)
			{
			struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6 *)ifa->ifa_addr;
			if (IN6_IS_ADDR_LINKLOCAL(&ifa_addr->sin6_addr))
				continue;
			if (SetupAddr(&addr, ifa->ifa_addr) != mStatus_NoError)
				{
				LogInfo("GetIPv6AddressForIfname: SetupAddr error, continuing to the next address");
				continue;
				}
			else
				{
				*ipv6Addr = *(mDNSv6Addr *)&addr.ip.v6;
				LogInfo("GetIPv6AddressForIfname: Returning IPv6 address %.16a", ipv6Addr);
				freeifaddrs(ifaddrs);
				return 0;
				}
			}
       }
	LogInfo("GetIPv6AddressForIfname: No Valid IPv6 address");
	freeifaddrs(ifaddrs);
	return (-1);
	}

mDNSlocal void AddAutoTunnel6Record(mDNS *const m, char *ifname, CFDictionaryRef BTMMDict)
	{
	mDNSv6Addr v6addr;
	DomainAuthInfo *info;

	if (GetIPv6AddressForIfname(ifname, &v6addr) != 0)
		{
		LogInfo("AddAutoTunnel6Record: No Valid IPv6 addresses found for %s", ifname);
		// If the interface does not exist but the dictionary has the value, we treat
		// this case as though the dictionary does not have the value
		RemoveAutoTunnel6Record(m);
		// If awacsd crashes or exits for some reason, restart the relay connection
		UpdateBTMMRelayConnection(m);
		return;
		}
	
	m->AutoTunnelRelayAddrOut = v6addr;

	// if disabled administratively, don't bother to register. RegisterAutoTunnel6Record makes these same
	// checks, but we do it here not just as an optimization but mainly to keep AutoTunnelRelayAddrIn zero
	// as a non-zero AutoTunnelRelayAddrIn indicates that we have registered _autotunnel6 record and hence
	// other hosts can connect to this host through the relay
	if (!m->RegisterAutoTunnel6 || DisableInboundRelayConnection)
		{
		LogInfo("RegisterAutoTunnel6Record: registration Disabled RegisterAutoTunnel6 %d, DisableInbound %d",
			m->RegisterAutoTunnel6, DisableInboundRelayConnection);
		return;
		}
	m->AutoTunnelRelayAddrIn = v6addr;

	if (!m->AuthInfoList) LogInfo("AddAutoTunnel6Record: No Domain AuthInfo");
	for (info = m->AuthInfoList; info; info = info->next)
		{
		// clientContext for a domain tells us that we are listening for at least one Service/Record
		// in a domain and SetLocalAutoTunnelInterface_internal was called
		if (!info->AutoTunnel) { LogInfo("AddAutoTunnel6Record: Domain %##s not an AutoTunnel", info->domain.c); continue;}

		if (!info->AutoTunnelNAT.clientContext) {LogInfo("AddAutoTunnel6Record: Domain %##s has no services", info->domain.c); continue;}

		if (info->deltime) {LogInfo("AddAutoTunnel6Record: Domain %##s about to be deleted", info->domain.c); continue;}

		LogInfo("AddAutoTunnel6Record: Registering records for domain %##s", info->domain.c);
		mDNS_Lock(m);
		RegisterAutoTunnel6Record(m, info);
		mDNS_Unlock(m);
		}
	if (m->p->ConndBTMMDict) CFRelease(m->p->ConndBTMMDict);
	m->p->ConndBTMMDict = CFRetain(BTMMDict);
	}

mDNSlocal void ParseBackToMyMac(mDNS *const m, CFDictionaryRef connd)
	{
	CFDictionaryRef BTMMDict;
	char buf[IFNAMSIZ];

	BTMMDict = ParseBackToMyMacKey(connd);
	if (!BTMMDict)
		{
		LogInfo("ParseBackToMyMac: CFDictionaryGetValue No value for BackToMyMac, Removing autotunnel6");
		RemoveAutoTunnel6Record(m);
		// Note: AutoTunnelRelayAddrIn is zeroed out in RemoveAutoTunnel6Record as it is called
		// from other places.
		m->AutoTunnelRelayAddrOut = zerov6Addr;
		mDNS_Lock(m);
		UpdateAutoTunnelDomainStatuses(m);
		mDNS_Unlock(m);
		return;
		}

	ParseBTMMInterfaceKey(BTMMDict, buf, sizeof(buf));
	if (!strlen(buf))
		{
		LogInfo("ParseBackToMyMac: NULL value for Interface, Removing autotunnel6"); 
		RemoveAutoTunnel6Record(m);
		m->AutoTunnelRelayAddrOut = zerov6Addr;
		// We don't have a utun interface, start the relay connection if possible
		UpdateBTMMRelayConnection(m);
		}
	else
		{
		LogInfo("ParseBackToMyMac: non-NULL value for Interface, Adding autotunnel6"); 
		AddAutoTunnel6Record(m, buf, BTMMDict);
		}

	mDNS_Lock(m);
	UpdateAutoTunnelDomainStatuses(m);
	mDNS_Unlock(m);
	}

mDNSlocal void SetupConndConfigChanges(mDNS *const m)
	{
	CFDictionaryRef connd;
	SCDynamicStoreRef store;

	store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:SetupConndConfigChanges"), NULL, NULL);
	if (!store) {LogMsg("SetupConndConfigChanges: SCDynamicStoreCreate failed: %s", SCErrorString(SCError())); return;}

	connd = SCDynamicStoreCopyValue(store, NetworkChangedKey_BTMMConnectivity);
	if (!connd)
		{LogInfo("SetupConndConfigChanges: SCDynamicStoreCopyValue failed: %s", SCErrorString(SCError())); CFRelease(store); return;}
	else
		{
		ParseBackToMyMac(m, connd);
		}
	CFRelease(connd);
	CFRelease(store);
	}
#endif /* APPLE_OSX_mDNSResponder */

		
mDNSexport void mDNSMacOSXNetworkChanged(mDNS *const m)
	{
	LogInfo("***   Network Configuration Change   ***  (%d)%s",
		m->p->NetworkChanged ? mDNS_TimeNow(m) - m->p->NetworkChanged : 0,
		m->p->NetworkChanged ? "" : " (no scheduled configuration change)");
	m->p->NetworkChanged = 0;		// If we received a network change event and deferred processing, we're now dealing with it
	mDNSs32 utc = mDNSPlatformUTC();
	m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess();
	m->SystemSleepOnlyIfWakeOnLAN = SystemSleepOnlyIfWakeOnLAN();
	MarkAllInterfacesInactive(m, utc);
	UpdateInterfaceList(m, utc);
	ClearInactiveInterfaces(m, utc);
	SetupActiveInterfaces(m, utc);

#if APPLE_OSX_mDNSResponder

	SetupConndConfigChanges(m);

	if (m->AutoTunnelHostAddr.b[0])
		{
		mDNS_Lock(m);
		if (TunnelClients(m) || TunnelServers(m))
			SetupLocalAutoTunnelInterface_internal(m, mDNSfalse);
		mDNS_Unlock(m);
		}
	
	// Scan to find client tunnels whose questions have completed,
	// but whose local inner/outer addresses have changed since the tunnel was set up
	ClientTunnel *p;
	for (p = m->TunnelClients; p; p = p->next)
		if (p->q.ThisQInterval < 0)
			{
			if (!mDNSIPPortIsZero(p->rmt_outer_port))
				{
				mDNSAddr tmpSrc = zeroAddr;
				mDNSAddr tmpDst = { mDNSAddrType_IPv4, {{{0}}} };
				tmpDst.ip.v4 = p->rmt_outer;
				mDNSPlatformSourceAddrForDest(&tmpSrc, &tmpDst);
				if (!mDNSSameIPv6Address(p->loc_inner, m->AutoTunnelHostAddr) ||
					!mDNSSameIPv4Address(p->loc_outer, tmpSrc.ip.v4))
					{
					AutoTunnelSetKeys(p, mDNSfalse);
					p->loc_inner = m->AutoTunnelHostAddr;
					p->loc_outer = tmpSrc.ip.v4;
					AutoTunnelSetKeys(p, mDNStrue);
					}
				}
				else
					{
					if (!mDNSSameIPv6Address(p->loc_inner, m->AutoTunnelHostAddr) ||
						!mDNSSameIPv6Address(p->loc_outer6, m->AutoTunnelRelayAddrOut))
						{
						AutoTunnelSetKeys(p, mDNSfalse);
						p->loc_inner = m->AutoTunnelHostAddr;
						p->loc_outer6 = m->AutoTunnelRelayAddrOut;
						AutoTunnelSetKeys(p, mDNStrue);
						}
					}
			}


	SetSPS(m);

	NetworkInterfaceInfoOSX *i;
	for (i = m->p->InterfaceList; i; i = i->next)
		{
		if (!m->SPSSocket)		// Not being Sleep Proxy Server; close any open BPF fds
			{
			if (i->BPF_fd >= 0 && CountProxyTargets(m, i, mDNSNULL, mDNSNULL) == 0) CloseBPF(i);
			}
		else								// else, we're Sleep Proxy Server; open BPF fds
			{
			if (i->Exists && i->Registered == i && i->ifinfo.McastTxRx && !(i->ifa_flags & IFF_LOOPBACK) && i->BPF_fd == -1)
				{ LogSPS("%s requesting BPF", i->ifinfo.ifname); i->BPF_fd = -2; mDNSRequestBPF(); }
			}
		}

#endif // APPLE_OSX_mDNSResponder

	uDNS_SetupDNSConfig(m);
	mDNS_ConfigChanged(m);
	}

// Called with KQueueLock & mDNS lock
mDNSlocal void SetNetworkChanged(mDNS *const m, mDNSs32 delay)
	{
	if (!m->p->NetworkChanged || m->p->NetworkChanged - NonZeroTime(m->timenow + delay) < 0)
		{
		m->p->NetworkChanged = NonZeroTime(m->timenow + delay);
		LogInfo("SetNetworkChanged: setting network changed to %d (%d)", delay, m->p->NetworkChanged);
		}
	}

// Called with KQueueLock & mDNS lock
mDNSlocal void SetKeyChainTimer(mDNS *const m, mDNSs32 delay)
	{
	// If it's not set or it needs to happen sooner than when it's currently set
	if (!m->p->KeyChainTimer || m->p->KeyChainTimer - NonZeroTime(m->timenow + delay) > 0)
		{
		m->p->KeyChainTimer = NonZeroTime(m->timenow + delay);
		LogInfo("SetKeyChainTimer: %d", delay);
		}
	}

// Copy the fourth slash-delimited element from either:
//   State:/Network/Interface/<bsdname>/IPv4
// or
//   Setup:/Network/Service/<servicename>/Interface
mDNSlocal CFStringRef CopyNameFromKey(CFStringRef key)
	{
	CFArrayRef a;
	CFStringRef name = NULL;

	a = CFStringCreateArrayBySeparatingStrings(NULL, key, CFSTR("/"));
	if (a && CFArrayGetCount(a) == 5) name = CFRetain(CFArrayGetValueAtIndex(a, 3));
	if (a != NULL) CFRelease(a);

	return name;
	}

// Whether a key from a network change notification corresponds to
// an IP service that is explicitly configured for IPv4 Link Local
mDNSlocal mDNSBool ChangedKeysHaveIPv4LL(CFArrayRef inkeys)
	{
	SCDynamicStoreRef store = NULL;
	CFDictionaryRef dict = NULL;
	CFMutableArrayRef a;
	const void **keys = NULL, **vals = NULL;
	CFStringRef pattern = NULL;
	int i, ic, j, jc;
	mDNSBool found = mDNSfalse;
	
	jc = CFArrayGetCount(inkeys);
	if (!jc) goto done;

	store = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:ChangedKeysHaveIPv4LL"), NULL, NULL);
	if (store == NULL) goto done;

	a = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	if (a == NULL) goto done;

	// Setup:/Network/Service/[^/]+/Interface
	pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetInterface);
	if (pattern == NULL) goto done;
	CFArrayAppendValue(a, pattern);
	CFRelease(pattern);

	// Setup:/Network/Service/[^/]+/IPv4
	pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, kSCCompAnyRegex, kSCEntNetIPv4);
	if (pattern == NULL) goto done;
	CFArrayAppendValue(a, pattern);
	CFRelease(pattern);

	dict = SCDynamicStoreCopyMultiple(store, NULL, a);
	CFRelease(a);

	if (!dict)
		{
		LogMsg("ChangedKeysHaveIPv4LL: Empty dictionary");
		goto done;
		}

	ic = CFDictionaryGetCount(dict);
	vals = mDNSPlatformMemAllocate(sizeof (void *) * ic);
	keys = mDNSPlatformMemAllocate(sizeof (void *) * ic);
	CFDictionaryGetKeysAndValues(dict, keys, vals);
	
	for (j = 0; j < jc && !found; j++)
		{
		CFStringRef key = CFArrayGetValueAtIndex(inkeys, j);
		CFStringRef ifname = NULL;

		char buf[256];

		// It would be nice to use a regex here
		if (!CFStringHasPrefix(key, CFSTR("State:/Network/Interface/")) || !CFStringHasSuffix(key, kSCEntNetIPv4)) continue;
		
		if ((ifname = CopyNameFromKey(key)) == NULL) continue;
		if (mDNS_LoggingEnabled)
			{
			if (!CFStringGetCString(ifname, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
			LogInfo("ChangedKeysHaveIPv4LL: potential ifname %s", buf);
			}

		for (i = 0; i < ic; i++)
			{
			CFDictionaryRef ipv4dict;
			CFStringRef name;
			CFStringRef serviceid;
			CFStringRef configmethod;
			
			if (!CFStringHasSuffix(keys[i], kSCEntNetInterface)) continue;
			
			if (CFDictionaryGetTypeID() != CFGetTypeID(vals[i])) continue;
	
			if ((name = CFDictionaryGetValue(vals[i], kSCPropNetInterfaceDeviceName)) == NULL) continue;
	
			if (!CFEqual(ifname, name)) continue;
			
			if ((serviceid = CopyNameFromKey(keys[i])) == NULL) continue;
			if (mDNS_LoggingEnabled)
				{
				if (!CFStringGetCString(serviceid, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
				LogInfo("ChangedKeysHaveIPv4LL: found serviceid %s", buf);
				}
			
			pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, serviceid, kSCEntNetIPv4);
			CFRelease(serviceid);
			if (pattern == NULL) continue;
			
			ipv4dict = CFDictionaryGetValue(dict, pattern);
			CFRelease(pattern);
			if (!ipv4dict || CFDictionaryGetTypeID() != CFGetTypeID(ipv4dict)) continue;
			
			configmethod = CFDictionaryGetValue(ipv4dict, kSCPropNetIPv4ConfigMethod);
			if (!configmethod) continue;

			if (mDNS_LoggingEnabled)
				{
				if (!CFStringGetCString(configmethod, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
				LogInfo("ChangedKeysHaveIPv4LL: configmethod %s", buf);
				}
				
			if (CFEqual(configmethod, kSCValNetIPv4ConfigMethodLinkLocal)) { found = mDNStrue; break; }
			}
		
		CFRelease(ifname);
		}

	done:
	if (vals != NULL) mDNSPlatformMemFree(vals);
	if (keys != NULL) mDNSPlatformMemFree(keys);
	if (dict != NULL) CFRelease(dict);
	if (store != NULL) CFRelease(store);

	return found;
	}

mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context)
	{
	(void)store;        // Parameter not used
	mDNSBool changeNow = mDNSfalse;
	mDNS *const m = (mDNS *const)context;
	KQueueLock(m);
	mDNS_Lock(m);

	mDNSs32 delay = mDNSPlatformOneSecond * 2;				// Start off assuming a two-second delay

	int c = CFArrayGetCount(changedKeys);					// Count changes
	CFRange range = { 0, c };
	int c1 = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Hostnames   ) != 0);
	int c2 = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Computername) != 0);
	int c3 = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DynamicDNS  ) != 0);
	int c4 = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DNS         ) != 0);
	if (c && c - c1 - c2 - c3 - c4 == 0) delay = mDNSPlatformOneSecond/10;	// If these were the only changes, shorten delay
	
	{
	int i;
	for (i=0; i<c; i++)
		{
		char buf[256];
		if (!CFStringGetCString(CFArrayGetValueAtIndex(changedKeys, i), buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
		if (buf[0])
			{
			if (strstr(buf, "p2p"))
				{
				LogInfo("NetworkChanged SC key: %s, not delaying network change", buf);
				changeNow = mDNStrue;
				break;
				}
			}
		}
	}

	if (mDNS_LoggingEnabled)
		{
		int i;
		for (i=0; i<c; i++)
			{
			char buf[256];
			if (!CFStringGetCString(CFArrayGetValueAtIndex(changedKeys, i), buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
			LogInfo("***   NetworkChanged SC key: %s", buf);
			}
		LogInfo("***   NetworkChanged   *** %d change%s %s%s%s%sdelay %d",
			c, c>1?"s":"",
			c1 ? "(Local Hostname) " : "",
			c2 ? "(Computer Name) "  : "",
			c3 ? "(DynamicDNS) "     : "",
			c4 ? "(DNS) "            : "",
			delay);
		}

	mDNSBool btmmChanged = CFArrayContainsValue(changedKeys, range, NetworkChangedKey_BackToMyMac);
	if (btmmChanged) delay = 0;

	SetNetworkChanged(m, delay);
	
	// Other software might pick up these changes to register or browse in WAB or BTMM domains,
	// so in order for secure updates to be made to the server, make sure to read the keychain and
	// setup the DomainAuthInfo before handing the network change.
	// If we don't, then we will first try to register services in the clear, then later setup the
	// DomainAuthInfo, which is incorrect.
	if (c3 || btmmChanged)
		SetKeyChainTimer(m, delay);

	mDNS_Unlock(m);

	// If DNS settings changed, immediately force a reconfig (esp. cache flush)
	// Similarly, if an interface changed that is explicitly IPv4 link local, immediately force a reconfig
	if (c4 || ChangedKeysHaveIPv4LL(changedKeys) || changeNow) mDNSMacOSXNetworkChanged(m);

	KQueueUnlock(m, "NetworkChanged");
	}

#if APPLE_OSX_mDNSResponder
mDNSlocal void RefreshSPSStatus(const void *key, const void *value, void *context)
	{
	(void)context;
	char buf[IFNAMSIZ];

	CFStringRef ifnameStr = (CFStringRef)key;
	CFArrayRef array = (CFArrayRef)value;
	if (!CFStringGetCString(ifnameStr, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;

	LogInfo("RefreshSPSStatus: Updating SPS state for key %s, array count %d", buf, CFArrayGetCount(array));
	mDNSDynamicStoreSetConfig(kmDNSSleepProxyServersState, buf, value);
	}
#endif

mDNSlocal void DynamicStoreReconnected(SCDynamicStoreRef store, void *info)
	{
	mDNS *const m = (mDNS *const)info;
	(void)store;

	LogInfo("DynamicStoreReconnected: Reconnected");

	// State:/Network/MulticastDNS
	SetLocalDomains();

	// State:/Network/DynamicDNS
	if (m->FQDN.c[0])
		mDNSPlatformDynDNSHostNameStatusChanged(&m->FQDN, 1);

	// Note: PrivateDNS and BackToMyMac are automatically populated when configd is restarted
	// as we receive network change notifications and thus not necessary. But we leave it here
	// so that if things are done differently in the future, this code still works.

	// State:/Network/PrivateDNS
	CFMutableArrayRef sa = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	if (!sa)
		LogMsg("DynamicStoreReconnected:PrivateDNS: CFArrayCreateMutable failed");
	else
		{
		DomainAuthInfo *FoundInList;
		char stringbuf[MAX_ESCAPED_DOMAIN_NAME];	// Max legal domainname as C-string, including terminating NUL
		for (FoundInList = m->AuthInfoList; FoundInList; FoundInList = FoundInList->next)
			{
			ConvertDomainNameToCString(&FoundInList->domain, stringbuf);
			CFStringRef cfs = CFStringCreateWithCString(NULL, stringbuf, kCFStringEncodingUTF8);
			if (cfs) { CFArrayAppendValue(sa, cfs); CFRelease(cfs); }
			}
		mDNSDynamicStoreSetConfig(kmDNSPrivateConfig, mDNSNULL, sa);
		CFRelease(sa);
		}

	// State:/Network/BackToMyMac
#if APPLE_OSX_mDNSResponder
	mDNS_Lock(m);	
	UpdateAutoTunnelDomainStatuses(m);
	mDNS_Unlock(m);	

	// State:/Network/Interface/en0/SleepProxyServers
	if (spsStatusDict) CFDictionaryApplyFunction(spsStatusDict, RefreshSPSStatus, NULL);
#endif
	}

mDNSlocal mStatus WatchForNetworkChanges(mDNS *const m)
	{
	mStatus err = -1;
	SCDynamicStoreContext context = { 0, m, NULL, NULL, NULL };
	SCDynamicStoreRef     store    = SCDynamicStoreCreate(NULL, CFSTR("mDNSResponder:WatchForNetworkChanges"), NetworkChanged, &context);
	CFMutableArrayRef     keys     = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
	CFStringRef           pattern1 = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4);
	CFStringRef           pattern2 = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL, kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv6);
	CFMutableArrayRef     patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

	if (!store) { LogMsg("SCDynamicStoreCreate failed: %s", SCErrorString(SCError())); goto error; }
	if (!keys || !pattern1 || !pattern2 || !patterns) goto error;

	CFArrayAppendValue(keys, NetworkChangedKey_IPv4);
	CFArrayAppendValue(keys, NetworkChangedKey_IPv6);
	CFArrayAppendValue(keys, NetworkChangedKey_Hostnames);
	CFArrayAppendValue(keys, NetworkChangedKey_Computername);
	CFArrayAppendValue(keys, NetworkChangedKey_DNS);
	CFArrayAppendValue(keys, NetworkChangedKey_DynamicDNS);
	CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
	CFArrayAppendValue(keys, NetworkChangedKey_PowerSettings); // should remove as part of <rdar://problem/6751656>
	CFArrayAppendValue(keys, NetworkChangedKey_BTMMConnectivity);
	CFArrayAppendValue(patterns, pattern1);
	CFArrayAppendValue(patterns, pattern2);
	CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/[^/]+/AirPort"));
	if (!SCDynamicStoreSetNotificationKeys(store, keys, patterns))
		{ LogMsg("SCDynamicStoreSetNotificationKeys failed: %s", SCErrorString(SCError())); goto error; }

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	if (!SCDynamicStoreSetDispatchQueue(store, dispatch_get_main_queue()))
		{ LogMsg("SCDynamicStoreCreateRunLoopSource failed: %s", SCErrorString(SCError())); goto error; }
#else
	m->p->StoreRLS = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
	if (!m->p->StoreRLS) { LogMsg("SCDynamicStoreCreateRunLoopSource failed: %s", SCErrorString(SCError())); goto error; }
	CFRunLoopAddSource(CFRunLoopGetCurrent(), m->p->StoreRLS, kCFRunLoopDefaultMode);
#endif
	SCDynamicStoreSetDisconnectCallBack(store, DynamicStoreReconnected);
	m->p->Store = store;
	err = 0;
	goto exit;

	error:
	if (store)    CFRelease(store);

	exit:
	if (patterns) CFRelease(patterns);
	if (pattern2) CFRelease(pattern2);
	if (pattern1) CFRelease(pattern1);
	if (keys)     CFRelease(keys);

	return(err);
	}

#if 0 // <rdar://problem/6751656>
mDNSlocal void PMChanged(void *context)
	{
	mDNS *const m = (mDNS *const)context;

	KQueueLock(m);
	mDNS_Lock(m);
	
	LogSPS("PMChanged");
	
	SetNetworkChanged(m, mDNSPlatformOneSecond * 2);
	
	mDNS_Unlock(m);
	KQueueUnlock(m, "PMChanged");
	}

mDNSlocal mStatus WatchForPMChanges(mDNS *const m)
	{
	m->p->PMRLS = IOPMPrefsNotificationCreateRunLoopSource(PMChanged, m);
	if (!m->p->PMRLS) { LogMsg("IOPMPrefsNotificationCreateRunLoopSource failed!"); return mStatus_UnknownErr; }

	CFRunLoopAddSource(CFRunLoopGetCurrent(), m->p->PMRLS, kCFRunLoopDefaultMode);

	return mStatus_NoError;
	}
#endif

#ifndef KEV_DL_WAKEFLAGS_CHANGED
#define KEV_DL_WAKEFLAGS_CHANGED 17
#endif

#if !TARGET_OS_EMBEDDED		// don't setup packet filter rules on embedded

mDNSlocal void mDNSSetPacketFilterRules(mDNS *const m, char * ifname)
	{
	AuthRecord *rr;
	int	found = 0;
	
	for (rr = m->ResourceRecords; rr; rr=rr->next)
		{
		if ((rr->resrec.rrtype == kDNSServiceType_SRV) && (rr->ARType == AuthRecordAnyIncludeP2P))
			{
			uint16_t 		port = rr->resrec.rdata->u.srv.port.NotAnInteger;
			uint16_t 		protocol;
			const mDNSu8	*p;

			LogInfo("mDNSSetPacketFilterRules: found %s", ARDisplayString(m, rr));

			// Note presence of more than one p2p service in list
			// since code is currently opening only one port in the packet filter.
			found++;
			if (found > 1)
				{
				LogMsg("mDNSSetPacketFilterRules: found service #%d %s", found, ARDisplayString(m, rr));
				}

			// Assume <Service Instance>.<App Protocol>.<Transport Protocol>.<Name>
			p = rr->resrec.name->c;

			// Skip to App Protocol
			if (p[0]) p += 1 + p[0];
			// Skip to Transport Protocol
			if (p[0]) p += 1 + p[0];

			if      (SameDomainLabel(p, (mDNSu8 *)"\x4" "_tcp")) protocol = IPPROTO_TCP;
			else if (SameDomainLabel(p, (mDNSu8 *)"\x4" "_udp")) protocol = IPPROTO_UDP;
			else 
				{ 
				LogMsg("mDNSSetPacketFilterRules: could not determine transport protocol of service");
				LogMsg("mDNSSetPacketFilterRules: %s", ARDisplayString(m, rr));
				return;
				}

			LogInfo("mDNSSetPacketFilterRules: ifname %s, port(in NBO)0x%X, protocol %d",
					ifname, port, protocol);
			mDNSPacketFilterControl(PF_SET_RULES, ifname, port, protocol);
			}
		}
	}
#endif // !TARGET_OS_EMBEDDED


#if APPLE_OSX_mDNSResponder	

#if !TARGET_OS_EMBEDDED	
// If the p2p interface already exists, set the Bonjour packet filter rules for it.
mDNSexport void mDNSInitPacketFilter(void)
	{
	mDNS *const m = & mDNSStorage;

	NetworkInterfaceInfo *intf = GetFirstActiveInterface(m->HostInterfaces);
	while (intf)
		{
		if (strncmp(intf->ifname, "p2p", 3) == 0)
			{
			LogInfo("mDNSInitPacketFilter: Setting rules for ifname %s", intf->ifname);
			mDNSSetPacketFilterRules(m, intf->ifname);
			break;
			}
		intf = GetFirstActiveInterface(intf->next);
		}
	}

#else // !TARGET_OS_EMBEDDED

	// Currently no packet filter setup required embedded on platforms.
mDNSexport void mDNSInitPacketFilter()
	{
	}

#endif // !TARGET_OS_EMBEDDED
#endif // APPLE_OSX_mDNSResponder

mDNSlocal void SysEventCallBack(int s1, short __unused filter, void *context)
	{
	mDNS *const m = (mDNS *const)context;

	mDNS_Lock(m);

	struct { struct kern_event_msg k; char extra[256]; } msg;
	int bytes = recv(s1, &msg, sizeof(msg), 0);
	if (bytes < 0)
		LogMsg("SysEventCallBack: recv error %d errno %d (%s)", bytes, errno, strerror(errno));
	else
		{
		LogInfo("SysEventCallBack got %d bytes size %d %X %s %X %s %X %s id %d code %d %s",
			bytes, msg.k.total_size,
			msg.k.vendor_code , msg.k.vendor_code  == KEV_VENDOR_APPLE  ? "KEV_VENDOR_APPLE"  : "?",
			msg.k.kev_class   , msg.k.kev_class    == KEV_NETWORK_CLASS ? "KEV_NETWORK_CLASS" : "?",
			msg.k.kev_subclass, msg.k.kev_subclass == KEV_DL_SUBCLASS   ? "KEV_DL_SUBCLASS"   : "?",
			msg.k.id, msg.k.event_code,
			msg.k.event_code == KEV_DL_SIFFLAGS             ? "KEV_DL_SIFFLAGS"             :
			msg.k.event_code == KEV_DL_SIFMETRICS           ? "KEV_DL_SIFMETRICS"           :
			msg.k.event_code == KEV_DL_SIFMTU               ? "KEV_DL_SIFMTU"               :
			msg.k.event_code == KEV_DL_SIFPHYS              ? "KEV_DL_SIFPHYS"              :
			msg.k.event_code == KEV_DL_SIFMEDIA             ? "KEV_DL_SIFMEDIA"             :
			msg.k.event_code == KEV_DL_SIFGENERIC           ? "KEV_DL_SIFGENERIC"           :
			msg.k.event_code == KEV_DL_ADDMULTI             ? "KEV_DL_ADDMULTI"             :
			msg.k.event_code == KEV_DL_DELMULTI             ? "KEV_DL_DELMULTI"             :
			msg.k.event_code == KEV_DL_IF_ATTACHED          ? "KEV_DL_IF_ATTACHED"          :
			msg.k.event_code == KEV_DL_IF_DETACHING         ? "KEV_DL_IF_DETACHING"         :
			msg.k.event_code == KEV_DL_IF_DETACHED          ? "KEV_DL_IF_DETACHED"          :
			msg.k.event_code == KEV_DL_LINK_OFF             ? "KEV_DL_LINK_OFF"             :
			msg.k.event_code == KEV_DL_LINK_ON              ? "KEV_DL_LINK_ON"              :
			msg.k.event_code == KEV_DL_PROTO_ATTACHED       ? "KEV_DL_PROTO_ATTACHED"       :
			msg.k.event_code == KEV_DL_PROTO_DETACHED       ? "KEV_DL_PROTO_DETACHED"       :
			msg.k.event_code == KEV_DL_LINK_ADDRESS_CHANGED ? "KEV_DL_LINK_ADDRESS_CHANGED" :
			msg.k.event_code == KEV_DL_WAKEFLAGS_CHANGED    ? "KEV_DL_WAKEFLAGS_CHANGED"    : "?");

		// We receive network change notifications both through configd and through SYSPROTO_EVENT socket.
		// Configd may not generate network change events for manually configured interfaces (i.e., non-DHCP)
		// always during sleep/wakeup due to some race conditions (See radar:8666757). At the same time, if
		// "Wake on Network Access" is not turned on, the notification will not have KEV_DL_WAKEFLAGS_CHANGED.
		// Hence, during wake up, if we see a KEV_DL_LINK_ON (i.e., link is UP), we trigger a network change.

		if (msg.k.event_code == KEV_DL_WAKEFLAGS_CHANGED || msg.k.event_code == KEV_DL_LINK_ON)
			SetNetworkChanged(m, mDNSPlatformOneSecond * 2);

#if !TARGET_OS_EMBEDDED		// don't setup packet filter rules on embedded

		// For p2p interfaces, need to open the advertised service port in the firewall.
		if (msg.k.event_code == KEV_DL_IF_ATTACHED)
			{
			struct net_event_data   * p;
			p = (struct net_event_data *) & msg.k.event_data;

			if (strncmp(p->if_name, "p2p", 3) == 0)
				{
				char	ifname[IFNAMSIZ];
				snprintf(ifname, IFNAMSIZ, "%s%d", p->if_name, p->if_unit);

				LogInfo("SysEventCallBack: KEV_DL_IF_ATTACHED if_family = %d, if_unit = %d, if_name = %s", p->if_family, p->if_unit, p->if_name);

				mDNSSetPacketFilterRules(m, ifname);
				}
			}

		// For p2p interfaces, need to clear the firewall rules on interface detach
		if (msg.k.event_code == KEV_DL_IF_DETACHED)
			{
			struct net_event_data   * p;
			p = (struct net_event_data *) & msg.k.event_data;

			if (strncmp(p->if_name, "p2p", 3) == 0)
				{
				char	ifname[IFNAMSIZ];
				snprintf(ifname, IFNAMSIZ, "%s%d", p->if_name, p->if_unit);

				LogInfo("SysEventCallBack: KEV_DL_IF_DETACHED if_family = %d, if_unit = %d, if_name = %s", p->if_family, p->if_unit, p->if_name);

				mDNSPacketFilterControl(PF_CLEAR_RULES, ifname, 0, 0);
				}
			}
#endif // !TARGET_OS_EMBEDDED

		}

	mDNS_Unlock(m);
	}

mDNSlocal mStatus WatchForSysEvents(mDNS *const m)
	{
	m->p->SysEventNotifier = socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT);
	if (m->p->SysEventNotifier < 0)
		{ LogMsg("WatchForSysEvents: socket failed error %d errno %d (%s)", m->p->SysEventNotifier, errno, strerror(errno)); return(mStatus_NoMemoryErr); }

	struct kev_request kev_req = { KEV_VENDOR_APPLE, KEV_NETWORK_CLASS, KEV_DL_SUBCLASS };
	int err = ioctl(m->p->SysEventNotifier, SIOCSKEVFILT, &kev_req);
	if (err < 0)
		{
		LogMsg("WatchForSysEvents: SIOCSKEVFILT failed error %d errno %d (%s)", err, errno, strerror(errno));
		close(m->p->SysEventNotifier);
		m->p->SysEventNotifier = -1;
		return(mStatus_UnknownErr);
		}

	m->p->SysEventKQueue.KQcallback = SysEventCallBack;
	m->p->SysEventKQueue.KQcontext  = m;
	m->p->SysEventKQueue.KQtask     = "System Event Notifier";
	KQueueSet(m->p->SysEventNotifier, EV_ADD, EVFILT_READ, &m->p->SysEventKQueue);

	return(mStatus_NoError);
	}

#ifndef NO_SECURITYFRAMEWORK
mDNSlocal OSStatus KeychainChanged(SecKeychainEvent keychainEvent, SecKeychainCallbackInfo *info, void *context)
	{
	LogInfo("***   Keychain Changed   ***");
	mDNS *const m = (mDNS *const)context;
	SecKeychainRef skc;
	OSStatus err = SecKeychainCopyDefault(&skc);
	if (!err)
		{
		if (info->keychain == skc)
			{
			// For delete events, attempt to verify what item was deleted fail because the item is already gone, so we just assume they may be relevant
			mDNSBool relevant = (keychainEvent == kSecDeleteEvent);
			if (!relevant)
				{
				UInt32 tags[3] = { kSecTypeItemAttr, kSecServiceItemAttr, kSecAccountItemAttr };
				SecKeychainAttributeInfo attrInfo = { 3, tags, NULL };	// Count, array of tags, array of formats
				SecKeychainAttributeList *a = NULL;
				err = SecKeychainItemCopyAttributesAndData(info->item, &attrInfo, NULL, &a, NULL, NULL);
				if (!err)
					{
					relevant = ((a->attr[0].length == 4 && (!strncasecmp(a->attr[0].data, "ddns", 4) || !strncasecmp(a->attr[0].data, "sndd", 4))) ||
								(a->attr[1].length >= mDNSPlatformStrLen(dnsprefix) && (!strncasecmp(a->attr[1].data, dnsprefix, mDNSPlatformStrLen(dnsprefix)))) || 
								(a->attr[1].length >= mDNSPlatformStrLen(btmmprefix) && (!strncasecmp(a->attr[1].data, btmmprefix, mDNSPlatformStrLen(btmmprefix)))));
					SecKeychainItemFreeAttributesAndData(a, NULL);
					}
				}
			if (relevant)
				{
				LogInfo("***   Keychain Changed   *** KeychainEvent=%d %s",
					keychainEvent,
					keychainEvent == kSecAddEvent    ? "kSecAddEvent"    :
					keychainEvent == kSecDeleteEvent ? "kSecDeleteEvent" :
					keychainEvent == kSecUpdateEvent ? "kSecUpdateEvent" : "<Unknown>");
				// We're running on the CFRunLoop (Mach port) thread, not the kqueue thread, so we need to grab the KQueueLock before proceeding
				KQueueLock(m);
				mDNS_Lock(m);

				// To not read the keychain twice: when BTMM is enabled, changes happen to the keychain
				// then the BTMM DynStore dictionary, so delay reading the keychain for a second.
				// NetworkChanged() will reset the keychain timer to fire immediately when the DynStore changes.
				//
				// In the "fixup" case where the BTMM DNS servers aren't accepting the key mDNSResponder has,
				// the DynStore dictionary won't change (because the BTMM zone won't change).  In that case,
				// a one second delay is ok, as we'll still converge to correctness, and there's no race
				// condition between the RegistrationDomain and the DomainAuthInfo.
				//
				// Lastly, non-BTMM WAB cases can use the keychain but not the DynStore, so we need to set
				// the timer here, as it will not get set by NetworkChanged().
				SetKeyChainTimer(m, mDNSPlatformOneSecond);

				mDNS_Unlock(m);
				KQueueUnlock(m, "KeychainChanged");
				}
			}
		CFRelease(skc);
		}

	return 0;
	}
#endif

mDNSlocal void PowerOn(mDNS *const m)
	{
	mDNSCoreMachineSleep(m, false);		// Will set m->SleepState = SleepState_Awake;
	if (m->p->WakeAtUTC)
		{
		long utc = mDNSPlatformUTC();
		mDNSPowerRequest(-1,-1);		// Need to explicitly clear any previous power requests -- they're not cleared automatically on wake
		if      (m->p->WakeAtUTC - utc > 30)               LogSPS("PowerChanged PowerOn %d seconds early, assuming not maintenance wake",     m->p->WakeAtUTC - utc);
		else if (utc - m->p->WakeAtUTC > 30)               LogSPS("PowerChanged PowerOn %d seconds late, assuming not maintenance wake",      utc - m->p->WakeAtUTC);
		else if (!strncasecmp(HINFO_HWstring, "K66AP", 5)) LogSPS("PowerChanged PowerOn %d seconds late, device is K66AP so not re-sleeping", utc - m->p->WakeAtUTC);
		else
			{
			LogSPS("PowerChanged: Waking for network maintenance operations %d seconds early; re-sleeping in 20 seconds", m->p->WakeAtUTC - utc);
			m->p->RequestReSleep = mDNS_TimeNow(m) + 20 * mDNSPlatformOneSecond;
			}
		}
	}

mDNSlocal void PowerChanged(void *refcon, io_service_t service, natural_t messageType, void *messageArgument)
	{
	mDNS *const m = (mDNS *const)refcon;
	KQueueLock(m);
	(void)service;    // Parameter not used
	debugf("PowerChanged %X %lX", messageType, messageArgument);

	// Make sure our m->SystemWakeOnLANEnabled value correctly reflects the current system setting
	m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess();

	switch(messageType)
		{
		case kIOMessageCanSystemPowerOff:		LogSPS("PowerChanged kIOMessageCanSystemPowerOff     (no action)");	break;		// E0000240
		case kIOMessageSystemWillPowerOff:		LogSPS("PowerChanged kIOMessageSystemWillPowerOff");							// E0000250
												mDNSCoreMachineSleep(m, true);
												if (m->SleepState == SleepState_Sleeping) mDNSMacOSXNetworkChanged(m);
												break;
		case kIOMessageSystemWillNotPowerOff:	LogSPS("PowerChanged kIOMessageSystemWillNotPowerOff (no action)");	break;		// E0000260
		case kIOMessageCanSystemSleep:			LogSPS("PowerChanged kIOMessageCanSystemSleep        (no action)");	break;		// E0000270
		case kIOMessageSystemWillSleep:			LogSPS("PowerChanged kIOMessageSystemWillSleep");								// E0000280
												mDNSCoreMachineSleep(m, true);
												break;
		case kIOMessageSystemWillNotSleep:		LogSPS("PowerChanged kIOMessageSystemWillNotSleep    (no action)");	break;		// E0000290
		case kIOMessageSystemHasPoweredOn:		LogSPS("PowerChanged kIOMessageSystemHasPoweredOn");							// E0000300
												// If still sleeping (didn't get 'WillPowerOn' message for some reason?) wake now
												if (m->SleepState)
													{
													LogMsg("PowerChanged kIOMessageSystemHasPoweredOn: ERROR m->SleepState %d", m->SleepState);
													PowerOn(m);
													}
												// Just to be safe, schedule a mDNSMacOSXNetworkChanged(), in case we never received
												// the System Configuration Framework "network changed" event that we expect
												// to receive some time shortly after the kIOMessageSystemWillPowerOn message
												mDNS_Lock(m);
												if (!m->p->NetworkChanged ||
													m->p->NetworkChanged - NonZeroTime(m->timenow + mDNSPlatformOneSecond * 2) < 0)
													m->p->NetworkChanged = NonZeroTime(m->timenow + mDNSPlatformOneSecond * 2);
												mDNS_Unlock(m);
											
												break;
		case kIOMessageSystemWillRestart:		LogSPS("PowerChanged kIOMessageSystemWillRestart     (no action)");	break;		// E0000310
		case kIOMessageSystemWillPowerOn:		LogSPS("PowerChanged kIOMessageSystemWillPowerOn");								// E0000320

												// On Leopard and earlier, on wake from sleep, instead of reporting link state down, Apple
												// Ethernet drivers report "hardware incapable of detecting link state", which the kernel
												// interprets as "should assume we have networking", which results in the first 4-5 seconds
												// of packets vanishing into a black hole. To work around this, on wake from sleep,
												// we block for five seconds to let Ethernet come up, and then resume normal operation.
												if (OSXVers && OSXVers < OSXVers_10_6_SnowLeopard)
													{
													sleep(5);
													LogMsg("Running on Mac OS X version 10.%d earlier than 10.6; "
														"PowerChanged did sleep(5) to wait for Ethernet hardware", OSXVers - OSXVers_Base);
													}

												// Make sure our interface list is cleared to the empty state, then tell mDNSCore to wake
												if (m->SleepState != SleepState_Sleeping)
													{
													LogMsg("kIOMessageSystemWillPowerOn: ERROR m->SleepState %d", m->SleepState);
													m->SleepState = SleepState_Sleeping;
													mDNSMacOSXNetworkChanged(m);
													}
												PowerOn(m);
												break;
		default:								LogSPS("PowerChanged unknown message %X", messageType); break;
		}

	if (messageType == kIOMessageSystemWillSleep) m->p->SleepCookie = (long)messageArgument;
	else IOAllowPowerChange(m->p->PowerConnection, (long)messageArgument);

	KQueueUnlock(m, "PowerChanged Sleep/Wake");
	}

// iPhone OS doesn't currently have SnowLeopard's IO Power Management
// but it does define kIOPMAcknowledgmentOptionSystemCapabilityRequirements
#if defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements) && !TARGET_OS_EMBEDDED
mDNSlocal void SnowLeopardPowerChanged(void *refcon, IOPMConnection connection, IOPMConnectionMessageToken token, IOPMSystemPowerStateCapabilities eventDescriptor)
	{
	mDNS *const m = (mDNS *const)refcon;
	KQueueLock(m);
	LogSPS("SnowLeopardPowerChanged %X %X %X%s%s%s%s%s",
		connection, token, eventDescriptor,
		eventDescriptor & kIOPMSystemPowerStateCapabilityCPU     ? " CPU"     : "",
		eventDescriptor & kIOPMSystemPowerStateCapabilityVideo   ? " Video"   : "",
		eventDescriptor & kIOPMSystemPowerStateCapabilityAudio   ? " Audio"   : "",
		eventDescriptor & kIOPMSystemPowerStateCapabilityNetwork ? " Network" : "",
		eventDescriptor & kIOPMSystemPowerStateCapabilityDisk    ? " Disk"    : "");

	// Make sure our m->SystemWakeOnLANEnabled value correctly reflects the current system setting
	m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess();

	if (eventDescriptor & kIOPMSystemPowerStateCapabilityCPU)
		{
		// We might be in Sleeping or Transferring state. When we go from "wakeup" to "sleep" state, we don't
		// go directly to sleep state, but transfer in to the sleep state during which SleepState is set to
		// SleepState_Transferring. During that time, we might get another wakeup before we transition to Sleeping
		// state. In that case, we need to acknowledge the previous "sleep" before we acknowledge the wakeup.
		if (m->SleepLimit)
			{
			LogSPS("SnowLeopardPowerChanged: Waking up, Acking old Sleep, SleepLimit %d SleepState %d", m->SleepLimit, m->SleepState);
			IOPMConnectionAcknowledgeEvent(connection, m->p->SleepCookie);
			m->SleepLimit = 0;
			}
		LogSPS("SnowLeopardPowerChanged: Waking up, Acking Wakeup, SleepLimit %d SleepState %d", m->SleepLimit, m->SleepState);
		// CPU Waking. Note: Can get this message repeatedly, as other subsystems power up or down.
		if (m->SleepState != SleepState_Awake) PowerOn(m);
		IOPMConnectionAcknowledgeEvent(connection, token);
		}
	else
		{
		// CPU sleeping. Should not get this repeatedly -- once we're told that the CPU is halting
		// we should hear nothing more until we're told that the CPU has started executing again.
		if (m->SleepState) LogMsg("SnowLeopardPowerChanged: Sleep Error %X m->SleepState %d", eventDescriptor, m->SleepState);
		//sleep(5);
		//mDNSMacOSXNetworkChanged(m);
		mDNSCoreMachineSleep(m, true);
		//if (m->SleepState == SleepState_Sleeping) mDNSMacOSXNetworkChanged(m);
		m->p->SleepCookie = token;
		}

	KQueueUnlock(m, "SnowLeopardPowerChanged Sleep/Wake");
	}
#endif

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - /etc/hosts support
#endif

// Implementation Notes
//
// As /etc/hosts file can be huge (1000s of entries - when this comment was written, the test file had about
// 23000 entries with about 4000 duplicates), we can't use a linked list to store these entries. So, we parse
// them into a hash table. The implementation need to be able to do the following things efficiently
//
// 1. Detect duplicates e.g., two entries with "1.2.3.4 foo" 
// 2. Detect whether /etc/hosts has changed and what has changed since the last read from the disk
// 3. Ability to support multiple addresses per name e.g., "1.2.3.4 foo, 2.3.4.5 foo". To support this, we
//    need to be able set the RRSet of a resource record to the first one in the list and also update when
//    one of them go away. This is needed so that the core thinks that they are all part of the same RRSet and
//    not a duplicate
// 4. Don't maintain any local state about any records registered with the core to detect changes to /etc/hosts
//
// CFDictionary is not a suitable candidate because it does not support duplicates and even if we use a custom
// "hash" function to solve this, the others are hard to solve. Hence, we share the hash (AuthHash) implementation
// of the core layer which does all of the above very efficiently

mDNSexport void FreeEtcHosts(mDNS *const m, AuthRecord *const rr, mStatus result)
    {
    (void)m;  // unused
	(void)rr;
	(void)result;
	if (result == mStatus_MemFree)
		{
		LogInfo("FreeEtcHosts: %s", ARDisplayString(m, rr));
		freeL("etchosts", rr);
		}
	}

// Returns true on success and false on failure
mDNSlocal mDNSBool mDNSMacOSXCreateEtcHostsEntry(mDNS *const m, const domainname *domain, const struct sockaddr *sa, const domainname *cname, char *ifname, AuthHash *auth)
	{
	AuthRecord *rr;
	mDNSu32 slot;
	mDNSu32 namehash;
	AuthGroup *ag;
	mDNSInterfaceID InterfaceID = mDNSInterface_LocalOnly;
	mDNSu16 rrtype;

	if (!domain)
		{
		LogMsg("mDNSMacOSXCreateEtcHostsEntry: ERROR!! name NULL");
		return mDNSfalse;
		}
	if (!sa && !cname)
		{
		LogMsg("mDNSMacOSXCreateEtcHostsEntry: ERROR!! sa and cname both NULL");
		return mDNSfalse;
		}

	if (sa && sa->sa_family != AF_INET && sa->sa_family != AF_INET6)
		{
		LogMsg("mDNSMacOSXCreateEtcHostsEntry: ERROR!! sa with bad family %d", sa->sa_family);
		return mDNSfalse;
		}
	

	if (ifname)
		{
		mDNSu32 ifindex = if_nametoindex(ifname);
		if (!ifindex)
			{
			LogMsg("mDNSMacOSXCreateEtcHostsEntry: hosts entry %##s with invalid ifname %s", domain->c, ifname);
			return mDNSfalse;
			}
		InterfaceID = (mDNSInterfaceID)(uintptr_t)ifindex;
		}

	if (sa)
		rrtype = (sa->sa_family == AF_INET ? kDNSType_A : kDNSType_AAAA);
	else
		rrtype = kDNSType_CNAME;

	// Check for duplicates. See whether we parsed an entry before like this ?
	slot = AuthHashSlot(domain);
	namehash = DomainNameHashValue(domain);
	ag = AuthGroupForName(auth, slot, namehash, domain);
	if (ag)
		{
		rr = ag->members;
		while (rr)
			{
			if (rr->resrec.rrtype == rrtype)
				{
				if (rrtype == kDNSType_A)
					{
					mDNSv4Addr ip;
					ip.NotAnInteger = ((struct sockaddr_in*)sa)->sin_addr.s_addr;
					if (mDNSSameIPv4Address(rr->resrec.rdata->u.ipv4, ip))
						{
						LogInfo("mDNSMacOSXCreateEtcHostsEntry: Same IPv4 address for name %##s", domain->c);
						return mDNSfalse;
						}
					}
				else if (rrtype == kDNSType_AAAA)
					{
					mDNSv6Addr ip6;
					ip6.l[0] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[0];
					ip6.l[1] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[1];
					ip6.l[2] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[2];
					ip6.l[3] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[3];
					if (mDNSSameIPv6Address(rr->resrec.rdata->u.ipv6, ip6))
						{
						LogInfo("mDNSMacOSXCreateEtcHostsEntry: Same IPv6 address for name %##s", domain->c);
						return mDNSfalse;
						}
					}
				else if (rrtype == kDNSType_CNAME)
					{
					if (SameDomainName(&rr->resrec.rdata->u.name, cname))
						{
						LogInfo("mDNSMacOSXCreateEtcHostsEntry: Same cname %##s for name %##s", cname->c, domain->c);
						return mDNSfalse;
						}
					}
				}
			rr = rr->next;
			}
		}
	rr= mallocL("etchosts", sizeof(*rr));
	if (rr == NULL) return mDNSfalse;
	mDNSPlatformMemZero(rr, sizeof(*rr));
	mDNS_SetupResourceRecord(rr, NULL, InterfaceID, rrtype, 1, kDNSRecordTypeKnownUnique, AuthRecordLocalOnly, FreeEtcHosts, NULL);
	AssignDomainName(&rr->namestorage, domain);

	if (sa)
		{
		rr->resrec.rdlength = sa->sa_family == AF_INET ? sizeof(mDNSv4Addr) : sizeof(mDNSv6Addr);
		if (sa->sa_family == AF_INET)
			rr->resrec.rdata->u.ipv4.NotAnInteger = ((struct sockaddr_in*)sa)->sin_addr.s_addr;
		else
			{
			rr->resrec.rdata->u.ipv6.l[0] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[0];
			rr->resrec.rdata->u.ipv6.l[1] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[1];
			rr->resrec.rdata->u.ipv6.l[2] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[2];
			rr->resrec.rdata->u.ipv6.l[3] = ((struct sockaddr_in6*)sa)->sin6_addr.__u6_addr.__u6_addr32[3];
			}
		}
	else
		{
		rr->resrec.rdlength = DomainNameLength(cname);
		rr->resrec.rdata->u.name.c[0] = 0;
		AssignDomainName(&rr->resrec.rdata->u.name, cname);
		}
	rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
	SetNewRData(&rr->resrec, mDNSNULL, 0);	// Sets rr->rdatahash for us
	LogInfo("mDNSMacOSXCreateEtcHostsEntry: Adding resource record %s", ARDisplayString(m, rr));
	InsertAuthRecord(m, auth, rr);
	return mDNStrue;
	}

mDNSlocal int EtcHostsParseOneName(int start, int length, char *buffer, char **name)
	{
	int i;

	*name = NULL;
	for (i = start; i < length; i++)
		{
		if (buffer[i] != ' ' && buffer[i] != ',' && buffer[i] != '\t')
			{
			*name = &buffer[i];
			
			// Found the start of a name, find the end and null terminate
			for (i++; i < length; i++)
				{
				if (buffer[i] == ' ' || buffer[i] == ',' || buffer[i] == '\t')
					{
					buffer[i] = 0;
					break;
					}
				}
			return i;
			}
		}
	return -1;
	}

mDNSlocal void mDNSMacOSXParseEtcHostsLine(mDNS *const m, char *buffer, ssize_t length, AuthHash *auth)
	{
	int i;
	int ifStart = 0;
	char *ifname = NULL;
	domainname name1d;
	domainname name2d;
	char *name1;
	char *name2;
	int aliasIndex;
	
	// Find the end of the address string
	for (i = 0; i < length; i++)
		{
		if (buffer[i] == ' ' || buffer[i] == ',' || buffer[i] == '\t' || buffer[i] == '%')
			{
			if (buffer[i] == '%')
				ifStart = i + 1;
			buffer[i] = 0;
			break;
			}
		}
	
	// Convert the address string to an address
	struct addrinfo	hints;
	bzero(&hints, sizeof(hints));
	hints.ai_flags = AI_NUMERICHOST;
	struct addrinfo	*gairesults = NULL;
	if (getaddrinfo(buffer, NULL, &hints, &gairesults) != 0)
		{
		LogInfo("mDNSMacOSXParseEtcHostsLine: getaddrinfo returning null");
		return;
		}

	if (ifStart)
		{
		// Parse the interface
		ifname = &buffer[ifStart];
		for (i = ifStart + 1; i < length; i++)
			{
			if (buffer[i] == ' ' || buffer[i] == ',' || buffer[i] == '\t')
				{
				buffer[i] = 0;
				break;
				}
			}
		}
		
	i = EtcHostsParseOneName(i + 1, length, buffer, &name1);
	if (i == length)
		{
		// Common case (no aliases) : The entry is of the form "1.2.3.4 somehost" with no trailing white spaces/tabs etc.
		if (!MakeDomainNameFromDNSNameString(&name1d, name1))
			{
			LogMsg("mDNSMacOSXParseEtcHostsLine: ERROR!! cannot convert to domain name %s", name1);
			freeaddrinfo(gairesults);
			return;
			}
		mDNSMacOSXCreateEtcHostsEntry(m, &name1d, gairesults->ai_addr, mDNSNULL, ifname, auth);
		}
	else if (i != -1)
		{
		domainname first;
		// We might have some extra white spaces at the end for the common case of "1.2.3.4 somehost".
		// When we parse again below, EtchHostsParseOneName would return -1 and we will end up
		// doing the right thing.
		if (!MakeDomainNameFromDNSNameString(&first, name1))
			{
			LogMsg("mDNSMacOSXParseEtcHostsLine: ERROR!! cannot convert to domain name %s", name1);
			freeaddrinfo(gairesults);
			return;
			}
		// If the /etc/hosts has an entry like this
		//
		// 1.2.3.4 sun star bright
		//
		// star and bright are aliases (gethostbyname h_alias should point to these) and sun is the canonical
		// name (getaddrinfo ai_cannonname and gethostbyname h_name points to "sun")
		//
		// To achieve this, we need to add the entry like this:
		//
		// star CNAME bright
		// bright CNAME sun
		// sun A 1.2.3.4
		//
		// We store the first name we parsed in "first". Then we parse additional names adding CNAME records
		// till we reach the end. When we reach the end, we wrap around and add one final CNAME with the last 
		// entry and the first entry. Finally, we add the Address (A/AAAA) record.
		aliasIndex = 0;
		while (i <= length)
			{
			// Parse a name. If there are no names, we need to know whether we
			// parsed CNAMEs before or not. If we parsed CNAMEs before, then we
			// add a CNAME with the last name and the first name. Otherwise, this
			// is same as the common case above where the line has just one name
			// but with trailing white spaces.
			i = EtcHostsParseOneName(i + 1, length, buffer, &name2);
			if (name2)
				{
				if (!MakeDomainNameFromDNSNameString(&name2d, name2))
					{
					LogMsg("mDNSMacOSXParseEtcHostsLine: ERROR!! cannot convert to domain name %s", name2);
					freeaddrinfo(gairesults);
					return;
					}
				aliasIndex++;
				}
			else if (!aliasIndex)
				{
				// We have never parsed any aliases. This case happens if there
				// is just one name and some extra white spaces at the end.
				LogInfo("mDNSMacOSXParseEtcHostsLine: White space at the end of %##s", first.c);
				break;
				}
			else
				{
				// We have parsed at least one alias before and we reached the end of the line.
				// Setup a CNAME for the last name with "first" name as its RDATA
				name2d.c[0] = 0;
				AssignDomainName(&name2d, &first);
				}

			// Don't add a CNAME for the first alias we parse (see the example above).
			// As we parse more, we might discover that there are no more aliases, in
			// which case we would have set "name2d" to "first" above. We need to add
			// the CNAME in that case.

			if (aliasIndex > 1 || SameDomainName(&name2d, &first))
				{
				// Ignore if it points to itself 
				if (!SameDomainName(&name1d, &name2d))
					{
		 			if (!mDNSMacOSXCreateEtcHostsEntry(m, &name1d, mDNSNULL, &name2d, ifname, auth))
						{
						freeaddrinfo(gairesults);
						return;
						}
					}
				else
					LogMsg("mDNSMacOSXParseEtcHostsLine: Ignoring entry with same names name1 %##s, name2 %##s", name1d.c, name2d.c);
				}

			// If we have already wrapped around, we just need to add the A/AAAA record alone
			// which is done below
			if (SameDomainName(&name2d, &first)) break;

			// Remember the current name so that we can set the CNAME record if we parse one
			// more name
			name1d.c[0] = 0;
			AssignDomainName(&name1d, &name2d);
			}
		// Added all the CNAMEs if any, add the "A/AAAA" record
		mDNSMacOSXCreateEtcHostsEntry(m, &first, gairesults->ai_addr, mDNSNULL, ifname, auth);
		}
	freeaddrinfo(gairesults);
	}

mDNSlocal void mDNSMacOSXParseEtcHosts(mDNS *const m, int fd, AuthHash *auth)
	{
	if (fd == -1) { LogInfo("mDNSMacOSXParseEtcHosts: fd is -1"); return; }
	
	LogInfo("mDNSMacOSXParseEtcHosts: Parsing etc hosts");

	// Read through the file
	char	readBuf[1024]; // support a maximum line of 1024 bytes including newline
	off_t	offset = 0;
	int	comment = 0;
	ssize_t	i = 0;
	ssize_t	length;
	ssize_t	linestart;
	while ((length = pread(fd, &readBuf[i], sizeof(readBuf) - i, offset) + i) >= i)
		{
		if (length == i)
			{
			// end of file
			if (length != 0) mDNSMacOSXParseEtcHostsLine(m, readBuf, length, auth);
			break;
			}
		
		// increment our offset by the number of bytes we read
		offset += length - i;
		
		// loop through the buffer looking for end of lines
		for (linestart = 0; i < length; i++)
			{
			if (readBuf[i] == '#' || readBuf[i] == '\r' || readBuf[i] == '\n')
				{
				int parseline = i - linestart > 0 && comment == 0;
				comment = readBuf[i] == '#';
				if (parseline)
					{
					readBuf[i] = 0;
					mDNSMacOSXParseEtcHostsLine(m, &readBuf[linestart], i - linestart, auth);
					}
				linestart = i + 1;
				}
			}
		
		// prepare to read the next chunk of the file
		if (linestart == 0)
			{
			// single line was larger than our buffer, we're going to ignore it
			comment = 1; // treat remainder of line as comment
			i = 0;
			}
		else
			{
			i = length - linestart;
			if (i) memmove(readBuf, &readBuf[linestart], i);
			}
		}
	}

mDNSlocal void mDNSMacOSXUpdateEtcHosts(mDNS *const m);

mDNSlocal int mDNSMacOSXGetEtcHostsFD(mDNS *const m)
	{
#ifdef __DISPATCH_GROUP__
	// Can't do this stuff to be notified of changes in /etc/hosts if we don't have libdispatch
	static dispatch_queue_t		etcq	 = 0;
	static dispatch_source_t	etcsrc	 = 0;
	static dispatch_source_t	hostssrc = 0;
	
	// First time through? just schedule ourselves on the main queue and we'll do the work later
	if (!etcq)
	{
		etcq = dispatch_get_main_queue();
		if (etcq)
			{
			// Do this work on the queue, not here - solves potential synchronization issues
			dispatch_async(etcq, ^{mDNSMacOSXUpdateEtcHosts(m);});
			}
		return -1;
	}
	
	if (hostssrc) return dispatch_source_get_handle(hostssrc);
#endif

	int fd = open("/etc/hosts", O_RDONLY);
	
#ifdef __DISPATCH_GROUP__
	// Can't do this stuff to be notified of changes in /etc/hosts if we don't have libdispatch
	if (fd == -1)
		{
		// If the open failed and we're already watching /etc, we're done
		if (etcsrc) { LogInfo("mDNSMacOSXGetEtcHostsFD: Returning etcfd because no etchosts"); return fd; }
		
		// we aren't watching /etc, we should be
		fd = open("/etc", O_RDONLY);
		if (fd == -1) { LogInfo("mDNSMacOSXGetEtcHostsFD: etc does not exist"); return -1; }
		etcsrc = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_RENAME, etcq);
		if (etcsrc == NULL)
			{
			close(fd);
			return -1;
			}
		dispatch_source_set_event_handler(etcsrc,
			^{
			u_int32_t	flags = dispatch_source_get_data(etcsrc);
			LogMsg("mDNSMacOSXGetEtcHostsFD: /etc changed 0x%x", flags);
			if ((flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) != 0)
				{
				dispatch_source_cancel(etcsrc);
				dispatch_release(etcsrc);
				etcsrc = NULL;
				dispatch_async(etcq, ^{mDNSMacOSXUpdateEtcHosts(m);});
				return;
				}
			if ((flags & DISPATCH_VNODE_WRITE) != 0 && hostssrc == NULL)
				{
				mDNSMacOSXUpdateEtcHosts(m);
				}
			});
		dispatch_source_set_cancel_handler(etcsrc, ^{close(fd);});
		dispatch_resume(etcsrc);
		
		// Try and open /etc/hosts once more now that we're watching /etc, in case we missed the creation
		fd = open("/etc/hosts", O_RDONLY | O_EVTONLY);
		if (fd == -1) { LogMsg("mDNSMacOSXGetEtcHostsFD etc hosts does not exist, watching etc"); return -1; }
		}
	
	// create a dispatch source to watch for changes to hosts file
	hostssrc = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
					(DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_RENAME |
					DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_LINK | DISPATCH_VNODE_REVOKE), etcq);
	if (hostssrc == NULL)
		{
		close(fd);
		return -1;
		}
	dispatch_source_set_event_handler(hostssrc,
		^{
		u_int32_t	flags = dispatch_source_get_data(hostssrc);
		LogInfo("mDNSMacOSXGetEtcHostsFD: /etc/hosts changed 0x%x", flags);
		if ((flags & (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME)) != 0)
			{
			dispatch_source_cancel(hostssrc);
			dispatch_release(hostssrc);
			hostssrc = NULL;
			// Bug in LibDispatch: wait a second before scheduling the block. If we schedule
			// the block immediately, we try to open the file and the file may not exist and may
			// fail to get a notification in the future. When the file does not exist and
			// we start to monitor the directory, on "dispatch_resume" of that source, there
			// is no guarantee that the file creation will be notified always because when
			// the dispatch_resume returns, the kevent manager may not have registered the
			// kevent yet but the file may have been created
			usleep(1000000);
			dispatch_async(etcq, ^{mDNSMacOSXUpdateEtcHosts(m);});
			return;
			}
		if ((flags & DISPATCH_VNODE_WRITE) != 0)
			{
			mDNSMacOSXUpdateEtcHosts(m);
			}
		});
	dispatch_source_set_cancel_handler(hostssrc, ^{LogInfo("mDNSMacOSXGetEtcHostsFD: Closing etchosts fd %d", fd); close(fd);});
	dispatch_resume(hostssrc);
	
	// Cleanup /etc source, no need to watch it if we already have /etc/hosts
	if (etcsrc)
		{
		dispatch_source_cancel(etcsrc);
		dispatch_release(etcsrc);
		etcsrc = NULL;
		}
	
	LogInfo("mDNSMacOSXGetEtcHostsFD: /etc/hosts being monitored, and not etc");
	return hostssrc ? (int)dispatch_source_get_handle(hostssrc) : -1;
#else
	(void)m;
	return fd;
#endif
	}

// When /etc/hosts is modified, flush all the cache records as there may be local
// authoritative answers now
mDNSlocal void FlushAllCacheRecords(mDNS *const m)
	{
	CacheRecord *cr;
	mDNSu32 slot;
	CacheGroup *cg;

	FORALL_CACHERECORDS(slot, cg, cr)
		{
		// Skip multicast.
		if (cr->resrec.InterfaceID) continue;

		// If a resource record can answer A or AAAA, they need to be flushed so that we will
		// never used to deliver an ADD or RMV
		if (RRTypeAnswersQuestionType(&cr->resrec, kDNSType_A) ||
			RRTypeAnswersQuestionType(&cr->resrec, kDNSType_AAAA))
			{
			LogInfo("FlushAllCacheRecords: Purging Resourcerecord %s", CRDisplayString(m, cr));
			mDNS_PurgeCacheResourceRecord(m, cr);
			}
		}
	}

// Add new entries to the core. If justCheck is set, this function does not add, just returns true
mDNSlocal mDNSBool EtcHostsAddNewEntries(mDNS *const m, AuthHash *newhosts, mDNSBool justCheck)
	{
	AuthGroup *ag;
	mDNSu32 slot;
	AuthRecord *rr, *primary, *rrnext;
	for (slot = 0; slot < AUTH_HASH_SLOTS; slot++)
		for (ag = newhosts->rrauth_hash[slot]; ag; ag = ag->next)
			{
			primary = NULL;
			for (rr = ag->members; rr; rr = rrnext)
				{
				rrnext = rr->next;
				AuthGroup *ag1;
				AuthRecord *rr1;
				mDNSBool found = mDNSfalse;
				ag1 = AuthGroupForRecord(&m->rrauth, slot, &rr->resrec);
				if (ag1 && ag1->members)
					{
					if (!primary) primary = ag1->members;
					rr1 = ag1->members;
					while (rr1)
						{
						// We are not using InterfaceID in checking for duplicates. This means,
						// if there are two addresses for a given name e.g., fe80::1%en0 and
						// fe80::1%en1, we only add the first one. It is not clear whether
						// this is a common case. To fix this, we also need to modify
						// mDNS_Register_internal in how it handles duplicates. If it becomes a
						// common case, we will fix it then.
						if (IdenticalResourceRecord(&rr1->resrec, &rr->resrec))
							{
							LogInfo("EtcHostsAddNewEntries: Skipping, not adding %s", ARDisplayString(m, rr1));
							found = mDNStrue;
							break;
							}
						rr1 = rr1->next;
						}
					}
				if (!found)
					{
					if (justCheck) 
						{
						LogInfo("EtcHostsAddNewEntries: Entry %s not registered with core yet", ARDisplayString(m, rr));
						return mDNStrue;
						}
					RemoveAuthRecord(m, newhosts, rr);
					// if there is no primary, point to self
					rr->RRSet = (primary ? primary : rr);
					rr->next = NULL;
					LogInfo("EtcHostsAddNewEntries: Adding %s", ARDisplayString(m, rr));
					if (mDNS_Register_internal(m, rr) != mStatus_NoError)
						LogMsg("EtcHostsAddNewEntries: mDNS_Register failed for %s", ARDisplayString(m, rr));
					}
				}
			}
		return mDNSfalse;
	}

// Delete entries from the core that are no longer needed. If justCheck is set, this function
// does not delete, just returns true
mDNSlocal mDNSBool EtcHostsDeleteOldEntries(mDNS *const m, AuthHash *newhosts, mDNSBool justCheck)
	{
	AuthGroup *ag;
	mDNSu32 slot;
	AuthRecord *rr, *primary, *rrnext;
	for (slot = 0; slot < AUTH_HASH_SLOTS; slot++)
		for (ag = m->rrauth.rrauth_hash[slot]; ag; ag = ag->next)
			for (rr = ag->members; rr; rr = rrnext)
				{
				mDNSBool found = mDNSfalse;
				AuthGroup *ag1;
				AuthRecord *rr1;
				rrnext = rr->next;
				if (rr->RecordCallback != FreeEtcHosts) continue;
				ag1 = AuthGroupForRecord(newhosts, slot, &rr->resrec);
				if (ag1)
					{
					primary = rr1 = ag1->members;
					while (rr1)
						{
						if (IdenticalResourceRecord(&rr1->resrec, &rr->resrec))
							{
							LogInfo("EtcHostsDeleteOldEntries: Old record %s found in new, skipping", ARDisplayString(m, rr));
							found = mDNStrue;
							break;
							}
						rr1 = rr1->next;
						}
					}
				// there is no corresponding record in newhosts for the same name. This means
				// we should delete this from the core.
				if (!found)
					{
					if (justCheck)
						{
						LogInfo("EtcHostsDeleteOldEntries: Record %s not found in new, deleting", ARDisplayString(m, rr));
						return mDNStrue;
						}
					// if primary is going away, make sure that the rest of the records
					// point to the new primary
					if (rr == ag->members)
						{
						AuthRecord *new_primary = rr->next;
						AuthRecord *r = new_primary;
						while (r)
							{
							if (r->RRSet == rr)
								{
								LogInfo("EtcHostsDeleteOldEntries: Updating Resource Record %s to primary", ARDisplayString(m, r));
								r->RRSet = new_primary;
								}
							else LogMsg("EtcHostsDeleteOldEntries: ERROR!! Resource Record %s not pointing to primary %##s", ARDisplayString(m, r), r->resrec.name);
							r = r->next;
							}
						}
					LogInfo("EtcHostsDeleteOldEntries: Deleting %s", ARDisplayString(m, rr));
					mDNS_Deregister_internal(m, rr, mDNS_Dereg_normal);
					}
				}
	return mDNSfalse;
	}

mDNSlocal void UpdateEtcHosts(mDNS *const m, void *context)
	{
	AuthHash *newhosts = (AuthHash *)context;

	if (!m->mDNS_busy) LogMsg("UpdateEtcHosts: ERROR!! Lock not held");

	//Delete old entries from the core if they are not present in the newhosts
	EtcHostsDeleteOldEntries(m, newhosts, mDNSfalse);
	// Add the new entries to the core if not already present in the core
	EtcHostsAddNewEntries(m, newhosts, mDNSfalse);
	}

mDNSlocal void FreeNewHosts(AuthHash *newhosts)
	{
	mDNSu32 slot;
	AuthGroup *ag, *agnext;
	AuthRecord *rr, *rrnext;

	for (slot = 0; slot < AUTH_HASH_SLOTS; slot++)
		for (ag = newhosts->rrauth_hash[slot]; ag; ag = agnext)
			{
			agnext = ag->next;
			for (rr = ag->members; rr; rr = rrnext)
				{
				rrnext = rr->next;
				freeL("etchosts", rr);
				}
			freeL("AuthGroups", ag);
			}
	}

mDNSlocal void mDNSMacOSXUpdateEtcHosts(mDNS *const m)
	{
	AuthHash newhosts;
	
	// As we will be modifying the core, we can only have one thread running at
	// any point in time.
	KQueueLock(m);

	mDNSPlatformMemZero(&newhosts, sizeof(AuthHash));

	// Get the file desecriptor (will trigger us to start watching for changes)
	int fd = mDNSMacOSXGetEtcHostsFD(m);
	if (fd != -1)
		{
		LogInfo("mDNSMacOSXUpdateEtcHosts: Parsing /etc/hosts fd %d", fd);
		mDNSMacOSXParseEtcHosts(m, fd, &newhosts);
		}
	else LogInfo("mDNSMacOSXUpdateEtcHosts: /etc/hosts is not present");
	
	// Optimization: Detect whether /etc/hosts changed or not.
	//
	// 1. Check to see if there are any new entries. We do this by seeing whether any entries in 
	//    newhosts is already registered with core.  If we find at least one entry that is not
	//    registered with core, then it means we have work to do.
	//    
	// 2. Next, we check to see if any of the entries that are registered with core is not present
	//   in newhosts. If we find at least one entry that is not present, it means we have work to
	//   do.
	// 
	// Note: We may not have to hold the lock right here as KQueueLock is held which prevents any
	// other thread from running. But mDNS_Lock is needed here as we will be traversing the core
	// data structure in EtcHostsDeleteOldEntries/NewEntries which might expect the lock to be held
	// in the future and this code does not have to change.
	mDNS_Lock(m);
	// Add the new entries to the core if not already present in the core
	if (!EtcHostsAddNewEntries(m, &newhosts, mDNStrue))
		{
		// No new entries to add, check to see if we need to delete any old entries from the
		// core if they are not present in the newhosts
		if (!EtcHostsDeleteOldEntries(m, &newhosts, mDNStrue))
			{
			LogInfo("mDNSMacOSXUpdateEtcHosts: No work");
			mDNS_Unlock(m);
			KQueueUnlock(m, "/etc/hosts changed");
			FreeNewHosts(&newhosts);
			return;
			}
		}

	// This will flush the cache, stop and start the query so that the queries
	// can look at the /etc/hosts again
	//
	// Notes:
	//
	// We can't delete and free the records here. We wait for the mDNSCoreRestartAddressQueries to
	// deliver RMV events. It has to be done in a deferred way because we can't deliver RMV
	// events for local records *before* the RMV events for cache records. mDNSCoreRestartAddressQueries
	// delivers these events in the right order and then calls us back to delete them.
	//
	// Similarly, we do a deferred Registration of the record because mDNSCoreRestartAddressQueries
	// is a common function that looks at all local auth records and delivers a RMV including
	// the records that we might add here. If we deliver a ADD here, it will get a RMV and then when
	// the query is restarted, it will get another ADD. To avoid this (ADD-RMV-ADD), we defer registering
	// the record until the RMVs are delivered in mDNSCoreRestartAddressQueries after which UpdateEtcHosts
	// is called back where we do the Registration of the record. This results in RMV followed by ADD which
    // looks normal.
	mDNSCoreRestartAddressQueries(m, mDNSfalse, FlushAllCacheRecords, UpdateEtcHosts, &newhosts);
	mDNS_Unlock(m);

	KQueueUnlock(m, "/etc/hosts changed");
	FreeNewHosts(&newhosts);
	}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Initialization & Teardown
#endif

CF_EXPORT CFDictionaryRef _CFCopySystemVersionDictionary(void);
CF_EXPORT const CFStringRef _kCFSystemVersionProductNameKey;
CF_EXPORT const CFStringRef _kCFSystemVersionProductVersionKey;
CF_EXPORT const CFStringRef _kCFSystemVersionBuildVersionKey;

// Major version  6 is 10.2.x (Jaguar)
// Major version  7 is 10.3.x (Panther)
// Major version  8 is 10.4.x (Tiger)
// Major version  9 is 10.5.x (Leopard)
// Major version 10 is 10.6.x (SnowLeopard)
mDNSexport void mDNSMacOSXSystemBuildNumber(char *HINFO_SWstring)
	{
	int major = 0, minor = 0;
	char letter = 0, prodname[256]="<Unknown>", prodvers[256]="<Unknown>", buildver[256]="<Unknown>";
	CFDictionaryRef vers = _CFCopySystemVersionDictionary();
	if (vers)
		{
		CFStringRef cfprodname = CFDictionaryGetValue(vers, _kCFSystemVersionProductNameKey);
		CFStringRef cfprodvers = CFDictionaryGetValue(vers, _kCFSystemVersionProductVersionKey);
		CFStringRef cfbuildver = CFDictionaryGetValue(vers, _kCFSystemVersionBuildVersionKey);
		if (cfprodname) CFStringGetCString(cfprodname, prodname, sizeof(prodname), kCFStringEncodingUTF8);
		if (cfprodvers) CFStringGetCString(cfprodvers, prodvers, sizeof(prodvers), kCFStringEncodingUTF8);
		if (cfbuildver && CFStringGetCString(cfbuildver, buildver, sizeof(buildver), kCFStringEncodingUTF8))
			sscanf(buildver, "%d%c%d", &major, &letter, &minor);
		CFRelease(vers);
		}
	if (!major) { major=8; LogMsg("Note: No Major Build Version number found; assuming 8"); }
	if (HINFO_SWstring) mDNS_snprintf(HINFO_SWstring, 256, "%s %s (%s), %s", prodname, prodvers, buildver, STRINGIFY(mDNSResponderVersion));
	//LogMsg("%s %s (%s), %d %c %d", prodname, prodvers, buildver, major, letter, minor);
	
	// If product name is "Mac OS X" (or similar) we set OSXVers, else we set iOSVers;
	if ((prodname[0] & 0xDF) == 'M') OSXVers = major; else iOSVers = major;
	}

// Test to see if we're the first client running on UDP port 5353, by trying to bind to 5353 without using SO_REUSEPORT.
// If we fail, someone else got here first. That's not a big problem; we can share the port for multicast responses --
// we just need to be aware that we shouldn't expect to successfully receive unicast UDP responses.
mDNSlocal mDNSBool mDNSPlatformInit_CanReceiveUnicast(void)
	{
	int err = -1;
	int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (s < 3)
		LogMsg("mDNSPlatformInit_CanReceiveUnicast: socket error %d errno %d (%s)", s, errno, strerror(errno));
	else
		{
		struct sockaddr_in s5353;
		s5353.sin_family      = AF_INET;
		s5353.sin_port        = MulticastDNSPort.NotAnInteger;
		s5353.sin_addr.s_addr = 0;
		err = bind(s, (struct sockaddr *)&s5353, sizeof(s5353));
		close(s);
		}

	if (err) LogMsg("No unicast UDP responses");
	else     debugf("Unicast UDP responses okay");
	return(err == 0);
	}

mDNSlocal void CreatePTRRecord(mDNS *const m, const domainname *domain)
	{
	AuthRecord *rr;
	const domainname *pname = (domainname *)"\x9""localhost";

	rr= mallocL("localhosts", sizeof(*rr));
	if (rr == NULL) return;
	mDNSPlatformMemZero(rr, sizeof(*rr));

	mDNS_SetupResourceRecord(rr, mDNSNULL, mDNSInterface_LocalOnly, kDNSType_PTR, kHostNameTTL, kDNSRecordTypeKnownUnique, AuthRecordLocalOnly, mDNSNULL, mDNSNULL);
	AssignDomainName(&rr->namestorage, domain);

	rr->resrec.rdlength = DomainNameLength(pname);
	rr->resrec.rdata->u.name.c[0] = 0;
	AssignDomainName(&rr->resrec.rdata->u.name, pname);

	rr->resrec.namehash = DomainNameHashValue(rr->resrec.name);
	SetNewRData(&rr->resrec, mDNSNULL, 0);	// Sets rr->rdatahash for us
	mDNS_Register(m, rr);
	}

// Setup PTR records for 127.0.0.1 and ::1. This helps answering them locally rather than relying
// on the external DNS server to answer this. Sometimes, the DNS servers don't respond in a timely
// fashion and applications depending on this e.g., telnetd, times out after 30 seconds creating
// a bad user experience. For now, we specifically create only localhosts to handle radar://9354225
//
// Note: We could have set this up while parsing the entries in /etc/hosts. But this is kept separate
// intentionally to avoid adding to the complexity of code handling /etc/hosts.
mDNSlocal void SetupLocalHostRecords(mDNS *const m)
	{
	char buffer[MAX_REVERSE_MAPPING_NAME];
	domainname name;
	int i;
	struct in6_addr addr;
	mDNSu8 *ptr = addr.__u6_addr.__u6_addr8;

	if (inet_pton(AF_INET, "127.0.0.1", &addr) == 1)
		{
		mDNS_snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d.in-addr.arpa.",
			ptr[3], ptr[2], ptr[1], ptr[0]);
		MakeDomainNameFromDNSNameString(&name, buffer);
		CreatePTRRecord(m, &name);
		}
	else LogMsg("SetupLocalHostRecords: ERROR!! inet_pton AF_INET failed");

	if (inet_pton(AF_INET6, "::1", &addr) == 1)
		{
		for (i = 0; i < 16; i++)
			{
			static const char hexValues[] = "0123456789ABCDEF";
			buffer[i * 4    ] = hexValues[ptr[15 - i] & 0x0F];
			buffer[i * 4 + 1] = '.';
			buffer[i * 4 + 2] = hexValues[ptr[15 - i] >> 4];
			buffer[i * 4 + 3] = '.';
			}
		mDNS_snprintf(&buffer[64], sizeof(buffer)-64, "ip6.arpa.");
		MakeDomainNameFromDNSNameString(&name, buffer);
		CreatePTRRecord(m, &name);
		}
	else LogMsg("SetupLocalHostRecords: ERROR!! inet_pton AF_INET6 failed");
	}

// Construction of Default Browse domain list (i.e. when clients pass NULL) is as follows:
// 1) query for b._dns-sd._udp.local on LocalOnly interface
//    (.local manually generated via explicit callback)
// 2) for each search domain (from prefs pane), query for b._dns-sd._udp.<searchdomain>.
// 3) for each result from (2), register LocalOnly PTR record b._dns-sd._udp.local. -> <result>
// 4) result above should generate a callback from question in (1).  result added to global list
// 5) global list delivered to client via GetSearchDomainList()
// 6) client calls to enumerate domains now go over LocalOnly interface
//    (!!!KRS may add outgoing interface in addition)

mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
	{
	mStatus err;
	m->p->CFRunLoop = CFRunLoopGetCurrent();

	char HINFO_SWstring[256] = "";
	mDNSMacOSXSystemBuildNumber(HINFO_SWstring);

	// In 10.4, mDNSResponder is launched very early in the boot process, while other subsystems are still in the process of starting up.
	// If we can't read the user's preferences, then we sleep a bit and try again, for up to five seconds before we give up.
	int i;
	for (i=0; i<100; i++)
		{
		domainlabel testlabel;
		testlabel.c[0] = 0;
		GetUserSpecifiedLocalHostName(&testlabel);
		if (testlabel.c[0]) break;
		usleep(50000);
		}

	m->hostlabel.c[0]        = 0;

	int    get_model[2] = { CTL_HW, HW_MODEL };
	size_t len_model = sizeof(HINFO_HWstring_buffer);
	
	// Normal Apple model names are of the form "iPhone2,1", and
	// internal code names are strings containing no commas, e.g. "N88AP".
	// We used to ignore internal code names, but Apple now uses these internal code names
	// even in released shipping products, so we no longer ignore strings containing no commas.
//	if (sysctl(get_model, 2, HINFO_HWstring_buffer, &len_model, NULL, 0) == 0 && strchr(HINFO_HWstring_buffer, ','))
	if (sysctl(get_model, 2, HINFO_HWstring_buffer, &len_model, NULL, 0) == 0)
		HINFO_HWstring = HINFO_HWstring_buffer;

	// For names of the form "iPhone2,1" we use "iPhone" as the prefix for automatic name generation.
	// For names of the form "N88AP" containg no comma, we use the entire string.
	HINFO_HWstring_prefixlen = strchr(HINFO_HWstring_buffer, ',') ? strcspn(HINFO_HWstring, "0123456789") : strlen(HINFO_HWstring);

	if (OSXVers && OSXVers <= OSXVers_10_6_SnowLeopard) m->KnownBugs |= mDNS_KnownBug_LimitedIPv6;
	if (OSXVers && OSXVers >= OSXVers_10_6_SnowLeopard) m->KnownBugs |= mDNS_KnownBug_LossySyslog;
	if (mDNSPlatformInit_CanReceiveUnicast()) m->CanReceiveUnicastOn5353 = mDNStrue;

	mDNSu32 hlen = mDNSPlatformStrLen(HINFO_HWstring);
	mDNSu32 slen = mDNSPlatformStrLen(HINFO_SWstring);
	if (hlen + slen < 254)
		{
		m->HIHardware.c[0] = hlen;
		m->HISoftware.c[0] = slen;
		mDNSPlatformMemCopy(&m->HIHardware.c[1], HINFO_HWstring, hlen);
		mDNSPlatformMemCopy(&m->HISoftware.c[1], HINFO_SWstring, slen);
		}

 	m->p->permanentsockets.port  = MulticastDNSPort;
 	m->p->permanentsockets.m     = m;
	m->p->permanentsockets.sktv4 = -1;
	m->p->permanentsockets.kqsv4.KQcallback = myKQSocketCallBack;
	m->p->permanentsockets.kqsv4.KQcontext  = &m->p->permanentsockets;
	m->p->permanentsockets.kqsv4.KQtask     = "UDP packet reception";
#ifndef NO_IPV6
	m->p->permanentsockets.sktv6 = -1;
	m->p->permanentsockets.kqsv6.KQcallback = myKQSocketCallBack;
	m->p->permanentsockets.kqsv6.KQcontext  = &m->p->permanentsockets;
	m->p->permanentsockets.kqsv6.KQtask     = "UDP packet reception";
#endif

	err = SetupSocket(&m->p->permanentsockets, MulticastDNSPort, AF_INET, mDNSNULL);
#ifndef NO_IPV6
	err = SetupSocket(&m->p->permanentsockets, MulticastDNSPort, AF_INET6, mDNSNULL);
#endif

	struct sockaddr_in s4;
	socklen_t n4 = sizeof(s4);
	if (getsockname(m->p->permanentsockets.sktv4, (struct sockaddr *)&s4, &n4) < 0) LogMsg("getsockname v4 error %d (%s)", errno, strerror(errno));
	else m->UnicastPort4.NotAnInteger = s4.sin_port;
#ifndef NO_IPV6
	if (m->p->permanentsockets.sktv6 >= 0)
		{
		struct sockaddr_in6 s6;
		socklen_t n6 = sizeof(s6);
		if (getsockname(m->p->permanentsockets.sktv6, (struct sockaddr *)&s6, &n6) < 0) LogMsg("getsockname v6 error %d (%s)", errno, strerror(errno));
		else m->UnicastPort6.NotAnInteger = s6.sin6_port;
		}
#endif

	m->p->InterfaceList      = mDNSNULL;
	m->p->userhostlabel.c[0] = 0;
	m->p->usernicelabel.c[0] = 0;
	m->p->prevoldnicelabel.c[0] = 0;
	m->p->prevnewnicelabel.c[0] = 0;
	m->p->prevoldhostlabel.c[0] = 0;
	m->p->prevnewhostlabel.c[0] = 0;
	m->p->NotifyUser         = 0;
	m->p->KeyChainTimer      = 0;
	m->p->WakeAtUTC          = 0;
	m->p->RequestReSleep     = 0;
	
#if APPLE_OSX_mDNSResponder
	uuid_generate(m->asl_uuid);
#endif

	m->AutoTunnelHostAddr.b[0] = 0;		// Zero out AutoTunnelHostAddr so UpdateInterfaceList() know it has to set it up
	m->AutoTunnelRelayAddrIn = zerov6Addr;
	m->AutoTunnelRelayAddrOut = zerov6Addr;

	NetworkChangedKey_IPv4         = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4);
	NetworkChangedKey_IPv6         = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6);
	NetworkChangedKey_Hostnames    = SCDynamicStoreKeyCreateHostNames(NULL);
	NetworkChangedKey_Computername = SCDynamicStoreKeyCreateComputerName(NULL);
	NetworkChangedKey_DNS          = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL, kSCDynamicStoreDomainState, kSCEntNetDNS);
	if (!NetworkChangedKey_IPv4 || !NetworkChangedKey_IPv6 || !NetworkChangedKey_Hostnames || !NetworkChangedKey_Computername || !NetworkChangedKey_DNS)
		{ LogMsg("SCDynamicStore string setup failed"); return(mStatus_NoMemoryErr); }

	err = WatchForNetworkChanges(m);
	if (err) { LogMsg("mDNSPlatformInit_setup: WatchForNetworkChanges failed %d", err); return(err); }

#if 0 // <rdar://problem/6751656>
	err = WatchForPMChanges(m);
	if (err) { LogMsg("mDNSPlatformInit_setup: WatchForPMChanges failed %d", err); return(err); }
#endif

	err = WatchForSysEvents(m);
	if (err) { LogMsg("mDNSPlatformInit_setup: WatchForSysEvents failed %d", err); return(err); }

	mDNSs32 utc = mDNSPlatformUTC();
	m->SystemWakeOnLANEnabled = SystemWakeForNetworkAccess();
	UpdateInterfaceList(m, utc);
	SetupActiveInterfaces(m, utc);

	// Explicitly ensure that our Keychain operations utilize the system domain.
#ifndef NO_SECURITYFRAMEWORK
	SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem);
#endif

	mDNS_Lock(m);
	SetDomainSecrets(m);
	SetLocalDomains();
	mDNS_Unlock(m);

#ifndef NO_SECURITYFRAMEWORK
	err = SecKeychainAddCallback(KeychainChanged, kSecAddEventMask|kSecDeleteEventMask|kSecUpdateEventMask, m);
	if (err) { LogMsg("mDNSPlatformInit_setup: SecKeychainAddCallback failed %d", err); return(err); }
#endif

#if !defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements) || TARGET_OS_EMBEDDED
	LogMsg("Note: Compiled without SnowLeopard Fine-Grained Power Management support");
#else
	IOPMConnection c;
	IOReturn iopmerr = IOPMConnectionCreate(CFSTR("mDNSResponder"), kIOPMSystemPowerStateCapabilityCPU, &c);
	if (iopmerr) LogMsg("IOPMConnectionCreate failed %d", iopmerr);
	else
		{
		iopmerr = IOPMConnectionSetNotification(c, m, SnowLeopardPowerChanged);
		if (iopmerr) LogMsg("IOPMConnectionSetNotification failed %d", iopmerr);
		else
			{
			iopmerr = IOPMConnectionScheduleWithRunLoop(c, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
			if (iopmerr) LogMsg("IOPMConnectionScheduleWithRunLoop failed %d", iopmerr);
			}
		}
	m->p->IOPMConnection = iopmerr ? mDNSNULL : c;
	if (iopmerr) // If IOPMConnectionCreate unavailable or failed, proceed with old-style power notification code below
#endif // kIOPMAcknowledgmentOptionSystemCapabilityRequirements
		{
		m->p->PowerConnection = IORegisterForSystemPower(m, &m->p->PowerPortRef, PowerChanged, &m->p->PowerNotifier);
		if (!m->p->PowerConnection) { LogMsg("mDNSPlatformInit_setup: IORegisterForSystemPower failed"); return(-1); }
		else
			{
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
			IONotificationPortSetDispatchQueue(m->p->PowerPortRef, dispatch_get_main_queue());
#else
			CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(m->p->PowerPortRef), kCFRunLoopDefaultMode);
#endif /* MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM */
			}
		}

#if APPLE_OSX_mDNSResponder
	// Note: We use SPMetricPortability > 35 to indicate a laptop of some kind
	// SPMetricPortability <= 35 means nominally a non-portable machine (i.e. Mac mini or better)
	// Apple TVs, AirPort base stations, and Time Capsules do not actually weigh 3kg, but we assign them
	// higher 'nominal' masses to indicate they should be treated as being relatively less portable than a laptop
	if      (!strncasecmp(HINFO_HWstring, "Xserve",       6)) { SPMetricPortability = 25 /* 30kg */; SPMetricMarginalPower = 84 /* 250W */; SPMetricTotalPower = 85 /* 300W */; }
	else if (!strncasecmp(HINFO_HWstring, "RackMac",      7)) { SPMetricPortability = 25 /* 30kg */; SPMetricMarginalPower = 84 /* 250W */; SPMetricTotalPower = 85 /* 300W */; }
	else if (!strncasecmp(HINFO_HWstring, "MacPro",       6)) { SPMetricPortability = 27 /* 20kg */; SPMetricMarginalPower = 84 /* 250W */; SPMetricTotalPower = 85 /* 300W */; }
	else if (!strncasecmp(HINFO_HWstring, "PowerMac",     8)) { SPMetricPortability = 27 /* 20kg */; SPMetricMarginalPower = 82 /* 160W */; SPMetricTotalPower = 83 /* 200W */; }
	else if (!strncasecmp(HINFO_HWstring, "iMac",         4)) { SPMetricPortability = 30 /* 10kg */; SPMetricMarginalPower = 77 /*  50W */; SPMetricTotalPower = 78 /*  60W */; }
	else if (!strncasecmp(HINFO_HWstring, "Macmini",      7)) { SPMetricPortability = 33 /*  5kg */; SPMetricMarginalPower = 73 /*  20W */; SPMetricTotalPower = 74 /*  25W */; }
	else if (!strncasecmp(HINFO_HWstring, "TimeCapsule", 11)) { SPMetricPortability = 34 /*  4kg */; SPMetricMarginalPower = 10 /*  ~0W */; SPMetricTotalPower = 70 /*  13W */; }
	else if (!strncasecmp(HINFO_HWstring, "AirPort",      7)) { SPMetricPortability = 35 /*  3kg */; SPMetricMarginalPower = 10 /*  ~0W */; SPMetricTotalPower = 70 /*  12W */; }
	else if (!strncasecmp(HINFO_HWstring, "AppleTV",      7)) { SPMetricPortability = 35 /*  3kg */; SPMetricMarginalPower = 10 /*  ~0W */; SPMetricTotalPower = 73 /*  20W */; }
	else if (!strncasecmp(HINFO_HWstring, "K66AP",        5)) { SPMetricPortability = 35 /*  3kg */; SPMetricMarginalPower = 60 /*   1W */; SPMetricTotalPower = 63 /*   2W */; }
	else if (!strncasecmp(HINFO_HWstring, "MacBook",      7)) { SPMetricPortability = 37 /*  2kg */; SPMetricMarginalPower = 71 /*  13W */; SPMetricTotalPower = 72 /*  15W */; }
	else if (!strncasecmp(HINFO_HWstring, "PowerBook",    9)) { SPMetricPortability = 37 /*  2kg */; SPMetricMarginalPower = 71 /*  13W */; SPMetricTotalPower = 72 /*  15W */; }
	LogSPS("HW_MODEL: %.*s (%s) Portability %d Marginal Power %d Total Power %d",
		HINFO_HWstring_prefixlen, HINFO_HWstring, HINFO_HWstring, SPMetricPortability, SPMetricMarginalPower, SPMetricTotalPower);

	err = WatchForInternetSharingChanges(m);
	if (err) { LogMsg("WatchForInternetSharingChanges failed %d", err); return(err); }

	m->p->ConndBTMMDict = mDNSNULL;
#endif // APPLE_OSX_mDNSResponder

#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
	// Currently this is not defined. SSL code will eventually fix this. If it becomes
	// critical, we will define this to workaround the bug in SSL.
#ifdef __SSL_NEEDS_SERIALIZATION__
	SSLqueue = dispatch_queue_create("com.apple.mDNSResponder.SSLQueue", NULL);
#else
	SSLqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
#endif
	if (SSLqueue == mDNSNULL) LogMsg("dispatch_queue_create: SSL queue NULL");
#endif
	mDNSMacOSXUpdateEtcHosts(m);
	SetupLocalHostRecords(m);
	return(mStatus_NoError);
	}

mDNSexport mStatus mDNSPlatformInit(mDNS *const m)
	{
#if MDNS_NO_DNSINFO
	LogMsg("Note: Compiled without Apple-specific Split-DNS support");
#endif

	// Adding interfaces will use this flag, so set it now.
	m->DivertMulticastAdvertisements = !m->AdvertiseLocalAddresses;
	
#if APPLE_OSX_mDNSResponder
	m->SPSBrowseCallback = UpdateSPSStatus;
#endif // APPLE_OSX_mDNSResponder

	mStatus result = mDNSPlatformInit_setup(m);

	// We don't do asynchronous initialization on OS X, so by the time we get here the setup will already
	// have succeeded or failed -- so if it succeeded, we should just call mDNSCoreInitComplete() immediately
	if (result == mStatus_NoError)
		{
		mDNSCoreInitComplete(m, mStatus_NoError);

#if ! NO_D2D
		// We only initialize if mDNSCore successfully initialized.
		CHECK_D2D_FUNCTION(D2DInitialize)
			{
			D2DStatus ds = D2DInitialize(m->p->CFRunLoop, xD2DServiceCallback, m) ;
			if (ds != kD2DSuccess)
				LogMsg("D2DInitialiize failed: %d", ds);
			else
				LogMsg("D2DInitialize succeeded");
			}
#endif // ! NO_D2D
			
		}
	return(result);
	}

mDNSexport void mDNSPlatformClose(mDNS *const m)
	{
	if (m->p->PowerConnection)
		{
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
		IONotificationPortSetDispatchQueue(m->p->PowerPortRef, NULL);		
#else
		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(m->p->PowerPortRef), kCFRunLoopDefaultMode);
#endif
		// According to <http://developer.apple.com/qa/qa2004/qa1340.html>, a single call
		// to IORegisterForSystemPower creates *three* objects that need to be disposed individually:
		IODeregisterForSystemPower(&m->p->PowerNotifier);
		IOServiceClose            ( m->p->PowerConnection);
		IONotificationPortDestroy ( m->p->PowerPortRef);
		m->p->PowerConnection = 0;
		}

	if (m->p->Store)
		{
#ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
		if (!SCDynamicStoreSetDispatchQueue(m->p->Store, NULL))
			LogMsg("mDNSPlatformClose: SCDynamicStoreSetDispatchQueue failed");
#else
		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m->p->StoreRLS, kCFRunLoopDefaultMode);
		CFRunLoopSourceInvalidate(m->p->StoreRLS);
		CFRelease(m->p->StoreRLS);
		m->p->StoreRLS = NULL;
#endif
		CFRelease(m->p->Store);
		m->p->Store    = NULL;
		}
	
	if (m->p->PMRLS)
		{
		CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m->p->PMRLS, kCFRunLoopDefaultMode);
		CFRunLoopSourceInvalidate(m->p->PMRLS);
		CFRelease(m->p->PMRLS);
		m->p->PMRLS = NULL;
		}

	if (m->p->SysEventNotifier >= 0) { close(m->p->SysEventNotifier); m->p->SysEventNotifier = -1; }

#if ! NO_D2D
		CHECK_D2D_FUNCTION(D2DTerminate)
		{
		D2DStatus ds = D2DTerminate();
		if (ds != kD2DSuccess)
			LogMsg("D2DTerminate failed: %d", ds);
		else
			LogMsg("D2DTerminate succeeded");
		}
#endif // ! NO_D2D

	mDNSs32 utc = mDNSPlatformUTC();
	MarkAllInterfacesInactive(m, utc);
	ClearInactiveInterfaces(m, utc);
	CloseSocketSet(&m->p->permanentsockets);

#if APPLE_OSX_mDNSResponder
	// clean up tunnels
	while (m->TunnelClients)
		{
		ClientTunnel *cur = m->TunnelClients;
		LogInfo("mDNSPlatformClose: removing client tunnel %p %##s from list", cur, cur->dstname.c);
		if (cur->q.ThisQInterval >= 0) mDNS_StopQuery(m, &cur->q);
		AutoTunnelSetKeys(cur, mDNSfalse);
		m->TunnelClients = cur->next;
		freeL("ClientTunnel", cur);
		}

	if (AnonymousRacoonConfig)
		{
		AnonymousRacoonConfig = mDNSNULL;
		LogInfo("mDNSPlatformClose: Deconfiguring autotunnel");
		(void)mDNSConfigureServer(kmDNSDown, mDNSNULL, mDNSNULL);
		}

	if (m->AutoTunnelHostAddrActive && m->AutoTunnelHostAddr.b[0])
		{
		m->AutoTunnelHostAddrActive = mDNSfalse;
		LogInfo("mDNSPlatformClose: Removing AutoTunnel address %.16a", &m->AutoTunnelHostAddr);
		(void)mDNSAutoTunnelInterfaceUpDown(kmDNSDown, m->AutoTunnelHostAddr.b);
		}
	if (m->p->ConndBTMMDict) CFRelease(m->p->ConndBTMMDict);
#endif // APPLE_OSX_mDNSResponder
	}

#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - General Platform Support Layer functions
#endif

mDNSexport mDNSu32 mDNSPlatformRandomNumber(void)
	{
	return(arc4random());
	}

mDNSexport mDNSs32 mDNSPlatformOneSecond = 1000;
mDNSexport mDNSu32 mDNSPlatformClockDivisor = 0;

mDNSexport mStatus mDNSPlatformTimeInit(void)
	{
	// Notes: Typical values for mach_timebase_info:
	// tbi.numer = 1000 million
	// tbi.denom =   33 million
	// These are set such that (mach_absolute_time() * numer/denom) gives us nanoseconds;
	//          numer  / denom = nanoseconds per hardware clock tick (e.g. 30);
	//          denom  / numer = hardware clock ticks per nanosecond (e.g. 0.033)
	// (denom*1000000) / numer = hardware clock ticks per millisecond (e.g. 33333)
	// So: mach_absolute_time() / ((denom*1000000)/numer) = milliseconds
	//
	// Arithmetic notes:
	// tbi.denom is at least 1, and not more than 2^32-1.
	// Therefore (tbi.denom * 1000000) is at least one million, but cannot overflow a uint64_t.
	// tbi.denom is at least 1, and not more than 2^32-1.
	// Therefore clockdivisor should end up being a number roughly in the range 10^3 - 10^9.
	// If clockdivisor is less than 10^3 then that means that the native clock frequency is less than 1MHz,
	// which is unlikely on any current or future Macintosh.
	// If clockdivisor is greater than 10^9 then that means the native clock frequency is greater than 1000GHz.
	// When we ship Macs with clock frequencies above 1000GHz, we may have to update this code.
	struct mach_timebase_info tbi;
	kern_return_t result = mach_timebase_info(&tbi);
	if (result == KERN_SUCCESS) mDNSPlatformClockDivisor = ((uint64_t)tbi.denom * 1000000) / tbi.numer;
	return(result);
	}

mDNSexport mDNSs32 mDNSPlatformRawTime(void)
	{
	if (mDNSPlatformClockDivisor == 0) { LogMsg("mDNSPlatformRawTime called before mDNSPlatformTimeInit"); return(0); }

	static uint64_t last_mach_absolute_time = 0;
	//static uint64_t last_mach_absolute_time = 0x8000000000000000LL;	// Use this value for testing the alert display
	uint64_t this_mach_absolute_time = mach_absolute_time();
	if ((int64_t)this_mach_absolute_time - (int64_t)last_mach_absolute_time < 0)
		{
		LogMsg("mDNSPlatformRawTime: last_mach_absolute_time %08X%08X", last_mach_absolute_time);
		LogMsg("mDNSPlatformRawTime: this_mach_absolute_time %08X%08X", this_mach_absolute_time);
		// Update last_mach_absolute_time *before* calling NotifyOfElusiveBug()
		last_mach_absolute_time = this_mach_absolute_time;
		// Note: This bug happens all the time on 10.3
		NotifyOfElusiveBug("mach_absolute_time went backwards!",
			"This error occurs from time to time, often on newly released hardware, "
			"and usually the exact cause is different in each instance.\r\r"
			"Please file a new Radar bug report with the title “mach_absolute_time went backwards” "
			"and assign it to Radar Component “Kernel” Version “X”.");
		}
	last_mach_absolute_time = this_mach_absolute_time;

	return((mDNSs32)(this_mach_absolute_time / mDNSPlatformClockDivisor));
	}

mDNSexport mDNSs32 mDNSPlatformUTC(void)
	{
	return time(NULL);
	}

// Locking is a no-op here, because we're single-threaded with a CFRunLoop, so we can never interrupt ourselves
mDNSexport void     mDNSPlatformLock   (const mDNS *const m) { (void)m; }
mDNSexport void     mDNSPlatformUnlock (const mDNS *const m) { (void)m; }
mDNSexport void     mDNSPlatformStrCopy(      void *dst, const void *src)              { strcpy((char *)dst, (char *)src); }
mDNSexport mDNSu32  mDNSPlatformStrLen (                 const void *src)              { return(strlen((char*)src)); }
mDNSexport void     mDNSPlatformMemCopy(      void *dst, const void *src, mDNSu32 len) { memcpy(dst, src, len); }
mDNSexport mDNSBool mDNSPlatformMemSame(const void *dst, const void *src, mDNSu32 len) { return(memcmp(dst, src, len) == 0); }
mDNSexport void     mDNSPlatformMemZero(      void *dst,                  mDNSu32 len) { memset(dst, 0, len); }
#if !(APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING)
mDNSexport void *   mDNSPlatformMemAllocate(mDNSu32 len) { return(mallocL("mDNSPlatformMemAllocate", len)); }
#endif
mDNSexport void     mDNSPlatformMemFree    (void *mem)   { freeL("mDNSPlatformMemFree", mem); }

mDNSexport void mDNSPlatformSetAllowSleep(mDNS *const m, mDNSBool allowSleep, const char *reason)
	{
	if (allowSleep && m->p->IOPMAssertion)
		{
		LogInfo("%s Destroying NoIdleSleep power assertion", __FUNCTION__);
		IOPMAssertionRelease(m->p->IOPMAssertion);
		m->p->IOPMAssertion = 0;
		}
	else if (!allowSleep && m->p->IOPMAssertion == 0)
		{
#ifdef kIOPMAssertionTypeNoIdleSleep
		CFStringRef assertionName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s.%d %s"), getprogname(), getpid(), reason ? reason : "");
		IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, assertionName ? assertionName : CFSTR("mDNSResponder"), &m->p->IOPMAssertion);
		if (assertionName) CFRelease(assertionName);
		LogInfo("%s Creating NoIdleSleep power assertion", __FUNCTION__);
#endif
		}
	}

mDNSexport void mDNSPlatformSendWakeupPacket(mDNS *const m, mDNSInterfaceID InterfaceID, char *EthAddr, char *IPAddr, int iteration)
	{
	mDNSu32 ifindex;

	// Sanity check
	ifindex = mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNStrue);
	if (ifindex <= 0)
		{
		LogMsg("mDNSPlatformSendWakeupPacket: ERROR!! Invalid InterfaceID %u", ifindex);
		return;
		}
	mDNSSendWakeupPacket(ifindex, EthAddr, IPAddr, iteration);
	}

// Called for rr->InterfaceID == mDNSInterface_Any.  
// If current interface is P2P, verify that record is marked to IncludeP2P.
mDNSexport mDNSBool mDNSPlatformValidRecordForInterface(AuthRecord *rr, const NetworkInterfaceInfo *intf)
	{
	mDNSBool p2pInterface = (strncmp(intf->ifname, "p2p", 3) == 0);

	if (!p2pInterface || (rr->ARType == AuthRecordAnyIncludeP2P))
		return mDNStrue;
	else
		return mDNSfalse;
	}