[rtems-libbsd commit] mDNSResponder: Update to v878.200.35

Sebastian Huber sebh at rtems.org
Tue Jun 23 16:16:57 UTC 2020


Module:    rtems-libbsd
Branch:    5-freebsd-12
Commit:    c6d65faa42f67d11bc442ff09a8f19e1ca620614
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=c6d65faa42f67d11bc442ff09a8f19e1ca620614

Author:    Sebastian Huber <sebastian.huber at embedded-brains.de>
Date:      Thu Jun 18 13:07:18 2020 +0200

mDNSResponder: Update to v878.200.35

The sources can be obtained via:

https://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-878.200.35.tar.gz

Update #4010.

---

 mDNSResponder/Clients/dns-sd.c                     |  107 +-
 mDNSResponder/Clients/dnssdutil.c                  | 5813 ++++++++++++++++++--
 mDNSResponder/Makefile                             |    6 +-
 mDNSResponder/mDNSCore/DNSCommon.c                 |  359 +-
 mDNSResponder/mDNSCore/DNSCommon.h                 |    4 +-
 mDNSResponder/mDNSCore/mDNS.c                      |  265 +-
 mDNSResponder/mDNSCore/mDNSDebug.h                 |    6 +-
 mDNSResponder/mDNSCore/mDNSEmbeddedAPI.h           |   60 +-
 mDNSResponder/mDNSCore/uDNS.c                      |    2 +-
 mDNSResponder/mDNSMacOSX/D2D.c                     |   49 +-
 mDNSResponder/mDNSMacOSX/DNS64.c                   |    8 +-
 mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.c     |   10 +-
 mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.h     |   15 +-
 .../AppleInternal/com.apple.mDNSResponder.plist    |   14 +
 .../LoggingProfiles/com.apple.mDNSResponder.plist  |   16 +
 mDNSResponder/mDNSMacOSX/Metrics.h                 |    2 +-
 mDNSResponder/mDNSMacOSX/Metrics.m                 |   29 +-
 .../Private/com.apple.mDNSResponder.plist          |   19 -
 .../mDNSMacOSX/Scripts/bonjour-mcast-diagnose      |  380 ++
 .../mDNSMacOSX/Scripts/bonjour-start-mdns-tcpdump  |   56 +
 .../com.apple.mDNSResponder.mdns-tcpdump.plist     |   21 +
 mDNSResponder/mDNSMacOSX/daemon.c                  |   20 +-
 mDNSResponder/mDNSMacOSX/helper-stubs.c            |   18 +-
 mDNSResponder/mDNSMacOSX/helper.c                  |   15 +-
 mDNSResponder/mDNSMacOSX/mDNSMacOSX.c              |  280 +-
 mDNSResponder/mDNSMacOSX/mDNSMacOSX.h              |    3 +-
 mDNSResponder/mDNSMacOSX/mDNSResponder.sb          |    4 +-
 .../mDNSResponder.xcodeproj/project.pbxproj        |  156 +-
 mDNSResponder/mDNSMacOSX/uDNSPathEvalulation.c     |    8 +-
 mDNSResponder/mDNSPosix/ProxyResponder.c           |    2 +-
 mDNSResponder/mDNSPosix/Responder.c                |    2 +-
 mDNSResponder/mDNSPosix/mDNSUNP.c                  |   60 +-
 mDNSResponder/mDNSPosix/mDNSUNP.h                  |    8 +-
 mDNSResponder/mDNSShared/dns_sd.h                  |   62 +-
 mDNSResponder/mDNSShared/dns_sd_private.h          |    8 +-
 mDNSResponder/mDNSShared/dnssd_clientlib.c         |   16 +-
 mDNSResponder/mDNSShared/dnssd_clientstub.c        |    1 -
 mDNSResponder/mDNSShared/mDNSDebug.c               |    3 +-
 mDNSResponder/mDNSShared/uds_daemon.c              |  114 +-
 mDNSResponder/unittests/mDNSCoreReceiveTest.c      |   18 +-
 mDNSResponder/unittests/mdns_ut.c                  |    9 -
 mDNSResponder/unittests/unittest_common.h          |    2 -
 42 files changed, 6914 insertions(+), 1136 deletions(-)

diff --git a/mDNSResponder/Clients/dns-sd.c b/mDNSResponder/Clients/dns-sd.c
index 78c8f29..5260b3f 100644
--- a/mDNSResponder/Clients/dns-sd.c
+++ b/mDNSResponder/Clients/dns-sd.c
@@ -174,10 +174,6 @@ static const char kFilePathSep = '/';
 #include "../mDNSShared/dnssd_clientstub.c"
 #endif
 
-#if _DNS_SD_LIBDISPATCH
-#include <dispatch/private.h>
-#endif
-
 //*************************************************************************************************************
 // Globals
 
@@ -416,46 +412,36 @@ static unsigned int keytag(unsigned char *key, unsigned int keysize)
     return ac & 0xFFFF;
 }
 
-static void base64Encode(char *buffer, int buflen, void *rdata, unsigned int rdlen)
+// Base 64 encoding according to <https://tools.ietf.org/html/rfc4648#section-4>.
+#define kBase64EncodingTable "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+
+static void base64Encode(char *buffer, size_t buflen, void *rdata, size_t rdlen)
 {
-#if _DNS_SD_LIBDISPATCH
-    const void *result = NULL;
-    size_t size;
-    dispatch_data_t src_data = NULL, dest_data = NULL, null_str = NULL, data = NULL, map = NULL;
-
-    src_data = dispatch_data_create(rdata, rdlen, dispatch_get_global_queue(0, 0), ^{});
-    if (!src_data)
-        goto done;
-
-    dest_data = dispatch_data_create_with_transform(src_data, DISPATCH_DATA_FORMAT_TYPE_NONE, DISPATCH_DATA_FORMAT_TYPE_BASE64);
-    if (!dest_data)
-        goto done;
-
-    null_str = dispatch_data_create("", 1, dispatch_get_global_queue(0, 0), ^{});
-    if (!null_str)
-        goto done;
- 
-    data = dispatch_data_create_concat(dest_data, null_str);
-    if (!data)
-        goto done;    
-
-    map = dispatch_data_create_map(data, &result, &size);    
-    if (!map)
-        goto done;
-
-    snprintf(buffer, buflen, " %s", (char *)result);
-    
-done:
-    if (src_data) dispatch_release(src_data);
-    if (dest_data) dispatch_release(dest_data);
-    if (data)     dispatch_release(data);
-    if (null_str) dispatch_release(null_str);
-    if (map)      dispatch_release(map);
-    return;
-#else  //_DNS_SD_LIBDISPATCH
-    snprintf(buffer, buflen, " %s", ".");
-    return;
-#endif //_DNS_SD_LIBDISPATCH 
+    const uint8_t *src = (const uint8_t *)rdata;
+    const uint8_t *const end = &src[rdlen];
+    char *dst = buffer;
+    const char *lim;
+
+    if (buflen == 0) return;
+    lim = &buffer[buflen - 1];
+    while ((src < end) && (dst < lim))
+    {
+        uint32_t i;
+        const size_t rem = (size_t)(end - src);
+
+        // Form a 24-bit input group. If less than 24 bits remain, pad with zero bits.
+        if (     rem >= 3) i = (src[0] << 16) | (src[1] << 8) | src[2]; // 24 bits are equal to 4 6-bit groups.
+        else if (rem == 2) i = (src[0] << 16) | (src[1] << 8);          // 16 bits are treated as 3 6-bit groups + 1 pad
+        else               i =  src[0] << 16;                           //  8 bits are treated as 2 6-bit groups + 2 pads
+
+        // Encode each 6-bit group.
+                       *dst++ =              kBase64EncodingTable[(i >> 18) & 0x3F];
+        if (dst < lim) *dst++ =              kBase64EncodingTable[(i >> 12) & 0x3F];
+        if (dst < lim) *dst++ = (rem >= 2) ? kBase64EncodingTable[(i >>  6) & 0x3F] : '=';
+        if (dst < lim) *dst++ = (rem >= 3) ? kBase64EncodingTable[ i        & 0x3F] : '=';
+        src += (rem > 3) ? 3 : rem;
+    }
+    *dst = '\0';
 }
 
 static DNSServiceProtocol GetProtocol(const char *s)
@@ -924,9 +910,9 @@ static int snprintd(char *p, int max, const unsigned char **rd)
     return(p-buf);
 }
 
-static void ParseDNSSECRecords(uint16_t rrtype, char *rdb, char *p, unsigned const char *rd, uint16_t rdlen)
+static void ParseDNSSECRecords(uint16_t rrtype, char *rdb, size_t rdb_size, unsigned const char *rd, uint16_t rdlen)
 {
-    int rdb_size = 1000;
+    char *p = rdb;
     switch (rrtype) 
     {
         case kDNSServiceType_DS:
@@ -945,7 +931,7 @@ static void ParseDNSSECRecords(uint16_t rrtype, char *rdb, char *p, unsigned con
         case kDNSServiceType_DNSKEY:
         {
             rdataDNSKey *rrkey = (rdataDNSKey *)rd;
-            p += snprintf(p, rdb + rdb_size - p, "%d  %d  %d  %u", swap16(rrkey->flags), rrkey->proto,
+            p += snprintf(p, rdb + rdb_size - p, "%d  %d  %d  %u ", swap16(rrkey->flags), rrkey->proto,
                           rrkey->alg, (unsigned int)keytag((unsigned char *)rrkey, rdlen));
             base64Encode(p, rdb + rdb_size - p, (unsigned char *)(rd + DNSKEY_FIXED_SIZE), rdlen - DNSKEY_FIXED_SIZE);
             break;
@@ -1027,6 +1013,11 @@ static void ParseDNSSECRecords(uint16_t rrtype, char *rdb, char *p, unsigned con
             p += snprintd(p, rdb + rdb_size - p, &q);
             len = p - k + 1;
             
+            if ((&rdb[rdb_size] - p) >= 2)
+            {
+                *p++ = ' ';
+                *p   = '\0';
+            }
             base64Encode(p, rdb + rdb_size - p, (unsigned char *)(rd + len + RRSIG_FIXED_SIZE), rdlen - (len + RRSIG_FIXED_SIZE));
             break;
         }
@@ -1058,7 +1049,7 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
         if (operation == 'D') 
             printf("Timestamp     A/R if %-30s%-6s%-7s%-18s Rdata\n", "Name", "Type", "Class", "DNSSECStatus"); 
         else     
-            printf("Timestamp     A/R Flags if %-30s%-6s%-7s Rdata\n", "Name", "Type", "Class");
+            printf("Timestamp     A/R    Flags if %-30s%-6s%-7s Rdata\n", "Name", "Type", "Class");
     }
     printtimestamp();
 
@@ -1115,7 +1106,7 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
                 case kDNSServiceType_DNSKEY:
                 case kDNSServiceType_NSEC:
                 case kDNSServiceType_RRSIG:
-                    ParseDNSSECRecords(rrtype, rdb, p, rd, rdlen);
+                    ParseDNSSECRecords(rrtype, rdb, sizeof(rdb), rd, rdlen);
                     break;
 
                 default: 
@@ -1143,7 +1134,7 @@ static void DNSSD_API qr_reply(DNSServiceRef sdref, const DNSServiceFlags flags,
     if (operation == 'D')
         printf("%s%3d %-30s%-6s%-7s%-18s %s", op, ifIndex, fullname, rr_type, rr_class, dnssec_status, rdb);
     else
-        printf("%s%6X%3d %-30s%-7s%-6s %s", op, flags, ifIndex, fullname, rr_type, rr_class, rdb);
+        printf("%s%9X%3d %-30s%-7s%-6s %s", op, flags, ifIndex, fullname, rr_type, rr_class, rdb);
     if (unknowntype)
     { 
         while (rd < end) 
@@ -1208,7 +1199,7 @@ static void DNSSD_API addrinfo_reply(DNSServiceRef sdref, const DNSServiceFlags
         if (operation == 'g') 
             printf("Timestamp     A/R if %-25s %-44s %-18s\n", "Hostname", "Address", "DNSSECStatus");
         else  
-            printf("Timestamp     A/R Flags if %-38s %-44s %s\n", "Hostname", "Address", "TTL");
+            printf("Timestamp     A/R    Flags if %-38s %-44s %s\n", "Hostname", "Address", "TTL");
     }
     printtimestamp();
    
@@ -1248,7 +1239,7 @@ static void DNSSD_API addrinfo_reply(DNSServiceRef sdref, const DNSServiceFlags
     if (operation == 'g')
         printf("%s%3d %-25s %-44s %-18s", op, interfaceIndex, hostname, addr, dnssec_status);
     else
-        printf("%s%6X%3d %-38s %-44s %d", op, flags, interfaceIndex, hostname, addr, ttl);
+        printf("%s%9X%3d %-38s %-44s %d", op, flags, interfaceIndex, hostname, addr, ttl);
     if (errorCode)
     {
         if (errorCode == kDNSServiceErr_NoSuchRecord) 
@@ -1513,12 +1504,6 @@ static int API_string_limit_test()
     return 0;
 }
 
-// local prototypes for routines that don't have prototypes in dns_sd.h
-#if APPLE_OSX_mDNSResponder
-DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain);
-DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid);
-#endif
-
 static int API_NULL_input_test()
 {
     printf("Running basic API input range tests with various pointer parameters set to NULL:\n");
@@ -1916,6 +1901,14 @@ int main(int argc, char **argv)
 	        opinterface = kDNSServiceInterfaceIndexBLE;
 	    }
 	
+        if (argc > 1 && !strcasecmp(argv[1], "-allowexpired"))
+        {
+            argc--;
+            argv++;
+            flags |= kDNSServiceFlagsAllowExpiredAnswers;
+            printf("Setting kDNSServiceFlagsAllowExpiredAnswers\n");
+        }
+        
 	    if (argc > 1 && !strcasecmp(argv[1], "-includep2p"))
 	    {
 	        argc--;
diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c
index 1bde0da..206e2b6 100644
--- a/mDNSResponder/Clients/dnssdutil.c
+++ b/mDNSResponder/Clients/dnssdutil.c
@@ -6,10 +6,13 @@
 
 #include <CoreUtils/CommonServices.h>	// Include early.
 #include <CoreUtils/AsyncConnection.h>
+#include <CoreUtils/CFUtils.h>
 #include <CoreUtils/CommandLineUtils.h>
 #include <CoreUtils/DataBufferUtils.h>
 #include <CoreUtils/DebugServices.h>
 #include <CoreUtils/HTTPUtils.h>
+#include <CoreUtils/JSONUtils.h>
+#include <CoreUtils/LogUtils.h>
 #include <CoreUtils/MiscUtils.h>
 #include <CoreUtils/NetUtils.h>
 #include <CoreUtils/PrintFUtils.h>
@@ -20,10 +23,17 @@
 #include <dns_sd.h>
 #include <dns_sd_private.h>
 
+#include CF_RUNTIME_HEADER
+
 #if( TARGET_OS_DARWIN )
+	#include <CFNetwork/CFHost.h>
+	#include <CoreFoundation/CoreFoundation.h>
+	#include <SystemConfiguration/SCPrivate.h>
 	#include <dnsinfo.h>
 	#include <libproc.h>
 	#include <netdb.h>
+	#include <pcap.h>
+	#include <spawn.h>
 	#include <sys/proc_info.h>
 #endif
 
@@ -36,17 +46,19 @@
 #endif
 
 //===========================================================================================================================
-//	Global Constants
+//	Versioning
 //===========================================================================================================================
 
-// Versioning
-
 #define kDNSSDUtilNumVersion	NumVersionBuild( 2, 0, 0, kVersionStageBeta, 0 )
 
 #if( !MDNSRESPONDER_PROJECT && !defined( DNSSDUTIL_SOURCE_VERSION ) )
 	#define DNSSDUTIL_SOURCE_VERSION	"0.0.0"
 #endif
 
+//===========================================================================================================================
+//	DNS-SD
+//===========================================================================================================================
+
 // DNS-SD API flag descriptors
 
 #define kDNSServiceFlagsDescriptors		\
@@ -90,19 +102,76 @@
 	"\x05" "TCP\0"						\
 	"\x00"
 
-// (m)DNS
+#define kBadDNSServiceRef		( (DNSServiceRef)(intptr_t) -1 )
+
+//===========================================================================================================================
+//	DNS
+//===========================================================================================================================
+
+#define kDNSPort						53
+#define kDNSCompressionOffsetMax		0x3FFF
+#define kDNSMaxUDPMessageSize			512
+#define kDNSMaxTCPMessageSize			UINT16_MAX
+
+#define kDomainLabelLengthMax		63
+#define kDomainNameLengthMax		256
+
+typedef struct
+{
+	uint8_t		id[ 2 ];
+	uint8_t		flags[ 2 ];
+	uint8_t		questionCount[ 2 ];
+	uint8_t		answerCount[ 2 ];
+	uint8_t		authorityCount[ 2 ];
+	uint8_t		additionalCount[ 2 ];
+	
+}	DNSHeader;
+
+#define kDNSHeaderLength		12
+check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
+
+#define DNSHeaderGetID( HDR )					ReadBig16( ( HDR )->id )
+#define DNSHeaderGetFlags( HDR )				ReadBig16( ( HDR )->flags )
+#define DNSHeaderGetQuestionCount( HDR )		ReadBig16( ( HDR )->questionCount )
+#define DNSHeaderGetAnswerCount( HDR )			ReadBig16( ( HDR )->answerCount )
+#define DNSHeaderGetAuthorityCount( HDR )		ReadBig16( ( HDR )->authorityCount )
+#define DNSHeaderGetAdditionalCount( HDR )		ReadBig16( ( HDR )->additionalCount )
+
+#define DNSHeaderSetID( HDR, X )					WriteBig16( ( HDR )->id, (X) )
+#define DNSHeaderSetFlags( HDR, X )					WriteBig16( ( HDR )->flags, (X) )
+#define DNSHeaderSetQuestionCount( HDR, X )			WriteBig16( ( HDR )->questionCount, (X) )
+#define DNSHeaderSetAnswerCount( HDR, X )			WriteBig16( ( HDR )->answerCount, (X) )
+#define DNSHeaderSetAuthorityCount( HDR, X )		WriteBig16( ( HDR )->authorityCount, (X) )
+#define DNSHeaderSetAdditionalCount( HDR, X )		WriteBig16( ( HDR )->additionalCount, (X) )
+
+// Single-bit DNS header fields
+
+#define kDNSHeaderFlag_Response					( 1 << 15 )	// QR (bit 15), Query (0)/Response (1)
+#define kDNSHeaderFlag_AuthAnswer				( 1 << 10 )	// AA (bit 10), Authoritative Answer
+#define kDNSHeaderFlag_Truncation				( 1 <<  9 )	// TC (bit  9), TrunCation
+#define kDNSHeaderFlag_RecursionDesired			( 1 <<  8 )	// RD (bit  8), Recursion Desired
+#define kDNSHeaderFlag_RecursionAvailable		( 1 <<  7 )	// RA (bit  7), Recursion Available
+#define kDNSHeaderFlag_Z						( 1 <<  6 )	//  Z (bit  6), Reserved (must be zero)
+#define kDNSHeaderFlag_AuthenticData			( 1 <<  5 )	// AD (bit  5), Authentic Data (RFC 2535, Section 6)
+#define kDNSHeaderFlag_CheckingDisabled			( 1 <<  4 )	// CD (bit  4), Checking Disabled (RFC 2535, Section 6)
 
-#define kDNSHeaderFlag_Response					( 1 << 15 )
-#define kDNSHeaderFlag_AuthAnswer				( 1 << 10 )
-#define kDNSHeaderFlag_Truncation				( 1 <<  9 )
-#define kDNSHeaderFlag_RecursionDesired			( 1 <<  8 )
-#define kDNSHeaderFlag_RecursionAvailable		( 1 <<  7 )
+// OPCODE (bits 14-11), Operation Code
 
-#define kDNSOpCode_Query			0
-#define kDNSOpCode_InverseQuery		1
-#define kDNSOpCode_Status			2
-#define kDNSOpCode_Notify			4
-#define kDNSOpCode_Update			5
+#define DNSFlagsGetOpCode( FLAGS )		( ( (FLAGS) >> 11 ) & 0x0FU )
+#define DNSFlagsSetOpCode( FLAGS, OPCODE ) \
+	do{ (FLAGS) = ( (FLAGS) & ~0x7800U ) | ( ( (OPCODE) & 0x0FU ) << 11 ); } while( 0 )
+
+#define kDNSOpCode_Query			0	// QUERY (standard query)
+#define kDNSOpCode_InverseQuery		1	// IQUERY (inverse query)
+#define kDNSOpCode_Status			2	// STATUS
+#define kDNSOpCode_Notify			4	// NOTIFY
+#define kDNSOpCode_Update			5	// UPDATE
+
+// RCODE (bits 3-0), Response Code
+
+#define DNSFlagsGetRCode( FLAGS )		( (FLAGS) & 0x0FU )
+#define DNSFlagsSetRCode( FLAGS, RCODE ) \
+	do{ (FLAGS) = ( (FLAGS) & ~0x000FU ) | ( (RCODE) & 0x0FU ); } while( 0 )
 
 #define kDNSRCode_NoError				0
 #define kDNSRCode_FormatError			1
@@ -111,11 +180,66 @@
 #define kDNSRCode_NotImplemented		4
 #define kDNSRCode_Refused				5
 
-#define kQClassUnicastResponseBit	( 1U << 15 )
-#define kRRClassCacheFlushBit		( 1U << 15 )
+typedef struct
+{
+	uint8_t		type[ 2 ];
+	uint8_t		class[ 2 ];
+	
+}	DNSQuestionFixedFields;
+
+check_compile_time( sizeof( DNSQuestionFixedFields ) == 4 );
 
-#define kDomainLabelLengthMax		63
-#define kDomainNameLengthMax		256
+#define DNSQuestionFixedFieldsInit( FIELDS, QTYPE, QCLASS ) \
+	do { WriteBig16( (FIELDS)->type, QTYPE ); WriteBig16( (FIELDS)->class, QCLASS ); } while( 0 )
+
+#define DNSQuestionFixedFieldsGetType( FIELDS )			ReadBig16( (FIELDS)->type )
+#define DNSQuestionFixedFieldsGetClass( FIELDS )		ReadBig16( (FIELDS)->class )
+
+typedef struct
+{
+	uint8_t		type[ 2 ];
+	uint8_t		class[ 2 ];
+	uint8_t		ttl[ 4 ];
+	uint8_t		rdlength[ 2 ];
+	
+}	DNSRecordFixedFields;
+
+check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
+
+#define DNSRecordFixedFieldsInit( FIELDS, TYPE, CLASS, TTL, RDLENGTH )	\
+	do																	\
+	{																	\
+		WriteBig16( (FIELDS)->type,		TYPE );							\
+		WriteBig16( (FIELDS)->class,	CLASS );						\
+		WriteBig32( (FIELDS)->ttl,		TTL );							\
+		WriteBig16( (FIELDS)->rdlength,	RDLENGTH );						\
+																		\
+	}	while( 0 )
+
+//===========================================================================================================================
+//	mDNS
+//===========================================================================================================================
+
+#define kMDNSPort		5353
+
+#define kDefaultMDNSMessageID		0
+#define kDefaultMDNSQueryFlags		0
+
+#define kQClassUnicastResponseBit		( 1U << 15 )
+#define kRRClassCacheFlushBit			( 1U << 15 )
+
+//===========================================================================================================================
+//	Test DNS Server
+//===========================================================================================================================
+
+// IPv4 address block 203.0.113.0/24 (TEST-NET-3) is reserved for documentation. See <https://tools.ietf.org/html/rfc5737>.
+
+#define kTestDNSServerBaseAddrV4		UINT32_C( 0xCB007100 )	// 203.0.113.0
+
+// IPv6 address block 2001:db8::/32 is reserved for documentation. See <https://tools.ietf.org/html/rfc3849>.
+
+#define kTestDNSServerBaseAddrV6 \
+	( (const uint8_t *) "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )
 
 //===========================================================================================================================
 //	Gerneral Command Options
@@ -153,6 +277,11 @@
 #define StringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
 	StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
 
+#define CFStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED )	\
+	CLI_OPTION_CFSTRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,						\
+		(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,						\
+		(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+
 // DNS-SD API flag options
 
 static int		gDNSSDFlags						= 0;
@@ -634,7 +763,6 @@ static CLIOption		kPortMappingOpts[] =
 static const char *		gBrowseAll_Domain				= NULL;
 static char **			gBrowseAll_ServiceTypes			= NULL;
 static size_t			gBrowseAll_ServiceTypesCount	= 0;
-static int				gBrowseAll_IncludeAWDL			= false;
 static int				gBrowseAll_BrowseTimeSecs		= 5;
 static int				gBrowseAll_MaxConnectTimeSecs	= 0;
 
@@ -654,6 +782,36 @@ static CLIOption		kBrowseAllOpts[] =
 };
 
 //===========================================================================================================================
+//	GetNameInfo Command Options
+//===========================================================================================================================
+
+static void	GetNameInfoCmd( void );
+
+static char *		gGetNameInfo_IPAddress			= NULL;
+static int			gGetNameInfoFlag_DGram			= false;
+static int			gGetNameInfoFlag_NameReqd		= false;
+static int			gGetNameInfoFlag_NoFQDN			= false;
+static int			gGetNameInfoFlag_NumericHost	= false;
+static int			gGetNameInfoFlag_NumericScope	= false;
+static int			gGetNameInfoFlag_NumericServ	= false;
+
+static CLIOption		kGetNameInfoOpts[] =
+{
+	StringOption( 'a', "address",           &gGetNameInfo_IPAddress,        "IP address", "IPv4 or IPv6 address to use in sockaddr structure.", true ),
+	
+	CLI_OPTION_GROUP( "Flags" ),
+	BooleanOption( 0 , "flag-dgram",        &gGetNameInfoFlag_DGram,        "Use NI_DGRAM flag." ),
+	BooleanOption( 0 , "flag-namereqd",     &gGetNameInfoFlag_NameReqd,     "Use NI_NAMEREQD flag." ),
+	BooleanOption( 0 , "flag-nofqdn",       &gGetNameInfoFlag_NoFQDN,       "Use NI_NOFQDN flag." ),
+	BooleanOption( 0 , "flag-numerichost",  &gGetNameInfoFlag_NumericHost,  "Use NI_NUMERICHOST flag." ),
+	BooleanOption( 0 , "flag-numericscope", &gGetNameInfoFlag_NumericScope, "Use NI_NUMERICSCOPE flag." ),
+	BooleanOption( 0 , "flag-numericserv",  &gGetNameInfoFlag_NumericServ,  "Use NI_NUMERICSERV flag." ),
+	
+	CLI_SECTION( "Notes", "See getnameinfo(3) man page for more details.\n" ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
 //	GetAddrInfoStress Command Options
 //===========================================================================================================================
 
@@ -782,6 +940,298 @@ static CLIOption		kPIDToUUIDOpts[] =
 };
 
 //===========================================================================================================================
+//	DNSServer Command Options
+//===========================================================================================================================
+
+#define kDNSServerInfoText_Intro																						\
+	"The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n"	\
+	"presence of special labels in the query's QNAME. There are currently seven types of special labels that can be\n"	\
+	"used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n"	\
+	"IPv4 label, and the IPv6 label.\n"
+
+#define kDNSServerInfoText_NameExistence																				\
+	"A name is considered to exist if and only if it ends in d.test., and the other labels, if\n"						\
+	"any, consist of\n"																									\
+	"\n"																												\
+	"    1. at most one Alias or Alias-TTL label as the first label;\n"													\
+	"    2. at most one Count label;\n"																					\
+	"    3. zero or more Tag labels;\n"																				\
+	"    4. at most one TTL label; and\n"																				\
+	"    5. at most one IPv4 or IPv6 label.\n"
+
+#define kDNSServerInfoText_ResourceRecords																				\
+	"Currently, the server only provides CNAME, A, and AAAA records.\n"													\
+	"\n"																												\
+	"Names that exist and begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n"	\
+	"names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n"								\
+	"\n"																												\
+	"Names that exist and have an IPv4 label have at least one A record, but no AAAA records. Names that exist and\n"	\
+	"have an IPv6 label, have at least one AAAA record, but no A records. All other names that exist have at least\n"	\
+	"one A record and at least one AAAA record. See \"Count Labels\" for how the number of address records for a\n"		\
+	"given name is determined.\n"																						\
+	"\n"																												\
+	"A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n"	\
+	"2001:db8::/32 block. Both of these address blocks are reserved for documentation.\n"								\
+	"See <https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n"							\
+	"\n"																												\
+	"Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n"	\
+	"--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for records with\n"	\
+	"specific TTL values.\n"
+
+#define kDNSServerInfoText_AliasLabel																					\
+	"Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2 .. 2^31 - 1].\n"				\
+	"\n"																												\
+	"If QNAME exist and its first label is Alias label \"alias-N\", then the response will contain exactly N CNAME\n"	\
+	"records:\n"																										\
+	"\n"																												\
+	"    1. For each i in [3 .. N], the response will contain a CNAME record whose name is identical to QNAME,\n"		\
+	"       except that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME\n"		\
+	"       record whose name has \"alias-(i - 1)\" as its first label.\n"												\
+	"\n"																												\
+	"    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"	\
+	"       is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n"	\
+	"       \"alias\" instead.\n"																						\
+	"\n"																												\
+	"    3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"	\
+	"       is \"alias\" instead, and whose RDATA is the name identical to QNAME stripped of its first label.\n"		\
+	"\n"																												\
+	"If QNAME exist and its first label is Alias label \"alias\", then the response will contain a single CNAME\n"		\
+	"record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to QNAME\n"		\
+	"stripped of its first label.\n"																					\
+	"\n"																												\
+	"Example. A response to a query with a QNAME of alias-3.count-5.d.test will contain the following CNAME\n"			\
+	"records:\n"																										\
+	"\n"																												\
+	"    alias-4.count-5.d.test.                        60    IN CNAME alias-3.count-5.d.test.\n"						\
+	"    alias-3.count-5.d.test.                        60    IN CNAME alias-2.count-5.d.test.\n"						\
+	"    alias-2.count-5.d.test.                        60    IN CNAME alias.count-5.d.test.\n"							\
+	"    alias.count-5.d.test.                          60    IN CNAME count-5.d.test.\n"
+
+#define kDNSServerInfoText_AliasTTLLabel																				\
+	"Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n"				\
+	"[0 .. 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n"	\
+	"\n"																												\
+	"If QNAME exists and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response will contain\n"	\
+	"exactly N CNAME records:\n"																						\
+	"\n"																												\
+	"    1. For each i in [1 .. N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n"	\
+	"       except that the first label is \"alias-ttl-T_i...-T_N\" instead, whose TTL value is T_i, and whose RDATA\n"	\
+	"       is the name of the other CNAME record whose name has \"alias-ttl-T_(i+1)...-T_N\" as its first label.\n"	\
+	"\n"																												\
+	"    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"	\
+	"       is \"alias-ttl-T_N\", whose TTL is T_N, and whose RDATA is identical to QNAME stripped of its first\n"		\
+	"       label.\n"																									\
+	"\n"																												\
+	"Example. A response to a query with a QNAME of alias-ttl-20-40-80.count-5.d.test will contain the following\n"		\
+	"CNAME records:\n"																									\
+	"\n"																												\
+	"    alias-ttl-20-40-80.count-5.d.test.             20    IN CNAME alias-ttl-40-80.count-5.d.test.\n"				\
+	"    alias-ttl-40-80.count-5.d.test.                40    IN CNAME alias-ttl-80.count-5.d.test.\n"					\
+	"    alias-ttl-80.count-5.d.test.                   80    IN CNAME count-5.d.test.\n"
+
+#define kDNSServerInfoText_CountLabel																					\
+	"Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1 .. 255] and N_2\n"	\
+	"is an integer in [N_1 .. 255].\n"																					\
+	"\n"																												\
+	"If QNAME exists, contains Count label \"count-N\", and has the type of address records specified by QTYPE, then\n"	\
+	"the response will contain exactly N address records:\n"															\
+	"\n"																												\
+	"    1. For i in [1 .. N], the response will contain an address record of type QTYPE whose name is equal to\n"		\
+	"       QNAME and whose RDATA is an address equal to a constant base address + i.\n"								\
+	"\n"																												\
+	"    2. The address records will be ordered by the address contained in RDATA in ascending order.\n"				\
+	"\n"																												\
+	"Example. A response to an A record query with a QNAME of alias.count-3.d.test will contain the following A\n"		\
+	"records:\n"																										\
+	"\n"																												\
+	"    count-3.d.test.                                60    IN A     203.0.113.1\n"									\
+	"    count-3.d.test.                                60    IN A     203.0.113.2\n"									\
+	"    count-3.d.test.                                60    IN A     203.0.113.3\n"									\
+	"\n"																												\
+	"If QNAME exists, contains Count label \"count-N_1-N_2\", and has the type of address records specified by\n"		\
+	"QTYPE, then the response will contain exactly N_1 address records:\n"												\
+	"\n"																												\
+	"    1. Each of the address records will be of type QTYPE, have name equal to QNAME, and have as its RDATA a\n"		\
+	"       unique address equal to a constant base address + i, where i is a randomly chosen integer in [1 .. N_2].\n"	\
+	"\n"																												\
+	"    2. The order of the address records will be random.\n"															\
+	"\n"																												\
+	"Example. A response to a AAAA record query with a QNAME of count-3-100.ttl-20.d.test could contain the\n"			\
+	"following AAAA records:\n"																							\
+	"\n"																												\
+	"    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8::c\n"									\
+	"    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8::3a\n"									\
+	"    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8::4f\n"									\
+	"\n"																												\
+	"If QNAME exists, but doesn't have the type of address records specified by QTYPE, then the response will\n"		\
+	"contain no address records, regardless of whether it contains a Count label.\n"									\
+	"\n"																												\
+	"QNAMEs that exist, but don't have a Count label are treated as though they contain a count label equal to\n"		\
+	"\"count-1\".\n"
+
+#define kDNSServerInfoText_TagLabel																						\
+	"Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n"		\
+	"\n"																												\
+	"This type of label exists to allow testers to \"uniquify\" domain names. Tag labels can also serve as padding\n"	\
+	"to increase the sizes of domain names.\n"
+
+#define kDNSServerInfoText_TTLLabel																						\
+	"TTL labels are of the form \"ttl-T\", where T is an integer in [0 .. 2^31 - 1].\n"									\
+	"\n"																												\
+	"If the name specified by QNAME exists, and contains TTL label \"ttl-T\", then all non-CNAME records contained\n"	\
+	"in the response will have a TTL value equal to T.\n"
+
+#define kDNSServerInfoText_IPv4Label \
+	"The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n"
+
+#define kDNSServerInfoText_IPv6Label \
+	"The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"
+
+#define kDNSServerDefaultTTL		60
+
+static int				gDNSServer_LoopbackOnly		= false;
+static int				gDNSServer_Foreground		= false;
+static int				gDNSServer_ResponseDelayMs	= 0;
+static int				gDNSServer_DefaultTTL		= kDNSServerDefaultTTL;
+#if( TARGET_OS_DARWIN )
+static const char *		gDNSServer_FollowPID		= NULL;
+#endif
+
+static CLIOption		kDNSServerOpts[] =
+{
+	BooleanOption( 'l', "loopback",      &gDNSServer_LoopbackOnly,    "Bind to to the loopback interface." ),
+	BooleanOption( 'f', "foreground",    &gDNSServer_Foreground,      "Directlog output to stdout instead of system logging." ),
+	IntegerOption( 'd', "responseDelay", &gDNSServer_ResponseDelayMs, "ms", "The amount of additional delay in milliseconds to apply to responses. (default: 0)", false ),
+	IntegerOption(  0 , "defaultTTL",    &gDNSServer_DefaultTTL,      "seconds", "Resource record TTL value to use when unspecified. (default: 60)", false ),
+#if( TARGET_OS_DARWIN )
+	StringOption(   0 , "followPID",     &gDNSServer_FollowPID,       "pid", "Exit when the process (usually the parent proccess) specified by PID exits.", false ),
+#endif
+	
+	CLI_SECTION( "Intro",				kDNSServerInfoText_Intro ),
+	CLI_SECTION( "Name Existence",		kDNSServerInfoText_NameExistence ),
+	CLI_SECTION( "Resource Records",	kDNSServerInfoText_ResourceRecords ),
+	CLI_SECTION( "Alias Labels",		kDNSServerInfoText_AliasLabel ),
+	CLI_SECTION( "Alias-TTL Labels",	kDNSServerInfoText_AliasTTLLabel ),
+	CLI_SECTION( "Count Labels",		kDNSServerInfoText_CountLabel ),
+	CLI_SECTION( "Tag Labels",			kDNSServerInfoText_TagLabel ),
+	CLI_SECTION( "TTL Labels",			kDNSServerInfoText_TTLLabel ),
+	CLI_SECTION( "IPv4 Label",			kDNSServerInfoText_IPv4Label ),
+	CLI_SECTION( "IPv6 Label",			kDNSServerInfoText_IPv6Label ),
+	CLI_OPTION_END()
+};
+
+static void	DNSServerCmd( void );
+
+//===========================================================================================================================
+//	Test Command Options
+//===========================================================================================================================
+
+static const char *		gGAIPerf_TestSuite				= NULL;
+static int				gGAIPerf_CallDelayMs			= 10;
+static int				gGAIPerf_ServerDelayMs			= 10;
+static int				gGAIPerf_DefaultIterCount		= 100;
+static const char *		gGAIPerf_OutputFilePath			= NULL;
+static const char *		gGAIPerf_OutputFormat			= "json";
+static int				gGAIPerf_OutputAppendNewLine	= false;
+
+static void	GAIPerfCmd( void );
+
+#define kGAIPerfSectionTitle_TestSuiteBasic		"Test Suite \"Basic\""
+#define kGAIPerfSectionText_TestSuiteBasic																					\
+	"This test suite consists of the following three test cases:\n"															\
+	"\n"																													\
+	"Test Case #1: Resolve a domain name with\n"																			\
+	"\n"																													\
+	"    2 CNAME records, 4 A records, and 4 AAAA records\n"																\
+	"\n"																													\
+	"to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which requires\n"		\
+	"server queries.\n"																										\
+	"\n"																													\
+	"Test Case #2: Resolve a domain name with\n"																			\
+	"\n"																													\
+	"    2 CNAME records, 4 A records, and 4 AAAA records\n"																\
+	"\n"																													\
+	"to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which\n"		\
+	"requires server queries. Each subsequent iteration resolves the same domain name as the preliminary iteration,\n"		\
+	"which should ideally require no additional server queries, i.e., the results should come from the cache.\n"			\
+	"\n"																													\
+	"Unlike the preceding test case, this test case is concerned with DNSServiceGetAddrInfo() performance when the\n"		\
+	"records of the domain name being resolved are already in the cache. Therefore, the time required to resolve the\n"		\
+	"domain name in the preliminary iteration isn't counted in the performance stats.\n"									\
+	"\n"																													\
+	"Test Case #3: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n"
+
+#define kGAIPerfSectionTitle_TestSuiteAdvanced		"Test Suite \"Advanced\""
+#define kGAIPerfSectionText_TestSuiteAdvanced																				\
+	"This test suite consists of 33 test cases. Test cases 1 through 32 can be described in the following way\n"			\
+	"\n"																													\
+	"Test Case #N (where N is in [1, 32] and odd): Resolve a domain name with\n"											\
+	"\n"																													\
+	"    N_c CNAME records, N_a A records, and N_a AAAA records\n"															\
+	"\n"																													\
+	"to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which requires\n"		\
+	"server queries.\n"																										\
+	"\n"																													\
+	"Test Case #N (where N is in [1, 32] and even): Resolve a domain name with\n"											\
+	"\n"																													\
+	"    N_c CNAME records, N_a A records, and N_a AAAA records\n"															\
+	"\n"																													\
+	"to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which\n"		\
+	"requires server queries. Each subsequent iteration resolves the same domain name as the preliminary iteration,\n"		\
+	"which should ideally require no additional server queries, i.e., the results should come from the cache.\n"			\
+	"\n"																													\
+	"Unlike the preceding test case, this test case is concerned with DNSServiceGetAddrInfo() performance when the\n"		\
+	"records of the domain name being resolved are already in the cache. Therefore, the time required to resolve the\n"		\
+	"domain name in the preliminary iteration isn't counted in the performance stats.\n"									\
+	"\n"																													\
+	"N_c and N_a take on the following values, depending on the value of N:\n"												\
+	"\n"																													\
+	"    N_c is 0 if N is in [1, 8].\n"																						\
+	"    N_c is 1 if N is in [9, 16].\n"																					\
+	"    N_c is 2 if N is in [17, 24].\n"																					\
+	"    N_c is 4 if N is in [25, 32].\n"																					\
+	"\n"																													\
+	"    N_a is 1 if N mod 8 is 1 or 2.\n"																					\
+	"    N_a is 2 if N mod 8 is 3 or 4.\n"																					\
+	"    N_a is 4 if N mod 8 is 5 or 6.\n"																					\
+	"    N_a is 8 if N mod 8 is 7 or 0.\n"																					\
+	"\n"																													\
+	"Finally,\n"																											\
+	"\n"																													\
+	"Test Case #33: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n"
+
+static CLIOption		kGAIPerfOpts[] =
+{
+	StringOptionEx( 's', "suite",         &gGAIPerf_TestSuite,           "name", "Name of the predefined test suite to run.", true,
+		"\n"
+		"There are currently two predefined test suites, 'basic' and 'advanced', which are described below.\n"
+		"\n"
+	),
+	StringOption(   'o', "output",        &gGAIPerf_OutputFilePath,      "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+	StringOptionEx( 'f', "format",        &gGAIPerf_OutputFormat,        "format", "Specifies the test results output format. (default: json)", false,
+		"\n"
+		"Use 'json' for JavaScript Object Notation (JSON).\n"
+		"Use 'xml' for property list XML version 1.0.\n"
+		"Use 'binary' for property list binary version 1.0.\n"
+		"\n"
+	),
+	BooleanOption(  'n', "appendNewline", &gGAIPerf_OutputAppendNewLine, "If the output format is JSON, output a trailing newline character." ),
+	IntegerOption(   0 , "callDelay",     &gGAIPerf_CallDelayMs,         "ms", "Time to wait before calling DNSServiceGetAddrInfo() in milliseconds. (default: 10)", false ),
+	IntegerOption(   0 , "responseDelay", &gGAIPerf_ServerDelayMs,       "ms", "Additional delay in milliseconds to have the test DNS server apply to responses. (default: 0)", false ),
+	IntegerOption(  'i', "iterations",    &gGAIPerf_DefaultIterCount,    "count", "The default number of test case iterations. (default: 100)", false ),
+	
+	CLI_SECTION( kGAIPerfSectionTitle_TestSuiteBasic,		kGAIPerfSectionText_TestSuiteBasic ),
+	CLI_SECTION( kGAIPerfSectionTitle_TestSuiteAdvanced,	kGAIPerfSectionText_TestSuiteAdvanced ),
+	CLI_OPTION_END()
+};
+
+static CLIOption		kTestOpts[] =
+{
+	Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Run DNSServiceGetAddrInfo() performance tests.", false ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
 //	SSDP Command Options
 //===========================================================================================================================
 
@@ -812,10 +1262,13 @@ static CLIOption		kSSDPOpts[] =
 	CLI_OPTION_END()
 };
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
 //	res_query Command Options
 //===========================================================================================================================
 
+static void	ResQueryCmd( void );
+
 static const char *		gResQuery_Name			= NULL;
 static const char *		gResQuery_Type			= NULL;
 static const char *		gResQuery_Class			= NULL;
@@ -834,6 +1287,8 @@ static CLIOption		kResQueryOpts[] =
 //	dns_query Command Options
 //===========================================================================================================================
 
+static void ResolvDNSQueryCmd( void );
+
 static const char *		gResolvDNSQuery_Name	= NULL;
 static const char *		gResolvDNSQuery_Type	= NULL;
 static const char *		gResolvDNSQuery_Class	= NULL;
@@ -849,6 +1304,78 @@ static CLIOption		kResolvDNSQueryOpts[] =
 };
 
 //===========================================================================================================================
+//	CFHost Command Options
+//===========================================================================================================================
+
+static void	CFHostCmd( void );
+
+static const char *		gCFHost_Name		= NULL;
+static int				gCFHost_WaitSecs	= 0;
+
+static CLIOption		kCFHostOpts[] =
+{
+	StringOption(  'n', "name", &gCFHost_Name,     "hostname", "Hostname to resolve.", true ),
+	IntegerOption( 'w', "wait", &gCFHost_WaitSecs, "seconds",  "Time in seconds to wait before a normal exit. (default: 0)", false ),
+	CLI_OPTION_END()
+};
+
+static CLIOption		kLegacyOpts[] =
+{
+	Command( "res_query", ResQueryCmd,       kResQueryOpts,       "Uses res_query() from either libresolv or libinfo to query for a record.", true ),
+	Command( "dns_query", ResolvDNSQueryCmd, kResolvDNSQueryOpts, "Uses dns_query() from libresolv to query for a record.", true ),
+	Command( "cfhost",    CFHostCmd,         kCFHostOpts,         "Uses CFHost to resolve a hostname.", true ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+//	DNSConfigAdd Command Options
+//===========================================================================================================================
+
+static void	DNSConfigAddCmd( void );
+
+static CFStringRef		gDNSConfigAdd_ID			= NULL;
+static char **			gDNSConfigAdd_IPAddrArray	= NULL;
+static size_t			gDNSConfigAdd_IPAddrCount	= 0;
+static char **			gDNSConfigAdd_DomainArray	= NULL;
+static size_t			gDNSConfigAdd_DomainCount	= 0;
+static const char *		gDNSConfigAdd_Interface		= NULL;
+
+static CLIOption		kDNSConfigAddOpts[] =
+{
+	CFStringOption(     0 , "id",        &gDNSConfigAdd_ID,                                      "ID", "Arbitrary ID to use for resolver entry.", true ),
+	MultiStringOption( 'a', "address",   &gDNSConfigAdd_IPAddrArray, &gDNSConfigAdd_IPAddrCount, "IP address", "DNS server IP address(es). Can be specified more than once.", true ),
+	MultiStringOption( 'd', "domain",    &gDNSConfigAdd_DomainArray, &gDNSConfigAdd_DomainCount, "domain", "Specific domain(s) for the resolver entry. Can be specified more than once.", false ),
+	StringOption(      'i', "interface", &gDNSConfigAdd_Interface,                               "interface name", "Specific interface for the resolver entry.", false ),
+	
+	CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+//	DNSConfigRemove Command Options
+//===========================================================================================================================
+
+static void	DNSConfigRemoveCmd( void );
+
+static CFStringRef		gDNSConfigRemove_ID = NULL;
+
+static CLIOption		kDNSConfigRemoveOpts[] =
+{
+	CFStringOption( 0, "id", &gDNSConfigRemove_ID, "ID", "ID of resolver entry to remove.", true ),
+	
+	CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ),
+	CLI_OPTION_END()
+};
+
+static CLIOption		kDNSConfigOpts[] =
+{
+	Command( "add",    DNSConfigAddCmd,    kDNSConfigAddOpts,    "Add a supplemental resolver entry to the system's DNS configuration.", true ),
+	Command( "remove", DNSConfigRemoveCmd, kDNSConfigRemoveOpts, "Remove a supplemental resolver entry from the system's DNS configuration.", true ),
+	CLI_OPTION_END()
+};
+#endif	// TARGET_OS_DARWIN
+
+//===========================================================================================================================
 //	Command Table
 //===========================================================================================================================
 
@@ -872,10 +1399,6 @@ static void	DNSCryptCmd( void );
 #endif
 static void	MDNSQueryCmd( void );
 static void	PIDToUUIDCmd( void );
-#if( TARGET_OS_DARWIN )
-static void	ResQueryCmd( void );
-static void	ResolvDNSQueryCmd( void );
-#endif
 static void	DaemonVersionCmd( void );
 
 static CLIOption		kGlobalOpts[] =
@@ -900,6 +1423,7 @@ static CLIOption		kGlobalOpts[] =
 	
 	// Uncommon commands.
 	
+	Command( "getnameinfo",			GetNameInfoCmd,			kGetNameInfoOpts,		"Calls getnameinfo() and prints results.", true ),
 	Command( "getAddrInfoStress",	GetAddrInfoStressCmd,	kGetAddrInfoStressOpts,	"Runs DNSServiceGetAddrInfo() stress testing.", true ),
 	Command( "DNSQuery",			DNSQueryCmd,			kDNSQueryOpts,			"Crafts and sends a DNS query.", true ),
 #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
@@ -907,10 +1431,12 @@ static CLIOption		kGlobalOpts[] =
 #endif
 	Command( "mDNSQuery",			MDNSQueryCmd,			kMDNSQueryOpts,			"Crafts and sends an mDNS query over the specified interface.", true ),
 	Command( "pid2uuid",			PIDToUUIDCmd,			kPIDToUUIDOpts,			"Prints the UUID of a process.", true ),
-	Command( "ssdp",				NULL,					kSSDPOpts,				"Commands for testing with Simple Service Discovery Protocol (SSDP).", true ),
+	Command( "server",				DNSServerCmd,			kDNSServerOpts,			"DNS server for testing.", true ),
+	Command( "test",				NULL,					kTestOpts,				"Commands for testing DNS-SD.", true ),
+	Command( "ssdp",				NULL,					kSSDPOpts,				"Commands for testing Simple Service Discovery Protocol (SSDP).", true ),
 #if( TARGET_OS_DARWIN )
-	Command( "res_query",			ResQueryCmd,			kResQueryOpts,			"Uses res_query() from either libresolv or libinfo to query for a record.", true ),
-	Command( "dns_query",			ResolvDNSQueryCmd,		kResolvDNSQueryOpts,	"Uses dns_query() from libresolv to query for a record.", true ),
+	Command( "legacy",				NULL,					kLegacyOpts,			"Commands for legacy non-DNS-SD API.", true ),
+	Command( "dnsconfig",			NULL,					kDNSConfigOpts,			"Add/remove a supplemental resolver entry to/from the system's DNS configuration.", true ),
 #endif
 	Command( "daemonVersion",		DaemonVersionCmd,		NULL,					"Prints the version of the DNS-SD daemon.", true ),
 	
@@ -930,9 +1456,19 @@ static CLIOption		kGlobalOpts[] =
 
 static void	Exit( void *inContext ) ATTRIBUTE_NORETURN;
 
-#define kTimestampBufLen		27
+static int
+	PrintFTimestampHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext );
+static int
+	PrintFDNSMessageHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext );
 
-static char *			GetTimestampStr( char inBuffer[ kTimestampBufLen ] );
 static DNSServiceFlags	GetDNSSDFlagsFromOpts( void );
 
 typedef enum
@@ -972,29 +1508,6 @@ static OSStatus			RecordClassFromArgString( const char *inString, uint16_t *outV
 static char *			InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] );
 static const char *		RecordTypeToString( unsigned int inValue );
 
-// DNS message helpers
-
-typedef struct
-{
-	uint8_t		id[ 2 ];
-	uint8_t		flags[ 2 ];
-	uint8_t		questionCount[ 2 ];
-	uint8_t		answerCount[ 2 ];
-	uint8_t		authorityCount[ 2 ];
-	uint8_t		additionalCount[ 2 ];
-	
-}	DNSHeader;
-
-#define kDNSHeaderLength		12
-check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
-
-#define DNSHeaderGetID( HDR )					ReadBig16( ( HDR )->id )
-#define DNSHeaderGetFlags( HDR )				ReadBig16( ( HDR )->flags )
-#define DNSHeaderGetQuestionCount( HDR )		ReadBig16( ( HDR )->questionCount )
-#define DNSHeaderGetAnswerCount( HDR )			ReadBig16( ( HDR )->answerCount )
-#define DNSHeaderGetAuthorityCount( HDR )		ReadBig16( ( HDR )->authorityCount )
-#define DNSHeaderGetAdditionalCount( HDR )		ReadBig16( ( HDR )->additionalCount )
-
 static OSStatus
 	DNSMessageExtractDomainName(
 		const uint8_t *		inMsgPtr,
@@ -1036,6 +1549,7 @@ static OSStatus
 		const char *	inString,
 		uint8_t **		outEndPtr );
 static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
+static size_t	DomainNameLength( const uint8_t *inName );
 static OSStatus
 	DomainNameFromString(
 		uint8_t			inDomainName[ kDomainNameLengthMax ],
@@ -1048,10 +1562,13 @@ static OSStatus
 		char				inBuf[ kDNSServiceMaxDomainName ],
 		const uint8_t **	outNextPtr );
 
-static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, Boolean inIsMDNS, Boolean inPrintRaw );
-
-#define PrintMDNSMessage( MSGPTR, MSGLEN, RAW )		PrintDNSMessage( MSGPTR, MSGLEN, true, RAW )
-#define PrintUDNSMessage( MSGPTR, MSGLEN, RAW )		PrintDNSMessage( MSGPTR, MSGLEN, false, RAW )
+static OSStatus
+	DNSMessageToText(
+		const uint8_t *	inMsgPtr,
+		size_t			inMsgLen,
+		Boolean			inIsMDNS,
+		Boolean			inPrintRaw,
+		char **			outText );
 
 #define kDNSQueryMessageMaxLen		( kDNSHeaderLength + kDomainNameLengthMax + 4 )
 
@@ -1076,37 +1593,82 @@ static OSStatus
 		void *				inContext,
 		dispatch_source_t *	outSource );
 static OSStatus
-	DispatchReadSourceCreate(
-		SocketRef			inSock,
-		DispatchHandler		inEventHandler,
-		DispatchHandler		inCancelHandler,
-		void *				inContext,
-		dispatch_source_t *	outSource );
+	DispatchSocketSourceCreate(
+		SocketRef				inSock,
+		dispatch_source_type_t	inType,
+		dispatch_queue_t		inQueue,
+		DispatchHandler			inEventHandler,
+		DispatchHandler			inCancelHandler,
+		void *					inContext,
+		dispatch_source_t *		outSource );
+
+#define DispatchReadSourceCreate( SOCK, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) \
+	DispatchSocketSourceCreate( SOCK, DISPATCH_SOURCE_TYPE_READ, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE )
+
+#define DispatchWriteSourceCreate( SOCK, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) \
+	DispatchSocketSourceCreate( SOCK, DISPATCH_SOURCE_TYPE_WRITE, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE )
+
 static OSStatus
 	DispatchTimerCreate(
 		dispatch_time_t		inStart,
 		uint64_t			inIntervalNs,
 		uint64_t			inLeewayNs,
+		dispatch_queue_t	inQueue,
 		DispatchHandler		inEventHandler,
 		DispatchHandler		inCancelHandler,
 		void *				inContext,
 		dispatch_source_t *	outTimer );
+static OSStatus
+	DispatchProcessMonitorCreate(
+		pid_t				inPID,
+		unsigned long		inFlags,
+		dispatch_queue_t	inQueue,
+		DispatchHandler		inEventHandler,
+		DispatchHandler		inCancelHandler,
+		void *				inContext,
+		dispatch_source_t *	outMonitor );
 
 static const char *	ServiceTypeDescription( const char *inName );
 
 typedef struct
 {
-	SocketRef		sock;
-	void *			context;
+	SocketRef		sock;			// Socket.
+	void *			userContext;	// User context.
+	int32_t			refCount;		// Reference count.
 	
 }	SocketContext;
 
-static void			SocketContextCancelHandler( void *inContext );
+static OSStatus			SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext );
+static SocketContext *	SocketContextRetain( SocketContext *inContext );
+static void				SocketContextRelease( SocketContext *inContext );
+static void				SocketContextCancelHandler( void *inContext );
+
+#define ForgetSocketContext( X )	ForgetCustom( X, SocketContextRelease )
+
 static OSStatus		StringToInt32( const char *inString, int32_t *outValue );
 static OSStatus		StringToUInt32( const char *inString, uint32_t *outValue );
+static OSStatus		StringToLongLong( const char *inString, long long *outValue );
+static OSStatus		StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus		StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus		StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen );
 #if( TARGET_OS_DARWIN )
 static OSStatus		GetDefaultDNSServer( sockaddr_ip *outAddr );
 #endif
+static OSStatus
+	_ServerSocketOpenEx2( 
+		int				inFamily, 
+		int				inType, 
+		int				inProtocol, 
+		const void *	inAddr, 
+		int				inPort, 
+		int *			outPort, 
+		int				inRcvBufSize, 
+		Boolean			inNoPortReuse,
+		SocketRef *		outSock );
+
+typedef uint64_t		MicroTime64;
+
+static MicroTime64	GetCurrentMicroTime( void );	// Gets the number of milliseconds since 1970-01-01T00:00:00Z
 
 #define AddRmvString( X )		( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
 #define Unused( X )				(void)(X)
@@ -1117,12 +1679,17 @@ static OSStatus		GetDefaultDNSServer( sockaddr_ip *outAddr );
 
 int	main( int argc, const char **argv )
 {
+	OSStatus		err;
+	
 	// Route DebugServices logging output to stderr.
 	
 	dlog_control( "DebugServices:output=file;stderr" );
 	
+	PrintFRegisterExtension( "du:time",   PrintFTimestampHandler,  NULL );
+	PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL );
 	CLIInit( argc, argv );
-	CLIParse( kGlobalOpts, kCLIFlags_None );
+	err = CLIParse( kGlobalOpts, kCLIFlags_None );
+	if( err ) exit( 1 );
 	
 	return( gExitCode );
 }
@@ -1299,7 +1866,7 @@ static void	BrowseCmd( void )
 	{
 		DNSServiceRef		sdRef;
 		
-		if( useMainConnection ) sdRef = context->mainRef;
+		sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 		err = DNSServiceBrowse( &sdRef, context->flags, context->ifIndex, context->serviceTypes[ i ], context->domain,
 			BrowseCallback, context );
 		require_noerr( err, exit );
@@ -1334,23 +1901,22 @@ exit:
 static void	BrowsePrintPrologue( const BrowseContext *inContext )
 {
 	const int						timeLimitSecs	= inContext->timeLimitSecs;
-	const char * const *			serviceType		= (const char **) inContext->serviceTypes;
+	const char * const *			ptr				= (const char **) inContext->serviceTypes;
 	const char * const * const		end				= (const char **) inContext->serviceTypes + inContext->serviceTypesCount;
-	char							time[ kTimestampBufLen ];
 	char							ifName[ kInterfaceNameBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
 	FPrintF( stdout, "Flags:         %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
 	FPrintF( stdout, "Interface:     %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Service types: %s",			*serviceType++ );
-	while( serviceType < end ) FPrintF( stdout, ", %s", *serviceType++ );
+	FPrintF( stdout, "Service types: %s",			*ptr++ );
+	while( ptr < end ) FPrintF( stdout, ", %s",		*ptr++ );
 	FPrintF( stdout, "\n" );
 	FPrintF( stdout, "Domain:        %s\n",	inContext->domain ? inContext->domain : "<NULL> (default domains)" );
 	FPrintF( stdout, "Time limit:    " );
 	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time:    %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:    %{du:time}\n", NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -1433,11 +1999,11 @@ static void DNSSD_API
 	BrowseResolveOp *			newOp = NULL;
 	BrowseResolveOp **			p;
 	char						fullName[ kDNSServiceMaxDomainName ];
-	char						time[ kTimestampBufLen ];
+	struct timeval				now;
 	
 	Unused( inSDRef );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	err = inError;
 	require_noerr( err, exit );
@@ -1447,8 +2013,8 @@ static void DNSSD_API
 		FPrintF( stdout, "%-26s  A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%-26s  %-3s %5X %2d %-20s %-20s %s\n",
-		time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
+	FPrintF( stdout, "%{du:time}  %-3s %5X %2d %-20s %-20s %s\n",
+		&now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
 	
 	if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
 	
@@ -1536,22 +2102,22 @@ static void DNSSD_API
 		uint32_t				inTTL,
 		void *					inContext )
 {
-	OSStatus		err;
-	char			time[ kTimestampBufLen ];
+	OSStatus			err;
+	struct timeval		now;
 	
 	Unused( inSDRef );
 	Unused( inClass );
 	Unused( inTTL );
 	Unused( inContext );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	err = inError;
 	require_noerr( err, exit );
 	require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr );
 	
-	FPrintF( stdout, "%s  %s %s TXT on interface %d\n    TXT: %#{txt}\n",
-		time, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
+	FPrintF( stdout, "%{du:time}  %s %s TXT on interface %d\n    TXT: %#{txt}\n",
+		&now, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
 	
 exit:
 	if( err ) exit( 1 );
@@ -1574,19 +2140,19 @@ static void DNSSD_API
 		const unsigned char *	inTXTPtr,
 		void *					inContext )
 {
-	char		time[ kTimestampBufLen ];
-	char		errorStr[ 64 ];
+	struct timeval		now;
+	char				errorStr[ 64 ];
 	
 	Unused( inSDRef );
 	Unused( inFlags );
 	Unused( inContext );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError );
 	
-	FPrintF( stdout, "%s  %s can be reached at %s:%u (interface %d)%?s\n",
-		time, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
+	FPrintF( stdout, "%{du:time}  %s can be reached at %s:%u (interface %d)%?s\n",
+		&now, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
 	if( inTXTLen == 1 )
 	{
 		FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
@@ -1701,7 +2267,7 @@ static void	GetAddrInfoCmd( void )
 	
 	// Start operation.
 	
-	if( useMainConnection ) sdRef = context->mainRef;
+	sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 	err = DNSServiceGetAddrInfo( &sdRef, context->flags, context->ifIndex, context->protocols, context->name,
 		GetAddrInfoCallback, context );
 	require_noerr( err, exit );
@@ -1736,19 +2302,18 @@ static void	GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext )
 {
 	const int		timeLimitSecs = inContext->timeLimitSecs;
 	char			ifName[ kInterfaceNameBufLen ];
-	char			time[ kTimestampBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
-	FPrintF( stdout, "Flags:      %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:  %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Protocols:  %#{flags}\n",	inContext->protocols, kDNSServiceProtocolDescriptors );
-	FPrintF( stdout, "Name:       %s\n",		inContext->name );
-	FPrintF( stdout, "Mode:       %s\n",		inContext->oneShotMode ? "one-shot" : "continuous" );
+	FPrintF( stdout, "Flags:      %#{flags}\n",		inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:  %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Protocols:  %#{flags}\n",		inContext->protocols, kDNSServiceProtocolDescriptors );
+	FPrintF( stdout, "Name:       %s\n",			inContext->name );
+	FPrintF( stdout, "Mode:       %s\n",			inContext->oneShotMode ? "one-shot" : "continuous" );
 	FPrintF( stdout, "Time limit: " );
 	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -1779,14 +2344,14 @@ static void DNSSD_API
 		void *					inContext )
 {
 	GetAddrInfoContext * const		context = (GetAddrInfoContext *) inContext;
+	struct timeval					now;
 	OSStatus						err;
 	const char *					addrStr;
 	char							addrStrBuf[ kSockAddrStringMaxSize ];
-	char							time[ kTimestampBufLen ];
 	
 	Unused( inSDRef );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	switch( inError )
 	{
@@ -1826,8 +2391,8 @@ static void DNSSD_API
 		FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%-26s  %s %5X %2d %-32s %-38s %6u\n",
-		time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
+	FPrintF( stdout, "%{du:time}  %s %5X %2d %-32s %-38s %6u\n",
+		&now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
 	
 	if( context->oneShotMode )
 	{
@@ -1952,7 +2517,7 @@ static void	QueryRecordCmd( void )
 	
 	// Start operation.
 	
-	if( useMainConnection ) sdRef = context->mainRef;
+	sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 	err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
 		kDNSServiceClass_IN, QueryRecordCallback, context );
 	require_noerr( err, exit );
@@ -1998,7 +2563,6 @@ static void	QueryRecordPrintPrologue( const QueryRecordContext *inContext )
 {
 	const int		timeLimitSecs = inContext->timeLimitSecs;
 	char			ifName[ kInterfaceNameBufLen ];
-	char			time[ kTimestampBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
@@ -2010,7 +2574,7 @@ static void	QueryRecordPrintPrologue( const QueryRecordContext *inContext )
 	FPrintF( stdout, "Time limit:  " );
 	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time:  %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:  %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 	
 }
@@ -2034,13 +2598,13 @@ static void DNSSD_API
 		void *					inContext )
 {
 	QueryRecordContext * const		context		= (QueryRecordContext *) inContext;
+	struct timeval					now;
 	OSStatus						err;
 	char *							rdataStr	= NULL;
-	char							time[ kTimestampBufLen ];
 	
 	Unused( inSDRef );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	switch( inError )
 	{
@@ -2076,8 +2640,8 @@ static void DNSSD_API
 		FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%-26s  %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
-		time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
+	FPrintF( stdout, "%{du:time}  %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
+		&now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
 		( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
 	
 	if( context->oneShotMode )
@@ -2273,7 +2837,6 @@ static void	RegisterPrintPrologue( const RegisterContext *inContext )
 	size_t		i;
 	int			infinite;
 	char		ifName[ kInterfaceNameBufLen ];
-	char		time[ kTimestampBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
@@ -2304,7 +2867,7 @@ static void	RegisterPrintPrologue( const RegisterContext *inContext )
 		FPrintF( stdout, "    TTL:   %u%?s\n",		record->ttl, record->ttl == 0, " (system will use a default value.)" );
 		FPrintF( stdout, "    RData: %#H\n\n",		record->dataPtr, (int) record->dataLen, INT_MAX );
 	}
-	FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -2345,19 +2908,19 @@ static void DNSSD_API
 {
 	RegisterContext * const		context = (RegisterContext *) inContext;
 	OSStatus					err;
-	char						time[ kTimestampBufLen ];
+	struct timeval				now;
 	
 	Unused( inSDRef );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	if( !context->printedHeader )
 	{
 		FPrintF( stdout, "%-26s  A/R Flags Service\n", "Timestamp" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%-26s  %-3s %5X %s.%s%s %?#m\n",
-		time, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
+	FPrintF( stdout, "%{du:time}  %-3s %5X %s.%s%s %?#m\n",
+		&now, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
 	
 	require_noerr_action_quiet( inError, exit, err = inError );
 	
@@ -2548,7 +3111,6 @@ exit:
 static void	RegisterRecordPrintPrologue( const RegisterRecordContext *inContext )
 {
 	int			infinite;
-	char		time[ kTimestampBufLen ];
 	char		ifName[ kInterfaceNameBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
@@ -2569,7 +3131,7 @@ static void	RegisterRecordPrintPrologue( const RegisterRecordContext *inContext
 			inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" );
 		FPrintF( stdout, "    RData:    %#H\n",		inContext->updateDataPtr, (int) inContext->updateDataLen, INT_MAX );
 	}
-	FPrintF( stdout, "Start time:  %s\n",			GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:  %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -2598,15 +3160,15 @@ static void
 		void *				inContext )
 {
 	RegisterRecordContext *		context = (RegisterRecordContext *) inContext;
-	char						time[ kTimestampBufLen ];
+	struct timeval				now;
 	
 	Unused( inSDRef );
 	Unused( inRecordRef );
 	Unused( inFlags );
 	Unused( context );
 	
-	GetTimestampStr( time );
-	FPrintF( stdout, "%s Record registration result (error %#m)\n", time, inError );
+	gettimeofday( &now, NULL );
+	FPrintF( stdout, "%{du:time} Record registration result (error %#m)\n", &now, inError );
 	
 	if( !context->didRegister && !inError )
 	{
@@ -2749,7 +3311,7 @@ static void	ResolveCmd( void )
 	
 	// Start operation.
 	
-	if( useMainConnection ) sdRef = context->mainRef;
+	sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 	err = DNSServiceResolve( &sdRef, context->flags, context->ifIndex, context->name, context->type, context->domain,
 		ResolveCallback, NULL );
 	require_noerr( err, exit );
@@ -2850,19 +3412,18 @@ static void	ResolvePrintPrologue( const ResolveContext *inContext )
 {
 	const int		timeLimitSecs = inContext->timeLimitSecs;
 	char			ifName[ kInterfaceNameBufLen ];
-	char			time[ kTimestampBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
-	FPrintF( stdout, "Flags:      %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:  %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Name:       %s\n",		inContext->name );
-	FPrintF( stdout, "Type:       %s\n",		inContext->type );
-	FPrintF( stdout, "Domain:     %s\n",		inContext->domain );
+	FPrintF( stdout, "Flags:      %#{flags}\n",		inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:  %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Name:       %s\n",			inContext->name );
+	FPrintF( stdout, "Type:       %s\n",			inContext->type );
+	FPrintF( stdout, "Domain:     %s\n",			inContext->domain );
 	FPrintF( stdout, "Time limit: " );
 	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -2894,19 +3455,19 @@ static void DNSSD_API
 		const unsigned char *	inTXTPtr,
 		void *					inContext )
 {
-	char		time[ kTimestampBufLen ];
-	char		errorStr[ 64 ];
+	struct timeval		now;
+	char				errorStr[ 64 ];
 	
 	Unused( inSDRef );
 	Unused( inFlags );
 	Unused( inContext );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError );
 	
-	FPrintF( stdout, "%s: %s can be reached at %s:%u (interface %d)%?s\n",
-		time, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
+	FPrintF( stdout, "%{du:time}: %s can be reached at %s:%u (interface %d)%?s\n",
+		&now, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
 	if( inTXTLen == 1 )
 	{
 		FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
@@ -2958,10 +3519,10 @@ static void	GetAddrInfoPOSIXCmd( void )
 {
 	OSStatus					err;
 	struct addrinfo				hints;
+	struct timeval				now;
 	const struct addrinfo *		addrInfo;
 	struct addrinfo *			addrInfoList = NULL;
 	const FlagStringPair *		pair;
-	char						time[ kTimestampBufLen ];
 	
 	memset( &hints, 0, sizeof( hints ) );
 	hints.ai_socktype = SOCK_STREAM;
@@ -3009,13 +3570,13 @@ static void	GetAddrInfoPOSIXCmd( void )
 		if( ( (unsigned int) hints.ai_flags ) & pair->flag ) FPrintF( stdout, "%s ", pair->str );
 	}
 	FPrintF( stdout, ">\n" );
-	FPrintF( stdout, "Start time:     %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:     %{du:time}\n", NULL );
 	FPrintF( stdout, "---\n" );
 	
 	// Call getaddrinfo().
 	
 	err = getaddrinfo( gGAIPOSIX_HostName, gGAIPOSIX_ServName, &hints, &addrInfoList );
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	if( err )
 	{
 		FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
@@ -3033,7 +3594,7 @@ static void	GetAddrInfoPOSIXCmd( void )
 		}
 	}
 	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "End time:       %s\n", time );
+	FPrintF( stdout, "End time:       %{du:time}\n", &now );
 	
 exit:
 	if( addrInfoList ) freeaddrinfo( addrInfoList );
@@ -3151,7 +3712,7 @@ static void	ReverseLookupCmd( void )
 	
 	// Start operation.
 	
-	if( useMainConnection ) sdRef = context->mainRef;
+	sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 	err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
 		kDNSServiceClass_IN, QueryRecordCallback, context );
 	require_noerr( err, exit );
@@ -3284,7 +3845,7 @@ static void	PortMappingCmd( void )
 	
 	// Start operation.
 	
-	if( useMainConnection ) sdRef = context->mainRef;
+	sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 	err = DNSServiceNATPortMappingCreate( &sdRef, context->flags, context->ifIndex, context->protocols,
 		htons( context->internalPort ), htons( context->externalPort ), context->ttl, PortMappingCallback, context );
 	require_noerr( err, exit );
@@ -3311,17 +3872,17 @@ exit:
 static void	PortMappingPrintPrologue( const PortMappingContext *inContext )
 {
 	char		ifName[ kInterfaceNameBufLen ];
-	char		time[ kTimestampBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
-	FPrintF( stdout, "Flags:         %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:     %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Protocols:     %#{flags}\n",	inContext->protocols, kDNSServiceProtocolDescriptors );
-	FPrintF( stdout, "Internal Port: %u\n",			inContext->internalPort );
-	FPrintF( stdout, "External Port: %u\n",			inContext->externalPort );
-	FPrintF( stdout, "TTL:           %u%?s\n",		inContext->ttl, !inContext->ttl, " (system will use a default value.)" );
-	FPrintF( stdout, "Start time:    %s\n",			GetTimestampStr( time ) );
+	FPrintF( stdout, "Flags:         %#{flags}\n",		inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:     %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Protocols:     %#{flags}\n",		inContext->protocols, kDNSServiceProtocolDescriptors );
+	FPrintF( stdout, "Internal Port: %u\n",				inContext->internalPort );
+	FPrintF( stdout, "External Port: %u\n",				inContext->externalPort );
+	FPrintF( stdout, "TTL:           %u%?s\n",			inContext->ttl, !inContext->ttl,
+		" (system will use a default value.)" );
+	FPrintF( stdout, "Start time:    %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 	
 }
@@ -3355,13 +3916,13 @@ static void DNSSD_API
 		void *				inContext )
 {
 	PortMappingContext * const		context = (PortMappingContext *) inContext;
-	char							time[ kTimestampBufLen ];
+	struct timeval					now;
 	char							errorStr[ 128 ];
 	
 	Unused( inSDRef );
 	Unused( inFlags );
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " (error: %#m)", inError );
 	if( !context->printedHeader )
@@ -3369,8 +3930,8 @@ static void DNSSD_API
 		FPrintF( stdout, "%-26s  IF %7s %15s %7s %6s Protocol\n", "Timestamp", "IntPort", "ExtAddr", "ExtPort", "TTL" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%-26s  %2u %7u %15.4a %7u %6u %#{flags}%?s\n",
-		time, inInterfaceIndex, ntohs( inInternalPort), &inExternalIPv4Address, ntohs( inExternalPort ), inTTL,
+	FPrintF( stdout, "%{du:time}  %2u %7u %15.4a %7u %6u %#{flags}%?s\n",
+		&now, inInterfaceIndex, ntohs( inInternalPort), &inExternalIPv4Address, ntohs( inExternalPort ), inTTL,
 		inProtocol, kDNSServiceProtocolDescriptors, inError, errorStr );
 }
 
@@ -3632,7 +4193,7 @@ static void	BrowseAllCmd( void )
 	gBrowseAll_ServiceTypesCount	= 0;
 	context->browseTimeSecs			= gBrowseAll_BrowseTimeSecs;
 	context->maxConnectTimeSecs		= gBrowseAll_MaxConnectTimeSecs;
-	context->includeAWDL			= gBrowseAll_IncludeAWDL ? true : false;
+	context->includeAWDL			= gDNSSDFlag_IncludeAWDL ? true : false;
 #if( TARGET_OS_POSIX )
 	context->useColoredText			= isatty( STDOUT_FILENO ) ? true : false;
 #endif
@@ -3683,7 +4244,6 @@ static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext )
 {
 	size_t		i;
 	char		ifName[ kInterfaceNameBufLen ];
-	char		time[ kTimestampBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
@@ -3703,7 +4263,8 @@ static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext )
 	FPrintF( stdout, "Browse time:      %d second%?c\n", inContext->browseTimeSecs, inContext->browseTimeSecs != 1, 's' );
 	FPrintF( stdout, "Max connect time: %d second%?c\n",
 		inContext->maxConnectTimeSecs, inContext->maxConnectTimeSecs != 1, 's' );
-	FPrintF( stdout, "Start time:         %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "IncludeAWDL:      %s\n", inContext->includeAWDL ? "YES" : "NO" );
+	FPrintF( stdout, "Start time:       %{du:time}\n", NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -4063,7 +4624,7 @@ static void	BrowseAllStop( void *inContext )
 	{
 		check( !context->exitTimer );
 		err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER,
-			100 * kNanosecondsPerMillisecond, BrowseAllExit, NULL, context, &context->exitTimer );
+			100 * kNanosecondsPerMillisecond, NULL, BrowseAllExit, NULL, context, &context->exitTimer );
 		require_noerr( err, exit );
 		dispatch_resume( context->exitTimer );
 	}
@@ -4744,6 +5305,81 @@ static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
 }
 
 //===========================================================================================================================
+//	GetNameInfoCmd
+//===========================================================================================================================
+
+const FlagStringPair		kGetNameInfoFlagStringPairs[] =
+{
+	CaseFlagStringify( NI_NUMERICSCOPE ),
+	CaseFlagStringify( NI_DGRAM ),
+	CaseFlagStringify( NI_NUMERICSERV ),
+	CaseFlagStringify( NI_NAMEREQD ),
+	CaseFlagStringify( NI_NUMERICHOST ),
+	CaseFlagStringify( NI_NOFQDN ),
+	{ 0, NULL }
+};
+
+static void	GetNameInfoCmd( void )
+{
+	OSStatus					err;
+	sockaddr_ip					sip;
+	size_t						sockAddrLen;
+	unsigned int				flags;
+	const FlagStringPair *		pair;
+	struct timeval				now;
+	char						host[ NI_MAXHOST ];
+	char						serv[ NI_MAXSERV ];
+	
+	err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
+	check_noerr( err );
+	if( err )
+	{
+		FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+		goto exit;
+	}
+	
+	flags = 0;
+	if( gGetNameInfoFlag_DGram )		flags |= NI_DGRAM;
+	if( gGetNameInfoFlag_NameReqd )		flags |= NI_NAMEREQD;
+	if( gGetNameInfoFlag_NoFQDN )		flags |= NI_NOFQDN;
+	if( gGetNameInfoFlag_NumericHost )	flags |= NI_NUMERICHOST;
+	if( gGetNameInfoFlag_NumericScope )	flags |= NI_NUMERICSCOPE;
+	if( gGetNameInfoFlag_NumericServ )	flags |= NI_NUMERICSERV;
+	
+	// Print prologue.
+	
+	FPrintF( stdout, "SockAddr:   %##a\n",	&sip.sa );
+	FPrintF( stdout, "Flags:      0x%X < ",	flags );
+	for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair )
+	{
+		if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str );
+	}
+	FPrintF( stdout, ">\n" );
+	FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+	FPrintF( stdout, "---\n" );
+	
+	// Call getnameinfo().
+	
+	err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ),
+		(int) flags );
+	gettimeofday( &now, NULL );
+	if( err )
+	{
+		FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+	}
+	else
+	{
+		FPrintF( stdout, "host: %s\n", host );
+		FPrintF( stdout, "serv: %s\n", serv );
+	}
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "End time:   %{du:time}\n", &now );
+	
+exit:
+	gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
 //	GetAddrInfoStressCmd
 //===========================================================================================================================
 
@@ -4782,7 +5418,6 @@ static void	GetAddrInfoStressCmd( void )
 	DNSServiceFlags			flags;
 	uint32_t				ifIndex;
 	char					ifName[ kInterfaceNameBufLen ];
-	char					time[ kTimestampBufLen ];
 	
 	if( gGAIStress_TestDurationSecs < 0 )
 	{
@@ -4863,11 +5498,11 @@ static void	GetAddrInfoStressCmd( void )
 	{
 		FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
 	}
-	FPrintF( stdout, "Connection count:     %d\n",		gGAIStress_ConnectionCount );
-	FPrintF( stdout, "Request duration min: %d ms\n",	gGAIStress_DurationMinMs );
-	FPrintF( stdout, "Request duration max: %d ms\n",	gGAIStress_DurationMaxMs );
-	FPrintF( stdout, "Request count max:    %d\n",		gGAIStress_RequestCountMax );
-	FPrintF( stdout, "Start time:           %s\n",		GetTimestampStr( time ) );
+	FPrintF( stdout, "Connection count:     %d\n",			gGAIStress_ConnectionCount );
+	FPrintF( stdout, "Request duration min: %d ms\n",		gGAIStress_DurationMinMs );
+	FPrintF( stdout, "Request duration max: %d ms\n",		gGAIStress_DurationMaxMs );
+	FPrintF( stdout, "Request count max:    %d\n",			gGAIStress_RequestCountMax );
+	FPrintF( stdout, "Start time:           %{du:time}\n",	NULL);
 	FPrintF( stdout, "---\n" );
 	
 	dispatch_main();
@@ -4893,7 +5528,6 @@ static void	GetAddrInfoStressEvent( void *inContext )
 	unsigned int					nextMs;
 	char							randomStr[ kStressRandStrLen + 1 ];
 	char							hostname[ kStressRandStrLen + 4 + 1 ];
-	char							time[ kTimestampBufLen ];
 	Boolean							isConnectionNew	= false;
 	static Boolean					printedHeader	= false;
 	
@@ -4923,8 +5557,8 @@ static void	GetAddrInfoStressEvent( void *inContext )
 		FPrintF( stdout, "%-26s Conn  Hostname Dur (ms)\n", "Timestamp" );
 		printedHeader = true;
 	}
-	FPrintF( stdout, "%-26s %3u%c %9s %8u\n",
-		GetTimestampStr( time ), context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
+	FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
+		NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
 	
 	DNSServiceForget( &context->sdRef );
 	sdRef = context->mainRef;
@@ -4970,8 +5604,6 @@ static void DNSSD_API
 //	DNSQueryCmd
 //===========================================================================================================================
 
-#define kDNSPort	53
-
 typedef struct
 {
 	sockaddr_ip				serverAddr;
@@ -5073,8 +5705,7 @@ static void	DNSQueryCmd( void )
 	
 	if( gDNSQuery_Verbose )
 	{
-		FPrintF( stdout, "DNS message to send:\n\n" );
-		PrintUDNSMessage( msgPtr, msgLen, false );
+		FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
 		FPrintF( stdout, "---\n" );
 	}
 	
@@ -5103,7 +5734,7 @@ static void	DNSQueryCmd( void )
 	
 	if( context->timeLimitSecs == 0 ) goto exit;
 	
-	err = DispatchReadSourceCreate( context->sock, DNSQueryReadHandler, DNSQueryCancelHandler, context,
+	err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
 		&context->readSource );
 	require_noerr( err, exit );
 	dispatch_resume( context->readSource );
@@ -5132,7 +5763,6 @@ exit:
 static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext )
 {
 	const int		timeLimitSecs = inContext->timeLimitSecs;
-	char			time[ kTimestampBufLen ];
 	
 	FPrintF( stdout, "Name:        %s\n",		inContext->name );
 	FPrintF( stdout, "Type:        %s (%u)\n",	RecordTypeToString( inContext->type ), inContext->type );
@@ -5141,7 +5771,7 @@ static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext )
 	FPrintF( stdout, "Time limit:  " );
 	if( timeLimitSecs >= 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
 	else						FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time:  %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:  %{du:time}\n", NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -5152,11 +5782,11 @@ static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext )
 static void	DNSQueryReadHandler( void *inContext )
 {
 	OSStatus					err;
+	struct timeval				now;
 	const uint64_t				nowTicks	= UpTicks();
 	DNSQueryContext * const		context		= (DNSQueryContext *) inContext;
-	char						time[ kTimestampBufLen ];
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	if( context->useTCP )
 	{
@@ -5198,11 +5828,11 @@ static void	DNSQueryReadHandler( void *inContext )
 		check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
 	}
 	
-	FPrintF( stdout, "Receive time: %s\n",			time );
+	FPrintF( stdout, "Receive time: %{du:time}\n",	&now );
 	FPrintF( stdout, "Source:       %##a\n",		&context->serverAddr );
 	FPrintF( stdout, "Message size: %zu\n",			context->msgLen );
 	FPrintF( stdout, "RTT:          %llu ms\n\n",	UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-	PrintUDNSMessage( context->msgPtr, context->msgLen, context->printRawRData );
+	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
 	
 	if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
 	{
@@ -5336,7 +5966,7 @@ static void	DNSCryptCmd( void )
 	DNSCryptContext *		context		= NULL;
 	size_t					writtenBytes;
 	size_t					totalBytes;
-	SocketContext *			sockContext;
+	SocketContext *			sockCtx;
 	SocketRef				sock		= kInvalidSocketRef;
 	const char *			ptr;
 	
@@ -5403,17 +6033,15 @@ static void	DNSCryptCmd( void )
 	err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
 	require_noerr( err, exit );
 	
-	sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
-	require_action( sockContext, exit, err = kNoMemoryErr );
+	err = SocketContextCreate( sock, context, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
 	
-	err = DispatchReadSourceCreate( sock, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockContext,
+	err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
 		&context->readSource );
-	if( err ) ForgetMem( &sockContext );
+	if( err ) ForgetSocketContext( &sockCtx );
 	require_noerr( err, exit );
 	
-	sockContext->context	= context;
-	sockContext->sock		= sock;
-	sock = kInvalidSocketRef;
 	dispatch_resume( context->readSource );
 	
 	if( context->timeLimitSecs > 0 )
@@ -5436,9 +6064,10 @@ exit:
 static void	DNSCryptReceiveCertHandler( void *inContext )
 {
 	OSStatus					err;
+	struct timeval				now;
 	const uint64_t				nowTicks	= UpTicks();
-	SocketContext * const		sockContext	= (SocketContext *) inContext;
-	DNSCryptContext * const		context		= (DNSCryptContext *) sockContext->context;
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	DNSCryptContext * const		context		= (DNSCryptContext *) sockCtx->userContext;
 	const DNSHeader *			hdr;
 	sockaddr_ip					fromAddr;
 	const uint8_t *				ptr;
@@ -5446,23 +6075,21 @@ static void	DNSCryptReceiveCertHandler( void *inContext )
 	size_t						txtLen;
 	unsigned int				answerCount, i;
 	uint8_t						targetName[ kDomainNameLengthMax ];
-	char						time[ kTimestampBufLen ];
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	dispatch_source_forget( &context->readSource );
 	
-	err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+	err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
 		&fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
 	require_noerr( err, exit );
 	check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
 	
-	FPrintF( stdout, "Receive time: %s\n",			time );
+	FPrintF( stdout, "Receive time: %{du:time}\n",	&now );
 	FPrintF( stdout, "Source:       %##a\n",		&context->serverAddr );
 	FPrintF( stdout, "Message size: %zu\n",			context->msgLen );
 	FPrintF( stdout, "RTT:          %llu ms\n\n",	UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-	
-	PrintUDNSMessage( context->msgBuf, context->msgLen, context->printRawRData );
+	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
 	
 	require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
 	
@@ -5521,28 +6148,28 @@ exit:
 static void	DNSCryptReceiveResponseHandler( void *inContext )
 {
 	OSStatus						err;
+	struct timeval					now;
 	const uint64_t					nowTicks	= UpTicks();
-	SocketContext * const			sockContext	= (SocketContext *) inContext;
-	DNSCryptContext * const			context		= (DNSCryptContext *) sockContext->context;
+	SocketContext * const			sockCtx		= (SocketContext *) inContext;
+	DNSCryptContext * const			context		= (DNSCryptContext *) sockCtx->userContext;
 	sockaddr_ip						fromAddr;
 	DNSCryptResponseHeader *		hdr;
 	const uint8_t *					end;
 	uint8_t *						ciphertext;
 	uint8_t *						plaintext;
 	const uint8_t *					response;
-	char							time[ kTimestampBufLen ];
 	uint8_t							nonce[ crypto_box_NONCEBYTES ];
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
 	dispatch_source_forget( &context->readSource );
 	
-	err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+	err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
 		&fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
 	require_noerr( err, exit );
 	check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
 	
-	FPrintF( stdout, "Receive time: %s\n",			time );
+	FPrintF( stdout, "Receive time: %{du:time}\n",	&now );
 	FPrintF( stdout, "Source:       %##a\n",		&context->serverAddr );
 	FPrintF( stdout, "Message size: %zu\n",			context->msgLen );
 	FPrintF( stdout, "RTT:          %llu ms\n\n",	UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
@@ -5586,7 +6213,7 @@ static void	DNSCryptReceiveResponseHandler( void *inContext )
 	require_noerr( err, exit );
 	
 	response = plaintext + crypto_box_ZEROBYTES;
-	PrintUDNSMessage( response, (size_t)( end - response ), context->printRawRData );
+	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
 	Exit( kExitReason_ReceivedResponse );
 	
 exit:
@@ -5762,7 +6389,7 @@ exit:
 static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext )
 {
 	OSStatus			err;
-	SocketContext *		sockContext;
+	SocketContext *		sockCtx;
 	SocketRef			sock = kInvalidSocketRef;
 	
 	check( inContext->msgLen > 0 );
@@ -5775,18 +6402,15 @@ static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext )
 	err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
 	require_noerr( err, exit );
 	
-	sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
-	require_action( sockContext, exit, err = kNoMemoryErr );
+	err = SocketContextCreate( sock, inContext, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
 	
-	err = DispatchReadSourceCreate( sock, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockContext,
+	err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
 		&inContext->readSource );
-	if( err ) ForgetMem( &sockContext );
+	if( err ) ForgetSocketContext( &sockCtx );
 	require_noerr( err, exit );
 	
-	sockContext->context	= inContext;
-	sockContext->sock		= sock;
-	sock = kInvalidSocketRef;
-	
 	dispatch_resume( inContext->readSource );
 	
 exit:
@@ -5857,11 +6481,6 @@ static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
 //	MDNSQueryCmd
 //===========================================================================================================================
 
-#define kMDNSPort					5353
-
-#define kDefaultMDNSMessageID		0
-#define kDefaultMDNSQueryFlags		0
-
 typedef struct
 {
 	const char *			qnameStr;							// Name (QNAME) of the record being queried as a C string.
@@ -6039,37 +6658,33 @@ static void	MDNSQueryCmd( void )
 	
 	if( IsValidSocket( sockV4 ) )
 	{
-		SocketContext *		sockContext;
+		SocketContext *		sockCtx;
 		
-		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
-		require_action( sockContext, exit, err = kNoMemoryErr );
+		err = SocketContextCreate( sockV4, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV4 = kInvalidSocketRef;
 		
-		err = DispatchReadSourceCreate( sockV4, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
 			&context->readSourceV4 );
-		if( err ) ForgetMem( &sockContext );
+		if( err ) ForgetSocketContext( &sockCtx );
 		require_noerr( err, exit );
 		
-		sockContext->context	= context;
-		sockContext->sock		= sockV4;
-		sockV4 = kInvalidSocketRef;
 		dispatch_resume( context->readSourceV4 );
 	}
 	
 	if( IsValidSocket( sockV6 ) )
 	{
-		SocketContext *		sockContext;
+		SocketContext *		sockCtx;
 		
-		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
-		require_action( sockContext, exit, err = kNoMemoryErr );
+		err = SocketContextCreate( sockV6, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV6 = kInvalidSocketRef;
 		
-		err = DispatchReadSourceCreate( sockV6, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
 			&context->readSourceV6 );
-		if( err ) ForgetMem( &sockContext );
+		if( err ) ForgetSocketContext( &sockCtx );
 		require_noerr( err, exit );
 		
-		sockContext->context	= context;
-		sockContext->sock		= sockV6;
-		sockV6 = kInvalidSocketRef;
 		dispatch_resume( context->readSourceV6 );
 	}
 	
@@ -6093,7 +6708,6 @@ exit:
 static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
 {
 	const int		receiveSecs = inContext->receiveSecs;
-	char			time[ kTimestampBufLen ];
 	
 	FPrintF( stdout, "Interface:        %d (%s)\n",		(int32_t) inContext->ifIndex, inContext->ifName );
 	FPrintF( stdout, "Name:             %s\n",			inContext->qnameStr );
@@ -6105,7 +6719,7 @@ static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
 	FPrintF( stdout, "Receive duration: " );
 	if( receiveSecs >= 0 )	FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time:  %s\n",		GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:       %{du:time}\n",	NULL );
 }
 
 //===========================================================================================================================
@@ -6115,16 +6729,16 @@ static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
 static void	MDNSQueryReadHandler( void *inContext )
 {
 	OSStatus						err;
-	SocketContext * const			sockContext	= (SocketContext *) inContext;
-	MDNSQueryContext * const		context		= (MDNSQueryContext *) sockContext->context;
+	struct timeval					now;
+	SocketContext * const			sockCtx = (SocketContext *) inContext;
+	MDNSQueryContext * const		context = (MDNSQueryContext *) sockCtx->userContext;
 	size_t							msgLen;
 	sockaddr_ip						fromAddr;
-	char							time[ kTimestampBufLen ];
 	Boolean							foundAnswer	= false;
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
-	err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
+	err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
 		sizeof( fromAddr ), NULL, NULL, NULL, NULL );
 	require_noerr( err, exit );
 	
@@ -6162,11 +6776,10 @@ static void	MDNSQueryReadHandler( void *inContext )
 	if( context->allResponses || foundAnswer )
 	{
 		FPrintF( stdout, "---\n" );
-		FPrintF( stdout, "Receive time: %s\n",		time );
-		FPrintF( stdout, "Source:       %##a\n",	&fromAddr );
-		FPrintF( stdout, "Message size: %zu\n\n",	msgLen );
-		
-		PrintMDNSMessage( context->msgBuf, msgLen, context->printRawRData );
+		FPrintF( stdout, "Receive time: %{du:time}\n",	&now );
+		FPrintF( stdout, "Source:       %##a\n",		&fromAddr );
+		FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
+			msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
 	}
 	
 exit:
@@ -6194,221 +6807,3657 @@ exit:
 }
 
 //===========================================================================================================================
-//	SSDPDiscoverCmd
+//	DNSServerCmd
 //===========================================================================================================================
 
-#define kSSDPPort		1900
+typedef uint32_t		DNSServerEventType;
+#define kDNSServerEvent_Started		1
+#define kDNSServerEvent_Stopped		2
+
+typedef struct DNSServerPrivate *		DNSServerRef;
 
 typedef struct
 {
-	HTTPHeader				header;			// HTTP header object for sending and receiving.
-	dispatch_source_t		readSourceV4;	// Read dispatch source for IPv4 socket.
-	dispatch_source_t		readSourceV6;	// Read dispatch source for IPv6 socket.
-	int						receiveSecs;	// After send, the amount of time to spend receiving.
-	uint32_t				ifindex;		// Index of the interface over which to send the query.
-	Boolean					useIPv4;		// True if the query should be sent via IPv4 multicast.
-	Boolean					useIPv6;		// True if the query should be sent via IPv6 multicast.
+	DNSServerRef			server;				// Reference to the DNS server.
+	dispatch_source_t		sigIntSource;		// Dispatch SIGINT source.
+	dispatch_source_t		sigTermSource;		// Dispatch SIGTERM source.
+#if( TARGET_OS_DARWIN )
+	dispatch_source_t		processMonitor;		// Process monitor source for process being followed, if any.
+	pid_t					followPID;			// PID of process being followed (we exit when they exit), if any.
+	Boolean					resolverRegistered;	// True if system DNS settings contains a resolver entry for server.
+#endif
+	Boolean					loopbackOnly;		// True if the server should be bound to the loopback interface.
+	Boolean					serverStarted;		// True if the server was successfully started.
+	Boolean					calledStop;			// True if the server was explicitly stopped.
 	
-}	SSDPDiscoverContext;
+}	DNSServerCmdContext;
 
-static void		SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext );
-static void		SSDPDiscoverReadHandler( void *inContext );
-static int		SocketToPortNumber( SocketRef inSock );
-static OSStatus	WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST );
+typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext );
 
-static void	SSDPDiscoverCmd( void )
+CFTypeID	DNSServerGetTypeID( void );
+static OSStatus
+	DNSServerCreate(
+		dispatch_queue_t		inQueue,
+		DNSServerEventHandler_f	inEventHandler,
+		void *					inEventContext,
+		int						inResponseDelayMs,
+		Boolean					inLoopbackOnly,
+		DNSServerRef *			outServer );
+static void	DNSServerStart( DNSServerRef inServer );
+static void	DNSServerStop( DNSServerRef inServer );
+
+static void	DNSServerCmdContextFree( DNSServerCmdContext *inContext );
+static void	DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext );
+static void	DNSServerCmdSigIntHandler( void *inContext );
+static void	DNSServerCmdSigTermHandler( void *inContext );
+#if( TARGET_OS_DARWIN )
+static void	DNSServerCmdFollowedProcessHandler( void *inContext );
+#endif
+
+ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
+#define ds_ulog( LEVEL, ... )		ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
+
+static void	DNSServerCmd( void )
 {
 	OSStatus					err;
-	SSDPDiscoverContext *		context;
-	dispatch_source_t			signalSource	= NULL;
-	SocketRef					sockV4			= kInvalidSocketRef;
-	SocketRef					sockV6			= kInvalidSocketRef;
-	ssize_t						n;
-	int							sendCount;
-	char						time[ kTimestampBufLen ];
-	
-	// Set up SIGINT handler.
-	
-	signal( SIGINT, SIG_IGN );
-	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
-	require_noerr( err, exit );
-	dispatch_resume( signalSource );
+	DNSServerCmdContext *		context;
 	
-	// Check command parameters.
-	
-	if( gSSDPDiscover_ReceiveSecs < -1 )
-	{
-		FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
-		err = kParamErr;
-		goto exit;
-	}
-	
-	// Create context.
-	
-	context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
+	context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
 	require_action( context, exit, err = kNoMemoryErr );
 	
-	context->receiveSecs	= gSSDPDiscover_ReceiveSecs;
-	context->useIPv4		= ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
-	context->useIPv6		= ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
-	
-	err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
-	require_noerr_quiet( err, exit );
-	
-	// Set up IPv4 socket.
+	context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false;
 	
-	if( context->useIPv4 )
+#if( TARGET_OS_DARWIN )
+	if( gDNSServer_FollowPID )
 	{
-		int port;
-		err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
-		require_noerr( err, exit );
+		long long		value;
 		
-		err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
-		require_noerr( err, exit );
+		err = StringToLongLong( gDNSServer_FollowPID, &value );
+		if( !err && ( value < 0 ) ) err = kValueErr;
+		if( err )
+		{
+			FPrintF( stderr, "Invalid followPID argument \"%s\".\n", gDNSServer_FollowPID );
+			goto exit;
+		}
+		context->followPID = (pid_t) value;
 		
-		err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
-		err = map_socket_noerr_errno( sockV4, err );
+		err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+			DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
 		require_noerr( err, exit );
+		dispatch_resume( context->processMonitor );
+	}
+	else
+	{
+		context->followPID = -1;
 	}
+#endif
 	
-	// Set up IPv6 socket.
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->sigIntSource );
 	
-	if( context->useIPv6 )
+	signal( SIGTERM, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->sigTermSource );
+	
+	if( gDNSServer_Foreground )
 	{
-		err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
-		require_noerr( err, exit );
-		
-		err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
-		require_noerr( err, exit );
-		
-		err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
-		err = map_socket_noerr_errno( sockV6, err );
-		require_noerr( err, exit );
+		LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
 	}
 	
-	// Print prologue.
+	if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) )
+	{
+		ds_ulog( kLogLevelError, "The default TTL %d provided by user is out-of-range. Will use %d instead.\n",
+			gDNSServer_DefaultTTL, kDNSServerDefaultTTL );
+		gDNSServer_DefaultTTL = kDNSServerDefaultTTL;
+	}
 	
-	SSDPDiscoverPrintPrologue( context );
+	err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs,
+		context->loopbackOnly, &context->server );
+	require_noerr( err, exit );
 	
-	// Send mDNS query message.
+	DNSServerStart( context->server );
+	dispatch_main();
 	
-	sendCount = 0;
-	if( IsValidSocket( sockV4 ) )
+exit:
+	ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err );
+	if( context ) DNSServerCmdContextFree( context );
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	DNSServerCmdContextFree
+//===========================================================================================================================
+
+static void	DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+{
+	ForgetCF( &inContext->server );
+	dispatch_source_forget( &inContext->sigIntSource );
+	dispatch_source_forget( &inContext->sigTermSource );
+	dispatch_source_forget( &inContext->processMonitor );
+	free( inContext );
+}
+
+//===========================================================================================================================
+//	DNSServerCmdEventHandler
+//===========================================================================================================================
+
+#if( TARGET_OS_DARWIN )
+static OSStatus	_DNSServerCmdRegisterResolver( void );
+static OSStatus	_DNSServerCmdUnregisterResolver( void );
+#endif
+
+static void	DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext )
+{
+	DNSServerCmdContext * const		context = (DNSServerCmdContext *) inContext;
+#if( TARGET_OS_DARWIN )
+	OSStatus						err;
+#endif
+	
+	if( inType == kDNSServerEvent_Started )
 	{
-		struct sockaddr_in		mcastAddr4;
-		
-		memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
-		SIN_LEN_SET( &mcastAddr4 );
-		mcastAddr4.sin_family		= AF_INET;
-		mcastAddr4.sin_port			= htons( kSSDPPort );
-		mcastAddr4.sin_addr.s_addr	= htonl( 0xEFFFFFFA );	// 239.255.255.250
-		
-		err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
-		require_noerr( err, exit );
-		
-		n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
-			(socklen_t) sizeof( mcastAddr4 ) );
-		err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
+		context->serverStarted = true;
+	#if( TARGET_OS_DARWIN )
+		err = _DNSServerCmdRegisterResolver();
 		if( err )
 		{
-			FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
-			ForgetSocket( &sockV4 );
+			ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err );
+			if( context->loopbackOnly ) exit( 1 );
 		}
 		else
 		{
-			if( gSSDPDiscover_Verbose )
-			{
-				GetTimestampStr( time );
-				FPrintF( stdout, "---\n" );
-				FPrintF( stdout, "Send time:    %s\n",		time );
-				FPrintF( stdout, "Source Port:  %d\n",		SocketToPortNumber( sockV4 ) );
-				FPrintF( stdout, "Destination:  %##a\n",	&mcastAddr4 );
-				FPrintF( stdout, "Message size: %zu\n",		context->header.len );
-				FPrintF( stdout, "HTTP header:\n%1{text}",	context->header.buf, context->header.len );
-			}
-			++sendCount;
+			context->resolverRegistered = true;
 		}
+	#endif
 	}
-	
-	if( IsValidSocket( sockV6 ) )
+	else if( inType == kDNSServerEvent_Stopped )
 	{
-		struct sockaddr_in6		mcastAddr6;
-		
-		memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
-		SIN6_LEN_SET( &mcastAddr6 );
-		mcastAddr6.sin6_family				= AF_INET6;
-		mcastAddr6.sin6_port				= htons( kSSDPPort );
-		mcastAddr6.sin6_addr.s6_addr[  0 ]	= 0xFF;	// SSDP IPv6 link-local multicast address FF02::C
-		mcastAddr6.sin6_addr.s6_addr[  1 ]	= 0x02;
-		mcastAddr6.sin6_addr.s6_addr[ 15 ]	= 0x0C;
-		
-		err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
-		require_noerr( err, exit );
-		
-		n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
-			(socklen_t) sizeof( mcastAddr6 ) );
-		err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
-		if( err )
-		{
-			FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
-			ForgetSocket( &sockV6 );
-		}
-		else
+	#if( TARGET_OS_DARWIN )
+		if( context->resolverRegistered )
 		{
-			if( gSSDPDiscover_Verbose )
+			err = _DNSServerCmdUnregisterResolver();
+			if( err )
 			{
-				GetTimestampStr( time );
-				FPrintF( stdout, "---\n" );
-				FPrintF( stdout, "Send time:    %s\n",		time );
-				FPrintF( stdout, "Source Port:  %d\n",		SocketToPortNumber( sockV6 ) );
-				FPrintF( stdout, "Destination:  %##a\n",	&mcastAddr6 );
-				FPrintF( stdout, "Message size: %zu\n",		context->header.len );
-				FPrintF( stdout, "HTTP header:\n%1{text}",	context->header.buf, context->header.len );
+				ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
+			}
+			else
+			{
+				context->resolverRegistered = false;
 			}
-			++sendCount;
 		}
+		
+		if( !context->calledStop )
+		{
+			ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" );
+			exit( 1 );
+		}
+	#endif
+		DNSServerCmdContextFree( context );
 	}
-	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
-	
-	// If there's no wait period after the send, then exit.
-	
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//	_DNSServerCmdRegisterResolver
+//===========================================================================================================================
+
+static OSStatus	_DNSServerCmdRegisterResolver( void )
+{
+	OSStatus				err;
+	SCDynamicStoreRef		store;
+	CFPropertyListRef		plist		= NULL;
+	CFStringRef				key			= NULL;
+	const uint32_t			loopbackV4	= htonl( INADDR_LOOPBACK );
+	Boolean					success;
+	
+	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+	err = map_scerror( store );
+	require_noerr( err, exit );
+	
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO="
+			"["
+				"%s"
+			"]"
+			"%kO="
+			"["
+				"%.4a"
+				"%.16a"
+			"]"
+		"}",
+		kSCPropNetDNSSupplementalMatchDomains, "d.test.",
+		kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr );
+	require_noerr( err, exit );
+	
+	key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+		CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+	require_action( key, exit, err = kUnknownErr );
+	
+	success = SCDynamicStoreSetValue( store, key, plist );
+	require_action( success, exit, err = kUnknownErr );
+	
+exit:
+	CFReleaseNullSafe( store );
+	CFReleaseNullSafe( plist );
+	CFReleaseNullSafe( key );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DNSServerCmdUnregisterResolver
+//===========================================================================================================================
+
+static OSStatus	_DNSServerCmdUnregisterResolver( void )
+{
+	OSStatus				err;
+	SCDynamicStoreRef		store;
+	CFStringRef				key = NULL;
+	Boolean					success;
+	
+	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+	err = map_scerror( store );
+	require_noerr( err, exit );
+	
+	key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+		CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+	require_action( key, exit, err = kUnknownErr );
+	
+	success = SCDynamicStoreRemoveValue( store, key );
+	require_action( success, exit, err = kUnknownErr );
+	
+exit:
+	CFReleaseNullSafe( store );
+	CFReleaseNullSafe( key );
+	return( err );
+}
+#endif
+
+//===========================================================================================================================
+//	DNSServerCmdSigIntHandler
+//===========================================================================================================================
+
+static void	_DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal );
+
+static void	DNSServerCmdSigIntHandler( void *inContext )
+{
+	_DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGINT );
+}
+
+//===========================================================================================================================
+//	DNSServerCmdSigTermHandler
+//===========================================================================================================================
+
+static void	DNSServerCmdSigTermHandler( void *inContext )
+{
+	_DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGTERM );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//	DNSServerCmdFollowedProcessHandler
+//===========================================================================================================================
+
+static void	DNSServerCmdFollowedProcessHandler( void *inContext )
+{
+	DNSServerCmdContext * const		context = (DNSServerCmdContext *) inContext;
+	
+	if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
+	{
+		_DNSServerCmdExternalExit( context, 0 );
+	}
+}
+#endif
+
+//===========================================================================================================================
+//	_DNSServerCmdExternalExit
+//===========================================================================================================================
+
+#define SignalNumberToString( X ) (		\
+	( (X) == SIGINT )  ? "SIGINT"  :	\
+	( (X) == SIGTERM ) ? "SIGTERM" :	\
+						 "???" )
+
+static void	_DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal )
+{
+	OSStatus		err;
+	
+#if( TARGET_OS_DARWIN )
+	if( inSignal == 0 )
+	{
+		ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
+	}
+	else
+#endif
+	{
+		ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
+	}
+	
+#if( TARGET_OS_DARWIN )
+	if( inContext->resolverRegistered )
+	{
+		err = _DNSServerCmdUnregisterResolver();
+		if( err )
+		{
+			ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
+			goto exit;
+		}
+		inContext->resolverRegistered = false;
+	}
+#endif
+	if( inContext->serverStarted )
+	{
+		DNSServerStop( inContext->server );
+		inContext->calledStop = true;
+	}
+	err = kNoErr;
+	
+exit:
+	exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+//	DNSServerCreate
+//===========================================================================================================================
+
+typedef struct DNSDelayedResponse		DNSDelayedResponse;
+struct DNSDelayedResponse
+{
+	DNSDelayedResponse *		next;
+	sockaddr_ip					clientAddr;
+	uint64_t					targetTicks;
+	uint8_t *					msgPtr;
+	size_t						msgLen;
+};
+
+#define DNSScheduledResponseFree( X )		do { ForgetMem( &(X)->msgPtr ) ; free( X ); } while( 0 )
+
+struct DNSServerPrivate
+{
+	CFRuntimeBase				base;				// CF object base.
+	dispatch_queue_t			queue;				// Queue for DNS server's events.
+	dispatch_source_t			readSourceUDPv4;	// Read source for IPv4 UDP socket.
+	dispatch_source_t			readSourceUDPv6;	// Read source for IPv6 UDP socket.
+	dispatch_source_t			readSourceTCPv4;	// Read source for IPv4 TCP socket.
+	dispatch_source_t			readSourceTCPv6;	// Read source for IPv6 TCP socket.
+	DNSServerEventHandler_f		eventHandler;
+	void *						eventContext;
+	DNSDelayedResponse *		responseList;
+	int							responseDelayMs;
+	dispatch_source_t			responseTimer;
+	Boolean						loopbackOnly;
+	Boolean						stopped;
+};
+
+CF_CLASS_DEFINE( DNSServer );
+
+static OSStatus
+	DNSServerCreate(
+		dispatch_queue_t		inQueue,
+		DNSServerEventHandler_f	inEventHandler,
+		void *					inEventContext,
+		int						inResponseDelayMs,
+		Boolean					inLoopbackOnly,
+		DNSServerRef *			outServer )
+{
+	OSStatus			err;
+	DNSServerRef		obj = NULL;
+	
+	CF_OBJECT_CREATE( DNSServer, obj, err, exit );
+	
+	ReplaceDispatchQueue( &obj->queue, inQueue );
+	obj->eventHandler		= inEventHandler;
+	obj->eventContext		= inEventContext;
+	obj->responseDelayMs	= inResponseDelayMs;
+	if( inLoopbackOnly ) obj->loopbackOnly = true;
+	
+	*outServer = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	CFReleaseNullSafe( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DNSServerFinalize
+//===========================================================================================================================
+
+static void	_DNSServerFinalize( CFTypeRef inObj )
+{
+	DNSServerRef const		me = (DNSServerRef) inObj;
+	
+	check( !me->readSourceUDPv4 );
+	check( !me->readSourceUDPv6 );
+	check( !me->readSourceTCPv4 );
+	check( !me->readSourceTCPv6 );
+	check( !me->responseTimer );
+	dispatch_forget( &me->queue );
+}
+
+//===========================================================================================================================
+//	DNSServerStart
+//===========================================================================================================================
+
+static void	_DNSServerStart( void *inContext );
+static void	_DNSServerUDPReadHandler( void *inContext );
+static void	_DNSServerTCPReadHandler( void *inContext );
+
+static void	DNSServerStart( DNSServerRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _DNSServerStart );
+}
+
+static void	_DNSServerStart( void *inContext )
+{
+	OSStatus				err;
+	DNSServerRef const		me			= (DNSServerRef) inContext;
+	SocketRef				sock		= kInvalidSocketRef;
+	SocketContext *			sockCtx		= NULL;
+	const uint32_t			loopbackV4	= htonl( INADDR_LOOPBACK );
+	
+	// Create IPv4 UDP socket.
+	
+	err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL,
+		kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+	require_noerr( err, exit );
+	
+	// Create read source for IPv4 UDP socket.
+	
+	err = SocketContextCreate( sock, me, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx,
+		&me->readSourceUDPv4 );
+	require_noerr( err, exit );
+	dispatch_resume( me->readSourceUDPv4 );
+	sockCtx = NULL;
+	
+	// Create IPv6 UDP socket.
+	
+	err = _ServerSocketOpenEx2( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &in6addr_loopback : NULL,
+		kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+	require_noerr( err, exit );
+	
+	// Create read source for IPv6 UDP socket.
+	
+	err = SocketContextCreate( sock, me, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx,
+		&me->readSourceUDPv6 );
+	require_noerr( err, exit );
+	dispatch_resume( me->readSourceUDPv6 );
+	sockCtx = NULL;
+	
+	// Create IPv4 TCP socket.
+	
+	err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL,
+		kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+	require_noerr( err, exit );
+	
+	// Create read source for IPv4 TCP socket.
+	
+	err = SocketContextCreate( sock, me, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx,
+		&me->readSourceTCPv4 );
+	require_noerr( err, exit );
+	dispatch_resume( me->readSourceTCPv4 );
+	sockCtx = NULL;
+	
+	// Create IPv6 TCP socket.
+	
+	err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL,
+		kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+	require_noerr( err, exit );
+	
+	// Create read source for IPv6 TCP socket.
+	
+	err = SocketContextCreate( sock, me, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx,
+		&me->readSourceTCPv6 );
+	require_noerr( err, exit );
+	dispatch_resume( me->readSourceTCPv6 );
+	sockCtx = NULL;
+	
+	CFRetain( me );
+	if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, me->eventContext );
+	
+exit:
+	ForgetSocket( &sock );
+	if( sockCtx ) SocketContextRelease( sockCtx );
+	if( err ) DNSServerStop( me );
+	CFRelease( me );
+}
+
+//===========================================================================================================================
+//	DNSServerStop
+//===========================================================================================================================
+
+static void	_DNSServerStop( void *inContext );
+static void	_DNSServerStop2( void *inContext );
+
+static void	DNSServerStop( DNSServerRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _DNSServerStop );
+}
+
+static void	_DNSServerStop( void *inContext )
+{
+	DNSServerRef const			me = (DNSServerRef) inContext;
+	DNSDelayedResponse *		resp;
+	
+	dispatch_source_forget( &me->readSourceUDPv4 );
+	dispatch_source_forget( &me->readSourceUDPv6 );
+	dispatch_source_forget( &me->readSourceTCPv4 );
+	dispatch_source_forget( &me->readSourceTCPv6 );
+	dispatch_source_forget( &me->responseTimer );
+	
+	while( ( resp = me->responseList ) != NULL )
+	{
+		me->responseList = resp->next;
+		DNSScheduledResponseFree( resp );
+	}
+	
+	dispatch_async_f( me->queue, me, _DNSServerStop2 );
+}
+
+static void	_DNSServerStop2( void *inContext )
+{
+	DNSServerRef const		me = (DNSServerRef) inContext;
+	
+	if( !me->stopped )
+	{
+		me->stopped = true;
+		if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, me->eventContext );
+		CFRelease( me );
+	}
+	CFRelease( me );
+}
+
+//===========================================================================================================================
+//	_DNSServerUDPReadHandler
+//===========================================================================================================================
+
+static OSStatus
+	_DNSServerAnswerQuery(
+		const uint8_t *	inQueryPtr,
+		size_t			inQueryLen,
+		Boolean			inForTCP,
+		uint8_t **		outResponsePtr,
+		size_t *		outResponseLen );
+
+#define _DNSServerAnswerQueryForUDP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+	_DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+
+#define _DNSServerAnswerQueryForTCP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+	_DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+
+static void	_DNSServerUDPDelayedSend( void *inContext );
+
+static void	_DNSServerUDPReadHandler( void *inContext )
+{
+	OSStatus					err;
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	DNSServerRef const			me			= (DNSServerRef) sockCtx->userContext;
+	struct timeval				now;
+	ssize_t						n;
+	sockaddr_ip					clientAddr;
+	socklen_t					clientAddrLen;
+	uint8_t *					responsePtr	= NULL;	// malloc'd
+	size_t						responseLen;
+	uint8_t						msg[ 512 ];
+	
+	gettimeofday( &now, NULL );
+	
+	// Receive message.
+	
+	clientAddrLen = (socklen_t) sizeof( clientAddr );
+	n = recvfrom( sockCtx->sock, (char *) msg, sizeof( msg ), 0, &clientAddr.sa, &clientAddrLen );
+	err = map_socket_value_errno( sockCtx->sock, n >= 0, n );
+	require_noerr( err, exit );
+	
+	ds_ulog( kLogLevelInfo, "UDP server received %zd bytes from %##a at %{du:time}.\n", n, &clientAddr, &now );
+	
+	if( n < kDNSHeaderLength )
+	{
+		ds_ulog( kLogLevelInfo, "UDP DNS message is too small (%zd < %d).\n", n, kDNSHeaderLength );
+		goto exit;
+	}
+	
+	ds_ulog( kLogLevelInfo, "UDP received message:\n\n%1{du:dnsmsg}", msg, (size_t) n );
+	
+	// Create response.
+	
+	err = _DNSServerAnswerQueryForUDP( msg, (size_t) n, &responsePtr, &responseLen );
+	require_noerr_quiet( err, exit );
+	
+	// Schedule response.
+	
+	if( me->responseDelayMs > 0 )
+	{
+		DNSDelayedResponse *		resp;
+		DNSDelayedResponse **		ptr;
+		DNSDelayedResponse *		newResp;
+		
+		newResp = (DNSDelayedResponse *) calloc( 1, sizeof( *newResp ) );
+		require_action( newResp, exit, err = kNoMemoryErr );
+		
+		SockAddrCopy( &clientAddr, &newResp->clientAddr );
+		newResp->targetTicks	= UpTicks() + MillisecondsToUpTicks( (uint64_t) me->responseDelayMs );
+		newResp->msgLen			= responseLen;
+		newResp->msgPtr			= responsePtr;
+		responsePtr = NULL;
+		
+		for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next )
+		{
+			if( newResp->targetTicks < resp->targetTicks ) break;
+		}
+		
+		newResp->next = resp;
+		*ptr = newResp;
+		
+		if( me->responseList == newResp )
+		{
+			dispatch_source_forget( &me->responseTimer );
+			
+			err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
+				( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue,
+				_DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
+			require_noerr( err, exit );
+			dispatch_resume( me->responseTimer );
+		}
+	}
+	else
+	{
+		ds_ulog( kLogLevelInfo, "UDP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen );
+		
+		n = sendto( sockCtx->sock, (char *) responsePtr, responseLen, 0, &clientAddr.sa, clientAddrLen );
+		err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) responseLen, n );
+		require_noerr( err, exit );
+	}
+	
+exit:
+	FreeNullSafe( responsePtr );
+	return;
+}
+
+static void	_DNSServerUDPDelayedSend( void *inContext )
+{
+	OSStatus					err;
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	DNSServerRef const			me			= (DNSServerRef) sockCtx->userContext;
+	DNSDelayedResponse *		resp;
+	ssize_t						n;
+	uint64_t					nowTicks;
+	DNSDelayedResponse *		freeList	= NULL;
+	
+	dispatch_source_forget( &me->responseTimer );
+	
+	nowTicks = UpTicks();
+	while( ( resp = me->responseList ) != NULL )
+	{
+		if( resp->targetTicks > nowTicks ) break;
+		me->responseList = resp->next;
+		
+		ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}",
+			resp->msgLen, resp->msgPtr, resp->msgLen );
+		
+		n = sendto( sockCtx->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->clientAddr.sa,
+			SockAddrGetSize( &resp->clientAddr ) );
+		err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) resp->msgLen, n );
+		check_noerr( err );
+		
+		resp->next	= freeList;
+		freeList	= resp;
+		nowTicks = UpTicks();
+	}
+	
+	if( ( resp = me->responseList ) != NULL )
+	{
+		uint64_t		remainingNs;
+		
+		remainingNs = UpTicksToNanoseconds( resp->targetTicks - nowTicks );
+		if( remainingNs > INT64_MAX ) remainingNs = INT64_MAX;
+		
+		err = DispatchTimerCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) remainingNs ), DISPATCH_TIME_FOREVER, 0,
+			me->queue, _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
+		require_noerr( err, exit );
+		dispatch_resume( me->responseTimer );
+	}
+	
+exit:
+	while( ( resp = freeList ) != NULL )
+	{
+		freeList = resp->next;
+		DNSScheduledResponseFree( resp );
+	}
+}
+
+//===========================================================================================================================
+//	_DNSServerAnswerQuery
+//===========================================================================================================================
+
+#define kLabelPrefix_Alias			"alias"
+#define kLabelPrefix_AliasTTL		"alias-ttl"
+#define kLabelPrefix_Count			"count"
+#define kLabelPrefix_TTL			"ttl"
+#define kLabel_IPv4					"ipv4"
+#define kLabel_IPv6					"ipv6"
+
+#define kMaxAliasTTLCount		( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
+
+static OSStatus
+	_DNSServerInitializeResponseMessage(
+		DataBuffer *	inDB,
+		unsigned int	inID,
+		unsigned int	inFlags,
+		const uint8_t *	inQName,
+		unsigned int	inQType,
+		unsigned int	inQClass );
+static OSStatus
+	_DNSServerAnswerQueryDynamically(
+		const uint8_t *	inQName,
+		unsigned int	inQType,
+		unsigned int	inQClass,
+		Boolean			inForTCP,
+		DataBuffer *	inDB );
+
+static OSStatus
+	_DNSServerAnswerQuery(
+		const uint8_t * const	inQueryPtr,
+		const size_t			inQueryLen,
+		Boolean					inForTCP,
+		uint8_t **				outResponsePtr,
+		size_t *				outResponseLen )
+{
+	OSStatus					err;
+	DataBuffer					dataBuf;
+	const uint8_t *				ptr;
+	const uint8_t * const		queryEnd = &inQueryPtr[ inQueryLen ];
+	const DNSHeader *			qhdr;
+	unsigned int				msgID, qflags, qtype, qclass, rflags;
+	uint8_t						qname[ kDomainNameLengthMax ];
+	
+	DataBuffer_Init( &dataBuf, NULL, 0, kDNSMaxTCPMessageSize );
+	
+	require_action_quiet( inQueryLen >= kDNSHeaderLength, exit, err = kUnderrunErr );
+	
+	qhdr	= (const DNSHeader *) inQueryPtr;
+	msgID	= DNSHeaderGetID( qhdr );
+	qflags	= DNSHeaderGetFlags( qhdr );
+	
+	// Minimal checking of the query message's header.
+	
+	if( ( qflags & kDNSHeaderFlag_Response ) ||					// The message must be a query, not a response.
+		( DNSFlagsGetOpCode( qflags ) != kDNSOpCode_Query ) ||	// OPCODE must be QUERY (standard query).
+		( DNSHeaderGetQuestionCount( qhdr ) != 1 ) )			// There should be a single question.
+	{
+		err = kRequestErr;
+		goto exit;
+	}
+	
+	// Get QNAME.
+	
+	ptr = (const uint8_t *) &qhdr[ 1 ];
+	err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, ptr, qname, &ptr );
+	require_noerr( err, exit );
+	
+	// Get QTYPE and QCLASS.
+	
+	require_action_quiet( ( queryEnd - ptr ) >= 4, exit, err = kUnderrunErr );
+	qtype	= DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
+	qclass	= DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
+	ptr += 4;
+	
+	// Create a tentative response message.
+	
+	rflags = kDNSHeaderFlag_Response;
+	if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired;
+	DNSFlagsSetOpCode( rflags, kDNSOpCode_Query );
+	
+	err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
+	require_noerr( err, exit );
+	
+	err = _DNSServerAnswerQueryDynamically( qname, qtype, qclass, inForTCP, &dataBuf );
+	if( err )
+	{
+		DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure );
+		err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
+		require_noerr( err, exit );
+	}
+	
+	err = DataBuffer_Detach( &dataBuf, outResponsePtr, outResponseLen );
+	require_noerr( err, exit );
+	
+exit:
+	DataBuffer_Free( &dataBuf );
+	return( err );
+}
+
+static OSStatus
+	_DNSServerInitializeResponseMessage(
+		DataBuffer *	inDB,
+		unsigned int	inID,
+		unsigned int	inFlags,
+		const uint8_t *	inQName,
+		unsigned int	inQType,
+		unsigned int	inQClass )
+{
+	OSStatus					err;
+	DNSHeader					header;
+	DNSQuestionFixedFields		fields;
+	
+	DataBuffer_Reset( inDB );
+	
+	memset( &header, 0, sizeof( header ) );
+	DNSHeaderSetID( &header, inID );
+	DNSHeaderSetFlags( &header, inFlags );
+	DNSHeaderSetQuestionCount( &header, 1 );
+	
+	err = DataBuffer_Append( inDB, &header, sizeof( header ) );
+	require_noerr( err, exit );
+	
+	err = DataBuffer_Append( inDB, inQName, DomainNameLength( inQName ) );
+	require_noerr( err, exit );
+	
+	DNSQuestionFixedFieldsInit( &fields, inQType, inQClass );
+	err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+	require_noerr( err, exit );
+	
+exit:
+	return( err );
+}
+
+static OSStatus
+	_DNSServerAnswerQueryDynamically(
+		const uint8_t * const	inQName,
+		const unsigned int		inQType,
+		const unsigned int		inQClass,
+		const Boolean			inForTCP,
+		DataBuffer * const		inDB )
+{
+	OSStatus			err;							// General-purpose error variable.
+	const uint8_t *		labelPtr;						// QNAME label pointer.
+	size_t				labelLen;						// QNAME label length.
+	DNSHeader *			hdr;							// Response header pointer.
+	unsigned int		flags;							// Response header flags.
+	unsigned int		rcode;							// Response header response code.
+	unsigned int		answerCount		= 0;			// Number of answers contained in response.
+	int32_t				aliasCount		= -1;			// Arg from "alias" label. Valid values are in [2 .. 2^31 - 1].
+	int					count			= -1;			// First arg from "count" label. Valid values are in [1 .. 255].
+	int					randCount		= -1;			// Second arg from "count" label. Valid values are in [1 .. 255].
+	int32_t				ttl				= -1;			// Arg from "ttl" label. Valid values are in [0 .. 2^31 - 1].
+	uint32_t			aliasTTLs[ kMaxAliasTTLCount ];	// Args from "alias-ttl" label.	Valid values are in [0 .. 2^31 - 1].
+	int					i;								// General-purpose array index.
+	Boolean				useAliasTTLs	= false;		// True if QNAME contained a valid "alias-ttl" label.
+	Boolean				nameExists		= false;		// True if name specified by QNAME exists.
+	Boolean				nameHasA		= false;		// True if name specified by QNAME has an A record.
+	Boolean				nameHasAAAA		= false;		// True if name specified by QNAME has a AAAA record.
+	Boolean				notImplemented	= false;		// True if the kind of the query is not supported.
+	Boolean				truncated		= false;		// True if the response message is truncated.
+	uint8_t				namePtr[ 2 ];					// Name compression pointer.
+	
+	if( inQClass != kDNSServiceClass_IN )
+	{
+		notImplemented = true;
+		goto done;
+	}
+	
+	for( labelPtr = inQName; ( labelLen = *labelPtr ) != 0; labelPtr += ( 1 + labelLen ) )
+	{
+		const char * const		labelStr = (const char *) &labelPtr[ 1 ];
+		const char *			next;
+		long long				arg;
+		int						n;
+		
+		require_action( labelLen <= kDomainNameLengthMax, exit, err = kUnexpectedErr );
+		
+		// Check if the first label is a valid alias TTL sequence label.
+		
+		if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_AliasTTL ) == 0 ) )
+		{
+			const char *			src			= &labelStr[ sizeof_string( kLabelPrefix_AliasTTL ) ];
+			const char * const		end			= &labelStr[ labelLen ];
+			int						argCount	= 0;
+			
+			while( src < end )
+			{
+				n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
+				if( n != 1 ) break;
+				if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break;	// TTL must be >= 0 and <= (2^31 - 1).
+				aliasTTLs[ argCount++ ] = (uint32_t) arg;
+				src = next;
+			}
+			if( ( argCount > 0 ) && ( src == end ) )
+			{
+				aliasCount		= argCount;
+				useAliasTTLs	= true;
+				continue;
+			}
+		}
+		
+		// Check if the first label is a valid alias label.
+		
+		if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Alias ) == 0 ) )
+		{
+			const char *			src = &labelStr[ sizeof_string( kLabelPrefix_Alias ) ];
+			const char * const		end = &labelStr[ labelLen ];
+			
+			if( src == end )
+			{
+				aliasCount = 1;
+				continue;
+			}
+			
+			n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
+			if( ( n == 1 ) && ( next == end ) )
+			{
+				if( ( arg < 2 ) || ( arg > INT32_MAX ) ) break;	// Alias count must be >= 2 and <= (2^31 - 1).
+				aliasCount = (int32_t) arg;
+				continue;
+			}
+		}
+		
+		// Check if the label is a valid count label.
+		
+		if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Count ) == 0  )
+		{
+			const char *			src = &labelStr[ sizeof_string( kLabelPrefix_Count ) ];
+			const char * const		end = &labelStr[ labelLen ];
+			
+			n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
+			if( n == 1 )
+			{
+				if( count > 0 ) break;						// Count cannot be specified more than once.
+				if( ( arg < 1 ) || ( arg > 255 ) ) break;	// Count must be >= 1 and <= 255.
+				count = (int) arg;
+				
+				src = next;
+				if( src < end )
+				{
+					n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
+					if( ( n != 1 ) || ( next != end ) ) break;
+					if( ( arg < count ) || ( arg > 255 ) ) break;	// Rand count must be >= count and <= 255.
+					randCount = (int) arg;
+				}
+				continue;
+			}
+		}
+		
+		// Check if the label is a valid tag label.
+		
+		if( strnicmp_prefix( labelStr, labelLen, "tag-" ) == 0  ) continue;
+		
+		// Check if the label is a valid TTL label.
+		
+		if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_TTL ) == 0  )
+		{
+			const char *			src = &labelStr[ sizeof_string( kLabelPrefix_TTL ) ];
+			const char * const		end = &labelStr[ labelLen ];
+			
+			n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
+			if( ( n == 1 ) && ( next == end ) )
+			{
+				if( ttl >= 0 ) break;							// TTL cannot be specified more than once.
+				if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break;	// TTL must be >= 0 and <= (2^31 - 1).
+				ttl = (int32_t) arg;
+				continue;
+			}
+		}
+		
+		// Check if the label is a valid IPv4 or IPv6 label.
+		
+		if( MemIEqual( labelStr, labelLen, kLabel_IPv4, sizeof_string( kLabel_IPv4 ) ) )
+		{
+			if( nameHasA || nameHasAAAA ) break;	// Valid names have at most one IPv4 or IPv6 label.
+			nameHasA = true;
+			continue;
+		}
+		if( MemIEqual( labelStr, labelLen, kLabel_IPv6, sizeof_string( kLabel_IPv6 ) ) )
+		{
+			if( nameHasA || nameHasAAAA ) break;	// Valid names have at most one IPv4 or IPv6 label.
+			nameHasAAAA = true;
+			continue;
+		}
+		
+		// If the remaining labels are equal to "d.test.", the name exists.
+		
+		if( DomainNameEqual( labelPtr, (const uint8_t *) "\x01" "d" "\x04" "test" ) ) nameExists = true;
+		break;
+	}
+	require_quiet( nameExists, done );
+	
+	// Set default values for count and TTL, if those labels were present.
+	
+	if( count <= 0 ) count = 1;
+	check( ( gDNSServer_DefaultTTL >= 0 ) && ( gDNSServer_DefaultTTL <= INT32_MAX ) );
+	if( ttl < 0 ) ttl = gDNSServer_DefaultTTL;
+	
+	// Names that don't specify v4 or v6 have both A and AAAA records.
+	
+	if( !nameHasA && !nameHasAAAA )
+	{
+		nameHasA	= true;
+		nameHasAAAA	= true;
+	}
+	
+	check( ( count >= 1 ) && ( count <= 255 ) );
+	check( ( randCount <= 0 ) || ( ( randCount >= count ) && ( randCount <= 255 ) ) );
+	
+	if( aliasCount > 0 )
+	{
+		size_t				nameOffset;
+		uint8_t				rdataLabel[ 1 + kDomainLabelLengthMax + 1  ];
+		
+		// If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-<N>". superPtr is a name
+		// compression pointer to the second label of QNAME, i.e., the immediate superdomain name of QNAME. It's used for
+		// the RDATA of CNAME records whose canonical name ends with the superdomain name. It may also be used to construct
+		// CNAME record names, when the offset to the previous CNAME's RDATA doesn't fit in a compression pointer.
+		
+		const uint8_t		superPtr[ 2 ] = { 0xC0, (uint8_t)( kDNSHeaderLength + 1 + inQName[ 0 ] ) };
+		
+		// The name of the first CNAME record is equal to QNAME, so nameOffset is set to offset of QNAME.
+		
+		nameOffset = kDNSHeaderLength;
+		
+		for( i = aliasCount; i >= 1; --i )
+		{
+			size_t						nameLen;
+			size_t						rdataLen;
+			int							j;
+			uint32_t					aliasTTL;
+			uint8_t						nameLabel[ 1 + kDomainLabelLengthMax + 1  ];
+			DNSRecordFixedFields		fields;
+			
+			if( nameOffset <= kDNSCompressionOffsetMax )
+			{
+				namePtr[ 0 ] = (uint8_t)( ( ( nameOffset >> 8 ) & 0x3F ) | 0xC0 );
+				namePtr[ 1 ] = (uint8_t)(     nameOffset        & 0xFF );
+				
+				nameLen = sizeof( namePtr );
+			}
+			else
+			{
+				memcpy( nameLabel, rdataLabel, 1 + rdataLabel[ 0 ] );
+				nameLen = 1 + nameLabel[ 0 ] + sizeof( superPtr );
+			}
+			
+			if( i >= 2 )
+			{
+				char *				dst = (char *) &rdataLabel[ 1 ];
+				char * const		end = (char *) &rdataLabel[ countof( rdataLabel ) ];
+				
+				if( useAliasTTLs )
+				{
+					err = SNPrintF_Add( &dst, end, kLabelPrefix_AliasTTL );
+					require_noerr( err, exit );
+					
+					for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j )
+					{
+						err = SNPrintF_Add( &dst, end, "-%u", aliasTTLs[ j ] );
+						require_noerr( err, exit );
+					}
+				}
+				else
+				{
+					err = SNPrintF_Add( &dst, end, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
+					require_noerr( err, exit );
+				}
+				rdataLabel[ 0 ]	= (uint8_t)( dst - (char *) &rdataLabel[ 1 ] );
+				rdataLen		= 1 + rdataLabel[ 0 ] + sizeof( superPtr );
+			}
+			else
+			{
+				rdataLen = sizeof( superPtr );
+			}
+			
+			if( !inForTCP )
+			{
+				size_t		recordLen = nameLen + sizeof( fields ) + rdataLen;
+				
+				if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
+				{
+					truncated = true;
+					goto done;
+				}
+			}
+			++answerCount;
+			
+			// Set CNAME record's NAME.
+			
+			if( nameOffset <= kDNSCompressionOffsetMax )
+			{
+				err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				err = DataBuffer_Append( inDB, nameLabel, 1 + nameLabel[ 0 ] );
+				require_noerr( err, exit );
+				
+				err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) );
+				require_noerr( err, exit );
+			}
+			
+			// Set CNAME record's TYPE, CLASS, TTL, and RDLENGTH.
+			
+			aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : ( (uint32_t) gDNSServer_DefaultTTL );
+			DNSRecordFixedFieldsInit( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, rdataLen );
+			err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+			require_noerr( err, exit );
+			
+			// Save offset of CNAME record's RDATA, which may be used for the name of the next CNAME record.
+			
+			nameOffset = DataBuffer_GetLen( inDB );
+			
+			// Set CNAME record's RDATA.
+			
+			if( i >= 2 )
+			{
+				err = DataBuffer_Append( inDB, rdataLabel, 1 + rdataLabel[ 0 ] );
+				require_noerr( err, exit );
+			}
+			err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) );
+			require_noerr( err, exit );
+		}
+		
+		namePtr[ 0 ] = superPtr[ 0 ];
+		namePtr[ 1 ] = superPtr[ 1 ];
+	}
+	else
+	{
+		// There are no aliases, so initialize the name compression pointer to point to QNAME.
+		
+		namePtr[ 0 ] = 0xC0;
+		namePtr[ 1 ] = kDNSHeaderLength;
+	}
+	
+	if( ( ( inQType == kDNSServiceType_A    ) && nameHasA    ) ||
+		( ( inQType == kDNSServiceType_AAAA ) && nameHasAAAA ) )
+	{
+		uint8_t *					lsb;				// Pointer to the least significant byte of record data.
+		size_t						recordLen;			// Length of the entire record.
+		size_t						rdataLen;			// Length of record's RDATA.
+		uint8_t						rdata[ 16 ];		// A buffer that's big enough for either A or AAAA RDATA.
+		uint8_t						randItegers[ 255 ];	// Array for random integers in [1 .. 255].
+		DNSRecordFixedFields		fields;
+		
+		if( inQType == kDNSServiceType_A )
+		{
+			rdataLen = 4;
+			WriteBig32( rdata, kTestDNSServerBaseAddrV4 );
+			lsb = &rdata[ 3 ];
+		}
+		else
+		{
+			rdataLen = 16;
+			memcpy( rdata, kTestDNSServerBaseAddrV6, 16 );
+			lsb = &rdata[ 15 ];
+		}
+		
+		if( randCount > 0 )
+		{
+			// Populate the array with all integers between 1 and <randCount>, inclusive.
+			
+			for( i = 0; i < randCount; ++i ) randItegers[ i ] = (uint8_t)( i + 1 );
+			
+			// Create a contiguous subarray starting at index 0 that contains <count> randomly chosen integers between
+			// 1 and <randCount>, inclusive.
+			// Loop invariant 1: Array elements with indexes in [0 .. i - 1] have been randomly chosen.
+			// Loop invariant 2: Array elements with indexes in [i .. randCount - 1] are candidates for being chosen.
+			
+			for( i = 0; i < count; ++i )
+			{
+				uint8_t		tmp;
+				int			j;
+				
+				j = (int) RandomRange( i, randCount - 1 );
+				if( i != j )
+				{
+					tmp = randItegers[ i ];
+					randItegers[ i ] = randItegers[ j ];
+					randItegers[ j ] = tmp;
+				}
+			}
+		}
+		
+		recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+		for( i = 0; i < count; ++i )
+		{
+			if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+			{
+				truncated = true;
+				goto done;
+			}
+			++answerCount;
+			
+			// Set record NAME.
+			
+			err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+			require_noerr( err, exit );
+			
+			// Set record TYPE, CLASS, TTL, and RDLENGTH.
+			
+			DNSRecordFixedFieldsInit( &fields, inQType, kDNSServiceClass_IN, ttl, rdataLen );
+			err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+			require_noerr( err, exit );
+			
+			// Set record RDATA.
+			
+			*lsb = ( randCount > 0 ) ? randItegers[ i ] : ( *lsb + 1 );
+			
+			err = DataBuffer_Append( inDB, rdata, rdataLen );
+			require_noerr( err, exit );
+		}
+	}
+	
+done:
+	hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
+	flags = DNSHeaderGetFlags( hdr );
+	if( truncated ) flags |= kDNSHeaderFlag_Truncation;
+	if( notImplemented )
+	{
+		rcode = kDNSRCode_NotImplemented;
+	}
+	else
+	{
+		flags |= kDNSHeaderFlag_AuthAnswer;
+		rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
+	}
+	DNSFlagsSetRCode( flags, rcode );
+	DNSHeaderSetFlags( hdr, flags );
+	DNSHeaderSetAnswerCount( hdr, answerCount );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DNSServerTCPReadHandler
+//===========================================================================================================================
+
+typedef struct
+{
+	sockaddr_ip				clientAddr;		// Client's address.
+	dispatch_source_t		readSource;		// Dispatch read source for client socket.
+	dispatch_source_t		writeSource;	// Dispatch write source for client socket.
+	size_t					offset;			// Offset into receive buffer.
+	void *					msgPtr;			// Pointer to dynamically allocated message buffer.
+	size_t					msgLen;			// Length of message buffer.
+	Boolean					readSuspended;	// True if the read source is currently suspended.
+	Boolean					writeSuspended;	// True if the write source is currently suspended.
+	Boolean					receivedLength;	// True if receiving DNS message as opposed to the message length.
+	uint8_t					lenBuf[ 2 ];	// Buffer for two-octet message length field.
+	iovec_t					iov[ 2 ];		// IO vector for writing response message.
+	iovec_t *				iovPtr;			// Vector pointer for SocketWriteData().
+	int						iovCount;		// Vector count for SocketWriteData().
+	
+}	TCPConnectionContext;
+
+static void	TCPConnectionStop( TCPConnectionContext *inContext );
+static void	TCPConnectionContextFree( TCPConnectionContext *inContext );
+static void	TCPConnectionReadHandler( void *inContext );
+static void	TCPConnectionWriteHandler( void *inContext );
+
+#define	TCPConnectionForget( X )		ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
+
+static void	_DNSServerTCPReadHandler( void *inContext )
+{
+	OSStatus					err;
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	TCPConnectionContext *		connection;
+	socklen_t					clientAddrLen;
+	SocketRef					newSock		= kInvalidSocketRef;
+	SocketContext *				newSockCtx	= NULL;
+	
+	connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
+	require_action( connection, exit, err = kNoMemoryErr );
+	
+	clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
+	newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
+	err = map_socket_creation_errno( newSock );
+	require_noerr( err, exit );
+	
+	err = SocketContextCreate( newSock, connection, &newSockCtx );
+	require_noerr( err, exit );
+	newSock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( newSockCtx->sock, NULL, TCPConnectionReadHandler, SocketContextCancelHandler,
+		newSockCtx, &connection->readSource );
+	require_noerr( err, exit );
+	SocketContextRetain( newSockCtx );
+	dispatch_resume( connection->readSource );
+	
+	err = DispatchWriteSourceCreate( newSockCtx->sock, NULL, TCPConnectionWriteHandler, SocketContextCancelHandler,
+		newSockCtx, &connection->writeSource );
+	require_noerr( err, exit );
+	SocketContextRetain( newSockCtx );
+	connection->writeSuspended = true;
+	connection = NULL;
+	
+exit:
+	ForgetSocket( &newSock );
+	SocketContextRelease( newSockCtx );
+	TCPConnectionForget( &connection );
+}
+
+//===========================================================================================================================
+//	TCPConnectionStop
+//===========================================================================================================================
+
+static void	TCPConnectionStop( TCPConnectionContext *inContext )
+{
+	dispatch_source_forget_ex( &inContext->readSource, &inContext->readSuspended );
+	dispatch_source_forget_ex( &inContext->writeSource, &inContext->writeSuspended );
+}
+
+//===========================================================================================================================
+//	TCPConnectionContextFree
+//===========================================================================================================================
+
+static void	TCPConnectionContextFree( TCPConnectionContext *inContext )
+{
+	check( !inContext->readSource );
+	check( !inContext->writeSource );
+	ForgetMem( &inContext->msgPtr );
+	free( inContext );
+}
+
+//===========================================================================================================================
+//	TCPConnectionReadHandler
+//===========================================================================================================================
+
+static void	TCPConnectionReadHandler( void *inContext )
+{
+	OSStatus					err;
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	TCPConnectionContext *		connection	= (TCPConnectionContext *) sockCtx->userContext;
+	struct timeval				now;
+	uint8_t *					responsePtr	= NULL;	// malloc'd
+	size_t						responseLen;
+	
+	// Receive message length.
+	
+	if( !connection->receivedLength )
+	{
+		err = SocketReadData( sockCtx->sock, connection->lenBuf, sizeof( connection->lenBuf ), &connection->offset );
+		if( err == EWOULDBLOCK ) goto exit;
+		require_noerr( err, exit );
+		
+		connection->offset = 0;
+		connection->msgLen = ReadBig16( connection->lenBuf );
+		connection->msgPtr = malloc( connection->msgLen );
+		require_action( connection->msgPtr, exit, err = kNoMemoryErr );
+		connection->receivedLength = true;
+	}
+	
+	// Receive message.
+	
+	err = SocketReadData( sockCtx->sock, connection->msgPtr, connection->msgLen, &connection->offset );
+	if( err == EWOULDBLOCK ) goto exit;
+	require_noerr( err, exit );
+	
+	gettimeofday( &now, NULL );
+	dispatch_suspend( connection->readSource );
+	connection->readSuspended = true;
+	
+	ds_ulog( kLogLevelInfo, "TCP server received %zu bytes from %##a at %{du:time}.\n",
+		connection->msgLen, &connection->clientAddr, &now );
+	
+	if( connection->msgLen < kDNSHeaderLength )
+	{
+		ds_ulog( kLogLevelInfo, "TCP DNS message is too small (%zu < %d).\n", connection->msgLen, kDNSHeaderLength );
+		goto exit;
+	}
+	
+	ds_ulog( kLogLevelInfo, "TCP received message:\n\n%1{du:dnsmsg}", connection->msgPtr, connection->msgLen );
+	
+	// Create response.
+	
+	err = _DNSServerAnswerQueryForTCP( connection->msgPtr, connection->msgLen, &responsePtr, &responseLen );
+	require_noerr_quiet( err, exit );
+	
+	// Send response.
+	
+	ds_ulog( kLogLevelInfo, "TCP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen );
+	
+	free( connection->msgPtr );
+	connection->msgPtr = responsePtr;
+	connection->msgLen = responseLen;
+	responsePtr = NULL;
+	
+	check( connection->msgLen <= UINT16_MAX );
+	WriteBig16( connection->lenBuf, connection->msgLen );
+	connection->iov[ 0 ].iov_base	= connection->lenBuf;
+	connection->iov[ 0 ].iov_len	= sizeof( connection->lenBuf );
+	connection->iov[ 1 ].iov_base	= connection->msgPtr;
+	connection->iov[ 1 ].iov_len	= connection->msgLen;
+	
+	connection->iovPtr		= connection->iov;
+	connection->iovCount	= 2;
+	
+	check( connection->writeSuspended );
+	dispatch_resume( connection->writeSource );
+	connection->writeSuspended = false;
+	
+exit:
+	FreeNullSafe( responsePtr );
+	if( err && ( err != EWOULDBLOCK ) ) TCPConnectionForget( &connection );
+}
+
+//===========================================================================================================================
+//	TCPConnectionWriteHandler
+//===========================================================================================================================
+
+static void	TCPConnectionWriteHandler( void *inContext )
+{
+	OSStatus					err;
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	TCPConnectionContext *		connection	= (TCPConnectionContext *) sockCtx->userContext;
+	
+	err = SocketWriteData( sockCtx->sock, &connection->iovPtr, &connection->iovCount );
+	if( err == EWOULDBLOCK ) goto exit;
+	check_noerr( err );
+	
+	TCPConnectionForget( &connection );
+	
+exit:
+	return;
+}
+
+//===========================================================================================================================
+//	GAIPerfCmd
+//===========================================================================================================================
+
+#define kGAIPerfStandardTTL		( 1 * kSecondsPerHour )
+
+typedef struct GAITesterPrivate *		GAITesterRef;
+typedef struct GAITestCase				GAITestCase;
+
+typedef uint32_t		GAITesterEventType;
+#define kGAITesterEvent_Started		1
+#define kGAITesterEvent_Stopped		2
+
+typedef struct
+{
+	const char *		name;				// Domain name that was resolved.
+	int64_t				connectionTimeUs;	// Time in microseconds that it took to create a DNS-SD connection.
+	int64_t				firstTimeUs;		// Time in microseconds that it took to get the first address result.
+	int64_t				timeUs;				// Time in microseconds that it took to get all expected address results.
+	
+}	GAITestItemResult;
+
+typedef void ( *GAITesterEventHandler_f )( GAITesterEventType inType, void *inContext );
+typedef void
+	( *GAITesterResultsHandler_f )(
+		const char *				inCaseTitle,
+		MicroTime64					inCaseStartTime,
+		MicroTime64					inCaseEndTime,
+		const GAITestItemResult *	inResults,
+		size_t						inResultCount,
+		size_t						inItemCount,
+		void *						inContext );
+
+typedef unsigned int		GAITestAddrType;
+#define kGAITestAddrType_None		0
+#define kGAITestAddrType_IPv4		( 1U << 0 )
+#define kGAITestAddrType_IPv6		( 1U << 1 )
+#define kGAITestAddrType_Both		( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 )
+
+#define GAITestAddrTypeIsValid( X ) \
+	( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) )
+
+typedef enum
+{
+	kGAIPerfOutputFormat_JSON	= 1,
+	kGAIPerfOutputFormat_XML	= 2,
+	kGAIPerfOutputFormat_Binary	= 3
+	
+}	GAIPerfOutputFormatType;
+
+typedef struct
+{
+	GAITesterRef				tester;				// GAI tester object.
+	CFMutableArrayRef			caseResults;		// Array of test case results.
+	char *						outputFilePath;		// File to write test results to. If NULL, then write to stdout.
+	GAIPerfOutputFormatType		outputFormat;		// Format of test results output.
+	unsigned int				callDelayMs;		// Amount of time to wait before calling DNSServiceGetAddrInfo().
+	unsigned int				serverDelayMs;		// Amount of additional time to have server delay its responses.
+	unsigned int				defaultIterCount;	// Default test case iteration count.
+	dispatch_source_t			sigIntSource;		// Dispatch source for SIGINT.
+	dispatch_source_t			sigTermSource;		// Dispatch source for SIGTERM.
+	Boolean						gotSignal;			// True if SIGINT or SIGTERM was caught.
+	Boolean						testerStarted;		// True if the GAI tester was started.
+	Boolean						appendNewLine;		// True if a newline character should be appended to JSON output.
+	
+}	GAIPerfContext;
+
+static void		GAIPerfContextFree( GAIPerfContext *inContext );
+static OSStatus	GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
+static OSStatus	GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
+static void		GAIPerfEventHandler( GAITesterEventType inType, void *inContext );
+static void
+	GAIPerfResultsHandler(
+		const char *				inCaseTitle,
+		MicroTime64					inCaseStartTime,
+		MicroTime64					inCaseEndTime,
+		const GAITestItemResult *	inResults,
+		size_t						inResultCount,
+		size_t						inItemCount,
+		void *						inContext );
+static void		GAIPerfSignalHandler( void *inContext );
+
+CFTypeID		GAITesterGetTypeID( void );
+static OSStatus
+	GAITesterCreate(
+		dispatch_queue_t	inQueue,
+		int					inCallDelayMs,
+		int					inServerDelayMs,
+		int					inServerDefaultTTL,
+		GAITesterRef *		outTester );
+static void		GAITesterStart( GAITesterRef inTester );
+static void		GAITesterStop( GAITesterRef inTester );
+static void		GAITesterAddCase( GAITesterRef inTester, GAITestCase *inCase );
+static void
+	GAITesterSetEventHandler(
+		GAITesterRef			inTester,
+		GAITesterEventHandler_f	inEventHandler,
+		void *					inEventContext );
+static void
+	GAITesterSetResultsHandler(
+		GAITesterRef				inTester,
+		GAITesterResultsHandler_f	inResultsHandler,
+		void *						inResultsContext );
+
+static OSStatus	GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet );
+static void		GAITestCaseFree( GAITestCase *inCase );
+static OSStatus
+	GAITestCaseAddItem(
+		GAITestCase *	inCase,
+		unsigned int	inAliasCount,
+		unsigned int	inAddressCount,
+		int				inTTL,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		unsigned int	inItemCount );
+static OSStatus	GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount );
+
+#define kGAIPerfTestSuite_Basic			1
+#define kGAIPerfTestSuite_Advanced		2
+
+static void	GAIPerfCmd( void )
+{
+	OSStatus				err;
+	GAIPerfContext *		context;
+	int						suiteValue;
+	
+	context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( context->caseResults, exit, err = kNoMemoryErr );
+	
+	context->outputFormat = (GAIPerfOutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
+		"json",		kGAIPerfOutputFormat_JSON,
+		"xml",		kGAIPerfOutputFormat_XML,
+		"binary",	kGAIPerfOutputFormat_Binary,
+		NULL );
+	require_noerr_quiet( err, exit );
+	
+	context->callDelayMs		= ( gGAIPerf_CallDelayMs >= 0 ) ? (unsigned int) gGAIPerf_CallDelayMs : 0;
+	context->serverDelayMs		= ( gGAIPerf_ServerDelayMs >= 0 ) ? (unsigned int) gGAIPerf_ServerDelayMs : 0;
+	context->defaultIterCount	= ( gGAIPerf_DefaultIterCount >= 0 ) ? (unsigned int) gGAIPerf_DefaultIterCount : 0;
+	context->appendNewLine		= gGAIPerf_OutputAppendNewLine ? true : false;
+	
+	if( gGAIPerf_OutputFilePath )
+	{
+		context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
+		require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+	}
+	
+	err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
+		kGAIPerfStandardTTL, &context->tester );
+	require_noerr( err, exit );
+	
+	check( gGAIPerf_TestSuite );
+	suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err,
+		"basic",	kGAIPerfTestSuite_Basic,
+		"advanced",	kGAIPerfTestSuite_Advanced,
+		NULL );
+	require_noerr_quiet( err, exit );
+	
+	switch( suiteValue )
+	{
+		case kGAIPerfTestSuite_Basic:
+			err = GAIPerfAddBasicTestCases( context );
+			require_noerr( err, exit );
+			break;
+		
+		case kGAIPerfTestSuite_Advanced:
+			err = GAIPerfAddAdvancedTestCases( context );
+			require_noerr( err, exit );
+			break;
+		
+		default:
+			err = kValueErr;
+			break;
+	}
+	
+	GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context );
+	GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
+	
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->sigIntSource );
+	
+	signal( SIGTERM, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->sigTermSource );
+	
+	GAITesterStart( context->tester );
+	dispatch_main();
+	
+exit:
+	if( context ) GAIPerfContextFree( context );
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	GAIPerfContextFree
+//===========================================================================================================================
+
+static void	GAIPerfContextFree( GAIPerfContext *inContext )
+{
+	ForgetCF( &inContext->tester );
+	ForgetCF( &inContext->caseResults );
+	ForgetMem( &inContext->outputFilePath );
+	dispatch_source_forget( &inContext->sigIntSource );
+	dispatch_source_forget( &inContext->sigTermSource );
+	free( inContext );
+}
+
+//===========================================================================================================================
+//	GAIPerfAddAdvancedTestCases
+//===========================================================================================================================
+
+#define kTestCaseTitleBufferSize		128
+
+static void
+	_GAIPerfWriteTestCaseTitle(
+		char			inBuffer[ kTestCaseTitleBufferSize ],
+		unsigned int	inCNAMERecordCount,
+		unsigned int	inARecordCount,
+		unsigned int	inAAAARecordCount,
+		GAITestAddrType	inRequested,
+		unsigned int	inIterationCount,
+		Boolean			inIterationsAreUnique );
+static void
+	_GAIPerfWriteLocalHostTestCaseTitle(
+		char			inBuffer[ kTestCaseTitleBufferSize ],
+		GAITestAddrType	inRequested,
+		unsigned int	inIterationCount );
+static unsigned int
+	_GAIPerfTimeLimitMs(
+		unsigned int	inCallDelayMs,
+		unsigned int	inServerDelayMs,
+		unsigned int	inIterationCount );
+
+#define kGAIPerfAdvancedTestSuite_MaxAliasCount		4
+#define kGAIPerfAdvancedTestSuite_MaxAddrCount		8
+
+static OSStatus	GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
+{
+	OSStatus			err;
+	unsigned int		aliasCount, addressCount, timeLimitMs, i;
+	GAITestCase *		testCase = NULL;
+	char				title[ kTestCaseTitleBufferSize ];
+	
+	aliasCount = 0;
+	while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+	{
+		for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+		{
+			// Add a test case to resolve a domain name with
+			//
+			//     <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+			//
+			// to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which
+			// requires server queries.
+			
+			_GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+				inContext->defaultIterCount, true );
+			
+			timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs,
+				inContext->defaultIterCount );
+			err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+			require_noerr( err, exit );
+			
+			for( i = 0; i < inContext->defaultIterCount; ++i )
+			{
+				err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+					kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+				require_noerr( err, exit );
+			}
+			
+			GAITesterAddCase( inContext->tester, testCase );
+			testCase = NULL;
+			
+			// Add a test case to resolve a domain name with
+			//
+			//     <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+			//
+			// to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server
+			// query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should
+			// ideally require no server queries, i.e., the results should come from the cache.
+			
+			_GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+				inContext->defaultIterCount, false );
+			
+			timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
+				_GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+			err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+			require_noerr( err, exit );
+			
+			err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+				kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
+			require_noerr( err, exit );
+			
+			GAITesterAddCase( inContext->tester, testCase );
+			testCase = NULL;
+		}
+		
+		if( aliasCount == 0 )	aliasCount  = 1;
+		else					aliasCount *= 2;
+	}
+	
+	// Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
+	
+	_GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
+	
+	timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+	err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+	require_noerr( err, exit );
+	
+	err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
+	require_noerr( err, exit );
+	
+	GAITesterAddCase( inContext->tester, testCase );
+	testCase = NULL;
+	
+exit:
+	if( testCase ) GAITestCaseFree( testCase );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_GAIPerfWriteTestCaseTitle
+//===========================================================================================================================
+
+#define GAITestAddrTypeToRequestKeyValue( X ) (				\
+	( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6"	:	\
+	( (X) == kGAITestAddrType_IPv4 ) ? "ipv4"			:	\
+	( (X) == kGAITestAddrType_IPv6 ) ? "ipv6"			:	\
+									   "" )
+
+static void
+	_GAIPerfWriteTestCaseTitle(
+		char			inBuffer[ kTestCaseTitleBufferSize ],
+		unsigned int	inCNAMERecordCount,
+		unsigned int	inARecordCount,
+		unsigned int	inAAAARecordCount,
+		GAITestAddrType	inRequested,
+		unsigned int	inIterationCount,
+		Boolean			inIterationsAreUnique )
+{
+	SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
+		inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
+		inIterationCount, inIterationsAreUnique, ",unique" );
+}
+
+//===========================================================================================================================
+//	_GAIPerfWriteLocalHostTestCaseTitle
+//===========================================================================================================================
+
+static void
+	_GAIPerfWriteLocalHostTestCaseTitle(
+		char			inBuffer[ kTestCaseTitleBufferSize ],
+		GAITestAddrType	inRequested,
+		unsigned int	inIterationCount )
+{
+	SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
+		GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
+}
+
+//===========================================================================================================================
+//	_GAIPerfTimeLimitMs
+//===========================================================================================================================
+
+static unsigned int
+	_GAIPerfTimeLimitMs(
+		unsigned int	inCallDelayMs,
+		unsigned int	inServerDelayMs,
+		unsigned int	inIterationCount )
+{
+	// Allow each iteration 20 ms to complete (in addition to the call and server delay times).
+	
+	return( ( inCallDelayMs + inServerDelayMs + 20 ) * inIterationCount );
+}
+
+//===========================================================================================================================
+//	GAIPerfAddBasicTestCases
+//===========================================================================================================================
+
+#define kGAIPerfBasicTestSuite_AliasCount		2
+#define kGAIPerfBasicTestSuite_AddrCount		4
+
+static OSStatus	GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
+{
+	OSStatus			err;
+	GAITestCase *		testCase = NULL;
+	char				title[ kTestCaseTitleBufferSize ];
+	unsigned int		timeLimitMs, i;
+	
+	// Test Case #1:
+	// Resolve a domain name with
+	//
+	//     2 CNAME records, 4 A records, and 4 AAAA records
+	//
+	// to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server
+	// queries.
+	
+	_GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+		kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+		inContext->defaultIterCount, true );
+	
+	timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount );
+	err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+	require_noerr( err, exit );
+	
+	for( i = 0; i < inContext->defaultIterCount; ++i )
+	{
+		err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+			kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+		require_noerr( err, exit );
+	}
+	
+	GAITesterAddCase( inContext->tester, testCase );
+	testCase = NULL;
+	
+	// Test Case #2:
+	// Resolve a domain name with
+	//
+	//     2 CNAME records, 4 A records, and 4 AAAA records
+	//
+	// to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which
+	// requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary
+	// iteration, which should ideally require no additional server queries, i.e., the results should come from the cache.
+	
+	_GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+		kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+		inContext->defaultIterCount, false );
+	
+	timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
+		_GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+	err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+	require_noerr( err, exit );
+	
+	err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+		kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
+	require_noerr( err, exit );
+	
+	GAITesterAddCase( inContext->tester, testCase );
+	testCase = NULL;
+	
+	// Test Case #3:
+	// Each iteration resolves localhost to its IPv4 and IPv6 addresses.
+	
+	_GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
+	
+	timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+	err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+	require_noerr( err, exit );
+	
+	err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
+	require_noerr( err, exit );
+	
+	GAITesterAddCase( inContext->tester, testCase );
+	testCase = NULL;
+	
+exit:
+	if( testCase ) GAITestCaseFree( testCase );
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAIPerfEventHandler
+//===========================================================================================================================
+
+static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void	GAIPerfEventHandler( GAITesterEventType inType, void *inContext )
+{
+	GAIPerfContext * const		context	= (GAIPerfContext *) inContext;
+	
+	if( inType == kGAITesterEvent_Started )
+	{
+		context->testerStarted = true;
+	}
+	else if( inType == kGAITesterEvent_Stopped )
+	{
+		if( context->gotSignal ) exit( 1 );
+		_GAIPerfOutputResultsAndExit( context );
+	}
+}
+
+//===========================================================================================================================
+//	_GAIPerfOutputResultsAndExit
+//===========================================================================================================================
+
+#define kGAIPerfResultsKey_TestCases		CFSTR( "testCases" )
+#define kGAIPerfResultsKey_Info				CFSTR( "info" )
+
+#define kGAIPerfInfoKey_CallDelay		CFSTR( "callDelayMs" )
+#define kGAIPerfInfoKey_ServerDelay		CFSTR( "serverDelayMs" )
+
+static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext )
+{
+	OSStatus				err;
+	CFPropertyListRef		plist	= NULL;
+	CFDataRef				results	= NULL;
+	FILE *					file	= NULL;
+	
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO=%O"
+			"%kO="
+			"{"
+				"%kO=%lli"
+				"%kO=%lli"
+			"}"
+		"}",
+		kGAIPerfResultsKey_TestCases,	inContext->caseResults,
+		kGAIPerfResultsKey_Info,
+		kGAIPerfInfoKey_CallDelay,		(int64_t) inContext->callDelayMs,
+		kGAIPerfInfoKey_ServerDelay,	(int64_t) inContext->serverDelayMs );
+	require_noerr( err, exit );
+	
+	// Convert results to a specific format.
+	
+	switch( inContext->outputFormat )
+	{
+		case kGAIPerfOutputFormat_JSON:
+			results = CFCreateJSONData( plist, kJSONFlags_None, NULL );
+			require_action( results, exit, err = kUnknownErr );
+			break;
+		
+		case kGAIPerfOutputFormat_XML:
+			results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL );
+			require_action( results, exit, err = kUnknownErr );
+			break;
+		
+		case kGAIPerfOutputFormat_Binary:
+			results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
+			require_action( results, exit, err = kUnknownErr );
+			break;
+		
+		default:
+			err = kValueErr;
+			goto exit;
+	}
+	
+	// Write formatted results to file or stdout.
+	
+	if( inContext->outputFilePath )
+	{
+		file = fopen( inContext->outputFilePath, "wb" );
+		err = map_global_value_errno( file, file );
+		require_noerr( err, exit );
+	}
+	else
+	{
+		file = stdout;
+	}
+	
+	err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
+	require_noerr( err, exit );
+	
+	// Write a trailing newline for JSON-formatted results if requested.
+	
+	if( ( inContext->outputFormat == kGAIPerfOutputFormat_JSON ) && inContext->appendNewLine )
+	{
+		err = WriteANSIFile( file, "\n", 1 );
+		require_noerr( err, exit );
+	}
+	
+exit:
+	CFReleaseNullSafe( plist );
+	CFReleaseNullSafe( results );
+	if( file && ( file != stdout ) ) fclose( file );
+	GAIPerfContextFree( inContext );
+	exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+//	GAIPerfResultsHandler
+//===========================================================================================================================
+
+// Keys for test case dictionary
+
+#define kGAIPerfTestCaseKey_Title				CFSTR( "title" )
+#define kGAIPerfTestCaseKey_StartTime			CFSTR( "startTimeUs" )
+#define kGAIPerfTestCaseKey_EndTime				CFSTR( "endTimeUs" )
+#define kGAIPerfTestCaseKey_Results				CFSTR( "results" )
+#define kGAIPerfTestCaseKey_FirstStats			CFSTR( "firstStats" )
+#define kGAIPerfTestCaseKey_ConnectionStats		CFSTR( "connectionStats" )
+#define kGAIPerfTestCaseKey_Stats				CFSTR( "stats" )
+#define kGAIPerfTestCaseKey_TimedOut			CFSTR( "timedOut" )
+
+// Keys for test case results array entry dictionaries
+
+#define kGAIPerfTestCaseResultKey_Name					CFSTR( "name" )
+#define kGAIPerfTestCaseResultKey_ConnectionTime		CFSTR( "connectionTimeUs" )
+#define kGAIPerfTestCaseResultKey_FirstTime				CFSTR( "firstTimeUs" )
+#define kGAIPerfTestCaseResultKey_Time					CFSTR( "timeUs" )
+
+// Keys for test case stats dictionaries
+
+#define kGAIPerfTestCaseStatsKey_Count		CFSTR( "count" )
+#define kGAIPerfTestCaseStatsKey_Min		CFSTR( "min" )
+#define kGAIPerfTestCaseStatsKey_Max		CFSTR( "max" )
+#define kGAIPerfTestCaseStatsKey_Mean		CFSTR( "mean" )
+#define kGAIPerfTestCaseStatsKey_StdDev		CFSTR( "sd" )
+
+typedef struct
+{
+	double		min;
+	double		max;
+	double		mean;
+	double		stdDev;
+	
+}	GAIPerfStats;
+
+#define GAIPerfStatsInit( X ) \
+	do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
+
+static void
+	GAIPerfResultsHandler(
+		const char *				inCaseTitle,
+		MicroTime64					inCaseStartTime,
+		MicroTime64					inCaseEndTime,
+		const GAITestItemResult *	inResults,
+		size_t						inResultCount,
+		size_t						inItemCount,
+		void *						inContext )
+{
+	OSStatus					err;
+	GAIPerfContext * const		context	= (GAIPerfContext *) inContext;
+	int							namesAreDynamic, namesAreUnique;
+	const char *				ptr;
+	size_t						count, startIndex;
+	CFMutableArrayRef			results	= NULL;
+	GAIPerfStats				stats, firstStats, connStats;
+	double						sum, firstSum, connSum, value, diff;
+	size_t						keyValueLen, i;
+	char						keyValue[ 16 ];	// Size must be at least strlen( "name=dynamic" ) + 1 bytes.
+	
+	// If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value
+	// pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with the
+	// domain name's CNAME, A, and AAAA records.
+	
+	namesAreDynamic	= false;
+	namesAreUnique	= false;
+	ptr = inCaseTitle;
+	while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
+	{
+		if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
+		{
+			namesAreDynamic = true;
+		}
+		else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+		{
+			namesAreUnique = true;
+		}
+		if( namesAreDynamic && namesAreUnique ) break;
+	}
+	
+	if( namesAreDynamic && !namesAreUnique && ( inItemCount > 0 ) )
+	{
+		count		= ( inResultCount > 0 ) ? ( inResultCount - 1 ) : 0;
+		startIndex	= 1;
+	}
+	else
+	{
+		count		= inResultCount;
+		startIndex	= 0;
+	}
+	
+	results = CFArrayCreateMutable( NULL, (CFIndex) count, &kCFTypeArrayCallBacks );
+	require_action( results, exit, err = kNoMemoryErr );
+	
+	GAIPerfStatsInit( &stats );
+	GAIPerfStatsInit( &firstStats );
+	GAIPerfStatsInit( &connStats );
+	
+	sum			= 0.0;
+	firstSum	= 0.0;
+	connSum		= 0.0;
+	for( i = startIndex; i < count; ++i )
+	{
+		value = (double) inResults[ i ].timeUs;
+		if( value < stats.min ) stats.min = value;
+		if( value > stats.max ) stats.max = value;
+		sum += value;
+		
+		value = (double) inResults[ i ].firstTimeUs;
+		if( value < firstStats.min ) firstStats.min = value;
+		if( value > firstStats.max ) firstStats.max = value;
+		firstSum += value;
+		
+		value = (double) inResults[ i ].connectionTimeUs;
+		if( value < connStats.min ) connStats.min = value;
+		if( value > connStats.max ) connStats.max = value;
+		connSum += value;
+		
+		err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
+			"{"
+				"%kO=%s"
+				"%kO=%lli"
+				"%kO=%lli"
+				"%kO=%lli"
+			"}",
+			kGAIPerfTestCaseResultKey_Name,				inResults[ i ].name,
+			kGAIPerfTestCaseResultKey_ConnectionTime,	inResults[ i ].connectionTimeUs,
+			kGAIPerfTestCaseResultKey_FirstTime,		inResults[ i ].firstTimeUs,
+			kGAIPerfTestCaseResultKey_Time,				inResults[ i ].timeUs );
+		require_noerr( err, exit );
+	}
+	
+	if( count > 0 )
+	{
+		stats.mean		= sum      / count;
+		firstStats.mean	= firstSum / count;
+		connStats.mean	= connSum  / count;
+		
+		sum			= 0.0;
+		firstSum	= 0.0;
+		connSum		= 0.0;
+		for( i = startIndex; i < count; ++i )
+		{
+			diff		 = stats.mean - (double) inResults[ i ].timeUs;
+			sum			+= ( diff * diff );
+			
+			diff		 = firstStats.mean - (double) inResults[ i ].firstTimeUs;
+			firstSum	+= ( diff * diff );
+			
+			diff		 = connStats.mean - (double) inResults[ i ].connectionTimeUs;
+			connSum		+= ( diff * diff );
+		}
+		stats.stdDev		= sqrt( sum      / count );
+		firstStats.stdDev	= sqrt( firstSum / count );
+		connStats.stdDev	= sqrt( connSum  / count );
+	}
+	
+	err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->caseResults,
+		"{"
+			"%kO=%s"
+			"%kO=%lli"
+			"%kO=%lli"
+			"%kO=%O"
+			"%kO="
+			"{"
+				"%kO=%lli"
+				"%kO=%f"
+				"%kO=%f"
+				"%kO=%f"
+				"%kO=%f"
+			"}"
+			"%kO="
+			"{"
+				"%kO=%lli"
+				"%kO=%f"
+				"%kO=%f"
+				"%kO=%f"
+				"%kO=%f"
+			"}"
+			"%kO="
+			"{"
+				"%kO=%lli"
+				"%kO=%f"
+				"%kO=%f"
+				"%kO=%f"
+				"%kO=%f"
+			"}"
+			"%kO=%b"
+		"}",
+		kGAIPerfTestCaseKey_Title,			inCaseTitle,
+		kGAIPerfTestCaseKey_StartTime,		(int64_t) inCaseStartTime,
+		kGAIPerfTestCaseKey_EndTime,		(int64_t) inCaseEndTime,
+		kGAIPerfTestCaseKey_Results,		results,
+		kGAIPerfTestCaseKey_Stats,
+		kGAIPerfTestCaseStatsKey_Count,		(int64_t) count,
+		kGAIPerfTestCaseStatsKey_Min,		stats.min,
+		kGAIPerfTestCaseStatsKey_Max,		stats.max,
+		kGAIPerfTestCaseStatsKey_Mean,		stats.mean,
+		kGAIPerfTestCaseStatsKey_StdDev,	stats.stdDev,
+		kGAIPerfTestCaseKey_FirstStats,
+		kGAIPerfTestCaseStatsKey_Count,		(int64_t) count,
+		kGAIPerfTestCaseStatsKey_Min,		firstStats.min,
+		kGAIPerfTestCaseStatsKey_Max,		firstStats.max,
+		kGAIPerfTestCaseStatsKey_Mean,		firstStats.mean,
+		kGAIPerfTestCaseStatsKey_StdDev,	firstStats.stdDev,
+		kGAIPerfTestCaseKey_ConnectionStats,
+		kGAIPerfTestCaseStatsKey_Count,		(int64_t) count,
+		kGAIPerfTestCaseStatsKey_Min,		connStats.min,
+		kGAIPerfTestCaseStatsKey_Max,		connStats.max,
+		kGAIPerfTestCaseStatsKey_Mean,		connStats.mean,
+		kGAIPerfTestCaseStatsKey_StdDev,	connStats.stdDev,
+		kGAIPerfTestCaseKey_TimedOut,		( inResultCount < inItemCount ) ? true : false );
+	require_noerr( err, exit );
+	
+exit:
+	CFReleaseNullSafe( results );
+}
+
+//===========================================================================================================================
+//	GAIPerfSignalHandler
+//===========================================================================================================================
+
+static void	GAIPerfSignalHandler( void *inContext )
+{
+	GAIPerfContext * const		context = (GAIPerfContext *) inContext;
+	
+	context->gotSignal = true;
+	if( context->tester && context->testerStarted )
+	{
+		GAITesterStop( context->tester );
+	}
+	else
+	{
+		exit( 1 );
+	}
+}
+
+//===========================================================================================================================
+//	GAITesterCreate
+//===========================================================================================================================
+
+typedef enum
+{
+	kGAITestConnType_UseMainConnection		= 1,
+	kGAITestConnType_OwnSharedConnection	= 2
+	
+}	GAITestConnType;
+
+typedef struct GAITestItem		GAITestItem;
+struct GAITestItem
+{
+	GAITestItem *		next;				// Next test item in list.
+	char *				name;				// Domain name to resolve.
+	int64_t				connectionTimeUs;	// Time in microseconds that it took to create a DNS-SD connection.
+	int64_t				firstTimeUs;		// Time in microseconds that it took to get the first address result.
+	int64_t				timeUs;				// Time in microseconds that it took to get all expected address results.
+	unsigned int		addressCount;		// Address count of the domain name, i.e., the Count label argument.
+	Boolean				hasV4;				// True if the domain name has one or more IPv4 addresses.
+	Boolean				hasV6;				// True if the domain name has one or more IPv6 addresses.
+	Boolean				wantV4;				// True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses.
+	Boolean				wantV6;				// True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses.
+};
+
+struct GAITestCase
+{
+	GAITestCase *		next;			// Next test case in list.
+	GAITestItem *		itemList;		// List of test items.
+	char *				title;			// Title of the test case.
+	unsigned int		timeLimitMs;	// Time limit in milliseconds for the test case's completion.
+};
+
+struct GAITesterPrivate
+{
+	CFRuntimeBase					base;				// CF object base.
+	dispatch_queue_t				queue;				// Serial work queue.
+	DNSServiceRef					mainRef;			// Reference to the main shared DNS-SD connection.
+	DNSServiceRef					opRef;				// Reference to the current DNSServiceGetAddrInfo operation.
+	GAITestCase *					caseList;			// List of test cases.
+	GAITestCase *					currentCase;		// Pointer to the current test case.
+	GAITestItem *					currentItem;		// Pointer to the current test item.
+	MicroTime64						caseStartTime;		// Start time of current test case in Unix time as microseconds.
+	MicroTime64						caseEndTime;		// End time of current test case in Unix time as microseconds.
+	Boolean							started;			// True if the tester has been successfully started.
+	Boolean							stopped;			// True if the tester has been stopped.
+	int								callDelayMs;		// Amount of time to wait before calling DNSServiceGetAddrInfo().
+	dispatch_source_t				caseTimer;			// Timer for enforcing a test case time limits.
+	pcap_t *						pcap;				// Captures traffic between mDNSResponder and test DNS server.
+	pid_t							serverPID;			// PID of the test DNS server.
+	int								serverDelayMs;		// Additional time to have the server delay its responses by.
+	int								serverDefaultTTL;	// Default TTL for the server's records.
+	GAITesterEventHandler_f			eventHandler;		// User's event handler.
+	void *							eventContext;		// User's event handler context.
+	GAITesterResultsHandler_f		resultsHandler;		// User's results handler.
+	void *							resultsContext;		// User's results handler context.
+	
+	// Variables for current test item.
+	
+	uint64_t						bitmapV4;		// Bitmap of IPv4 results that have yet to be received.
+	uint64_t						bitmapV6;		// Bitmap of IPv6 results that have yet to be received.
+	uint64_t						startTicks;		// Start ticks of DNSServiceGetAddrInfo().
+	uint64_t						connTicks;		// Ticks when the connection was created.
+	uint64_t						firstTicks;		// Ticks when the first DNSServiceGetAddrInfo result was received.
+	uint64_t						endTicks;		// Ticks when the last DNSServiceGetAddrInfo result was received.
+	Boolean							gotFirstResult;	// True if the first result has been received.
+};
+
+CF_CLASS_DEFINE( GAITester );
+
+static void		_GAITesterRun( void *inContext );
+static OSStatus	_GAITesterCreatePacketCapture( pcap_t **outPCap );
+static void		_GAITesterTimeout( void *inContext );
+static void		_GAITesterAdvanceCurrentItem( GAITesterRef inTester );
+static void		_GAITesterAdvanceCurrentSet( GAITesterRef inTester );
+static void		_GAITesterInitializeCurrentTest( GAITesterRef inTester );
+static void DNSSD_API
+	_GAITesterGetAddrInfoCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext );
+static void		_GAITesterCompleteCurrentTest( GAITesterRef inTester, Boolean inTimedOut );
+
+#define ForgetPacketCapture( X )		ForgetCustom( X, pcap_close )
+
+static OSStatus
+	GAITestItemCreate(
+		const char *	inName,
+		unsigned int	inAddressCount,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		GAITestItem **	outItem );
+static OSStatus	GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem );
+static void		GAITestItemFree( GAITestItem *inItem );
+
+static OSStatus
+	GAITesterCreate(
+		dispatch_queue_t	inQueue,
+		int					inCallDelayMs,
+		int					inServerDelayMs,
+		int					inServerDefaultTTL,
+		GAITesterRef *		outTester )
+{
+	OSStatus			err;
+	GAITesterRef		obj = NULL;
+	
+	CF_OBJECT_CREATE( GAITester, obj, err, exit );
+	
+	ReplaceDispatchQueue( &obj->queue, inQueue );
+	obj->callDelayMs		= inCallDelayMs;
+	obj->serverPID			= -1;
+	obj->serverDelayMs		= inServerDelayMs;
+	obj->serverDefaultTTL	= inServerDefaultTTL;
+	
+	*outTester = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	CFReleaseNullSafe( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_GAITesterFinalize
+//===========================================================================================================================
+
+static void	_GAITesterFinalize( CFTypeRef inObj )
+{
+	GAITesterRef const		me = (GAITesterRef) inObj;
+	GAITestCase *			testCase;
+	
+	check( !me->opRef );
+	check( !me->mainRef );
+	check( !me->caseTimer );
+	dispatch_forget( &me->queue );
+	while( ( testCase = me->caseList ) != NULL )
+	{
+		me->caseList = testCase->next;
+		GAITestCaseFree( testCase );
+	}
+}
+
+//===========================================================================================================================
+//	GAITesterStart
+//===========================================================================================================================
+
+static void	_GAITesterStart( void *inContext );
+static void	_GAITesterStop( GAITesterRef me );
+
+static void	GAITesterStart( GAITesterRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _GAITesterStart );
+}
+
+extern char **		environ;
+
+static void	_GAITesterStart( void *inContext )
+{
+	OSStatus				err;
+	GAITesterRef const		me = (GAITesterRef) inContext;
+	char *					argv[ 4 ];
+	char *					ptr;
+	char *					end;
+	char					command[ 128 ];
+	
+	ptr = &command[ 0 ];
+	end = &command[ countof( command ) ];
+	SNPrintF_Add( &ptr, end, "dnssdutil server --loopback --followPID %lld", (int64_t) getpid() );
+	if( me->serverDefaultTTL >= 0 ) SNPrintF_Add( &ptr, end, " --defaultTTL %d", me->serverDefaultTTL );
+	if( me->serverDelayMs    >= 0 ) SNPrintF_Add( &ptr, end, " --responseDelay %d", me->serverDelayMs );
+	
+	argv[ 0 ] = "/bin/sh";
+	argv[ 1 ] = "-c";
+	argv[ 2 ] = command;
+	argv[ 3 ] = NULL;
+	err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ );
+	require_noerr( err, exit );
+	
+	me->currentCase = me->caseList;
+	me->currentItem = me->currentCase ? me->currentCase->itemList : NULL;
+	_GAITesterInitializeCurrentTest( me );
+	
+	// Hack: The first tester run is delayed for three seconds to allow the test DNS server to start up.
+	// A better way to handle this is to issue an asynchronous query for something in the d.test. domain. As soon as an
+	// expected response is received, the server can be considered to be up and running.
+	
+	CFRetain( me );
+	dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun );
+	
+	CFRetain( me );
+	me->started = true;
+	if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext );
+	
+exit:
+	if( err ) _GAITesterStop( me );
+	CFRelease( me );
+}
+
+//===========================================================================================================================
+//	GAITesterStop
+//===========================================================================================================================
+
+static void	_GAITesterUserStop( void *inContext );
+
+static void	GAITesterStop( GAITesterRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _GAITesterUserStop );
+}
+
+static void	_GAITesterUserStop( void *inContext )
+{
+	GAITesterRef const		me = (GAITesterRef) inContext;
+	
+	_GAITesterStop( me );
+	CFRelease( me );
+}
+
+static void	_GAITesterStop( GAITesterRef me )
+{
+	OSStatus		err;
+	
+	DNSServiceForget( &me->opRef );
+	DNSServiceForget( &me->mainRef );
+	ForgetPacketCapture( &me->pcap );
+	dispatch_source_forget( &me->caseTimer );
+	if( me->serverPID != -1 )
+	{
+		err = kill( me->serverPID, SIGTERM );
+		err = map_global_noerr_errno( err );
+		check_noerr( err );
+	}
+	
+	if( !me->stopped )
+	{
+		me->stopped = true;
+		if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext );
+		if( me->started ) CFRelease( me );
+	}
+}
+
+//===========================================================================================================================
+//	GAITesterAddCase
+//===========================================================================================================================
+
+static void	GAITesterAddCase( GAITesterRef me, GAITestCase *inCase )
+{
+	GAITestCase **		ptr;
+	
+	for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {}
+	*ptr = inCase;
+}
+
+//===========================================================================================================================
+//	GAITesterSetEventHandler
+//===========================================================================================================================
+
+static void	GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext )
+{
+	me->eventHandler = inEventHandler;
+	me->eventContext = inEventContext;
+}
+
+//===========================================================================================================================
+//	GAITesterSetResultsHandler
+//===========================================================================================================================
+
+static void	GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+{
+	me->resultsHandler = inResultsHandler;
+	me->resultsContext = inResultsContext;
+}
+
+//===========================================================================================================================
+//	_GAITesterRun
+//===========================================================================================================================
+
+static void	_GAITesterRun( void *inContext )
+{
+	OSStatus				err;
+	GAITesterRef const		me		= (GAITesterRef) inContext;
+	GAITestItem *			item;
+	GAITestItemResult *		results	= NULL;
+	
+	require_action_quiet( !me->stopped, exit, err = kNoErr );
+	
+	for( ;; )
+	{
+		item = me->currentItem;
+		if( item )
+		{
+			DNSServiceProtocol		protocols;
+			
+			check( !me->opRef );
+			check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
+			
+			// Perform preliminary tasks if this is the start of a new test case.
+			
+			if( item == me->currentCase->itemList )
+			{
+				// Flush mDNSResponder's cache.
+				
+				err = systemf( NULL, "killall -HUP mDNSResponder" );
+				require_noerr( err, exit );
+				usleep( kMicrosecondsPerSecond );
+				
+				// Start a packet capture.
+				
+				check( !me->pcap );
+				err = _GAITesterCreatePacketCapture( &me->pcap );
+				require_noerr( err, exit );
+				
+				// Start the test case time limit timer.
+				
+				check( !me->caseTimer );
+				if( me->currentCase->timeLimitMs > 0 )
+				{
+					const int64_t		timeLimitSecs = ( me->currentCase->timeLimitMs + 999 ) / 1000;
+					
+					err = DispatchTimerCreate( dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER,
+						( (uint64_t) timeLimitSecs ) * kNanosecondsPerSecond / 10,
+						me->queue, _GAITesterTimeout, NULL, me, &me->caseTimer );
+					require_noerr( err, exit );
+					dispatch_resume( me->caseTimer );
+				}
+				
+				me->caseStartTime = GetCurrentMicroTime();
+			}
+			
+			// Call DNSServiceGetAddrInfo().
+			
+			if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
+			
+			protocols = 0;
+			if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
+			if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
+			
+			check( !me->mainRef );
+			me->startTicks = UpTicks();
+			
+			err = DNSServiceCreateConnection( &me->mainRef );
+			require_noerr( err, exit );
+			
+			err = DNSServiceSetDispatchQueue( me->mainRef, me->queue );
+			require_noerr( err, exit );
+			
+			me->connTicks = UpTicks();
+			
+			me->opRef = me->mainRef;
+			err = DNSServiceGetAddrInfo( &me->opRef, kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates,
+				kDNSServiceInterfaceIndexAny, protocols, item->name, _GAITesterGetAddrInfoCallback, me );
+			require_noerr( err, exit );
+			break;
+		}
+		else
+		{
+			// No more test items means that this test case has completed (or timed out).
+			
+			me->caseEndTime = GetCurrentMicroTime();
+			dispatch_source_forget( &me->caseTimer );
+			ForgetPacketCapture( &me->pcap );
+			
+			if( me->resultsHandler )
+			{
+				size_t		resultCount, itemCount, i;
+				int			timedOut;
+				
+				itemCount	= 0;
+				resultCount	= 0;
+				timedOut	= false;
+				for( item = me->currentCase->itemList; item; item = item->next )
+				{
+					if( !timedOut )
+					{
+						if( item->timeUs < 0 )
+						{
+							timedOut = true;
+						}
+						else
+						{
+							++resultCount;
+						}
+					}
+					++itemCount;
+				}
+				if( resultCount > 0 )
+				{
+					results = (GAITestItemResult *) calloc( resultCount, sizeof( *results ) );
+					require_action( results, exit, err = kNoMemoryErr );
+					
+					item = me->currentCase->itemList;
+					for( i = 0; i < resultCount; ++i )
+					{
+						results[ i ].name				= item->name;
+						results[ i ].connectionTimeUs	= item->connectionTimeUs;
+						results[ i ].firstTimeUs		= item->firstTimeUs;
+						results[ i ].timeUs				= item->timeUs;
+						item = item->next;
+					}
+				}
+				me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, results, resultCount,
+					itemCount, me->resultsContext );
+				ForgetMem( &results );
+			}
+			
+			_GAITesterAdvanceCurrentSet( me );
+			require_action_quiet( me->currentCase, exit, err = kEndingErr );
+		}
+	}
+	
+exit:
+	FreeNullSafe( results );
+	if( err ) _GAITesterStop( me );
+	CFRelease( me );
+}
+
+//===========================================================================================================================
+//	_GAITesterCreatePacketCapture
+//===========================================================================================================================
+
+static OSStatus	_GAITesterCreatePacketCapture( pcap_t **outPCap )
+{
+	OSStatus				err;
+	pcap_t *				pcap;
+	struct bpf_program		program;
+	char					errBuf[ PCAP_ERRBUF_SIZE ];
+	
+	pcap = pcap_create( "lo0", errBuf );
+	require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+	
+	err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
+	require_noerr_action( err, exit, err = kUnknownErr );
+	
+	err = pcap_set_snaplen( pcap, 512 );
+	require_noerr_action( err, exit, err = kUnknownErr );
+	
+	err = pcap_set_immediate_mode( pcap, 0 );
+	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	
+	err = pcap_activate( pcap );
+	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	
+	err = pcap_setdirection( pcap, PCAP_D_INOUT );
+	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	
+	err = pcap_setnonblock( pcap, 1, errBuf );
+	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	
+	err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
+	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	
+	err = pcap_setfilter( pcap, &program );
+	pcap_freecode( &program );
+	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	
+	*outPCap = pcap;
+	pcap = NULL;
+	
+exit:
+	if( pcap ) pcap_close( pcap );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_GAITesterTimeout
+//===========================================================================================================================
+
+static void	_GAITesterTimeout( void *inContext )
+{
+	GAITesterRef const		me = (GAITesterRef) inContext;
+	
+	dispatch_source_forget( &me->caseTimer );
+	
+	_GAITesterCompleteCurrentTest( me, true );
+}
+
+//===========================================================================================================================
+//	_GAITesterAdvanceCurrentItem
+//===========================================================================================================================
+
+static void	_GAITesterAdvanceCurrentItem( GAITesterRef me )
+{
+	if( me->currentItem )
+	{
+		me->currentItem = me->currentItem->next;
+		_GAITesterInitializeCurrentTest( me );
+	}
+}
+
+//===========================================================================================================================
+//	_GAITesterAdvanceCurrentSet
+//===========================================================================================================================
+
+static void	_GAITesterAdvanceCurrentSet( GAITesterRef me )
+{
+	if( me->currentCase )
+	{
+		me->caseStartTime	= 0;
+		me->caseEndTime		= 0;
+		me->currentCase		= me->currentCase->next;
+		if( me->currentCase )
+		{
+			me->currentItem = me->currentCase->itemList;
+			_GAITesterInitializeCurrentTest( me );
+		}
+	}
+}
+
+//===========================================================================================================================
+//	_GAITesterInitializeCurrentTest
+//===========================================================================================================================
+
+static void	_GAITesterInitializeCurrentTest( GAITesterRef me )
+{
+	GAITestItem * const		item = me->currentItem;
+	
+	if( item )
+	{
+		check( item->addressCount > 0 );
+		if( item->wantV4 )
+		{
+			me->bitmapV4 = item->hasV4 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
+		}
+		else
+		{
+			me->bitmapV4 = 0;
+		}
+		
+		if( item->wantV6 )
+		{
+			me->bitmapV6 = item->hasV6 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
+		}
+		else
+		{
+			me->bitmapV6 = 0;
+		}
+		me->gotFirstResult = false;
+	}
+}
+
+//===========================================================================================================================
+//	_GAITesterGetAddrInfoCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_GAITesterGetAddrInfoCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext )
+{
+	GAITesterRef const				me		= (GAITesterRef) inContext;
+	GAITestItem * const				item	= me->currentItem;
+	const sockaddr_ip * const		sip		= (const sockaddr_ip *) inSockAddr;
+	uint64_t						nowTicks;
+	uint64_t *						bitmapPtr;
+	uint64_t						bitmask;
+	unsigned int					addrOffset;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inHostname );
+	Unused( inTTL );
+	
+	nowTicks = UpTicks();
+	
+	require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
+	require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit );
+	
+	bitmapPtr	= NULL;
+	bitmask		= 0;
+	if( ( sip->sa.sa_family == AF_INET ) && item->wantV4 )
+	{
+		if( item->hasV4 )
+		{
+			if( !inError )
+			{
+				const uint32_t		addrV4 = ntohl( sip->v4.sin_addr.s_addr );
+				
+				if( strcasecmp( item->name, "localhost." ) == 0 )
+				{
+					if( addrV4 == INADDR_LOOPBACK )
+					{
+						bitmask		= 1;
+						bitmapPtr	= &me->bitmapV4;
+					}
+				}
+				else
+				{
+					addrOffset = addrV4 - kTestDNSServerBaseAddrV4;
+					if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+					{
+						bitmask		= UINT64_C( 1 ) << ( addrOffset - 1 );
+						bitmapPtr	= &me->bitmapV4;
+					}
+				}
+			}
+		}
+		else if( inError == kDNSServiceErr_NoSuchRecord )
+		{
+			bitmask		= 1;
+			bitmapPtr	= &me->bitmapV4;
+		}
+	}
+	else if( ( sip->sa.sa_family == AF_INET6 ) && item->wantV6 )
+	{
+		if( item->hasV6 )
+		{
+			if( !inError )
+			{
+				const uint8_t * const		addrV6 = sip->v6.sin6_addr.s6_addr;
+				
+				if( strcasecmp( item->name, "localhost." ) == 0 )
+				{
+					if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 )
+					{
+						bitmask		= 1;
+						bitmapPtr	= &me->bitmapV6;
+					}
+				}
+				else if( memcmp( addrV6, kTestDNSServerBaseAddrV6, 15 ) == 0 )
+				{
+					addrOffset = addrV6[ 15 ];
+					if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+					{
+						bitmask		= UINT64_C( 1 ) << ( addrOffset - 1 );
+						bitmapPtr	= &me->bitmapV6;
+					}
+				}
+			}
+		}
+		else if( inError == kDNSServiceErr_NoSuchRecord )
+		{
+			bitmask		= 1;
+			bitmapPtr	= &me->bitmapV6;
+		}
+	}
+	
+	if( bitmapPtr && ( *bitmapPtr & bitmask ) )
+	{
+		*bitmapPtr &= ~bitmask;
+		if( !me->gotFirstResult )
+		{
+			me->firstTicks		= nowTicks;
+			me->gotFirstResult	= true;
+		}
+		
+		if( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) )
+		{
+			me->endTicks = nowTicks;
+			_GAITesterCompleteCurrentTest( me, false );
+		}
+	}
+	
+exit:
+	return;
+}
+
+//===========================================================================================================================
+//	_GAITesterCompleteCurrentTest
+//===========================================================================================================================
+
+static OSStatus
+	_GAITesterGetDNSMessageFromPacket(
+		const uint8_t *		inPacketPtr,
+		size_t				inPacketLen,
+		const uint8_t **	outMsgPtr,
+		size_t *			outMsgLen );
+
+static void	_GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut )
+{
+	OSStatus				err;
+	GAITestItem *			item;
+	struct timeval *		tsQA	= NULL;
+	struct timeval *		tsQAAAA	= NULL;
+	struct timeval *		tsRA	= NULL;
+	struct timeval *		tsRAAAA	= NULL;
+	struct timeval			timeStamps[ 4 ];
+	struct timeval *		tsPtr	= &timeStamps[ 0 ];
+	struct timeval *		tsQ;
+	struct timeval *		tsR;
+	int64_t					idleTimeUs;
+	uint8_t					name[ kDomainNameLengthMax ];
+	
+	DNSServiceForget( &me->opRef );
+	DNSServiceForget( &me->mainRef );
+	
+	if( inTimedOut )
+	{
+		for( item = me->currentItem; item; item = item->next )
+		{
+			item->firstTimeUs	= -1;
+			item->timeUs		= -1;
+		}
+		me->currentItem = NULL;
+		
+		CFRetain( me );
+		dispatch_async_f( me->queue, me, _GAITesterRun );
+		return;
+	}
+	
+	item = me->currentItem;
+	err = DomainNameFromString( name, item->name, NULL );
+    require_noerr( err, exit );
+	
+	for( ;; )
+	{
+		int									status;
+		struct pcap_pkthdr *				pktHdr;
+		const uint8_t *						packet;
+		const uint8_t *						msgPtr;
+		size_t								msgLen;
+		const DNSHeader *					hdr;
+		unsigned int						flags;
+		const uint8_t *						ptr;
+		const DNSQuestionFixedFields *		qfields;
+		unsigned int						qtype;
+		uint8_t								qname[ kDomainNameLengthMax ];
+		
+		status = pcap_next_ex( me->pcap, &pktHdr, &packet );
+		if( status != 1 ) break;
+		if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue;
+		if( msgLen < kDNSHeaderLength ) continue;
+		
+		hdr = (const DNSHeader *) msgPtr;
+		flags = DNSHeaderGetFlags( hdr );
+		if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue;
+		if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue;
+		
+		ptr = (const uint8_t *) &hdr[ 1 ];
+		if( DNSMessageExtractDomainName( msgPtr, msgLen, ptr, qname, &ptr ) != kNoErr ) continue;
+		if( !DomainNameEqual( qname, name ) ) continue;
+		
+		qfields = (const DNSQuestionFixedFields *) ptr;
+		if( DNSQuestionFixedFieldsGetClass( qfields ) != kDNSServiceClass_IN ) continue;
+		
+		qtype = DNSQuestionFixedFieldsGetType( qfields );
+		if( item->wantV4 && ( qtype == kDNSServiceType_A ) )
+		{
+			if( flags & kDNSHeaderFlag_Response )
+			{
+				if( tsQA && !tsRA )
+				{
+					tsRA  = tsPtr++;
+					*tsRA = pktHdr->ts;
+				}
+			}
+			else if( !tsQA )
+			{
+				tsQA  = tsPtr++;
+				*tsQA = pktHdr->ts;
+			}
+		}
+		else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) )
+		{
+			if( flags & kDNSHeaderFlag_Response )
+			{
+				if( tsQAAAA && !tsRAAAA )
+				{
+					tsRAAAA  = tsPtr++;
+					*tsRAAAA = pktHdr->ts;
+				}
+			}
+			else if( !tsQAAAA )
+			{
+				tsQAAAA  = tsPtr++;
+				*tsQAAAA = pktHdr->ts;
+			}
+		}
+	}
+	
+	if( tsQA && tsQAAAA )	tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
+	else					tsQ = tsQA ? tsQA : tsQAAAA;
+	
+	if( tsRA && tsRAAAA )	tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
+	else					tsR = tsQA ? tsQA : tsQAAAA;
+	
+	if( tsQ && tsR )
+	{
+		idleTimeUs = TIMEVAL_USEC64_DIFF( *tsR, *tsQ );
+		if( idleTimeUs < 0 ) idleTimeUs = 0;
+	}
+	else
+	{
+		idleTimeUs = 0;
+	}
+	
+	item->connectionTimeUs	= (int64_t)  UpTicksToMicroseconds( me->connTicks  - me->startTicks );
+	item->firstTimeUs		= (int64_t)( UpTicksToMicroseconds( me->firstTicks - me->connTicks  ) - (uint64_t) idleTimeUs );
+	item->timeUs			= (int64_t)( UpTicksToMicroseconds( me->endTicks   - me->connTicks  ) - (uint64_t) idleTimeUs );
+	
+	_GAITesterAdvanceCurrentItem( me );
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _GAITesterRun );
+	
+exit:
+	if( err ) _GAITesterStop( me );
+}
+
+//===========================================================================================================================
+//	_GAITesterGetDNSMessageFromPacket
+//===========================================================================================================================
+
+#define kHeaderSizeNullLink		 4
+#define kHeaderSizeIPv4Min		20
+#define kHeaderSizeIPv6			40
+#define kHeaderSizeUDP			 8
+
+#define kIPProtocolUDP		0x11
+
+static OSStatus
+	_GAITesterGetDNSMessageFromPacket(
+		const uint8_t *		inPacketPtr,
+		size_t				inPacketLen,
+		const uint8_t **	outMsgPtr,
+		size_t *			outMsgLen )
+{
+	OSStatus					err;
+	const uint8_t *				nullLink;
+	uint32_t					addressFamily;
+	const uint8_t *				ip;
+	int							ipHeaderLen;
+	int							protocol;
+	const uint8_t *				msg;
+	const uint8_t * const		end = &inPacketPtr[ inPacketLen ];
+	
+	nullLink = &inPacketPtr[ 0 ];
+	require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
+	addressFamily = ReadHost32( &nullLink[ 0 ] );
+	
+	ip = &nullLink[ kHeaderSizeNullLink ];
+	if( addressFamily == AF_INET )
+	{
+		require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
+		ipHeaderLen	= ( ip[ 0 ] & 0x0F ) * 4;
+		protocol	=   ip[ 9 ];
+	}
+	else if( addressFamily == AF_INET6 )
+	{
+		require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
+		ipHeaderLen	= kHeaderSizeIPv6;
+		protocol	= ip[ 6 ];
+	}
+	else
+	{
+		err = kTypeErr;
+		goto exit;
+	}
+	require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
+	require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
+	
+	msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+	
+	*outMsgPtr = msg;
+	*outMsgLen = (size_t)( end - msg );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestCaseCreate
+//===========================================================================================================================
+
+static OSStatus	GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet )
+{
+	OSStatus			err;
+	GAITestCase *		obj;
+	
+	obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->title = strdup( inTitle );
+	require_action( obj->title, exit, err = kNoMemoryErr );
+	
+	obj->timeLimitMs = inTimeLimitMs;
+	
+	*outSet = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) GAITestCaseFree( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestCaseFree
+//===========================================================================================================================
+
+static void	GAITestCaseFree( GAITestCase *inCase )
+{
+	GAITestItem *		item;
+	
+	while( ( item = inCase->itemList ) != NULL )
+	{
+		inCase->itemList = item->next;
+		GAITestItemFree( item );
+	}
+	ForgetMem( &inCase->title );
+	free( inCase );
+}
+
+//===========================================================================================================================
+//	GAITestCaseAddItem
+//===========================================================================================================================
+
+// A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336
+// possible strings to use in the Tag label.
+
+#define kUniqueStringCharSet		"abcdefghijklmnopqrstuvwxyz0123456789"
+#define kUniqueStringCharSetLen		sizeof_string( kUniqueStringCharSet )
+#define kUniqueStringLen			6
+
+static OSStatus
+	GAITestCaseAddItem(
+		GAITestCase *	inCase,
+		unsigned int	inAliasCount,
+		unsigned int	inAddressCount,
+		int				inTTL,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		unsigned int	inItemCount )
+{
+	OSStatus			err;
+	GAITestItem *		item;
+	GAITestItem *		item2;
+	GAITestItem *		newItemList = NULL;
+	GAITestItem **		itemPtr;
+	char *				ptr;
+	char *				end;
+	unsigned int		i;
+	char				name[ 64 ];
+	char				uniqueStr[ kUniqueStringLen + 1 ];
+	
+	require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+	
+	// Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+	
+	require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr );
+	require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr );
+	require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+	
+	ptr = &name[ 0 ];
+	end = &name[ countof( name ) ];
+	
+	// Add Alias label.
+	
+	if(      inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
+	else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+	
+	// Add Count label.
+	
+	SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+	
+	// Add TTL label.
+	
+	if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+	
+	// Add Tag label.
+	
+	RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr );
+	SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr );
+	
+	// Add IPv4 or IPv6 label if necessary.
+	
+	switch( inHasAddrs )
+	{
+		case kGAITestAddrType_IPv4:
+			SNPrintF_Add( &ptr, end, "ipv4." );
+			break;
+		
+		case kGAITestAddrType_IPv6:
+			SNPrintF_Add( &ptr, end, "ipv6." );
+			break;
+	}
+	
+	// Add d.test. labels.
+	
+	SNPrintF_Add( &ptr, end, "d.test." );
+	
+	// Create item.
+	
+	err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item );
+	require_noerr( err, exit );
+	
+	newItemList	= item;
+	itemPtr		= &item->next;
+	
+	// Create repeat items.
+	
+	for( i = 1; i < inItemCount; ++i )
+	{
+		err = GAITestItemDuplicate( item, &item2 );
+		require_noerr( err, exit );
+		
+		*itemPtr	= item2;
+		itemPtr		= &item2->next;
+	}
+	
+	// Append to test case's item list.
+	
+	for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+	*itemPtr	= newItemList;
+	newItemList	= NULL;
+	
+exit:
+	while( ( item = newItemList ) != NULL )
+	{
+		newItemList = item->next;
+		GAITestItemFree( item );
+	}
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestCaseAddLocalHostItem
+//===========================================================================================================================
+
+static OSStatus	GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount )
+{
+	OSStatus			err;
+	GAITestItem *		item;
+	GAITestItem *		item2;
+	GAITestItem *		newItemList = NULL;
+	GAITestItem **		itemPtr;
+	unsigned int		i;
+	
+	require_action_quiet( inItemCount > 1, exit, err = kNoErr );
+	
+	err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, &item );
+	require_noerr( err, exit );
+	
+	newItemList	= item;
+	itemPtr		= &item->next;
+	
+	// Create repeat items.
+	
+	for( i = 1; i < inItemCount; ++i )
+	{
+		err = GAITestItemDuplicate( item, &item2 );
+		require_noerr( err, exit );
+		
+		*itemPtr	= item2;
+		itemPtr		= &item2->next;
+	}
+	
+	for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+	*itemPtr	= newItemList;
+	newItemList	= NULL;
+	
+exit:
+	while( ( item = newItemList ) != NULL )
+	{
+		newItemList = item->next;
+		GAITestItemFree( item );
+	}
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestItemCreate
+//===========================================================================================================================
+
+static OSStatus
+	GAITestItemCreate(
+		const char *	inName,
+		unsigned int	inAddressCount,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		GAITestItem **	outItem )
+{
+	OSStatus			err;
+	GAITestItem *		obj = NULL;
+	
+	require_action_quiet( inAddressCount >= 1, exit, err = kCountErr );
+	require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+	require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr );
+	
+	obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
+	
+	obj->addressCount	= inAddressCount;
+	obj->hasV4			= ( inHasAddrs  & kGAITestAddrType_IPv4 ) ? true : false;
+	obj->hasV6			= ( inHasAddrs  & kGAITestAddrType_IPv6 ) ? true : false;
+	obj->wantV4			= ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false;
+	obj->wantV6			= ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false;
+	
+	*outItem = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) GAITestItemFree( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestItemDuplicate
+//===========================================================================================================================
+
+static OSStatus	GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem )
+{
+	OSStatus			err;
+	GAITestItem *		obj;
+	
+	obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	*obj = *inItem;
+	obj->next = NULL;
+	if( inItem->name )
+	{
+		obj->name = strdup( inItem->name );
+		require_action( obj->name, exit, err = kNoMemoryErr );
+	}
+	
+	*outItem = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) GAITestItemFree( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestItemFree
+//===========================================================================================================================
+
+static void	GAITestItemFree( GAITestItem *inItem )
+{
+	ForgetMem( &inItem->name );
+	free( inItem );
+}
+
+//===========================================================================================================================
+//	SSDPDiscoverCmd
+//===========================================================================================================================
+
+#define kSSDPPort		1900
+
+typedef struct
+{
+	HTTPHeader				header;			// HTTP header object for sending and receiving.
+	dispatch_source_t		readSourceV4;	// Read dispatch source for IPv4 socket.
+	dispatch_source_t		readSourceV6;	// Read dispatch source for IPv6 socket.
+	int						receiveSecs;	// After send, the amount of time to spend receiving.
+	uint32_t				ifindex;		// Index of the interface over which to send the query.
+	Boolean					useIPv4;		// True if the query should be sent via IPv4 multicast.
+	Boolean					useIPv6;		// True if the query should be sent via IPv6 multicast.
+	
+}	SSDPDiscoverContext;
+
+static void		SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext );
+static void		SSDPDiscoverReadHandler( void *inContext );
+static int		SocketToPortNumber( SocketRef inSock );
+static OSStatus	WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST );
+
+static void	SSDPDiscoverCmd( void )
+{
+	OSStatus					err;
+	struct timeval				now;
+	SSDPDiscoverContext *		context;
+	dispatch_source_t			signalSource	= NULL;
+	SocketRef					sockV4			= kInvalidSocketRef;
+	SocketRef					sockV6			= kInvalidSocketRef;
+	ssize_t						n;
+	int							sendCount;
+	
+	// Set up SIGINT handler.
+	
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+	require_noerr( err, exit );
+	dispatch_resume( signalSource );
+	
+	// Check command parameters.
+	
+	if( gSSDPDiscover_ReceiveSecs < -1 )
+	{
+		FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
+		err = kParamErr;
+		goto exit;
+	}
+	
+	// Create context.
+	
+	context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->receiveSecs	= gSSDPDiscover_ReceiveSecs;
+	context->useIPv4		= ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
+	context->useIPv6		= ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
+	
+	err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
+	require_noerr_quiet( err, exit );
+	
+	// Set up IPv4 socket.
+	
+	if( context->useIPv4 )
+	{
+		int port;
+		err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
+		require_noerr( err, exit );
+		
+		err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
+		require_noerr( err, exit );
+		
+		err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+		err = map_socket_noerr_errno( sockV4, err );
+		require_noerr( err, exit );
+	}
+	
+	// Set up IPv6 socket.
+	
+	if( context->useIPv6 )
+	{
+		err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+		require_noerr( err, exit );
+		
+		err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+		require_noerr( err, exit );
+		
+		err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+		err = map_socket_noerr_errno( sockV6, err );
+		require_noerr( err, exit );
+	}
+	
+	// Print prologue.
+	
+	SSDPDiscoverPrintPrologue( context );
+	
+	// Send mDNS query message.
+	
+	sendCount = 0;
+	if( IsValidSocket( sockV4 ) )
+	{
+		struct sockaddr_in		mcastAddr4;
+		
+		memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
+		SIN_LEN_SET( &mcastAddr4 );
+		mcastAddr4.sin_family		= AF_INET;
+		mcastAddr4.sin_port			= htons( kSSDPPort );
+		mcastAddr4.sin_addr.s_addr	= htonl( 0xEFFFFFFA );	// 239.255.255.250
+		
+		err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
+		require_noerr( err, exit );
+		
+		n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
+			(socklen_t) sizeof( mcastAddr4 ) );
+		err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
+		if( err )
+		{
+			FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+			ForgetSocket( &sockV4 );
+		}
+		else
+		{
+			if( gSSDPDiscover_Verbose )
+			{
+				gettimeofday( &now, NULL );
+				FPrintF( stdout, "---\n" );
+				FPrintF( stdout, "Send time:    %{du:time}\n",	&now );
+				FPrintF( stdout, "Source Port:  %d\n",			SocketToPortNumber( sockV4 ) );
+				FPrintF( stdout, "Destination:  %##a\n",		&mcastAddr4 );
+				FPrintF( stdout, "Message size: %zu\n",			context->header.len );
+				FPrintF( stdout, "HTTP header:\n%1{text}",		context->header.buf, context->header.len );
+			}
+			++sendCount;
+		}
+	}
+	
+	if( IsValidSocket( sockV6 ) )
+	{
+		struct sockaddr_in6		mcastAddr6;
+		
+		memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
+		SIN6_LEN_SET( &mcastAddr6 );
+		mcastAddr6.sin6_family				= AF_INET6;
+		mcastAddr6.sin6_port				= htons( kSSDPPort );
+		mcastAddr6.sin6_addr.s6_addr[  0 ]	= 0xFF;	// SSDP IPv6 link-local multicast address FF02::C
+		mcastAddr6.sin6_addr.s6_addr[  1 ]	= 0x02;
+		mcastAddr6.sin6_addr.s6_addr[ 15 ]	= 0x0C;
+		
+		err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
+		require_noerr( err, exit );
+		
+		n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
+			(socklen_t) sizeof( mcastAddr6 ) );
+		err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
+		if( err )
+		{
+			FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+			ForgetSocket( &sockV6 );
+		}
+		else
+		{
+			if( gSSDPDiscover_Verbose )
+			{
+				gettimeofday( &now, NULL );
+				FPrintF( stdout, "---\n" );
+				FPrintF( stdout, "Send time:    %{du:time}\n",	&now );
+				FPrintF( stdout, "Source Port:  %d\n",			SocketToPortNumber( sockV6 ) );
+				FPrintF( stdout, "Destination:  %##a\n",		&mcastAddr6 );
+				FPrintF( stdout, "Message size: %zu\n",			context->header.len );
+				FPrintF( stdout, "HTTP header:\n%1{text}",		context->header.buf, context->header.len );
+			}
+			++sendCount;
+		}
+	}
+	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+	
+	// If there's no wait period after the send, then exit.
+	
 	if( context->receiveSecs == 0 ) goto exit;
 	
 	// Create dispatch read sources for socket(s).
 	
 	if( IsValidSocket( sockV4 ) )
 	{
-		SocketContext *		sockContext;
+		SocketContext *		sockCtx;
 		
-		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
-		require_action( sockContext, exit, err = kNoMemoryErr );
+		err = SocketContextCreate( sockV4, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV4 = kInvalidSocketRef;
 		
-		err = DispatchReadSourceCreate( sockV4, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
 			&context->readSourceV4 );
-		if( err ) ForgetMem( &sockContext );
+		if( err ) ForgetSocketContext( &sockCtx );
 		require_noerr( err, exit );
 		
-		sockContext->context	= context;
-		sockContext->sock		= sockV4;
-		sockV4 = kInvalidSocketRef;
 		dispatch_resume( context->readSourceV4 );
 	}
 	
 	if( IsValidSocket( sockV6 ) )
 	{
-		SocketContext *		sockContext;
+		SocketContext *		sockCtx;
 		
-		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
-		require_action( sockContext, exit, err = kNoMemoryErr );
+		err = SocketContextCreate( sockV6, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV6 = kInvalidSocketRef;
 		
-		err = DispatchReadSourceCreate( sockV6, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
 			&context->readSourceV6 );
-		if( err ) ForgetMem( &sockContext );
+		if( err ) ForgetSocketContext( &sockCtx );
 		require_noerr( err, exit );
 		
-		sockContext->context	= context;
-		sockContext->sock		= sockV6;
-		sockV6 = kInvalidSocketRef;
 		dispatch_resume( context->readSourceV6 );
 	}
 	
@@ -6475,7 +10524,6 @@ static void	SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
 	const char *			ifName;
 	char					ifNameBuf[ IF_NAMESIZE + 1 ];
 	NetTransportType		ifType;
-	char					time[ kTimestampBufLen ];
 	
 	ifName = if_indextoname( inContext->ifindex, ifNameBuf );
 	
@@ -6489,7 +10537,7 @@ static void	SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
 	FPrintF( stdout, "Receive duration: " );
 	if( receiveSecs >= 0 )	FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time:       %s\n",		GetTimestampStr( time ) );
+	FPrintF( stdout, "Start time:       %{du:time}\n", NULL );
 }
 
 //===========================================================================================================================
@@ -6499,23 +10547,23 @@ static void	SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
 static void	SSDPDiscoverReadHandler( void *inContext )
 {
 	OSStatus						err;
-	SocketContext * const			sockContext	= (SocketContext *) inContext;
-	SSDPDiscoverContext * const		context		= (SSDPDiscoverContext *) sockContext->context;
-	HTTPHeader * const				header		= &context->header;
+	struct timeval					now;
+	SocketContext * const			sockCtx	= (SocketContext *) inContext;
+	SSDPDiscoverContext * const		context	= (SSDPDiscoverContext *) sockCtx->userContext;
+	HTTPHeader * const				header	= &context->header;
 	sockaddr_ip						fromAddr;
 	size_t							msgLen;
-	char							time[ kTimestampBufLen ];
 	
-	GetTimestampStr( time );
+	gettimeofday( &now, NULL );
 	
-	err = SocketRecvFrom( sockContext->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
+	err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
 		NULL, NULL, NULL, NULL );
 	require_noerr( err, exit );
 	
 	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "Receive time: %s\n",		time );
-	FPrintF( stdout, "Source:       %##a\n", 	&fromAddr );
-	FPrintF( stdout, "Message size: %zu\n",		msgLen );
+	FPrintF( stdout, "Receive time: %{du:time}\n",	&now );
+	FPrintF( stdout, "Source:       %##a\n", 		&fromAddr );
+	FPrintF( stdout, "Message size: %zu\n",			msgLen );
 	header->len = msgLen;
 	if( HTTPHeader_Validate( header ) )
 	{
@@ -6618,7 +10666,6 @@ static void	ResQueryCmd( void )
 	res_query_f		res_query_ptr;
 	int				n;
 	uint16_t		type, class;
-	char			time[ kTimestampBufLen ];
 	uint8_t			answer[ 1024 ];
 	
 	// Get pointer to one of the res_query() functions.
@@ -6663,10 +10710,10 @@ static void	ResQueryCmd( void )
 	
 	// Print prologue.
 	
-	FPrintF( stdout, "Name:       %s\n",		gResQuery_Name );
-	FPrintF( stdout, "Type:       %s (%u)\n",	RecordTypeToString( type ), type );
-	FPrintF( stdout, "Class:      %s (%u)\n",	( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
-	FPrintF( stdout, "Start time: %s\n",		GetTimestampStr( time ) );
+	FPrintF( stdout, "Name:       %s\n",			gResQuery_Name );
+	FPrintF( stdout, "Type:       %s (%u)\n",		RecordTypeToString( type ), type );
+	FPrintF( stdout, "Class:      %s (%u)\n",		( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 	
 	// Call res_query().
@@ -6681,8 +10728,7 @@ static void	ResQueryCmd( void )
 	
 	// Print result.
 	
-	FPrintF( stdout, "Message size: %d\n\n", n );
-	PrintUDNSMessage( answer, (size_t) n, false );
+	FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
 	
 exit:
 	if( err ) exit( 1 );
@@ -6719,7 +10765,6 @@ static void	ResolvDNSQueryCmd( void )
 	uint16_t			type, class;
 	sockaddr_ip			from;
 	uint32_t			fromLen;
-	char				time[ kTimestampBufLen ];
 	uint8_t				answer[ 1024 ];
 	
 	// Make sure that the required symbols are available.
@@ -6774,11 +10819,11 @@ static void	ResolvDNSQueryCmd( void )
 	
 	// Print prologue.
 	
-	FPrintF( stdout, "Name:       %s\n",		gResolvDNSQuery_Name );
-	FPrintF( stdout, "Type:       %s (%u)\n",	RecordTypeToString( type ), type );
-	FPrintF( stdout, "Class:      %s (%u)\n",	( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
-	FPrintF( stdout, "Path:       %s\n",		gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
-	FPrintF( stdout, "Start time: %s\n",		GetTimestampStr( time ) );
+	FPrintF( stdout, "Name:       %s\n",			gResolvDNSQuery_Name );
+	FPrintF( stdout, "Type:       %s (%u)\n",		RecordTypeToString( type ), type );
+	FPrintF( stdout, "Class:      %s (%u)\n",		( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+	FPrintF( stdout, "Path:       %s\n",			gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
 	FPrintF( stdout, "---\n" );
 	
 	// Call dns_query().
@@ -6797,13 +10842,258 @@ static void	ResolvDNSQueryCmd( void )
 	// Print result.
 	
 	FPrintF( stdout, "From:         %##a\n", &from );
-	FPrintF( stdout, "Message size: %d\n\n", n );
-	PrintUDNSMessage( answer, (size_t) n, false );
+	FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
 	
 exit:
 	if( dns ) soft_dns_free( dns );
 	if( err ) exit( 1 );
 }
+
+//===========================================================================================================================
+//	CFHostCmd
+//===========================================================================================================================
+
+static void
+	_CFHostResolveCallback(
+		CFHostRef				inHost,
+		CFHostInfoType			inInfoType,
+		const CFStreamError *	inError,
+		void *					inInfo );
+
+static void	CFHostCmd( void )
+{
+	OSStatus				err;
+	CFStringRef				name;
+	Boolean					success;
+	CFHostRef				host = NULL;
+	CFHostClientContext		context;
+	CFStreamError			streamErr;
+	
+	name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 );
+	require_action( name, exit, err = kUnknownErr );
+	
+	host = CFHostCreateWithName( kCFAllocatorDefault, name );
+	ForgetCF( &name );
+	require_action( host, exit, err = kUnknownErr );
+	
+	memset( &context, 0, sizeof( context ) );
+	success = CFHostSetClient( host, _CFHostResolveCallback, &context );
+	require_action( success, exit, err = kUnknownErr );
+	
+	CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+	
+	// Print prologue.
+	
+	FPrintF( stdout, "Hostname:   %s\n",			gCFHost_Name );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
+	FPrintF( stdout, "---\n" );
+	
+	success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
+	require_action( success, exit, err = kUnknownErr );
+	err = kNoErr;
+	
+	CFRunLoopRun();
+	
+exit:
+	CFReleaseNullSafe( host );
+	if( err ) exit( 1 );
+}
+
+static void	_CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+{
+	OSStatus			err;
+	struct timeval		now;
+	
+	gettimeofday( &now, NULL );
+	
+	Unused( inInfoType );
+	Unused( inInfo );
+	
+	if( inError && ( inError->domain != 0 ) && ( inError->error ) )
+	{
+		err = inError->error;
+		if( inError->domain == kCFStreamErrorDomainNetDB )
+		{
+			FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+		}
+		else
+		{
+			FPrintF( stderr, "Error %#m\n", err );
+		}
+	}
+	else
+	{
+		CFArrayRef					addresses;
+		CFIndex						count, i;
+		CFDataRef					addrData;
+		const struct sockaddr *		sockAddr;
+		Boolean						wasResolved = false;
+		
+		addresses = CFHostGetAddressing( inHost, &wasResolved );
+		check( wasResolved );
+		
+		if( addresses )
+		{
+			count = CFArrayGetCount( addresses );
+			for( i = 0; i < count; ++i )
+			{
+				addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
+				require_noerr( err, exit );
+				
+				sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
+				FPrintF( stdout, "%##a\n", sockAddr );
+			}
+		}
+		err = kNoErr;
+	}
+	
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "End time:   %{du:time}\n", &now );
+	
+	if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+	
+exit:
+	exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+//	DNSConfigAddCmd
+//
+//	Note: Based on ajn's supplemental test tool.
+//===========================================================================================================================
+
+static void	DNSConfigAddCmd( void )
+{
+	OSStatus					err;
+	CFMutableDictionaryRef		dict	= NULL;
+	CFMutableArrayRef			array	= NULL;
+	size_t						i;
+	SCDynamicStoreRef			store	= NULL;
+	CFStringRef					key		= NULL;
+	Boolean						success;
+	
+	if( geteuid() != 0 )
+	{
+		FPrintF( stderr, "error: This command must to be run as root.\n" );
+		err = kIDErr;
+		goto exit;
+	}
+	
+	// Create dictionary.
+	
+	dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+	require_action( dict, exit, err = kNoMemoryErr );
+	
+	// Add DNS server IP addresses.
+	
+	array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
+	require_action( array, exit, err = kNoMemoryErr );
+	
+	for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+	{
+		CFStringRef		addrStr;
+		
+		addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
+		require_action( addrStr, exit, err = kUnknownErr );
+		
+		CFArrayAppendValue( array, addrStr );
+		CFRelease( addrStr );
+	}
+	
+	CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
+	ForgetCF( &array );
+	
+	// Add domains, if any.
+	
+	array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
+	require_action( array, exit, err = kNoMemoryErr );
+	
+	if( gDNSConfigAdd_DomainCount > 0 )
+	{
+		for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+		{
+			CFStringRef		domainStr;
+			
+			domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
+			require_action( domainStr, exit, err = kUnknownErr );
+			
+			CFArrayAppendValue( array, domainStr );
+			CFRelease( domainStr );
+		}
+	}
+	else
+	{
+		// There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
+		
+		CFArrayAppendValue( array, CFSTR( "" ) );
+	}
+	
+	CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
+	ForgetCF( &array );
+	
+	// Add interface, if any.
+	
+	if( gDNSConfigAdd_Interface )
+	{
+		err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
+		require_noerr( err, exit );
+		
+		CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+	}
+	
+	// Set dictionary in dynamic store.
+	
+	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+	err = map_scerror( store );
+	require_noerr( err, exit );
+	
+	key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
+	require_action( key, exit, err = kUnknownErr );
+	
+	success = SCDynamicStoreSetValue( store, key, dict );
+	require_action( success, exit, err = kUnknownErr );
+	
+exit:
+	CFReleaseNullSafe( dict );
+	CFReleaseNullSafe( array );
+	CFReleaseNullSafe( store );
+	CFReleaseNullSafe( key );
+	gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+//	DNSConfigRemoveCmd
+//===========================================================================================================================
+
+static void	DNSConfigRemoveCmd( void )
+{
+	OSStatus				err;
+	SCDynamicStoreRef		store	= NULL;
+	CFStringRef				key		= NULL;
+	Boolean					success;
+	
+	if( geteuid() != 0 )
+	{
+		FPrintF( stderr, "error: This command must to be run as root.\n" );
+		err = kIDErr;
+		goto exit;
+	}
+	
+	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+	err = map_scerror( store );
+	require_noerr( err, exit );
+	
+	key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
+	require_action( key, exit, err = kUnknownErr );
+	
+	success = SCDynamicStoreRemoveValue( store, key );
+	require_action( success, exit, err = kUnknownErr );
+	
+exit:
+	CFReleaseNullSafe( store );
+	CFReleaseNullSafe( key );
+	gExitCode = err ? 1 : 0;
+}
 #endif	// TARGET_OS_DARWIN
 
 //===========================================================================================================================
@@ -6833,33 +11123,98 @@ exit:
 static void	Exit( void *inContext )
 {
 	const char * const		reason = (const char *) inContext;
-	char					time[ kTimestampBufLen ];
 	
 	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "End time:   %s\n", GetTimestampStr( time ) );
+	FPrintF( stdout, "End time:   %{du:time}\n", NULL );
 	if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
 	exit( gExitCode );
 }
 
 //===========================================================================================================================
-//	GetTimestampStr
+//	PrintFTimestampHandler
 //===========================================================================================================================
 
-static char *	GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
+static int
+	PrintFTimestampHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext )
 {
-	struct timeval		now;
-	struct tm *			tm;
-	size_t				len;
+	struct timeval				now;
+	const struct timeval *		tv;
+	struct tm *					localTime;
+	size_t						len;
+	int							n;
+	char						dateTimeStr[ 32 ];
 	
-	gettimeofday( &now, NULL );
-	tm = localtime( &now.tv_sec );
-	require_action( tm, exit, *inBuffer = '\0' );
+	Unused( inUserContext );
+	
+	tv = va_arg( inArgs->args, const struct timeval * );
+	require_action_quiet( !inFormat->suppress, exit, n = 0 );
+	
+	if( !tv )
+	{
+		gettimeofday( &now, NULL );
+		tv = &now;
+	}
+	localTime = localtime( &tv->tv_sec );
+	len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime );
+	if( len == 0 ) dateTimeStr[ 0 ] = '\0';
 	
-	len = strftime( inBuffer, kTimestampBufLen, "%Y-%m-%d %H:%M:%S", tm );
-	SNPrintF( &inBuffer[ len ], kTimestampBufLen - len, ".%06u", (unsigned int) now.tv_usec );
+	n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
 	
 exit:
-	return( inBuffer );
+	return( n );
+}
+
+//===========================================================================================================================
+//	PrintFDNSMessageHandler
+//===========================================================================================================================
+
+static int
+	PrintFDNSMessageHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext )
+{
+	OSStatus			err;
+	const void *		msgPtr;
+	size_t				msgLen;
+	char *				text;
+	int					n;
+	Boolean				isMDNS;
+	Boolean				printRawRData;
+	
+	Unused( inUserContext );
+	
+	msgPtr = va_arg( inArgs->args, const void * );
+	msgLen = va_arg( inArgs->args, size_t );
+	require_action_quiet( !inFormat->suppress, exit, n = 0 );
+	
+	isMDNS = ( inFormat->altForm > 0 ) ? true : false;
+	if(      inFormat->precision == 0 ) printRawRData = false;
+	else if( inFormat->precision == 1 ) printRawRData = true;
+	else
+	{
+		n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" );
+		goto exit;
+	}
+	
+	err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
+	if( !err )
+	{
+		n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
+		free( text );
+	}
+	else
+	{
+		n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+	}
+	
+exit:
+	return( n );
 }
 
 //===========================================================================================================================
@@ -7043,17 +11398,9 @@ static OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outData
 	else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
 	{
 		const char * const		str = inString + sizeof_string( kRDataArgPrefix_Domain );
-		uint8_t *				end;
-		uint8_t					dname[ kDomainNameLengthMax ];
-		
-		err = DomainNameFromString( dname, str, &end );
-		require_noerr( err, exit );
-		
-		dataLen = (size_t)( end - dname );
-		dataPtr = malloc( dataLen );
-		require_action( dataPtr, exit, err = kNoMemoryErr );
 		
-		memcpy( dataPtr, dname, dataLen );
+		err = StringToDomainName( str, &dataPtr, &dataLen );
+		require_noerr_quiet( err, exit );
 	}
 	
 	// File path
@@ -7083,18 +11430,9 @@ static OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outData
 	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
 	{
 		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
-		const char *			end;
-		
-		dataLen = 4;
-		dataPtr = (uint8_t *) malloc( dataLen );
-		require_action( dataPtr, exit, err = kNoMemoryErr );
-		
-		err = StringToIPv4Address( str, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix,
-			(uint32_t *) dataPtr, NULL, NULL, NULL, &end );
-		if( !err && ( *end != '\0' ) ) err = kMalformedErr;
-		require_noerr( err, exit );
 		
-		*( (uint32_t *) dataPtr ) = HostToBig32( *( (uint32_t *) dataPtr ) );
+		err = StringToARecordData( str, &dataPtr, &dataLen );
+		require_noerr_quiet( err, exit );
 	}
 	
 	// IPv6 address string
@@ -7102,17 +11440,9 @@ static OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outData
 	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
 	{
 		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
-		const char *			end;
-		
-		dataLen = 16;
-		dataPtr = (uint8_t *) malloc( dataLen );
-		require_action( dataPtr, exit, err = kNoMemoryErr );
 		
-		err = StringToIPv6Address( str,
-			kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
-			dataPtr, NULL, NULL, NULL, &end );
-		if( !err && ( *end != '\0' ) ) err = kMalformedErr;
-		require_noerr( err, exit );
+		err = StringToAAAARecordData( str, &dataPtr, &dataLen );
+		require_noerr_quiet( err, exit );
 	}
 	
 	// SRV record
@@ -7964,6 +12294,18 @@ static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
 }
 
 //===========================================================================================================================
+//	DomainNameLength
+//===========================================================================================================================
+
+static size_t	DomainNameLength( const uint8_t * const inName )
+{
+	const uint8_t *		ptr;
+	
+	for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {}
+	return( (size_t)( ptr - inName ) + 1 );
+}
+
+//===========================================================================================================================
 //	DomainNameFromString
 //===========================================================================================================================
 
@@ -8039,7 +12381,7 @@ exit:
 }
 
 //===========================================================================================================================
-//	PrintDNSMessage
+//	DNSMessageToText
 //===========================================================================================================================
 
 #define DNSFlagsOpCodeToString( X ) (					\
@@ -8059,12 +12401,17 @@ exit:
 	( (X) == kDNSRCode_Refused )		? "Refused"		:	\
 										  "???" )
 
-#define DNSFlagsGetOpCode( X )		( ( (X) >> 11 ) & 0x0F )
-#define DNSFlagsGetRCode( X )		(   (X)         & 0x0F )
-
-static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const Boolean inIsMDNS, const Boolean inPrintRaw )
+static OSStatus
+	DNSMessageToText(
+		const uint8_t *	inMsgPtr,
+		size_t			inMsgLen,
+		const Boolean	inMDNS,
+		const Boolean	inPrintRaw,
+		char **			outText )
 {
 	OSStatus					err;
+	DataBuffer					dataBuf;
+	size_t						len;
 	const DNSHeader *			hdr;
 	const uint8_t * const		msgEnd = inMsgPtr + inMsgLen;
 	const uint8_t *				ptr;
@@ -8072,6 +12419,9 @@ static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
 	unsigned int				questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
 	char						nameStr[ kDNSServiceMaxDomainName ];
 	
+	DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
+	#define _Append( ... )		do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
+	
 	require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
 	
 	hdr				= (DNSHeader *) inMsgPtr;
@@ -8084,24 +12434,26 @@ static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
 	opcode			= DNSFlagsGetOpCode( flags );
 	rcode			= DNSFlagsGetRCode( flags );
 	
-	FPrintF( stdout, "ID:               0x%04X (%u)\n", id, id );
-	FPrintF( stdout, "Flags:            0x%04X %c/%s %cAA%cTC%cRD%cRA %s\n",
+	_Append( "ID:               0x%04X (%u)\n", id, id );
+	_Append( "Flags:            0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
 		flags,
 		( flags & kDNSHeaderFlag_Response )				? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
 		( flags & kDNSHeaderFlag_AuthAnswer )			? ' ' : '!',
 		( flags & kDNSHeaderFlag_Truncation )			? ' ' : '!',
 		( flags & kDNSHeaderFlag_RecursionDesired )		? ' ' : '!',
 		( flags & kDNSHeaderFlag_RecursionAvailable )	? ' ' : '!',
+		!inMDNS, ( flags & kDNSHeaderFlag_AuthenticData )		? " AD" : "!AD",
+		!inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled )	? " CD" : "!CD",
 		DNSFlagsRCodeToString( rcode ) );
-	FPrintF( stdout, "Question count:   %u\n", questionCount );
-	FPrintF( stdout, "Answer count:     %u\n", answerCount );
-	FPrintF( stdout, "Authority count:  %u\n", authorityCount );
-	FPrintF( stdout, "Additional count: %u\n", additionalCount );
+	_Append( "Question count:   %u\n", questionCount );
+	_Append( "Answer count:     %u\n", answerCount );
+	_Append( "Authority count:  %u\n", authorityCount );
+	_Append( "Additional count: %u\n", additionalCount );
 	
-	ptr = (uint8_t *)( hdr + 1 );
+	ptr = (const uint8_t *) &hdr[ 1 ];
 	for( i = 0; i < questionCount; ++i )
 	{
-		unsigned int		qType, qClass;
+		unsigned int		qtype, qclass;
 		Boolean				isQU;
 		
 		err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr );
@@ -8113,20 +12465,18 @@ static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
 			goto exit;
 		}
 		
-		qType = ReadBig16( ptr );
-		ptr += 2;
-		qClass = ReadBig16( ptr );
-		ptr += 2;
+		qtype	= DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
+		qclass	= DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
+		ptr += 4;
 		
-		isQU = ( inIsMDNS && ( qClass & kQClassUnicastResponseBit ) ) ? true : false;
-		if( inIsMDNS ) qClass &= ~kQClassUnicastResponseBit;
+		isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
+		if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
 		
-		if( i == 0 ) FPrintF( stdout, "\nQUESTION SECTION\n" );
+		if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
 		
-		FPrintF( stdout, "%s %2s %?2s%?2u %-5s\n",
-			nameStr, inIsMDNS ? ( isQU ? "QU" : "QM" ) : "",
-			( qClass == kDNSServiceClass_IN ), "IN", ( qClass != kDNSServiceClass_IN ), qClass,
-			RecordTypeToString( qType ) );
+		_Append( "%s %2s %?2s%?2u %-5s\n",
+			nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
+			( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
 	}
 	
 	totalRRCount = answerCount + authorityCount + additionalCount;
@@ -8147,8 +12497,8 @@ static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
 		err = DomainNameToString( name, NULL, nameStr, NULL );
 		require_noerr( err, exit );
 		
-		cacheFlush = ( inIsMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
-		if( inIsMDNS ) class &= ~kRRClassCacheFlushBit;
+		cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
+		if( inMDNS ) class &= ~kRRClassCacheFlushBit;
 		
 		rdataStr = NULL;
 		if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
@@ -8158,20 +12508,26 @@ static OSStatus	PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
 			require_action( rdataStr, exit, err = kNoMemoryErr );
 		}
 		
-		if(      answerCount     && ( i ==   0                              ) ) FPrintF( stdout, "\nANSWER SECTION\n" );
-		else if( authorityCount  && ( i ==   answerCount                    ) ) FPrintF( stdout, "\nAUTHORITY SECTION\n" );
-		else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) FPrintF( stdout, "\nADDITIONAL SECTION\n" );
+		if(      answerCount     && ( i ==   0                              ) ) _Append( "\nANSWER SECTION\n" );
+		else if( authorityCount  && ( i ==   answerCount                    ) ) _Append( "\nAUTHORITY SECTION\n" );
+		else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
 		
-		FPrintF( stdout, "%-42s %6u %2s %?2s%?2u %-5s %s\n",
+		_Append( "%-42s %6u %2s %?2s%?2u %-5s %s\n",
 			nameStr, ttl, cacheFlush ? "CF" : "",
 			( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
 			RecordTypeToString( type ), rdataStr );
 		free( rdataStr );
 	}
-	FPrintF( stdout, "\n" );
-	err = kNoErr;
+	_Append( "\n" );
+	
+	err = DataBuffer_Append( &dataBuf, "", 1 );
+	require_noerr( err, exit );
+	
+	err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
+	require_noerr( err, exit );
 	
 exit:
+	DataBuffer_Free( &dataBuf );
 	return( err );
 }
 
@@ -8194,21 +12550,18 @@ static OSStatus
 	uint8_t *				ptr;
 	size_t					msgLen;
 	
-	WriteBig16( hdr->id,				inMsgID );
-	WriteBig16( hdr->flags,				inFlags );
-	WriteBig16( hdr->questionCount,		1 );
-	WriteBig16( hdr->answerCount,		0 );
-	WriteBig16( hdr->authorityCount,	0 );
-	WriteBig16( hdr->additionalCount,	0 );
+	memset( hdr, 0, sizeof( *hdr ) );
+	DNSHeaderSetID( hdr, inMsgID );
+	DNSHeaderSetFlags( hdr, inFlags );
+	DNSHeaderSetQuestionCount( hdr, 1 );
 	
 	ptr = (uint8_t *)( hdr + 1 );
 	err = DomainNameFromString( ptr, inQName, &ptr );
 	require_noerr_quiet( err, exit );
 	
-	WriteBig16( ptr, inQType );
-	ptr += 2;
-	WriteBig16( ptr, inQClass );
-	ptr += 2;
+	DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
+	ptr += 4;
+	
 	msgLen = (size_t)( ptr - inMsg );
 	check( msgLen <= kDNSQueryMessageMaxLen );
 	
@@ -8246,21 +12599,23 @@ exit:
 }
 
 //===========================================================================================================================
-//	DispatchReadSourceCreate
+//	DispatchSocketSourceCreate
 //===========================================================================================================================
 
 static OSStatus
-	DispatchReadSourceCreate(
-		SocketRef			inSock,
-		DispatchHandler		inEventHandler,
-		DispatchHandler		inCancelHandler,
-		void *				inContext,
-		dispatch_source_t *	outSource )
+	DispatchSocketSourceCreate(
+		SocketRef				inSock,
+		dispatch_source_type_t	inType,
+		dispatch_queue_t		inQueue,
+		DispatchHandler			inEventHandler,
+		DispatchHandler			inCancelHandler,
+		void *					inContext,
+		dispatch_source_t *		outSource )
 {
 	OSStatus				err;
 	dispatch_source_t		source;
 	
-	source = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, (uintptr_t) inSock, 0, dispatch_get_main_queue() );
+	source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
 	require_action( source, exit, err = kUnknownErr );
 	
 	dispatch_set_context( source, inContext );
@@ -8283,6 +12638,7 @@ static OSStatus
 		dispatch_time_t		inStart,
 		uint64_t			inIntervalNs,
 		uint64_t			inLeewayNs,
+		dispatch_queue_t	inQueue,
 		DispatchHandler		inEventHandler,
 		DispatchHandler		inCancelHandler,
 		void *				inContext,
@@ -8291,7 +12647,7 @@ static OSStatus
 	OSStatus				err;
 	dispatch_source_t		timer;
 	
-	timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
+	timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
 	require_action( timer, exit, err = kUnknownErr );
 	
 	dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
@@ -8307,6 +12663,38 @@ exit:
 }
 
 //===========================================================================================================================
+//	DispatchProcessMonitorCreate
+//===========================================================================================================================
+
+static OSStatus
+	DispatchProcessMonitorCreate(
+		pid_t				inPID,
+		unsigned long		inFlags,
+		dispatch_queue_t	inQueue,
+		DispatchHandler		inEventHandler,
+		DispatchHandler		inCancelHandler,
+		void *				inContext,
+		dispatch_source_t *	outMonitor )
+{
+	OSStatus				err;
+	dispatch_source_t		monitor;
+	
+	monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
+		inQueue ? inQueue : dispatch_get_main_queue() );
+	require_action( monitor, exit, err = kUnknownErr );
+	
+	dispatch_set_context( monitor, inContext );
+	dispatch_source_set_event_handler_f( monitor, inEventHandler );
+	dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
+	
+	*outMonitor = monitor;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
 //	ServiceTypeDescription
 //===========================================================================================================================
 
@@ -8369,15 +12757,58 @@ static const char *	ServiceTypeDescription( const char *inName )
 }
 
 //===========================================================================================================================
+//	SocketContextCreate
+//===========================================================================================================================
+
+static OSStatus	SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+{
+	OSStatus			err;
+	SocketContext *		context;
+	
+	context = (SocketContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->refCount		= 1;
+	context->sock			= inSock;
+	context->userContext	= inUserContext;
+	
+	*outContext = context;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	SocketContextRetain
+//===========================================================================================================================
+
+static SocketContext *	SocketContextRetain( SocketContext *inContext )
+{
+	++inContext->refCount;
+	return( inContext );
+}
+
+//===========================================================================================================================
+//	SocketContextRelease
+//===========================================================================================================================
+
+static void	SocketContextRelease( SocketContext *inContext )
+{
+	if( --inContext->refCount == 0 )
+	{
+		ForgetSocket( &inContext->sock );
+		free( inContext );
+	}
+}
+
+//===========================================================================================================================
 //	SocketContextCancelHandler
 //===========================================================================================================================
 
 static void	SocketContextCancelHandler( void *inContext )
 {
-	SocketContext * const		context = (SocketContext *) inContext;
-	
-	ForgetSocket( &context->sock );
-	free( context );
+	SocketContextRelease( (SocketContext *) inContext );
 }
 
 //===========================================================================================================================
@@ -8421,6 +12852,115 @@ exit:
 	return( err );
 }
 
+//===========================================================================================================================
+//	StringToLongLong
+//===========================================================================================================================
+
+static OSStatus	StringToLongLong( const char *inString, long long *outValue )
+{
+	OSStatus		err;
+	long long		value;
+	char *			endPtr;
+	
+	set_errno_compat( 0 );
+	value = strtol( inString, &endPtr, 0 );
+	err = errno_compat();
+	if( ( ( value == LLONG_MIN ) || ( value == LLONG_MAX ) ) && ( err == ERANGE ) ) goto exit;
+	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+	
+	*outValue = value;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	StringToARecordData
+//===========================================================================================================================
+
+static OSStatus	StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+	OSStatus			err;
+	uint32_t *			addrPtr;
+	const size_t		addrLen = sizeof( *addrPtr );
+	const char *		end;
+	
+	addrPtr = (uint32_t *) malloc( addrLen );
+	require_action( addrPtr, exit, err = kNoMemoryErr );
+	
+	err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
+		NULL, NULL, NULL, &end );
+	if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+	require_noerr_quiet( err, exit );
+	
+	*addrPtr = HostToBig32( *addrPtr );
+	
+	*outPtr = (uint8_t *) addrPtr;
+	addrPtr = NULL;
+	*outLen = addrLen;
+	
+exit:
+	FreeNullSafe( addrPtr );
+	return( err );
+}
+
+//===========================================================================================================================
+//	StringToAAAARecordData
+//===========================================================================================================================
+
+static OSStatus	StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+	OSStatus			err;
+	uint8_t *			addrPtr;
+	const size_t		addrLen = 16;
+	const char *		end;
+	
+	addrPtr = (uint8_t *) malloc( addrLen );
+	require_action( addrPtr, exit, err = kNoMemoryErr );
+	
+	err = StringToIPv6Address( inString,
+		kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+		addrPtr, NULL, NULL, NULL, &end );
+	if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+	require_noerr_quiet( err, exit );
+	
+	*outPtr = addrPtr;
+	addrPtr = NULL;
+	*outLen = addrLen;
+	
+exit:
+	FreeNullSafe( addrPtr );
+	return( err );
+}
+
+//===========================================================================================================================
+//	StringToDomainName
+//===========================================================================================================================
+
+static OSStatus	StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+	OSStatus		err;
+	uint8_t *		namePtr;
+	size_t			nameLen;
+	uint8_t *		end;
+	uint8_t			nameBuf[ kDomainNameLengthMax ];
+	
+	err = DomainNameFromString( nameBuf, inString, &end );
+	require_noerr_quiet( err, exit );
+	
+	nameLen = (size_t)( end - nameBuf );
+	namePtr = memdup( nameBuf, nameLen );
+	require_action( namePtr, exit, err = kNoMemoryErr );
+	
+	*outPtr = namePtr;
+	namePtr = NULL;
+	if( outLen ) *outLen = nameLen;
+	
+exit:
+	return( err );
+}
+
 #if( TARGET_OS_DARWIN )
 //===========================================================================================================================
 //	GetDefaultDNSServer
@@ -8446,7 +12986,7 @@ static OSStatus	GetDefaultDNSServer( sockaddr_ip *outAddr )
 			addr = resolver->nameserver[ 0 ];
 			break;
 		}
-	}
+ 	}
 	require_action_quiet( addr, exit, err = kNotFoundErr );
 	
 	SockAddrCopy( addr, outAddr );
@@ -8459,6 +12999,20 @@ exit:
 #endif
 
 //===========================================================================================================================
+//	GetCurrentMicroTime
+//===========================================================================================================================
+
+static MicroTime64	GetCurrentMicroTime( void )
+{
+	struct timeval		now;
+	
+	TIMEVAL_ZERO( now );
+	gettimeofday( &now, NULL );
+	
+	return( (MicroTime64) TIMEVAL_USEC64( now ) );
+}
+
+//===========================================================================================================================
 //	SocketWriteAll
 //
 //	Note: This was copied from CoreUtils because the SocketWriteAll function is currently not exported in the framework.
@@ -8919,47 +13473,6 @@ exit:
 }
 
 //===========================================================================================================================
-//	StringArray_Append
-//
-//	Note: This was copied from CoreUtils because the StringArray_Append function is currently not exported in the framework.
-//===========================================================================================================================
-
-OSStatus	StringArray_Append( char ***ioArray, size_t *ioCount, const char *inStr )
-{
-	OSStatus		err;
-	char *			newStr;
-	size_t			oldCount;
-	size_t			newCount;
-	char **			oldArray;
-	char **			newArray;
-	
-	newStr = strdup( inStr );
-	require_action( newStr, exit, err = kNoMemoryErr );
-	
-	oldCount = *ioCount;
-	newCount = oldCount + 1;
-	newArray = (char **) malloc( newCount * sizeof( *newArray ) );
-	require_action( newArray, exit, err = kNoMemoryErr );
-	
-	if( oldCount > 0 )
-	{
-		oldArray = *ioArray;
-		memcpy( newArray, oldArray, oldCount * sizeof( *oldArray ) );
-		free( oldArray );
-	}
-	newArray[ oldCount ] = newStr;
-	newStr = NULL;
-	
-	*ioArray = newArray;
-	*ioCount = newCount;
-	err = kNoErr;
-	
-exit:
-	if( newStr ) free( newStr );
-	return( err );
-}
-
-//===========================================================================================================================
 //	StringArray_Free
 //
 //	Note: This was copied from CoreUtils because the StringArray_Free function is currently not exported in the framework.
@@ -9150,3 +13663,193 @@ Boolean
 	if( outSrc )		*outSrc			= (const char *) src;
 	return( true );
 }
+
+//===========================================================================================================================
+//	_ServerSocketOpenEx2
+//
+//	Note: Based on ServerSocketOpenEx() from CoreUtils. Added parameter to not use SO_REUSEPORT.
+//===========================================================================================================================
+
+static OSStatus
+	_ServerSocketOpenEx2( 
+		int				inFamily, 
+		int				inType, 
+		int				inProtocol, 
+		const void *	inAddr, 
+		int				inPort, 
+		int *			outPort, 
+		int				inRcvBufSize, 
+		Boolean			inNoPortReuse,
+		SocketRef *		outSock )
+{
+	OSStatus		err;
+	int				port;
+	SocketRef		sock;
+	int				name;
+	int				option;
+	sockaddr_ip		sip;
+	socklen_t		len;
+	
+	port = ( inPort < 0 ) ? -inPort : inPort; // Negated port number means "try this port, but allow dynamic".
+	
+	sock = socket( inFamily, inType, inProtocol );
+	err = map_socket_creation_errno( sock );
+	require_noerr_quiet( err, exit );
+	
+#if( defined( SO_NOSIGPIPE ) )
+	setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, (socklen_t) sizeof( int ) );
+#endif
+	
+	err = SocketMakeNonBlocking( sock );
+	require_noerr( err, exit );
+	
+	// Set receive buffer size. This has to be done on the listening socket *before* listen is called because
+	// accept does not return until after the window scale option is exchanged during the 3-way handshake. 
+	// Since accept returns a new socket, the only way to use a larger window scale option is to set the buffer
+	// size on the listening socket since SO_RCVBUF is inherited by the accepted socket. See UNPv1e3 Section 7.5.
+	
+	err = SocketSetBufferSize( sock, SO_RCVBUF, inRcvBufSize );
+	check_noerr( err );
+	
+	// Allow port or address reuse because we may bind separate IPv4 and IPv6 sockets to the same port.
+	
+	if( ( inType != SOCK_DGRAM ) || !inNoPortReuse )
+	{
+		option = 1;
+		name = ( inType == SOCK_DGRAM ) ? SO_REUSEPORT : SO_REUSEADDR;
+		err = setsockopt( sock, SOL_SOCKET, name, (char *) &option, (socklen_t) sizeof( option ) );
+		err = map_socket_noerr_errno( sock, err );
+		require_noerr( err, exit );
+	}
+	
+	if( inFamily == AF_INET )
+	{
+		// Bind to the port. If it fails, retry with a dynamic port.
+		
+		memset( &sip.v4, 0, sizeof( sip.v4 ) );
+		SIN_LEN_SET( &sip.v4 );
+		sip.v4.sin_family		= AF_INET;
+		sip.v4.sin_port			= htons( (uint16_t) port );
+		sip.v4.sin_addr.s_addr	= inAddr ? *( (const uint32_t *) inAddr ) : htonl( INADDR_ANY );
+		err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v4 ) );
+		err = map_socket_noerr_errno( sock, err );
+		if( err && ( inPort < 0 ) )
+		{
+			sip.v4.sin_port = 0;
+			err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v4 ) );
+			err = map_socket_noerr_errno( sock, err );
+		}
+		require_noerr( err, exit );
+	}
+#if( defined( AF_INET6 ) )
+	else if( inFamily == AF_INET6 )
+	{
+		// Restrict this socket to IPv6 only because we're going to use a separate socket for IPv4.
+		
+		option = 1;
+		err = setsockopt( sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &option, (socklen_t) sizeof( option ) );
+		err = map_socket_noerr_errno( sock, err );
+		require_noerr( err, exit );
+		
+		// Bind to the port. If it fails, retry with a dynamic port.
+		
+		memset( &sip.v6, 0, sizeof( sip.v6 ) );
+		SIN6_LEN_SET( &sip.v6 );
+		sip.v6.sin6_family	= AF_INET6;
+		sip.v6.sin6_port	= htons( (uint16_t) port );
+		sip.v6.sin6_addr	= inAddr ? *( (const struct in6_addr *) inAddr ) : in6addr_any;	
+		err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v6 ) );
+		err = map_socket_noerr_errno( sock, err );
+		if( err && ( inPort < 0 ) )
+		{
+			sip.v6.sin6_port = 0;
+			err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v6 ) );
+			err = map_socket_noerr_errno( sock, err );
+		}
+		require_noerr( err, exit );
+	}
+#endif
+	else
+	{
+		dlogassert( "Unsupported family: %d", inFamily );
+		err = kUnsupportedErr;
+		goto exit;
+	}
+	
+	if( inType == SOCK_STREAM )
+	{
+		err = listen( sock, SOMAXCONN );
+		err = map_socket_noerr_errno( sock, err );
+		if( err )
+		{
+			err = listen( sock, 5 );
+			err = map_socket_noerr_errno( sock, err );
+			require_noerr( err, exit );
+		}
+	}
+	
+	if( outPort )
+	{
+		len = (socklen_t) sizeof( sip );
+		err = getsockname( sock, &sip.sa, &len );
+		err = map_socket_noerr_errno( sock, err );
+		require_noerr( err, exit );
+		
+		*outPort = SockAddrGetPort( &sip );
+	}
+	*outSock = sock;
+	sock = kInvalidSocketRef;
+	
+exit:
+	ForgetSocket( &sock );
+	return( err );
+}
+
+//===========================================================================================================================
+//	memdup
+//
+//	Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+void *	memdup( const void *inPtr, size_t inLen )
+{
+	void *		mem;
+	
+	mem = malloc( ( inLen > 0 ) ? inLen : 1 ); // If inLen is 0, use 1 since malloc( 0 ) is not well defined.
+	require( mem, exit );
+	if( inLen > 0 ) memcpy( mem, inPtr, inLen );
+	
+exit:
+	return( mem );
+}
+
+#if( !TARGET_OS_WINDOWS )
+//===========================================================================================================================
+//	memicmp
+//
+//	Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+int	memicmp( const void *inP1, const void *inP2, size_t inLen )
+{
+	const unsigned char *		p1;
+	const unsigned char *		e1;
+	const unsigned char *		p2;
+	int							c1;
+	int							c2;
+	
+	p1 = (const unsigned char *) inP1;
+	e1 = p1 + inLen;
+	p2 = (const unsigned char *) inP2;
+	while( p1 < e1 )
+	{
+		c1 = *p1++;
+		c2 = *p2++;
+		c1 = tolower( c1 );
+		c2 = tolower( c2 );
+		if( c1 < c2 ) return( -1 );
+		if( c1 > c2 ) return(  1 );
+	}
+	return( 0 );
+}
+#endif
diff --git a/mDNSResponder/Makefile b/mDNSResponder/Makefile
index 978c895..d50841c 100644
--- a/mDNSResponder/Makefile
+++ b/mDNSResponder/Makefile
@@ -11,12 +11,13 @@
 #         install:
 #         installsrc:
 #         installhdrs:
+#         installapi:
 #         clean:
 #
 
 include $(MAKEFILEPATH)/pb_makefiles/platform.make
 
-MVERS = "mDNSResponder-878.70.2"
+MVERS = "mDNSResponder-878.200.35"
 
 VER =
 ifneq ($(strip $(GCC_VERSION)),)
@@ -44,6 +45,9 @@ installhdrs::
 	cd "$(SRCROOT)/mDNSMacOSX"; xcodebuild installhdrs OBJROOT=$(OBJROOT) SYMROOT=$(SYMROOT) DSTROOT=$(DSTROOT) MVERS=$(MVERS) SDKROOT=$(SDKROOT)  -target SystemLibraries $(VER)
 	cd "$(SRCROOT)/mDNSMacOSX"; xcodebuild installhdrs OBJROOT=$(OBJROOT) SYMROOT=$(SYMROOT) DSTROOT=$(DSTROOT) MVERS=$(MVERS) SDKROOT=$(SDKROOT)  -target dns_services $(VER)
 
+installapi:
+	cd "$(SRCROOT)/mDNSMacOSX"; xcodebuild installapi  OBJROOT=$(OBJROOT) SYMROOT=$(SYMROOT) DSTROOT=$(DSTROOT) MVERS=$(MVERS) SDKROOT=$(SDKROOT)  -target SystemLibrariesDynamic $(VER)
+
 java:
 	cd "$(SRCROOT)/mDNSMacOSX"; xcodebuild install  OBJROOT=$(OBJROOT) SYMROOT=$(SYMROOT) DSTROOT=$(DSTROOT) MVERS=$(MVERS) SDKROOT=$(SDKROOT) -target libjdns_sd.jnilib $(VER)
 
diff --git a/mDNSResponder/mDNSCore/DNSCommon.c b/mDNSResponder/mDNSCore/DNSCommon.c
index 597c4cc..a249b96 100644
--- a/mDNSResponder/mDNSCore/DNSCommon.c
+++ b/mDNSResponder/mDNSCore/DNSCommon.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -3450,7 +3450,6 @@ mDNSexport const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage
     rr->TimeRcvd          = m ? m->timenow : 0;
     rr->DelayDelivery     = 0;
     rr->NextRequiredQuery = m ? m->timenow : 0;     // Will be updated to the real value when we call SetNextCacheCheckTimeForRecord()
-    rr->LastUsed          = m ? m->timenow : 0;
     rr->CRActiveQuestion  = mDNSNULL;
     rr->UnansweredQueries = 0;
     rr->LastUnansweredTime= 0;
@@ -3630,25 +3629,6 @@ mDNSexport mDNSBool GetPktLease(mDNS *const m, const DNSMessage *const msg, cons
     return mDNSfalse;
 }
 
-mDNSlocal const mDNSu8 *DumpRecords(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end, int count, char *label)
-{
-    int i;
-    LogInfo("%2d %s", count, label);
-    for (i = 0; i < count && ptr; i++)
-    {
-        // This puts a LargeCacheRecord on the stack instead of using the shared m->rec storage,
-        // but since it's only used for debugging (and probably only on OS X, not on
-        // embedded systems) putting a 9kB object on the stack isn't a big problem.
-        LargeCacheRecord largecr;
-        ptr = GetLargeResourceRecord(m, msg, ptr, end, mDNSInterface_Any, kDNSRecordTypePacketAns, &largecr);
-        if (ptr)
-            LogInfo("%2d TTL%8d %s", i, largecr.r.resrec.rroriginalttl, CRDisplayString(m, &largecr.r));
-    }
-    if (!ptr)
-        LogInfo("DumpRecords: ERROR: Premature end of packet data");
-    return(ptr);
-}
-
 #define DNS_OP_Name(X) (                              \
         (X) == kDNSFlag0_OP_StdQuery ? ""         :       \
         (X) == kDNSFlag0_OP_Iquery   ? "Iquery "  :       \
@@ -3672,52 +3652,198 @@ mDNSlocal const mDNSu8 *DumpRecords(mDNS *const m, const DNSMessage *const msg,
         (X) == kDNSFlag1_RC_NotAuth  ? "NotAuth"  :      \
         (X) == kDNSFlag1_RC_NotZone  ? "NotZone"  : "??" )
 
-// Note: DumpPacket expects the packet header fields in host byte order, not network byte order
-mDNSexport void DumpPacket(mDNS *const m, mStatus status, mDNSBool sent, char *transport,
-                           const mDNSAddr *srcaddr, mDNSIPPort srcport,
-                           const mDNSAddr *dstaddr, mDNSIPPort dstport, const DNSMessage *const msg, const mDNSu8 *const end)
+mDNSlocal void mDNS_snprintf_add(char **ptr, const char *lim, const char *fmt, ...)
 {
-    mDNSBool IsUpdate = ((msg->h.flags.b[0] & kDNSFlag0_OP_Mask) == kDNSFlag0_OP_Update);
-    const mDNSu8 *ptr = msg->data;
-    int i;
-    DNSQuestion q;
-    char tbuffer[64], sbuffer[64], dbuffer[64] = "";
-    if (!status) tbuffer[mDNS_snprintf(tbuffer, sizeof(tbuffer), sent ? "Sent" : "Received"                        )] = 0;
-    else tbuffer[mDNS_snprintf(tbuffer, sizeof(tbuffer), "ERROR %d %sing", status, sent ? "Send" : "Receive")] = 0;
-    if (sent) sbuffer[mDNS_snprintf(sbuffer, sizeof(sbuffer), "port "        )] = 0;
-    else sbuffer[mDNS_snprintf(sbuffer, sizeof(sbuffer), "%#a:", srcaddr)] = 0;
-    if (dstaddr || !mDNSIPPortIsZero(dstport))
-        dbuffer[mDNS_snprintf(dbuffer, sizeof(dbuffer), " to %#a:%d", dstaddr, mDNSVal16(dstport))] = 0;
-
-    LogInfo("-- %s %s DNS %s%s (flags %02X%02X) RCODE: %s (%d) %s%s%s%s%s%sID: %d %d bytes from %s%d%s%s --",
-           tbuffer, transport,
+    va_list args;
+    mDNSu32 buflen, n;
+    char *const dst = *ptr;
+
+    buflen = (mDNSu32)(lim - dst);
+    if (buflen > 0)
+    {
+        va_start(args, fmt);
+        n = mDNS_vsnprintf(dst, buflen, fmt, args);
+        va_end(args);
+        *ptr = dst + n;
+    }
+}
+
+#define DNSTypeString(X) (((X) == kDNSType_A) ? "A" : DNSTypeName(X))
+
+#define ReadField16(PTR) ((mDNSu16)((((mDNSu16)((mDNSu8 *)(PTR))[0]) << 8) | ((mDNSu16)((mDNSu8 *)(PTR))[1])))
+#define ReadField32(PTR) \
+    ((mDNSu32)( \
+        (((mDNSu32)((mDNSu8 *)(PTR))[0]) << 24) | \
+        (((mDNSu32)((mDNSu8 *)(PTR))[1]) << 16) | \
+        (((mDNSu32)((mDNSu8 *)(PTR))[2]) <<  8) | \
+         ((mDNSu32)((mDNSu8 *)(PTR))[3])))
+
+mDNSlocal void DNSMessageDump(const DNSMessage *const msg, const mDNSu8 *const end, char *buffer, mDNSu32 buflen)
+{
+    domainname *name;
+    const mDNSu8 *ptr;
+    domainname nameStorage[2];
+    char *dst = buffer;
+    const char *const lim = &buffer[buflen];
+    mDNSu32 i;
+    const mDNSu32 rrcount = msg->h.numAnswers + msg->h.numAuthorities + msg->h.numAdditionals;
+
+    mDNS_snprintf_add(&dst, lim, "DNS %s%s (%lu) (flags %02X%02X) RCODE: %s (%d)%s%s%s%s%s%s ID: %u:",
            DNS_OP_Name(msg->h.flags.b[0] & kDNSFlag0_OP_Mask),
-           msg->h.flags.b[0] & kDNSFlag0_QR_Response ? "Response" : "Query",
+           (msg->h.flags.b[0] & kDNSFlag0_QR_Response) ? "Response" : "Query",
+           (unsigned long)(end - (const mDNSu8 *)msg),
            msg->h.flags.b[0], msg->h.flags.b[1],
            DNS_RC_Name(msg->h.flags.b[1] & kDNSFlag1_RC_Mask),
            msg->h.flags.b[1] & kDNSFlag1_RC_Mask,
-           msg->h.flags.b[0] & kDNSFlag0_AA ? "AA " : "",
-           msg->h.flags.b[0] & kDNSFlag0_TC ? "TC " : "",
-           msg->h.flags.b[0] & kDNSFlag0_RD ? "RD " : "",
-           msg->h.flags.b[1] & kDNSFlag1_RA ? "RA " : "",
-           msg->h.flags.b[1] & kDNSFlag1_AD ? "AD " : "",
-           msg->h.flags.b[1] & kDNSFlag1_CD ? "CD " : "",
-           mDNSVal16(msg->h.id),
-           end - msg->data,
-           sbuffer, mDNSVal16(srcport), dbuffer,
-           (msg->h.flags.b[0] & kDNSFlag0_TC) ? " (truncated)" : ""
-           );
-
-    LogInfo("%2d %s", msg->h.numQuestions, IsUpdate ? "Zone" : "Questions");
-    for (i = 0; i < msg->h.numQuestions && ptr; i++)
+           (msg->h.flags.b[0] & kDNSFlag0_AA) ? " AA" : "",
+           (msg->h.flags.b[0] & kDNSFlag0_TC) ? " TC" : "",
+           (msg->h.flags.b[0] & kDNSFlag0_RD) ? " RD" : "",
+           (msg->h.flags.b[1] & kDNSFlag1_RA) ? " RA" : "",
+           (msg->h.flags.b[1] & kDNSFlag1_AD) ? " AD" : "",
+           (msg->h.flags.b[1] & kDNSFlag1_CD) ? " CD" : "",
+           mDNSVal16(msg->h.id));
+
+    name = mDNSNULL;
+    ptr  = msg->data;
+    for (i = 0; i < msg->h.numQuestions; i++)
     {
-        ptr = getQuestion(msg, ptr, end, mDNSInterface_Any, &q);
-        if (ptr) LogInfo("%2d %##s %s", i, q.qname.c, DNSTypeName(q.qtype));
+        mDNSu16 qtype, qclass;
+
+        name = &nameStorage[0];
+        ptr = getDomainName(msg, ptr, end, name);
+        if (!ptr) goto exit;
+
+        if ((end - ptr) < 4) goto exit;
+        qtype  = ReadField16(&ptr[0]);
+        qclass = ReadField16(&ptr[2]);
+        ptr += 4;
+
+        mDNS_snprintf_add(&dst, lim, " %##s %s", name->c, DNSTypeString(qtype));
+        if (qclass != kDNSClass_IN) mDNS_snprintf_add(&dst, lim, "/%u", qclass);
+        mDNS_snprintf_add(&dst, lim, "?");
     }
-    ptr = DumpRecords(m, msg, ptr, end, msg->h.numAnswers,     IsUpdate ? "Prerequisites" : "Answers");
-    ptr = DumpRecords(m, msg, ptr, end, msg->h.numAuthorities, IsUpdate ? "Updates"       : "Authorities");
-          DumpRecords(m, msg, ptr, end, msg->h.numAdditionals, "Additionals");
-    LogInfo("--------------");
+
+    mDNS_snprintf_add(&dst, lim, " %u/%u/%u", msg->h.numAnswers, msg->h.numAuthorities, msg->h.numAdditionals);
+    for (i = 0; i < rrcount; i++)
+    {
+        mDNSu16 rrtype, rrclass, rdlength;
+        mDNSu32 ttl;
+        int handled;
+        const mDNSu8 *rdata;
+        const domainname *const previousName = name;
+
+        name = &nameStorage[(name == &nameStorage[0]) ? 1 : 0];
+        ptr = getDomainName(msg, ptr, end, name);
+        if (!ptr) goto exit;
+
+        if ((end - ptr) < 10) goto exit;
+        rrtype   = ReadField16(&ptr[0]);
+        rrclass  = ReadField16(&ptr[2]);
+        ttl      = ReadField32(&ptr[4]);
+        rdlength = ReadField16(&ptr[8]);
+        ptr += 10;
+
+        if ((end - ptr) < rdlength) goto exit;
+        rdata = ptr;
+
+        if (i > 0) mDNS_snprintf_add(&dst, lim, ",");
+        if (!previousName || !SameDomainName(name, previousName)) mDNS_snprintf_add(&dst, lim, " %##s", name);
+
+        mDNS_snprintf_add(&dst, lim, " %s", DNSTypeString(rrtype));
+        if (rrclass != kDNSClass_IN) mDNS_snprintf_add(&dst, lim, "/%u", rrclass);
+        mDNS_snprintf_add(&dst, lim, " ");
+
+        handled = mDNSfalse;
+        switch (rrtype)
+        {
+        case kDNSType_A:
+            if (rdlength == 4)
+            {
+                mDNS_snprintf_add(&dst, lim, "%.4a", rdata);
+                handled = mDNStrue;
+            }
+            break;
+
+        case kDNSType_AAAA:
+            if (rdlength == 16)
+            {
+                mDNS_snprintf_add(&dst, lim, "%.16a", rdata);
+                handled = mDNStrue;
+            }
+            break;
+
+        case kDNSType_CNAME:
+            ptr = getDomainName(msg, rdata, end, name);
+            if (!ptr) goto exit;
+
+            mDNS_snprintf_add(&dst, lim, "%##s", name);
+            handled = mDNStrue;
+            break;
+
+        case kDNSType_SOA:
+        {
+            mDNSu32 serial, refresh, retry, expire, minimum;
+            domainname *const mname = &nameStorage[0];
+            domainname *const rname = &nameStorage[1];
+            name = mDNSNULL;
+
+            ptr = getDomainName(msg, rdata, end, mname);
+             if (!ptr) goto exit;
+
+            ptr = getDomainName(msg, ptr, end, rname);
+            if (!ptr) goto exit;
+
+            if ((end - ptr) < 20) goto exit;
+            serial  = ReadField32(&ptr[0]);
+            refresh = ReadField32(&ptr[4]);
+            retry   = ReadField32(&ptr[8]);
+            expire  = ReadField32(&ptr[12]);
+            minimum = ReadField32(&ptr[16]);
+
+            mDNS_snprintf_add(&dst, lim, "%##s %##s %lu %lu %lu %lu %lu", mname, rname, (unsigned long)serial,
+                (unsigned long)refresh, (unsigned long)retry, (unsigned long)expire, (unsigned long)minimum);
+
+            handled = mDNStrue;
+            break;
+        }
+
+        default:
+            break;
+        }
+        if (!handled) mDNS_snprintf_add(&dst, lim, "RDATA[%u]: %.*H", rdlength, rdlength, rdata);
+        mDNS_snprintf_add(&dst, lim, " (%lu)", (unsigned long)ttl);
+        ptr = rdata + rdlength;
+    }
+
+exit:
+    return;
+}
+
+// Note: DumpPacket expects the packet header fields in host byte order, not network byte order
+mDNSexport void DumpPacket(mStatus status, mDNSBool sent, char *transport,
+                           const mDNSAddr *srcaddr, mDNSIPPort srcport,
+                           const mDNSAddr *dstaddr, mDNSIPPort dstport, const DNSMessage *const msg, const mDNSu8 *const end)
+{
+    char buffer[512];
+    char *dst = buffer;
+    const char *const lim = &buffer[512];
+
+    buffer[0] = '\0';
+    if (!status) mDNS_snprintf_add(&dst, lim, sent ? "Sent" : "Received");
+    else         mDNS_snprintf_add(&dst, lim, "ERROR %d %sing", status, sent ? "Send" : "Receiv");
+
+    mDNS_snprintf_add(&dst, lim, " %s DNS Message %u bytes from ", transport, (unsigned long)(end - (const mDNSu8 *)msg));
+
+    if (sent) mDNS_snprintf_add(&dst, lim, "port %d", mDNSVal16(srcport));
+    else      mDNS_snprintf_add(&dst, lim, "%#a:%d", srcaddr, mDNSVal16(srcport));
+
+    if (dstaddr || !mDNSIPPortIsZero(dstport)) mDNS_snprintf_add(&dst, lim, " to %#a:%d", dstaddr, mDNSVal16(dstport));
+
+    LogInfo("%s", buffer);
+
+    buffer[0] = '\0';
+    DNSMessageDump(msg, end, buffer, (mDNSu32)sizeof(buffer));
+    LogInfo("%s", buffer);
 }
 
 // ***************************************************************************
@@ -3824,7 +3950,7 @@ mDNSexport mStatus mDNSSendDNSMessage(mDNS *const m, DNSMessage *const msg, mDNS
 
     // Dump the packet with the HINFO and TSIG
     if (mDNS_PacketLoggingEnabled && !mDNSOpaque16IsZero(msg->h.id))
-        DumpPacket(m, status, mDNStrue, sock && (sock->flags & kTCPSocketFlags_UseTLS) ? "TLS" : sock ? "TCP" : "UDP", mDNSNULL, src ? src->port : MulticastDNSPort, dst, dstport, msg, end);
+        DumpPacket(status, mDNStrue, sock && (sock->flags & kTCPSocketFlags_UseTLS) ? "TLS" : sock ? "TCP" : "UDP", mDNSNULL, src ? src->port : MulticastDNSPort, dst, dstport, msg, end);
 
     // put the number of additionals back the way it was
     msg->h.numAdditionals = numAdditionals;
@@ -4053,6 +4179,9 @@ static const struct mDNSprintf_format
     unsigned int precision;
 } mDNSprintf_format_default = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 
+#define kHexDigitsLowercase "0123456789abcdef"
+#define kHexDigitsUppercase "0123456789ABCDEF";
+
 mDNSexport mDNSu32 mDNS_vsnprintf(char *sbuffer, mDNSu32 buflen, const char *fmt, va_list arg)
 {
     mDNSu32 nwritten = 0;
@@ -4064,6 +4193,7 @@ mDNSexport mDNSu32 mDNS_vsnprintf(char *sbuffer, mDNSu32 buflen, const char *fmt
     for (c = *fmt; c != 0; c = *++fmt)
     {
         unsigned long n;
+        int hexdump = mDNSfalse;
 		if (c != '%')
         {
             *sbuffer++ = (char)c;
@@ -4190,10 +4320,63 @@ decimal:    if (!F.havePrecision)
                                                    a[0], a[1], a[2], a[3]); break;
                         case  6: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "%02X:%02X:%02X:%02X:%02X:%02X",
                                                    a[0], a[1], a[2], a[3], a[4], a[5]); break;
-                        case 16: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB),
-                                                   "%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X",
-                                                   a[0x0], a[0x1], a[0x2], a[0x3], a[0x4], a[0x5], a[0x6], a[0x7],
-                                                   a[0x8], a[0x9], a[0xA], a[0xB], a[0xC], a[0xD], a[0xE], a[0xF]); break;
+                        case 16: {
+                            // Print IPv6 addresses according to RFC 5952, A Recommendation for IPv6 Address Text
+                            // Representation. See <https://tools.ietf.org/html/rfc5952>.
+
+                            int idx, runLen = 0, runStart = 0, maxRunLen = 0, maxRunStart = 0, maxRunEnd;
+
+                            // Find the leftmost longest run of consecutive zero hextets.
+                            for (idx = 0; idx < 8; ++idx)
+                            {
+                                const unsigned int hextet = (a[idx * 2] << 8) | a[(idx * 2) + 1];
+                                if (hextet == 0)
+                                {
+                                    if (runLen++ == 0) runStart = idx;
+                                    if (runLen > maxRunLen)
+                                    {
+                                        maxRunStart = runStart;
+                                        maxRunLen   = runLen;
+                                    }
+                                }
+                                else
+                                {
+                                    // If the number of remaining hextets is less than or equal to the length of the longest
+                                    // run so far, then we've found the leftmost longest run.
+                                    if ((8 - (idx + 1)) <= maxRunLen) break;
+                                    runLen = 0;
+                                }
+                            }
+
+                            // Compress the leftmost longest run of two or more consecutive zero hextets as "::".
+                            // For each reminaing hextet, suppress zeros leading up to the least-significant nibble, which
+                            // is always written, even if it's zero. Because of this requirement, it's easier to write the
+                            // IPv6 address in reverse. Also, write a colon separator before each hextet except for the
+                            // first one.
+                            s = mDNS_VACB_Lim;
+                            maxRunEnd = (maxRunLen >= 2) ? (maxRunStart + maxRunLen - 1) : -1;
+                            for (idx = 7; idx >= 0; --idx)
+                            {
+                                if (idx == maxRunEnd)
+                                {
+                                    if (idx == 7) *--s = ':';
+                                    idx = maxRunStart;
+                                    *--s = ':';
+                                }
+                                else
+                                {
+                                    unsigned int hextet = (a[idx * 2] << 8) | a[(idx * 2) + 1];
+                                    do {
+                                        *--s = kHexDigitsLowercase[hextet % 16];
+                                        hextet /= 16;
+                                    } while (hextet);
+                                    if (idx > 0) *--s = ':';
+                                }
+                            }
+                            i = (unsigned int)(mDNS_VACB_Lim - s);
+                        }
+                        break;
+
                         default: i = mDNS_snprintf(mDNS_VACB, sizeof(mDNS_VACB), "%s", "<< ERROR: Must specify"
                                                    " address size (i.e. %.4a=IPv4, %.6a=Ethernet, %.16a=IPv6) >>"); break;
                         }
@@ -4203,9 +4386,9 @@ decimal:    if (!F.havePrecision)
 
             case 'p':  F.havePrecision = F.lSize = 1;
                 F.precision = sizeof(void*) * 2;                // 8 characters on 32-bit; 16 characters on 64-bit
-            case 'X':  digits = "0123456789ABCDEF";
+            case 'X':  digits = kHexDigitsUppercase;
                 goto hexadecimal;
-            case 'x':  digits = "0123456789abcdef";
+            case 'x':  digits = kHexDigitsLowercase;
 hexadecimal: if (F.lSize) n = va_arg(arg, unsigned long);
                 else n = va_arg(arg, unsigned int);
                 if (F.hSize) n = (unsigned short) n;
@@ -4288,6 +4471,12 @@ hexadecimal: if (F.lSize) n = va_arg(arg, unsigned long);
                 { i = F.precision; while (i>0 && (s[i] & 0xC0) == 0x80) i--;}
                 break;
 
+            case 'H': {
+                    s = va_arg(arg, char *);
+                    hexdump = mDNStrue;
+                }
+                break;
+
             case 'n':  s = va_arg(arg, char *);
                 if      (F.hSize) *(short *) s = (short)nwritten;
                 else if (F.lSize) *(long  *) s = (long)nwritten;
@@ -4308,14 +4497,34 @@ hexadecimal: if (F.lSize) n = va_arg(arg, unsigned long);
                     if (++nwritten >= buflen) goto exit;
                 } while (i < --F.fieldWidth);
 
-            // Make sure we don't truncate in the middle of a UTF-8 character.
-            // Note: s[i] is the first eliminated character; i.e. the next character *after* the last character of the
-            // allowed output. If s[i] is a UTF-8 continuation character, then we've cut a unicode character in half,
-            // so back up 'i' until s[i] is no longer a UTF-8 continuation character. (if the input was proprly
-            // formed, s[i] will now be the UTF-8 start character of the multi-byte character we just eliminated).
-            if (i > buflen - nwritten)
-            { i = buflen - nwritten; while (i>0 && (s[i] & 0xC0) == 0x80) i--;}
-            for (j=0; j<i; j++) *sbuffer++ = *s++;          // Write the converted result
+            if (hexdump)
+            {
+                char *dst = sbuffer;
+                const char *const lim = &sbuffer[buflen - nwritten];
+                if (F.havePrecision)
+                {
+                    for (i = 0; (i < F.precision) && (dst < lim); i++)
+                    {
+                        const unsigned int b = (unsigned int) *s++;
+                        if (i > 0)     *dst++ = ' ';
+                        if (dst < lim) *dst++ = kHexDigitsLowercase[(b >> 4) & 0xF];
+                        if (dst < lim) *dst++ = kHexDigitsLowercase[ b       & 0xF];
+                    }
+                }
+                i = (unsigned int)(dst - sbuffer);
+                sbuffer = dst;
+            }
+            else
+            {
+                // Make sure we don't truncate in the middle of a UTF-8 character.
+                // Note: s[i] is the first eliminated character; i.e. the next character *after* the last character of the
+                // allowed output. If s[i] is a UTF-8 continuation character, then we've cut a unicode character in half,
+                // so back up 'i' until s[i] is no longer a UTF-8 continuation character. (if the input was proprly
+                // formed, s[i] will now be the UTF-8 start character of the multi-byte character we just eliminated).
+                if (i > buflen - nwritten)
+                { i = buflen - nwritten; while (i>0 && (s[i] & 0xC0) == 0x80) i--;}
+                for (j=0; j<i; j++) *sbuffer++ = *s++;          // Write the converted result
+            }
             nwritten += i;
             if (nwritten >= buflen) goto exit;
 
diff --git a/mDNSResponder/mDNSCore/DNSCommon.h b/mDNSResponder/mDNSCore/DNSCommon.h
index e1ef261..b100a40 100644
--- a/mDNSResponder/mDNSCore/DNSCommon.h
+++ b/mDNSResponder/mDNSCore/DNSCommon.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -260,7 +260,7 @@ extern const mDNSu8 *LocateAdditionals(const DNSMessage *const msg, const mDNSu8
 extern const mDNSu8 *LocateOptRR(const DNSMessage *const msg, const mDNSu8 *const end, int minsize);
 extern const rdataOPT *GetLLQOptData(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end);
 extern mDNSBool GetPktLease(mDNS *const m, const DNSMessage *const msg, const mDNSu8 *const end, mDNSu32 *const lease);
-extern void DumpPacket(mDNS *const m, mStatus status, mDNSBool sent, char *transport,
+extern void DumpPacket(mStatus status, mDNSBool sent, char *transport,
                        const mDNSAddr *srcaddr, mDNSIPPort srcport,
                        const mDNSAddr *dstaddr, mDNSIPPort dstport, const DNSMessage *const msg, const mDNSu8 *const end);
 extern mDNSBool RRAssertsNonexistence(const ResourceRecord *const rr, mDNSu16 type);
diff --git a/mDNSResponder/mDNSCore/mDNS.c b/mDNSResponder/mDNSCore/mDNS.c
index 0788ab6..8deada2 100755
--- a/mDNSResponder/mDNSCore/mDNS.c
+++ b/mDNSResponder/mDNSCore/mDNS.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2017 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -548,6 +548,7 @@ mDNSexport void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, Re
         // because mDNS_StartQuery_internal re-initializes CNAMEReferrals to zero
         q->CNAMEReferrals = c;
 #if AWD_METRICS
+        metrics.expiredAnswerState = q->metrics.expiredAnswerState; //  We want the newly initialized state for this value
         q->metrics = metrics;
 #endif
         if (sock)
@@ -785,6 +786,7 @@ mDNSlocal void AnswerAllLocalQuestionsWithLocalAuthRecord(mDNS *const m, AuthRec
 #define GoodbyeCount ((mDNSu8)3)
 #define WakeupCount ((mDNSu8)18)
 #define MAX_PROBE_RESTARTS ((mDNSu8)20)
+#define MAX_GHOST_TIME ((mDNSs32)((60*60*24*7)*mDNSPlatformOneSecond))  //  One week
 
 // Number of wakeups we send if WakeOnResolve is set in the question
 #define InitialWakeOnResolveCount ((mDNSu8)3)
@@ -3220,21 +3222,25 @@ mDNSlocal mDNSBool BuildQuestion(mDNS *const m, const NetworkInterfaceInfo *intf
 // Depth 3: PTR "_services._dns-sd._udp.local." refers to "_example._tcp.local."; may be stale
 // Currently depths 4 and 5 are not expected to occur; if we did get to depth 5 we'd reconfim any records we
 // found referring to the given name, but not recursively descend any further reconfirm *their* antecedents.
-mDNSlocal void ReconfirmAntecedents(mDNS *const m, const domainname *const name, const mDNSu32 namehash, const int depth)
+mDNSlocal void ReconfirmAntecedents(mDNS *const m, const domainname *const name, const mDNSu32 namehash, const mDNSInterfaceID InterfaceID, const int depth)
 {
     mDNSu32 slot;
-    CacheGroup *cg;
+    const CacheGroup *cg;
     CacheRecord *cr;
     debugf("ReconfirmAntecedents (depth=%d) for %##s", depth, name->c);
+    if (!InterfaceID) return; // mDNS records have a non-zero InterfaceID. If InterfaceID is 0, then there's nothing to do.
     FORALL_CACHERECORDS(slot, cg, cr)
     {
-        domainname *crtarget = GetRRDomainNameTarget(&cr->resrec);
-        if (crtarget && cr->resrec.rdatahash == namehash && SameDomainName(crtarget, name))
+        const domainname *crtarget;
+        if (cr->resrec.InterfaceID != InterfaceID) continue; // Skip non-mDNS records and mDNS records from other interfaces.
+        if (cr->resrec.rdatahash != namehash)      continue; // Skip records whose rdata hash doesn't match the name hash.
+        crtarget = GetRRDomainNameTarget(&cr->resrec);
+        if (crtarget && SameDomainName(crtarget, name))
         {
-            LogInfo("ReconfirmAntecedents: Reconfirming (depth=%d) %s", depth, CRDisplayString(m, cr));
+            LogInfo("ReconfirmAntecedents: Reconfirming (depth=%d, InterfaceID=%p) %s", depth, InterfaceID, CRDisplayString(m, cr));
             mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForNoAnswer);
             if (depth < 5)
-                ReconfirmAntecedents(m, cr->resrec.name, cr->resrec.namehash, depth+1);
+                ReconfirmAntecedents(m, cr->resrec.name, cr->resrec.namehash, InterfaceID, depth+1);
         }
     }
 }
@@ -3612,7 +3618,8 @@ mDNSlocal void SendQueries(mDNS *const m)
                 {
                     q->ThisQInterval = MaxQuestionInterval;
                 }
-                else if (q->CurrentAnswers == 0 && q->ThisQInterval == InitialQuestionInterval * QuestionIntervalStep3 && !q->RequestUnicast &&
+                else if (mDNSOpaque16IsZero(q->TargetQID) && q->InterfaceID &&
+                         q->CurrentAnswers == 0 && q->ThisQInterval == InitialQuestionInterval * QuestionIntervalStep3 && !q->RequestUnicast &&
                          !(RRTypeIsAddressType(q->qtype) && CacheHasAddressTypeForName(m, &q->qname, q->qnamehash)))
                 {
                     // Generally don't need to log this.
@@ -3623,7 +3630,7 @@ mDNSlocal void SendQueries(mDNS *const m)
                     debugf("SendQueries: Zero current answers for %##s (%s); will reconfirm antecedents",
                            q->qname.c, DNSTypeName(q->qtype));
                     // Sending third query, and no answers yet; time to begin doubting the source
-                    ReconfirmAntecedents(m, &q->qname, q->qnamehash, 0);
+                    ReconfirmAntecedents(m, &q->qname, q->qnamehash, q->InterfaceID, 0);
                 }
             }
 
@@ -4107,8 +4114,9 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
     DNSQuestion *const q = m->CurrentQuestion;
     const mDNSBool followcname = FollowCNAME(q, &rr->resrec, AddRecord);
 
-    verbosedebugf("AnswerCurrentQuestionWithResourceRecord:%4lu %s TTL %d %s",
-                  q->CurrentAnswers, AddRecord ? "Add" : "Rmv", rr->resrec.rroriginalttl, CRDisplayString(m, rr));
+    verbosedebugf("AnswerCurrentQuestionWithResourceRecord:%4lu %s (%s) TTL %d %s",
+                  q->CurrentAnswers, AddRecord ? "Add" : "Rmv", MortalityDisplayString(rr->resrec.mortality),
+                  rr->resrec.rroriginalttl, CRDisplayString(m, rr));
 
     // When the response for the question was validated, the entire rrset was validated. If we deliver
     // a RMV for a single record in the rrset, we invalidate the response. If we deliver another add
@@ -4149,7 +4157,11 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
         if (!q->TimeoutQuestion || rr->resrec.RecordType != kDNSRecordTypePacketNegative || (m->timenow - q->StopTime < 0))
             return;
     }
-
+    
+    //  Set the record to immortal if appropriate
+    if (AddRecord == QC_add && Question_uDNS(q) && rr->resrec.RecordType != kDNSRecordTypePacketNegative &&
+        q->allowExpired != AllowExpired_None && rr->resrec.mortality == Mortality_Mortal ) rr->resrec.mortality = Mortality_Immortal; // Update a non-expired cache record to immortal if appropriate
+    
 #if AWD_METRICS
     if ((AddRecord == QC_add) && Question_uDNS(q) && !followcname)
     {
@@ -4170,7 +4182,7 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
                 responseLatencyMs = 0;
             }
 
-            MetricsUpdateDNSQueryStats(queryName, q->qtype, &rr->resrec, q->metrics.querySendCount, responseLatencyMs, isForCellular);
+            MetricsUpdateDNSQueryStats(queryName, q->qtype, &rr->resrec, q->metrics.querySendCount, q->metrics.expiredAnswerState, responseLatencyMs, isForCellular);
             q->metrics.answered = mDNStrue;
         }
         if (q->metrics.querySendCount > 0)
@@ -4183,8 +4195,7 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
     // may be called twice, once when the record is received, and again when it's time to notify local clients.
     // If any counters or similar are added here, care must be taken to ensure that they are not double-incremented by this.
 
-    rr->LastUsed = m->timenow;
-    if (AddRecord == QC_add && !q->DuplicateOf && rr->CRActiveQuestion != q)
+    if (AddRecord == QC_add && !q->DuplicateOf && rr->CRActiveQuestion != q && rr->resrec.mortality != Mortality_Ghost)
     {
         if (!rr->CRActiveQuestion) m->rrcache_active++; // If not previously active, increment rrcache_active count
         debugf("AnswerCurrentQuestionWithResourceRecord: Updating CRActiveQuestion from %p to %p for cache record %s, CurrentAnswer %d",
@@ -4293,14 +4304,21 @@ mDNSexport void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheReco
         return;
     }
 
-    // Note: Proceed with caution here because client callback function is allowed to do anything,
-    // including starting/stopping queries, registering/deregistering records, etc.
-    //
-    // If we get a CNAME back while we are validating the response (i.e., CNAME for DS, DNSKEY, RRSIG),
-    // don't follow them. If it is a ValidationRequired question, wait for the CNAME to be validated
-    // first before following it
-    if ((m->CurrentQuestion == q) && followcname && !ValidatingQuestion(q))
-        AnswerQuestionByFollowingCNAME(m, q, &rr->resrec);
+    if ((m->CurrentQuestion == q) && !ValidatingQuestion(q))
+    {
+        // If we get a CNAME back while we are validating the response (i.e., CNAME for DS, DNSKEY, RRSIG),
+        // don't follow them. If it is a ValidationRequired question, wait for the CNAME to be validated
+        // first before following it
+        if (followcname)  AnswerQuestionByFollowingCNAME(m, q, &rr->resrec);
+        
+        // If we are returning expired RRs, then remember the first expired qname we we can start the query again
+        if (rr->resrec.mortality == Mortality_Ghost && !q->firstExpiredQname.c[0] && (q->allowExpired == AllowExpired_AllowExpiredAnswers) && rr->resrec.RecordType != kDNSRecordTypePacketNegative)
+        {
+            debugf("AnswerCurrentQuestionWithResourceRecord: Keeping track of domain for expired RR %s for question %p", CRDisplayString(m,rr), q);
+            // Note: question->qname is already changed at this point if following a CNAME
+            AssignDomainName(&q->firstExpiredQname, rr->resrec.name);           // Update firstExpiredQname
+        }
+    }
 }
 
 mDNSlocal void CacheRecordDeferredAdd(mDNS *const m, CacheRecord *rr)
@@ -4474,7 +4492,8 @@ mDNSlocal void CacheRecordRmv(mDNS *const m, CacheRecord *rr)
         // response. A cache may be present that answers this question e.g., cache entry generated
         // before the question became suppressed. We need to skip the suppressed questions here as
         // the RMV event has already been generated.
-        if (!QuerySuppressed(q) && ResourceRecordAnswersQuestion(&rr->resrec, q))
+        if (!QuerySuppressed(q) && ResourceRecordAnswersQuestion(&rr->resrec, q) &&
+            (q->allowExpired == AllowExpired_None || rr->resrec.mortality == Mortality_Mortal))
         {
             verbosedebugf("CacheRecordRmv %p %s", rr, CRDisplayString(m, rr));
             q->FlappingInterface1 = mDNSNULL;
@@ -4503,11 +4522,11 @@ mDNSlocal void CacheRecordRmv(mDNS *const m, CacheRecord *rr)
             }
             if (rr->resrec.rdata->MaxRDLength) // Never generate "remove" events for negative results
             {
-                if (q->CurrentAnswers == 0)
+                if ((q->CurrentAnswers == 0) && mDNSOpaque16IsZero(q->TargetQID))
                 {
                     LogInfo("CacheRecordRmv: Last answer for %##s (%s) expired from cache; will reconfirm antecedents",
                             q->qname.c, DNSTypeName(q->qtype));
-                    ReconfirmAntecedents(m, &q->qname, q->qnamehash, 0);
+                    ReconfirmAntecedents(m, &q->qname, q->qnamehash, rr->resrec.InterfaceID, 0);
                 }
                 AnswerCurrentQuestionWithResourceRecord(m, rr, QC_rmv);
             }
@@ -4631,16 +4650,15 @@ mDNSlocal void CheckCacheExpiration(mDNS *const m, const mDNSu32 slot, CacheGrou
     while (*rp)
     {
         CacheRecord *const rr = *rp;
+        mDNSBool recordReleased = mDNSfalse;
         mDNSs32 event = RRExpireTime(rr);
         if (m->timenow - event >= 0)    // If expired, delete it
         {
-            *rp = rr->next;             // Cut it from the list
-
-            verbosedebugf("CheckCacheExpiration: Deleting%7d %7d %p %s",
-                          m->timenow - rr->TimeRcvd, rr->resrec.rroriginalttl, rr->CRActiveQuestion, CRDisplayString(m, rr));
             if (rr->CRActiveQuestion)   // If this record has one or more active questions, tell them it's going away
             {
                 DNSQuestion *q = rr->CRActiveQuestion;
+                verbosedebugf("CheckCacheExpiration: Removing%7d %7d %p %s",
+                              m->timenow - rr->TimeRcvd, rr->resrec.rroriginalttl, rr->CRActiveQuestion, CRDisplayString(m, rr));
                 // When a cache record is about to expire, we expect to do four queries at 80-82%, 85-87%, 90-92% and
                 // then 95-97% of the TTL. If the DNS server does not respond, then we will remove the cache entry
                 // before we pick a new DNS server. As the question interval is set to MaxQuestionInterval, we may
@@ -4657,9 +4675,30 @@ mDNSlocal void CheckCacheExpiration(mDNS *const m, const mDNSu32 slot, CacheGrou
                 CacheRecordRmv(m, rr);
                 m->rrcache_active--;
             }
-            ReleaseCacheRecord(m, rr);
+            
+            event += MAX_GHOST_TIME;                                                    // Adjust so we can check for a ghost expiration
+            if (rr->resrec.mortality == Mortality_Mortal ||                             // Normal expired mortal record that needs released
+                (rr->resrec.mortality == Mortality_Ghost && m->timenow - event >= 0))   // A ghost record that expired more than MAX_GHOST_TIME ago
+            {   //  Release as normal
+                *rp = rr->next;                                     // Cut it from the list before ReleaseCacheRecord
+                verbosedebugf("CheckCacheExpiration: Deleting (%s)%7d %7d %p %s",
+                              MortalityDisplayString(rr->resrec.mortality),
+                              m->timenow - rr->TimeRcvd, rr->resrec.rroriginalttl, rr->CRActiveQuestion, CRDisplayString(m, rr));
+                ReleaseCacheRecord(m, rr);
+                recordReleased = mDNStrue;
+            }
+            else                                                    // An immortal record needs to become a ghost when it expires
+            {   // Don't release this entry
+                if (rr->resrec.mortality == Mortality_Immortal)
+                {
+                    rr->resrec.mortality = Mortality_Ghost;         // Expired immortal records become ghosts
+                    verbosedebugf("CheckCacheExpiration: NOT Deleting (%s)%7d %7d %p %s",
+                                  MortalityDisplayString(rr->resrec.mortality),
+                                  m->timenow - rr->TimeRcvd, rr->resrec.rroriginalttl, rr->CRActiveQuestion, CRDisplayString(m, rr));
+                }
+            }
         }
-        else                            // else, not expired; see if we need to query
+        else                                                        // else, not expired; see if we need to query
         {
             // If waiting to delay delivery, do nothing until then
             if (rr->DelayDelivery && rr->DelayDelivery - m->timenow > 0)
@@ -4682,6 +4721,10 @@ mDNSlocal void CheckCacheExpiration(mDNS *const m, const mDNSu32 slot, CacheGrou
                     }
                 }
             }
+        }
+        
+        if (!recordReleased)  //  Schedule if we did not release the record
+        {
             verbosedebugf("CheckCacheExpiration:%6d %5d %s",
                           (event - m->timenow) / mDNSPlatformOneSecond, CacheCheckGracePeriod(rr), CRDisplayString(m, rr));
             if (m->rrcache_nextcheck[slot] - event > 0)
@@ -4893,12 +4936,7 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
             {
                 // SecsSinceRcvd is whole number of elapsed seconds, rounded down
                 mDNSu32 SecsSinceRcvd = ((mDNSu32)(m->timenow - rr->TimeRcvd)) / mDNSPlatformOneSecond;
-                if (rr->resrec.rroriginalttl <= SecsSinceRcvd)
-                {
-                    LogMsg("AnswerNewQuestion: How is rr->resrec.rroriginalttl %lu <= SecsSinceRcvd %lu for %s %d %d",
-                           rr->resrec.rroriginalttl, SecsSinceRcvd, CRDisplayString(m, rr), m->timenow, rr->TimeRcvd);
-                    continue;   // Go to next one in loop
-                }
+                if (rr->resrec.rroriginalttl <= SecsSinceRcvd && q->allowExpired != AllowExpired_AllowExpiredAnswers) continue;   // Go to next one in loop
 
                 // If this record set is marked unique, then that means we can reasonably assume we have the whole set
                 // -- we don't need to rush out on the network and query immediately to see if there are more answers out there
@@ -4908,6 +4946,9 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
                 if (rr->resrec.rdlength > SmallRecordLimit) q->LargeAnswers++;
                 if (rr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) q->UniqueAnswers++;
                 AnsweredFromCache = mDNStrue;
+#if AWD_METRICS
+                if (q->metrics.expiredAnswerState == ExpiredAnswer_Allowed) q->metrics.expiredAnswerState = ExpiredAnswer_AnsweredWithExpired;
+#endif
                 AnswerCurrentQuestionWithResourceRecord(m, rr, QC_add);
                 if (m->CurrentQuestion != q) break;     // If callback deleted q, then we're finished here
             }
@@ -4930,6 +4971,21 @@ mDNSlocal void AnswerNewQuestion(mDNS *const m)
 
     if (m->CurrentQuestion != q) { debugf("AnswerNewQuestion: Question deleted while giving negative answer"); goto exit; }
 
+    if (q->allowExpired == AllowExpired_AllowExpiredAnswers)
+    {
+        q->allowExpired = AllowExpired_MakeAnswersImmortal;             // After looking through the cache for an answer, demote to make immortal
+        if (q->firstExpiredQname.c[0])                                  // If an original query name was saved on an expired answer, start it over in case it is updated
+        {
+            LogMsg("AnswerNewQuestion: Restarting original question %p firstExpiredQname %##s for allowExpiredAnswers question", q, &q->firstExpiredQname.c);
+            mDNS_StopQuery_internal(m, q);                              // Stop old query
+            AssignDomainName(&q->qname, &q->firstExpiredQname);         // Update qname
+            q->qnamehash = DomainNameHashValue(&q->qname);              // and namehash
+            mDNS_StartQuery_internal(m, q);                             // start new query
+            q->CNAMEReferrals = 0;                                      // Reset referral count
+            q->firstExpiredQname.c[0] = 0;                              // Erase the domain name
+        }
+    }
+    
     // Note: When a query gets suppressed or retried with search domains, we de-activate the question.
     // Hence we don't execute the following block of code for those cases.
     if (ShouldQueryImmediately && ActiveQuestion(q))
@@ -7467,6 +7523,7 @@ mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, con
     AuthRecord  **nrp                = &ResponseRecords;
 
 #if POOF_ENABLED
+    mDNSBool    notD2D = !mDNSPlatformInterfaceIsD2D(InterfaceID);  // We don't run the POOF algorithm on D2D interfaces.
     CacheRecord  *ExpectedAnswers    = mDNSNULL;            // Records in our cache we expect to see updated
     CacheRecord **eap                = &ExpectedAnswers;
 #endif // POOF_ENABLED
@@ -7626,18 +7683,21 @@ mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, con
         if (QuestionNeedsMulticastResponse && !(query->h.flags.b[0] & kDNSFlag0_TC))
         {
 #if POOF_ENABLED
-            CacheGroup *cg = CacheGroupForName(m, pktq.qnamehash, &pktq.qname);
-            CacheRecord *cr;
-
-            // Make a list indicating which of our own cache records we expect to see updated as a result of this query
-            // Note: Records larger than 1K are not habitually multicast, so don't expect those to be updated
-            for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
-                if (SameNameRecordAnswersQuestion(&cr->resrec, &pktq) && cr->resrec.rdlength <= SmallRecordLimit)
-                    if (!cr->NextInKAList && eap != &cr->NextInKAList)
-                    {
-                        *eap = cr;
-                        eap = &cr->NextInKAList;
-                    }
+            if (notD2D)
+            {
+                CacheGroup *cg = CacheGroupForName(m, pktq.qnamehash, &pktq.qname);
+                CacheRecord *cr;
+    
+                // Make a list indicating which of our own cache records we expect to see updated as a result of this query
+                // Note: Records larger than 1K are not habitually multicast, so don't expect those to be updated
+                for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+                    if (SameNameRecordAnswersQuestion(&cr->resrec, &pktq) && cr->resrec.rdlength <= SmallRecordLimit)
+                        if (!cr->NextInKAList && eap != &cr->NextInKAList)
+                        {
+                            *eap = cr;
+                            eap = &cr->NextInKAList;
+                        }
+            }
 #endif // POOF_ENABLED
 
             // Check if this question is the same as any of mine.
@@ -7728,15 +7788,18 @@ mDNSlocal mDNSu8 *ProcessQuery(mDNS *const m, const DNSMessage *const query, con
             ourcacherr = FindIdenticalRecordInCache(m, &m->rec.r.resrec);
 
 #if POOF_ENABLED
-            // Having built our ExpectedAnswers list from the questions in this packet, we then remove
-            // any records that are suppressed by the Known Answer list in this packet.
-            eap = &ExpectedAnswers;
-            while (*eap)
+            if (notD2D)
             {
-                CacheRecord *cr = *eap;
-                if (cr->resrec.InterfaceID == InterfaceID && IdenticalResourceRecord(&m->rec.r.resrec, &cr->resrec))
-                { *eap = cr->NextInKAList; cr->NextInKAList = mDNSNULL; }
-                else eap = &cr->NextInKAList;
+                // Having built our ExpectedAnswers list from the questions in this packet, we then remove
+                // any records that are suppressed by the Known Answer list in this packet.
+                eap = &ExpectedAnswers;
+                while (*eap)
+                {
+                    CacheRecord *cr = *eap;
+                    if (cr->resrec.InterfaceID == InterfaceID && IdenticalResourceRecord(&m->rec.r.resrec, &cr->resrec))
+                    { *eap = cr->NextInKAList; cr->NextInKAList = mDNSNULL; }
+                    else eap = &cr->NextInKAList;
+                }
             }
 #endif // POOF_ENABLED
 
@@ -7900,7 +7963,7 @@ exit:
     }
 
 #if POOF_ENABLED
-    while (ExpectedAnswers)
+    while (ExpectedAnswers && notD2D)
     {
         CacheRecord *cr = ExpectedAnswers;
         ExpectedAnswers = cr->NextInKAList;
@@ -8106,10 +8169,11 @@ mDNSexport CacheRecord *CreateNewCacheEntry(mDNS *const m, const mDNSu32 slot, C
     if (!rr) NoCacheAnswer(m, &m->rec.r);
     else
     {
-        RData *saveptr = rr->resrec.rdata;      // Save the rr->resrec.rdata pointer
-        *rr = m->rec.r;                         // Block copy the CacheRecord object
-        rr->resrec.rdata  = saveptr;            // Restore rr->resrec.rdata after the structure assignment
-        rr->resrec.name   = cg->name;           // And set rr->resrec.name to point into our CacheGroup header
+        RData *saveptr         = rr->resrec.rdata;   // Save the rr->resrec.rdata pointer
+        *rr                    = m->rec.r;           // Block copy the CacheRecord object
+        rr->resrec.rdata       = saveptr;            // Restore rr->resrec.rdata after the structure assignment
+        rr->resrec.name        = cg->name;           // And set rr->resrec.name to point into our CacheGroup header
+        rr->resrec.mortality   = Mortality_Mortal;
 
         // We need to add the anonymous info before we call CacheRecordAdd so that
         // if it finds a matching question with this record, it bumps up the counters like
@@ -8176,6 +8240,7 @@ mDNSlocal void RefreshCacheRecord(mDNS *const m, CacheRecord *rr, mDNSu32 ttl)
     rr->TimeRcvd             = m->timenow;
     rr->resrec.rroriginalttl = ttl;
     rr->UnansweredQueries = 0;
+    if (rr->resrec.mortality != Mortality_Mortal) rr->resrec.mortality = Mortality_Immortal;
     SetNextCacheCheckTimeForRecord(m, rr);
 }
 
@@ -8246,7 +8311,7 @@ mDNSlocal mDNSBool IsResponseAcceptable(mDNS *const m, const CacheRecord *crlist
 
         if (target && cr->resrec.rdatahash == rr->namehash && SameDomainName(target, rr->name))
         {
-            LogInfo("IsResponseAcceptable: Found a matching entry for %##s in the CacheFlushRecords %s", rr->name->c, CRDisplayString(m, cr));
+            LogDebug("IsResponseAcceptable: Found a matching entry for %##s in the CacheFlushRecords %s", rr->name->c, CRDisplayString(m, cr));
             return (mDNStrue);
         }
     }
@@ -8744,6 +8809,12 @@ mDNSlocal CacheRecord* mDNSCoreReceiveCacheCheck(mDNS *const m, const DNSMessage
                 DNSQuestion *q;
 
                 m->mDNSStats.CacheRefreshed++;
+                
+                if (rr->resrec.mortality == Mortality_Ghost && unicastQuestion && (unicastQuestion->allowExpired != AllowExpired_AllowExpiredAnswers) && !rr->DelayDelivery)
+                {
+                    rr->DelayDelivery = NonZeroTime(m->timenow);
+                    debugf("mDNSCoreReceiveCacheCheck: Reset DelayDelivery for mortalityExpired EXP:%d RR %s", m->timenow - RRExpireTime(rr), CRDisplayString(m, rr));
+                }
 
                 if (rr->resrec.rroriginalttl == 0) debugf("uDNS rescuing %s", CRDisplayString(m, rr));
                 RefreshCacheRecord(m, rr, m->rec.r.resrec.rroriginalttl);
@@ -9513,6 +9584,12 @@ exit:
                 r1->resrec.rrtype      == r2->resrec.rrtype &&
                 r1->resrec.rrclass     == r2->resrec.rrclass)
             {
+                if (r1->resrec.mortality == Mortality_Mortal && r2->resrec.mortality != Mortality_Mortal)
+                {
+                    verbosedebugf("mDNSCoreReceiveResponse: R1(%p) is being immortalized by R2(%p)", r1, r2);
+                    r1->resrec.mortality = Mortality_Immortal;   //  Immortalize the replacement record
+                }
+
                 // If record is recent, just ensure the whole RRSet has the same TTL (as required by DNS semantics)
                 // else, if record is old, mark it to be flushed
                 if (m->timenow - r2->TimeRcvd < mDNSPlatformOneSecond && RRExpireTime(r2) - m->timenow > mDNSPlatformOneSecond)
@@ -9588,7 +9665,23 @@ exit:
                 }
                 else
                 {
+#if AWD_METRICS
+                    if (r2->resrec.mortality == Mortality_Ghost)
+                    {
+                        DNSQuestion * q;
+                        for (q = m->Questions; q; q=q->next)
+                        {
+                            if (!q->LongLived && ActiveQuestion(q) &&
+                                ResourceRecordAnswersQuestion(&r2->resrec, q) &&
+                                q->metrics.expiredAnswerState == ExpiredAnswer_AnsweredWithExpired)
+                            {
+                                q->metrics.expiredAnswerState = ExpiredAnswer_ExpiredAnswerChanged;
+                            }
+                        }
+                    }
+#endif
                     // Old uDNS records are scheduled to be purged instead of given at most one second to live.
+                    r2->resrec.mortality = Mortality_Mortal;       //  We want it purged, so remove any immortality
                     mDNS_PurgeCacheResourceRecord(m, r2);
                     purgedRecords = mDNStrue;
                 }
@@ -10207,7 +10300,7 @@ mDNSlocal void mDNSCoreReceiveUpdate(mDNS *const m,
     if (!InterfaceID || !m->SPSSocket || !mDNSSameIPPort(dstport, m->SPSSocket->port)) return;
 
     if (mDNS_PacketLoggingEnabled)
-        DumpPacket(m, mStatus_NoError, mDNSfalse, "UDP", srcaddr, srcport, dstaddr, dstport, msg, end);
+        DumpPacket(mStatus_NoError, mDNSfalse, "UDP", srcaddr, srcport, dstaddr, dstport, msg, end);
 
     ptr = LocateOptRR(msg, end, DNSOpt_LeaseData_Space + DNSOpt_OwnerData_ID_Space);
     if (ptr)
@@ -10466,7 +10559,6 @@ mDNSexport void MakeNegativeCacheRecord(mDNS *const m, CacheRecord *const cr,
     cr->TimeRcvd           = m->timenow;
     cr->DelayDelivery      = 0;
     cr->NextRequiredQuery  = m->timenow;
-    cr->LastUsed           = m->timenow;
     cr->CRActiveQuestion   = mDNSNULL;
     cr->UnansweredQueries  = 0;
     cr->LastUnansweredTime = 0;
@@ -10561,7 +10653,7 @@ mDNSexport void mDNSCoreReceive(mDNS *const m, DNSMessage *const msg, const mDNS
         {
             ifid = mDNSInterface_Any;
             if (mDNS_PacketLoggingEnabled)
-                DumpPacket(m, mStatus_NoError, mDNSfalse, TLS ? "TLS" : !dstaddr ? "TCP" : "UDP", srcaddr, srcport, dstaddr, dstport, msg, end);
+                DumpPacket(mStatus_NoError, mDNSfalse, TLS ? "TLS" : !dstaddr ? "TCP" : "UDP", srcaddr, srcport, dstaddr, dstport, msg, end);
             uDNS_ReceiveMsg(m, msg, end, srcaddr, srcport);
             // Note: mDNSCore also needs to get access to received unicast responses
         }
@@ -11126,11 +11218,11 @@ mDNSlocal DNSServer *GetServerForName(mDNS *m, const domainname *name, mDNSInter
     curmatch = GetBestServer(m, name, InterfaceID, ServiceID, allValid, mDNSNULL, mDNStrue);
 
     if (curmatch != mDNSNULL)
-        LogInfo("GetServerForName: DNS server %#a:%d (Penalty Time Left %d) (Scope %s:%p) found for name %##s", &curmatch->addr,
+        LogInfo("GetServerForName: DNS server %#a:%d (Penalty Time Left %d) (Scope %s:%p) for %##s", &curmatch->addr,
                 mDNSVal16(curmatch->port), (curmatch->penaltyTime ? (curmatch->penaltyTime - m->timenow) : 0), ifname ? ifname : "None",
                 InterfaceID, name);
     else
-        LogInfo("GetServerForName: no DNS server (Scope %s:%p) found for name %##s", ifname ? ifname : "None", InterfaceID, name);
+        LogInfo("GetServerForName: no DNS server (Scope %s:%p) for %##s", ifname ? ifname : "None", InterfaceID, name);
 
     return(curmatch);
 }
@@ -11159,14 +11251,14 @@ mDNSexport DNSServer *GetServerForQuestion(mDNS *m, DNSQuestion *question)
 
     if (curmatch != mDNSNULL)
     {
-        LogInfo("GetServerForQuestion: %p DNS server (%p) %#a:%d (Penalty Time Left %d) (Scope %s:%p:%d) found for name %##s (%s)",
+        LogInfo("GetServerForQuestion: %p DNS server (%p) %#a:%d (Penalty Time Left %d) (Scope %s:%p:%d) for %##s (%s)",
                 question, curmatch, &curmatch->addr, mDNSVal16(curmatch->port),
                 (curmatch->penaltyTime ? (curmatch->penaltyTime - m->timenow) : 0), ifname ? ifname : "None",
                 InterfaceID, question->ServiceID, name, DNSTypeName(question->qtype));
     }
     else
     {
-        LogInfo("GetServerForQuestion: %p no DNS server (Scope %s:%p:%d) found for name %##s (%s)",
+        LogInfo("GetServerForQuestion: %p no DNS server (Scope %s:%p:%d) for %##s (%s)",
             question, ifname ? ifname : "None", InterfaceID, question->ServiceID, name, DNSTypeName(question->qtype));
     }
 
@@ -11218,14 +11310,14 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(mDNS *const m, DNSQuestion *q, DNS
     // Some callers don't check for the qtype
     if (q->qtype != kDNSType_A && q->qtype != kDNSType_AAAA)
     {
-        LogInfo("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, not A/AAAA type", q->qname.c, DNSTypeName(q->qtype));
+        LogDebug("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, not A/AAAA type", q->qname.c, DNSTypeName(q->qtype));
         return mDNSfalse;
     }
 
     // Private domains are exempted irrespective of what the DNSServer says
     if (IsPrivateDomain(m, q))
     {
-        LogInfo("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, Private Domain", q->qname.c, DNSTypeName(q->qtype));
+        LogDebug("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, Private Domain", q->qname.c, DNSTypeName(q->qtype));
         return mDNSfalse;
     }
 
@@ -11238,20 +11330,20 @@ mDNSlocal mDNSBool ShouldSuppressUnicastQuery(mDNS *const m, DNSQuestion *q, DNS
     // Check if the DNS Configuration allows A/AAAA queries to be sent
     if ((q->qtype == kDNSType_A) && (d->req_A))
     {
-        LogInfo("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, DNSServer %##s %#a:%d allows A queries", q->qname.c,
+        LogDebug("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, DNSServer %##s %#a:%d allows A queries", q->qname.c,
                 DNSTypeName(q->qtype), d->domain.c, &d->addr, mDNSVal16(d->port));
         return mDNSfalse;
     }
     if ((q->qtype == kDNSType_AAAA) && (d->req_AAAA))
     {
-        LogInfo("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, DNSServer %##s %#a:%d allows AAAA queries", q->qname.c,
+        LogDebug("ShouldSuppressUnicastQuery: Query not suppressed for %##s, qtype %s, DNSServer %##s %#a:%d allows AAAA queries", q->qname.c,
                 DNSTypeName(q->qtype), d->domain.c, &d->addr, mDNSVal16(d->port));
         return mDNSfalse;
     }
 #if USE_DNS64
     if (DNS64IsQueryingARecord(q->dns64.state))
     {
-        LogInfo("ShouldSuppressUnicastQuery: DNS64 query not suppressed for %##s, qtype %s", q->qname.c, DNSTypeName(q->qtype));
+        LogDebug("ShouldSuppressUnicastQuery: DNS64 query not suppressed for %##s, qtype %s", q->qname.c, DNSTypeName(q->qtype));
         return mDNSfalse;
     }
 #endif
@@ -11652,6 +11744,7 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
     question->StopTime            = (question->TimeoutQuestion) ? question->StopTime : 0;
 #if AWD_METRICS
     mDNSPlatformMemZero(&question->metrics, sizeof(question->metrics));
+    question->metrics.expiredAnswerState = (question->allowExpired != AllowExpired_None) ? ExpiredAnswer_Allowed : ExpiredAnswer_None;
 #endif
 
     // Need not initialize the DNS Configuration for Local Only OR P2P Questions when timeout not specified
@@ -11672,7 +11765,7 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
         }
 
         question->qDNSServer = GetServerForQuestion(m, question);
-        LogInfo("InitDNSConfig: question %p %##s (%s) Timeout %d, DNS Server %#a:%d",
+        LogDebug("InitDNSConfig: question %p %##s (%s) Timeout %d, DNS Server %#a:%d",
                  question, question->qname.c, DNSTypeName(question->qtype), timeout,
                  question->qDNSServer ? &question->qDNSServer->addr : mDNSNULL,
                  mDNSVal16(question->qDNSServer ? question->qDNSServer->port : zeroIPPort));
@@ -12113,7 +12206,7 @@ mDNSexport mStatus mDNS_StopQuery_internal(mDNS *const m, DNSQuestion *const que
         queryName  = question->metrics.originalQName ? question->metrics.originalQName : &question->qname;
         isForCell  = (question->qDNSServer && question->qDNSServer->cellIntf);
         durationMs = ((m->timenow - question->metrics.firstQueryTime) * 1000) / mDNSPlatformOneSecond;
-        MetricsUpdateDNSQueryStats(queryName, question->qtype, mDNSNULL, question->metrics.querySendCount, durationMs, isForCell);
+        MetricsUpdateDNSQueryStats(queryName, question->qtype, mDNSNULL, question->metrics.querySendCount, question->metrics.expiredAnswerState, durationMs, isForCell);
     }
 #endif
     // Take care to cut question from list *before* calling UpdateQuestionDuplicates
@@ -12333,7 +12426,7 @@ mDNSexport mStatus mDNS_Reconfirm(mDNS *const m, CacheRecord *const cr)
     mStatus status;
     mDNS_Lock(m);
     status = mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForNoAnswer);
-    if (status == mStatus_NoError) ReconfirmAntecedents(m, cr->resrec.name, cr->resrec.namehash, 0);
+    if (status == mStatus_NoError) ReconfirmAntecedents(m, cr->resrec.name, cr->resrec.namehash, cr->resrec.InterfaceID, 0);
     mDNS_Unlock(m);
     return(status);
 }
@@ -12346,7 +12439,7 @@ mDNSexport mStatus mDNS_ReconfirmByValue(mDNS *const m, ResourceRecord *const rr
     cr = FindIdenticalRecordInCache(m, rr);
     debugf("mDNS_ReconfirmByValue: %p %s", cr, RRDisplayString(m, rr));
     if (cr) status = mDNS_Reconfirm_internal(m, cr, kDefaultReconfirmTimeForNoAnswer);
-    if (status == mStatus_NoError) ReconfirmAntecedents(m, cr->resrec.name, cr->resrec.namehash, 0);
+    if (status == mStatus_NoError) ReconfirmAntecedents(m, cr->resrec.name, cr->resrec.namehash, cr->resrec.InterfaceID, 0);
     mDNS_Unlock(m);
     return(status);
 }
@@ -13152,6 +13245,7 @@ mDNSexport void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *se
                     }
                     else
                     {
+                        rr->resrec.mortality = Mortality_Mortal;
                         mDNS_PurgeCacheResourceRecord(m, rr);
                     }
                 }
@@ -13349,7 +13443,7 @@ mDNSexport mDNSu32 deriveD2DFlagsFromAuthRecType(AuthRecType authRecType)
 // If the optional target host parameter is set, then the storage it points to must remain valid for the lifetime of the service registration
 mDNSexport mStatus mDNS_RegisterService(mDNS *const m, ServiceRecordSet *sr,
                                         const domainlabel *const name, const domainname *const type, const domainname *const domain,
-                                        const domainname *const host, mDNSIPPort port, const mDNSu8 txtinfo[], mDNSu16 txtlen,
+                                        const domainname *const host, mDNSIPPort port, RData *const txtrdata, const mDNSu8 txtinfo[], mDNSu16 txtlen,
                                         AuthRecord *SubTypes, mDNSu32 NumSubTypes,
                                         mDNSInterfaceID InterfaceID, mDNSServiceCallback Callback, void *Context, mDNSu32 flags)
 {
@@ -13386,7 +13480,7 @@ mDNSexport mStatus mDNS_RegisterService(mDNS *const m, ServiceRecordSet *sr,
         hostTTL = kHostNameTTL;
 
     mDNS_SetupResourceRecord(&sr->RR_SRV, mDNSNULL, InterfaceID, kDNSType_SRV, hostTTL, recordType, artype, ServiceCallback, sr);
-    mDNS_SetupResourceRecord(&sr->RR_TXT, mDNSNULL, InterfaceID, kDNSType_TXT, kStandardTTL, recordType, artype, ServiceCallback, sr);
+    mDNS_SetupResourceRecord(&sr->RR_TXT, txtrdata, InterfaceID, kDNSType_TXT, kStandardTTL, recordType, artype, ServiceCallback, sr);
 
     // If port number is zero, that means the client is really trying to do a RegisterNoSuchService
     if (mDNSIPPortIsZero(port))
@@ -13596,7 +13690,9 @@ mDNSexport mStatus mDNS_RenameAndReregisterService(mDNS *const m, ServiceRecordS
     else debugf("%##s service (domain %##s) renamed from \"%#s\" to \"%#s\"",type.c, domain.c, name1.c, newname->c);
 
     err = mDNS_RegisterService(m, sr, newname, &type, &domain,
-                               host, sr->RR_SRV.resrec.rdata->u.srv.port, sr->RR_TXT.resrec.rdata->u.txt.c, sr->RR_TXT.resrec.rdlength,
+                               host, sr->RR_SRV.resrec.rdata->u.srv.port,
+                               (sr->RR_TXT.resrec.rdata != &sr->RR_TXT.rdatastorage) ? sr->RR_TXT.resrec.rdata : mDNSNULL,
+                               sr->RR_TXT.resrec.rdata->u.txt.c, sr->RR_TXT.resrec.rdlength,
                                sr->SubTypes, sr->NumSubTypes,
                                sr->RR_PTR.resrec.InterfaceID, sr->ServiceCallback, sr->ServiceContext, sr->flags);
 
@@ -14235,6 +14331,7 @@ mDNSlocal void SleepProxyServerCallback(mDNS *const m, ServiceRecordSet *const s
                 mDNS_RegisterService(m, srs,
                                      &name, &SleepProxyServiceType, &localdomain,
                                      mDNSNULL, m->SPSSocket->port, // Host, port
+                                     mDNSNULL,
                                      (mDNSu8 *)"", 1,           // TXT data, length
                                      mDNSNULL, 0,               // Subtypes (none)
                                      mDNSInterface_Any,         // Interface ID
@@ -14953,6 +15050,7 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
             {
                 LogInfo("uDNS_SetupDNSConfig: Purging Resourcerecord %s, New DNS server %#a , Old DNS server %#a", CRDisplayString(m, cr),
                         &ptr->addr, (cr->resrec.rDNSServer != mDNSNULL ?  &cr->resrec.rDNSServer->addr : mDNSNULL));
+                cr->resrec.mortality = Mortality_Mortal;
                 mDNS_PurgeCacheResourceRecord(m, cr);
             }
             else
@@ -15021,6 +15119,7 @@ mDNSexport mStatus uDNS_SetupDNSConfig(mDNS *const m)
                         cr->resrec.rDNSServer = mDNSNULL;
                     }
 
+                    cr->resrec.mortality = Mortality_Mortal;
                     PurgeOrReconfirmCacheRecord(m, cr, ptr, mDNStrue);
                 }
             }
diff --git a/mDNSResponder/mDNSCore/mDNSDebug.h b/mDNSResponder/mDNSCore/mDNSDebug.h
index 68a696e..d690fd2 100755
--- a/mDNSResponder/mDNSCore/mDNSDebug.h
+++ b/mDNSResponder/mDNSCore/mDNSDebug.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -99,12 +99,14 @@ extern "C" {
         #define LogOperation(... )     do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_OPERATION, __VA_ARGS__);} while (0)
         #define LogSPS(... )           do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_SPS,       __VA_ARGS__);} while (0)
         #define LogInfo(... )          do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_INFO,      __VA_ARGS__);} while (0)
+        #define LogDebug(... )         do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_DEBUG,     __VA_ARGS__);} while (0)
     #elif (MDNS_GNU_VA_ARGS)
         #define debug_noop( ARGS... ) ((void)0)
         #define LogMsg( ARGS... )       LogMsgWithLevel(MDNS_LOG_MSG, ARGS)
         #define LogOperation( ARGS... ) do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_OPERATION, ARGS);} while (0)
         #define LogSPS( ARGS... )       do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_SPS,       ARGS);} while (0)
         #define LogInfo( ARGS... )      do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_INFO,      ARGS);} while (0)
+        #define LogDebug( ARGS... )     do { if (mDNS_LoggingEnabled) LogMsgWithLevel(MDNS_LOG_DEBUG,     ARGS);} while (0)
     #else
         #error Unknown variadic macros
     #endif
@@ -116,10 +118,12 @@ extern "C" {
     #define LogOperation (mDNS_LoggingEnabled == 0) ? ((void)0) : LogOperation_
     #define LogSPS       (mDNS_LoggingEnabled == 0) ? ((void)0) : LogSPS_
     #define LogInfo      (mDNS_LoggingEnabled == 0) ? ((void)0) : LogInfo_
+    #define LogDebug     (mDNS_LoggingEnabled == 0) ? ((void)0) : LogDebug_
 extern void LogMsg_(const char *format, ...)       IS_A_PRINTF_STYLE_FUNCTION(1,2);
 extern void LogOperation_(const char *format, ...) IS_A_PRINTF_STYLE_FUNCTION(1,2);
 extern void LogSPS_(const char *format, ...)       IS_A_PRINTF_STYLE_FUNCTION(1,2);
 extern void LogInfo_(const char *format, ...)      IS_A_PRINTF_STYLE_FUNCTION(1,2);
+extern void LogDebug_(const char *format, ...)     IS_A_PRINTF_STYLE_FUNCTION(1,2);
 #endif
 
 #if MDNS_DEBUGMSGS
diff --git a/mDNSResponder/mDNSCore/mDNSEmbeddedAPI.h b/mDNSResponder/mDNSCore/mDNSEmbeddedAPI.h
index 1962bb1..511aa3b 100755
--- a/mDNSResponder/mDNSCore/mDNSEmbeddedAPI.h
+++ b/mDNSResponder/mDNSCore/mDNSEmbeddedAPI.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2017 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -101,6 +101,10 @@ extern "C" {
 #define MaximumRDSize               264
 #endif
 
+#if !defined(MDNSRESPONDER_BTMM_SUPPORT)
+#define MDNSRESPONDER_BTMM_SUPPORT 0
+#endif
+
 // ***************************************************************************
 // Function scope indicators
 
@@ -1335,6 +1339,13 @@ typedef struct McastResolver
     mDNSu32 timeout;            // timeout value for questions
 } McastResolver;
 
+enum {
+    Mortality_Mortal      = 0,          // This cache record can expire and get purged
+    Mortality_Immortal    = 1,          // Allow this record to remain in the cache indefinitely
+    Mortality_Ghost       = 2           // An immortal record that has expired and can linger in the cache
+};
+typedef mDNSu8 MortalityState;
+
 // scoped values for DNSServer matching
 enum
 {
@@ -1386,6 +1397,7 @@ typedef struct
 struct ResourceRecord_struct
 {
     mDNSu8 RecordType;                  // See kDNSRecordTypes enum.
+    MortalityState mortality;           // Mortality of this resource record (See MortalityState enum)
     mDNSu16 rrtype;                     // See DNS_TypeValues enum.
     mDNSu16 rrclass;                    // See DNS_ClassValues enum.
     mDNSu32 rroriginalttl;              // In seconds
@@ -1399,7 +1411,6 @@ struct ResourceRecord_struct
                                         // ReconfirmAntecedents(), etc., use rdatahash as a pre-flight check to see
                                         // whether it's worth doing a full SameDomainName() call. If the rdatahash
                                         // is not a correct case-insensitive name hash, they'll get false negatives.
-
     // Grouping pointers together at the end of the structure improves the memory layout efficiency
     mDNSInterfaceID InterfaceID;        // Set if this RR is specific to one interface
                                         // For records received off the wire, InterfaceID is *always* set to the receiving interface
@@ -1638,7 +1649,7 @@ struct CacheRecord_struct
     mDNSs32 TimeRcvd;                   // In platform time units
     mDNSs32 DelayDelivery;              // Set if we want to defer delivery of this answer to local clients
     mDNSs32 NextRequiredQuery;          // In platform time units
-    mDNSs32 LastUsed;                   // In platform time units
+    // Extra four bytes here (on 64bit)
     DNSQuestion    *CRActiveQuestion;   // Points to an active question referencing this answer. Can never point to a NewQuestion.
     mDNSs32 LastUnansweredTime;         // In platform time units; last time we incremented UnansweredQueries
     mDNSu8  UnansweredQueries;          // Number of times we've issued a query for this record without getting an answer
@@ -1812,7 +1823,12 @@ typedef enum {
     DNSPUSH_ESTABLISHED  = 4
 } DNSPush_State;
     
-
+enum {
+    AllowExpired_None = 0,                  // Don't allow expired answers or mark answers immortal (behave normally)
+    AllowExpired_MakeAnswersImmortal = 1,   // Any answers to this question get marked as immortal
+    AllowExpired_AllowExpiredAnswers = 2    // Allow already expired answers from the cache
+};
+typedef mDNSu8 AllowExpiredState;
 
 #define HMAC_LEN    64
 #define HMAC_IPAD   0x36
@@ -1899,13 +1915,26 @@ typedef enum { DNSSECValNotRequired = 0, DNSSECValRequired, DNSSECValInProgress,
 #define AWD_METRICS (USE_AWD && TARGET_OS_IOS)
 
 #if AWD_METRICS
-typedef struct
+    
+enum
 {
-    domainname *    originalQName;          // Name of original A/AAAA record if this question is for a CNAME record.
-    mDNSu32         querySendCount;         // Number of queries that have been sent to DNS servers so far.
-    mDNSs32         firstQueryTime;         // The time when the first query was sent to a DNS server.
-    mDNSBool        answered;               // Has this question been answered?
+    ExpiredAnswer_None = 0,                  // No expired answers used
+    ExpiredAnswer_Allowed = 1,               // An expired answer is allowed by this request
+    ExpiredAnswer_AnsweredWithExpired = 2,   // Question was answered with an expired answer
+    ExpiredAnswer_ExpiredAnswerChanged = 3,  // Expired answer changed on refresh
+    
+    ExpiredAnswer_EnumCount
+};
+typedef mDNSu8 ExpiredAnswerMetric;
 
+typedef struct
+{
+    domainname *        originalQName;          // Name of original A/AAAA record if this question is for a CNAME record.
+    mDNSu32             querySendCount;         // Number of queries that have been sent to DNS servers so far.
+    mDNSs32             firstQueryTime;         // The time when the first query was sent to a DNS server.
+    mDNSBool            answered;               // Has this question been answered?
+    ExpiredAnswerMetric expiredAnswerState;     // Expired answer state (see ExpiredAnswerMetric above)
+    
 }   uDNSMetrics;
 #endif
 
@@ -1977,6 +2006,7 @@ struct DNSQuestion_struct
     mDNSu16 noServerResponse;               // At least one server did not respond.
     mDNSu16 triedAllServersOnce;            // Tried all DNS servers once
     mDNSu8 unansweredQueries;               // The number of unanswered queries to this server
+    AllowExpiredState allowExpired;         // Allow expired answers state (see enum AllowExpired_None, etc. above)
 
     ZoneData             *nta;              // Used for getting zone data for private or LLQ query
     mDNSAddr servAddr;                      // Address and port learned from _dns-llq, _dns-llq-tls or _dns-query-tls SRV query
@@ -2016,6 +2046,7 @@ struct DNSQuestion_struct
     mDNSIPPort TargetPort;                  // Must be set if Target is set
     mDNSOpaque16 TargetQID;                 // Must be set if Target is set
     domainname qname;
+    domainname firstExpiredQname;           // first expired qname in request chain
     mDNSu16 qtype;
     mDNSu16 qclass;
     mDNSBool LongLived;                     // Set by client for calls to mDNS_StartQuery to indicate LLQs to unicast layer.
@@ -2778,7 +2809,7 @@ extern void    mDNS_SetupResourceRecord(AuthRecord *rr, RData *RDataStorage, mDN
 extern mDNSu32 deriveD2DFlagsFromAuthRecType(AuthRecType authRecType);
 extern mStatus mDNS_RegisterService  (mDNS *const m, ServiceRecordSet *sr,
                                       const domainlabel *const name, const domainname *const type, const domainname *const domain,
-                                      const domainname *const host, mDNSIPPort port, const mDNSu8 txtinfo[], mDNSu16 txtlen,
+                                      const domainname *const host, mDNSIPPort port, RData *txtrdata, const mDNSu8 txtinfo[], mDNSu16 txtlen,
                                       AuthRecord *SubTypes, mDNSu32 NumSubTypes,
                                       mDNSInterfaceID InterfaceID, mDNSServiceCallback Callback, void *Context, mDNSu32 flags);
 extern mStatus mDNS_AddRecordToService(mDNS *const m, ServiceRecordSet *sr, ExtraResourceRecord *extra, RData *rdata, mDNSu32 ttl,  mDNSu32 flags);
@@ -2939,6 +2970,7 @@ extern char *GetRRDisplayString_rdb(const ResourceRecord *const rr, const RDataB
 #define RRDisplayString(m, rr) GetRRDisplayString_rdb(rr, &(rr)->rdata->u, (m)->MsgBuffer)
 #define ARDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer)
 #define CRDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer)
+#define MortalityDisplayString(M) (M == Mortality_Mortal ? "mortal" : (M == Mortality_Immortal ? "immortal" : "ghost"))
 extern mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2);
 extern void IncrementLabelSuffix(domainlabel *name, mDNSBool RichText);
 extern mDNSBool mDNSv4AddrIsRFC1918(const mDNSv4Addr * const addr);  // returns true for RFC1918 private addresses
@@ -3601,17 +3633,17 @@ struct CompileTimeAssertionChecks_mDNS
     char sizecheck_AuthRecord          [(sizeof(AuthRecord)           <=  1208) ? 1 : -1];
     char sizecheck_CacheRecord         [(sizeof(CacheRecord)          <=   232) ? 1 : -1];
     char sizecheck_CacheGroup          [(sizeof(CacheGroup)           <=   232) ? 1 : -1];
-    char sizecheck_DNSQuestion         [(sizeof(DNSQuestion)          <=   912) ? 1 : -1];
+    char sizecheck_DNSQuestion         [(sizeof(DNSQuestion)          <=  1168) ? 1 : -1];
 
-    char sizecheck_ZoneData            [(sizeof(ZoneData)             <=  1744) ? 1 : -1];
+    char sizecheck_ZoneData            [(sizeof(ZoneData)             <=  2000) ? 1 : -1];
     char sizecheck_NATTraversalInfo    [(sizeof(NATTraversalInfo)     <=   200) ? 1 : -1];
     char sizecheck_HostnameInfo        [(sizeof(HostnameInfo)         <=  3050) ? 1 : -1];
     char sizecheck_DNSServer           [(sizeof(DNSServer)            <=   330) ? 1 : -1];
-    char sizecheck_NetworkInterfaceInfo[(sizeof(NetworkInterfaceInfo) <=  7376) ? 1 : -1];
+    char sizecheck_NetworkInterfaceInfo[(sizeof(NetworkInterfaceInfo) <=  8400) ? 1 : -1];
     char sizecheck_ServiceRecordSet    [(sizeof(ServiceRecordSet)     <=  5540) ? 1 : -1];
     char sizecheck_DomainAuthInfo      [(sizeof(DomainAuthInfo)       <=  7888) ? 1 : -1];
 #if APPLE_OSX_mDNSResponder
-    char sizecheck_ClientTunnel        [(sizeof(ClientTunnel)         <=  1256) ? 1 : -1];
+    char sizecheck_ClientTunnel        [(sizeof(ClientTunnel)         <=  1512) ? 1 : -1];
 #endif
 };
 
diff --git a/mDNSResponder/mDNSCore/uDNS.c b/mDNSResponder/mDNSCore/uDNS.c
index 64dae89..cd91f4d 100755
--- a/mDNSResponder/mDNSCore/uDNS.c
+++ b/mDNSResponder/mDNSCore/uDNS.c
@@ -5807,7 +5807,7 @@ struct CompileTimeAssertionChecks_uDNS
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
     char sizecheck_tcpInfo_t     [(sizeof(tcpInfo_t)      <=  9056) ? 1 : -1];
-    char sizecheck_SearchListElem[(sizeof(SearchListElem) <=  5000) ? 1 : -1];
+    char sizecheck_SearchListElem[(sizeof(SearchListElem) <=  6136) ? 1 : -1];
 };
 
 #if COMPILER_LIKES_PRAGMA_MARK
diff --git a/mDNSResponder/mDNSMacOSX/D2D.c b/mDNSResponder/mDNSMacOSX/D2D.c
index 2848cda..6ad8e94 100644
--- a/mDNSResponder/mDNSMacOSX/D2D.c
+++ b/mDNSResponder/mDNSMacOSX/D2D.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2016 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -97,7 +97,6 @@ static mDNSu8 *const compression_limit = (mDNSu8 *) &compression_base_msg + size
 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);
 
 typedef struct D2DRecordListElem
 {
@@ -167,44 +166,15 @@ mDNSlocal mDNSu8 * DNSNameCompressionBuildRHS(mDNSu8 *start, const ResourceRecor
     return putRData(&compression_base_msg, start, compression_limit, resourceRecord);
 }
 
-#define PRINT_DEBUG_BYTES_LIMIT 64  // set limit on number of record bytes printed for debugging
-
-mDNSlocal void PrintHex(mDNSu8 *data, mDNSu16 len)
+mDNSlocal void PrintHelper(const char *const tag, mDNSu8 *lhs, mDNSu16 lhs_len, mDNSu8 *rhs, mDNSu16 rhs_len)
 {
-    mDNSu8 *end;
-    char buffer[49] = {0};
-    char *bufend = buffer + sizeof(buffer);
-
-    if (len > PRINT_DEBUG_BYTES_LIMIT)
-    {
-        LogInfo(" (limiting debug output to %d bytes)", PRINT_DEBUG_BYTES_LIMIT);
-        len = PRINT_DEBUG_BYTES_LIMIT;
-    }
-    end = data + len;
-
-    while(data < end)
+    if (mDNS_LoggingEnabled)
     {
-        char *ptr = buffer;
-        for(; data < end && ptr < bufend-1; ptr+=3,data++)
-            mDNS_snprintf(ptr, bufend - ptr, "%02X ", *data);
-        LogInfo("    %s", buffer);
+        LogDebug("%s: LHS: (%d bytes) %.*H", tag, lhs_len, lhs_len, lhs);
+        if (rhs) LogDebug("%s: RHS: (%d bytes) %.*H", tag, rhs_len, rhs_len, rhs);
     }
 }
 
-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: (%d bytes)", lhs_len);
-    PrintHex(lhs, lhs_len);
-
-    if (!rhs) return;
-
-    LogInfo("  RHS: (%d bytes)", rhs_len);
-    PrintHex(rhs, rhs_len);
-}
-
 mDNSlocal void FreeD2DARElemCallback(mDNS *const m, AuthRecord *const rr, mStatus result)
 {
     (void)m;  // unused
@@ -333,9 +303,8 @@ mDNSlocal mStatus xD2DParse(const mDNSu8 * const lhs, const mDNSu16 lhs_len, con
 
     if (mDNS_LoggingEnabled)
     {
-        LogInfo("%s", __func__);
-        LogInfo("  Static Bytes: (%d bytes)", compression_lhs - (mDNSu8*)&compression_base_msg);
-        PrintHex((mDNSu8*)&compression_base_msg, compression_lhs - (mDNSu8*)&compression_base_msg);
+        const int len = (int)(compression_lhs - (mDNSu8*)&compression_base_msg);
+        LogInfo("xD2DParse: Static Bytes: (%d bytes) %.*H", len, len, &compression_base_msg);
     }
 
     mDNSu8 *ptr = compression_lhs; // pointer to the end of our fake packet
@@ -366,8 +335,8 @@ mDNSlocal mStatus xD2DParse(const mDNSu8 * const lhs, const mDNSu16 lhs_len, con
 
     if (mDNS_LoggingEnabled)
     {
-        LogInfo("  Our Bytes (%d bytes): ", ptr - compression_lhs);
-        PrintHex(compression_lhs, ptr - compression_lhs);
+        const int len = (int)(ptr - compression_lhs);
+        LogInfo("xD2DParse: Our Bytes (%d bytes): %.*H", len, len, compression_lhs);
     }
 
     ptr = (mDNSu8 *) GetLargeResourceRecord(m, &compression_base_msg, compression_lhs, ptr, mDNSInterface_Any, kDNSRecordTypePacketAns, &m->rec);
diff --git a/mDNSResponder/mDNSMacOSX/DNS64.c b/mDNSResponder/mDNSMacOSX/DNS64.c
index 1f4e0ec..6fef38f 100644
--- a/mDNSResponder/mDNSMacOSX/DNS64.c
+++ b/mDNSResponder/mDNSMacOSX/DNS64.c
@@ -22,7 +22,13 @@
 #include "DNS64.h"
 
 #include <AssertMacros.h>
-#include <network/nat64.h>
+
+#if __has_include(<nw/private.h>)
+    #include <nw/private.h>
+#else
+    #include <network/nat64.h>
+#endif
+
 #include <stdlib.h>
 #include <string.h>
 
diff --git a/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.c b/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.c
index dd670ab..2b89bc2 100644
--- a/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.c
+++ b/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2012 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -15,14 +15,6 @@
  * limitations under the License.
  */
 
-// Suppress "warning: 'DNSServiceDiscoveryMachPort' is deprecated" messages -- we already know this code is building the deprecated API
-// Since we compile with all warnings treated as errors, we have to turn off the warnings here or the project won't compile
-#include <AvailabilityMacros.h>
-#undef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED
-#define AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED
-#undef AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_3
-#define AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_3
-
 #include "../mDNSMacOSX/DNSServiceDiscovery.h"
 #include "DNSServiceDiscoveryDefines.h"
 
diff --git a/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.h b/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.h
index 004d325..3f683a8 100644
--- a/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.h
+++ b/mDNSResponder/mDNSMacOSX/DNSServiceDiscovery.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002, 2004, 2006, 2011 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -31,8 +31,9 @@
 #include <sys/cdefs.h>
 
 #include <netinet/in.h>
+#include <os/availability.h>
 
-#include <AvailabilityMacros.h>
+#define kDNSServiceDiscoveryDeprecatedMsg "This API was deprecated in Mac OS X 10.3 and replaced by the portable cross-platform /usr/include/dns_sd.h API"
 
 __BEGIN_DECLS
 
@@ -90,7 +91,7 @@ typedef uint32_t DNSRecordReference;
    call to the specified callout function.
    @param replyMsg The Mach message.
  */
-void DNSServiceDiscovery_handleReply(void *replyMsg);
+void DNSServiceDiscovery_handleReply(void *replyMsg) API_DEPRECATED(kDNSServiceDiscoveryDeprecatedMsg, macos(10.2, 10.3));
 
 /***************************************************************************/
 /*   DNS Service Browser   */
@@ -125,7 +126,7 @@ dns_service_discovery_ref DNSServiceBrowserCreate
     const char      *domain,
     DNSServiceBrowserReply callBack,
     void        *context
-) AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_3;
+) API_DEPRECATED(kDNSServiceDiscoveryDeprecatedMsg, macos(10.2, 10.3));
 
 /***************************************************************************/
 /* Resolver requests */
@@ -158,7 +159,7 @@ dns_service_discovery_ref DNSServiceResolverResolve
     const char      *domain,
     DNSServiceResolverReply callBack,
     void        *context
-) AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_3;
+) API_DEPRECATED(kDNSServiceDiscoveryDeprecatedMsg, macos(10.2, 10.3));
 
 /***************************************************************************/
 /* Mach port accessor and deallocation */
@@ -173,7 +174,7 @@ dns_service_discovery_ref DNSServiceResolverResolve
         specified or some other error occurred which prevented the
         resolution from being started.
  */
-mach_port_t DNSServiceDiscoveryMachPort(dns_service_discovery_ref dnsServiceDiscovery) AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_3;
+mach_port_t DNSServiceDiscoveryMachPort(dns_service_discovery_ref dnsServiceDiscovery) API_DEPRECATED(kDNSServiceDiscoveryDeprecatedMsg, macos(10.2, 10.3));
 
 /*!
     @function DNSServiceDiscoveryDeallocate
@@ -181,7 +182,7 @@ mach_port_t DNSServiceDiscoveryMachPort(dns_service_discovery_ref dnsServiceDisc
     @param dnsServiceDiscovery A dns_service_discovery_ref as returned from a creation or enumeration call
     @result void
  */
-void DNSServiceDiscoveryDeallocate(dns_service_discovery_ref dnsServiceDiscovery) AVAILABLE_MAC_OS_X_VERSION_10_2_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_3;
+void DNSServiceDiscoveryDeallocate(dns_service_discovery_ref dnsServiceDiscovery) API_DEPRECATED(kDNSServiceDiscoveryDeprecatedMsg, macos(10.2, 10.3));
 
 __END_DECLS
 
diff --git a/mDNSResponder/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mDNSResponder.plist b/mDNSResponder/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mDNSResponder.plist
new file mode 100644
index 0000000..72352ce
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/LoggingProfiles/AppleInternal/com.apple.mDNSResponder.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>DEFAULT-OPTIONS</key>
+	<dict>
+		<key>Level</key>
+		<dict>
+			<key>Persist</key>
+			<string>Info</string>
+		</dict>
+	</dict>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist b/mDNSResponder/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist
new file mode 100644
index 0000000..1eba185
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/LoggingProfiles/com.apple.mDNSResponder.plist
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>DEFAULT-OPTIONS</key>
+	<dict>
+		<key>Level</key>
+		<dict>
+			<key>Persist</key>
+			<string>Inherit</string>
+			<key>Enable</key>
+			<string>Inherit</string>
+		</dict>
+	</dict>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/Metrics.h b/mDNSResponder/mDNSMacOSX/Metrics.h
index ff419fd..e377229 100644
--- a/mDNSResponder/mDNSMacOSX/Metrics.h
+++ b/mDNSResponder/mDNSMacOSX/Metrics.h
@@ -26,7 +26,7 @@ extern "C" {
 
 #if TARGET_OS_IOS
 mStatus MetricsInit(void);
-void    MetricsUpdateDNSQueryStats(const domainname *inQueryName, mDNSu16 inType, const ResourceRecord *inRR, mDNSu32 inSendCount, mDNSu32 inLatencyMs, mDNSBool inForCell);
+void    MetricsUpdateDNSQueryStats(const domainname *inQueryName, mDNSu16 inType, const ResourceRecord *inRR, mDNSu32 inSendCount, ExpiredAnswerMetric inExpiredAnswerState, mDNSu32 inLatencyMs, mDNSBool inForCell);
 void    MetricsUpdateDNSResolveStats(const domainname *inQueryName, const ResourceRecord *inRR, mDNSBool inForCell);
 void    MetricsUpdateDNSQuerySize(mDNSu32 inSize);
 void    MetricsUpdateDNSResponseSize(mDNSu32 inSize);
diff --git a/mDNSResponder/mDNSMacOSX/Metrics.m b/mDNSResponder/mDNSMacOSX/Metrics.m
index e540f3b..dd20bc2 100644
--- a/mDNSResponder/mDNSMacOSX/Metrics.m
+++ b/mDNSResponder/mDNSMacOSX/Metrics.m
@@ -93,10 +93,11 @@ SOFT_LINK_CLASS(WirelessDiagnostics, AWDMDNSResponderDNSMessageSizeStats)
 //  Constants
 //===========================================================================================================================
 
-#define kQueryStatsMaxQuerySendCount    10
-#define kQueryStatsSendCountBinCount    (kQueryStatsMaxQuerySendCount + 1)
-#define kQueryStatsLatencyBinCount      55
-#define kResolveStatsMaxObjCount        2000
+#define kQueryStatsMaxQuerySendCount        10
+#define kQueryStatsSendCountBinCount        (kQueryStatsMaxQuerySendCount + 1)
+#define kQueryStatsLatencyBinCount          55
+#define kQueryStatsExpiredAnswerStateCount  (ExpiredAnswer_EnumCount)
+#define kResolveStatsMaxObjCount            2000
 
 //===========================================================================================================================
 //  Data structures
@@ -152,6 +153,7 @@ typedef struct
     uint16_t    responseLatencyBins[kQueryStatsLatencyBinCount];
     uint16_t    negAnsweredQuerySendCountBins[kQueryStatsSendCountBinCount];
     uint16_t    negResponseLatencyBins[kQueryStatsLatencyBinCount];
+    uint16_t    expiredAnswerStateBins[kQueryStatsExpiredAnswerStateCount];
 
 }   DNSHist;
 
@@ -159,6 +161,7 @@ check_compile_time(sizeof(DNSHist) <= 512);
 check_compile_time(countof_field(DNSHist, unansweredQuerySendCountBins)  == (kQueryStatsMaxQuerySendCount + 1));
 check_compile_time(countof_field(DNSHist, answeredQuerySendCountBins)    == (kQueryStatsMaxQuerySendCount + 1));
 check_compile_time(countof_field(DNSHist, negAnsweredQuerySendCountBins) == (kQueryStatsMaxQuerySendCount + 1));
+check_compile_time(countof_field(DNSHist, expiredAnswerStateBins)         == (kQueryStatsExpiredAnswerStateCount));
 
 // Important: Do not modify kResponseLatencyMsLimits because the code used to generate AWD reports expects the response
 // latency histogram bins to observe these time interval upper bounds.
@@ -344,7 +347,7 @@ check_compile_time(sizeof(DNSMessageSizeStats) <= 132);
 mDNSlocal mStatus       QueryStatsCreate(const char *inDomainStr, const char *inAltDomainStr, QueryNameTest_f inTest, mDNSBool inTerminal, QueryStats **outStats);
 mDNSlocal void          QueryStatsFree(QueryStats *inStats);
 mDNSlocal void          QueryStatsFreeList(QueryStats *inList);
-mDNSlocal mStatus       QueryStatsUpdate(QueryStats *inStats, int inType, const ResourceRecord *inRR, mDNSu32 inQuerySendCount, mDNSu32 inLatencyMs, mDNSBool inForCell);
+mDNSlocal mStatus       QueryStatsUpdate(QueryStats *inStats, int inType, const ResourceRecord *inRR, mDNSu32 inQuerySendCount, ExpiredAnswerMetric inExpiredAnswerState, mDNSu32 inLatencyMs, mDNSBool inForCell);
 mDNSlocal const char *  QueryStatsGetDomainString(const QueryStats *inStats);
 mDNSlocal mDNSBool      QueryStatsDomainTest(const QueryStats *inStats, const domainname *inQueryName);
 mDNSlocal mDNSBool      QueryStatsHostnameTest(const QueryStats *inStats, const domainname *inQueryName);
@@ -492,7 +495,7 @@ mStatus MetricsInit(void)
 //  MetricsUpdateDNSQueryStats
 //===========================================================================================================================
 
-mDNSexport void MetricsUpdateDNSQueryStats(const domainname *inQueryName, mDNSu16 inType, const ResourceRecord *inRR, mDNSu32 inSendCount, mDNSu32 inLatencyMs, mDNSBool inForCell)
+mDNSexport void MetricsUpdateDNSQueryStats(const domainname *inQueryName, mDNSu16 inType, const ResourceRecord *inRR, mDNSu32 inSendCount, ExpiredAnswerMetric inExpiredAnswerState, mDNSu32 inLatencyMs, mDNSBool inForCell)
 {
     QueryStats *        stats;
     mDNSBool            match;
@@ -505,7 +508,7 @@ mDNSexport void MetricsUpdateDNSQueryStats(const domainname *inQueryName, mDNSu1
         match = stats->test(stats, inQueryName);
         if (match)
         {
-            QueryStatsUpdate(stats, inType, inRR, inSendCount, inLatencyMs, inForCell);
+            QueryStatsUpdate(stats, inType, inRR, inSendCount, inExpiredAnswerState, inLatencyMs, inForCell);
             if (stats->terminal) break;
         }
     }
@@ -839,7 +842,7 @@ mDNSlocal void QueryStatsFreeList(QueryStats *inList)
 //  QueryStatsUpdate
 //===========================================================================================================================
 
-mDNSlocal mStatus QueryStatsUpdate(QueryStats *inStats, int inType, const ResourceRecord *inRR, mDNSu32 inQuerySendCount, mDNSu32 inLatencyMs, mDNSBool inForCell)
+mDNSlocal mStatus QueryStatsUpdate(QueryStats *inStats, int inType, const ResourceRecord *inRR, mDNSu32 inQuerySendCount, ExpiredAnswerMetric inExpiredAnswerState, mDNSu32 inLatencyMs, mDNSBool inForCell)
 {
     mStatus             err;
     DNSHistSet *        set;
@@ -892,6 +895,7 @@ mDNSlocal mStatus QueryStatsUpdate(QueryStats *inStats, int inType, const Resour
         for (i = 0; (i < (int)countof(kResponseLatencyMsLimits)) && (inLatencyMs >= kResponseLatencyMsLimits[i]); ++i) {}
         increment_saturate(hist->unansweredQueryDurationBins[i], UINT16_MAX);
     }
+    increment_saturate(hist->expiredAnswerStateBins[Min(inExpiredAnswerState, (kQueryStatsExpiredAnswerStateCount-1))], UINT16_MAX);
     err = mStatus_NoError;
 
 exit:
@@ -2061,6 +2065,7 @@ mDNSlocal mStatus CreateAWDDNSDomainStats(DNSHist *inHist, const char *inDomain,
     size_t                  binCount;
     uint32_t                sendCountBins[kQueryStatsSendCountBinCount];
     uint32_t                latencyBins[kQueryStatsLatencyBinCount];
+    uint32_t                expiredAnswerBins[kQueryStatsExpiredAnswerStateCount];
 
     awdStats = [[AWDDNSDomainStatsSoft alloc] init];
     require_action_quiet(awdStats, exit, err = mStatus_UnknownErr);
@@ -2107,6 +2112,11 @@ mDNSlocal mStatus CreateAWDDNSDomainStats(DNSHist *inHist, const char *inDomain,
         binCount = CopyHistogramBins(latencyBins, inHist->unansweredQueryDurationBins, kQueryStatsLatencyBinCount);
         [awdStats setUnansweredQueryDurationMs:latencyBins count:(NSUInteger)binCount];
     }
+    
+    // Expired answers states
+    
+    binCount = CopyHistogramBins(expiredAnswerBins, inHist->expiredAnswerStateBins, kQueryStatsExpiredAnswerStateCount);
+    [awdStats setExpiredAnswerStates:expiredAnswerBins count:(NSUInteger)binCount];
 
     *outStats = awdStats;
     awdStats = nil;
@@ -2166,6 +2176,9 @@ mDNSlocal void LogDNSHist(const DNSHist *inHist, const char *inDomain, mDNSBool
     LogMsgNoIdent("Answered questions            %4u", totalAnswered);
     LogMsgNoIdent("Negatively answered questions %4u", totalNegAnswered);
     LogMsgNoIdent("Unanswered questions          %4u", totalUnanswered);
+    LogMsgNoIdent("Expired - no cached answer    %4u", inHist->expiredAnswerStateBins[ExpiredAnswer_Allowed]);
+    LogMsgNoIdent("Expired - answered from cache %4u", inHist->expiredAnswerStateBins[ExpiredAnswer_AnsweredWithExpired]);
+    LogMsgNoIdent("Expired - cache changed       %4u", inHist->expiredAnswerStateBins[ExpiredAnswer_ExpiredAnswerChanged]);
     LogMsgNoIdent("-- Query send counts ---------");
     LogDNSHistSendCounts(inHist->answeredQuerySendCountBins);
     LogMsgNoIdent("-- Query send counts (NAQs) --");
diff --git a/mDNSResponder/mDNSMacOSX/Private/com.apple.mDNSResponder.plist b/mDNSResponder/mDNSMacOSX/Private/com.apple.mDNSResponder.plist
deleted file mode 100644
index 6d403b5..0000000
--- a/mDNSResponder/mDNSMacOSX/Private/com.apple.mDNSResponder.plist
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>                                                                                                                                        
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>DEFAULT-OPTIONS</key>
-    <dict>
-        <key>Default-Privacy-Setting</key>
-        <string>Public</string>
-        <key>Level</key>
-        <dict>
-            <key>Persist</key>
-            <string>Inherit</string>
-            <key>Enable</key>
-            <string>Inherit</string>
-        </dict>
-    </dict>
-</dict>
-</plist>
-
diff --git a/mDNSResponder/mDNSMacOSX/Scripts/bonjour-mcast-diagnose b/mDNSResponder/mDNSMacOSX/Scripts/bonjour-mcast-diagnose
new file mode 100755
index 0000000..bda53ba
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/Scripts/bonjour-mcast-diagnose
@@ -0,0 +1,380 @@
+#! /bin/bash
+#
+#	Copyright (c) 2017-2018 Apple Inc. All rights reserved.
+#
+#	This script is currently for Apple Internal use only.
+#
+
+version=1.4
+script=${BASH_SOURCE[0]}
+dnssdutil=${dnssdutil:-dnssdutil}
+
+#============================================================================================================================
+#	PrintUsage
+#============================================================================================================================
+
+PrintUsage()
+{
+	echo ""
+	echo "Usage: $( basename "${script}" ) [options]"
+	echo ""
+	echo "Options:"
+	echo "    -V    Display version of this script and exit."
+	echo ""
+}
+
+#============================================================================================================================
+#	LogOut
+#============================================================================================================================
+
+LogOut()
+{
+	echo "$( date '+%Y-%m-%d %H:%M:%S%z' ): $*"
+}
+
+#============================================================================================================================
+#	LogMsg
+#============================================================================================================================
+
+LogMsg()
+{
+	echo "$*"
+	if [ -d "${workPath}" ]; then
+		LogOut "$*" >> "${workPath}/log.txt"
+	fi
+}
+
+#============================================================================================================================
+#	ErrQuit
+#============================================================================================================================
+
+ErrQuit()
+{
+	echo "error: $*"
+	exit 1
+}
+
+#============================================================================================================================
+#	SignalHandler
+#============================================================================================================================
+
+SignalHandler()
+{
+	LogMsg "Exiting due to signal."
+	trap '' SIGINT SIGTERM
+	pkill -TERM -P $$
+	wait
+	exit 2
+}
+
+#============================================================================================================================
+#	ExitHandler
+#============================================================================================================================
+
+ExitHandler()
+{
+	if [ -d "${tempPath}" ]; then
+		rm -fr "${tempPath}"
+	fi
+}
+
+#============================================================================================================================
+#	RunNetStat
+#============================================================================================================================
+
+RunNetStat()
+{
+	LogMsg "Running netstat -g -n -s"
+	netstat -g -n -s &> "${workPath}/netstat-g-n-s.txt"
+}
+
+#============================================================================================================================
+#	StartPacketCapture
+#============================================================================================================================
+
+StartPacketCapture()
+{
+	LogMsg "Starting tcpdump."
+	tcpdump -n -w "${workPath}/tcpdump.pcapng" &> "${workPath}/tcpdump.txt" &
+	tcpdumpPID=$!
+}
+
+#============================================================================================================================
+#	SaveExistingPacketCaptures
+#============================================================================================================================
+
+SaveExistingPacketCaptures()
+{
+	LogMsg "Saving existing mDNS packet captures."
+	mkdir "${workPath}/pcaps"
+	for file in /tmp/mdns-tcpdump.pcapng*; do
+		[ -e "${file}" ] || continue
+		baseName=$( sed -E 's/^mdns-tcpdump.pcapng([0-9]+)$/mdns-tcpdump-\1.pcapng/' <<< "$( basename ${file} )" )
+		gzip < ${file} > "${workPath}/pcaps/${baseName}.gz"
+	done
+}
+
+#============================================================================================================================
+#	StopPacketCapture
+#============================================================================================================================
+
+StopPacketCapture()
+{
+	LogMsg "Stopping tcpdump."
+	kill -TERM ${tcpdumpPID}
+}
+
+#============================================================================================================================
+#	RunInterfaceMulticastTests
+#============================================================================================================================
+
+RunInterfaceMulticastTests()
+{
+	local ifname="$1"
+	local allHostsV4=224.0.0.1
+	local allHostsV6=ff02::1
+	local mDNSV4=224.0.0.251
+	local mDNSV6=ff02::fb
+	local serviceList=( $( "${dnssdutil}" queryrecord -i "${ifname}" -A -t ptr -n _services._dns-sd._udp.local -l 6 | sed -E -n 's/.*(_.*_(tcp|udp)\.local\.)$/\1/p' | sort -u ) )
+	local log="${workPath}/mcast-test-log-${ifname}.txt"
+	
+	LogOut "List of services: ${serviceList[*]}" >> "${log}"
+	# Ping All Hosts IPv4 multicast address.
+	
+	local routeOutput=$( route -n get -ifscope ${ifname} "${allHostsV4}" 2> /dev/null )
+	if [ -n "${routeOutput}" ]; then
+		LogOut "Pinging "${allHostsV4}" on interface ${ifname}." >> "${log}"
+		ping -t 5 -b ${ifname} "${allHostsV4}" &> "${workPath}/ping-all-hosts-${ifname}.txt"
+	else
+		LogOut "No route to "${allHostsV4}" on interface ${ifname}." >> "${log}"
+	fi
+	
+	# Ping mDNS IPv4 multicast address.
+	
+	routeOutput=$( route -n get -ifscope ${ifname} "${mDNSV4}" 2> /dev/null )
+	if [ -n "${routeOutput}" ]; then
+		LogOut "Pinging "${mDNSV4}" on interface ${ifname}." >> "${log}"
+		ping -t 5 -b ${ifname} "${mDNSV4}" &> "${workPath}/ping-mDNS-${ifname}.txt"
+	else
+		LogOut "No route to "${mDNSV4}" on interface ${ifname}." >> "${log}"
+	fi
+	
+	# Ping All Hosts IPv6 multicast address.
+	
+	routeOutput=$( route -n get -ifscope ${ifname} -inet6 "${allHostsV6}" 2> /dev/null )
+	if [ -n "${routeOutput}" ]; then
+		LogOut "Pinging "${allHostsV6}" on interface ${ifname}." >> "${log}"
+		ping6 -c 6 -I ${ifname} "${allHostsV6}" &> "${workPath}/ping6-all-hosts-${ifname}.txt"
+	else
+		LogOut "No route to "${allHostsV6}" on interface ${ifname}." >> "${log}"
+	fi
+	
+	# Ping mDNS IPv6 multicast address.
+	
+	routeOutput=$( route -n get -ifscope ${ifname} -inet6 "${mDNSV6}" 2> /dev/null )
+	if [ -n "${routeOutput}" ]; then
+		LogOut "Pinging "${mDNSV6}" on interface ${ifname}." >> "${log}"
+		ping6 -c 6 -I ${ifname} "${mDNSV6}" &> "${workPath}/ping6-mDNS-${ifname}.txt"
+	else
+		LogOut "No route to "${mDNSV6}" on interface ${ifname}." >> "${log}"
+	fi
+	
+	# Send mDNS queries for services.
+	
+	for service in "${serviceList[@]}"; do
+		LogOut "Sending mDNS queries for "${service}" on interface ${ifname}." >> "${log}"
+		for(( i = 1; i <= 3; ++i )); do
+			printf "\n"
+			"${dnssdutil}" mdnsquery -i "${ifname}" -n "${service}" -t ptr -r 2
+			printf "\n"
+			"${dnssdutil}" mdnsquery -i "${ifname}" -n "${service}" -t ptr -r 1 --QU -p 5353
+			printf "\n"
+		done >> "${workPath}/mdnsquery-${ifname}.txt" 2>&1
+	done
+}
+
+#============================================================================================================================
+#	RunMulticastTests
+#============================================================================================================================
+
+RunMulticastTests()
+{
+	local interfaces=( $( ifconfig -l -u ) )
+	local skipPrefixes=( ap awdl bridge ipsec lo p2p pdp_ip pktap UDC utun )
+	local ifname=""
+	local pid=""
+	local pids=()
+	
+	LogMsg "List of interfaces: ${interfaces[*]}"
+	for ifname in "${interfaces[@]}"; do
+		local skip=false
+		for prefix in ${skipPrefixes[@]}; do
+			if [[ ${ifname} =~ ^${prefix}[0-9]*$ ]]; then
+				skip=true
+				break
+			fi
+		done
+		
+		if [ "${skip}" != "true" ]; then
+			ifconfig ${ifname} | grep -q inet
+			if [ $? -ne 0 ]; then
+				skip=true
+			fi
+		fi
+		
+		if [ "${skip}" == "true" ]; then
+			continue
+		fi
+		
+		LogMsg "Starting interface multicast tests for ${ifname}."
+		RunInterfaceMulticastTests "${ifname}" & pids+=($!)
+	done
+	
+	LogMsg "Waiting for interface multicast tests to complete..."
+	for pid in "${pids[@]}"; do
+		wait "${pid}"
+	done
+	LogMsg "All interface multicast tests completed."
+}
+
+#============================================================================================================================
+#	RunBrowseTest
+#============================================================================================================================
+
+RunBrowseTest()
+{
+	LogMsg "Running dnssdutil browseAll command."
+	"${dnssdutil}" browseAll -A -d local -b 10 -c 10 &> "${workPath}/browseAll.txt"
+}
+
+#============================================================================================================================
+#	IsMacOS
+#============================================================================================================================
+
+IsMacOS()
+{
+	[[ $( sw_vers -productName ) =~ ^Mac\ OS ]]
+}
+
+#============================================================================================================================
+#	ArchiveLogs
+#============================================================================================================================
+
+ArchiveLogs()
+{
+	local workdir=$( basename "${workPath}" )
+	local archivePath="${dstPath}/${workdir}.tar.gz"
+	
+	LogMsg "Archiving logs."
+	echo "---"
+	tar -C "${tempPath}" -czf "${archivePath}" "${workdir}"
+	if [ -e "${archivePath}" ]; then
+		echo "Created log archive at ${archivePath}"
+		echo "*** Please run sysdiagnose NOW. ***"
+		echo "Attach both the log archive and the sysdiagnose archive to the radar."
+		if IsMacOS; then
+			open "${dstPath}"
+		fi
+	else
+		echo "Failed to create archive at ${archivePath}."
+	fi
+	echo "---"
+}
+
+#============================================================================================================================
+#	CreateWorkDirName
+#============================================================================================================================
+
+CreateWorkDirName()
+{
+	local suffix=""
+	local productName=$( sw_vers -productName )
+	if [ -n "${productName}" ]; then
+		suffix+="_${productName}"
+	fi
+	
+	local model=""
+	if IsMacOS; then
+		model=$( sysctl -n hw.model )
+		model=${model//,/-}
+	else
+		model=$( gestalt_query -undecorated DeviceName )
+	fi
+	if [ -n "${model}" ]; then
+		suffix+="_${model}"
+	fi
+	
+	local buildVersion=$( sw_vers -buildVersion )
+	if [ -n "${buildVersion}" ]; then
+		suffix+="_${buildVersion}"
+	fi
+	
+	suffix=${suffix//[^A-Za-z0-9._-]/_}
+	
+	printf "bonjour-mcast-diags_$( date '+%Y.%m.%d_%H-%M-%S%z' )${suffix}"
+}
+
+#============================================================================================================================
+#	main
+#============================================================================================================================
+
+main()
+{
+	while getopts ":hV" option; do
+		case "${option}" in
+			h)
+				PrintUsage
+				exit 0
+				;;
+			V)
+				echo "$( basename "${script}" ) version ${version}"
+				exit 0
+				;;
+			:)
+				ErrQuit "option '${OPTARG}' requires an argument."
+				;;
+			*)
+				ErrQuit "unknown option '${OPTARG}'."
+				;;
+		esac
+	done
+	
+	[ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \""${!OPTIND}"\"."
+	
+	if IsMacOS; then
+		if [ "${EUID}" -ne 0 ]; then
+			echo "Re-launching with sudo"
+			exec sudo ${script}
+		fi
+		dstPath=/var/tmp
+	else
+		[ "${EUID}" -eq 0 ] || ErrQuit "$( basename "${script}" ) needs to be run as root."
+		dstPath=/var/mobile/Library/Logs/CrashReporter
+	fi
+	
+	tempPath=$( mktemp -d -q ) || ErrQuit "Failed to make temp directory."
+	workPath="${tempPath}/$( CreateWorkDirName )"
+	mkdir "${workPath}" || ErrQuit "Failed to make work directory."
+	
+	trap SignalHandler	SIGINT SIGTERM
+	trap ExitHandler	EXIT
+	
+	LogMsg "About: $( basename "${script}" ) version ${version} ($( md5 -q ${script} ))."
+	if [ "${dnssdutil}" != "dnssdutil" ]; then
+		if [ -x "$( which "${dnssdutil}" )" ]; then
+			LogMsg "Using $( "${dnssdutil}" -V ) at $( which "${dnssdutil}" )."
+		else
+			LogMsg "WARNING: dnssdutil (${dnssdutil}) isn't an executable."
+		fi
+	fi
+	
+	RunNetStat
+	StartPacketCapture
+	SaveExistingPacketCaptures
+	RunBrowseTest
+	RunMulticastTests
+	StopPacketCapture
+	ArchiveLogs
+}
+
+main "$@"
diff --git a/mDNSResponder/mDNSMacOSX/Scripts/bonjour-start-mdns-tcpdump b/mDNSResponder/mDNSMacOSX/Scripts/bonjour-start-mdns-tcpdump
new file mode 100755
index 0000000..2a81b5f
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/Scripts/bonjour-start-mdns-tcpdump
@@ -0,0 +1,56 @@
+#! /bin/bash
+#
+#	Copyright (c) 2018 Apple Inc. All rights reserved.
+#
+#	This script is currently for Apple Internal use only.
+#
+
+version=1.0
+script=${BASH_SOURCE[0]}
+
+#============================================================================================================================
+#	PrintUsage
+#============================================================================================================================
+
+PrintUsage()
+{
+	echo ""
+	echo "Usage: $( basename "${script}" ) [options]"
+	echo ""
+	echo "Options:"
+	echo "    -V    Display version of this script and exit."
+	echo ""
+}
+
+#============================================================================================================================
+#	main
+#============================================================================================================================
+
+main()
+{
+	while getopts ":hV" option; do
+		case "${option}" in
+			h)
+				PrintUsage
+				exit 0
+				;;
+			V)
+				echo "$( basename "${script}" ) version ${version}"
+				exit 0
+				;;
+			:)
+				ErrQuit "option '${OPTARG}' requires an argument."
+				;;
+			*)
+				ErrQuit "unknown option '${OPTARG}'."
+				;;
+		esac
+	done
+	
+	[ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \""${!OPTIND}"\"."
+	
+	launchctl load /Library/LaunchDaemons/com.apple.mDNSResponder.mdns-tcpdump.plist
+	launchctl start com.apple.mDNSResponder.mdns-tcpdump
+}
+
+main "$@"
diff --git a/mDNSResponder/mDNSMacOSX/Scripts/com.apple.mDNSResponder.mdns-tcpdump.plist b/mDNSResponder/mDNSMacOSX/Scripts/com.apple.mDNSResponder.mdns-tcpdump.plist
new file mode 100644
index 0000000..0e48c21
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/Scripts/com.apple.mDNSResponder.mdns-tcpdump.plist
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>Label</key>
+	<string>com.apple.mDNSResponder.mdns-tcpdump</string>
+	<key>ProgramArguments</key>
+	<array>
+		<string>/usr/sbin/tcpdump</string>
+		<string>-w</string>
+		<string>/tmp/mdns-tcpdump.pcapng</string>
+		<string>-C</string>
+		<string>10</string>
+		<string>-W</string>
+		<string>16</string>
+		<string>( udp port 5353 ) or arp or icmp or icmp6</string>
+	</array>
+	<key>KeepAlive</key>
+	<false/>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/daemon.c b/mDNSResponder/mDNSMacOSX/daemon.c
index fa192b6..e16f1db 100644
--- a/mDNSResponder/mDNSMacOSX/daemon.c
+++ b/mDNSResponder/mDNSMacOSX/daemon.c
@@ -62,6 +62,7 @@ static os_log_t	log_general	        = NULL;
 #define kPreferencesKey_DebugLogging              CFSTR("DebugLogging")
 #define kPreferencesKey_UnicastPacketLogging      CFSTR("UnicastPacketLogging")
 #define kPreferencesKey_AlwaysAppendSearchDomains CFSTR("AlwaysAppendSearchDomains")
+#define kPreferencesKey_EnableAllowExpired        CFSTR("EnableAllowExpired")
 #define kPreferencesKey_NoMulticastAdvertisements CFSTR("NoMulticastAdvertisements")
 #define kPreferencesKey_StrictUnicastOrdering     CFSTR("StrictUnicastOrdering")
 #define kPreferencesKey_OfferSleepProxyService    CFSTR("OfferSleepProxyService")
@@ -84,13 +85,13 @@ static os_log_t	log_general	        = NULL;
 
 static mDNS_PlatformSupport PlatformStorage;
 
-// Start off with a default cache of 32K (141 records of 232 bytes each)
-// Each time we grow the cache we add another 141 records
-// 141 * 232 = 32712 bytes.
-// This fits in eight 4kB pages, with 56 bytes spare for memory block headers and similar overhead
+// Start off with a default cache of 32K (136 records of 240 bytes each)
+// Each time we grow the cache we add another 136 records
+// 136 * 240 = 32640 bytes.
+// This fits in eight 4kB pages, with 128 bytes spare for memory block headers and similar overhead
 #define RR_CACHE_SIZE ((32*1024) / sizeof(CacheRecord))
 static CacheEntity rrcachestorage[RR_CACHE_SIZE];
-struct CompileTimeAssertionChecks_RR_CACHE_SIZE { char a[(RR_CACHE_SIZE >= 141) ? 1 : -1]; };
+struct CompileTimeAssertionChecks_RR_CACHE_SIZE { char a[(RR_CACHE_SIZE >= 136) ? 1 : -1]; };
 #define kRRCacheGrowSize (sizeof(CacheEntity) * RR_CACHE_SIZE)
 
 
@@ -107,6 +108,7 @@ static mDNSBool NoMulticastAdvertisements = mDNSfalse; // By default, advertise
 
 extern mDNSBool StrictUnicastOrdering;
 extern mDNSBool AlwaysAppendSearchDomains;
+extern mDNSBool EnableAllowExpired;
 
 #if ENABLE_BLE_TRIGGERED_BONJOUR
 extern mDNSBool EnableBLEBasedDiscovery;
@@ -571,7 +573,7 @@ mDNSexport void mDNSPlatformLogToFile(int log_level, const char *buffer)
     if (!log_general)
         os_log_error(OS_LOG_DEFAULT, "Could NOT create log handle in init_logging()");
     else
-        os_log_with_type(log_general, log_level, "%s", buffer);
+        os_log_with_type(log_general, log_level, "%{private}s", buffer);
     
 }
 
@@ -652,6 +654,7 @@ mDNSlocal void SignalCallback(CFMachPortRef port, void *msg, CFIndex size, void
         mDNS_Lock(m);
         FORALL_CACHERECORDS(slot, cg, rr)
         {
+            rr->resrec.mortality = Mortality_Mortal;
             mDNS_PurgeCacheResourceRecord(m, rr);
         }
         // Restart unicast and multicast queries
@@ -763,7 +766,8 @@ mDNSlocal void SignalDispatch(dispatch_source_t source)
         mDNS_Lock(m);
         FORALL_CACHERECORDS(slot, cg, rr)
         {
-            mDNS_PurgeCacheResourceRecord(m, rr);
+           rr->resrec.mortality = Mortality_Mortal;
+           mDNS_PurgeCacheResourceRecord(m, rr);
         }
         // Restart unicast and multicast queries
         mDNSCoreRestartQueries(m);
@@ -1528,6 +1532,7 @@ mDNSexport int main(int argc, char **argv)
             UseInternalSleepProxy = (i+1<argc && mDNSIsDigit(argv[i+1][0]) && argv[i+1][1]==0) ? atoi(argv[++i]) : 1;
         if (!strcasecmp(argv[i], "-StrictUnicastOrdering"    )) StrictUnicastOrdering     = mDNStrue;
         if (!strcasecmp(argv[i], "-AlwaysAppendSearchDomains")) AlwaysAppendSearchDomains = mDNStrue;
+        if (!strcasecmp(argv[i], "-DisableAllowExpired"      )) EnableAllowExpired        = mDNSfalse;
 #if DEBUG
         if (!strcasecmp(argv[i], "-UseDebugSocket"))            useDebugSocket = mDNStrue;
         if (!strcasecmp(argv[i], "-NoSandbox"))                 useSandbox = mDNSfalse;
@@ -1568,6 +1573,7 @@ mDNSexport int main(int argc, char **argv)
     NoMulticastAdvertisements = PreferencesGetValueBool(kPreferencesKey_NoMulticastAdvertisements, NoMulticastAdvertisements);
     StrictUnicastOrdering     = PreferencesGetValueBool(kPreferencesKey_StrictUnicastOrdering,     StrictUnicastOrdering);
     AlwaysAppendSearchDomains = PreferencesGetValueBool(kPreferencesKey_AlwaysAppendSearchDomains, AlwaysAppendSearchDomains);
+    EnableAllowExpired        = PreferencesGetValueBool(kPreferencesKey_EnableAllowExpired,        EnableAllowExpired);
     OfferSleepProxyService    = PreferencesGetValueInt(kPreferencesKey_OfferSleepProxyService,     OfferSleepProxyService);
     UseInternalSleepProxy     = PreferencesGetValueInt(kPreferencesKey_UseInternalSleepProxy,      UseInternalSleepProxy);
 
diff --git a/mDNSResponder/mDNSMacOSX/helper-stubs.c b/mDNSResponder/mDNSMacOSX/helper-stubs.c
index a1e804f..c7c3496 100644
--- a/mDNSResponder/mDNSMacOSX/helper-stubs.c
+++ b/mDNSResponder/mDNSMacOSX/helper-stubs.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2007-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2007-2018 Apple 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.
@@ -55,10 +55,10 @@ static int64_t maxwait_secs = 5LL;
 //*************************************************************************************************************
 // Utility Functions
 
-static void LogDebug(const char *prefix, xpc_object_t o)
+static void HelperLog(const char *prefix, xpc_object_t o)
 {
     char *desc = xpc_copy_description(o);
-    mDNSHELPER_DEBUG("LogDebug %s: %s", prefix, desc);
+    mDNSHELPER_DEBUG("HelperLog %s: %s", prefix, desc);
     free(desc);
 }
 
@@ -83,7 +83,7 @@ mDNSlocal int SendDict_ToServer(xpc_object_t msg)
 {
     __block int errorcode = kHelperErr_NoResponse;
     
-    LogDebug("SendDict_ToServer Sending msg to Daemon", msg);
+    HelperLog("SendDict_ToServer Sending msg to Daemon", msg);
     
     dispatch_semaphore_t sem = dispatch_semaphore_create(0);
     dispatch_retain(sem); // for the block below
@@ -94,7 +94,7 @@ mDNSlocal int SendDict_ToServer(xpc_object_t msg)
                                                
         if (type == XPC_TYPE_DICTIONARY)
         {
-            LogDebug("SendDict_ToServer Received reply msg from Daemon", recv_msg);
+            HelperLog("SendDict_ToServer Received reply msg from Daemon", recv_msg);
             uint64_t reply_status = xpc_dictionary_get_uint64(recv_msg, kHelperReplyStatus);
             errorcode = xpc_dictionary_get_int64(recv_msg, kHelperErrCode);
             
@@ -112,7 +112,7 @@ mDNSlocal int SendDict_ToServer(xpc_object_t msg)
         {
             LogMsg("SendDict_ToServer Received unexpected reply from daemon [%s]",
                     xpc_dictionary_get_string(recv_msg, XPC_ERROR_KEY_DESCRIPTION));
-            LogDebug("SendDict_ToServer Unexpected Reply contents", recv_msg);
+            HelperLog("SendDict_ToServer Unexpected Reply contents", recv_msg);
         }
         
         dispatch_semaphore_signal(sem);
@@ -137,7 +137,7 @@ mDNSlocal xpc_object_t SendDict_GetReply(xpc_object_t msg)
     if (!dict) return NULL;
     xpc_retain(dict);
 
-    LogDebug("SendDict_GetReply Sending msg to Daemon", msg);
+    HelperLog("SendDict_GetReply Sending msg to Daemon", msg);
     
     dispatch_semaphore_t sem = dispatch_semaphore_create(0);
     dispatch_retain(sem); // for the block below
@@ -148,7 +148,7 @@ mDNSlocal xpc_object_t SendDict_GetReply(xpc_object_t msg)
                                                
         if (type == XPC_TYPE_DICTIONARY)
         {
-            LogDebug("SendDict_GetReply Received reply msg from Daemon", recv_msg);
+            HelperLog("SendDict_GetReply Received reply msg from Daemon", recv_msg);
             uint64_t reply_status = xpc_dictionary_get_uint64(recv_msg, kHelperReplyStatus);
             
             switch (reply_status)
@@ -171,7 +171,7 @@ mDNSlocal xpc_object_t SendDict_GetReply(xpc_object_t msg)
         {
             LogMsg("SendDict_GetReply Received unexpected reply from daemon [%s]",
                     xpc_dictionary_get_string(recv_msg, XPC_ERROR_KEY_DESCRIPTION));
-            LogDebug("SendDict_GetReply Unexpected Reply contents", recv_msg);
+            HelperLog("SendDict_GetReply Unexpected Reply contents", recv_msg);
         }
         
         dispatch_semaphore_signal(sem);
diff --git a/mDNSResponder/mDNSMacOSX/helper.c b/mDNSResponder/mDNSMacOSX/helper.c
index dc90b35..7b6cb89 100644
--- a/mDNSResponder/mDNSMacOSX/helper.c
+++ b/mDNSResponder/mDNSMacOSX/helper.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2007-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2007-2018 Apple 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.
@@ -710,7 +710,12 @@ fin:
 
 enum DNSKeyFormat
 {
-    formatNotDNSKey, formatDdnsTypeItem, formatDnsPrefixedServiceItem, formatBtmmPrefixedServiceItem
+    formatNotDNSKey,
+    formatDdnsTypeItem,
+    formatDnsPrefixedServiceItem,
+#if MDNSRESPONDER_BTMM_SUPPORT
+    formatBtmmPrefixedServiceItem
+#endif
 };
 
 // On Mac OS X on Intel, the four-character string seems to be stored backwards, at least sometimes.
@@ -720,7 +725,9 @@ enum DNSKeyFormat
 
 
 #ifndef NO_SECURITYFRAMEWORK
+#if MDNSRESPONDER_BTMM_SUPPORT
 static const char btmmprefix[] = "btmmdns:";
+#endif
 static const char dnsprefix[] = "dns:";
 static const char ddns[] = "ddns";
 static const char ddnsrev[] = "sndd";
@@ -778,8 +785,10 @@ static enum DNSKeyFormat getDNSKeyFormat(SecKeychainItemRef item, SecKeychainAtt
     }
     if (attributes->attr[1].length >= sizeof(dnsprefix)-1 && 0 == strncasecmp(attributes->attr[1].data, dnsprefix, sizeof(dnsprefix)-1))
         format = formatDnsPrefixedServiceItem;
+#if MDNSRESPONDER_BTMM_SUPPORT
     else if (attributes->attr[1].length >= sizeof(btmmprefix)-1 && 0 == strncasecmp(attributes->attr[1].data, btmmprefix, sizeof(btmmprefix)-1))
         format = formatBtmmPrefixedServiceItem;
+#endif
     else if (attributes->attr[0].length == sizeof(ddns)-1 && 0 == strncasecmp(attributes->attr[0].data, ddns, sizeof(ddns)-1))
         format = formatDdnsTypeItem;
     else if (attributes->attr[0].length == sizeof(ddnsrev)-1 && 0 == strncasecmp(attributes->attr[0].data, ddnsrev, sizeof(ddnsrev)-1))
@@ -821,7 +830,9 @@ static CFPropertyListRef copyKeychainItemInfo(SecKeychainItemRef item, SecKeycha
             data = CFDataCreate(kCFAllocatorDefault, attributes->attr[1].data, attributes->attr[1].length);
             break;
         case formatDnsPrefixedServiceItem:
+#if MDNSRESPONDER_BTMM_SUPPORT
         case formatBtmmPrefixedServiceItem:
+#endif
             data = CFDataCreate(kCFAllocatorDefault, attributes->attr[1].data, attributes->attr[1].length);
             break;
         default:
diff --git a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
index 3bb4ec6..f64e28a 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
+++ b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2016 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -86,22 +86,22 @@
 #include <SystemConfiguration/SCPrivate.h>
 
 #if TARGET_OS_IPHONE
-// For WiFiManagerClientRef etc, declarations.
-#include <MobileGestalt.h>
-#include <MobileWiFi/WiFiManagerClient.h>
+#include <MobileWiFi/WiFiManagerClient.h> // For WiFiManagerClientRef etc, declarations.
 #include <dlfcn.h>
+#include <os/variant_private.h>           // For os_variant_has_internal_diagnostics().
 #endif // TARGET_OS_IPHONE
 
 // Include definition of opaque_presence_indication for KEV_DL_NODE_PRESENCE handling logic.
 #include <Kernel/IOKit/apple80211/apple80211_var.h>
 #include <network_information.h>  // for nwi_state
 
-#if APPLE_OSX_mDNSResponder
+#if MDNSRESPONDER_BTMM_SUPPORT
 #include <AWACS.h>
+#endif
+
+#if APPLE_OSX_mDNSResponder
 #include <ne_session.h> // for ne_session_set_socket_attributes()
-#else
-#define NO_AWACS 1
-#endif // APPLE_OSX_mDNSResponder
+#endif
 
 #if APPLE_OSX_mDNSResponder && !TARGET_OS_EMBEDDED
 #include <IOKit/platform/IOPlatformSupportPrivate.h>
@@ -153,9 +153,11 @@ static CFStringRef NetworkChangedKey_Computername;
 static CFStringRef NetworkChangedKey_DNS;
 static CFStringRef NetworkChangedKey_StateInterfacePrefix;
 static CFStringRef NetworkChangedKey_DynamicDNS       = CFSTR("Setup:/Network/DynamicDNS");
+static CFStringRef NetworkChangedKey_PowerSettings    = CFSTR("State:/IOKit/PowerManagement/CurrentSettings");
+#if MDNSRESPONDER_BTMM_SUPPORT
 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");
+#endif
 
 static char HINFO_HWstring_buffer[32];
 static char *HINFO_HWstring = "Device";
@@ -794,8 +796,8 @@ mDNSexport mStatus mDNSPlatformSendUDP(const mDNS *const m, const void *const ms
                 s, InterfaceID, ifa_name, dst->type, dst, mDNSVal16(dstPort), s, err, sendto_errno, strerror(sendto_errno), (mDNSu32)(m->timenow));
         if (!mDNSAddressIsAllDNSLinkGroup(dst))
         {
-            if (sendto_errno == EHOSTUNREACH) return(mStatus_HostUnreachErr);
-            if (sendto_errno == EHOSTDOWN || sendto_errno == ENETDOWN || sendto_errno == ENETUNREACH) return(mStatus_TransientErr);
+            if ((sendto_errno == EHOSTUNREACH) || (sendto_errno == ENETUNREACH)) return(mStatus_HostUnreachErr);
+            if ((sendto_errno == EHOSTDOWN)    || (sendto_errno == ENETDOWN))    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>)
@@ -3310,6 +3312,40 @@ mDNSlocal u_int64_t getExtendedFlags(char * ifa_name)
     return ifr.ifr_eflags;
 }
 
+#if TARGET_OS_OSX 
+// IFRTYPE_FUNCTIONAL_INTCOPROC type interfaces on macOS do not support Bonjour discovery.
+mDNSlocal mDNSBool isCoprocessorInterface(int sockFD, char * ifa_name)
+{
+    struct ifreq ifr;
+
+    if (sockFD < 0)
+    {
+        LogMsg("isCoprocessorInterface: invalid socket FD passed: %d", sockFD);
+        return mDNSfalse;
+    }
+
+    memset(&ifr, 0, sizeof(struct ifreq));
+    strlcpy(ifr.ifr_name, ifa_name, sizeof(ifr.ifr_name));
+
+    if (ioctl(sockFD, SIOCGIFFUNCTIONALTYPE, (caddr_t)&ifr) == -1)
+    {
+        LogMsg("isCoprocessorInterface: SIOCGIFFUNCTIONALTYPE failed, errno = %d (%s)", errno, strerror(errno));
+        return mDNSfalse;
+    }
+
+    if (ifr.ifr_functional_type == IFRTYPE_FUNCTIONAL_INTCOPROC)
+    {
+        LogMsg("isCoprocessorInterface: %s marked as coprocessor interface", ifa_name);
+        return mDNStrue;
+    }
+    else
+        return mDNSfalse;
+}
+
+#else   // TARGET_OS_OSX
+#define isCoprocessorInterface(A, B)    mDNSfalse
+#endif   // TARGET_OS_OSX
+
 #if TARGET_OS_IPHONE
 
 // Function pointers for the routines we use in the MobileWiFi framework.
@@ -3499,7 +3535,17 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
             // 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;
+
+            // Determine if multicast state has changed.
+            const mDNSBool txrx = MulticastInterface(*p);
+            if ((*p)->ifinfo.McastTxRx != txrx)
+            {
+                (*p)->ifinfo.McastTxRx = txrx;
+                (*p)->Exists = MulticastStateChanged; // State change; need to deregister and reregister this interface
+            }
+            else
+                 (*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;
 
@@ -3539,7 +3585,6 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
     // 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->ifinfo.IgnoreIPv4LL = ((eflags & IFEF_ARPLL) != 0) ? mDNSfalse : mDNStrue;
 
@@ -3566,6 +3611,7 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
         LogInfo("AddInterfaceToList: D2DInterface set for %s", ifa->ifa_name);
 
     i->isExpensive     = (eflags & IFEF_EXPENSIVE) ? mDNStrue: mDNSfalse;
+    i->isAWDL          = (eflags & IFEF_AWDL)      ? mDNStrue: mDNSfalse;
     if (eflags & IFEF_AWDL)
     {
         // Set SupportsUnicastMDNSResponse false for the AWDL interface since unicast reserves
@@ -3590,6 +3636,8 @@ mDNSlocal NetworkInterfaceInfoOSX *AddInterfaceToList(struct ifaddrs *ifa, mDNSs
     i->BPF_len         = 0;
     i->Registered      = mDNSNULL;
 
+    // MulticastInterface() depends on the "m" and "ifa_flags" values being initialized above.
+    i->ifinfo.McastTxRx   = MulticastInterface(i);
     // Do this AFTER i->BSSID has been set up
     i->ifinfo.NetWake  = (eflags & IFEF_EXPENSIVE)? mDNSfalse :  NetWakeInterface(i);
     GetMAC(&i->ifinfo.MAC, scope_id);
@@ -4827,14 +4875,14 @@ mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc)
                 mDNSPlatformMemCopy(m->PrimaryMAC.b, sdl->sdl_data + sdl->sdl_nlen, 6);
         }
 
-        if (ifa->ifa_flags & IFF_UP && ifa->ifa_addr)
+        if (ifa->ifa_flags & IFF_UP && ifa->ifa_addr && !isCoprocessorInterface(InfoSocket, ifa->ifa_name))
             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",
+                    LogMsg("UpdateInterfaceList: 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
@@ -4843,7 +4891,7 @@ mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc)
                 {
                     mDNSAddr ip;
                     SetupAddr(&ip, ifa->ifa_addr);
-                    LogMsg("getifaddrs ifa_netmask for %5s(%d) Flags %04X Family %2d %#a has different family: %d",
+                    LogMsg("UpdateInterfaceList: 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).
@@ -4900,19 +4948,6 @@ mDNSlocal mStatus UpdateInterfaceList(mDNSs32 utc)
     if (!foundav4 && v4Loopback) AddInterfaceToList(v4Loopback, utc);
     if (!foundav6 && v6Loopback) AddInterfaceToList(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 (i->ifinfo.McastTxRx != txrx)
-            {
-                i->ifinfo.McastTxRx = txrx;
-                i->Exists = MulticastStateChanged; // State change; need to deregister and reregister this interface
-            }
-        }
-
     if (InfoSocket >= 0) 
         close(InfoSocket);
 
@@ -5234,7 +5269,7 @@ mDNSlocal int ClearInactiveInterfaces(mDNSs32 utc)
         if (!i->Exists)
         {
             if (i->LastSeen == utc) i->LastSeen = utc - 1;
-            mDNSBool delete = (NumCacheRecordsForInterfaceID(m, i->ifinfo.InterfaceID) == 0) && (utc - i->LastSeen >= 60);
+            const mDNSBool delete = (i->isAWDL || (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,
@@ -5818,6 +5853,7 @@ mDNSlocal void SetupDDNSDomains(domainname *const fqdn, DNameListElem **RegDomai
         }
         CFRelease(ddnsdict);
     }
+#if MDNSRESPONDER_BTMM_SUPPORT
     if (RegDomains)
     {
         CFDictionaryRef btmm = SCDynamicStoreCopyValue(NULL, NetworkChangedKey_BackToMyMac);
@@ -5847,7 +5883,7 @@ mDNSlocal void SetupDDNSDomains(domainname *const fqdn, DNameListElem **RegDomai
             CFRelease(btmm);
         }
     }
-
+#endif
 }
 
 // Returns mDNSfalse, if it does not set the configuration i.e., if the DNS configuration did not change
@@ -6141,7 +6177,7 @@ mDNSexport void mDNSPlatformDynDNSHostNameStatusChanged(const domainname *const
     }
 }
 
-#if APPLE_OSX_mDNSResponder
+#if MDNSRESPONDER_BTMM_SUPPORT
 #if !NO_AWACS
 
 // checks whether a domain is present in Setup:/Network/BackToMyMac. Just because there is a key in the
@@ -6283,7 +6319,7 @@ mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m)
         else LogInfo("UpdateBTMMRelayConnection: Not calling AWS_Disconnect");
     }
 }
-#elif !TARGET_OS_EMBEDDED
+#else
 mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m)
 {
     (void) m; // Unused
@@ -6291,11 +6327,9 @@ mDNSlocal void UpdateBTMMRelayConnection(mDNS *const m)
 }
 #endif // ! NO_AWACS
 
-#if !TARGET_OS_EMBEDDED
 mDNSlocal void ProcessConndConfigChanges(void);
-#endif
 
-#endif // APPLE_OSX_mDNSResponder
+#endif // MDNSRESPONDER_BTMM_SUPPORT
 
 // MUST be called holding the lock
 mDNSlocal void SetDomainSecrets_internal(mDNS *m)
@@ -6369,11 +6403,13 @@ mDNSlocal void SetDomainSecrets_internal(mDNS *m)
             offset = 0;
             if (!strncmp(stringbuf, dnsprefix, strlen(dnsprefix)))
                 offset = strlen(dnsprefix);
+#if MDNSRESPONDER_BTMM_SUPPORT
             else if (!strncmp(stringbuf, btmmprefix, strlen(btmmprefix)))
             {
                 AutoTunnel = mDNStrue;
                 offset = strlen(btmmprefix);
             }
+#endif
             domainname domain;
             if (!MakeDomainNameFromDNSNameString(&domain, stringbuf + offset)) { LogMsg("SetDomainSecrets: bad key domain %s", stringbuf); continue; }
 
@@ -6555,7 +6591,9 @@ mDNSlocal void SetDomainSecrets_internal(mDNS *m)
         }
 
         UpdateAnonymousRacoonConfig(m);     // Determine whether we need racoon to accept incoming connections
+#if MDNSRESPONDER_BTMM_SUPPORT
         ProcessConndConfigChanges();       // Update AutoTunnelInnerAddress values and default ipsec policies as necessary
+#endif
     }
 #endif // APPLE_OSX_mDNSResponder
 
@@ -7236,8 +7274,9 @@ mDNSexport void RemoveAutoTunnel6Record(mDNS *const m)
         if (info->AutoTunnel)
             UpdateAutoTunnel6Record(m, info);
 }
+#endif /* APPLE_OSX_mDNSResponder */
 
-#if !TARGET_OS_EMBEDDED
+#if MDNSRESPONDER_BTMM_SUPPORT
 mDNSlocal mDNSBool IPv6AddressIsOnInterface(mDNSv6Addr ipv6Addr, char *ifname)
 {
     struct ifaddrs  *ifa;
@@ -7474,8 +7513,7 @@ mDNSlocal void ProcessConndConfigChanges(void)
     // If awacsd crashes or exits for some reason, restart it
     UpdateBTMMRelayConnection(m);
 }
-#endif // !TARGET_OS_EMBEDDED
-#endif /* APPLE_OSX_mDNSResponder */
+#endif // MDNSRESPONDER_BTMM_SUPPORT
 
 mDNSlocal mDNSBool IsAppleNetwork(mDNS *const m)
 {
@@ -7580,9 +7618,11 @@ mDNSexport void mDNSMacOSXNetworkChanged(void)
 
 #if APPLE_OSX_mDNSResponder
 #if !TARGET_OS_EMBEDDED
+#if MDNSRESPONDER_BTMM_SUPPORT
     mDNS_Lock(m);
     ProcessConndConfigChanges();
     mDNS_Unlock(m);
+#endif
 
     // Scan to find client tunnels whose questions have completed,
     // but whose local inner/outer addresses have changed since the tunnel was set up
@@ -7806,14 +7846,18 @@ mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, v
     //mDNSs32 delay = mDNSPlatformOneSecond * 2;                // Start off assuming a two-second delay
     const mDNSs32 delay = (mDNSPlatformOneSecond + 39) / 40;	// 25 ms delay
 
-    int c = CFArrayGetCount(changedKeys);                   // Count changes
+    const int c = CFArrayGetCount(changedKeys);                   // Count changes
     CFRange range = { 0, c };
-    int c_host = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Hostnames   ) != 0);
-    int c_comp = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Computername) != 0);
-    int c_udns = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DNS         ) != 0);
-    int c_ddns = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DynamicDNS  ) != 0);
-    int c_btmm = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_BackToMyMac ) != 0);
-    int c_v4ll = ChangedKeysHaveIPv4LL(changedKeys);
+    const int c_host = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Hostnames   ) != 0);
+    const int c_comp = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_Computername) != 0);
+    const int c_udns = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DNS         ) != 0);
+    const int c_ddns = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_DynamicDNS  ) != 0);
+#if MDNSRESPONDER_BTMM_SUPPORT
+    const int c_btmm = (CFArrayContainsValue(changedKeys, range, NetworkChangedKey_BackToMyMac ) != 0);
+#else
+    const int c_btmm = 0;
+#endif
+    const int c_v4ll = ChangedKeysHaveIPv4LL(changedKeys);
     int c_fast = 0;
     
     // Do immediate network changed processing for "p2p*" interfaces and
@@ -7971,9 +8015,11 @@ mDNSlocal mStatus WatchForNetworkChanges(mDNS *const m)
     CFArrayAppendValue(keys, NetworkChangedKey_Computername);
     CFArrayAppendValue(keys, NetworkChangedKey_DNS);
     CFArrayAppendValue(keys, NetworkChangedKey_DynamicDNS);
-    CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
     CFArrayAppendValue(keys, NetworkChangedKey_PowerSettings);
+#if MDNSRESPONDER_BTMM_SUPPORT
+    CFArrayAppendValue(keys, NetworkChangedKey_BackToMyMac);
     CFArrayAppendValue(keys, NetworkChangedKey_BTMMConnectivity);
+#endif
     CFArrayAppendValue(patterns, pattern1);
     CFArrayAppendValue(patterns, pattern2);
     CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/[^/]+/AirPort"));
@@ -8177,13 +8223,9 @@ mDNSlocal void removeCachedPeerRecords(mDNSu32 ifindex, mDNSAddr *ap, bool purge
 // Handle KEV_DL_NODE_PRESENCE event.
 mDNSlocal void nodePresence(struct kev_dl_node_presence * p)
 {
-    char buf[INET6_ADDRSTRLEN];
     struct opaque_presence_indication *op = (struct opaque_presence_indication *) p->node_service_info;
 
-    if (inet_ntop(AF_INET6, & p->sin6_node_address.sin6_addr, buf, sizeof(buf)))
-        LogInfo("nodePresence:  IPv6 address: %s, SUI %d", buf, op->SUI);
-    else
-        LogInfo("nodePresence:  inet_ntop() error");
+    LogInfo("nodePresence: IPv6 address: %.16a, SUI %d", p->sin6_node_address.sin6_addr.s6_addr, op->SUI);
  
     // AWDL will generate a KEV_DL_NODE_PRESENCE event with SSTH field of
     // all zeroes when a node is present and has no services registered.
@@ -8203,17 +8245,11 @@ mDNSlocal void nodePresence(struct kev_dl_node_presence * p)
 mDNSlocal void nodeAbsence(struct kev_dl_node_absence * p)
 {
     mDNSAddr    peerAddr;
-    char buf[INET6_ADDRSTRLEN];
-
-    if (inet_ntop(AF_INET6, & p->sin6_node_address.sin6_addr, buf, sizeof(buf)))
-        LogInfo("nodeAbsence:  IPv6 address: %s", buf);
-    else
-        LogInfo("nodeAbsence:  inet_ntop() error");
 
     peerAddr.type = mDNSAddrType_IPv6;
     peerAddr.ip.v6 = *(mDNSv6Addr*)&p->sin6_node_address.sin6_addr;
 
-    LogInfo("nodeAbsence: immediately purge cached records from this peer");
+    LogInfo("nodeAbsence: immediately purge cached records from %.16a", p->sin6_node_address.sin6_addr.s6_addr);
     removeCachedPeerRecords(p->sdl_node_address.sdl_index, & peerAddr, true);
 }
 
@@ -8367,8 +8403,13 @@ mDNSlocal OSStatus KeychainChanged(SecKeychainEvent keychainEvent, SecKeychainCa
                 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)))));
+                                (a->attr[1].length >= mDNSPlatformStrLen(dnsprefix) && (!strncasecmp(a->attr[1].data, dnsprefix, mDNSPlatformStrLen(dnsprefix)))));
+#if MDNSRESPONDER_BTMM_SUPPORT
+                    if (!relevant && (a->attr[1].length >= mDNSPlatformStrLen(btmmprefix)) && !strncasecmp(a->attr[1].data, btmmprefix, mDNSPlatformStrLen(btmmprefix)))
+                    {
+                        relevant = mDNStrue;
+                    }
+#endif
                     SecKeychainItemFreeAttributesAndData(a, NULL);
                 }
             }
@@ -9419,36 +9460,13 @@ mDNSlocal void CreatePTRRecord(const domainname *domain)
 // intentionally to avoid adding to the complexity of code handling /etc/hosts.
 mDNSlocal void SetupLocalHostRecords(void)
 {
-    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(&name);
-    }
-    else LogMsg("SetupLocalHostRecords: ERROR!! inet_pton AF_INET failed");
+    MakeDomainNameFromDNSNameString(&name, "1.0.0.127.in-addr.arpa.");
+    CreatePTRRecord(&name);
 
-    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(&name);
-    }
-    else LogMsg("SetupLocalHostRecords: ERROR!! inet_pton AF_INET6 failed");
+    MakeDomainNameFromDNSNameString(&name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.");
+    CreatePTRRecord(&name);
 }
 
 #if APPLE_OSX_mDNSResponder // Don't compile for dnsextd target
@@ -9465,6 +9483,56 @@ mDNSlocal void setSameDomainLabelPointer(void);
 // 6) client calls to enumerate domains now go over LocalOnly interface
 //    (!!!KRS may add outgoing interface in addition)
 
+#if TARGET_OS_IPHONE
+mDNSlocal mDNSBool IsAppleInternalBuild(void)
+{
+    return (os_variant_has_internal_diagnostics("com.apple.mDNSResponder") ? mDNStrue : mDNSfalse);
+}
+
+mDNSlocal mStatus RegisterLocalOnlyAddressRecord(const domainname *const name, mDNSu16 type, const void *rdata, mDNSu16 rdlength)
+{
+    switch(type)
+    {
+    case kDNSType_A:
+        if (rdlength != 4) return (mStatus_BadParamErr);
+        break;
+
+    case kDNSType_AAAA:
+        if (rdlength != 16) return (mStatus_BadParamErr);
+        break;
+
+    default:
+        return (mStatus_BadParamErr);
+    }
+
+    AuthRecord *rr = mallocL("etchosts", sizeof(*rr));
+    if (!rr) return (mStatus_NoMemoryErr);
+    mDNSPlatformMemZero(rr, sizeof(*rr));
+
+    mDNS_SetupResourceRecord(rr, NULL, mDNSInterface_LocalOnly, type, 1, kDNSRecordTypeKnownUnique, AuthRecordLocalOnly, FreeEtcHosts, NULL);
+    AssignDomainName(&rr->namestorage, name);
+    mDNSPlatformMemCopy(rr->resrec.rdata->u.data, rdata, rdlength);
+
+    const mStatus err = mDNS_Register_internal(&mDNSStorage, rr);
+    if (err)
+    {
+        LogMsg("RegisterLocalOnlyAddressRecord: mDNS_Register error %d registering %s", err, ARDisplayString(&mDNSStorage, rr));
+        freeL("etchosts", rr);
+    }
+    return (err);
+}
+
+mDNSlocal void RegisterLocalOnlyARecord(const domainname *const name, const mDNSv4Addr *const addr)
+{
+    RegisterLocalOnlyAddressRecord(name, kDNSType_A, addr->b, (mDNSu16)sizeof(mDNSv4Addr));
+}
+
+mDNSlocal void RegisterLocalOnlyAAAARecord(const domainname *const name, const mDNSv6Addr *const addr)
+{
+    RegisterLocalOnlyAddressRecord(name, kDNSType_AAAA, addr->b, (mDNSu16)sizeof(mDNSv6Addr));
+}
+#endif
+
 mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
 {
     mStatus err;
@@ -9694,7 +9762,41 @@ mDNSlocal mStatus mDNSPlatformInit_setup(mDNS *const m)
 #endif
     if (SSLqueue == mDNSNULL) LogMsg("dispatch_queue_create: SSL queue NULL");
 
-    mDNSMacOSXUpdateEtcHosts(m);
+#if TARGET_OS_IPHONE
+    // On device OSes (iOS, tvOS, watchOS, etc.), ignore /etc/hosts unless the OS is an internal build. When the /etc/hosts
+    // file is ignored, LocalOnly auth records will be registered for localhost and broadcasthost addresses contained in the
+    // standard /etc/hosts file:
+    //
+    //  127.0.0.1       localhost
+    //  255.255.255.255 broadcasthost
+    //  ::1             localhost
+
+    if (!IsAppleInternalBuild())
+    {
+        const domainname *const localHostName     = (const domainname *) "\x9" "localhost";
+        const domainname *const broadcastHostName = (const domainname *) "\xd" "broadcasthost";
+        const mDNSv4Addr        localHostV4       = { { 127, 0, 0, 1 } };
+        mDNSv6Addr              localHostV6;
+
+        // Register localhost 127.0.0.1 A record.
+
+        RegisterLocalOnlyARecord(localHostName, &localHostV4);
+
+        // Register broadcasthost 255.255.255.255 A record.
+
+        RegisterLocalOnlyARecord(broadcastHostName, &onesIPv4Addr);
+
+        // Register localhost ::1 AAAA record.
+
+        mDNSPlatformMemZero(&localHostV6, sizeof(localHostV6));
+        localHostV6.b[15] = 1;
+        RegisterLocalOnlyAAAARecord(localHostName, &localHostV6);
+    }
+    else
+#endif
+    {
+        mDNSMacOSXUpdateEtcHosts(m);
+    }
     SetupLocalHostRecords();
 
     return(mStatus_NoError);
diff --git a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.h b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.h
index 55c74c6..f189b07 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.h
+++ b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.h
@@ -153,6 +153,7 @@ struct NetworkInterfaceInfoOSX_struct
     int BPF_mcfd;                               // Socket for our IPv6 ND group membership
     u_int BPF_len;
     mDNSBool isExpensive;                       // True if this interface has the IFEF_EXPENSIVE flag set.
+    mDNSBool isAWDL;                            // True if this interface has the IFEF_AWDL flag set.
 #ifdef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
     dispatch_source_t BPF_source;
 #else
@@ -272,7 +273,7 @@ struct CompileTimeAssertionChecks_mDNSMacOSX
     // Check our structures are reasonable sizes. Including overly-large buffers, or embedding
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
-    char sizecheck_NetworkInterfaceInfoOSX[(sizeof(NetworkInterfaceInfoOSX) <=  7464) ? 1 : -1];
+    char sizecheck_NetworkInterfaceInfoOSX[(sizeof(NetworkInterfaceInfoOSX) <=  8488) ? 1 : -1];
     char sizecheck_mDNS_PlatformSupport   [(sizeof(mDNS_PlatformSupport)    <=  1378) ? 1 : -1];
 };
 
diff --git a/mDNSResponder/mDNSMacOSX/mDNSResponder.sb b/mDNSResponder/mDNSMacOSX/mDNSResponder.sb
index 1458815..2918631 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSResponder.sb
+++ b/mDNSResponder/mDNSMacOSX/mDNSResponder.sb
@@ -1,6 +1,6 @@
 ; -*- Mode: Scheme; tab-width: 4 -*-
 ;
-; Copyright (c) 2012-2015 Apple Inc. All rights reserved.
+; Copyright (c) 2012-2018 Apple Inc. All rights reserved.
 ;
 ; Redistribution and use in source and binary forms, with or without 
 ; modification, are permitted provided that the following conditions are met:
@@ -128,7 +128,7 @@
        (literal "/private/var/preferences/SystemConfiguration/preferences.plist")
        (subpath "/System/Library/Preferences/Logging")
        (subpath "/AppleInternal/Library/Preferences/Logging")
-       (subpath "/private/var/preferences/Logging/Subsystems")
+       (subpath "/private/var/preferences/Logging")
        (subpath "/private/var/db/timezone")
        (subpath "/Library/Preferences/Logging"))
 
diff --git a/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj b/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
index f1a8b1d..bbbd12f 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
+++ b/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
@@ -271,8 +271,9 @@
 		4AAE0C9A0C68EA81003882A5 /* mDNSResponderHelper.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4AAE0C7A0C68E97F003882A5 /* mDNSResponderHelper.8 */; };
 		4BD2B63A134FE09F002B96D5 /* P2PPacketFilter.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD2B638134FE09F002B96D5 /* P2PPacketFilter.c */; };
 		4BD2B63B134FE09F002B96D5 /* P2PPacketFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BD2B639134FE09F002B96D5 /* P2PPacketFilter.h */; };
-		729DF4601CD40630005ECF70 /* com.apple.mDNSResponder.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = 729DF45F1CD40630005ECF70 /* com.apple.mDNSResponder.plist */; };
 		72FB5467166D5FCA0090B2D9 /* dnsctl.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FB545A166D5F960090B2D9 /* dnsctl.c */; };
+		789036921F7AC1FA0077A962 /* libnetwork.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 789036911F7AC1F90077A962 /* libnetwork.tbd */; };
+		789036931F7AC2050077A962 /* libnetwork.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 789036911F7AC1F90077A962 /* libnetwork.tbd */; };
 		8415A6571897109000BDBA26 /* libdns_services.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8415A6561897109000BDBA26 /* libdns_services.dylib */; };
 		8417375C1B967D37000CD5C2 /* dnsctl_server.c in Sources */ = {isa = PBXBuildFile; fileRef = 8417375A1B967CBE000CD5C2 /* dnsctl_server.c */; };
 		848DA5C7165477E000D2E8B4 /* xpc_services.c in Sources */ = {isa = PBXBuildFile; fileRef = 848DA5C6165477E000D2E8B4 /* xpc_services.c */; };
@@ -324,8 +325,14 @@
 		B7E06B0D1DBA9DFE00E4580C /* ClientCommon.c in Sources */ = {isa = PBXBuildFile; fileRef = FF5852100DD27BD300862BDF /* ClientCommon.c */; };
 		B7E06B0E1DBA9E9700E4580C /* DomainBrowser.strings in Resources */ = {isa = PBXBuildFile; fileRef = B7016F4F1D5D0D1900107E7C /* DomainBrowser.strings */; };
 		BD03E88D1AD31278005E8A81 /* SymptomReporter.c in Sources */ = {isa = PBXBuildFile; fileRef = BD03E88C1AD31278005E8A81 /* SymptomReporter.c */; };
+		BD28AE8F207B892D00F0B257 /* bonjour-mcast-diagnose in Copy diagnose scripts */ = {isa = PBXBuildFile; fileRef = BD28AE8E207B88F600F0B257 /* bonjour-mcast-diagnose */; };
+		BD41B27D203EBE6100A53629 /* dns_sd.h in Headers */ = {isa = PBXBuildFile; fileRef = FFA572630AF190C20055A0F1 /* dns_sd.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		BD41F9C4209B60AC0077F8B6 /* libpcap.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BD41F9C3209B60AC0077F8B6 /* libpcap.tbd */; };
 		BD691B2A1ED2F47100E6F317 /* DNS64.c in Sources */ = {isa = PBXBuildFile; fileRef = BD691B281ED2F43200E6F317 /* DNS64.c */; };
 		BD691B2B1ED2F4AB00E6F317 /* DNS64.h in Headers */ = {isa = PBXBuildFile; fileRef = BD691B291ED2F43200E6F317 /* DNS64.h */; };
+		BD75E940206ADEF400656ED3 /* com.apple.mDNSResponder.plist in Copy AppleInternal Logging Profile */ = {isa = PBXBuildFile; fileRef = BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */; };
+		BD893CE5206C0D980055F9E7 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */; };
+		BD893CE7206C0EAF0055F9E7 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */; };
 		BD9BA7551EAF91FB00658CCF /* dnssdutil.c in Sources */ = {isa = PBXBuildFile; fileRef = BD9BA7541EAF91E700658CCF /* dnssdutil.c */; };
 		BD9BA7581EAF929C00658CCF /* CoreUtils.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD9BA7571EAF929C00658CCF /* CoreUtils.framework */; };
 		BDA3F08A1C48DB920054FB4B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDA3F0891C48DB910054FB4B /* Foundation.framework */; };
@@ -333,6 +340,12 @@
 		BDA3F08F1C48DCA50054FB4B /* Metrics.m in Sources */ = {isa = PBXBuildFile; fileRef = BDA3F0881C48DB6D0054FB4B /* Metrics.m */; };
 		BDA9A7881B3A924C00523835 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		BDA9A7891B3A92A500523835 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		BDAF4BC020B52D3D0062219E /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BDAF4BBF20B52D3D0062219E /* CFNetwork.framework */; };
+		BDB04221203FEF4C00419961 /* dns_sd.h in Headers */ = {isa = PBXBuildFile; fileRef = FFA572630AF190C20055A0F1 /* dns_sd.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		BDB04222203FEF4D00419961 /* dns_sd.h in Headers */ = {isa = PBXBuildFile; fileRef = FFA572630AF190C20055A0F1 /* dns_sd.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		BDB04223203FF18000419961 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		BDB04224203FF18000419961 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		BDB61845206ADB9D00AFF600 /* com.apple.mDNSResponder.plist in Copy Base Logging Profile */ = {isa = PBXBuildFile; fileRef = BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */; };
 		BDBF9B941ED74B9C001498A8 /* DNS64State.h in Headers */ = {isa = PBXBuildFile; fileRef = BDBF9B931ED74B8C001498A8 /* DNS64State.h */; };
 		D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */ = {isa = PBXBuildFile; fileRef = F5E11B5B04A28126019798ED /* dnssd_ipc.h */; };
 		D284BE580ADD80740027CCDF /* mDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBE9022EAF5A00000109 /* mDNS.c */; };
@@ -717,14 +730,15 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
-		8418673D15AB8BFF00BB7F70 /* CopyFiles */ = {
+		8418673D15AB8BFF00BB7F70 /* Copy Base Logging Profile */ = {
 			isa = PBXCopyFilesBuildPhase;
 			buildActionMask = 8;
 			dstPath = /System/Library/Preferences/Logging/Subsystems;
 			dstSubfolderSpec = 0;
 			files = (
-				729DF4601CD40630005ECF70 /* com.apple.mDNSResponder.plist in CopyFiles */,
+				BDB61845206ADB9D00AFF600 /* com.apple.mDNSResponder.plist in Copy Base Logging Profile */,
 			);
+			name = "Copy Base Logging Profile";
 			runOnlyForDeploymentPostprocessing = 1;
 		};
 		B7D566C61E81D9B600E43008 /* CopyFiles */ = {
@@ -738,6 +752,28 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		BD28AE8C207B888E00F0B257 /* Copy diagnose scripts */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 8;
+			dstPath = /usr/local/bin;
+			dstSubfolderSpec = 0;
+			files = (
+				BD28AE8F207B892D00F0B257 /* bonjour-mcast-diagnose in Copy diagnose scripts */,
+			);
+			name = "Copy diagnose scripts";
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+		BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 8;
+			dstPath = /AppleInternal/Library/Preferences/Logging/Subsystems;
+			dstSubfolderSpec = 0;
+			files = (
+				BD75E940206ADEF400656ED3 /* com.apple.mDNSResponder.plist in Copy AppleInternal Logging Profile */,
+			);
+			name = "Copy AppleInternal Logging Profile";
+			runOnlyForDeploymentPostprocessing = 1;
+		};
 		D284BE6A0ADD80740027CCDF /* CopyFiles */ = {
 			isa = PBXCopyFilesBuildPhase;
 			buildActionMask = 8;
@@ -891,9 +927,9 @@
 		6575FBE9022EAF5A00000109 /* mDNS.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; name = mDNS.c; path = ../mDNSCore/mDNS.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
 		6575FBEB022EAF7200000109 /* mDNSMacOSX.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = mDNSMacOSX.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
 		6575FBEC022EAF7200000109 /* daemon.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = daemon.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 0; };
-		729DF45F1CD40630005ECF70 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = com.apple.mDNSResponder.plist; path = Private/com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
 		72FB545A166D5F960090B2D9 /* dnsctl.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = dnsctl.c; path = ../Clients/dnsctl.c; sourceTree = "<group>"; };
 		72FB545F166D5FB00090B2D9 /* dnsctl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dnsctl; sourceTree = BUILT_PRODUCTS_DIR; };
+		789036911F7AC1F90077A962 /* libnetwork.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libnetwork.tbd; path = usr/lib/libnetwork.tbd; sourceTree = SDKROOT; };
 		7F18A9F60587CEF6001880B3 /* DNSCommon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = DNSCommon.c; path = ../mDNSCore/DNSCommon.c; sourceTree = SOURCE_ROOT; };
 		7F18A9F70587CEF6001880B3 /* uDNS.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = uDNS.c; path = ../mDNSCore/uDNS.c; sourceTree = SOURCE_ROOT; };
 		7F461DB5062DBF2900672BF3 /* DNSDigest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = DNSDigest.c; path = ../mDNSCore/DNSDigest.c; sourceTree = SOURCE_ROOT; };
@@ -952,8 +988,12 @@
 		B7D6CA701D1076F3005E24CF /* DomainBrowser.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DomainBrowser.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		B7E5920F1DB687A700A38085 /* Base */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Base; path = Base.lproj/DNSServiceDiscoveryPref.nib; sourceTree = "<group>"; };
 		BD03E88C1AD31278005E8A81 /* SymptomReporter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SymptomReporter.c; sourceTree = "<group>"; };
+		BD28AE8E207B88F600F0B257 /* bonjour-mcast-diagnose */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "bonjour-mcast-diagnose"; sourceTree = "<group>"; };
+		BD41F9C3209B60AC0077F8B6 /* libpcap.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libpcap.tbd; path = usr/lib/libpcap.tbd; sourceTree = SDKROOT; };
 		BD691B281ED2F43200E6F317 /* DNS64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DNS64.c; sourceTree = "<group>"; };
 		BD691B291ED2F43200E6F317 /* DNS64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64.h; sourceTree = "<group>"; };
+		BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
+		BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
 		BD9BA7531EAF90E400658CCF /* dnssdutil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dnssdutil; sourceTree = BUILT_PRODUCTS_DIR; };
 		BD9BA7541EAF91E700658CCF /* dnssdutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dnssdutil.c; path = ../Clients/dnssdutil.c; sourceTree = "<group>"; };
 		BD9BA7571EAF929C00658CCF /* CoreUtils.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreUtils.framework; path = System/Library/PrivateFrameworks/CoreUtils.framework; sourceTree = SDKROOT; };
@@ -961,6 +1001,9 @@
 		BDA3F0881C48DB6D0054FB4B /* Metrics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Metrics.m; sourceTree = "<group>"; };
 		BDA3F0891C48DB910054FB4B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
 		BDA9A7871B3A923600523835 /* dns_sd_private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dns_sd_private.h; path = ../mDNSShared/dns_sd_private.h; sourceTree = "<group>"; };
+		BDAF4BBF20B52D3D0062219E /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
+		BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
+		BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
 		BDBF9B931ED74B8C001498A8 /* DNS64State.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64State.h; sourceTree = "<group>"; };
 		BDE238C11DF69D8300B9F696 /* dns_sd_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dns_sd_internal.h; path = ../mDNSShared/dns_sd_internal.h; sourceTree = "<group>"; };
 		D284BE730ADD80740027CCDF /* mDNSResponder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mDNSResponder; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1084,6 +1127,7 @@
 				21B830A61D8A63E400AE2001 /* CoreFoundation.framework in Frameworks */,
 				21B830A21D8A63A300AE2001 /* libxml2.dylib in Frameworks */,
 				37538E141D7A43B600226BE4 /* libicucore.dylib in Frameworks */,
+				789036931F7AC2050077A962 /* libnetwork.tbd in Frameworks */,
 				21B830A41D8A63BB00AE2001 /* Security.framework in Frameworks */,
 				21B830A51D8A63CB00AE2001 /* IOKit.framework in Frameworks */,
 			);
@@ -1149,7 +1193,11 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				BD41F9C4209B60AC0077F8B6 /* libpcap.tbd in Frameworks */,
+				BDAF4BC020B52D3D0062219E /* CFNetwork.framework in Frameworks */,
+				BD893CE7206C0EAF0055F9E7 /* CoreFoundation.framework in Frameworks */,
 				BD9BA7581EAF929C00658CCF /* CoreUtils.framework in Frameworks */,
+				BD893CE5206C0D980055F9E7 /* SystemConfiguration.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1164,6 +1212,7 @@
 				D284BE660ADD80740027CCDF /* SystemConfiguration.framework in Frameworks */,
 				21B830A81D8A642200AE2001 /* libicucore.dylib in Frameworks */,
 				219D5542149ED645004464AE /* libxml2.dylib in Frameworks */,
+				789036921F7AC1FA0077A962 /* libnetwork.tbd in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1246,6 +1295,8 @@
 				19C28FBDFE9D53C911CA2CBB /* Products */,
 				37DDE9241BA382280092AC61 /* Unit Tests */,
 				BD9BA7561EAF929C00658CCF /* Frameworks */,
+				BDB61842206ADB7700AFF600 /* LoggingProfiles */,
+				BD28AE8D207B88F600F0B257 /* Scripts */,
 			);
 			name = mDNSResponder;
 			sourceTree = "<group>";
@@ -1267,7 +1318,6 @@
 				21F51DBD1B3540DB0070B05C /* com.apple.dnsextd.plist */,
 				21F51DBF1B35412D0070B05C /* com.apple.mDNSResponder.plist */,
 				21F51DBE1B3541030070B05C /* com.apple.mDNSResponderHelper.plist */,
-				729DF45F1CD40630005ECF70 /* com.apple.mDNSResponder.plist */,
 				21A57F4A145B2AE100939099 /* CryptoAlg.c */,
 				21A57F4B145B2AE100939099 /* CryptoAlg.h */,
 				21A57F51145B2B1400939099 /* CryptoSupport.c */,
@@ -1568,14 +1618,44 @@
 			path = macOS;
 			sourceTree = "<group>";
 		};
+		BD28AE8D207B88F600F0B257 /* Scripts */ = {
+			isa = PBXGroup;
+			children = (
+				BD28AE8E207B88F600F0B257 /* bonjour-mcast-diagnose */,
+			);
+			path = Scripts;
+			sourceTree = "<group>";
+		};
 		BD9BA7561EAF929C00658CCF /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				BDAF4BBF20B52D3D0062219E /* CFNetwork.framework */,
+				BD41F9C3209B60AC0077F8B6 /* libpcap.tbd */,
+				BD893CE6206C0EAF0055F9E7 /* CoreFoundation.framework */,
+				BD893CE4206C0D980055F9E7 /* SystemConfiguration.framework */,
+				789036911F7AC1F90077A962 /* libnetwork.tbd */,
 				BD9BA7571EAF929C00658CCF /* CoreUtils.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
+		BDB61842206ADB7700AFF600 /* LoggingProfiles */ = {
+			isa = PBXGroup;
+			children = (
+				BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */,
+				BDB61844206ADB7700AFF600 /* AppleInternal */,
+			);
+			path = LoggingProfiles;
+			sourceTree = "<group>";
+		};
+		BDB61844206ADB7700AFF600 /* AppleInternal */ = {
+			isa = PBXGroup;
+			children = (
+				BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */,
+			);
+			path = AppleInternal;
+			sourceTree = "<group>";
+		};
 		DB2CC4420662DCE500335AB3 /* Java Support */ = {
 			isa = PBXGroup;
 			children = (
@@ -1784,6 +1864,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				BDB04221203FEF4C00419961 /* dns_sd.h in Headers */,
+				BDB04223203FF18000419961 /* dns_sd_private.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1791,6 +1873,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				BDB04222203FEF4D00419961 /* dns_sd.h in Headers */,
+				BDB04224203FF18000419961 /* dns_sd_private.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1799,6 +1883,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				BDA9A7881B3A924C00523835 /* dns_sd_private.h in Headers */,
+				BD41B27D203EBE6100A53629 /* dns_sd.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -1881,7 +1966,6 @@
 				2141DD19123FFCDB0086D23E /* Headers */,
 				2141DD1A123FFCDB0086D23E /* Sources */,
 				2141DD1B123FFCDB0086D23E /* Frameworks */,
-				2130256B12400DE600AC839F /* ShellScript */,
 			);
 			buildRules = (
 			);
@@ -2115,8 +2199,10 @@
 				4A7B9E7F14FDA21B00B84CC1 /* CopyFiles */,
 				4A7B9E8114FDA25500B84CC1 /* CopyFiles */,
 				D284BE6C0ADD80740027CCDF /* Run Script */,
-				8418673D15AB8BFF00BB7F70 /* CopyFiles */,
 				21F51DC01B35418C0070B05C /* CopyFiles */,
+				8418673D15AB8BFF00BB7F70 /* Copy Base Logging Profile */,
+				BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */,
+				BD28AE8C207B888E00F0B257 /* Copy diagnose scripts */,
 			);
 			buildRules = (
 			);
@@ -2439,19 +2525,6 @@
 			shellPath = /bin/sh;
 			shellScript = "if [ -e \"${SDKROOT}/usr/local/include/vproc.h\" -o -e \"${SDKROOT}/usr/include/vproc.h\" ]\nthen\nrm -f \"${CONFIGURATION_TEMP_DIR}/vproc.h\"\nelse\ntouch \"${CONFIGURATION_TEMP_DIR}/vproc.h\"\nfi\n\nipsec=$(ls \"${SDKROOT}/usr/lib/libipsec.*\" 2> /dev/null | wc -l)\nif [ \"$ipsec\" != \"0\" ]\nthen\nrm -f \"${CONFIGURATION_TEMP_DIR}/ipsec_options.h\"\ntouch \"${CONFIGURATION_TEMP_DIR}/ipsec_options.h\"\nrm -f \"${CONFIGURATION_TEMP_DIR}/libipsec.a\"\nelse\necho \"#define MDNS_NO_IPSEC 1\" > ${CONFIGURATION_TEMP_DIR}/ipsec_options.h\ntouch \"${CONFIGURATION_TEMP_DIR}/empty.c\"\nfor i in ${ARCHS}\ndo\nccflags=\"-arch $i $ccflags\"\ndone\ncc ${ccflags} \"${CONFIGURATION_TEMP_DIR}/empty.c\" -c -o \"${CONFIGURATION_TEMP_DIR}/libipsec.a\"\nrm -f \"${CONFIGURATION_TEMP_DIR}/empty.c\"\nfi\n";
 		};
-		2130256B12400DE600AC839F /* ShellScript */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 8;
-			files = (
-			);
-			inputPaths = (
-			);
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 1;
-			shellPath = /bin/sh;
-			shellScript = "DSTROOT=${DSTROOT}\n\nmkdir -p \"$DSTROOT/usr/include\"\nsed 's/\\(^#define _DNS_SD_LIBDISPATCH \\)0$/\\1 1/' \"$SRCROOT/../mDNSShared/dns_sd.h\" > \"$DSTROOT/usr/include/dns_sd.h\"";
-		};
 		21DE714D115831CB00DD4BD1 /* ShellScript */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 8;
@@ -2463,7 +2536,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 1;
 			shellPath = "/bin/bash -e -x";
-			shellScript = "DSTROOT=${DSTROOT}\nmkdir -p \"$DSTROOT/usr/include\"\nsed 's/\\(^#define _DNS_SD_LIBDISPATCH \\)0$/\\1 1/' \"$SRCROOT/../mDNSShared/dns_sd.h\" > \"$DSTROOT/usr/include/dns_sd.h\"\n\nif [[ \"${ACTION}\" == \"installhdrs\" ]]; then\n    exit 0\nfi\n\nif [[ \"${PLATFORM_NAME}\" =~ \"simulator\" ]]; then\n    ln -s libsystem_dnssd.dylib ${DSTROOT}${INSTALL_PATH}/libsystem_sim_dnssd.dylib\nfi\n";
+			shellScript = "DSTROOT=${DSTROOT}\n\nif [[ \"${ACTION}\" == \"installhdrs\" ]] || [[ \"${ACTION}\" == \"installapi\" ]]; then\n    exit 0\nfi\n\nif [[ \"${PLATFORM_NAME}\" =~ \"simulator\" ]]; then\n    ln -s libsystem_dnssd.dylib ${DSTROOT}${INSTALL_PATH}/libsystem_sim_dnssd.dylib\nfi\n";
 		};
 		37DDE9341BA384000092AC61 /* ShellScript */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -3154,7 +3227,6 @@
 				GCC_OPTIMIZATION_LEVEL = 0;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"__APPLE_USE_RFC_3542=1",
-					"_DNS_SD_LIBDISPATCH=1",
 					"APPLE_OSX_mDNSResponder=1",
 					"__MigTypeCheck=1",
 					"mDNSResponderVersion=${MVERS}",
@@ -3249,7 +3321,6 @@
 					"-lMobileGestalt",
 				);
 				"OTHER_LDFLAGS[sdk=macosx*][arch=*]" = (
-					"-lAWACS",
 					"-weak_framework",
 					WebFilterDNS,
 					"-weak_framework",
@@ -3339,7 +3410,6 @@
 			buildSettings = {
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"__APPLE_USE_RFC_3542=1",
-					"_DNS_SD_LIBDISPATCH=1",
 					"APPLE_OSX_mDNSResponder=1",
 					"__MigTypeCheck=1",
 					"mDNSResponderVersion=${MVERS}",
@@ -3431,6 +3501,7 @@
 				INSTALLHDRS_SCRIPT_PHASE = YES;
 				INSTALL_PATH = /usr/local/lib/system;
 				PRODUCT_NAME = dns_sd;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
 				"SKIP_INSTALL[sdk=iphonesimulator*]" = YES;
 			};
 			name = Debug;
@@ -3474,12 +3545,14 @@
 					"$(inherited)",
 					"__DARWIN_NON_CANCELABLE=1",
 				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders/";
 				INSTALLHDRS_COPY_PHASE = YES;
 				INSTALLHDRS_SCRIPT_PHASE = YES;
 				INSTALL_PATH = /usr/lib/system;
 				INTERPOSITION_SIM_SUFFIX = "";
 				"INTERPOSITION_SIM_SUFFIX[sdk=iphonesimulator*]" = _sim;
+				IS_ZIPPERED = YES;
 				LINK_WITH_STANDARD_LIBRARIES = NO;
 				OTHER_LDFLAGS = (
 					"-Wl,-umbrella,System",
@@ -3496,7 +3569,11 @@
 					"-llaunch",
 					"-lsystem_asl",
 				);
+				OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
 				PRODUCT_NAME = libsystem_dnssd;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
+				SUPPORTS_TEXT_BASED_API = YES;
+				TAPI_VERIFY_MODE = Pedantic;
 			};
 			name = Debug;
 		};
@@ -3509,6 +3586,7 @@
 					"$(inherited)",
 					"__DARWIN_NON_CANCELABLE=1",
 				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders/";
 				INSTALL_PATH = /usr/lib/system;
 				INTERPOSITION_SIM_SUFFIX = "";
@@ -3529,7 +3607,11 @@
 					"-llaunch",
 					"-lsystem_asl",
 				);
+				OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
 				PRODUCT_NAME = libsystem_dnssd_debug;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
+				SUPPORTS_TEXT_BASED_API = YES;
+				TAPI_VERIFY_MODE = Pedantic;
 			};
 			name = Debug;
 		};
@@ -3543,6 +3625,7 @@
 					"$(inherited)",
 					"__DARWIN_NON_CANCELABLE=1",
 				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				GENERATE_PROFILING_CODE = YES;
 				HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders/";
 				INSTALL_PATH = /usr/lib/system;
@@ -3564,7 +3647,11 @@
 					"-llaunch",
 					"-lsystem_asl",
 				);
+				OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
 				PRODUCT_NAME = libsystem_dnssd_profile;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
+				SUPPORTS_TEXT_BASED_API = YES;
+				TAPI_VERIFY_MODE = Pedantic;
 			};
 			name = Debug;
 		};
@@ -3755,6 +3842,7 @@
 				INSTALLHDRS_SCRIPT_PHASE = YES;
 				INSTALL_PATH = /usr/local/lib/system;
 				PRODUCT_NAME = dns_sd;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
 				"SKIP_INSTALL[sdk=iphonesimulator*]" = YES;
 			};
 			name = Release;
@@ -3836,7 +3924,6 @@
 				);
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"__APPLE_USE_RFC_3542=1",
-					"_DNS_SD_LIBDISPATCH=1",
 					"APPLE_OSX_mDNSResponder=1",
 					"__MigTypeCheck=1",
 					"mDNSResponderVersion=${MVERS}",
@@ -3899,7 +3986,6 @@
 				GCC_OPTIMIZATION_LEVEL = 0;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"__APPLE_USE_RFC_3542=1",
-					"_DNS_SD_LIBDISPATCH=1",
 					"APPLE_OSX_mDNSResponder=1",
 					"__MigTypeCheck=1",
 					"mDNSResponderVersion=${MVERS}",
@@ -4644,7 +4730,6 @@
 				DEAD_CODE_STRIPPING = YES;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"__APPLE_USE_RFC_3542=1",
-					"_DNS_SD_LIBDISPATCH=1",
 					"APPLE_OSX_mDNSResponder=1",
 					"__MigTypeCheck=1",
 					"mDNSResponderVersion=${MVERS}",
@@ -4715,7 +4800,6 @@
 					"-lMobileGestalt",
 				);
 				"OTHER_LDFLAGS[sdk=macosx*][arch=*]" = (
-					"-lAWACS",
 					"-weak_framework",
 					WebFilterDNS,
 					"-weak_framework",
@@ -4807,6 +4891,7 @@
 					"$(inherited)",
 					"__DARWIN_NON_CANCELABLE=1",
 				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders/";
 				INSTALL_PATH = /usr/lib/system;
 				INTERPOSITION_SIM_SUFFIX = "";
@@ -4827,7 +4912,11 @@
 					"-llaunch",
 					"-lsystem_asl",
 				);
+				OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
 				PRODUCT_NAME = libsystem_dnssd_debug;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
+				SUPPORTS_TEXT_BASED_API = YES;
+				TAPI_VERIFY_MODE = Pedantic;
 			};
 			name = Release;
 		};
@@ -4841,6 +4930,7 @@
 					"$(inherited)",
 					"__DARWIN_NON_CANCELABLE=1",
 				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				GENERATE_PROFILING_CODE = YES;
 				HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders/";
 				INSTALL_PATH = /usr/lib/system;
@@ -4862,7 +4952,11 @@
 					"-llaunch",
 					"-lsystem_asl",
 				);
+				OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
 				PRODUCT_NAME = libsystem_dnssd_profile;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
+				SUPPORTS_TEXT_BASED_API = YES;
+				TAPI_VERIFY_MODE = Pedantic;
 			};
 			name = Release;
 		};
@@ -4889,12 +4983,14 @@
 					"$(inherited)",
 					"__DARWIN_NON_CANCELABLE=1",
 				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders/";
 				INSTALLHDRS_COPY_PHASE = YES;
 				INSTALLHDRS_SCRIPT_PHASE = YES;
 				INSTALL_PATH = /usr/lib/system;
 				INTERPOSITION_SIM_SUFFIX = "";
 				"INTERPOSITION_SIM_SUFFIX[sdk=iphonesimulator*]" = _sim;
+				IS_ZIPPERED = YES;
 				LINK_WITH_STANDARD_LIBRARIES = NO;
 				OTHER_LDFLAGS = (
 					"-Wl,-umbrella,System",
@@ -4911,7 +5007,11 @@
 					"-llaunch",
 					"-lsystem_asl",
 				);
+				OTHER_TAPI_FLAGS = "-umbrella System -extra-public-header $(SRCROOT)/DNSServiceDiscovery.h";
 				PRODUCT_NAME = libsystem_dnssd;
+				PUBLIC_HEADERS_FOLDER_PATH = /usr/include;
+				SUPPORTS_TEXT_BASED_API = YES;
+				TAPI_VERIFY_MODE = Pedantic;
 			};
 			name = Release;
 		};
diff --git a/mDNSResponder/mDNSMacOSX/uDNSPathEvalulation.c b/mDNSResponder/mDNSMacOSX/uDNSPathEvalulation.c
index 96d4a0d..e474473 100644
--- a/mDNSResponder/mDNSMacOSX/uDNSPathEvalulation.c
+++ b/mDNSResponder/mDNSMacOSX/uDNSPathEvalulation.c
@@ -17,7 +17,13 @@
 
 #include "mDNSMacOSX.h"
 #include <libproc.h>
-#include <network/private.h>
+
+#if __has_include(<nw/private.h>)
+    #include <nw/private.h>
+#else
+    #include <network/private.h>
+#endif
+
 #include "dns_sd_internal.h"
 
 //Gets the DNSPolicy from NW PATH EVALUATOR
diff --git a/mDNSResponder/mDNSPosix/ProxyResponder.c b/mDNSResponder/mDNSPosix/ProxyResponder.c
index bd0fe58..94fde85 100644
--- a/mDNSResponder/mDNSPosix/ProxyResponder.c
+++ b/mDNSResponder/mDNSPosix/ProxyResponder.c
@@ -155,7 +155,7 @@ mDNSlocal void RegisterService(mDNS *m, ServiceRecordSet *recordset,
     mDNS_RegisterService(m, recordset,
                          &n, &t, &d, // Name, type, domain
                          host, mDNSOpaque16fromIntVal(PortAsNumber),
-                         txtbuffer, bptr-txtbuffer, // TXT data, length
+                         mDNSNULL, txtbuffer, bptr-txtbuffer, // TXT data, length
                          mDNSNULL, 0, // Subtypes
                          mDNSInterface_Any, // Interface ID
                          ServiceCallback, mDNSNULL, 0); // Callback, context, flags
diff --git a/mDNSResponder/mDNSPosix/Responder.c b/mDNSResponder/mDNSPosix/Responder.c
index 3850ea2..d4e2010 100755
--- a/mDNSResponder/mDNSPosix/Responder.c
+++ b/mDNSResponder/mDNSPosix/Responder.c
@@ -439,7 +439,7 @@ static mStatus RegisterOneService(const char *  richTextName,
         status = mDNS_RegisterService(&mDNSStorage, &thisServ->coreServ,
                                       &name, &type, &domain, // Name, type, domain
                                       NULL, mDNSOpaque16fromIntVal(portNumber),
-                                      text, textLen, // TXT data, length
+                                      NULL, text, textLen, // TXT data, length
                                       NULL, 0,      // Subtypes
                                       mDNSInterface_Any, // Interface ID
                                       RegistrationCallback, thisServ, 0); // Callback, context, flags
diff --git a/mDNSResponder/mDNSPosix/mDNSUNP.c b/mDNSResponder/mDNSPosix/mDNSUNP.c
index 17a37c6..e00ddd6 100755
--- a/mDNSResponder/mDNSPosix/mDNSUNP.c
+++ b/mDNSResponder/mDNSPosix/mDNSUNP.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -80,40 +80,56 @@ void plen_to_mask(int plen, char *addr) {
 }
 
 /* Gets IPv6 interface information from the /proc filesystem in linux*/
-struct ifi_info *get_ifi_info_linuxv6(int family, int doaliases)
+struct ifi_info *get_ifi_info_linuxv6(int doaliases)
 {
     struct ifi_info *ifi, *ifihead, **ifipnext, *ifipold, **ifiptr;
     FILE *fp = NULL;
-    char addr[8][5];
-    int flags, myflags, index, plen, scope;
-    char ifname[9], lastname[IFNAMSIZ];
-    char addr6[32+7+1]; /* don't forget the seven ':' */
+    int i, nitems, flags, index, plen, scope;
     struct addrinfo hints, *res0;
     int err;
     int sockfd = -1;
     struct ifreq ifr;
+    char ifnameFmt[16], addrStr[32 + 7 + 1], ifname[IFNAMSIZ], lastname[IFNAMSIZ];
 
     res0=NULL;
     ifihead = NULL;
     ifipnext = &ifihead;
-    lastname[0] = 0;
 
     if ((fp = fopen(PROC_IFINET6_PATH, "r")) != NULL) {
         sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
         if (sockfd < 0) {
             goto gotError;
         }
-        while (fscanf(fp,
-                      "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %8s\n",
-                      addr[0],addr[1],addr[2],addr[3],
-                      addr[4],addr[5],addr[6],addr[7],
-                      &index, &plen, &scope, &flags, ifname) != EOF) {
-
-            myflags = 0;
-            if (strncmp(lastname, ifname, IFNAMSIZ) == 0) {
+
+        // Parse /proc/net/if_inet6 according to <https://www.tldp.org/HOWTO/Linux+IPv6-HOWTO/ch11s04.html>.
+
+        // Create a string specifier with a width of IFNAMSIZ - 1 ("%<IFNAMSIZ - 1>s") to scan the interface name. The
+        // reason why we don't just use the string-ified macro expansion of IFNAMSIZ for the width is because the width
+        // needs to be a decimal string and there's no guarantee that IFNAMSIZ will be defined as a decimal integer. For
+        // example, it could be defined in hexadecimal or as an arithmetic expression.
+
+        snprintf(ifnameFmt, sizeof(ifnameFmt), "%%%ds", IFNAMSIZ - 1);
+
+        // Write the seven IPv6 address string colons and NUL terminator, i.e., "xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx".
+        // The remaining 32 IPv6 address characters come from /proc/net/if_inet6.
+
+        for (i = 4; i < 39; i += 5) addrStr[i] = ':';
+        addrStr[39] = '\0';
+
+        lastname[0] = '\0';
+        for (;;) {
+            nitems = fscanf(fp, " %4c%4c%4c%4c%4c%4c%4c%4c %x %x %x %x",
+                &addrStr[0],  &addrStr[5],  &addrStr[10], &addrStr[15],
+                &addrStr[20], &addrStr[25], &addrStr[30], &addrStr[35],
+                &index, &plen, &scope, &flags);
+            if (nitems != 12) break;
+
+            nitems = fscanf(fp, ifnameFmt, ifname);
+            if (nitems != 1) break;
+
+            if (strcmp(lastname, ifname) == 0) {
                 if (doaliases == 0)
                     continue;   /* already processed this interface */
-                myflags = IFI_ALIAS;
             }
             memcpy(lastname, ifname, IFNAMSIZ);
             ifi = (struct ifi_info*)calloc(1, sizeof(struct ifi_info));
@@ -126,15 +142,11 @@ struct ifi_info *get_ifi_info_linuxv6(int family, int doaliases)
             *ifipnext = ifi;            /* prev points to this new one */
             ifipnext = &ifi->ifi_next;  /* pointer to next one goes here */
 
-            sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s",
-                    addr[0],addr[1],addr[2],addr[3],
-                    addr[4],addr[5],addr[6],addr[7]);
-
             /* Add address of the interface */
             memset(&hints, 0, sizeof(hints));
             hints.ai_family = AF_INET6;
             hints.ai_flags = AI_NUMERICHOST;
-            err = getaddrinfo(addr6, NULL, &hints, &res0);
+            err = getaddrinfo(addrStr, NULL, &hints, &res0);
             if (err) {
                 goto gotError;
             }
@@ -152,9 +164,9 @@ struct ifi_info *get_ifi_info_linuxv6(int family, int doaliases)
                 goto gotError;
             }
 
-            ((struct sockaddr_in6 *)ifi->ifi_netmask)->sin6_family=family;
+            ((struct sockaddr_in6 *)ifi->ifi_netmask)->sin6_family=AF_INET6;
             ((struct sockaddr_in6 *)ifi->ifi_netmask)->sin6_scope_id=scope;
-            inet_pton(family, ipv6addr, &((struct sockaddr_in6 *)ifi->ifi_netmask)->sin6_addr);
+            inet_pton(AF_INET6, ipv6addr, &((struct sockaddr_in6 *)ifi->ifi_netmask)->sin6_addr);
 
             /* Add interface name */
             memcpy(ifi->ifi_name, ifname, IFI_NAME);
@@ -228,7 +240,7 @@ struct ifi_info *get_ifi_info(int family, int doaliases)
 #endif
 
 #if defined(AF_INET6) && HAVE_IPV6 && HAVE_LINUX
-    if (family == AF_INET6) return get_ifi_info_linuxv6(family, doaliases);
+    if (family == AF_INET6) return get_ifi_info_linuxv6(doaliases);
 #endif
 
     sockfd = -1;
diff --git a/mDNSResponder/mDNSPosix/mDNSUNP.h b/mDNSResponder/mDNSPosix/mDNSUNP.h
index cc81b7d..2b36ceb 100755
--- a/mDNSResponder/mDNSPosix/mDNSUNP.h
+++ b/mDNSResponder/mDNSPosix/mDNSUNP.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2002-2018 Apple 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.
@@ -62,8 +62,8 @@ typedef unsigned int socklen_t;
 #define GET_SA_LEN(X) (((struct sockaddr*)&(X))->sa_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr))
 #endif
 
-#define IFI_NAME    16          /* same as IFNAMSIZ in <net/if.h> */
-#define IFI_HADDR    8          /* allow for 64-bit EUI-64 in future */
+#define IFI_NAME    IFNAMSIZ    /* same as IFNAMSIZ in <net/if.h> */
+#define IFI_HADDR   8           /* allow for 64-bit EUI-64 in future */
 
 // Renamed from my_in_pktinfo because in_pktinfo is used by Linux.
 
@@ -98,7 +98,7 @@ struct ifi_info {
 
 #if defined(AF_INET6) && HAVE_IPV6 && HAVE_LINUX
 #define PROC_IFINET6_PATH "/proc/net/if_inet6"
-extern struct ifi_info  *get_ifi_info_linuxv6(int family, int doaliases);
+extern struct ifi_info  *get_ifi_info_linuxv6(int doaliases);
 #endif
 
 #if defined(AF_INET6) && HAVE_IPV6
diff --git a/mDNSResponder/mDNSShared/dns_sd.h b/mDNSResponder/mDNSShared/dns_sd.h
index 0e5c35e..a1f5a57 100644
--- a/mDNSResponder/mDNSShared/dns_sd.h
+++ b/mDNSResponder/mDNSShared/dns_sd.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -66,7 +66,7 @@
  */
 
 #ifndef _DNS_SD_H
-#define _DNS_SD_H 8787002
+#define _DNS_SD_H 8800035
 
 #ifdef  __cplusplus
 extern "C" {
@@ -75,9 +75,11 @@ extern "C" {
 /* Set to 1 if libdispatch is supported
  * Note: May also be set by project and/or Makefile
  */
-#ifndef _DNS_SD_LIBDISPATCH
+#if defined(__APPLE__)
+#define _DNS_SD_LIBDISPATCH 1
+#else
 #define _DNS_SD_LIBDISPATCH 0
-#endif /* ndef _DNS_SD_LIBDISPATCH */
+#endif
 
 /* standard calling convention under Win32 is __stdcall */
 /* Note: When compiling Intel EFI (Extensible Firmware Interface) under MS Visual Studio, the */
@@ -88,6 +90,12 @@ extern "C" {
 #define DNSSD_API
 #endif
 
+#if (defined(__GNUC__) && (__GNUC__ >= 4))
+#define DNSSD_EXPORT __attribute__((visibility("default")))
+#else
+#define DNSSD_EXPORT
+#endif
+
 #if defined(_WIN32)
 #include <winsock2.h>
 typedef SOCKET dnssd_sock_t;
@@ -526,11 +534,25 @@ enum
      * This flag is private and should not be used.
      */
 
-     kDNSServiceFlagsPrivateFour       = 0x40000000
+     kDNSServiceFlagsPrivateFour          = 0x40000000,
     /*
      * This flag is private and should not be used.
      */
 
+    kDNSServiceFlagsAllowExpiredAnswers   = 0x80000000,
+    /*
+     * When kDNSServiceFlagsAllowExpiredAnswers is passed to DNSServiceQueryRecord or DNSServiceGetAddrInfo,
+     * if there are matching expired records still in the cache, then they are immediately returned to the
+     * client, and in parallel a network query for that name is issued. All returned records from the query will
+     * remain in the cache after expiration.
+     */
+    
+    kDNSServiceFlagsExpiredAnswer         = 0x80000000
+    /*
+     * When kDNSServiceFlagsAllowExpiredAnswers is passed to DNSServiceQueryRecord or DNSServiceGetAddrInfo,
+     * an expired answer will have this flag set.
+     */
+
 };
 
 #define kDNSServiceOutputFlags (kDNSServiceFlagsValidate | kDNSServiceFlagsValidateOptional | kDNSServiceFlagsMoreComing | kDNSServiceFlagsAdd | kDNSServiceFlagsDefault)
@@ -871,6 +893,7 @@ typedef int32_t DNSServiceErrorType;
  *                  if the daemon (or "system service" on Windows) is not running.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceGetProperty
 (
     const char *property,  /* Requested property (i.e. kDNSServiceProperty_DaemonVersion) */
@@ -930,6 +953,7 @@ DNSServiceErrorType DNSSD_API DNSServiceGetProperty
  *                  error.
  */
 
+DNSSD_EXPORT
 dnssd_sock_t DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef);
 
 
@@ -951,6 +975,7 @@ dnssd_sock_t DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef);
  *                  an error code indicating the specific failure that occurred.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef);
 
 
@@ -978,6 +1003,7 @@ DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef);
  *
  */
 
+DNSSD_EXPORT
 void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef);
 
 
@@ -1062,6 +1088,7 @@ typedef void (DNSSD_API *DNSServiceDomainEnumReply)
  *                  is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains
 (
     DNSServiceRef                       *sdRef,
@@ -1252,6 +1279,7 @@ typedef void (DNSSD_API *DNSServiceRegisterReply)
  *                  is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceRegister
 (
     DNSServiceRef                       *sdRef,
@@ -1307,6 +1335,7 @@ DNSServiceErrorType DNSSD_API DNSServiceRegister
  *                  error code indicating the error that occurred (the RecordRef is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceAddRecord
 (
     DNSServiceRef sdRef,
@@ -1348,6 +1377,7 @@ DNSServiceErrorType DNSSD_API DNSServiceAddRecord
  *                  error code indicating the error that occurred.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord
 (
     DNSServiceRef sdRef,
@@ -1380,6 +1410,7 @@ DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord
  *                  error code indicating the error that occurred.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord
 (
     DNSServiceRef sdRef,
@@ -1485,6 +1516,7 @@ typedef void (DNSSD_API *DNSServiceBrowseReply)
  *                  is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceBrowse
 (
     DNSServiceRef                       *sdRef,
@@ -1613,6 +1645,7 @@ typedef void (DNSSD_API *DNSServiceResolveReply)
  *                  is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceResolve
 (
     DNSServiceRef                       *sdRef,
@@ -1730,6 +1763,7 @@ typedef void (DNSSD_API *DNSServiceQueryRecordReply)
  *                  is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceQueryRecord
 (
     DNSServiceRef                       *sdRef,
@@ -1834,6 +1868,7 @@ typedef void (DNSSD_API *DNSServiceGetAddrInfoReply)
  *                  the error that occurred.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo
 (
     DNSServiceRef                    *sdRef,
@@ -1870,6 +1905,7 @@ DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo
  *                  case the DNSServiceRef is not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef);
 
 /* DNSServiceRegisterRecord
@@ -1952,6 +1988,7 @@ typedef void (DNSSD_API *DNSServiceRegisterRecordReply)
  *                  not initialized).
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord
 (
     DNSServiceRef sdRef,
@@ -2001,6 +2038,7 @@ DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord
  *
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord
 (
     DNSServiceFlags flags,
@@ -2184,6 +2222,7 @@ typedef void (DNSSD_API *DNSServiceNATPortMappingReply)
  *                  display) then pass zero for protocol, internalPort, externalPort and ttl.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate
 (
     DNSServiceRef                    *sdRef,
@@ -2230,6 +2269,7 @@ DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate
  *
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceConstructFullName
 (
     char                            * const fullName,
@@ -2310,6 +2350,7 @@ typedef union _TXTRecordRef_t { char PrivateData[16]; char *ForceNaturalAlignmen
  *                  the TXTRecordRef.
  */
 
+DNSSD_EXPORT
 void DNSSD_API TXTRecordCreate
 (
     TXTRecordRef     *txtRecord,
@@ -2328,6 +2369,7 @@ void DNSSD_API TXTRecordCreate
  *
  */
 
+DNSSD_EXPORT
 void DNSSD_API TXTRecordDeallocate
 (
     TXTRecordRef     *txtRecord
@@ -2371,6 +2413,7 @@ void DNSSD_API TXTRecordDeallocate
  *                  exceed the available storage.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API TXTRecordSetValue
 (
     TXTRecordRef     *txtRecord,
@@ -2394,6 +2437,7 @@ DNSServiceErrorType DNSSD_API TXTRecordSetValue
  *                  exist in the TXTRecordRef.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API TXTRecordRemoveValue
 (
     TXTRecordRef     *txtRecord,
@@ -2413,6 +2457,7 @@ DNSServiceErrorType DNSSD_API TXTRecordRemoveValue
  *                  Returns 0 if the TXTRecordRef is empty.
  */
 
+DNSSD_EXPORT
 uint16_t DNSSD_API TXTRecordGetLength
 (
     const TXTRecordRef *txtRecord
@@ -2430,6 +2475,7 @@ uint16_t DNSSD_API TXTRecordGetLength
  *                  to DNSServiceUpdateRecord().
  */
 
+DNSSD_EXPORT
 const void * DNSSD_API TXTRecordGetBytesPtr
 (
     const TXTRecordRef *txtRecord
@@ -2484,6 +2530,7 @@ const void * DNSSD_API TXTRecordGetBytesPtr
  *                  Otherwise, it returns 0.
  */
 
+DNSSD_EXPORT
 int DNSSD_API TXTRecordContainsKey
 (
     uint16_t txtLen,
@@ -2513,6 +2560,7 @@ int DNSSD_API TXTRecordContainsKey
  *                  For non-empty value, valueLen will be length of value data.
  */
 
+DNSSD_EXPORT
 const void * DNSSD_API TXTRecordGetValuePtr
 (
     uint16_t txtLen,
@@ -2535,6 +2583,7 @@ const void * DNSSD_API TXTRecordGetValuePtr
  *
  */
 
+DNSSD_EXPORT
 uint16_t DNSSD_API TXTRecordGetCount
 (
     uint16_t txtLen,
@@ -2580,6 +2629,7 @@ uint16_t DNSSD_API TXTRecordGetCount
  *                  TXTRecordGetCount()-1.
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex
 (
     uint16_t txtLen,
@@ -2632,6 +2682,7 @@ DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex
  *                  queue param is invalid
  */
 
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceSetDispatchQueue
 (
     DNSServiceRef service,
@@ -2646,6 +2697,7 @@ typedef void (DNSSD_API *DNSServiceSleepKeepaliveReply)
     DNSServiceErrorType errorCode,
     void                                *context
 );
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceSleepKeepalive
 (
     DNSServiceRef                       *sdRef,
diff --git a/mDNSResponder/mDNSShared/dns_sd_private.h b/mDNSResponder/mDNSShared/dns_sd_private.h
index 4d02368..2d42973 100644
--- a/mDNSResponder/mDNSShared/dns_sd_private.h
+++ b/mDNSResponder/mDNSShared/dns_sd_private.h
@@ -1,11 +1,12 @@
 /* -*- Mode: C; tab-width: 4 -*-
  * 
- * Copyright (c) 2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2015-2018 Apple Inc. All rights reserved.
  */
 
 #ifndef _DNS_SD_PRIVATE_H
 #define _DNS_SD_PRIVATE_H
 
+#include <dns_sd.h>
 
 // Private flags (kDNSServiceFlagsPrivateOne, kDNSServiceFlagsPrivateTwo, kDNSServiceFlagsPrivateThree, kDNSServiceFlagsPrivateFour) from dns_sd.h
 enum
@@ -58,6 +59,7 @@ enum
  *                  returned to indicate that the calling process does not have entitlements
  *                  to use this API.
  */
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid);
 #endif
 
@@ -77,12 +79,16 @@ DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *
  *                  if the daemon is not running. The value of the pid is undefined if the return
  *                  value has error.
  */
+DNSSD_EXPORT
 DNSServiceErrorType DNSSD_API DNSServiceGetPID
 (
     uint16_t srcport,
     int32_t *pid
 );
 
+DNSSD_EXPORT
+DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain);
+
 #define kDNSServiceCompPrivateDNS   "PrivateDNS"
 #define kDNSServiceCompMulticastDNS "MulticastDNS"
 
diff --git a/mDNSResponder/mDNSShared/dnssd_clientlib.c b/mDNSResponder/mDNSShared/dnssd_clientlib.c
index cfc1d42..2a1f5ed 100644
--- a/mDNSResponder/mDNSShared/dnssd_clientlib.c
+++ b/mDNSResponder/mDNSShared/dnssd_clientlib.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2004-2011 Apple Inc. All rights reserved.
+ * Copyright (c) 2004-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -31,10 +31,6 @@
 
 #include "dns_sd.h"
 
-#if MDNS_BUILDINGSHAREDLIBRARY || MDNS_BUILDINGSTUBLIBRARY
-#pragma export on
-#endif
-
 #if defined(_WIN32)
 // disable warning "conversion from <data> to uint16_t"
 #pragma warning(disable:4244)
@@ -361,6 +357,14 @@ DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex
 #define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) # s
 #define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s)
 
+// The "used" variable attribute prevents a non-exported variable from being stripped, even if its visibility is hidden,
+// e.g., when compiling with -fvisibility=hidden.
+#if defined(__GNUC__)
+#define DNSSD_USED __attribute__((used))
+#else
+#define DNSSD_USED
+#endif
+
 // NOT static -- otherwise the compiler may optimize it out
 // The "@(#) " pattern is a special prefix the "what" command looks for
-const char VersionString_SCCS_libdnssd[] = "@(#) libdns_sd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
+const char VersionString_SCCS_libdnssd[] DNSSD_USED = "@(#) libdns_sd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
diff --git a/mDNSResponder/mDNSShared/dnssd_clientstub.c b/mDNSResponder/mDNSShared/dnssd_clientstub.c
index 27e90ee..e8600cb 100644
--- a/mDNSResponder/mDNSShared/dnssd_clientstub.c
+++ b/mDNSResponder/mDNSShared/dnssd_clientstub.c
@@ -1584,7 +1584,6 @@ DNSServiceErrorType DNSSD_API DNSServiceBrowse
     return err;
 }
 
-DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain);
 DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain)
 {
     DNSServiceErrorType err;
diff --git a/mDNSResponder/mDNSShared/mDNSDebug.c b/mDNSResponder/mDNSShared/mDNSDebug.c
index 1243ae7..dfe77a1 100644
--- a/mDNSResponder/mDNSShared/mDNSDebug.c
+++ b/mDNSResponder/mDNSShared/mDNSDebug.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2018 Apple 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.
@@ -77,6 +77,7 @@ void LogMsg_(const char *format, ...)       LOG_HELPER_BODY(MDNS_LOG_MSG)
 void LogOperation_(const char *format, ...) LOG_HELPER_BODY(MDNS_LOG_OPERATION)
 void LogSPS_(const char *format, ...)       LOG_HELPER_BODY(MDNS_LOG_SPS)
 void LogInfo_(const char *format, ...)      LOG_HELPER_BODY(MDNS_LOG_INFO)
+void LogDebug_(const char *format, ...)     LOG_HELPER_BODY(MDNS_LOG_DEBUG)
 #endif
 
 #if MDNS_DEBUGMSGS
diff --git a/mDNSResponder/mDNSShared/uds_daemon.c b/mDNSResponder/mDNSShared/uds_daemon.c
index 946ad16..d1fbc7f 100644
--- a/mDNSResponder/mDNSShared/uds_daemon.c
+++ b/mDNSResponder/mDNSShared/uds_daemon.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2015 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2018 Apple 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.
@@ -41,6 +41,9 @@
 // not fully qualified) with any number of labels e.g., moon, moon.cs, moon.cs.be, etc.
 mDNSBool AlwaysAppendSearchDomains = mDNSfalse;
 
+//  Control enabling ioptimistic DNS
+mDNSBool EnableAllowExpired = mDNStrue;
+
 // Apple-specific functionality, not required for other platforms
 #if APPLE_OSX_mDNSResponder
 #include <sys/ucred.h>
@@ -325,8 +328,8 @@ mDNSlocal void abort_request(request_state *req)
     // Now, if this request_state is not subordinate to some other primary, close file descriptor and discard replies
     if (!req->primary)
     {
-        if (req->errsd != req->sd) LogOperation("%3d: Removing FD and closing errsd %d", req->sd, req->errsd);
-        else LogOperation("%3d: Removing FD", req->sd);
+        if (req->errsd != req->sd) LogDebug("%3d: Removing FD and closing errsd %d", req->sd, req->errsd);
+        else LogDebug("%3d: Removing FD", req->sd);
         udsSupportRemoveFDFromEventLoop(req->sd, req->platform_data);       // Note: This also closes file descriptor req->sd for us
         if (req->errsd != req->sd) { dnssd_close(req->errsd); req->errsd = req->sd; }
 
@@ -1077,7 +1080,7 @@ mDNSlocal void connection_termination(request_state *request)
 mDNSlocal void handle_cancel_request(request_state *request)
 {
     request_state **req = &all_requests;
-    LogOperation("%3d: Cancel %08X %08X", request->sd, request->hdr.client_context.u32[1], request->hdr.client_context.u32[0]);
+    LogDebug("%3d: Cancel %08X %08X", request->sd, request->hdr.client_context.u32[1], request->hdr.client_context.u32[0]);
     while (*req)
     {
         if ((*req)->primary == request &&
@@ -1689,7 +1692,7 @@ mDNSlocal mStatus register_service_instance(request_state *request, const domain
                                   &request->u.servicereg.name, &request->u.servicereg.type, domain,
                                   request->u.servicereg.host.c[0] ? &request->u.servicereg.host : NULL,
                                   request->u.servicereg.port,
-                                  request->u.servicereg.txtdata, request->u.servicereg.txtlen,
+                                  mDNSNULL, request->u.servicereg.txtdata, request->u.servicereg.txtlen,
                                   instance->subtypes, request->u.servicereg.num_subtypes,
                                   interfaceID, regservice_callback, instance, request->flags);
 
@@ -2166,7 +2169,7 @@ mDNSlocal mStatus add_domain_to_browser(request_state *info, const domainname *d
         {
             domainname tmp;
             ConstructServiceName(&tmp, NULL, &info->u.browser.regtype, &b->domain);
-            LogInfo("add_domain_to_browser: calling external_start_browsing_for_service()");
+            LogDebug("add_domain_to_browser: calling external_start_browsing_for_service()");
             external_start_browsing_for_service(info->u.browser.interface_id, &tmp, kDNSType_PTR, info->flags);
         }
     }
@@ -3162,7 +3165,7 @@ mDNSlocal mDNSBool RetryQuestionWithSearchDomains(DNSQuestion *question, request
     }
     else
     {
-        LogInfo("%3d: RetryQuestionWithSearchDomains: Not appending search domains - SuppressQuery %d, SearchListIndex %d, AppendSearchDomains %d", req->sd, AddRecord, question->SearchListIndex, question->AppendSearchDomains);
+        LogDebug("%3d: RetryQuestionWithSearchDomains: Not appending search domains - SuppressQuery %d, SearchListIndex %d, AppendSearchDomains %d", req->sd, AddRecord, question->SearchListIndex, question->AppendSearchDomains);
     }
     return mDNSfalse;
 }
@@ -3178,10 +3181,11 @@ mDNSlocal void queryrecord_result_reply(mDNS *const m, request_state *req, DNSQu
 
     ConvertDomainNameToCString(answer->name, name);
 
-    LogOperation("%3d: %s(%##s, %s) RESULT %s interface %d: %s", req->sd,
+    LogOperation("%3d: %s(%##s, %s) RESULT %s interface %d: (%s)%s", req->sd,
                  req->hdr.op == query_request ? "DNSServiceQueryRecord" : "DNSServiceGetAddrInfo",
                  question->qname.c, DNSTypeName(question->qtype), AddRecord ? "ADD" : "RMV",
-                 mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse), RRDisplayString(m, answer));
+                 mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse),
+                 MortalityDisplayString(answer->mortality), RRDisplayString(m, answer));
 
     len = sizeof(DNSServiceFlags);  // calculate reply data length
     len += sizeof(mDNSu32);     // interface index
@@ -3195,6 +3199,8 @@ mDNSlocal void queryrecord_result_reply(mDNS *const m, request_state *req, DNSQu
 
     if (AddRecord)
         flags |= kDNSServiceFlagsAdd;
+    if (answer->mortality == Mortality_Ghost)
+        flags |= kDNSServiceFlagsExpiredAnswer;
     if (question->ValidationStatus != 0)
     {
         error =   kDNSServiceErr_NoError;
@@ -3452,7 +3458,7 @@ mDNSlocal void queryrecord_result_callback(mDNS *const m, DNSQuestion *question,
     // the "core" needs to temporarily turn off SuppressQuery to answer this query.
     if (AddRecord == QC_suppressed)
     {
-        LogInfo("queryrecord_result_callback: Suppressed question %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
+        LogDebug("queryrecord_result_callback: Suppressed question %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
         queryrecord_result_reply(m, req, question, answer, AddRecord, kDNSServiceErr_NoSuchRecord);
         return;
     }
@@ -3517,7 +3523,7 @@ mDNSlocal void queryrecord_result_callback(mDNS *const m, DNSQuestion *question,
             // appended .local, we need to see if we need to send an additional query. This should
             // normally happen just once because after we append .local, we ignore all negative
             // responses for .local above.
-            LogInfo("queryrecord_result_callback: Retrying question %##s (%s) after appending search domains", question->qname.c, DNSTypeName(question->qtype));
+            LogDebug("queryrecord_result_callback: Retrying question %##s (%s) after appending search domains", question->qname.c, DNSTypeName(question->qtype));
             if (RetryQuestionWithSearchDomains(question, req, AddRecord))
             {
                 // Note: We need to call SendAdditionalQuery every time after appending a search domain as .local could
@@ -3646,34 +3652,35 @@ mDNSlocal mStatus handle_queryrecord_request(request_state *request)
     request->interfaceIndex = interfaceIndex;
     mDNSPlatformMemZero(&request->u.queryrecord, sizeof(request->u.queryrecord));
 
-    q->InterfaceID      = InterfaceID;
-    q->flags            = flags;
-    q->Target           = zeroAddr;
+    q->InterfaceID         = InterfaceID;
+    q->flags               = flags;
+    q->Target              = zeroAddr;
     if (!MakeDomainNameFromDNSNameString(&q->qname, name)) return(mStatus_BadParamErr);
 #if 0
     if (!AuthorizedDomain(request, &q->qname, AutoBrowseDomains)) return (mStatus_NoError);
 #endif
-    q->qtype            = rrtype;
-    q->qclass           = rrclass;
-    q->LongLived        = (flags & kDNSServiceFlagsLongLivedQuery     ) != 0;
-    q->ExpectUnique     = mDNSfalse;
-    q->ForceMCast       = (flags & kDNSServiceFlagsForceMulticast     ) != 0;
-    q->ReturnIntermed   = (flags & kDNSServiceFlagsReturnIntermediates) != 0;
-    q->SuppressUnusable = (flags & kDNSServiceFlagsSuppressUnusable   ) != 0;
-    q->TimeoutQuestion  = (flags & kDNSServiceFlagsTimeout            ) != 0;
-    q->WakeOnResolve    = 0;
+    q->qtype               = rrtype;
+    q->qclass              = rrclass;
+    q->LongLived           = (flags & kDNSServiceFlagsLongLivedQuery     ) != 0;
+    q->ExpectUnique        = mDNSfalse;
+    q->ForceMCast          = (flags & kDNSServiceFlagsForceMulticast     ) != 0;
+    q->ReturnIntermed      = (flags & kDNSServiceFlagsReturnIntermediates) != 0;
+    q->SuppressUnusable    = (flags & kDNSServiceFlagsSuppressUnusable   ) != 0;
+    q->TimeoutQuestion     = (flags & kDNSServiceFlagsTimeout            ) != 0;
+    q->allowExpired        = (EnableAllowExpired && (flags & kDNSServiceFlagsAllowExpiredAnswers) != 0) ? AllowExpired_AllowExpiredAnswers : AllowExpired_None;
+    q->WakeOnResolve       = 0;
     q->UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
     if ((flags & kDNSServiceFlagsValidate) != 0)
         q->ValidationRequired = DNSSEC_VALIDATION_SECURE;
     else if ((flags & kDNSServiceFlagsValidateOptional) != 0)
         q->ValidationRequired = DNSSEC_VALIDATION_SECURE_OPTIONAL;
     q->ValidatingResponse = 0;
-    q->ProxyQuestion    = 0;
+    q->ProxyQuestion      = 0;
     q->AnonInfo = mDNSNULL;
-    q->QuestionCallback = queryrecord_result_callback;
-    q->QuestionContext  = request;
-    q->SearchListIndex  = 0;
-    q->StopTime         = 0;
+    q->QuestionCallback   = queryrecord_result_callback;
+    q->QuestionContext    = request;
+    q->SearchListIndex    = 0;
+    q->StopTime           = 0;
 
     q->DNSSECAuthInfo = mDNSNULL;
     q->DAIFreeCallback = mDNSNULL;
@@ -3737,7 +3744,7 @@ mDNSlocal mStatus handle_queryrecord_request(request_state *request)
         LogMcastQ(q, request, q_start);
         if (callExternalHelpers(q->InterfaceID, &q->qname, q->flags))
         {
-            LogInfo("handle_queryrecord_request: calling external_start_browsing_for_service()");
+            LogDebug("handle_queryrecord_request: calling external_start_browsing_for_service()");
             external_start_browsing_for_service(q->InterfaceID, &q->qname, q->qtype, q->flags);
         }
     }
@@ -4258,7 +4265,7 @@ mDNSlocal void addrinfo_termination_callback(request_state *request)
 
         if (callExternalHelpers(request->u.addrinfo.interface_id, &request->u.addrinfo.q4.qname, request->flags))
         {
-            LogInfo("addrinfo_termination_callback: calling external_stop_browsing_for_service() for kDNSServiceType_A record");
+            LogInfo("addrinfo_termination_callback: calling external_stop_browsing_for_service() for A record");
             external_stop_browsing_for_service(request->u.addrinfo.interface_id, &request->u.addrinfo.q4.qname, kDNSServiceType_A, request->flags);
         }
     }
@@ -4293,7 +4300,7 @@ mDNSlocal void addrinfo_termination_callback(request_state *request)
 
         if (callExternalHelpers(request->u.addrinfo.interface_id, &request->u.addrinfo.q6.qname, request->flags))
         {
-            LogInfo("addrinfo_termination_callback: calling external_stop_browsing_for_service() for kDNSServiceType_AAAA record");
+            LogInfo("addrinfo_termination_callback: calling external_stop_browsing_for_service() for AAAA record");
             external_stop_browsing_for_service(request->u.addrinfo.interface_id, &request->u.addrinfo.q6.qname, kDNSServiceType_AAAA, request->flags);
         }
     }
@@ -4423,19 +4430,20 @@ mDNSlocal mStatus handle_addrinfo_request(request_state *request)
         request->u.addrinfo.protocol = (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6);
     }
 
-    request->u.addrinfo.q4.InterfaceID      = request->u.addrinfo.q6.InterfaceID      = request->u.addrinfo.interface_id;
-    request->u.addrinfo.q4.ServiceID        = request->u.addrinfo.q6.ServiceID        = serviceIndex;
-    request->u.addrinfo.q4.flags            = request->u.addrinfo.q6.flags            = flags;
-    request->u.addrinfo.q4.Target           = request->u.addrinfo.q6.Target           = zeroAddr;
-    request->u.addrinfo.q4.qname            = request->u.addrinfo.q6.qname            = d;
-    request->u.addrinfo.q4.qclass           = request->u.addrinfo.q6.qclass           = kDNSServiceClass_IN;
-    request->u.addrinfo.q4.LongLived        = request->u.addrinfo.q6.LongLived        = (flags & kDNSServiceFlagsLongLivedQuery     ) != 0;
-    request->u.addrinfo.q4.ExpectUnique     = request->u.addrinfo.q6.ExpectUnique     = mDNSfalse;
-    request->u.addrinfo.q4.ForceMCast       = request->u.addrinfo.q6.ForceMCast       = (flags & kDNSServiceFlagsForceMulticast     ) != 0;
-    request->u.addrinfo.q4.ReturnIntermed   = request->u.addrinfo.q6.ReturnIntermed   = (flags & kDNSServiceFlagsReturnIntermediates) != 0;
-    request->u.addrinfo.q4.SuppressUnusable = request->u.addrinfo.q6.SuppressUnusable = (flags & kDNSServiceFlagsSuppressUnusable   ) != 0;
-    request->u.addrinfo.q4.TimeoutQuestion  = request->u.addrinfo.q6.TimeoutQuestion  = (flags & kDNSServiceFlagsTimeout            ) != 0;
-    request->u.addrinfo.q4.WakeOnResolve    = request->u.addrinfo.q6.WakeOnResolve    = 0;
+    request->u.addrinfo.q4.InterfaceID         = request->u.addrinfo.q6.InterfaceID         = request->u.addrinfo.interface_id;
+    request->u.addrinfo.q4.ServiceID           = request->u.addrinfo.q6.ServiceID           = serviceIndex;
+    request->u.addrinfo.q4.flags               = request->u.addrinfo.q6.flags               = flags;
+    request->u.addrinfo.q4.Target              = request->u.addrinfo.q6.Target              = zeroAddr;
+    request->u.addrinfo.q4.qname               = request->u.addrinfo.q6.qname               = d;
+    request->u.addrinfo.q4.qclass              = request->u.addrinfo.q6.qclass              = kDNSServiceClass_IN;
+    request->u.addrinfo.q4.LongLived           = request->u.addrinfo.q6.LongLived           = (flags & kDNSServiceFlagsLongLivedQuery     ) != 0;
+    request->u.addrinfo.q4.ExpectUnique        = request->u.addrinfo.q6.ExpectUnique        = mDNSfalse;
+    request->u.addrinfo.q4.ForceMCast          = request->u.addrinfo.q6.ForceMCast          = (flags & kDNSServiceFlagsForceMulticast     ) != 0;
+    request->u.addrinfo.q4.ReturnIntermed      = request->u.addrinfo.q6.ReturnIntermed      = (flags & kDNSServiceFlagsReturnIntermediates) != 0;
+    request->u.addrinfo.q4.SuppressUnusable    = request->u.addrinfo.q6.SuppressUnusable    = (flags & kDNSServiceFlagsSuppressUnusable   ) != 0;
+    request->u.addrinfo.q4.TimeoutQuestion     = request->u.addrinfo.q6.TimeoutQuestion     = (flags & kDNSServiceFlagsTimeout            ) != 0;
+    request->u.addrinfo.q4.allowExpired        = request->u.addrinfo.q6.allowExpired        = (EnableAllowExpired && (flags & kDNSServiceFlagsAllowExpiredAnswers) != 0) ? AllowExpired_AllowExpiredAnswers : AllowExpired_None;
+    request->u.addrinfo.q4.WakeOnResolve       = request->u.addrinfo.q6.WakeOnResolve    = 0;
     request->u.addrinfo.q4.UseBackgroundTrafficClass = request->u.addrinfo.q6.UseBackgroundTrafficClass  = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
     if ((flags & kDNSServiceFlagsValidate) != 0)
         request->u.addrinfo.q4.ValidationRequired = request->u.addrinfo.q6.ValidationRequired = DNSSEC_VALIDATION_SECURE;
@@ -4497,7 +4505,7 @@ mDNSlocal mStatus handle_addrinfo_request(request_state *request)
             LogMcastQ(&request->u.addrinfo.q6, request, q_start);
             if (callExternalHelpers(InterfaceID, &d, flags))
             {
-                LogInfo("handle_addrinfo_request: calling external_start_browsing_for_service() for kDNSServiceType_AAAA record");
+                LogDebug("handle_addrinfo_request: calling external_start_browsing_for_service() for AAAA record");
                 external_start_browsing_for_service(InterfaceID, &d, kDNSServiceType_AAAA, flags);
             }
         }
@@ -4539,7 +4547,7 @@ mDNSlocal mStatus handle_addrinfo_request(request_state *request)
 
                 if (callExternalHelpers(InterfaceID, &d, flags))
                 {
-                    LogInfo("addrinfo_termination_callback: calling external_stop_browsing_for_service() for kDNSServiceType_AAAA record");
+                    LogInfo("addrinfo_termination_callback: calling external_stop_browsing_for_service() for AAAA record");
                     external_stop_browsing_for_service(InterfaceID, &d, kDNSServiceType_AAAA, flags);
                 }
             }
@@ -4553,7 +4561,7 @@ mDNSlocal mStatus handle_addrinfo_request(request_state *request)
             LogMcastQ(&request->u.addrinfo.q4, request, q_start);
             if (callExternalHelpers(InterfaceID, &d, flags))
             {
-                LogInfo("handle_addrinfo_request: calling external_start_browsing_for_service() for kDNSServiceType_A record");
+                LogDebug("handle_addrinfo_request: calling external_start_browsing_for_service() for A record");
                 external_start_browsing_for_service(InterfaceID, &d, kDNSServiceType_A, flags);
             }
         }
@@ -4753,7 +4761,7 @@ mDNSlocal void read_msg(request_state *req)
 #if !defined(USE_TCP_LOOPBACK)
 got_errfd:
 #endif
-            LogOperation("%3d: Result code socket %d created %08X %08X", req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0]);
+            LogDebug("%3d: Result code socket %d created %08X %08X", req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0]);
 #if defined(_WIN32)
             if (ioctlsocket(req->errsd, FIONBIO, &opt) != 0)
 #else
@@ -4952,8 +4960,8 @@ mDNSlocal void request_callback(int fd, short filter, void *info)
             send_all(req->errsd, (const char *)&err_netorder, sizeof(err_netorder));
             if (req->errsd != req->sd)
             {
-                LogOperation("%3d: Result code socket %d closed  %08X %08X (%d)",
-                             req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0], err);
+                LogDebug("%3d: Result code socket %d closed  %08X %08X (%d)",
+                         req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0], err);
                 dnssd_close(req->errsd);
                 req->errsd = req->sd;
                 // Also need to reset the parent's errsd, if this is a subordinate operation
@@ -5024,7 +5032,7 @@ mDNSlocal void connect_callback(int fd, short filter, void *info)
 
         debugf("LOCAL_PEERCRED %d %u %u %d", xucredlen, x.cr_version, x.cr_uid, x.cr_ngroups);
 #endif // APPLE_OSX_mDNSResponder
-        LogOperation("%3d: connect_callback: Adding FD for uid %u", request->sd, request->uid);
+        LogDebug("%3d: connect_callback: Adding FD for uid %u", request->sd, request->uid);
         udsSupportAddFDToEventLoop(sd, request_callback, request, &request->platform_data);
     }
 }
@@ -6254,10 +6262,10 @@ struct CompileTimeAssertionChecks_uds_daemon
     // Check our structures are reasonable sizes. Including overly-large buffers, or embedding
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
-    char sizecheck_request_state          [(sizeof(request_state)           <= 2954) ? 1 : -1];
+    char sizecheck_request_state          [(sizeof(request_state)           <= 3696) ? 1 : -1];
     char sizecheck_registered_record_entry[(sizeof(registered_record_entry) <=   60) ? 1 : -1];
     char sizecheck_service_instance       [(sizeof(service_instance)        <= 6552) ? 1 : -1];
-    char sizecheck_browser_t              [(sizeof(browser_t)               <= 1202) ? 1 : -1];
+    char sizecheck_browser_t              [(sizeof(browser_t)               <= 1432) ? 1 : -1];
     char sizecheck_reply_hdr              [(sizeof(reply_hdr)               <=   12) ? 1 : -1];
     char sizecheck_reply_state            [(sizeof(reply_state)             <=   64) ? 1 : -1];
 };
diff --git a/mDNSResponder/unittests/mDNSCoreReceiveTest.c b/mDNSResponder/unittests/mDNSCoreReceiveTest.c
index ece2140..856eb4e 100644
--- a/mDNSResponder/unittests/mDNSCoreReceiveTest.c
+++ b/mDNSResponder/unittests/mDNSCoreReceiveTest.c
@@ -4,7 +4,6 @@
 int InitmDNSCoreReceiveTest(void);
 int ValidQueryReqTest(void);
 int NullDstQueryReqTest(void);
-int ReceiveArpLogMsgTest(void);
 void InitmDNSStorage(mDNS *const m);
 
 // This DNS message was gleaned from a uDNS query request packet that was captured with Wireshark.
@@ -46,7 +45,6 @@ UNITTEST_HEADER(mDNSCoreReceiveTest)
     UNITTEST_TEST(InitmDNSCoreReceiveTest)
     UNITTEST_TEST(ValidQueryReqTest)
     UNITTEST_TEST(NullDstQueryReqTest)
-    UNITTEST_TEST(ReceiveArpLogMsgTest)
 UNITTEST_FOOTER
 
 UNITTEST_HEADER(InitmDNSCoreReceiveTest)
@@ -56,16 +54,6 @@ UNITTEST_HEADER(InitmDNSCoreReceiveTest)
 	mDNS_PacketLoggingEnabled = 0;
 UNITTEST_FOOTER
 
-UNITTEST_HEADER(ReceiveArpLogMsgTest)
-    // Init unit test environment and verify no error occurred.
-    mStatus result = init_mdns_environment(mDNStrue);
-    UNITTEST_ASSERT(result == mStatus_NoError);
-
-    UNITTEST_ASSERT(result == mStatus_NoError);
-    ArpLogMsgTest(&mDNSStorage, (const ARP_EthIP *) arp_request_packet, primary_interfaceID);
-    UNITTEST_ASSERT(result == mStatus_NoError);
-UNITTEST_FOOTER
-
 UNITTEST_HEADER(ValidQueryReqTest)
 	mDNS *const m = &mDNSStorage;
 	mDNSAddr srcaddr, dstaddr;
@@ -73,9 +61,9 @@ UNITTEST_HEADER(ValidQueryReqTest)
 	DNSMessage * msg;
 	const mDNSu8 * end;
 
-	// This test case does not require setup of interfaces, the record's cache, or pending questions
-	// so m is initialized to all zeros.
-	InitmDNSStorage(m);
+    // Init unit test environment and verify no error occurred.
+    mStatus result = init_mdns_environment(mDNStrue);
+    UNITTEST_ASSERT(result == mStatus_NoError);
 
 	// Used random values for srcaddr and srcport
 	srcaddr.type	   = mDNSAddrType_IPv4;
diff --git a/mDNSResponder/unittests/mdns_ut.c b/mDNSResponder/unittests/mdns_ut.c
index 907cfa6..0384d2c 100644
--- a/mDNSResponder/unittests/mdns_ut.c
+++ b/mDNSResponder/unittests/mdns_ut.c
@@ -6,12 +6,3 @@ mDNSexport mStatus mDNS_InitStorage_ut(mDNS *const m, mDNS_PlatformSupport *cons
 {
 	return mDNS_InitStorage(m, p, rrcachestorage, rrcachesize, AdvertiseLocalAddresses, Callback, Context);
 }
-
-mDNSexport mStatus ArpLogMsgTest(mDNS *const m, const ARP_EthIP *const arp, const mDNSInterfaceID InterfaceID)
-{
-    NetworkInterfaceInfo *intf = FirstInterfaceForID(m, InterfaceID);
-    static const char msg[] = "ARP Req message";
-    LogMsg("Arp %-7s %s %.6a %.4a for %.4a",
-           intf->ifname, msg, arp->sha.b, arp->spa.b, arp->tpa.b);
-    return mStatus_NoError;
-}
diff --git a/mDNSResponder/unittests/unittest_common.h b/mDNSResponder/unittests/unittest_common.h
index a4af2e6..4b62bb9 100644
--- a/mDNSResponder/unittests/unittest_common.h
+++ b/mDNSResponder/unittests/unittest_common.h
@@ -51,7 +51,5 @@ extern int      LogEtcHosts_ut(mDNS *const m);
 extern mDNSBool mDNSMacOSXCreateEtcHostsEntry_ut(const domainname *domain, const struct sockaddr *sa,
                                                  const domainname *cname, char *ifname, AuthHash *auth);
 extern void     UpdateEtcHosts_ut(void *context);
-extern mStatus	ArpLogMsgTest(mDNS *const m, const ARP_EthIP *const arp, const mDNSInterfaceID InterfaceID);
-
 
 #endif /* UNITTEST_COMMON_H */



More information about the vc mailing list