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

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


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

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

mDNSResponder: Update to v878.250.4

The sources can be obtained via:

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

Update #4010.

---

 mDNSResponder/Clients/dnssdutil.c                  | 20886 +++++++++++++------
 mDNSResponder/Makefile                             |     2 +-
 mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist  |   627 +
 .../mDNSMacOSX/dnssdutil-entitlements.plist        |    12 +
 mDNSResponder/mDNSMacOSX/mDNSMacOSX.c              |    19 +-
 .../mDNSResponder.xcodeproj/project.pbxproj        |    29 +-
 mDNSResponder/mDNSShared/dns_sd.h                  |     2 +-
 7 files changed, 14821 insertions(+), 6756 deletions(-)

diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c
index 206e2b6..16b4875 100644
--- a/mDNSResponder/Clients/dnssdutil.c
+++ b/mDNSResponder/Clients/dnssdutil.c
@@ -6,6 +6,7 @@
 
 #include <CoreUtils/CommonServices.h>	// Include early.
 #include <CoreUtils/AsyncConnection.h>
+#include <CoreUtils/AtomicUtils.h>
 #include <CoreUtils/CFUtils.h>
 #include <CoreUtils/CommandLineUtils.h>
 #include <CoreUtils/DataBufferUtils.h>
@@ -20,6 +21,7 @@
 #include <CoreUtils/SoftLinking.h>
 #include <CoreUtils/StringUtils.h>
 #include <CoreUtils/TickUtils.h>
+#include <CoreUtils/TimeUtils.h>
 #include <dns_sd.h>
 #include <dns_sd_private.h>
 
@@ -93,6 +95,7 @@
 	"\x1C" "ServiceIndex\0"				\
 	"\x1D" "DenyExpensive\0"			\
 	"\x1E" "PathEvaluationDone\0"		\
+	"\x1F" "AllowExpiredAnswers\0"		\
 	"\x00"
 
 #define kDNSServiceProtocolDescriptors	\
@@ -108,14 +111,15 @@
 //	DNS
 //===========================================================================================================================
 
-#define kDNSPort						53
-#define kDNSCompressionOffsetMax		0x3FFF
-#define kDNSMaxUDPMessageSize			512
-#define kDNSMaxTCPMessageSize			UINT16_MAX
+#define kDNSPort					53
+#define kDNSMaxUDPMessageSize		512
+#define kDNSMaxTCPMessageSize		UINT16_MAX
 
 #define kDomainLabelLengthMax		63
 #define kDomainNameLengthMax		256
 
+#define kDNSRecordDataLengthMax		UINT16_MAX
+
 typedef struct
 {
 	uint8_t		id[ 2 ];
@@ -206,16 +210,47 @@ typedef struct
 
 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 );						\
-																		\
+// SRV RDATA fixed-length fields. See <https://tools.ietf.org/html/rfc2782>.
+
+typedef struct
+{
+	uint8_t		priority[ 2 ];
+	uint8_t		weight[ 2 ];
+	uint8_t		port[ 2 ];
+	
+}	SRVRecordDataFixedFields;
+
+check_compile_time( sizeof( SRVRecordDataFixedFields ) == 6 );
+
+// SOA RDATA fixed-length fields. See <https://tools.ietf.org/html/rfc1035#section-3.3.13>.
+
+typedef struct
+{
+	uint8_t		serial[ 4 ];
+	uint8_t		refresh[ 4 ];
+	uint8_t		retry[ 4 ];
+	uint8_t		expire[ 4 ];
+	uint8_t		minimum[ 4 ];
+	
+}	SOARecordDataFixedFields;
+
+check_compile_time( sizeof( SOARecordDataFixedFields ) == 20 );
+
+// DNS message compression. See <https://tools.ietf.org/html/rfc1035#section-4.1.4>.
+
+#define kDNSCompressionOffsetMax		0x3FFF
+
+#define IsCompressionByte( X )		( ( ( X ) & 0xC0 ) == 0xC0 )
+#define WriteDNSCompressionPtr( PTR, OFFSET )											\
+	do																					\
+	{																					\
+		( (uint8_t *)(PTR) )[ 0 ] = (uint8_t)( ( ( (OFFSET) >> 8 ) & 0x3F ) | 0xC0 );	\
+		( (uint8_t *)(PTR) )[ 1 ] = (uint8_t)(     (OFFSET)        & 0xFF          );	\
+																						\
 	}	while( 0 )
 
+#define NextLabel( LABEL )		( ( (LABEL)[ 0 ] == 0 ) ? NULL : ( (LABEL) + 1 + (LABEL)[ 0 ] ) )
+
 //===========================================================================================================================
 //	mDNS
 //===========================================================================================================================
@@ -228,18 +263,76 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
 #define kQClassUnicastResponseBit		( 1U << 15 )
 #define kRRClassCacheFlushBit			( 1U << 15 )
 
+// Recommended Resource Record TTL values. See <https://tools.ietf.org/html/rfc6762#section-10>.
+
+#define kMDNSRecordTTL_Host			120		// TTL for resource records related to a host name, e.g., A, AAAA, SRV, etc.
+#define kMDNSRecordTTL_Other		4500	// TTL for other resource records.
+
+// Maximum mDNS Message Size. See <https://tools.ietf.org/html/rfc6762#section-17>.
+
+#define kMDNSMessageSizeMax		8952	// 9000 B (Ethernet jumbo frame max size) - 40 B (IPv6 header) - 8 B (UDP header)
+
+#define kLocalStr			"\x05" "local"
+#define kLocalName			( (const uint8_t *) kLocalStr )
+#define kLocalNameLen		sizeof( kLocalStr )
+
 //===========================================================================================================================
-//	Test DNS Server
+//	Test Address Blocks
 //===========================================================================================================================
 
 // 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
+#define kDNSServerBaseAddrV4		UINT32_C( 0xCB007100 )	// 203.0.113.0/24
 
 // 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" )
+static const uint8_t		kDNSServerBaseAddrV6[] =
+{
+	0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00	// 2001:db8:1::/120
+};
+
+static const uint8_t		kMDNSReplierBaseAddrV6[] =
+{
+	0x20, 0x01, 0x0D, 0xB8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00	// 2001:db8:2::/96
+};
+
+check_compile_time( sizeof( kDNSServerBaseAddrV6 )   == 16 );
+check_compile_time( sizeof( kMDNSReplierBaseAddrV6 ) == 16 );
+
+// Bad IPv4 and IPv6 Address Blocks
+// Used by the DNS server when it needs to respond with intentionally "bad" A/AAAA record data, i.e., IP addresses neither
+// in 203.0.113.0/24 nor 2001:db8:1::/120.
+
+#define kDNSServerBadBaseAddrV4		UINT32_C( 0x00000000 )	// 0.0.0.0/24
+
+static const uint8_t		kDNSServerBadBaseAddrV6[] =
+{
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00	// ::ffff:0:0/120
+};
+
+check_compile_time( sizeof( kDNSServerBadBaseAddrV6 ) == 16 );
+
+//===========================================================================================================================
+//	Misc.
+//===========================================================================================================================
+
+#define kLowerAlphaNumericCharSet			"abcdefghijklmnopqrstuvwxyz0123456789"
+#define kLowerAlphaNumericCharSetSize		sizeof_string( kLowerAlphaNumericCharSet )
+
+// Note: strcpy_literal() appears in CoreUtils code, but isn't currently defined in framework headers.
+
+#if( !defined( strcpy_literal ) )
+	#define strcpy_literal( DST, SRC )		memcpy( DST, SRC, sizeof( SRC ) )
+#endif
+
+#define _RandomStringExact( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, OUT_STRING ) \
+	RandomString( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, CHAR_COUNT, OUT_STRING )
+
+#define kNoSuchRecordStr			"No Such Record"
+#define kNoSuchRecordAStr			"No Such Record (A)"
+#define kNoSuchRecordAAAAStr		"No Such Record (AAAA)"
+
+#define kRootLabel		( (const uint8_t *) "" )
 
 //===========================================================================================================================
 //	Gerneral Command Options
@@ -266,6 +359,11 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
 		(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,						\
 		(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
 
+#define DoubleOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED )	\
+	CLI_OPTION_DOUBLE_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,							\
+		(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,						\
+		(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+
 #define BooleanOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP ) \
 	CLI_OPTION_BOOLEAN( (SHORT_CHAR), (LONG_NAME), (VAL_PTR), (SHORT_HELP), NULL )
 
@@ -285,6 +383,7 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
 // DNS-SD API flag options
 
 static int		gDNSSDFlags						= 0;
+static int		gDNSSDFlag_AllowExpiredAnswers	= false;
 static int		gDNSSDFlag_BrowseDomains		= false;
 static int		gDNSSDFlag_DenyCellular			= false;
 static int		gDNSSDFlag_DenyExpensive		= false;
@@ -308,19 +407,20 @@ static int		gDNSSDFlag_WakeOnResolve		= false;
 #define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME ) \
 	BooleanOption( SHORT_CHAR, # FLAG_NAME, &gDNSSDFlag_ ## FLAG_NAME, "Use kDNSServiceFlags" # FLAG_NAME "." )
 
-#define DNSSDFlagsOption_DenyCellular()			DNSSDFlagOption( 'C', DenyCellular )
-#define DNSSDFlagsOption_DenyExpensive()		DNSSDFlagOption( 'E', DenyExpensive )
-#define DNSSDFlagsOption_ForceMulticast()		DNSSDFlagOption( 'M', ForceMulticast )
-#define DNSSDFlagsOption_IncludeAWDL()			DNSSDFlagOption( 'A', IncludeAWDL )
-#define DNSSDFlagsOption_NoAutoRename()			DNSSDFlagOption( 'N', NoAutoRename )
-#define DNSSDFlagsOption_PathEvalDone()			DNSSDFlagOption( 'P', PathEvaluationDone )
-#define DNSSDFlagsOption_ReturnIntermediates()	DNSSDFlagOption( 'I', ReturnIntermediates )
-#define DNSSDFlagsOption_Shared()				DNSSDFlagOption( 'S', Shared )
-#define DNSSDFlagsOption_SuppressUnusable()		DNSSDFlagOption( 'S', SuppressUnusable )
-#define DNSSDFlagsOption_Timeout()				DNSSDFlagOption( 'T', Timeout )
-#define DNSSDFlagsOption_UnicastResponse()		DNSSDFlagOption( 'U', UnicastResponse )
-#define DNSSDFlagsOption_Unique()				DNSSDFlagOption( 'U', Unique )
-#define DNSSDFlagsOption_WakeOnResolve()		DNSSDFlagOption( 'W', WakeOnResolve )
+#define DNSSDFlagsOption_AllowExpiredAnswers()		DNSSDFlagOption( 'X', AllowExpiredAnswers )
+#define DNSSDFlagsOption_DenyCellular()				DNSSDFlagOption( 'C', DenyCellular )
+#define DNSSDFlagsOption_DenyExpensive()			DNSSDFlagOption( 'E', DenyExpensive )
+#define DNSSDFlagsOption_ForceMulticast()			DNSSDFlagOption( 'M', ForceMulticast )
+#define DNSSDFlagsOption_IncludeAWDL()				DNSSDFlagOption( 'A', IncludeAWDL )
+#define DNSSDFlagsOption_NoAutoRename()				DNSSDFlagOption( 'N', NoAutoRename )
+#define DNSSDFlagsOption_PathEvalDone()				DNSSDFlagOption( 'P', PathEvaluationDone )
+#define DNSSDFlagsOption_ReturnIntermediates()		DNSSDFlagOption( 'I', ReturnIntermediates )
+#define DNSSDFlagsOption_Shared()					DNSSDFlagOption( 'S', Shared )
+#define DNSSDFlagsOption_SuppressUnusable()			DNSSDFlagOption( 'S', SuppressUnusable )
+#define DNSSDFlagsOption_Timeout()					DNSSDFlagOption( 'T', Timeout )
+#define DNSSDFlagsOption_UnicastResponse()			DNSSDFlagOption( 'U', UnicastResponse )
+#define DNSSDFlagsOption_Unique()					DNSSDFlagOption( 'U', Unique )
+#define DNSSDFlagsOption_WakeOnResolve()			DNSSDFlagOption( 'W', WakeOnResolve )
 
 // Interface option
 
@@ -395,6 +495,32 @@ static const char *		gConnectionOpt = kConnectionArg_Normal;
 #define RecordDataSection()		CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
 
 //===========================================================================================================================
+//	Output Formatting
+//===========================================================================================================================
+
+#define kOutputFormatStr_JSON		"json"
+#define kOutputFormatStr_XML		"xml"
+#define kOutputFormatStr_Binary		"binary"
+
+typedef enum
+{
+	kOutputFormatType_Invalid	= 0,
+	kOutputFormatType_JSON		= 1,
+	kOutputFormatType_XML		= 2,
+	kOutputFormatType_Binary	= 3
+	
+}	OutputFormatType;
+
+#define FormatOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP, IS_REQUIRED )			\
+	StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, "format", SHORT_HELP, IS_REQUIRED,	\
+		"\n"																			\
+		"Use '" kOutputFormatStr_JSON   "' for JavaScript Object Notation (JSON).\n"	\
+		"Use '" kOutputFormatStr_XML    "' for property list XML version 1.0.\n"		\
+		"Use '" kOutputFormatStr_Binary "' for property list binary version 1.0.\n"		\
+		"\n"																			\
+	)
+
+//===========================================================================================================================
 //	Browse Command Options
 //===========================================================================================================================
 
@@ -444,6 +570,7 @@ static CLIOption		kGetAddrInfoOpts[] =
 	
 	CLI_OPTION_GROUP( "Flags" ),
 	DNSSDFlagsOption(),
+	DNSSDFlagsOption_AllowExpiredAnswers(),
 	DNSSDFlagsOption_DenyCellular(),
 	DNSSDFlagsOption_DenyExpensive(),
 	DNSSDFlagsOption_PathEvalDone(),
@@ -478,15 +605,16 @@ static CLIOption		kQueryRecordOpts[] =
 	
 	CLI_OPTION_GROUP( "Flags" ),
 	DNSSDFlagsOption(),
-	DNSSDFlagsOption_IncludeAWDL(),
+	DNSSDFlagsOption_AllowExpiredAnswers(),
+	DNSSDFlagsOption_DenyCellular(),
+	DNSSDFlagsOption_DenyExpensive(),
 	DNSSDFlagsOption_ForceMulticast(),
-	DNSSDFlagsOption_Timeout(),
+	DNSSDFlagsOption_IncludeAWDL(),
+	DNSSDFlagsOption_PathEvalDone(),
 	DNSSDFlagsOption_ReturnIntermediates(),
 	DNSSDFlagsOption_SuppressUnusable(),
+	DNSSDFlagsOption_Timeout(),
 	DNSSDFlagsOption_UnicastResponse(),
-	DNSSDFlagsOption_DenyCellular(),
-	DNSSDFlagsOption_DenyExpensive(),
-	DNSSDFlagsOption_PathEvalDone(),
 	
 	CLI_OPTION_GROUP( "Operation" ),
 	ConnectionOptions(),
@@ -672,7 +800,7 @@ static CLIOption		kGetAddrInfoPOSIXOpts[] =
 	StringOption(	'n', "hostname",			&gGAIPOSIX_HostName,		"hostname", "Domain name to resolve or an IPv4 or IPv6 address.", true ),
 	StringOption(	's', "servname",			&gGAIPOSIX_ServName,		"servname", "Port number in decimal or service name from services(5).", false ),
 	
-	CLI_OPTION_GROUP( "Hints " ),
+	CLI_OPTION_GROUP( "Hints" ),
 	StringOptionEx(	'f', "family",				&gGAIPOSIX_Family,			"address family", "Address family to use for hints ai_family field.", false,
 		"\n"
 		"Possible address family values are 'inet' for AF_INET, 'inet6' for AF_INET6, or 'unspec' for AF_UNSPEC. If no\n"
@@ -761,23 +889,23 @@ static CLIOption		kPortMappingOpts[] =
 //===========================================================================================================================
 
 static const char *		gBrowseAll_Domain				= NULL;
-static char **			gBrowseAll_ServiceTypes			= NULL;
+static const char **	gBrowseAll_ServiceTypes			= NULL;
 static size_t			gBrowseAll_ServiceTypesCount	= 0;
 static int				gBrowseAll_BrowseTimeSecs		= 5;
-static int				gBrowseAll_MaxConnectTimeSecs	= 0;
+static int				gBrowseAll_ConnectTimeout		= 0;
 
 static CLIOption		kBrowseAllOpts[] =
 {
 	InterfaceOption(),
-	StringOption(	   'd', "domain",	&gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ),
-	MultiStringOption( 't', "type",		&gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\". All services are browsed for if none is specified.", false ),
+	StringOption(	   'd', "domain", &gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ),
+	MultiStringOption( 't', "type",   &gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\". All services are browsed for if none is specified.", false ),
 	
 	CLI_OPTION_GROUP( "Flags" ),
 	DNSSDFlagsOption_IncludeAWDL(),
 	
 	CLI_OPTION_GROUP( "Operation" ),
-	IntegerOption( 'b', "browseTime",		&gBrowseAll_BrowseTimeSecs,		"seconds", "Amount of time to spend browsing. (Default: 5 seconds)", false ),
-	IntegerOption( 'c', "maxConnectTime",	&gBrowseAll_MaxConnectTimeSecs,	"seconds", "Max duration of connection attempts. If <= 0, then no connections are attempted. (Default: 0 seconds)", false ),
+	IntegerOption( 'b', "browseTime",     &gBrowseAll_BrowseTimeSecs, "seconds", "Amount of time to spend browsing in seconds. (default: 5)", false ),
+	IntegerOption( 'c', "connectTimeout", &gBrowseAll_ConnectTimeout, "seconds", "Timeout for connection attempts. If <= 0, no connections are attempted. (default: 0)", false ),
 	CLI_OPTION_END()
 };
 
@@ -928,6 +1056,119 @@ static CLIOption		kMDNSQueryOpts[] =
 };
 
 //===========================================================================================================================
+//	MDNSCollider Command Options
+//===========================================================================================================================
+
+#define kMDNSColliderProgramSection_Intro																				\
+	"Programs dictate when the collider sends out unsolicited response messages for its record and how the collider\n"	\
+	"ought to react to probe queries that match its record's name, if at all.\n"										\
+	"\n"																												\
+	"For example, suppose that the goal is to cause a specific unique record in the verified state to be renamed.\n"	\
+	"The collider should be invoked such that its record's name is equal to that of the record being targeted. Also,\n"	\
+	"the record's type and data should be such that no record with that name, type, and data combination currently\n"	\
+	"exists. If the mDNS responder that owns the record follows sections 8.1 and 9 of RFC 6762, then the goal can be\n"	\
+	"accomplished with the following program:\n"																		\
+	"\n"																												\
+	"    probes 3r; send; wait 5000\n"																					\
+	"\n"																												\
+	"The first command, 'probes 3r', tells the collider to respond to the next three probe queries that match its\n"	\
+	"record's name. The second command, makes the collider send an unsolicited response message that contains its\n"	\
+	"record in the answer section. The third command makes the collider wait for five seconds before exiting, which\n"	\
+	"is more than enough time for the collider to respond to probe queries.\n"											\
+	"\n"																												\
+	"The send command will cause the targeted record to go into the probing state per section 9 since the collider's\n"	\
+	"record conflicts with target record. Per the probes command, the subsequent probe query sent during the probing\n"	\
+	"state will be answered by the collider, which will cause the record to be renamed per section 8.1.\n"
+
+#define kMDNSColliderProgramSection_Probes																				\
+	"The probes command defines how the collider ought to react to probe queries that match its record's name.\n"		\
+	"\n"																												\
+	"Usage: probes [<action-string>]\n"																					\
+	"\n"																												\
+	"The syntax for an action-string is\n"																				\
+	"\n"																												\
+	"    <action-string> ::= <action> | <action-string> \"-\" <action>\n"												\
+	"    <action>        ::= [<repeat-count>] <action-code>\n"															\
+	"    <repeat-count>  ::= \"1\" | \"2\" | ... | \"10\"\n"															\
+	"    <action-code>   ::= \"n\" | \"r\" | \"u\" | \"m\" | \"p\"\n"													\
+	"\n"																												\
+	"An expanded action-string is defined as\n"																			\
+	"\n"																												\
+	"    <expanded-action-string> ::= <action-code> | <expanded-action-string> \"-\" <action-code>\n"					\
+	"\n"																												\
+	"The action-string argument is converted into an expanded-action-string by expanding each action with a\n"			\
+	"repeat-count into an expanded-action-string consisting of exactly <repeat-count> <action-code>s. For example,\n"	\
+	"2n-r expands to n-n-r. Action-strings that expand to expanded-action-strings with more than 10 action-codes\n"		\
+	"are not allowed.\n"																								\
+	"\n"																												\
+	"When the probes command is executed, it does two things. Firstly, it resets to zero the collider's count of\n"		\
+	"probe queries that match its record's name. Secondly, it defines how the collider ought to react to such probe\n"	\
+	"queries based on the action-string argument. Specifically, the nth action-code in the expanded version of the\n"	\
+	"action-string argument defines how the collider ought to react to the nth received probe query:\n"					\
+	"\n"																												\
+	"    Code  Action\n"																								\
+	"    ----  ------\n"																								\
+	"    n     Do nothing.\n"																							\
+	"    r     Respond to the probe query.\n"																			\
+	"    u     Respond to the probe query via unicast.\n"																\
+	"    m     Respond to the probe query via multicast.\n"																\
+	"    p     Multicast own probe query. (Useful for causing simultaneous probe scenarios.)\n"							\
+	"\n"																												\
+	"Note: If no action is defined for a received probe query, then the collider does nothing, i.e., it doesn't send\n"	\
+	"a response nor does it multicast its own probe query.\n"
+
+#define kMDNSColliderProgramSection_Send																				\
+	"The send command multicasts an unsolicited mDNS response containing the collider's record in the answer\n"			\
+	"section, which can be used to force unique records with the same record name into the probing state.\n"			\
+	"\n"																												\
+	"Usage: send\n"
+
+#define kMDNSColliderProgramSection_Wait																				\
+	"The wait command pauses program execution for the interval of time specified by its argument.\n"					\
+	"\n"																												\
+	"Usage: wait <milliseconds>\n"
+
+#define kMDNSColliderProgramSection_Loop																				\
+	"The loop command starts a counting loop. The done statement marks the end of the loop body. The loop command's\n"	\
+	"argument specifies the number of loop iterations. Note: Loop nesting is supported up to a depth of 16.\n"			\
+	"\n"																												\
+	"Usage: loop <non-zero count>; ... ; done\n"																		\
+	"\n"																												\
+	"For example, the following program sends three unsolicited responses at an approximate rate of one per second:\n"	\
+	"\n"																												\
+	"    loop 3; wait 1000; send; done"
+
+#define ConnectionSection()		CLI_SECTION( kConnectionSection_Name, kConnectionSection_Text )
+
+static const char *		gMDNSCollider_Name			= NULL;
+static const char *		gMDNSCollider_Type			= NULL;
+static const char *		gMDNSCollider_RecordData	= NULL;
+static int				gMDNSCollider_UseIPv4		= false;
+static int				gMDNSCollider_UseIPv6		= false;
+static const char *		gMDNSCollider_Program		= NULL;
+
+static CLIOption		kMDNSColliderOpts[] =
+{
+	StringOption(  'i', "interface", &gInterface,               "name or index", "Network interface by name or index.", true ),
+	StringOption(  'n', "name",      &gMDNSCollider_Name,       "name", "Collider's record name.", true ),
+	StringOption(  't', "type",      &gMDNSCollider_Type,       "type", "Collider's record type.", true ),
+	StringOption(  'd', "data",      &gMDNSCollider_RecordData, "record data", "Collider's record data. See " kRecordDataSection_Name " below.", true ),
+	StringOption(  'p', "program",   &gMDNSCollider_Program,    "program", "Program to execute. See Program section below.", true ),
+	BooleanOption(  0 , "ipv4",      &gMDNSCollider_UseIPv4,    "Use IPv4." ),
+	BooleanOption(  0 , "ipv6",      &gMDNSCollider_UseIPv6,    "Use IPv6." ),
+	
+	RecordDataSection(),
+	CLI_SECTION( "Program",					kMDNSColliderProgramSection_Intro ),
+	CLI_SECTION( "Program Command: probes",	kMDNSColliderProgramSection_Probes ),
+	CLI_SECTION( "Program Command: send",	kMDNSColliderProgramSection_Send ),
+	CLI_SECTION( "Program Command: wait",	kMDNSColliderProgramSection_Wait ),
+	CLI_SECTION( "Program Command: loop",	kMDNSColliderProgramSection_Loop ),
+	CLI_OPTION_END()
+};
+
+static void	MDNSColliderCmd( void );
+
+//===========================================================================================================================
 //	PIDToUUID Command Options
 //===========================================================================================================================
 
@@ -945,59 +1186,72 @@ static CLIOption		kPIDToUUIDOpts[] =
 
 #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"	\
+	"presence of special labels in the query's QNAME. There are currently eight 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"
+	"IPv4 label, the IPv6 label, and SRV labels.\n"																		\
+	"\n"																												\
+	"Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\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"																									\
+	"A name is considered to exist if it's an Address name or an SRV name.\n"											\
+	"\n"																												\
+	"An Address name is defined as a name that ends with d.test., and the other labels, if any, and in no particular\n"	\
+	"order, unless otherwise noted, 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"																				\
+	"    3. zero or more Tag labels;\n"																					\
 	"    4. at most one TTL label; and\n"																				\
-	"    5. at most one IPv4 or IPv6 label.\n"
+	"    5. at most one IPv4 or IPv6 label.\n"																			\
+	"\n"																												\
+	"An SRV name is defined as a name with the following form:\n"														\
+	"\n"																												\
+	" _<service>._<proto>[.<parent domain>][.<SRV label 1>[.<target 1>][.<SRV label 2>[.<target 2>][...]]].d.test.\n"	\
+	"\n"																												\
+	"See \"SRV Names\" for details.\n"
 
 #define kDNSServerInfoText_ResourceRecords																				\
-	"Currently, the server only provides CNAME, A, and AAAA records.\n"													\
+	"Currently, the server only supports CNAME, A, AAAA, and SRV 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"	\
+	"Address names that 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"																						\
+	"A canonical Address name can exclusively be the name of one or more A records, can exclusively be the name or\n"	\
+	"one or more AAAA records, or can be the name of both A and AAAA records. Address names that contain an IPv4\n"		\
+	"label have at least one A record, but no AAAA records. Address names that contain an IPv6 label, have at least\n"	\
+	"one AAAA record, but no A records. All other Address names have at least one A record and at least one AAAA\n"		\
+	"record. See \"Count Labels\" for how the number of address records for a given Address 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"							\
+	"2001:db8:1::/120 block. Both of these address blocks are reserved for documentation. See\n"							\
+	"<https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n"								\
+	"\n"																												\
+	"SRV names are names of SRV records.\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"
+	"--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for CNAME, A, and\n"	\
+	"AAAA records with 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"				\
+	"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"																										\
+	"If QNAME is an Address name and its first label is Alias label \"alias-N\", then the response will contain\n"		\
+	"exactly N CNAME 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"												\
+	"    1. For each i in [3, N], the response will contain a CNAME record whose name is identical to QNAME, except\n"	\
+	"       that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME record whose\n"	\
+	"       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"		\
+	"       is \"alias\" instead, and whose RDATA is the name identical to QNAME minus 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"																					\
+	"If QNAME is an Address name and its first label is Alias label \"alias\", then the response will contain a\n"		\
+	"single CNAME record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to\n"	\
+	"QNAME minus 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"																										\
@@ -1009,12 +1263,12 @@ static CLIOption		kPIDToUUIDOpts[] =
 
 #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"	\
+	"[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"																						\
+	"If QNAME is an Address name and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response\n"	\
+	"will contain 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"	\
+	"    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"																												\
@@ -1030,14 +1284,14 @@ static CLIOption		kPIDToUUIDOpts[] =
 	"    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"																					\
+	"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 is\n"	\
+	"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"															\
+	"If QNAME is an Address name, contains Count label \"count-N\", and has the type of address records specified by\n"	\
+	"QTYPE, then 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"								\
+	"    1. For i in [1, N], the response will contain an address record of type QTYPE whose name is equal to QNAME\n"	\
+	"       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"																												\
@@ -1048,26 +1302,26 @@ static CLIOption		kPIDToUUIDOpts[] =
 	"    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"												\
+	"If QNAME is an Address name, contains Count label \"count-N_1-N_2\", and has the type of address records\n"		\
+	"specified by 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"	\
+	"       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"									\
+	"    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::c\n"									\
+	"    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::3a\n"								\
+	"    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::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"									\
+	"If QNAME is an Address name, but doesn't have the type of address records specified by QTYPE, then the response\n"	\
+	"will 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"
+	"Address names that 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"		\
@@ -1076,10 +1330,10 @@ static CLIOption		kPIDToUUIDOpts[] =
 	"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"									\
+	"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"
+	"If QNAME is an Address name and contains TTL label \"ttl-T\", then all non-CNAME records contained in the\n"		\
+	"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"
@@ -1087,25 +1341,58 @@ static CLIOption		kPIDToUUIDOpts[] =
 #define kDNSServerInfoText_IPv6Label \
 	"The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"
 
-#define kDNSServerDefaultTTL		60
+#define kDNSServerInfoText_SRVNames																						\
+	"SRV labels are of the form \"srv-R-W-P\", where R, W, and P are integers in [0, 2^16 - 1].\n"						\
+	"\n"																												\
+	"After the first two labels, i.e., the service and protocol labels, the sequence of labels, which may be empty,\n"	\
+	"leading up to the the first SRV label, if one exists, or the d.test. labels will be used as a parent domain for\n"	\
+	"the target hostname of each of the SRV name's SRV records.\n"														\
+	"\n"																												\
+	"If QNAME is an SRV name and QTYPE is SRV, then for each SRV label, the response will contain an SRV record with\n"	\
+	"priority R, weight W, port P, and target hostname <target>[.<parent domain>]., where <target> is the sequence\n"	\
+	"of labels, which may be empty, that follows the SRV label leading up to either the next SRV label or the\n"		\
+	"d.test. labels, whichever comes first.\n"																			\
+	"\n"																												\
+	"Example. A response to an SRV record query with a QNAME of\n"														\
+	"_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. will contain the following SRV records:\n"			\
+	"\n"																												\
+	"_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   0 0 80 www.example.com.\n"		\
+	"_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   1 0 8080 www.example.com.\n"
+
+#define kDNSServerInfoText_BadUDPMode \
+	"The purpose of Bad UDP mode is to test mDNSResponder's TCP fallback mechanism by which mDNSResponder reissues a\n"	\
+	"UDP query as a TCP query if the UDP response contains the expected QNAME, QTYPE, and QCLASS, but a message ID\n"	\
+	"that's not equal to the query's message ID.\n"																		\
+	"\n"																												\
+	"This mode is identical to the normal mode except that all responses sent via UDP have a message ID equal to the\n"	\
+	"query's message ID plus one. Also, in this mode, to aid in debugging, A records in responses sent via UDP have\n"	\
+	"IPv4 addresses in the 0.0.0.0/24 block instead of the 203.0.113.0/24 block, i.e., 0.0.0.0 is used as the IPv4\n"	\
+	"base address, and AAAA records in responses sent via UDP have IPv6 addresses in the ::ffff:0:0/120 block\n"		\
+	"instead of the 2001:db8:1::/120 block, i.e., ::ffff:0:0 is used as the IPv6 base address.\n"
 
 static int				gDNSServer_LoopbackOnly		= false;
 static int				gDNSServer_Foreground		= false;
 static int				gDNSServer_ResponseDelayMs	= 0;
-static int				gDNSServer_DefaultTTL		= kDNSServerDefaultTTL;
+static int				gDNSServer_DefaultTTL		= 60;
+static int				gDNSServer_Port				= kDNSPort;
+static const char *		gDNSServer_DomainOverride	= NULL;
 #if( TARGET_OS_DARWIN )
 static const char *		gDNSServer_FollowPID		= NULL;
 #endif
+static int				gDNSServer_BadUDPMode		= false;
 
 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." ),
+	BooleanOption( 'f', "foreground",    &gDNSServer_Foreground,      "Direct log 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 ),
+	IntegerOption( 'p', "port",          &gDNSServer_Port,            "port number", "UDP/TCP port number to use. Use 0 for any port. (default: 53)", false ),
+	StringOption(   0 , "domain",        &gDNSServer_DomainOverride,  "domain", "Used to override 'd.test.' as the server's domain.", false ),
 #if( TARGET_OS_DARWIN )
-	StringOption(   0 , "followPID",     &gDNSServer_FollowPID,       "pid", "Exit when the process (usually the parent proccess) specified by PID exits.", false ),
+	StringOption(   0 , "follow",        &gDNSServer_FollowPID,       "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
 #endif
+	BooleanOption(  0 , "badUDPMode",    &gDNSServer_BadUDPMode,      "Run in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
 	
 	CLI_SECTION( "Intro",				kDNSServerInfoText_Intro ),
 	CLI_SECTION( "Name Existence",		kDNSServerInfoText_NameExistence ),
@@ -1117,26 +1404,248 @@ static CLIOption		kDNSServerOpts[] =
 	CLI_SECTION( "TTL Labels",			kDNSServerInfoText_TTLLabel ),
 	CLI_SECTION( "IPv4 Label",			kDNSServerInfoText_IPv4Label ),
 	CLI_SECTION( "IPv6 Label",			kDNSServerInfoText_IPv6Label ),
+	CLI_SECTION( "SRV Names",			kDNSServerInfoText_SRVNames ),
+	CLI_SECTION( "Bad UDP Mode",		kDNSServerInfoText_BadUDPMode ),
 	CLI_OPTION_END()
 };
 
 static void	DNSServerCmd( void );
 
 //===========================================================================================================================
+//	MDNSReplier Command Options
+//===========================================================================================================================
+
+#define kMDNSReplierPortBase		50000
+
+#define kMDNSReplierInfoText_Intro																						\
+	"The mDNS replier answers mDNS queries for its authoritative records. These records are of class IN and of types\n"	\
+	"PTR, SRV, TXT, A, and AAAA as described below.\n"																	\
+	"\n"																												\
+	"Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
+
+#define kMDNSReplierInfoText_Parameters																					\
+	"There are five parameters that control the replier's set of authoritative records.\n"								\
+	"\n"																												\
+	"    1. <hostname> is the base name used for service instance names and the names of A and AAAA records. This\n"	\
+	"       parameter is specified with the --hostname option.\n"														\
+	"    2. <tag> is an arbitrary string used to uniquify service types. This parameter is specified with the --tag\n"	\
+	"       option.\n"																									\
+	"    3. N_max in an integer in [1, 65535] and limits service types to those that have no more than N_max\n"			\
+	"       instances. It also limits the number of hostnames to N_max, i.e., <hostname>.local.,\n"						\
+	"       <hostname>-1.local., ..., <hostname>-N_max.local. This parameter is specified with the\n"					\
+	"       --maxInstanceCount option.\n"																				\
+	"    4. N_a is an integer in [1, 255] and the number of A records per hostname. This parameter is specified\n"		\
+	"       with the --countA option.\n"																				\
+	"    5. N_aaaa is an integer in [1, 255] and the number of AAAA records per hostname. This parameter is\n"			\
+	"       specified with the --countAAAA option.\n"
+
+#define kMDNSReplierInfoText_PTR																						\
+	"The replier's authoritative PTR records have names of the form _t-<tag>-<L>-<N>._tcp.local., where L is an\n"		\
+	"integer in [1, 65535], and N is an integer in [1, N_max].\n"														\
+	"\n"																												\
+	"For a given L and N, the replier has exactly N authoritative PTR records:\n"										\
+	"\n"																												\
+	"    1. The first PTR record is defined as\n"																		\
+	"\n"																												\
+	"        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"																		\
+	"        TYPE:  PTR\n"																								\
+	"        CLASS: IN\n"																								\
+	"        TTL:   4500\n"																								\
+	"        RDATA: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"															\
+	"\n"																												\
+	"    2. For each i in [2, N], there is one PTR record defined as\n"													\
+	"\n"																												\
+	"        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"																		\
+	"        TYPE:  PTR\n"																								\
+	"        CLASS: IN\n"																								\
+	"        TTL:   4500\n"																								\
+	"        RDATA: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"
+
+#define kMDNSReplierInfoText_SRV																						\
+	"The replier's authoritative SRV records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"	\
+	"where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"		\
+	"\"<hostname> (<i>)\", where i is in [2, N].\n"																		\
+	"\n"																												\
+	"For a given L and N, the replier has exactly N authoritative SRV records:\n"										\
+	"\n"																												\
+	"    1. The first SRV record is defined as\n"																		\
+	"\n"																												\
+	"        NAME:  <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"															\
+	"        TYPE:  SRV\n"																								\
+	"        CLASS: IN\n"																								\
+	"        TTL:   120\n"																								\
+	"        RDATA:\n"																									\
+	"            Priority: 0\n"																							\
+	"            Weight:   0\n"																							\
+	"            Port:     (50000 + L) mod 2^16\n"																		\
+	"            Target:   <hostname>.local.\n"																			\
+	"\n"																												\
+	"    2. For each i in [2, N], there is one SRV record defined as:\n"												\
+	"\n"																												\
+	"        NAME:  \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"												\
+	"        TYPE:  SRV\n"																								\
+	"        CLASS: IN\n"																								\
+	"        TTL:   120\n"																								\
+	"        RDATA:\n"																									\
+	"            Priority: 0\n"																							\
+	"            Weight:   0\n"																							\
+	"            Port:     (50000 + L) mod 2^16\n"																		\
+	"            Target:   <hostname>-<i>.local.\n"
+
+#define kMDNSReplierInfoText_TXT																						\
+	"The replier's authoritative TXT records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"	\
+	"where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"		\
+	"\"<hostname> (<i>)\", where i is in [2, N].\n"																		\
+	"\n"																												\
+	"For a given L and N, the replier has exactly N authoritative TXT records:\n"										\
+	"\n"																												\
+	"    1. The first TXT record is defined as\n"																		\
+	"\n"																												\
+	"        NAME:     <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"														\
+	"        TYPE:     TXT\n"																							\
+	"        CLASS:    IN\n"																							\
+	"        TTL:      4500\n"																							\
+	"        RDLENGTH: L\n"																								\
+	"        RDATA:    <one or more strings with an aggregate length of L octets>\n"									\
+	"\n"																												\
+	"    2. For each i in [2, N], there is one TXT record:\n"															\
+	"\n"																												\
+	"        NAME:     \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"												\
+	"        TYPE:     TXT\n"																							\
+	"        CLASS:    IN\n"																							\
+	"        TTL:      4500\n"																							\
+	"        RDLENGTH: L\n"																								\
+	"        RDATA:    <one or more strings with an aggregate length of L octets>\n"									\
+	"\n"																												\
+	"The RDATA of each TXT record is exactly L octets and consists of a repeating series of the 15-byte string\n"		\
+	"\"hash=0x<32-bit FNV-1 hash of the record name as an 8-character hexadecimal string>\". The last instance of\n"	\
+	"the string may be truncated to satisfy the TXT record data's size requirement.\n"
+
+#define kMDNSReplierInfoText_A																							\
+	"The replier has exactly N_max x N_a authoritative A records:\n"													\
+	"\n"																												\
+	"    1. For each j in [1, N_a], an A record is defined as\n"														\
+	"\n"																												\
+	"        NAME:     <hostname>.local.\n"																				\
+	"        TYPE:     A\n"																								\
+	"        CLASS:    IN\n"																							\
+	"        TTL:      120\n"																							\
+	"        RDLENGTH: 4\n"																								\
+	"        RDATA:    0.0.1.<j>\n"																						\
+	"\n"																												\
+	"    2. For each i in [2, N_max], for each j in [1, N_a], an A record is defined as\n"								\
+	"\n"																												\
+	"        NAME:     <hostname>-<i>.local.\n"																			\
+	"        TYPE:     A\n"																								\
+	"        CLASS:    IN\n"																							\
+	"        TTL:      120\n"																							\
+	"        RDLENGTH: 4\n"																								\
+	"        RDATA:    0.<ceil(i / 256)>.<i mod 256>.<j>\n"
+
+#define kMDNSReplierInfoText_AAAA																						\
+	"The replier has exactly N_max x N_aaaa authoritative AAAA records:\n"												\
+	"\n"																												\
+	"    1. For each j in [1, N_aaaa], a AAAA record is defined as\n"													\
+	"\n"																												\
+	"        NAME:     <hostname>.local.\n"																				\
+	"        TYPE:     AAAA\n"																							\
+	"        CLASS:    IN\n"																							\
+	"        TTL:      120\n"																							\
+	"        RDLENGTH: 16\n"																							\
+	"        RDATA:    2001:db8:2::1:<j>\n"																				\
+	"\n"																												\
+	"    2. For each i in [2, N_max], for each j in [1, N_aaaa], a AAAA record is defined as\n"							\
+	"\n"																												\
+	"        NAME:     <hostname>-<i>.local.\n"																			\
+	"        TYPE:     AAAA\n"																							\
+	"        CLASS:    IN\n"																							\
+	"        TTL:      120\n"																							\
+	"        RDLENGTH: 16\n"																							\
+	"        RDATA:    2001:db8:2::<i>:<j>\n"
+
+#define kMDNSReplierInfoText_Responses																					\
+	"When generating answers for a query message, any two records pertaining to the same hostname will be grouped\n"	\
+	"together in the same response message, and any two records pertaining to different hostnames will be in\n"			\
+	"separate response messages.\n"
+
+static const char *		gMDNSReplier_Hostname			= NULL;
+static const char *		gMDNSReplier_ServiceTypeTag		= NULL;
+static int				gMDNSReplier_MaxInstanceCount	= 1000;
+static int				gMDNSReplier_NoAdditionals		= false;
+static int				gMDNSReplier_RecordCountA		= 1;
+static int				gMDNSReplier_RecordCountAAAA	= 1;
+static double			gMDNSReplier_UnicastDropRate	= 0.0;
+static double			gMDNSReplier_MulticastDropRate	= 0.0;
+static int				gMDNSReplier_MaxDropCount		= 0;
+static int				gMDNSReplier_UseIPv4			= false;
+static int				gMDNSReplier_UseIPv6			= false;
+static int				gMDNSReplier_Foreground			= false;
+static const char *		gMDNSReplier_FollowPID		    = NULL;
+
+static CLIOption		kMDNSReplierOpts[] =
+{
+	StringOption(  'i', "interface",        &gInterface,                     "name or index", "Network interface by name or index.", true ),
+	StringOption(  'n', "hostname",         &gMDNSReplier_Hostname,          "string", "Base name to use for hostnames and service instance names.", true ),
+	StringOption(  't', "tag",              &gMDNSReplier_ServiceTypeTag,    "string", "Tag to use for service types, e.g., _t-<tag>-<TXT size>-<count>._tcp.", true ),
+	IntegerOption( 'c', "maxInstanceCount", &gMDNSReplier_MaxInstanceCount,  "count", "Maximum number of service instances. (default: 1000)", false ),
+	BooleanOption(  0 , "noAdditionals",    &gMDNSReplier_NoAdditionals,     "When answering queries, don't include any additional records." ),
+	IntegerOption(  0 , "countA",           &gMDNSReplier_RecordCountA,      "count", "Number of A records per hostname. (default: 1)", false ),
+	IntegerOption(  0 , "countAAAA",        &gMDNSReplier_RecordCountAAAA,   "count", "Number of AAAA records per hostname. (default: 1)", false ),
+	DoubleOption(   0 , "udrop",            &gMDNSReplier_UnicastDropRate,   "probability", "Probability of dropping a unicast response. (default: 0.0)", false ),
+	DoubleOption(   0 , "mdrop",            &gMDNSReplier_MulticastDropRate, "probability", "Probability of dropping a multicast query or response. (default: 0.0)", false ),
+	IntegerOption(  0 , "maxDropCount",     &gMDNSReplier_MaxDropCount,      "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
+	BooleanOption(  0 , "ipv4",             &gMDNSReplier_UseIPv4,           "Use IPv4." ),
+	BooleanOption(  0 , "ipv6",             &gMDNSReplier_UseIPv6,           "Use IPv6." ),
+	BooleanOption( 'f', "foreground",       &gMDNSReplier_Foreground,        "Direct log output to stdout instead of system logging." ),
+#if( TARGET_OS_DARWIN )
+	StringOption(   0 , "follow",           &gMDNSReplier_FollowPID,         "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
+#endif
+	
+	CLI_SECTION( "Intro",							kMDNSReplierInfoText_Intro ),
+	CLI_SECTION( "Authoritative Record Parameters",	kMDNSReplierInfoText_Parameters ),
+	CLI_SECTION( "Authoritative PTR Records",		kMDNSReplierInfoText_PTR ),
+	CLI_SECTION( "Authoritative SRV Records",		kMDNSReplierInfoText_SRV ),
+	CLI_SECTION( "Authoritative TXT Records",		kMDNSReplierInfoText_TXT ),
+	CLI_SECTION( "Authoritative A Records",			kMDNSReplierInfoText_A ),
+	CLI_SECTION( "Authoritative AAAA Records",		kMDNSReplierInfoText_AAAA ),
+	CLI_SECTION( "Responses",						kMDNSReplierInfoText_Responses ),
+	CLI_OPTION_END()
+};
+
+static void	MDNSReplierCmd( void );
+
+//===========================================================================================================================
 //	Test Command Options
 //===========================================================================================================================
 
+#define kTestExitStatusSection_Name		"Exit Status"
+#define kTestExitStatusSection_Text																						\
+	"This test command can exit with one of three status codes:\n"														\
+	"\n"																												\
+	"0 - The test ran to completion and passed.\n"																		\
+	"1 - A fatal error prevented the test from completing.\n"															\
+	"2 - The test ran to completion, but it or a subtest failed. See test output for details.\n"						\
+	"\n"																												\
+	"Note: The pass/fail status applies to the correctness or results. It does not necessarily imply anything about\n"	\
+	"performance.\n"
+
+#define TestExitStatusSection()		CLI_SECTION( kTestExitStatusSection_Name, kTestExitStatusSection_Text )
+
+#define kGAIPerfTestSuiteName_Basic			"basic"
+#define kGAIPerfTestSuiteName_Advanced		"advanced"
+
 static const char *		gGAIPerf_TestSuite				= NULL;
 static int				gGAIPerf_CallDelayMs			= 10;
 static int				gGAIPerf_ServerDelayMs			= 10;
-static int				gGAIPerf_DefaultIterCount		= 100;
+static int				gGAIPerf_SkipPathEvalulation	= false;
+static int				gGAIPerf_BadUDPMode				= false;
+static int				gGAIPerf_IterationCount			= 100;
 static const char *		gGAIPerf_OutputFilePath			= NULL;
-static const char *		gGAIPerf_OutputFormat			= "json";
-static int				gGAIPerf_OutputAppendNewLine	= false;
+static const char *		gGAIPerf_OutputFormat			= kOutputFormatStr_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"																													\
@@ -1161,7 +1670,6 @@ static void	GAIPerfCmd( void );
 	"\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"																													\
@@ -1204,30 +1712,150 @@ 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"
+		"There are currently two predefined test suites, '" kGAIPerfTestSuiteName_Basic "' and '" kGAIPerfTestSuiteName_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." ),
+	FormatOption(   'f', "format",        &gGAIPerf_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+	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 ),
+	BooleanOption(   0 , "skipPathEval",  &gGAIPerf_SkipPathEvalulation, "Use kDNSServiceFlagsPathEvaluationDone when calling DNSServiceGetAddrInfo()." ),
+	IntegerOption(  'i', "iterations",    &gGAIPerf_IterationCount,      "count", "The number of iterations per test case. (default: 100)", false ),
+	
+	CLI_OPTION_GROUP( "DNS Server Options" ),
+	IntegerOption(   0 , "responseDelay", &gGAIPerf_ServerDelayMs,       "ms", "Additional delay in milliseconds to have the server apply to responses. (default: 10)", false ),
+	BooleanOption(   0 , "badUDPMode",    &gGAIPerf_BadUDPMode,          "Run server in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
+	
+	CLI_SECTION( "Test Suite \"Basic\"",	kGAIPerfSectionText_TestSuiteBasic ),
+	CLI_SECTION( "Test Suite \"Advanced\"",	kGAIPerfSectionText_TestSuiteAdvanced ),
+	TestExitStatusSection(),
+	CLI_OPTION_END()
+};
+
+static void	MDNSDiscoveryTestCmd( void );
+
+static int				gMDNSDiscoveryTest_InstanceCount		= 100;
+static int				gMDNSDiscoveryTest_TXTSize				= 100;
+static int				gMDNSDiscoveryTest_BrowseTimeSecs		= 2;
+static int				gMDNSDiscoveryTest_FlushCache			= false;
+static char *			gMDNSDiscoveryTest_Interface			= NULL;
+static int				gMDNSDiscoveryTest_NoAdditionals		= false;
+static int				gMDNSDiscoveryTest_RecordCountA			= 1;
+static int				gMDNSDiscoveryTest_RecordCountAAAA		= 1;
+static double			gMDNSDiscoveryTest_UnicastDropRate		= 0.0;
+static double			gMDNSDiscoveryTest_MulticastDropRate	= 0.0;
+static int				gMDNSDiscoveryTest_MaxDropCount			= 0;
+static int				gMDNSDiscoveryTest_UseIPv4				= false;
+static int				gMDNSDiscoveryTest_UseIPv6				= false;
+static const char *		gMDNSDiscoveryTest_OutputFormat			= kOutputFormatStr_JSON;
+static int				gMDNSDiscoveryTest_OutputAppendNewline	= false;
+static const char *		gMDNSDiscoveryTest_OutputFilePath		= NULL;
+
+static CLIOption		kMDNSDiscoveryTestOpts[] =
+{
+	IntegerOption( 'c', "instanceCount",  &gMDNSDiscoveryTest_InstanceCount,       "count", "Number of service instances to discover. (default: 100)", false ),
+	IntegerOption( 's', "txtSize",        &gMDNSDiscoveryTest_TXTSize,             "bytes", "Desired size of each service instance's TXT record data. (default: 100)", false ),
+	IntegerOption( 'b', "browseTime",     &gMDNSDiscoveryTest_BrowseTimeSecs,      "seconds", "Amount of time to spend browsing in seconds. (default: 2)", false ),
+	BooleanOption(  0 , "flushCache",     &gMDNSDiscoveryTest_FlushCache,          "Flush mDNSResponder's record cache before browsing. Requires root privileges." ),
+	
+	CLI_OPTION_GROUP( "mDNS Replier Parameters" ),
+	StringOption(  'i', "interface",      &gMDNSDiscoveryTest_Interface,           "name or index", "Network interface. If unspecified, any available mDNS-capable interface will be used.", false ),
+	BooleanOption(  0 , "noAdditionals",  &gMDNSDiscoveryTest_NoAdditionals,       "When answering queries, don't include any additional records." ),
+	IntegerOption(  0 , "countA",         &gMDNSDiscoveryTest_RecordCountA,        "count", "Number of A records per hostname. (default: 1)", false ),
+	IntegerOption(  0 , "countAAAA",      &gMDNSDiscoveryTest_RecordCountAAAA,     "count", "Number of AAAA records per hostname. (default: 1)", false ),
+	DoubleOption(   0 , "udrop",          &gMDNSDiscoveryTest_UnicastDropRate,     "probability", "Probability of dropping a unicast response. (default: 0.0)", false ),
+	DoubleOption(   0 , "mdrop",          &gMDNSDiscoveryTest_MulticastDropRate,   "probability", "Probability of dropping a multicast query or response. (default: 0.0)", false ),
+	IntegerOption(  0 , "maxDropCount",   &gMDNSDiscoveryTest_MaxDropCount,        "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
+	BooleanOption(  0 , "ipv4",           &gMDNSDiscoveryTest_UseIPv4,             "Use IPv4." ),
+	BooleanOption(  0 , "ipv6",           &gMDNSDiscoveryTest_UseIPv6,             "Use IPv6." ),
+	
+	CLI_OPTION_GROUP( "Results" ),
+	FormatOption(   'f', "format",        &gMDNSDiscoveryTest_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+	StringOption(   'o', "output",        &gMDNSDiscoveryTest_OutputFilePath,      "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+	BooleanOption(  'n', "appendNewline", &gMDNSDiscoveryTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+	
+	TestExitStatusSection(),
+	CLI_OPTION_END()
+};
+
+static void	DotLocalTestCmd( void );
+
+static const char *		gDotLocalTest_Interface				= NULL;
+static const char *		gDotLocalTest_OutputFormat			= kOutputFormatStr_JSON;
+static int				gDotLocalTest_OutputAppendNewline	= false;
+static const char *		gDotLocalTest_OutputFilePath		= NULL;
+
+#define kDotLocalTestSubtestDesc_GAIMDNSOnly	"GAI for a dotlocal name that has only MDNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAIDNSOnly		"GAI for a dotlocal name that has only DNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAIBoth		"GAI for a dotlocal name that has both mDNS and DNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAINeither		"GAI for a dotlocal name that has no A or AAAA records."
+#define kDotLocalTestSubtestDesc_GAINoSuchRecord \
+	"GAI for a dotlocal name that has no A or AAAA records, but is a subdomain name of a search domain."
+#define kDotLocalTestSubtestDesc_QuerySRV		"SRV query for a dotlocal name that has only a DNS SRV record."
+
+#define kDotLocalTestSectionText_Description																				\
+	"The goal of the dotlocal test is to verify that mDNSResponder properly handles queries for domain names in the\n"		\
+	"local domain when a local SOA record exists. As part of the test setup, a test DNS server and an mdnsreplier are\n"	\
+	"spawned, and a dummy local SOA record is registered with DNSServiceRegisterRecord(). The server is invoked such\n"		\
+	"that its domain is a second-level subdomain of the local domain, i.e., <some label>.local, while the mdnsreplier is\n"	\
+	"invoked such that its base hostname is equal to the server's domain, e.g., if the server's domain is test.local.,\n"	\
+	"then the mdnsreplier's base hostname is test.local.\n"																	\
+	"\n"																													\
+	"The dotlocal test consists of six subtests that perform either a DNSServiceGetAddrInfo (GAI) operation for a\n"		\
+	"hostname in the local domain or a DNSServiceQueryRecord operation to query for an SRV record in the local domain:\n"	\
+	"\n"																													\
+	"1. " kDotLocalTestSubtestDesc_GAIMDNSOnly		"\n"																	\
+	"2. " kDotLocalTestSubtestDesc_GAIDNSOnly		"\n"																	\
+	"3. " kDotLocalTestSubtestDesc_GAIBoth			"\n"																	\
+	"4. " kDotLocalTestSubtestDesc_GAINeither		"\n"																	\
+	"5. " kDotLocalTestSubtestDesc_GAINoSuchRecord	"\n"																	\
+	"6. " kDotLocalTestSubtestDesc_QuerySRV			"\n"																	\
+	"\n"																													\
+	"Each subtest runs for five seconds.\n"
+
+static CLIOption		kDotLocalTestOpts[] =
+{
+	StringOption(  'i', "interface",     &gDotLocalTest_Interface,           "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+	
+	CLI_OPTION_GROUP( "Results" ),
+	FormatOption(  'f', "format",        &gDotLocalTest_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+	StringOption(  'o', "output",        &gDotLocalTest_OutputFilePath,      "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+	BooleanOption( 'n', "appendNewline", &gDotLocalTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+	
+	CLI_SECTION( "Description", kDotLocalTestSectionText_Description ),
+	TestExitStatusSection(),
+	CLI_OPTION_END()
+};
+
+static void	ProbeConflictTestCmd( void );
+
+static const char *		gProbeConflictTest_Interface			= NULL;
+static int				gProbeConflictTest_UseComputerName		= false;
+static const char *		gProbeConflictTest_OutputFormat			= kOutputFormatStr_JSON;
+static int				gProbeConflictTest_OutputAppendNewline	= false;
+static const char *		gProbeConflictTest_OutputFilePath		= NULL;
+
+static CLIOption		kProbeConflictTestOpts[] =
+{
+	StringOption(  'i', "interface",       &gProbeConflictTest_Interface,           "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+	BooleanOption( 'c', "useComputerName", &gProbeConflictTest_UseComputerName,     "Use the device's \"computer name\" for the test service's name." ),
 	
-	CLI_SECTION( kGAIPerfSectionTitle_TestSuiteBasic,		kGAIPerfSectionText_TestSuiteBasic ),
-	CLI_SECTION( kGAIPerfSectionTitle_TestSuiteAdvanced,	kGAIPerfSectionText_TestSuiteAdvanced ),
+	CLI_OPTION_GROUP( "Results" ),
+	FormatOption(  'f', "format",          &gProbeConflictTest_OutputFormat,        "Specifies the test report output format. (default: " kOutputFormatStr_JSON ")", false ),
+	StringOption(  'o', "output",          &gProbeConflictTest_OutputFilePath,      "path", "Path of the file to write test report to instead of standard output (stdout).", false ),
+	BooleanOption( 'n', "appendNewline",   &gProbeConflictTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+	
+	TestExitStatusSection(),
 	CLI_OPTION_END()
 };
 
 static CLIOption		kTestOpts[] =
 {
-	Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Run DNSServiceGetAddrInfo() performance tests.", false ),
+	Command( "gaiperf",        GAIPerfCmd,           kGAIPerfOpts,            "Runs DNSServiceGetAddrInfo() performance tests.", false ),
+	Command( "mdnsdiscovery",  MDNSDiscoveryTestCmd, kMDNSDiscoveryTestOpts,  "Tests mDNS service discovery for correctness.", false ),
+	Command( "dotlocal",       DotLocalTestCmd,      kDotLocalTestOpts,       "Tests DNS and mDNS queries for domain names in the local domain.", false ),
+	Command( "probeconflicts", ProbeConflictTestCmd, kProbeConflictTestOpts,  "Tests various probing conflict scenarios.", false ),
+	
 	CLI_OPTION_END()
 };
 
@@ -1429,9 +2057,11 @@ static CLIOption		kGlobalOpts[] =
 #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
 	Command( "DNSCrypt",			DNSCryptCmd,			kDNSCryptOpts,			"Crafts and sends a DNSCrypt query.", true ),
 #endif
-	Command( "mDNSQuery",			MDNSQueryCmd,			kMDNSQueryOpts,			"Crafts and sends an mDNS query over the specified interface.", true ),
+	Command( "mdnsquery",			MDNSQueryCmd,			kMDNSQueryOpts,			"Crafts and sends an mDNS query over the specified interface.", true ),
+	Command( "mdnscollider",		MDNSColliderCmd,		kMDNSColliderOpts,		"Creates record name collision scenarios.", true ),
 	Command( "pid2uuid",			PIDToUUIDCmd,			kPIDToUUIDOpts,			"Prints the UUID of a process.", true ),
 	Command( "server",				DNSServerCmd,			kDNSServerOpts,			"DNS server for testing.", true ),
+	Command( "mdnsreplier",			MDNSReplierCmd,			kMDNSReplierOpts,		"Responds to mDNS queries for a set of authoritative resource records.", 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 )
@@ -1468,6 +2098,12 @@ static int
 		PrintFFormat *	inFormat,
 		PrintFVAList *	inArgs,
 		void *			inUserContext );
+static int
+	PrintFAddRmvFlagsHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext );
 
 static DNSServiceFlags	GetDNSSDFlagsFromOpts( void );
 
@@ -1523,6 +2159,15 @@ static OSStatus
 		char				inBuf[ kDNSServiceMaxDomainName ],
 		const uint8_t **	outNextPtr );
 static OSStatus
+	DNSMessageExtractQuestion(
+		const uint8_t *		inMsgPtr,
+		size_t				inMsgLen,
+		const uint8_t *		inPtr,
+		uint8_t				inNameBuf[ kDomainNameLengthMax ],
+		uint16_t *			outType,
+		uint16_t *			outClass,
+		const uint8_t **	outPtr );
+static OSStatus
 	DNSMessageExtractRecord(
 		const uint8_t *		inMsgPtr,
 		size_t				inMsgLen,
@@ -1550,6 +2195,10 @@ static OSStatus
 		uint8_t **		outEndPtr );
 static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
 static size_t	DomainNameLength( const uint8_t *inName );
+static OSStatus	DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen );
+#define DomainNameDup( IN_NAME, OUT_NAME, OUT_LEN )				DomainNameDupEx( IN_NAME, false, OUT_NAME, OUT_LEN )
+#define DomainNameDupLower( IN_NAME, OUT_NAME, OUT_LEN )		DomainNameDupEx( IN_NAME, true, OUT_NAME, OUT_LEN )
+
 static OSStatus
 	DomainNameFromString(
 		uint8_t			inDomainName[ kDomainNameLengthMax ],
@@ -1618,6 +2267,10 @@ static OSStatus
 		DispatchHandler		inCancelHandler,
 		void *				inContext,
 		dispatch_source_t *	outTimer );
+
+#define DispatchTimerOneShotCreate( IN_START, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, IN_CONTEXT, OUT_TIMER )	\
+	DispatchTimerCreate( IN_START, DISPATCH_TIME_FOREVER, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, NULL, IN_CONTEXT, OUT_TIMER )
+
 static OSStatus
 	DispatchProcessMonitorCreate(
 		pid_t				inPID,
@@ -1647,7 +2300,9 @@ static void				SocketContextCancelHandler( void *inContext );
 
 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 );
+#if( TARGET_OS_DARWIN )
+static OSStatus		StringToPID( const char *inString, pid_t *outPID );
+#endif
 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 );
@@ -1666,45 +2321,263 @@ static OSStatus
 		Boolean			inNoPortReuse,
 		SocketRef *		outSock );
 
-typedef uint64_t		MicroTime64;
+static const struct sockaddr *	GetMDNSMulticastAddrV4( void );
+static const struct sockaddr *	GetMDNSMulticastAddrV6( void );
+static OSStatus					GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex );
 
-static MicroTime64	GetCurrentMicroTime( void );	// Gets the number of milliseconds since 1970-01-01T00:00:00Z
+static OSStatus
+	CreateMulticastSocket(
+		const struct sockaddr *	inAddr,
+		int						inPort,
+		const char *			inIfName,
+		uint32_t				inIfIndex,
+		Boolean					inJoin,
+		int *					outPort,
+		SocketRef *				outSock );
+
+static OSStatus	DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint32_t *outValue, const char **outPtr );
+static OSStatus	CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax );
+static OSStatus	CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax );
+static OSStatus	CheckRootUser( void );
+static OSStatus	SpawnCommand( pid_t *outPID, const char *inFormat, ... );
+static OSStatus
+	OutputPropertyList(
+		CFPropertyListRef	inPList,
+		OutputFormatType	inType,
+		Boolean				inAppendNewline,
+		const char *		inOutputFilePath );
+static void
+	DNSRecordFixedFieldsSet(
+		DNSRecordFixedFields *	inFields,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint32_t				inTTL,
+		uint16_t				inRDLength );
+static void
+	SRVRecordDataFixedFieldsGet(
+		const SRVRecordDataFixedFields *	inFields,
+		unsigned int *						outPriority,
+		unsigned int *						outWeight,
+		unsigned int *						outPort );
+static void
+	SRVRecordDataFixedFieldsSet(
+		SRVRecordDataFixedFields *	inFields,
+		uint16_t					inPriority,
+		uint16_t					inWeight,
+		uint16_t					inPort );
+static void
+	SOARecordDataFixedFieldsGet(
+		const SOARecordDataFixedFields *	inFields,
+		uint32_t *							outSerial,
+		uint32_t *							outRefresh,
+		uint32_t *							outRetry,
+		uint32_t *							outExpire,
+		uint32_t *							outMinimum );
+static void
+	SOARecordDataFixedFieldsSet(
+		SOARecordDataFixedFields *	inFields,
+		uint32_t					inSerial,
+		uint32_t					inRefresh,
+		uint32_t					inRetry,
+		uint32_t					inExpire,
+		uint32_t					inMinimum );
+static OSStatus	CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus	CreateTXTRecordDataFromString( const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen );
+static OSStatus
+	CreateNSECRecordData(
+		const uint8_t *	inNextDomainName,
+		uint8_t **		outPtr,
+		size_t *		outLen,
+		unsigned int	inTypeCount,
+		... );
+static OSStatus
+	AppendSOARecord(
+		DataBuffer *	inDB,
+		const uint8_t *	inNamePtr,
+		size_t			inNameLen,
+		uint16_t		inType,
+		uint16_t		inClass,
+		uint32_t		inTTL,
+		const uint8_t *	inMName,
+		const uint8_t *	inRName,
+		uint32_t		inSerial,
+		uint32_t		inRefresh,
+		uint32_t		inRetry,
+		uint32_t		inExpire,
+		uint32_t		inMinimumTTL,
+		size_t *		outLen );
+static OSStatus
+	CreateSOARecordData(
+		const uint8_t *	inMName,
+		const uint8_t *	inRName,
+		uint32_t		inSerial,
+		uint32_t		inRefresh,
+		uint32_t		inRetry,
+		uint32_t		inExpire,
+		uint32_t		inMinimumTTL,
+		uint8_t **		outPtr,
+		size_t *		outLen );
+static OSStatus
+	_DataBuffer_AppendDNSQuestion(
+		DataBuffer *	inDB,
+		const uint8_t *	inNamePtr,
+		size_t			inNameLen,
+		uint16_t		inType,
+		uint16_t		inClass );
+static OSStatus
+	_DataBuffer_AppendDNSRecord(
+		DataBuffer *	inDB,
+		const uint8_t *	inNamePtr,
+		size_t			inNameLen,
+		uint16_t		inType,
+		uint16_t		inClass,
+		uint32_t		inTTL,
+		const uint8_t *	inRDataPtr,
+		size_t			inRDataLen );
+static char *	_NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen );
 
-#define AddRmvString( X )		( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
-#define Unused( X )				(void)(X)
+#define Unused( X )		(void)(X)
 
 //===========================================================================================================================
-//	main
+//	MDNSCollider
 //===========================================================================================================================
 
-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 );
-	err = CLIParse( kGlobalOpts, kCLIFlags_None );
-	if( err ) exit( 1 );
-	
-	return( gExitCode );
-}
+typedef struct MDNSColliderPrivate *		MDNSColliderRef;
+
+typedef uint32_t		MDNSColliderProtocols;
+#define kMDNSColliderProtocol_None		0
+#define kMDNSColliderProtocol_IPv4		( 1 << 0 )
+#define kMDNSColliderProtocol_IPv6		( 1 << 1 )
+
+typedef void ( *MDNSColliderStopHandler_f )( void *inContext, OSStatus inError );
+
+static OSStatus	MDNSColliderCreate( dispatch_queue_t inQueue, MDNSColliderRef *outCollider );
+static OSStatus	MDNSColliderStart( MDNSColliderRef inCollider );
+static void		MDNSColliderStop( MDNSColliderRef inCollider );
+static void		MDNSColliderSetProtocols( MDNSColliderRef inCollider, MDNSColliderProtocols inProtocols );
+static void		MDNSColliderSetInterfaceIndex( MDNSColliderRef inCollider, uint32_t inInterfaceIndex );
+static OSStatus	MDNSColliderSetProgram( MDNSColliderRef inCollider, const char *inProgramStr );
+static void
+	MDNSColliderSetStopHandler(
+		MDNSColliderRef				inCollider,
+		MDNSColliderStopHandler_f	inStopHandler,
+		void *						inStopContext );
+static OSStatus
+	MDNSColliderSetRecord(
+		MDNSColliderRef	inCollider,
+		const uint8_t *	inName,
+		uint16_t		inType,
+		const void *	inRDataPtr,
+		size_t			inRDataLen );
+static CFTypeID	MDNSColliderGetTypeID( void );
 
 //===========================================================================================================================
-//	VersionOptionCallback
+//	ServiceBrowser
 //===========================================================================================================================
 
-static OSStatus	VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
+typedef struct ServiceBrowserPrivate *		ServiceBrowserRef;
+typedef struct ServiceBrowserResults		ServiceBrowserResults;
+typedef struct SBRDomain					SBRDomain;
+typedef struct SBRServiceType				SBRServiceType;
+typedef struct SBRServiceInstance			SBRServiceInstance;
+typedef struct SBRIPAddress					SBRIPAddress;
+
+typedef void ( *ServiceBrowserCallback_f )( ServiceBrowserResults *inResults, OSStatus inError, void *inContext );
+
+struct ServiceBrowserResults
 {
-	const char *		srcVers;
-#if( MDNSRESPONDER_PROJECT )
-	char				srcStr[ 16 ];
-#endif
-	
+	SBRDomain *		domainList;	// List of domains in which services were found.
+};
+
+struct SBRDomain
+{
+	SBRDomain *				next;		// Next domain in list.
+	char *					name;		// Name of domain represented by this object.
+	SBRServiceType *		typeList;	// List of service types in this domain.
+};
+
+struct SBRServiceType
+{
+	SBRServiceType *			next;			// Next service type in list.
+	char *						name;			// Name of service type represented by this object.
+	SBRServiceInstance *		instanceList;	// List of service instances of this service type.
+};
+
+struct SBRServiceInstance
+{
+	SBRServiceInstance *		next;			// Next service instance in list.
+	char *						name;			// Name of service instance represented by this object.
+	char *						hostname;		// Target from service instance's SRV record.
+	uint32_t					ifIndex;		// Index of interface over which this service instance was discovered.
+	uint16_t					port;			// Port from service instance's SRV record.
+	uint8_t *					txtPtr;			// Service instance's TXT record data.
+	size_t						txtLen;			// Service instance's TXT record data length.
+	SBRIPAddress *				ipaddrList;		// List of IP addresses that the hostname resolved to.
+	uint64_t					discoverTimeUs;	// Time it took to discover this service instance in microseconds.
+	uint64_t					resolveTimeUs;	// Time it took to resolve this service instance in microseconds.
+};
+
+struct SBRIPAddress
+{
+	SBRIPAddress *		next;			// Next IP address in list.
+	sockaddr_ip			sip;			// IPv4 or IPv6 address.
+	uint64_t			resolveTimeUs;	// Time it took to resolve this IP address in microseconds.
+};
+
+static CFTypeID	ServiceBrowserGetTypeID( void );
+static OSStatus
+	ServiceBrowserCreate(
+		dispatch_queue_t	inQueue,
+		uint32_t			inInterfaceIndex,
+		const char *		inDomain,
+		unsigned int		inBrowseTimeSecs,
+		Boolean				inIncludeAWDL,
+		ServiceBrowserRef *	outBrowser );
+static void		ServiceBrowserStart( ServiceBrowserRef inBrowser );
+static OSStatus	ServiceBrowserAddServiceType( ServiceBrowserRef inBrowser, const char *inServiceType );
+static void
+	ServiceBrowserSetCallback(
+		ServiceBrowserRef			inBrowser,
+		ServiceBrowserCallback_f	inCallback,
+		void *						inContext );
+static void		ServiceBrowserResultsRetain( ServiceBrowserResults *inResults );
+static void		ServiceBrowserResultsRelease( ServiceBrowserResults *inResults );
+
+#define ForgetServiceBrowserResults( X )		ForgetCustom( X, ServiceBrowserResultsRelease )
+
+//===========================================================================================================================
+//	main
+//===========================================================================================================================
+
+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 );
+	PrintFRegisterExtension( "du:arflags", PrintFAddRmvFlagsHandler, NULL );
+	CLIInit( argc, argv );
+	err = CLIParse( kGlobalOpts, kCLIFlags_None );
+	if( err ) exit( 1 );
+	
+	return( gExitCode );
+}
+
+//===========================================================================================================================
+//	VersionOptionCallback
+//===========================================================================================================================
+
+static OSStatus	VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
+{
+	const char *		srcVers;
+#if( MDNSRESPONDER_PROJECT )
+	char				srcStr[ 16 ];
+#endif
+	
 	Unused( inOption );
 	Unused( inArg );
 	Unused( inUnset );
@@ -2010,11 +2883,11 @@ static void DNSSD_API
 	
 	if( !context->printedHeader )
 	{
-		FPrintF( stdout, "%-26s  A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" );
+		FPrintF( stdout, "%-26s  %-14s IF %-20s %-20s Instance Name\n", "Timestamp", "Flags", "Domain", "Service Type" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%{du:time}  %-3s %5X %2d %-20s %-20s %s\n",
-		&now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
+	FPrintF( stdout, "%{du:time}  %{du:arflags} %2d %-20s %-20s %s\n",
+		&now, inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
 	
 	if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
 	
@@ -2117,7 +2990,8 @@ static void DNSSD_API
 	require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr );
 	
 	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 );
+		&now, ( inFlags & kDNSServiceFlagsAdd ) ? "Add" : "Rmv", inFullName, (int32_t) inInterfaceIndex,
+		inRDataPtr, (size_t) inRDataLen );
 	
 exit:
 	if( err ) exit( 1 );
@@ -2383,16 +3257,16 @@ static void DNSSD_API
 	}
 	else
 	{
-		addrStr = ( inSockAddr->sa_family == AF_INET ) ? "No Such Record (A)" : "No Such Record (AAAA)";
+		addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr;
 	}
 	
 	if( !context->printedHeader )
 	{
-		FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" );
+		FPrintF( stdout, "%-26s  %-14s IF %-32s %-38s %6s\n", "Timestamp", "Flags", "Hostname", "Address", "TTL" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%{du:time}  %s %5X %2d %-32s %-38s %6u\n",
-		&now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
+	FPrintF( stdout, "%{du:time}  %{du:arflags} %2d %-32s %-38s %6u\n",
+		&now, inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
 	
 	if( context->oneShotMode )
 	{
@@ -2621,11 +3495,7 @@ static void DNSSD_API
 			goto exit;
 	}
 	
-	if( inError == kDNSServiceErr_NoSuchRecord )
-	{
-		ASPrintF( &rdataStr, "No Such Record" );
-	}
-	else
+	if( inError != kDNSServiceErr_NoSuchRecord )
 	{
 		if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
 		if( !rdataStr )
@@ -2637,12 +3507,14 @@ static void DNSSD_API
 	
 	if( !context->printedHeader )
 	{
-		FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" );
+		FPrintF( stdout, "%-26s  %-14s IF %-32s %-5s %-5s %6s RData\n",
+			"Timestamp", "Flags", "Name", "Type", "Class", "TTL" );
 		context->printedHeader = true;
 	}
-	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 );
+	FPrintF( stdout, "%{du:time}  %{du:arflags} %2d %-32s %-5s %?-5s%?5u %6u %s\n",
+		&now, inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
+		( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL,
+		rdataStr ? rdataStr : kNoSuchRecordStr );
 	
 	if( context->oneShotMode )
 	{
@@ -2916,11 +3788,10 @@ static void DNSSD_API
 	
 	if( !context->printedHeader )
 	{
-		FPrintF( stdout, "%-26s  A/R Flags Service\n", "Timestamp" );
+		FPrintF( stdout, "%-26s  %-14s Service\n", "Timestamp", "Flags" );
 		context->printedHeader = true;
 	}
-	FPrintF( stdout, "%{du:time}  %-3s %5X %s.%s%s %?#m\n",
-		&now, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
+	FPrintF( stdout, "%{du:time}  %{du:arflags} %s.%s%s %?#m\n", &now, inFlags, inName, inType, inDomain, inError, inError );
 	
 	require_noerr_action_quiet( inError, exit, err = inError );
 	
@@ -3605,6 +4476,8 @@ exit:
 //	ReverseLookupCmd
 //===========================================================================================================================
 
+#define kIP6ARPADomainStr		"ip6.arpa."
+
 static void	ReverseLookupCmd( void )
 {
 	OSStatus					err;
@@ -3613,7 +4486,7 @@ static void	ReverseLookupCmd( void )
 	dispatch_source_t			signalSource	= NULL;
 	uint32_t					ipv4Addr;
 	uint8_t						ipv6Addr[ 16 ];
-	char						recordName[ ( 16 * 4 ) + 9 + 1 ];
+	char						recordName[ ( 16 * 4 ) + sizeof( kIP6ARPADomainStr ) ];
 	int							useMainConnection;
 	const char *				endPtr;
 	
@@ -3687,7 +4560,7 @@ static void	ReverseLookupCmd( void )
 			*dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ];
 			*dst++ = '.';
 		}
-		strcpy( dst, "ip6.arpa." );
+		strcpy_literal( dst, kIP6ARPADomainStr );
 		check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) );
 	}
 	else
@@ -3939,221 +4812,56 @@ static void DNSSD_API
 //	BrowseAllCmd
 //===========================================================================================================================
 
-typedef struct BrowseDomain			BrowseDomain;
-typedef struct BrowseType			BrowseType;
-typedef struct BrowseOp				BrowseOp;
-typedef struct BrowseInstance		BrowseInstance;
-typedef struct BrowseIPAddr			BrowseIPAddr;
+typedef struct BrowseAllConnection		BrowseAllConnection;
 
 typedef struct
 {
-	int						refCount;
-	DNSServiceRef			mainRef;
-	DNSServiceRef			domainsQuery;
-	const char *			domain;
-	BrowseDomain *			domainList;
-	char **					serviceTypes;
-	size_t					serviceTypesCount;
-	dispatch_source_t		exitTimer;
-	uint32_t				ifIndex;
-	int						pendingConnectCount;
-	int						browseTimeSecs;
-	int						maxConnectTimeSecs;
-	Boolean					includeAWDL;
-	Boolean					useColoredText;
+	ServiceBrowserRef			browser;				// Service browser.
+	ServiceBrowserResults *		results;				// Results from the service browser.
+	BrowseAllConnection *		connectionList;			// List of connections.
+	dispatch_source_t			connectionTimer;		// Timer for connection timeout.
+	int							connectionPendingCount;	// Number of pending connections.
+	int							connectionTimeoutSecs;	// Timeout value for connections in seconds.
 	
 }	BrowseAllContext;
 
-struct BrowseDomain
-{
-	BrowseDomain *			next;
-	char *					name;
-	DNSServiceRef			servicesQuery;
-	BrowseAllContext *		context;
-	BrowseType *			typeList;
-};
-
-struct BrowseType
-{
-	BrowseType *		next;
-	char *				name;
-	BrowseOp *			browseList;
-};
-
-struct BrowseOp
-{
-	BrowseOp *				next;
-	BrowseAllContext *		context;
-	DNSServiceRef			browse;
-	uint64_t				startTicks;
-	BrowseInstance *		instanceList;
-	uint32_t				ifIndex;
-	Boolean					isTCP;
-};
-
-struct BrowseInstance
-{
-	BrowseInstance *		next;
-	BrowseAllContext *		context;
-	char *					name;
-	uint64_t				foundTicks;
-	DNSServiceRef			resolve;
-	uint64_t				resolveStartTicks;
-	uint64_t				resolveDoneTicks;
-	DNSServiceRef			getAddr;
-	uint64_t				getAddrStartTicks;
-	BrowseIPAddr *			addrList;
-	uint8_t *				txtPtr;
-	size_t					txtLen;
-	char *					hostname;
-	uint32_t				ifIndex;
-	uint16_t				port;
-	Boolean					isTCP;
-};
-
-typedef enum
-{
-	kConnectStatus_None			= 0,
-	kConnectStatus_Pending		= 1,
-	kConnectStatus_Succeeded	= 2,
-	kConnectStatus_Failed		= 3
-	
-}	ConnectStatus;
-
-struct BrowseIPAddr
+struct BrowseAllConnection
 {
-	BrowseIPAddr *			next;
-	sockaddr_ip				sip;
-	int						refCount;
-	BrowseAllContext *		context;
-	uint64_t				foundTicks;
-	AsyncConnectionRef		connection;
-	ConnectStatus			connectStatus;
-	CFTimeInterval			connectTimeSecs;
-	OSStatus				connectError;
+	BrowseAllConnection *		next;				// Next connection object in list.
+	sockaddr_ip					sip;				// IPv4 or IPv6 address to connect to.
+	uint16_t					port;				// TCP port to connect to.
+	AsyncConnectionRef			asyncCnx;			// AsyncConnection object to handle the actual connection.
+	OSStatus					status;				// Status of connection. NoErr means connection succeeded.
+	CFTimeInterval				connectTimeSecs;	// Time it took to connect in seconds.
+	int32_t						refCount;			// This object's reference count.
+	BrowseAllContext *			context;			// Back pointer to parent context.
 };
 
-static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext );
-static void DNSSD_API
-	BrowseAllQueryDomainsCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inFullName,
-		uint16_t				inType,
-		uint16_t				inClass,
-		uint16_t				inRDataLen,
-		const void *			inRDataPtr,
-		uint32_t				inTTL,
-		void *					inContext );
-static void DNSSD_API
-	BrowseAllQueryCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inFullName,
-		uint16_t				inType,
-		uint16_t				inClass,
-		uint16_t				inRDataLen,
-		const void *			inRDataPtr,
-		uint32_t				inTTL,
-		void *					inContext );
-static void DNSSD_API
-	BrowseAllBrowseCallback(
-		DNSServiceRef		inSDRef,
-		DNSServiceFlags		inFlags,
-		uint32_t			inInterfaceIndex,
-		DNSServiceErrorType	inError,
-		const char *		inName,
-		const char *		inRegType,
-		const char *		inDomain,
-		void *				inContext );
-static void DNSSD_API
-	BrowseAllResolveCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inFullName,
-		const char *			inHostname,
-		uint16_t				inPort,
-		uint16_t				inTXTLen,
-		const unsigned char *	inTXTPtr,
-		void *					inContext );
-static void DNSSD_API
-	BrowseAllGAICallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inHostname,
-		const struct sockaddr *	inSockAddr,
-		uint32_t				inTTL,
-		void *					inContext );
-static void		BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
-static void		BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
-static void		BrowseAllStop( void *inContext );
-static void		BrowseAllExit( void *inContext );
-static OSStatus	BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName );
-static OSStatus	BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName );
-static void		BrowseAllContextRelease( BrowseAllContext *inContext );
-static OSStatus
-	BrowseAllAddServiceType(
-		BrowseAllContext *	inContext,
-		BrowseDomain *		inDomain,
-		const char *		inName,
-		uint32_t			inIfIndex,
-		Boolean				inIncludeAWDL );
-static OSStatus
-	BrowseAllRemoveServiceType(
-		BrowseAllContext *	inContext,
-		BrowseDomain *		inDomain,
-		const char *		inName,
-		uint32_t			inIfIndex );
-static OSStatus
-	BrowseAllAddServiceInstance(
-		BrowseAllContext *	inContext,
-		BrowseOp *			inBrowse,
-		const char *		inName,
-		const char *		inRegType,
-		const char *		inDomain,
-		uint32_t			inIfIndex );
-static OSStatus
-	BrowseAllRemoveServiceInstance(
-		BrowseAllContext *	inContext,
-		BrowseOp *			inBrowse,
-		const char *		inName,
-		uint32_t			inIfIndex );
-static OSStatus
-	BrowseAllAddIPAddress(
-		BrowseAllContext *		inContext,
-		BrowseInstance *		inInstance,
-		const struct sockaddr *	inSockAddr );
+static void	_BrowseAllContextFree( BrowseAllContext *inContext );
+static void	_BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext );
 static OSStatus
-	BrowseAllRemoveIPAddress(
+	_BrowseAllConnectionCreate(
+		const struct sockaddr *	inSockAddr,
+		uint16_t				inPort,
 		BrowseAllContext *		inContext,
-		BrowseInstance *		inInstance,
-		const struct sockaddr *	inSockAddr );
-static void	BrowseDomainFree( BrowseDomain *inDomain );
-static void	BrowseTypeFree( BrowseType *inType );
-static void	BrowseOpFree( BrowseOp *inBrowse );
-static void	BrowseInstanceFree( BrowseInstance *inInstance );
-static void	BrowseIPAddrRelease( BrowseIPAddr *inAddr );
-static void	BrowseIPAddrReleaseList( BrowseIPAddr *inList );
-
-#define ForgetIPAddressList( X )		ForgetCustom( X, BrowseIPAddrReleaseList )
-#define ForgetBrowseAllContext( X )		ForgetCustom( X, BrowseAllContextRelease )
+		BrowseAllConnection **	outConnection );
+static void _BrowseAllConnectionRetain( BrowseAllConnection *inConnection );
+static void	_BrowseAllConnectionRelease( BrowseAllConnection *inConnection );
+static void	_BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
+static void	_BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
+static void	_BrowseAllExit( void *inContext );
 
-#define kBrowseAllOpenFileMin		4096
+static Boolean	_IsServiceTypeTCP( const char *inServiceType );
 
 static void	BrowseAllCmd( void )
 {
 	OSStatus				err;
 	BrowseAllContext *		context = NULL;
+	size_t					i;
+	uint32_t				ifIndex;
+	char					ifName[ kInterfaceNameBufLen ];
 	
-	// Check command parameters.
+	// Check parameters.
 	
 	if( gBrowseAll_BrowseTimeSecs <= 0 )
 	{
@@ -4162,9 +4870,14 @@ static void	BrowseAllCmd( void )
 		goto exit;
 	}
 	
+	context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->connectionTimeoutSecs	= gBrowseAll_ConnectTimeout;
 #if( TARGET_OS_POSIX )
-	// Set open file minimum.
+	// Increase the open file descriptor limit for connection sockets.
 	
+	if( context->connectionTimeoutSecs > 0 )
 	{
 		struct rlimit		fdLimits;
 		
@@ -4172,9 +4885,9 @@ static void	BrowseAllCmd( void )
 		err = map_global_noerr_errno( err );
 		require_noerr( err, exit );
 		
-		if( fdLimits.rlim_cur < kBrowseAllOpenFileMin )
+		if( fdLimits.rlim_cur < 4096 )
 		{
-			fdLimits.rlim_cur = kBrowseAllOpenFileMin;
+			fdLimits.rlim_cur = 4096;
 			err = setrlimit( RLIMIT_NOFILE, &fdLimits );
 			err = map_global_noerr_errno( err );
 			require_noerr( err, exit );
@@ -4182,3071 +4895,2423 @@ static void	BrowseAllCmd( void )
 	}
 #endif
 	
-	context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
-	require_action( context, exit, err = kNoMemoryErr );
-	
-	context->refCount				= 1;
-	context->domain					= gBrowseAll_Domain;
-	context->serviceTypes			= gBrowseAll_ServiceTypes;
-	context->serviceTypesCount		= gBrowseAll_ServiceTypesCount;
-	gBrowseAll_ServiceTypes			= NULL;
-	gBrowseAll_ServiceTypesCount	= 0;
-	context->browseTimeSecs			= gBrowseAll_BrowseTimeSecs;
-	context->maxConnectTimeSecs		= gBrowseAll_MaxConnectTimeSecs;
-	context->includeAWDL			= gDNSSDFlag_IncludeAWDL ? true : false;
-#if( TARGET_OS_POSIX )
-	context->useColoredText			= isatty( STDOUT_FILENO ) ? true : false;
-#endif
-	
-	err = DNSServiceCreateConnection( &context->mainRef );
-	require_noerr( err, exit );
-	
-	err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
-	require_noerr( err, exit );
-	
-	// Set interface index.
+	// Get interface index.
 	
-	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
 	require_noerr_quiet( err, exit );
 	
-	BrowseAllPrintPrologue( context );
+	// Print prologue.
 	
-	if( context->domain )
+	FPrintF( stdout, "Interface:       %d (%s)\n",	(int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+	FPrintF( stdout, "Service types:   ");
+	if( gBrowseAll_ServiceTypesCount > 0 )
 	{
-		err = BrowseAllAddDomain( context, context->domain );
-		require_noerr( err, exit );
+		FPrintF( stdout, "%s", gBrowseAll_ServiceTypes[ 0 ] );
+		for( i = 1; i < gBrowseAll_ServiceTypesCount; ++i )
+		{
+			FPrintF( stdout, ", %s", gBrowseAll_ServiceTypes[ i ] );
+		}
+		FPrintF( stdout, "\n" );
 	}
 	else
 	{
-		DNSServiceRef		sdRef;
-		
-		sdRef = context->mainRef;
-		err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
-			"b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, BrowseAllQueryDomainsCallback, context );
-		require_noerr( err, exit );
-		
-		context->domainsQuery = sdRef;
+		FPrintF( stdout, "all services\n" );
 	}
+	FPrintF( stdout, "Domain:          %s\n", gBrowseAll_Domain ? gBrowseAll_Domain : "default domains" );
+	FPrintF( stdout, "Browse time:     %d second%?c\n", gBrowseAll_BrowseTimeSecs, gBrowseAll_BrowseTimeSecs != 1, 's' );
+	FPrintF( stdout, "Connect timeout: %d second%?c\n",
+		context->connectionTimeoutSecs, context->connectionTimeoutSecs != 1, 's' );
+	FPrintF( stdout, "IncludeAWDL:     %s\n", gDNSSDFlag_IncludeAWDL ? "yes" : "no" );
+	FPrintF( stdout, "Start time:      %{du:time}\n", NULL );
+	FPrintF( stdout, "---\n" );
+	
+	err = ServiceBrowserCreate( dispatch_get_main_queue(), ifIndex, gBrowseAll_Domain,
+		(unsigned int) gBrowseAll_BrowseTimeSecs, gDNSSDFlag_IncludeAWDL ? true : false, &context->browser );
+	require_noerr( err, exit );
 	
-	dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop );
+	for( i = 0; i < gBrowseAll_ServiceTypesCount; ++i )
+	{
+		err = ServiceBrowserAddServiceType( context->browser, gBrowseAll_ServiceTypes[ i ] );
+		require_noerr( err, exit );
+	}
+	ServiceBrowserSetCallback( context->browser, _BrowseAllServiceBrowserCallback, context );
+	ServiceBrowserStart( context->browser );
 	dispatch_main();
 	
 exit:
-	if( context ) BrowseAllContextRelease( context );
+	if( context ) _BrowseAllContextFree( context );
 	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	BrowseAllPrintPrologue
+//	_BrowseAllContextFree
 //===========================================================================================================================
 
-static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext )
+static void	_BrowseAllContextFree( BrowseAllContext *inContext )
 {
-	size_t		i;
-	char		ifName[ kInterfaceNameBufLen ];
-	
-	InterfaceIndexToName( inContext->ifIndex, ifName );
-	
-	FPrintF( stdout, "Interface:        %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Service types:    ");
-	if( inContext->serviceTypesCount > 0 )
-	{
-		FPrintF( stdout, "%s", inContext->serviceTypes[ 0 ] );
-		for( i = 1; i < inContext->serviceTypesCount; ++i ) FPrintF( stdout, ", %s", inContext->serviceTypes[ i ] );
-		FPrintF( stdout, "\n" );
-	}
-	else
-	{
-		FPrintF( stdout, "all services\n" );
-	}
-	FPrintF( stdout, "Domain:           %s\n", inContext->domain ? inContext->domain : "default domains" );
-	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, "IncludeAWDL:      %s\n", inContext->includeAWDL ? "YES" : "NO" );
-	FPrintF( stdout, "Start time:       %{du:time}\n", NULL );
-	FPrintF( stdout, "---\n" );
+	check( !inContext->browser );
+	check( !inContext->connectionTimer );
+	check( !inContext->connectionList );
+	ForgetServiceBrowserResults( &inContext->results );
+	free( inContext );
 }
 
 //===========================================================================================================================
-//	BrowseAllQueryDomainsCallback
+//	_BrowseAllServiceBrowserCallback
 //===========================================================================================================================
 
-static void DNSSD_API
-	BrowseAllQueryDomainsCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inFullName,
-		uint16_t				inType,
-		uint16_t				inClass,
-		uint16_t				inRDataLen,
-		const void *			inRDataPtr,
-		uint32_t				inTTL,
-		void *					inContext )
+#define kDiscardProtocolPort		9
+
+static void	_BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext )
 {
 	OSStatus						err;
 	BrowseAllContext * const		context = (BrowseAllContext *) inContext;
-	char							domainStr[ kDNSServiceMaxDomainName ];
+	SBRDomain *						domain;
+	SBRServiceType *				type;
+	SBRServiceInstance *			instance;
+	SBRIPAddress *					ipaddr;
 	
-	Unused( inSDRef );
-	Unused( inInterfaceIndex );
-	Unused( inFullName );
-	Unused( inType );
-	Unused( inClass );
-	Unused( inTTL );
+	Unused( inError );
 	
-	err = inError;
-	require_noerr( err, exit );
+	require_action( inResults, exit, err = kUnexpectedErr );
 	
-	err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
-	require_noerr( err, exit );
+	check( !context->results );
+	context->results = inResults;
+	ServiceBrowserResultsRetain( context->results );
 	
-	if( inFlags & kDNSServiceFlagsAdd )
+	check( context->connectionPendingCount == 0 );
+	if( context->connectionTimeoutSecs > 0 )
 	{
-		err = BrowseAllAddDomain( context, domainStr );
-		if( err == kDuplicateErr ) err = kNoErr;
+		BrowseAllConnection *			connection;
+		BrowseAllConnection **			connectionPtr = &context->connectionList;
+		char							destination[ kSockAddrStringMaxSize ];
+		
+		for( domain = context->results->domainList; domain; domain = domain->next )
+		{
+			for( type = domain->typeList; type; type = type->next )
+			{
+				if( !_IsServiceTypeTCP( type->name ) ) continue;
+				for( instance = type->instanceList; instance; instance = instance->next )
+				{
+					if( instance->port == kDiscardProtocolPort ) continue;
+					for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+					{
+						err = _BrowseAllConnectionCreate( &ipaddr->sip.sa, instance->port, context, &connection );
+						require_noerr( err, exit );
+						
+						*connectionPtr = connection;
+						 connectionPtr = &connection->next;
+						
+						err = SockAddrToString( &ipaddr->sip, kSockAddrStringFlagsNoPort, destination );
+						check_noerr( err );
+						if( !err )
+						{
+							err = AsyncConnection_Connect( &connection->asyncCnx, destination, -instance->port,
+								kAsyncConnectionFlag_P2P, kAsyncConnectionNoTimeout,
+								kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
+								_BrowseAllConnectionProgress, connection, _BrowseAllConnectionHandler, connection,
+								dispatch_get_main_queue() );
+							check_noerr( err );
+						}
+						if( !err )
+						{
+							_BrowseAllConnectionRetain( connection );
+							connection->status = kInProgressErr;
+							++context->connectionPendingCount;
+						}
+						else
+						{
+							connection->status = err;
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	if( context->connectionPendingCount > 0 )
+	{
+		check( !context->connectionTimer );
+		err = DispatchTimerCreate( dispatch_time_seconds( context->connectionTimeoutSecs ), DISPATCH_TIME_FOREVER,
+			100 * kNanosecondsPerMillisecond, NULL, _BrowseAllExit, NULL, context, &context->connectionTimer );
 		require_noerr( err, exit );
+		dispatch_resume( context->connectionTimer );
 	}
 	else
 	{
-		err = BrowseAllRemoveDomain( context, domainStr );
-		if( err == kNotFoundErr ) err = kNoErr;
-		require_noerr( err, exit );
+		dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit );
 	}
+	err = kNoErr;
 	
 exit:
+	ForgetCF( &context->browser );
 	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	BrowseAllQueryCallback
+//	_BrowseAllConnectionCreate
 //===========================================================================================================================
 
-static void DNSSD_API
-	BrowseAllQueryCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inFullName,
-		uint16_t				inType,
-		uint16_t				inClass,
-		uint16_t				inRDataLen,
-		const void *			inRDataPtr,
-		uint32_t				inTTL,
-		void *					inContext )
+static OSStatus
+	_BrowseAllConnectionCreate(
+		const struct sockaddr *	inSockAddr,
+		uint16_t				inPort,
+		BrowseAllContext *		inContext,
+		BrowseAllConnection **	outConnection )
 {
 	OSStatus					err;
-	BrowseDomain * const		domain			= (BrowseDomain *) inContext;
-	const uint8_t *				firstLabel;
-	const uint8_t *				secondLabel;
-	char *						serviceTypeStr	= NULL;
-	const uint8_t * const		end				= ( (uint8_t * ) inRDataPtr ) + inRDataLen;
-	
-	Unused( inSDRef );
-	Unused( inFullName );
-	Unused( inTTL );
-	Unused( inType );
-	Unused( inClass );
-	
-	err = inError;
-	require_noerr( err, exit );
-	
-	check( inType	== kDNSServiceType_PTR );
-	check( inClass	== kDNSServiceClass_IN );
-	require_action( inRDataLen > 0, exit, err = kSizeErr );
-	
-	firstLabel = inRDataPtr;
-	require_action_quiet( ( firstLabel + 1 + firstLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
-	
-	secondLabel = firstLabel + 1 + firstLabel[ 0 ];
-	require_action_quiet( ( secondLabel + 1 + secondLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
-	
-	ASPrintF( &serviceTypeStr, "%#s.%#s", firstLabel, secondLabel );
-	require_action( serviceTypeStr, exit, err = kNoMemoryErr );
-	
-	if( inFlags & kDNSServiceFlagsAdd )
-	{
-		err = BrowseAllAddServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex, false );
-		if( err == kDuplicateErr ) err = kNoErr;
-		require_noerr( err, exit );
-	}
-	else
-	{
-		err = BrowseAllRemoveServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex );
-		if( err == kNotFoundErr ) err = kNoErr;
-		require_noerr( err, exit );
-	}
-	
-exit:
-	FreeNullSafe( serviceTypeStr );
-}
-
-//===========================================================================================================================
-//	BrowseAllBrowseCallback
-//===========================================================================================================================
-
-static void DNSSD_API
-	BrowseAllBrowseCallback(
-		DNSServiceRef		inSDRef,
-		DNSServiceFlags		inFlags,
-		uint32_t			inInterfaceIndex,
-		DNSServiceErrorType	inError,
-		const char *		inName,
-		const char *		inRegType,
-		const char *		inDomain,
-		void *				inContext )
-{
-	OSStatus				err;
-	BrowseOp * const		browse = (BrowseOp *) inContext;
+	BrowseAllConnection *		obj;
 	
-	Unused( inSDRef );
+	obj = (BrowseAllConnection *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
 	
-	err = inError;
-	require_noerr( err, exit );
+	obj->refCount	= 1;
+	SockAddrCopy( inSockAddr, &obj->sip );
+	obj->port		= inPort;
+	obj->context	= inContext;
 	
-	if( inFlags & kDNSServiceFlagsAdd )
-	{
-		err = BrowseAllAddServiceInstance( browse->context, browse, inName, inRegType, inDomain, inInterfaceIndex );
-		if( err == kDuplicateErr ) err = kNoErr;
-		require_noerr( err, exit );
-	}
-	else
-	{
-		err = BrowseAllRemoveServiceInstance( browse->context, browse, inName, inInterfaceIndex );
-		if( err == kNotFoundErr ) err = kNoErr;
-		require_noerr( err, exit );
-	}
+	*outConnection = obj;
+	err = kNoErr;
 	
 exit:
-	return;
+	return( err );
 }
 
 //===========================================================================================================================
-//	BrowseAllResolveCallback
+//	_BrowseAllConnectionRetain
 //===========================================================================================================================
 
-static void DNSSD_API
-	BrowseAllResolveCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inFullName,
-		const char *			inHostname,
-		uint16_t				inPort,
-		uint16_t				inTXTLen,
-		const unsigned char *	inTXTPtr,
-		void *					inContext )
+static void _BrowseAllConnectionRetain( BrowseAllConnection *inConnection )
 {
-	OSStatus					err;
-	const uint64_t				nowTicks	= UpTicks();
-	BrowseInstance * const		instance	= (BrowseInstance *) inContext;
-	
-	Unused( inSDRef );
-	Unused( inFlags );
-	Unused( inInterfaceIndex );
-	Unused( inFullName );
-	
-	err = inError;
-	require_noerr( err, exit );
-	
-	if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
-	{
-		FreeNullSafe( instance->txtPtr );
-		instance->txtPtr = malloc( inTXTLen );
-		require_action( instance->txtPtr, exit, err = kNoMemoryErr );
-		
-		memcpy( instance->txtPtr, inTXTPtr, inTXTLen );
-		instance->txtLen = inTXTLen;
-	}
-	
-	instance->port = ntohs( inPort );
-	
-	if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
-	{
-		DNSServiceRef		sdRef;
-		
-		if( !instance->hostname ) instance->resolveDoneTicks = nowTicks;
-		FreeNullSafe( instance->hostname );
-		instance->hostname = strdup( inHostname );
-		require_action( instance->hostname, exit, err = kNoMemoryErr );
-		
-		DNSServiceForget( &instance->getAddr );
-		ForgetIPAddressList( &instance->addrList );
-		
-		sdRef = instance->context->mainRef;
-		instance->getAddrStartTicks = UpTicks();
-		err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
-			kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, BrowseAllGAICallback, instance );
-		require_noerr( err, exit );
-		
-		instance->getAddr = sdRef;
-	}
-	
-exit:
-	if( err ) exit( 1 );
+	++inConnection->refCount;
 }
 
 //===========================================================================================================================
-//	BrowseAllGAICallback
+//	_BrowseAllConnectionRelease
 //===========================================================================================================================
 
-static void DNSSD_API
-	BrowseAllGAICallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inHostname,
-		const struct sockaddr *	inSockAddr,
-		uint32_t				inTTL,
-		void *					inContext )
+static void	_BrowseAllConnectionRelease( BrowseAllConnection *inConnection )
 {
-	OSStatus					err;
-	BrowseInstance * const		instance = (BrowseInstance *) inContext;
-	
-	Unused( inSDRef );
-	Unused( inInterfaceIndex );
-	Unused( inHostname );
-	Unused( inTTL );
-	
-	err = inError;
-	require_noerr( err, exit );
-	
-	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
-	{
-		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
-		goto exit;
-	}
-	
-	if( inFlags & kDNSServiceFlagsAdd )
-	{
-		err = BrowseAllAddIPAddress( instance->context, instance, inSockAddr );
-		if( err == kDuplicateErr ) err = kNoErr;
-		require_noerr( err, exit );
-	}
-	else
-	{
-		err = BrowseAllRemoveIPAddress( instance->context, instance, inSockAddr );
-		if( err == kNotFoundErr ) err = kNoErr;
-		require_noerr( err, exit );
-	}
-	
-exit:
-	return;
+	if( --inConnection->refCount == 0 ) free( inConnection );
 }
 
 //===========================================================================================================================
-//	BrowseAllConnectionProgress
+//	_BrowseAllConnectionProgress
 //===========================================================================================================================
 
-static void	BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
+static void	_BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
 {
-	BrowseIPAddr * const		addr = (BrowseIPAddr *) inArg;
+	BrowseAllConnection * const		connection = (BrowseAllConnection *) inArg;
 	
 	if( inPhase == kAsyncConnectionPhase_Connected )
 	{
 		const AsyncConnectedInfo * const		info = (AsyncConnectedInfo *) inDetails;
 		
-		addr->connectTimeSecs = info->connectSecs;
+		connection->connectTimeSecs = info->connectSecs;
 	}
 }
 
 //===========================================================================================================================
-//	BrowseAllConnectionHandler
+//	_BrowseAllConnectionHandler
 //===========================================================================================================================
 
-static void	BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
+static void	_BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
 {
-	BrowseIPAddr * const			addr	= (BrowseIPAddr *) inArg;
-	BrowseAllContext * const		context	= addr->context;
-	
-	if( inError )
-	{
-		addr->connectStatus	= kConnectStatus_Failed;
-		addr->connectError	= inError;
-	}
-	else
-	{
-		addr->connectStatus = kConnectStatus_Succeeded;
-	}
-	
-	check( context->pendingConnectCount > 0 );
-	if( --context->pendingConnectCount == 0 )
-	{
-		if( context->exitTimer )
-		{
-			dispatch_source_forget( &context->exitTimer );
-			dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
-		}
-	}
+	BrowseAllConnection * const		connection	= (BrowseAllConnection *) inArg;
+	BrowseAllContext * const		context		= connection->context;
 	
+	connection->status = inError;
 	ForgetSocket( &inSock );
-	BrowseIPAddrRelease( addr );
-}
-
-//===========================================================================================================================
-//	BrowseAllStop
-//===========================================================================================================================
-
-static void	BrowseAllStop( void *inContext )
-{
-	OSStatus						err;
-	BrowseAllContext * const		context = (BrowseAllContext *) inContext;
-	BrowseDomain *					domain;
-	BrowseType *					type;
-	BrowseOp *						browse;
-	BrowseInstance *				instance;
-	
-	DNSServiceForget( &context->domainsQuery );
-	for( domain = context->domainList; domain; domain = domain->next )
+	if( context )
 	{
-		DNSServiceForget( &domain->servicesQuery );
-		for( type = domain->typeList; type; type = type->next )
+		check( context->connectionPendingCount > 0 );
+		if( ( --context->connectionPendingCount == 0 ) && context->connectionTimer )
 		{
-			for( browse = type->browseList; browse; browse = browse->next )
-			{
-				DNSServiceForget( &browse->browse );
-				for( instance = browse->instanceList; instance; instance = instance->next )
-				{
-					DNSServiceForget( &instance->resolve );
-					DNSServiceForget( &instance->getAddr );
-				}
-			}
+			dispatch_source_forget( &context->connectionTimer );
+			dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit );
 		}
 	}
-	DNSServiceForget( &context->mainRef );
-	
-	if( ( context->pendingConnectCount > 0 ) && ( context->maxConnectTimeSecs > 0 ) )
-	{
-		check( !context->exitTimer );
-		err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER,
-			100 * kNanosecondsPerMillisecond, NULL, BrowseAllExit, NULL, context, &context->exitTimer );
-		require_noerr( err, exit );
-		dispatch_resume( context->exitTimer );
-	}
-	else
-	{
-		dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
-	}
-	err = kNoErr;
-	
-exit:
-	if( err ) exit( 1 );
+	_BrowseAllConnectionRelease( connection );
 }
 
 //===========================================================================================================================
-//	BrowseAllExit
+//	_BrowseAllExit
 //===========================================================================================================================
 
-#define kStatusStr_CouldConnect					"connected"
-#define kStatusStr_CouldConnectColored			kANSIGreen kStatusStr_CouldConnect kANSINormal
-#define kStatusStr_CouldNotConnect				"could not connect"
-#define kStatusStr_CouldNotConnectColored		kANSIRed kStatusStr_CouldNotConnect kANSINormal
-#define kStatusStr_NoConnectionAttempted		"no connection attempted"
-#define kStatusStr_Unknown						"unknown"
-
 #define Indent( X )		( (X) * 4 ), ""
 
-static void	BrowseAllExit( void *inContext )
+static void	_BrowseAllExit( void *inContext )
 {
-	BrowseAllContext * const		context = (BrowseAllContext *) inContext;
-	BrowseDomain *					domain;
-	BrowseType *					type;
-	BrowseOp *						browse;
-	BrowseInstance *				instance;
-	BrowseIPAddr *					addr;
+	BrowseAllContext * const		context		= (BrowseAllContext *) inContext;
+	SBRDomain *						domain;
+	SBRServiceType *				type;
+	SBRServiceInstance *			instance;
+	SBRIPAddress *					ipaddr;
+	char							textBuf[ 512 ];
+#if( TARGET_OS_POSIX )
+	const Boolean					useColor	= isatty( STDOUT_FILENO ) ? true : false;
+#endif
 	
-	dispatch_source_forget( &context->exitTimer );
+	dispatch_source_forget( &context->connectionTimer );
 	
-	for( domain = context->domainList; domain; domain = domain->next )
+	for( domain = context->results->domainList; domain; domain = domain->next )
 	{
 		FPrintF( stdout, "%s\n\n", domain->name );
 		
 		for( type = domain->typeList; type; type = type->next )
 		{
-			const char *		desc;
+			const char *		description;
+			const Boolean		serviceTypeIsTCP = _IsServiceTypeTCP( type->name );
 			
-			desc = ServiceTypeDescription( type->name );
-			if( desc )	FPrintF( stdout, "%*s" "%s (%s)\n\n",	Indent( 1 ), desc, type->name );
-			else		FPrintF( stdout, "%*s" "%s\n\n",		Indent( 1 ), type->name );
+			description = ServiceTypeDescription( type->name );
+			if( description )	FPrintF( stdout, "%*s" "%s (%s)\n\n",	Indent( 1 ), description, type->name );
+			else				FPrintF( stdout, "%*s" "%s\n\n",		Indent( 1 ), type->name );
 			
-			for( browse = type->browseList; browse; browse = browse->next )
+			for( instance = type->instanceList; instance; instance = instance->next )
 			{
-				for( instance = browse->instanceList; instance; instance = instance->next )
+				char *				dst = textBuf;
+				char * const		lim = &textBuf[ countof( textBuf ) ];
+				char				ifname[ IF_NAMESIZE + 1 ];
+				
+				SNPrintF_Add( &dst, lim, "%s via ", instance->name );
+				if( instance->ifIndex == 0 )
 				{
-					char		ifname[ IF_NAMESIZE + 1 ];
+					SNPrintF_Add( &dst, lim, "the Internet" );
+				}
+				else if( if_indextoname( instance->ifIndex, ifname ) )
+				{
+					NetTransportType		netType;
 					
-					FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
-					if( instance->ifIndex == 0 )
-					{
-						FPrintF( stdout, "the Internet" );
-					}
-					else if( if_indextoname( instance->ifIndex, ifname ) )
-					{
-						NetTransportType		netType;
-						
-						SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-							&netType );
-						FPrintF( stdout, "%s (%s)",
-							( netType == kNetTransportType_Ethernet ) ? "ethernet" : NetTransportTypeToString( netType ),
-							ifname );
-					}
-					else
-					{
-						FPrintF( stdout, "interface index %u", instance->ifIndex );
-					}
-					FPrintF( stdout, "\n\n" );
+					SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &netType );
+					SNPrintF_Add( &dst, lim, "%s (%s)",
+						( netType == kNetTransportType_Ethernet ) ? "Ethernet" : NetTransportTypeToString( netType ),
+						ifname );
+				}
+				else
+				{
+					SNPrintF_Add( &dst, lim, "interface index %u", instance->ifIndex );
+				}
+				FPrintF( stdout, "%*s" "%-55s %4llu.%03llu ms\n\n",
+					Indent( 2 ), textBuf, instance->discoverTimeUs / 1000, instance->discoverTimeUs % 1000 );
+				
+				if( instance->hostname )
+				{
+					SNPrintF( textBuf, sizeof( textBuf ), "%s:%u", instance->hostname, instance->port );
+					FPrintF( stdout, "%*s" "%-51s %4llu.%03llu ms\n",
+						Indent( 3 ), textBuf, instance->resolveTimeUs / 1000, instance->resolveTimeUs % 1000 );
+				}
+				else
+				{
+					FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
+				}
+				
+				for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+				{
+					BrowseAllConnection *		conn;
+					BrowseAllConnection **		connPtr;
 					
-					if( instance->hostname )
-					{
-						char		buffer[ 256 ];
-						
-						SNPrintF( buffer, sizeof( buffer ), "%s:%u", instance->hostname, instance->port );
-						FPrintF( stdout, "%*s" "%-51s %4llu ms\n", Indent( 3 ), buffer,
-							UpTicksToMilliseconds( instance->resolveDoneTicks - instance->resolveStartTicks ) );
-					}
-					else
-					{
-						FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
-					}
+					FPrintF( stdout, "%*s" "%-##47a %4llu.%03llu ms",
+						Indent( 4 ), &ipaddr->sip.sa, ipaddr->resolveTimeUs / 1000, ipaddr->resolveTimeUs % 1000 );
 					
-					for( addr = instance->addrList; addr; addr = addr->next )
+					conn = NULL;
+					if( serviceTypeIsTCP && ( instance->port != kDiscardProtocolPort ) )
 					{
-						AsyncConnection_Forget( &addr->connection );
-						
-						if( addr->connectStatus == kConnectStatus_Pending )
-						{
-							addr->connectStatus	= kConnectStatus_Failed;
-							addr->connectError	= kTimeoutErr;
-						}
-						
-						FPrintF( stdout, "%*s" "%-##47a %4llu ms", Indent( 4 ),
-							&addr->sip.sa, UpTicksToMilliseconds( addr->foundTicks - instance->getAddrStartTicks ) );
-						if( context->maxConnectTimeSecs <= 0 )
+						for( connPtr = &context->connectionList; ( conn = *connPtr ) != NULL; connPtr = &conn->next )
 						{
-							FPrintF( stdout, "\n" );
-							continue;
+							if( ( conn->port == instance->port ) &&
+								( SockAddrCompareAddr( &conn->sip, &ipaddr->sip ) == 0 ) ) break;
 						}
-						switch( addr->connectStatus )
+						if( conn )
 						{
-							case kConnectStatus_None:
-								FPrintF( stdout, " (%s)\n", kStatusStr_NoConnectionAttempted );
-								break;
-							
-							case kConnectStatus_Succeeded:
-								FPrintF( stdout, " (%s in %.2f ms)\n",
-									context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect,
-									addr->connectTimeSecs * 1000 );
-								break;
-							
-							case kConnectStatus_Failed:
-								FPrintF( stdout, " (%s: %m)\n",
-									context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect,
-									addr->connectError );
-								break;
-							
-							default:
-								FPrintF( stdout, " (%s)\n", kStatusStr_Unknown );
-								break;
+							if( conn->status == kInProgressErr ) conn->status = kTimeoutErr;
+							*connPtr = conn->next;
+							conn->context = NULL;
+							AsyncConnection_Forget( &conn->asyncCnx );
 						}
 					}
 					
-					FPrintF( stdout, "\n" );
-					if( instance->txtLen == 0 ) continue;
-					
-					FPrintF( stdout, "%*s" "TXT record:\n", Indent( 3 ) );
-					if( instance->txtLen > 1 )
+					if( conn )
 					{
-						FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
+						if( conn->status == kNoErr )
+						{
+							FPrintF( stdout, " (%sconnected%s in %.3f ms)\n",
+								useColor ? kANSIGreen : "", useColor ? kANSINormal : "", conn->connectTimeSecs * 1000 );
+						}
+						else
+						{
+							FPrintF( stdout, " (%scould not connect%s: %m)\n",
+								useColor ? kANSIRed : "", useColor ? kANSINormal : "", conn->status );
+						}
+						_BrowseAllConnectionRelease( conn );
 					}
 					else
 					{
-						FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
+						FPrintF( stdout, " (no connection attempted)\n" );
 					}
-					FPrintF( stdout, "\n" );
 				}
+				
+				FPrintF( stdout, "\n" );
+				if( instance->txtLen == 0 ) continue;
+				
+				FPrintF( stdout, "%*s" "TXT record (%zu byte%?c):\n",
+					Indent( 3 ), instance->txtLen, instance->txtLen != 1, 's' );
+				if( instance->txtLen > 1 )
+				{
+					FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
+				}
+				else
+				{
+					FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
+				}
+				FPrintF( stdout, "\n" );
 			}
 			FPrintF( stdout, "\n" );
 		}
 	}
 	
-	while( ( domain = context->domainList ) != NULL )
-	{
-		context->domainList = domain->next;
-		BrowseDomainFree( domain );
-	}
-	
-	BrowseAllContextRelease( context );
+	_BrowseAllContextFree( context );
 	Exit( NULL );
 }
 
 //===========================================================================================================================
-//	BrowseAllAddDomain
+//	_IsServiceTypeTCP
 //===========================================================================================================================
 
-static OSStatus	BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName )
+static Boolean	_IsServiceTypeTCP( const char *inServiceType )
 {
 	OSStatus			err;
-	BrowseDomain *		domain;
-	BrowseDomain **		p;
-	BrowseDomain *		newDomain = NULL;
+	const uint8_t *		secondLabel;
+	uint8_t				name[ kDomainNameLengthMax ];
 	
-	for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+	err = DomainNameFromString( name, inServiceType, NULL );
+	if( !err )
 	{
-		if( strcasecmp( domain->name, inName ) == 0 ) break;
+		secondLabel = NextLabel( name );
+		if( secondLabel && DomainNameEqual( secondLabel, (const uint8_t *) "\x04" "_tcp" ) ) return( true );
 	}
-	require_action_quiet( !domain, exit, err = kDuplicateErr );
-	
-	newDomain = (BrowseDomain *) calloc( 1, sizeof( *newDomain ) );
-	require_action( newDomain, exit, err = kNoMemoryErr );
-	
-	++inContext->refCount;
-	newDomain->context = inContext;
-	
-	newDomain->name = strdup( inName );
-	require_action( newDomain->name, exit, err = kNoMemoryErr );
-	
-	if( inContext->serviceTypesCount > 0 )
-	{
-		size_t		i;
-		
-		for( i = 0; i < inContext->serviceTypesCount; ++i )
-		{
-			err = BrowseAllAddServiceType( inContext, newDomain, inContext->serviceTypes[ i ], inContext->ifIndex,
-				inContext->includeAWDL );
-			if( err == kDuplicateErr ) err = kNoErr;
-			require_noerr( err, exit );
-		}
-	}
-	else
-	{
-		char *				recordName;
-		DNSServiceFlags		flags;
-		DNSServiceRef		sdRef;
-		
-		ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
-		require_action( recordName, exit, err = kNoMemoryErr );
-		
-		flags = kDNSServiceFlagsShareConnection;
-		if( inContext->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
-		
-		sdRef = newDomain->context->mainRef;
-		err = DNSServiceQueryRecord( &sdRef, flags, inContext->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
-			BrowseAllQueryCallback, newDomain );
-		free( recordName );
-		require_noerr( err, exit );
-		
-		newDomain->servicesQuery = sdRef;
-	}
-	
-	*p = newDomain;
-	newDomain = NULL;
-	err = kNoErr;
-	
-exit:
-	if( newDomain ) BrowseDomainFree( newDomain );
-	return( err );
+	return( false );
 }
 
 //===========================================================================================================================
-//	BrowseAllRemoveDomain
+//	GetNameInfoCmd
 //===========================================================================================================================
 
-static OSStatus	BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName )
+const FlagStringPair		kGetNameInfoFlagStringPairs[] =
 {
-	OSStatus			err;
-	BrowseDomain *		domain;
-	BrowseDomain **		p;
+	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 ];
 	
-	for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+	err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
+	check_noerr( err );
+	if( err )
 	{
-		if( strcasecmp( domain->name, inName ) == 0 ) break;
+		FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+		goto exit;
 	}
 	
-	if( domain )
+	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 )
 	{
-		*p = domain->next;
-		BrowseDomainFree( domain );
-		err = kNoErr;
+		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
 	{
-		err = kNotFoundErr;
+		FPrintF( stdout, "host: %s\n", host );
+		FPrintF( stdout, "serv: %s\n", serv );
 	}
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "End time:   %{du:time}\n", &now );
 	
-	return( err );
+exit:
+	gExitCode = err ? 1 : 0;
 }
 
 //===========================================================================================================================
-//	BrowseAllContextRelease
+//	GetAddrInfoStressCmd
 //===========================================================================================================================
 
-static void	BrowseAllContextRelease( BrowseAllContext *inContext )
+typedef struct
 {
-	if( --inContext->refCount == 0 )
-	{
-		check( !inContext->domainsQuery );
-		check( !inContext->domainList );
-		check( !inContext->exitTimer );
-		check( !inContext->pendingConnectCount );
-		DNSServiceForget( &inContext->mainRef );
-		if( inContext->serviceTypes )
-		{
-			StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount );
-			inContext->serviceTypes			= NULL;
-			inContext->serviceTypesCount	= 0;
-		}
-		free( inContext );
-	}
-}
+	DNSServiceRef			mainRef;
+	DNSServiceRef			sdRef;
+	DNSServiceFlags			flags;
+	unsigned int			interfaceIndex;
+	unsigned int			connectionNumber;
+	unsigned int			requestCount;
+	unsigned int			requestCountMax;
+	unsigned int			requestCountLimit;
+	unsigned int			durationMinMs;
+	unsigned int			durationMaxMs;
+	
+}	GAIStressContext;
 
-//===========================================================================================================================
-//	BrowseAllAddServiceType
-//===========================================================================================================================
+static void	GetAddrInfoStressEvent( void *inContext );
+static void	DNSSD_API
+	GetAddrInfoStressCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext );
 
-static OSStatus
-	BrowseAllAddServiceType(
-		BrowseAllContext *	inContext,
-		BrowseDomain *		inDomain,
-		const char *		inName,
-		uint32_t			inIfIndex,
-		Boolean				inIncludeAWDL )
+static void	GetAddrInfoStressCmd( void )
 {
-	OSStatus			err;
-	DNSServiceRef		sdRef;
-	DNSServiceFlags		flags;
-	BrowseType *		type;
-	BrowseType **		typePtr;
-	BrowseType *		newType		= NULL;
-	BrowseOp *			browse;
-	BrowseOp **			browsePtr;
-	BrowseOp *			newBrowse	= NULL;
+	OSStatus				err;
+	GAIStressContext *		context = NULL;
+	int						i;
+	DNSServiceFlags			flags;
+	uint32_t				ifIndex;
+	char					ifName[ kInterfaceNameBufLen ];
 	
-	for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+	if( gGAIStress_TestDurationSecs < 0 )
 	{
-		if( strcasecmp( type->name, inName ) == 0 ) break;
+		FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
+		err = kParamErr;
+		goto exit;
 	}
-	if( !type )
+	if( gGAIStress_ConnectionCount <= 0 )
 	{
-		newType = (BrowseType *) calloc( 1, sizeof( *newType ) );
-		require_action( newType, exit, err = kNoMemoryErr );
-		
-		newType->name = strdup( inName );
-		require_action( newType->name, exit, err = kNoMemoryErr );
-		
-		type = newType;
+		FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
+		err = kParamErr;
+		goto exit;
 	}
-	
-	for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+	if( gGAIStress_DurationMinMs <= 0 )
 	{
-		if( browse->ifIndex == inIfIndex ) break;
+		FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs );
+		err = kParamErr;
+		goto exit;
+	}
+	if( gGAIStress_DurationMaxMs <= 0 )
+	{
+		FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs );
+		err = kParamErr;
+		goto exit;
+	}
+	if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs )
+	{
+		FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n",
+			gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs );
+		err = kParamErr;
+		goto exit;
+	}
+	if( gGAIStress_RequestCountMax <= 0 )
+	{
+		FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax );
+		err = kParamErr;
+		goto exit;
 	}
-	require_action_quiet( !browse, exit, err = kDuplicateErr );
-	
-	newBrowse = (BrowseOp *) calloc( 1, sizeof( *newBrowse ) );
-	require_action( newBrowse, exit, err = kNoMemoryErr );
-	
-	++inContext->refCount;
-	newBrowse->context	= inContext;
-	newBrowse->ifIndex	= inIfIndex;
-	if( stricmp_suffix( inName, "._tcp" ) == 0 ) newBrowse->isTCP = true;
 	
-	flags = kDNSServiceFlagsShareConnection;
-	if( inIncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+	// Set flags.
 	
-	newBrowse->startTicks = UpTicks();
+	flags = GetDNSSDFlagsFromOpts();
 	
-	sdRef = inContext->mainRef;
-	err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback,
-		newBrowse );
-	require_noerr( err, exit );
+	// Set interface index.
 	
-	newBrowse->browse = sdRef;
-	*browsePtr = newBrowse;
-	newBrowse = NULL;
+	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+	require_noerr_quiet( err, exit );
 	
-	if( newType )
+	for( i = 0; i < gGAIStress_ConnectionCount; ++i )
 	{
-		*typePtr = newType;
-		newType = NULL;
+		context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
+		require_action( context, exit, err = kNoMemoryErr );
+		
+		context->flags				= flags;
+		context->interfaceIndex		= ifIndex;
+		context->connectionNumber	= (unsigned int)( i + 1 );
+		context->requestCountMax	= (unsigned int) gGAIStress_RequestCountMax;
+		context->durationMinMs		= (unsigned int) gGAIStress_DurationMinMs;
+		context->durationMaxMs		= (unsigned int) gGAIStress_DurationMaxMs;
+		
+		dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+		context = NULL;
 	}
 	
-exit:
-	if( newBrowse )	BrowseOpFree( newBrowse );
-	if( newType )	BrowseTypeFree( newType );
-	return( err );
-}
-
-//===========================================================================================================================
-//	BrowseAllRemoveServiceType
-//===========================================================================================================================
-
-static OSStatus
-	BrowseAllRemoveServiceType(
-		BrowseAllContext *	inContext,
-		BrowseDomain *		inDomain,
-		const char *		inName,
-		uint32_t			inIfIndex )
-{
-	OSStatus			err;
-	BrowseType *		type;
-	BrowseType **		typePtr;
-	BrowseOp *			browse;
-	BrowseOp **			browsePtr;
-	
-	Unused( inContext );
-	
-	for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+	if( gGAIStress_TestDurationSecs > 0 )
 	{
-		if( strcasecmp( type->name, inName ) == 0 ) break;
+		dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
 	}
-	require_action_quiet( type, exit, err = kNotFoundErr );
 	
-	for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+	FPrintF( stdout, "Flags:                %#{flags}\n",	flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:            %d (%s)\n",		(int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+	FPrintF( stdout, "Test duration:        " );
+	if( gGAIStress_TestDurationSecs == 0 )
 	{
-		if( browse->ifIndex == inIfIndex ) break;
+		FPrintF( stdout, "∞\n" );
 	}
-	require_action_quiet( browse, exit, err = kNotFoundErr );
-	
-	*browsePtr = browse->next;
-	BrowseOpFree( browse );
-	if( !type->browseList )
+	else
 	{
-		*typePtr = type->next;
-		BrowseTypeFree( type );
+		FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
 	}
-	err = kNoErr;
+	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();
 	
 exit:
-	return( err );
+	FreeNullSafe( context );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	BrowseAllAddServiceInstance
+//	GetAddrInfoStressEvent
 //===========================================================================================================================
 
-static OSStatus
-	BrowseAllAddServiceInstance(
-		BrowseAllContext *	inContext,
-		BrowseOp *			inBrowse,
-		const char *		inName,
-		const char *		inRegType,
-		const char *		inDomain,
-		uint32_t			inIfIndex )
+#define kStressRandStrLen		5
+
+#define kLowercaseAlphaCharSet		"abcdefghijklmnopqrstuvwxyz"
+
+static void	GetAddrInfoStressEvent( void *inContext )
 {
-	OSStatus				err;
-	DNSServiceRef			sdRef;
-	BrowseInstance *		instance;
-	BrowseInstance **		p;
-	const uint64_t			nowTicks	= UpTicks();
-	BrowseInstance *		newInstance	= NULL;
+	GAIStressContext * const		context = (GAIStressContext *) inContext;
+	OSStatus						err;
+	DNSServiceRef					sdRef;
+	unsigned int					nextMs;
+	char							randomStr[ kStressRandStrLen + 1 ];
+	char							hostname[ kStressRandStrLen + 4 + 1 ];
+	Boolean							isConnectionNew	= false;
+	static Boolean					printedHeader	= false;
 	
-	for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
+	if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
 	{
-		if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+		DNSServiceForget( &context->mainRef );
+		context->sdRef				= NULL;
+		context->requestCount		= 0;
+		context->requestCountLimit	= RandomRange( 1, context->requestCountMax );
+		
+		err = DNSServiceCreateConnection( &context->mainRef );
+		require_noerr( err, exit );
+		
+		err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
+		require_noerr( err, exit );
+		
+		isConnectionNew = true;
 	}
-	require_action_quiet( !instance, exit, err = kDuplicateErr );
 	
-	newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) );
-	require_action( newInstance, exit, err = kNoMemoryErr );
+	RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
+	SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
 	
-	++inContext->refCount;
-	newInstance->context	= inContext;
-	newInstance->foundTicks	= nowTicks;
-	newInstance->ifIndex	= inIfIndex;
-	newInstance->isTCP		= inBrowse->isTCP;
+	nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
 	
-	newInstance->name = strdup( inName );
-	require_action( newInstance->name, exit, err = kNoMemoryErr );
+	if( !printedHeader )
+	{
+		FPrintF( stdout, "%-26s Conn  Hostname Dur (ms)\n", "Timestamp" );
+		printedHeader = true;
+	}
+	FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
+		NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
 	
-	sdRef = inContext->mainRef;
-	newInstance->resolveStartTicks = UpTicks();
-	err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
-		BrowseAllResolveCallback, newInstance );
+	DNSServiceForget( &context->sdRef );
+	sdRef = context->mainRef;
+	err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex,
+		kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL );
 	require_noerr( err, exit );
+	context->sdRef = sdRef;
 	
-	newInstance->resolve = sdRef;
-	*p = newInstance;
-	newInstance = NULL;
+	context->requestCount++;
+	
+	dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
 	
 exit:
-	if( newInstance ) BrowseInstanceFree( newInstance );
-	return( err );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	BrowseAllRemoveServiceInstance
+//	GetAddrInfoStressCallback
 //===========================================================================================================================
 
-static OSStatus
-	BrowseAllRemoveServiceInstance(
-		BrowseAllContext *	inContext,
-		BrowseOp *			inBrowse,
-		const char *		inName,
-		uint32_t			inIfIndex )
+static void DNSSD_API
+	GetAddrInfoStressCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext )
 {
-	OSStatus				err;
-	BrowseInstance *		instance;
-	BrowseInstance **		p;
-	
+	Unused( inSDRef );
+	Unused( inFlags );
+	Unused( inInterfaceIndex );
+	Unused( inError );
+	Unused( inHostname );
+	Unused( inSockAddr );
+	Unused( inTTL );
 	Unused( inContext );
-	
-	for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
-	{
-		if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
-	}
-	require_action_quiet( instance, exit, err = kNotFoundErr );
-	
-	*p = instance->next;
-	BrowseInstanceFree( instance );
-	err = kNoErr;
-	
-exit:
-	return( err );
 }
 
 //===========================================================================================================================
-//	BrowseAllAddIPAddress
+//	DNSQueryCmd
 //===========================================================================================================================
 
-#define kDiscardProtocolPort	9
+typedef struct
+{
+	sockaddr_ip				serverAddr;
+	uint64_t				sendTicks;
+	uint8_t *				msgPtr;
+	size_t					msgLen;
+	size_t					msgOffset;
+	const char *			name;
+	dispatch_source_t		readSource;
+	SocketRef				sock;
+	int						timeLimitSecs;
+	uint16_t				queryID;
+	uint16_t				type;
+	Boolean					haveTCPLen;
+	Boolean					useTCP;
+	Boolean					printRawRData;	// True if RDATA results are not to be formatted.
+	uint8_t					msgBuf[ 512 ];
+	
+}	DNSQueryContext;
+
+static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext );
+static void	DNSQueryReadHandler( void *inContext );
+static void	DNSQueryCancelHandler( void *inContext );
 
-static OSStatus
-	BrowseAllAddIPAddress(
-		BrowseAllContext *		inContext,
-		BrowseInstance *		inInstance,
-		const struct sockaddr *	inSockAddr )
+static void	DNSQueryCmd( void )
 {
-	OSStatus			err;
-	BrowseIPAddr *		addr;
-	BrowseIPAddr **		p;
-	const uint64_t		nowTicks	= UpTicks();
-	BrowseIPAddr *		newAddr		= NULL;
+	OSStatus				err;
+	DNSQueryContext *		context = NULL;
+	uint8_t *				msgPtr;
+	size_t					msgLen, sendLen;
 	
-	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+	// Check command parameters.
+	
+	if( gDNSQuery_TimeLimitSecs < -1 )
 	{
-		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
-		err = kTypeErr;
+		FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs );
+		err = kParamErr;
+		goto exit;
+	}
+	if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
+	{
+		FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
+		err = kParamErr;
 		goto exit;
 	}
 	
-	for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+	// Create context.
+	
+	context = (DNSQueryContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->name			= gDNSQuery_Name;
+	context->sock			= kInvalidSocketRef;
+	context->timeLimitSecs	= gDNSQuery_TimeLimitSecs;
+	context->queryID		= (uint16_t) Random32();
+	context->useTCP			= gDNSQuery_UseTCP	 ? true : false;
+	context->printRawRData	= gDNSQuery_RawRData ? true : false;
+	
+#if( TARGET_OS_DARWIN )
+	if( gDNSQuery_Server )
+#endif
+	{
+		err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+		require_noerr( err, exit );
+	}
+#if( TARGET_OS_DARWIN )
+	else
+	{
+		err = GetDefaultDNSServer( &context->serverAddr );
+		require_noerr( err, exit );
+	}
+#endif
+	if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
+	
+	err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
+	require_noerr( err, exit );
+	
+	// Write query message.
+	
+	check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+	
+	msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
+	err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
+		kDNSServiceClass_IN, &msgLen );
+	require_noerr( err, exit );
+	check( msgLen <= UINT16_MAX );
+	
+	if( context->useTCP )
+	{
+		WriteBig16( context->msgBuf, msgLen );
+		sendLen = 2 + msgLen;
+	}
+	else
 	{
-		if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break;
+		sendLen = msgLen;
 	}
-	require_action_quiet( !addr, exit, err = kDuplicateErr );
 	
-	newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) );
-	require_action( newAddr, exit, err = kNoMemoryErr );
+	DNSQueryPrintPrologue( context );
 	
-	++inContext->refCount;
-	newAddr->refCount	= 1;
-	newAddr->context	= inContext;
-	newAddr->foundTicks	= nowTicks;
-	SockAddrCopy( inSockAddr, &newAddr->sip.sa );
+	if( gDNSQuery_Verbose )
+	{
+		FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
+		FPrintF( stdout, "---\n" );
+	}
 	
-	if( ( inContext->maxConnectTimeSecs > 0 ) && inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
+	if( context->useTCP )
 	{
-		char		destination[ kSockAddrStringMaxSize ];
+		// Create TCP socket.
 		
-		err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination );
+		context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
+		err = map_socket_creation_errno( context->sock );
 		require_noerr( err, exit );
 		
-		err = AsyncConnection_Connect( &newAddr->connection, destination, -inInstance->port, kAsyncConnectionFlag_P2P,
-			kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
-			BrowseAllConnectionProgress, newAddr, BrowseAllConnectionHandler, newAddr, dispatch_get_main_queue() );
+		err = SocketConnect( context->sock, &context->serverAddr, 5 );
 		require_noerr( err, exit );
+	}
+	else
+	{
+		// Create UDP socket.
 		
-		++newAddr->refCount;
-		newAddr->connectStatus = kConnectStatus_Pending;
-		++inContext->pendingConnectCount;
+		err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
+		require_noerr( err, exit );
 	}
 	
-	*p = newAddr;
-	newAddr = NULL;
-	err = kNoErr;
+	context->sendTicks = UpTicks();
+	err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+	require_noerr( err, exit );
 	
-exit:
-	if( newAddr ) BrowseIPAddrRelease( newAddr );
-	return( err );
-}
-
-//===========================================================================================================================
-//	BrowseAllRemoveIPAddress
-//===========================================================================================================================
-
-static OSStatus
-	BrowseAllRemoveIPAddress(
-		BrowseAllContext *		inContext,
-		BrowseInstance *		inInstance,
-		const struct sockaddr *	inSockAddr )
-{
-	OSStatus			err;
-	BrowseIPAddr *		addr;
-	BrowseIPAddr **		p;
+	if( context->timeLimitSecs == 0 ) goto exit;
 	
-	Unused( inContext );
+	err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
+		&context->readSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->readSource );
 	
-	for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+	if( context->timeLimitSecs > 0 )
 	{
-		if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break;
+		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+			Exit );
 	}
-	require_action_quiet( addr, exit, err = kNotFoundErr );
-	
-	*p = addr->next;
-	BrowseIPAddrRelease( addr );
-	err = kNoErr;
+	dispatch_main();
 	
 exit:
-	return( err );
-}
-
-//===========================================================================================================================
-//	BrowseDomainFree
-//===========================================================================================================================
-
-static void	BrowseDomainFree( BrowseDomain *inDomain )
-{
-	BrowseType *		type;
-	
-	ForgetBrowseAllContext( &inDomain->context );
-	ForgetMem( &inDomain->name );
-	DNSServiceForget( &inDomain->servicesQuery );
-	while( ( type = inDomain->typeList ) != NULL )
+	if( context )
 	{
-		inDomain->typeList = type->next;
-		BrowseTypeFree( type );
+		dispatch_source_forget( &context->readSource );
+		ForgetSocket( &context->sock );
+		free( context );
 	}
-	free( inDomain );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	BrowseTypeFree
+//	DNSQueryPrintPrologue
 //===========================================================================================================================
 
-static void	BrowseTypeFree( BrowseType *inType )
+static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext )
 {
-	BrowseOp *		browse;
+	const int		timeLimitSecs = inContext->timeLimitSecs;
 	
-	ForgetMem( &inType->name );
-	while( ( browse = inType->browseList ) != NULL )
-	{
-		inType->browseList = browse->next;
-		BrowseOpFree( browse );
-	}
-	free( inType );
+	FPrintF( stdout, "Name:        %s\n",		inContext->name );
+	FPrintF( stdout, "Type:        %s (%u)\n",	RecordTypeToString( inContext->type ), inContext->type );
+	FPrintF( stdout, "Server:      %##a\n",		&inContext->serverAddr );
+	FPrintF( stdout, "Transport:   %s\n",		inContext->useTCP ? "TCP" : "UDP" );
+	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:  %{du:time}\n", NULL );
+	FPrintF( stdout, "---\n" );
 }
 
 //===========================================================================================================================
-//	BrowseOpFree
+//	DNSQueryReadHandler
 //===========================================================================================================================
 
-static void	BrowseOpFree( BrowseOp *inBrowse )
+static void	DNSQueryReadHandler( void *inContext )
 {
-	BrowseInstance *		instance;
+	OSStatus					err;
+	struct timeval				now;
+	const uint64_t				nowTicks	= UpTicks();
+	DNSQueryContext * const		context		= (DNSQueryContext *) inContext;
 	
-	ForgetBrowseAllContext( &inBrowse->context );
-	DNSServiceForget( &inBrowse->browse );
-	while( ( instance = inBrowse->instanceList ) != NULL )
+	gettimeofday( &now, NULL );
+	
+	if( context->useTCP )
 	{
-		inBrowse->instanceList = instance->next;
-		BrowseInstanceFree( instance );
+		if( !context->haveTCPLen )
+		{
+			err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset );
+			if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
+			require_noerr( err, exit );
+			
+			context->msgOffset	= 0;
+			context->msgLen		= ReadBig16( context->msgBuf );
+			context->haveTCPLen	= true;
+			if( context->msgLen <= sizeof( context->msgBuf ) )
+			{
+				context->msgPtr = context->msgBuf;
+			}
+			else
+			{
+				context->msgPtr = (uint8_t *) malloc( context->msgLen );
+				require_action( context->msgPtr, exit, err = kNoMemoryErr );
+			}
+		}
+		
+		err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset );
+		if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
+		require_noerr( err, exit );
+		context->msgOffset	= 0;
+		context->haveTCPLen	= false;
 	}
-	free( inBrowse );
+	else
+	{
+		sockaddr_ip		fromAddr;
+		
+		context->msgPtr = context->msgBuf;
+		err = SocketRecvFrom( context->sock, context->msgPtr, 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: %{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 ) );
+	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
+	
+	if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+	{
+		Exit( kExitReason_ReceivedResponse );
+	}
+	
+exit:
+	if( err ) dispatch_source_forget( &context->readSource );
 }
 
 //===========================================================================================================================
-//	BrowseInstanceFree
+//	DNSQueryCancelHandler
 //===========================================================================================================================
 
-static void	BrowseInstanceFree( BrowseInstance *inInstance )
+static void	DNSQueryCancelHandler( void *inContext )
 {
-	ForgetBrowseAllContext( &inInstance->context );
-	ForgetMem( &inInstance->name );
-	DNSServiceForget( &inInstance->resolve );
-	DNSServiceForget( &inInstance->getAddr );
-	ForgetMem( &inInstance->txtPtr );
-	ForgetMem( &inInstance->hostname );
-	ForgetIPAddressList( &inInstance->addrList );
-	free( inInstance );
+	DNSQueryContext * const		context = (DNSQueryContext *) inContext;
+	
+	check( !context->readSource );
+	ForgetSocket( &context->sock );
+	if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr );
+	free( context );
+	dispatch_async_f( dispatch_get_main_queue(), NULL, Exit );
 }
 
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
 //===========================================================================================================================
-//	BrowseIPAddrRelease
+//	DNSCryptCmd
 //===========================================================================================================================
 
-static void BrowseIPAddrRelease( BrowseIPAddr *inAddr )
+#define kDNSCryptPort		443
+
+#define kDNSCryptMinPadLength				8
+#define kDNSCryptMaxPadLength				256
+#define kDNSCryptBlockSize					64
+#define kDNSCryptCertMinimumLength			124
+#define kDNSCryptClientMagicLength			8
+#define kDNSCryptResolverMagicLength		8
+#define kDNSCryptHalfNonceLength			12
+#define kDNSCryptCertMagicLength			4
+
+check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES );
+
+static const uint8_t		kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' };
+static const uint8_t		kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] =
 {
-	AsyncConnection_Forget( &inAddr->connection );
-	if( --inAddr->refCount == 0 )
-	{
-		ForgetBrowseAllContext( &inAddr->context );
-		free( inAddr );
-	}
-}
+	0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
+};
 
-//===========================================================================================================================
-//	BrowseIPAddrReleaseList
-//===========================================================================================================================
+typedef struct
+{
+	uint8_t		certMagic[ kDNSCryptCertMagicLength ];
+	uint8_t		esVersion[ 2 ];
+	uint8_t		minorVersion[ 2 ];
+	uint8_t		signature[ crypto_sign_BYTES ];
+	uint8_t		publicKey[ crypto_box_PUBLICKEYBYTES ];
+	uint8_t		clientMagic[ kDNSCryptClientMagicLength ];
+	uint8_t		serial[ 4 ];
+	uint8_t		startTime[ 4 ];
+	uint8_t		endTime[ 4 ];
+	uint8_t		extensions[ 1 ];	// Variably-sized extension data.
+	
+}	DNSCryptCert;
+
+check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength );
+
+typedef struct
+{
+	uint8_t		clientMagic[ kDNSCryptClientMagicLength ];
+	uint8_t		clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
+	uint8_t		clientNonce[ kDNSCryptHalfNonceLength ];
+	uint8_t		poly1305MAC[ 16 ];
+	
+}	DNSCryptQueryHeader;
 
-static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
+check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 );
+check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES );
+check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) ==
+	offsetof( DNSCryptQueryHeader, poly1305MAC ) );
+
+typedef struct
 {
-	BrowseIPAddr *		addr;
+	uint8_t		resolverMagic[ kDNSCryptResolverMagicLength ];
+	uint8_t		clientNonce[ kDNSCryptHalfNonceLength ];
+	uint8_t		resolverNonce[ kDNSCryptHalfNonceLength ];
+	uint8_t		poly1305MAC[ 16 ];
 	
-	while( ( addr = inList ) != NULL )
-	{
-		inList = addr->next;
-		BrowseIPAddrRelease( addr );
-	}
-}
+}	DNSCryptResponseHeader;
 
-//===========================================================================================================================
-//	GetNameInfoCmd
-//===========================================================================================================================
+check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 );
+check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES );
+check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) ==
+	sizeof( DNSCryptResponseHeader ) );
 
-const FlagStringPair		kGetNameInfoFlagStringPairs[] =
+typedef struct
 {
-	CaseFlagStringify( NI_NUMERICSCOPE ),
-	CaseFlagStringify( NI_DGRAM ),
-	CaseFlagStringify( NI_NUMERICSERV ),
-	CaseFlagStringify( NI_NAMEREQD ),
-	CaseFlagStringify( NI_NUMERICHOST ),
-	CaseFlagStringify( NI_NOFQDN ),
-	{ 0, NULL }
-};
+	sockaddr_ip				serverAddr;
+	uint64_t				sendTicks;
+	const char *			providerName;
+	const char *			qname;
+	const uint8_t *			certPtr;
+	size_t					certLen;
+	dispatch_source_t		readSource;
+	size_t					msgLen;
+	int						timeLimitSecs;
+	uint16_t				queryID;
+	uint16_t				qtype;
+	Boolean					printRawRData;
+	uint8_t					serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ];
+	uint8_t					serverPublicKey[ crypto_box_PUBLICKEYBYTES ];
+	uint8_t					clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
+	uint8_t					clientSecretKey[ crypto_box_SECRETKEYBYTES ];
+	uint8_t					clientMagic[ kDNSCryptClientMagicLength ];
+	uint8_t					clientNonce[ kDNSCryptHalfNonceLength ];
+	uint8_t					nmKey[ crypto_box_BEFORENMBYTES ];
+	uint8_t					msgBuf[ 512 ];
+	
+}	DNSCryptContext;
 
-static void	GetNameInfoCmd( void )
+static void		DNSCryptReceiveCertHandler( void *inContext );
+static void		DNSCryptReceiveResponseHandler( void *inContext );
+static void		DNSCryptProceed( void *inContext );
+static OSStatus	DNSCryptProcessCert( DNSCryptContext *inContext );
+static OSStatus	DNSCryptBuildQuery( DNSCryptContext *inContext );
+static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext );
+static void		DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen );
+
+static void	DNSCryptCmd( 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 ];
+	OSStatus				err;
+	DNSCryptContext *		context		= NULL;
+	size_t					writtenBytes;
+	size_t					totalBytes;
+	SocketContext *			sockCtx;
+	SocketRef				sock		= kInvalidSocketRef;
+	const char *			ptr;
 	
-	err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
-	check_noerr( err );
-	if( err )
+	// Check command parameters.
+	
+	if( gDNSCrypt_TimeLimitSecs < -1 )
 	{
-		FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+		FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
+		err = kParamErr;
 		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;
+	// Create context.
 	
-	// Print prologue.
+	context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
 	
-	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" );
+	context->providerName	= gDNSCrypt_ProviderName;
+	context->qname			= gDNSCrypt_Name;
+	context->timeLimitSecs	= gDNSCrypt_TimeLimitSecs;
+	context->printRawRData	= gDNSCrypt_RawRData ? true : false;
 	
-	// Call getnameinfo().
+	err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
+	require_noerr( err, exit );
 	
-	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 ) );
+	err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags,
+		context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr );
+	if( err || ( *ptr != '\0' ) )
+	{
+		FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey );
+		goto exit;
 	}
-	else
+	else if( totalBytes != sizeof( context->serverPublicSignKey ) )
 	{
-		FPrintF( stdout, "host: %s\n", host );
-		FPrintF( stdout, "serv: %s\n", serv );
+		FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
+			totalBytes, sizeof( context->serverPublicSignKey ) );
+		err = kSizeErr;
+		goto exit;
 	}
-	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "End time:   %{du:time}\n", &now );
+	check( writtenBytes == totalBytes );
+	
+	err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+	require_noerr( err, exit );
+	if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
+	
+	err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
+	require_noerr( err, exit );
+	
+	// Write query message.
+	
+	context->queryID = (uint16_t) Random32();
+	err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName,
+		kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen );
+	require_noerr( err, exit );
+	
+	// Create UDP socket.
+	
+	err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
+	require_noerr( err, exit );
+	
+	// Send DNS query.
+	
+	context->sendTicks = UpTicks();
+	err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
+	require_noerr( err, exit );
+	
+	err = SocketContextCreate( sock, context, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
+		&context->readSource );
+	if( err ) ForgetSocketContext( &sockCtx );
+	require_noerr( err, exit );
+	
+	dispatch_resume( context->readSource );
+	
+	if( context->timeLimitSecs > 0 )
+	{
+		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+			Exit );
+	}
+	dispatch_main();
 	
 exit:
-	gExitCode = err ? 1 : 0;
+	if( context ) free( context );
+	ForgetSocket( &sock );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	GetAddrInfoStressCmd
+//	DNSCryptReceiveCertHandler
 //===========================================================================================================================
 
-typedef struct
+static void	DNSCryptReceiveCertHandler( void *inContext )
 {
-	DNSServiceRef			mainRef;
-	DNSServiceRef			sdRef;
-	DNSServiceFlags			flags;
-	unsigned int			interfaceIndex;
-	unsigned int			connectionNumber;
-	unsigned int			requestCount;
-	unsigned int			requestCountMax;
-	unsigned int			requestCountLimit;
-	unsigned int			durationMinMs;
-	unsigned int			durationMaxMs;
+	OSStatus					err;
+	struct timeval				now;
+	const uint64_t				nowTicks	= UpTicks();
+	SocketContext * const		sockCtx		= (SocketContext *) inContext;
+	DNSCryptContext * const		context		= (DNSCryptContext *) sockCtx->userContext;
+	const DNSHeader *			hdr;
+	sockaddr_ip					fromAddr;
+	const uint8_t *				ptr;
+	const uint8_t *				txtPtr;
+	size_t						txtLen;
+	unsigned int				answerCount, i;
+	uint8_t						targetName[ kDomainNameLengthMax ];
 	
-}	GAIStressContext;
-
-static void	GetAddrInfoStressEvent( void *inContext );
-static void	DNSSD_API
-	GetAddrInfoStressCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inHostname,
-		const struct sockaddr *	inSockAddr,
-		uint32_t				inTTL,
-		void *					inContext );
-
-static void	GetAddrInfoStressCmd( void )
-{
-	OSStatus				err;
-	GAIStressContext *		context = NULL;
-	int						i;
-	DNSServiceFlags			flags;
-	uint32_t				ifIndex;
-	char					ifName[ kInterfaceNameBufLen ];
+	gettimeofday( &now, NULL );
 	
-	if( gGAIStress_TestDurationSecs < 0 )
-	{
-		FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
-		err = kParamErr;
-		goto exit;
-	}
-	if( gGAIStress_ConnectionCount <= 0 )
-	{
-		FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
-		err = kParamErr;
-		goto exit;
-	}
-	if( gGAIStress_DurationMinMs <= 0 )
-	{
-		FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs );
-		err = kParamErr;
-		goto exit;
-	}
-	if( gGAIStress_DurationMaxMs <= 0 )
-	{
-		FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs );
-		err = kParamErr;
-		goto exit;
-	}
-	if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs )
-	{
-		FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n",
-			gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs );
-		err = kParamErr;
-		goto exit;
-	}
-	if( gGAIStress_RequestCountMax <= 0 )
-	{
-		FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax );
-		err = kParamErr;
-		goto exit;
-	}
+	dispatch_source_forget( &context->readSource );
 	
-	// Set flags.
+	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 );
 	
-	flags = GetDNSSDFlagsFromOpts();
+	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 ) );
+	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
 	
-	// Set interface index.
+	require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
 	
-	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
-	require_noerr_quiet( err, exit );
+	hdr = (DNSHeader *) context->msgBuf;
+	require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
 	
-	for( i = 0; i < gGAIStress_ConnectionCount; ++i )
+	err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
+	require_noerr( err, exit );
+	
+	err = DomainNameFromString( targetName, context->providerName, NULL );
+	require_noerr( err, exit );
+	
+	answerCount = DNSHeaderGetAnswerCount( hdr );
+	for( i = 0; i < answerCount; ++i )
 	{
-		context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
-		require_action( context, exit, err = kNoMemoryErr );
+		uint16_t		type;
+		uint16_t		class;
+		uint8_t			name[ kDomainNameLengthMax ];
 		
-		context->flags				= flags;
-		context->interfaceIndex		= ifIndex;
-		context->connectionNumber	= (unsigned int)( i + 1 );
-		context->requestCountMax	= (unsigned int) gGAIStress_RequestCountMax;
-		context->durationMinMs		= (unsigned int) gGAIStress_DurationMinMs;
-		context->durationMaxMs		= (unsigned int) gGAIStress_DurationMaxMs;
+		err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
+			&ptr );
+		require_noerr( err, exit );
 		
-		dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
-		context = NULL;
-	}
-	
-	if( gGAIStress_TestDurationSecs > 0 )
-	{
-		dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
+		if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
+		{
+			break;
+		}
 	}
 	
-	FPrintF( stdout, "Flags:                %#{flags}\n",	flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:            %d (%s)\n",		(int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
-	FPrintF( stdout, "Test duration:        " );
-	if( gGAIStress_TestDurationSecs == 0 )
+	if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
 	{
-		FPrintF( stdout, "∞\n" );
+		FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
+		err = kSizeErr;
+		goto exit;
 	}
-	else
+	if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
 	{
-		FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
+		FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
+		err = kSizeErr;
+		goto exit;
 	}
-	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();
+	context->certLen = txtPtr[ 0 ];
+	context->certPtr = &txtPtr[ 1 ];
+	
+	dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
 	
 exit:
-	FreeNullSafe( context );
-	if( err ) exit( 1 );
+	if( err ) Exit( NULL );
 }
 
 //===========================================================================================================================
-//	GetAddrInfoStressEvent
+//	DNSCryptReceiveResponseHandler
 //===========================================================================================================================
 
-#define kStressRandStrLen		5
-
-#define kLowercaseAlphaCharSet		"abcdefghijklmnopqrstuvwxyz"
-
-static void	GetAddrInfoStressEvent( void *inContext )
+static void	DNSCryptReceiveResponseHandler( void *inContext )
 {
-	GAIStressContext * const		context = (GAIStressContext *) inContext;
 	OSStatus						err;
-	DNSServiceRef					sdRef;
-	unsigned int					nextMs;
-	char							randomStr[ kStressRandStrLen + 1 ];
-	char							hostname[ kStressRandStrLen + 4 + 1 ];
-	Boolean							isConnectionNew	= false;
-	static Boolean					printedHeader	= false;
-	
-	if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
-	{
-		DNSServiceForget( &context->mainRef );
-		context->sdRef				= NULL;
-		context->requestCount		= 0;
-		context->requestCountLimit	= RandomRange( 1, context->requestCountMax );
-		
-		err = DNSServiceCreateConnection( &context->mainRef );
-		require_noerr( err, exit );
-		
-		err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
-		require_noerr( err, exit );
-		
-		isConnectionNew = true;
+	struct timeval					now;
+	const uint64_t					nowTicks	= UpTicks();
+	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;
+	uint8_t							nonce[ crypto_box_NONCEBYTES ];
+	
+	gettimeofday( &now, NULL );
+	
+	dispatch_source_forget( &context->readSource );
+	
+	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: %{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 ) );
+	
+	if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
+	{
+		FPrintF( stderr, "DNSCrypt response is too short.\n" );
+		err = kSizeErr;
+		goto exit;
 	}
 	
-	RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
-	SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
+	hdr = (DNSCryptResponseHeader *) context->msgBuf;
 	
-	nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
+	if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
+	{
+		FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
+			hdr->resolverMagic,		kDNSCryptResolverMagicLength, INT_MAX,
+			kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
+		err = kValueErr;
+		goto exit;
+	}
 	
-	if( !printedHeader )
+	if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
 	{
-		FPrintF( stdout, "%-26s Conn  Hostname Dur (ms)\n", "Timestamp" );
-		printedHeader = true;
+		FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
+		err = kValueErr;
+		goto exit;
 	}
-	FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
-		NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
 	
-	DNSServiceForget( &context->sdRef );
-	sdRef = context->mainRef;
-	err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex,
-		kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL );
-	require_noerr( err, exit );
-	context->sdRef = sdRef;
+	memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
 	
-	context->requestCount++;
+	ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
+	memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
 	
-	dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+	plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
+	check( plaintext == ciphertext );
+	
+	end = context->msgBuf + context->msgLen;
+	
+	err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
+	require_noerr( err, exit );
+	
+	response = plaintext + crypto_box_ZEROBYTES;
+	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
+	Exit( kExitReason_ReceivedResponse );
 	
 exit:
-	if( err ) exit( 1 );
+	if( err ) Exit( NULL );
 }
 
 //===========================================================================================================================
-//	GetAddrInfoStressCallback
+//	DNSCryptProceed
 //===========================================================================================================================
 
-static void DNSSD_API
-	GetAddrInfoStressCallback(
-		DNSServiceRef			inSDRef,
-		DNSServiceFlags			inFlags,
-		uint32_t				inInterfaceIndex,
-		DNSServiceErrorType		inError,
-		const char *			inHostname,
-		const struct sockaddr *	inSockAddr,
-		uint32_t				inTTL,
-		void *					inContext )
+static void	DNSCryptProceed( void *inContext )
 {
-	Unused( inSDRef );
-	Unused( inFlags );
-	Unused( inInterfaceIndex );
-	Unused( inError );
-	Unused( inHostname );
-	Unused( inSockAddr );
-	Unused( inTTL );
-	Unused( inContext );
+	OSStatus					err;
+	DNSCryptContext * const		context = (DNSCryptContext *) inContext;
+	
+	err = DNSCryptProcessCert( context );
+	require_noerr_quiet( err, exit );
+	
+	err = DNSCryptBuildQuery( context );
+	require_noerr_quiet( err, exit );
+	
+	err = DNSCryptSendQuery( context );
+	require_noerr_quiet( err, exit );
+	
+exit:
+	if( err ) Exit( NULL );
 }
 
 //===========================================================================================================================
-//	DNSQueryCmd
+//	DNSCryptProcessCert
 //===========================================================================================================================
 
-typedef struct
-{
-	sockaddr_ip				serverAddr;
-	uint64_t				sendTicks;
-	uint8_t *				msgPtr;
-	size_t					msgLen;
-	size_t					msgOffset;
-	const char *			name;
-	dispatch_source_t		readSource;
-	SocketRef				sock;
-	int						timeLimitSecs;
-	uint16_t				queryID;
-	uint16_t				type;
-	Boolean					haveTCPLen;
-	Boolean					useTCP;
-	Boolean					printRawRData;	// True if RDATA results are not to be formatted.
-	uint8_t					msgBuf[ 512 ];
-	
-}	DNSQueryContext;
-
-static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext );
-static void	DNSQueryReadHandler( void *inContext );
-static void	DNSQueryCancelHandler( void *inContext );
-
-static void	DNSQueryCmd( void )
+static OSStatus	DNSCryptProcessCert( DNSCryptContext *inContext )
 {
-	OSStatus				err;
-	DNSQueryContext *		context = NULL;
-	uint8_t *				msgPtr;
-	size_t					msgLen, sendLen;
+	OSStatus						err;
+	const DNSCryptCert * const		cert	= (DNSCryptCert *) inContext->certPtr;
+	const uint8_t * const			certEnd	= inContext->certPtr + inContext->certLen;
+	struct timeval					now;
+	time_t							startTimeSecs, endTimeSecs;
+	size_t							signedLen;
+	uint8_t *						tempBuf;
+	unsigned long long				tempLen;
 	
-	// Check command parameters.
+	DNSCryptPrintCertificate( cert, inContext->certLen );
 	
-	if( gDNSQuery_TimeLimitSecs < -1 )
+	if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
 	{
-		FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs );
-		err = kParamErr;
+		FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
+			cert->certMagic,	kDNSCryptCertMagicLength, INT_MAX,
+			kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
+		err = kValueErr;
 		goto exit;
 	}
-	if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
+	
+	startTimeSecs	= (time_t) ReadBig32( cert->startTime );
+	endTimeSecs		= (time_t) ReadBig32( cert->endTime );
+	
+	gettimeofday( &now, NULL );
+	if( now.tv_sec < startTimeSecs )
 	{
-		FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
-		err = kParamErr;
+		FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
+		err = kDateErr;
 		goto exit;
 	}
-	
-	// Create context.
-	
-	context = (DNSQueryContext *) calloc( 1, sizeof( *context ) );
-	require_action( context, exit, err = kNoMemoryErr );
-	
-	context->name			= gDNSQuery_Name;
-	context->sock			= kInvalidSocketRef;
-	context->timeLimitSecs	= gDNSQuery_TimeLimitSecs;
-	context->queryID		= (uint16_t) Random32();
-	context->useTCP			= gDNSQuery_UseTCP	 ? true : false;
-	context->printRawRData	= gDNSQuery_RawRData ? true : false;
-	
-#if( TARGET_OS_DARWIN )
-	if( gDNSQuery_Server )
-#endif
+	if( now.tv_sec >= endTimeSecs )
 	{
-		err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
-		require_noerr( err, exit );
+		FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
+		err = kDateErr;
+		goto exit;
 	}
-#if( TARGET_OS_DARWIN )
-	else
+	
+	signedLen = (size_t)( certEnd - cert->signature );
+	tempBuf = (uint8_t *) malloc( signedLen );
+	require_action( tempBuf, exit, err = kNoMemoryErr );
+	err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey );
+	free( tempBuf );
+	if( err )
 	{
-		err = GetDefaultDNSServer( &context->serverAddr );
-		require_noerr( err, exit );
+		FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
+		err = kAuthenticationErr;
+		goto exit;
 	}
-#endif
-	if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
 	
-	err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
+	memcpy( inContext->serverPublicKey,	cert->publicKey,	crypto_box_PUBLICKEYBYTES );
+	memcpy( inContext->clientMagic,		cert->clientMagic,	kDNSCryptClientMagicLength );
+	
+	err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey );
 	require_noerr( err, exit );
 	
-	// Write query message.
+	inContext->certPtr	= NULL;
+	inContext->certLen	= 0;
+	inContext->msgLen	= 0;
 	
-	check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSCryptBuildQuery
+//===========================================================================================================================
+
+static OSStatus	DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen );
+
+static OSStatus	DNSCryptBuildQuery( DNSCryptContext *inContext )
+{
+	OSStatus						err;
+	DNSCryptQueryHeader * const		hdr			= (DNSCryptQueryHeader *) inContext->msgBuf;
+	uint8_t * const					queryPtr	= (uint8_t *)( hdr + 1 );
+	size_t							queryLen;
+	size_t							paddedQueryLen;
+	const uint8_t * const			msgLimit	= inContext->msgBuf + sizeof( inContext->msgBuf );
+	const uint8_t *					padLimit;
+	uint8_t							nonce[ crypto_box_NONCEBYTES ];
 	
-	msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
-	err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
-		kDNSServiceClass_IN, &msgLen );
+	check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) );
+	
+	inContext->queryID = (uint16_t) Random32();
+	err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname,
+		inContext->qtype, kDNSServiceClass_IN, &queryLen );
 	require_noerr( err, exit );
-	check( msgLen <= UINT16_MAX );
 	
-	if( context->useTCP )
-	{
-		WriteBig16( context->msgBuf, msgLen );
-		sendLen = 2 + msgLen;
-	}
-	else
-	{
-		sendLen = msgLen;
-	}
-	
-	DNSQueryPrintPrologue( context );
+	padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
+	if( padLimit > msgLimit ) padLimit = msgLimit;
 	
-	if( gDNSQuery_Verbose )
-	{
-		FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
-		FPrintF( stdout, "---\n" );
-	}
+	err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
+	require_noerr( err, exit );
 	
-	if( context->useTCP )
-	{
-		// Create TCP socket.
-		
-		context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
-		err = map_socket_creation_errno( context->sock );
-		require_noerr( err, exit );
-		
-		err = SocketConnect( context->sock, &context->serverAddr, 5 );
-		require_noerr( err, exit );
-	}
-	else
-	{
-		// Create UDP socket.
-		
-		err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
-		require_noerr( err, exit );
-	}
+	memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
+	RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
+	memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
+	memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
 	
-	context->sendTicks = UpTicks();
-	err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+	err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
+		paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
 	require_noerr( err, exit );
 	
-	if( context->timeLimitSecs == 0 ) goto exit;
+	memcpy( hdr->clientMagic,		inContext->clientMagic,		kDNSCryptClientMagicLength );
+	memcpy( hdr->clientPublicKey,	inContext->clientPublicKey,	crypto_box_PUBLICKEYBYTES );
+	memcpy( hdr->clientNonce,		nonce,						kDNSCryptHalfNonceLength );
 	
-	err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
-		&context->readSource );
-	require_noerr( err, exit );
-	dispatch_resume( context->readSource );
+	inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
 	
-	if( context->timeLimitSecs > 0 )
-	{
-		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-			Exit );
-	}
-	dispatch_main();
+exit:
+	return( err );
+}
+
+static OSStatus	DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen )
+{
+	OSStatus		err;
+	size_t			paddedLen;
+	
+	require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr );
+	
+	paddedLen = inMsgLen + kDNSCryptMinPadLength +
+		arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) );
+	paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) );
+	if( paddedLen > inMaxLen ) paddedLen = inMaxLen;
+	
+	inMsgPtr[ inMsgLen ] = 0x80;
+	memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
+	
+	if( outPaddedLen ) *outPaddedLen = paddedLen;
+	err = kNoErr;
 	
 exit:
-	if( context )
-	{
-		dispatch_source_forget( &context->readSource );
-		ForgetSocket( &context->sock );
-		free( context );
-	}
-	if( err ) exit( 1 );
+	return( err );
 }
 
 //===========================================================================================================================
-//	DNSQueryPrintPrologue
+//	DNSCryptSendQuery
 //===========================================================================================================================
 
-static void	DNSQueryPrintPrologue( const DNSQueryContext *inContext )
+static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext )
 {
-	const int		timeLimitSecs = inContext->timeLimitSecs;
+	OSStatus			err;
+	SocketContext *		sockCtx;
+	SocketRef			sock = kInvalidSocketRef;
 	
-	FPrintF( stdout, "Name:        %s\n",		inContext->name );
-	FPrintF( stdout, "Type:        %s (%u)\n",	RecordTypeToString( inContext->type ), inContext->type );
-	FPrintF( stdout, "Server:      %##a\n",		&inContext->serverAddr );
-	FPrintF( stdout, "Transport:   %s\n",		inContext->useTCP ? "TCP" : "UDP" );
-	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:  %{du:time}\n", NULL );
-	FPrintF( stdout, "---\n" );
+	check( inContext->msgLen > 0 );
+	check( !inContext->readSource );
+	
+	err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock );
+	require_noerr( err, exit );
+	
+	inContext->sendTicks = UpTicks();
+	err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
+	require_noerr( err, exit );
+	
+	err = SocketContextCreate( sock, inContext, &sockCtx );
+	require_noerr( err, exit );
+	sock = kInvalidSocketRef;
+	
+	err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
+		&inContext->readSource );
+	if( err ) ForgetSocketContext( &sockCtx );
+	require_noerr( err, exit );
+	
+	dispatch_resume( inContext->readSource );
+	
+exit:
+	ForgetSocket( &sock );
+	return( err );
 }
 
 //===========================================================================================================================
-//	DNSQueryReadHandler
+//	DNSCryptPrintCertificate
 //===========================================================================================================================
 
-static void	DNSQueryReadHandler( void *inContext )
+#define kCertTimeStrBufLen		32
+
+static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+
+static void	DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
 {
-	OSStatus					err;
-	struct timeval				now;
-	const uint64_t				nowTicks	= UpTicks();
-	DNSQueryContext * const		context		= (DNSQueryContext *) inContext;
+	time_t		startTime, endTime;
+	int			extLen;
+	char		timeBuf[ kCertTimeStrBufLen ];
 	
-	gettimeofday( &now, NULL );
+	check( inLen >= kDNSCryptCertMinimumLength );
 	
-	if( context->useTCP )
+	startTime	= (time_t) ReadBig32( inCert->startTime );
+	endTime		= (time_t) ReadBig32( inCert->endTime );
+	
+	FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen );
+	FPrintF( stdout, "Cert Magic:    %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX );
+	FPrintF( stdout, "ES Version:    %u\n",	ReadBig16( inCert->esVersion ) );
+	FPrintF( stdout, "Minor Version: %u\n",	ReadBig16( inCert->minorVersion ) );
+	FPrintF( stdout, "Signature:     %H\n",	inCert->signature, crypto_sign_BYTES / 2, INT_MAX );
+	FPrintF( stdout, "               %H\n",	&inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX );
+	FPrintF( stdout, "Public Key:    %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX );
+	FPrintF( stdout, "Client Magic:  %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX );
+	FPrintF( stdout, "Serial:        %u\n",	ReadBig32( inCert->serial ) );
+	FPrintF( stdout, "Start Time:    %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) );
+	FPrintF( stdout, "End Time:      %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) );
+	
+	if( inLen > kDNSCryptCertMinimumLength )
 	{
-		if( !context->haveTCPLen )
-		{
-			err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset );
-			if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
-			require_noerr( err, exit );
-			
-			context->msgOffset	= 0;
-			context->msgLen		= ReadBig16( context->msgBuf );
-			context->haveTCPLen	= true;
-			if( context->msgLen <= sizeof( context->msgBuf ) )
-			{
-				context->msgPtr = context->msgBuf;
-			}
-			else
-			{
-				context->msgPtr = (uint8_t *) malloc( context->msgLen );
-				require_action( context->msgPtr, exit, err = kNoMemoryErr );
-			}
-		}
-		
-		err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset );
-		if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
-		require_noerr( err, exit );
-		context->msgOffset	= 0;
-		context->haveTCPLen	= false;
+		extLen = (int)( inLen - kDNSCryptCertMinimumLength );
+		FPrintF( stdout, "Extensions:    %.1H\n", inCert->extensions, extLen, extLen );
 	}
-	else
+	FPrintF( stdout, "\n" );
+}
+
+static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
+{
+	struct tm *		tm;
+	
+	tm = localtime( &inTime );
+	if( !tm )
 	{
-		sockaddr_ip		fromAddr;
-		
-		context->msgPtr = context->msgBuf;
-		err = SocketRecvFrom( context->sock, context->msgPtr, sizeof( context->msgBuf ), &context->msgLen, &fromAddr,
-			sizeof( fromAddr ), NULL, NULL, NULL, NULL );
-		require_noerr( err, exit );
-		
-		check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+		dlogassert( "localtime() returned a NULL pointer.\n" );
+		*inBuffer = '\0';
 	}
-	
-	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 ) );
-	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
-	
-	if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+	else
 	{
-		Exit( kExitReason_ReceivedResponse );
+		strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
 	}
 	
-exit:
-	if( err ) dispatch_source_forget( &context->readSource );
+	return( inBuffer );
 }
 
+#endif	// DNSSDUTIL_INCLUDE_DNSCRYPT
+
 //===========================================================================================================================
-//	DNSQueryCancelHandler
+//	MDNSQueryCmd
 //===========================================================================================================================
 
-static void	DNSQueryCancelHandler( void *inContext )
+typedef struct
 {
-	DNSQueryContext * const		context = (DNSQueryContext *) inContext;
+	const char *			qnameStr;							// Name (QNAME) of the record being queried as a C string.
+	dispatch_source_t		readSourceV4;						// Read dispatch source for IPv4 socket.
+	dispatch_source_t		readSourceV6;						// Read dispatch source for IPv6 socket.
+	int						localPort;							// The port number to which the sockets are bound.
+	int						receiveSecs;						// After send, the amount of time to spend receiving.
+	uint32_t				ifIndex;							// Index of the interface over which to send the query.
+	uint16_t				qtype;								// The type (QTYPE) of the record being queried.
+	Boolean					isQU;								// True if the query is QU, i.e., requests unicast responses.
+	Boolean					allResponses;						// True if all mDNS messages received should be printed.
+	Boolean					printRawRData;						// True if RDATA should be printed as hexdumps.
+	Boolean					useIPv4;							// True if the query should be sent via IPv4 multicast.
+	Boolean					useIPv6;							// True if the query should be sent via IPv6 multicast.
+	char					ifName[ IF_NAMESIZE + 1 ];			// Name of the interface over which to send the query.
+	uint8_t					qname[ kDomainNameLengthMax ];		// Buffer to hold the QNAME in DNS label format.
+	uint8_t					msgBuf[ kMDNSMessageSizeMax ];		// mDNS message buffer.
 	
-	check( !context->readSource );
-	ForgetSocket( &context->sock );
-	if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr );
-	free( context );
-	dispatch_async_f( dispatch_get_main_queue(), NULL, Exit );
-}
+}	MDNSQueryContext;
 
-#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
-//===========================================================================================================================
-//	DNSCryptCmd
-//===========================================================================================================================
-
-#define kDNSCryptPort		443
-
-#define kDNSCryptMinPadLength				8
-#define kDNSCryptMaxPadLength				256
-#define kDNSCryptBlockSize					64
-#define kDNSCryptCertMinimumLength			124
-#define kDNSCryptClientMagicLength			8
-#define kDNSCryptResolverMagicLength		8
-#define kDNSCryptHalfNonceLength			12
-#define kDNSCryptCertMagicLength			4
-
-check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES );
-
-static const uint8_t		kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' };
-static const uint8_t		kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] =
-{
-	0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
-};
-
-typedef struct
-{
-	uint8_t		certMagic[ kDNSCryptCertMagicLength ];
-	uint8_t		esVersion[ 2 ];
-	uint8_t		minorVersion[ 2 ];
-	uint8_t		signature[ crypto_sign_BYTES ];
-	uint8_t		publicKey[ crypto_box_PUBLICKEYBYTES ];
-	uint8_t		clientMagic[ kDNSCryptClientMagicLength ];
-	uint8_t		serial[ 4 ];
-	uint8_t		startTime[ 4 ];
-	uint8_t		endTime[ 4 ];
-	uint8_t		extensions[ 1 ];	// Variably-sized extension data.
-	
-}	DNSCryptCert;
-
-check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength );
-
-typedef struct
-{
-	uint8_t		clientMagic[ kDNSCryptClientMagicLength ];
-	uint8_t		clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
-	uint8_t		clientNonce[ kDNSCryptHalfNonceLength ];
-	uint8_t		poly1305MAC[ 16 ];
-	
-}	DNSCryptQueryHeader;
-
-check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 );
-check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES );
-check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) ==
-	offsetof( DNSCryptQueryHeader, poly1305MAC ) );
-
-typedef struct
-{
-	uint8_t		resolverMagic[ kDNSCryptResolverMagicLength ];
-	uint8_t		clientNonce[ kDNSCryptHalfNonceLength ];
-	uint8_t		resolverNonce[ kDNSCryptHalfNonceLength ];
-	uint8_t		poly1305MAC[ 16 ];
-	
-}	DNSCryptResponseHeader;
-
-check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 );
-check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES );
-check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) ==
-	sizeof( DNSCryptResponseHeader ) );
-
-typedef struct
-{
-	sockaddr_ip				serverAddr;
-	uint64_t				sendTicks;
-	const char *			providerName;
-	const char *			qname;
-	const uint8_t *			certPtr;
-	size_t					certLen;
-	dispatch_source_t		readSource;
-	size_t					msgLen;
-	int						timeLimitSecs;
-	uint16_t				queryID;
-	uint16_t				qtype;
-	Boolean					printRawRData;
-	uint8_t					serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ];
-	uint8_t					serverPublicKey[ crypto_box_PUBLICKEYBYTES ];
-	uint8_t					clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
-	uint8_t					clientSecretKey[ crypto_box_SECRETKEYBYTES ];
-	uint8_t					clientMagic[ kDNSCryptClientMagicLength ];
-	uint8_t					clientNonce[ kDNSCryptHalfNonceLength ];
-	uint8_t					nmKey[ crypto_box_BEFORENMBYTES ];
-	uint8_t					msgBuf[ 512 ];
-	
-}	DNSCryptContext;
-
-static void		DNSCryptReceiveCertHandler( void *inContext );
-static void		DNSCryptReceiveResponseHandler( void *inContext );
-static void		DNSCryptProceed( void *inContext );
-static OSStatus	DNSCryptProcessCert( DNSCryptContext *inContext );
-static OSStatus	DNSCryptBuildQuery( DNSCryptContext *inContext );
-static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext );
-static void		DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen );
+static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
+static void	MDNSQueryReadHandler( void *inContext );
 
-static void	DNSCryptCmd( void )
+static void	MDNSQueryCmd( void )
 {
 	OSStatus				err;
-	DNSCryptContext *		context		= NULL;
-	size_t					writtenBytes;
-	size_t					totalBytes;
-	SocketContext *			sockCtx;
-	SocketRef				sock		= kInvalidSocketRef;
-	const char *			ptr;
+	MDNSQueryContext *		context;
+	SocketRef				sockV4 = kInvalidSocketRef;
+	SocketRef				sockV6 = kInvalidSocketRef;
+	ssize_t					n;
+	const char *			ifname;
+	size_t					msgLen;
+	unsigned int			sendCount;
 	
 	// Check command parameters.
 	
-	if( gDNSCrypt_TimeLimitSecs < -1 )
+	if( gMDNSQuery_ReceiveSecs < -1 )
 	{
-		FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
+		FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
 		err = kParamErr;
 		goto exit;
 	}
 	
-	// Create context.
-	
-	context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+	context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
 	require_action( context, exit, err = kNoMemoryErr );
 	
-	context->providerName	= gDNSCrypt_ProviderName;
-	context->qname			= gDNSCrypt_Name;
-	context->timeLimitSecs	= gDNSCrypt_TimeLimitSecs;
-	context->printRawRData	= gDNSCrypt_RawRData ? true : false;
-	
-	err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
-	require_noerr( err, exit );
+	context->qnameStr		= gMDNSQuery_Name;
+	context->receiveSecs	= gMDNSQuery_ReceiveSecs;
+	context->isQU			= gMDNSQuery_IsQU		  ? true : false;
+	context->allResponses	= gMDNSQuery_AllResponses ? true : false;
+	context->printRawRData	= gMDNSQuery_RawRData	  ? true : false;
+	context->useIPv4		= ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
+	context->useIPv6		= ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
 	
-	err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags,
-		context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr );
-	if( err || ( *ptr != '\0' ) )
-	{
-		FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey );
-		goto exit;
-	}
-	else if( totalBytes != sizeof( context->serverPublicSignKey ) )
-	{
-		FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
-			totalBytes, sizeof( context->serverPublicSignKey ) );
-		err = kSizeErr;
-		goto exit;
-	}
-	check( writtenBytes == totalBytes );
+	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+	require_noerr_quiet( err, exit );
 	
-	err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
-	require_noerr( err, exit );
-	if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
+	ifname = if_indextoname( context->ifIndex, context->ifName );
+	require_action( ifname, exit, err = kNameErr );
 	
-	err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
+	err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype );
 	require_noerr( err, exit );
 	
-	// Write query message.
+	// Set up IPv4 socket.
 	
-	context->queryID = (uint16_t) Random32();
-	err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName,
-		kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen );
-	require_noerr( err, exit );
+	if( context->useIPv4 )
+	{
+		err = CreateMulticastSocket( GetMDNSMulticastAddrV4(),
+			gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+			ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV4 );
+		require_noerr( err, exit );
+	}
 	
-	// Create UDP socket.
+	// Set up IPv6 socket.
 	
-	err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
-	require_noerr( err, exit );
+	if( context->useIPv6 )
+	{
+		err = CreateMulticastSocket( GetMDNSMulticastAddrV6(),
+			gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+			ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV6 );
+		require_noerr( err, exit );
+	}
 	
-	// Send DNS query.
+	// Craft mDNS query message.
 	
-	context->sendTicks = UpTicks();
-	err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
+	check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
+	err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
+		context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
 	require_noerr( err, exit );
 	
-	err = SocketContextCreate( sock, context, &sockCtx );
-	require_noerr( err, exit );
-	sock = kInvalidSocketRef;
+	// Print prologue.
 	
-	err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
-		&context->readSource );
-	if( err ) ForgetSocketContext( &sockCtx );
-	require_noerr( err, exit );
+	MDNSQueryPrintPrologue( context );
 	
-	dispatch_resume( context->readSource );
+	// Send mDNS query message.
 	
-	if( context->timeLimitSecs > 0 )
+	sendCount = 0;
+	if( IsValidSocket( sockV4 ) )
 	{
-		dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-			Exit );
+		const struct sockaddr * const		mcastAddr4 = GetMDNSMulticastAddrV4();
+		
+		n = sendto( sockV4, context->msgBuf, msgLen, 0, mcastAddr4, SockAddrGetSize( mcastAddr4 ) );
+		err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
+		if( err )
+		{
+			FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+			ForgetSocket( &sockV4 );
+		}
+		else
+		{
+			++sendCount;
+		}
 	}
-	dispatch_main();
+	if( IsValidSocket( sockV6 ) )
+	{
+		const struct sockaddr * const		mcastAddr6 = GetMDNSMulticastAddrV6();
+		
+		n = sendto( sockV6, context->msgBuf, msgLen, 0, mcastAddr6, SockAddrGetSize( mcastAddr6 ) );
+		err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
+		if( err )
+		{
+			FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+			ForgetSocket( &sockV6 );
+		}
+		else
+		{
+			++sendCount;
+		}
+	}
+	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
 	
-exit:
-	if( context ) free( context );
-	ForgetSocket( &sock );
-	if( err ) exit( 1 );
-}
-
-//===========================================================================================================================
-//	DNSCryptReceiveCertHandler
-//===========================================================================================================================
-
-static void	DNSCryptReceiveCertHandler( void *inContext )
-{
-	OSStatus					err;
-	struct timeval				now;
-	const uint64_t				nowTicks	= UpTicks();
-	SocketContext * const		sockCtx		= (SocketContext *) inContext;
-	DNSCryptContext * const		context		= (DNSCryptContext *) sockCtx->userContext;
-	const DNSHeader *			hdr;
-	sockaddr_ip					fromAddr;
-	const uint8_t *				ptr;
-	const uint8_t *				txtPtr;
-	size_t						txtLen;
-	unsigned int				answerCount, i;
-	uint8_t						targetName[ kDomainNameLengthMax ];
+	// If there's no wait period after the send, then exit.
 	
-	gettimeofday( &now, NULL );
+	if( context->receiveSecs == 0 ) goto exit;
 	
-	dispatch_source_forget( &context->readSource );
+	// Create dispatch read sources for socket(s).
 	
-	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: %{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 ) );
-	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
-	
-	require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
-	
-	hdr = (DNSHeader *) context->msgBuf;
-	require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
-	
-	err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
-	require_noerr( err, exit );
-	
-	err = DomainNameFromString( targetName, context->providerName, NULL );
-	require_noerr( err, exit );
-	
-	answerCount = DNSHeaderGetAnswerCount( hdr );
-	for( i = 0; i < answerCount; ++i )
+	if( IsValidSocket( sockV4 ) )
 	{
-		uint16_t		type;
-		uint16_t		class;
-		uint8_t			name[ kDomainNameLengthMax ];
+		SocketContext *		sockCtx;
 		
-		err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
-			&ptr );
+		err = SocketContextCreate( sockV4, context, &sockCtx );
 		require_noerr( err, exit );
+		sockV4 = kInvalidSocketRef;
 		
-		if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
-		{
-			break;
-		}
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
+			&context->readSourceV4 );
+		if( err ) ForgetSocketContext( &sockCtx );
+		require_noerr( err, exit );
+		
+		dispatch_resume( context->readSourceV4 );
 	}
 	
-	if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
+	if( IsValidSocket( sockV6 ) )
 	{
-		FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
-		err = kSizeErr;
-		goto exit;
+		SocketContext *		sockCtx;
+		
+		err = SocketContextCreate( sockV6, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV6 = kInvalidSocketRef;
+		
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
+			&context->readSourceV6 );
+		if( err ) ForgetSocketContext( &sockCtx );
+		require_noerr( err, exit );
+		
+		dispatch_resume( context->readSourceV6 );
 	}
-	if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
+	
+	if( context->receiveSecs > 0 )
 	{
-		FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
-		err = kSizeErr;
-		goto exit;
+		dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+			Exit );
 	}
-	
-	context->certLen = txtPtr[ 0 ];
-	context->certPtr = &txtPtr[ 1 ];
-	
-	dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
+	dispatch_main();
 	
 exit:
-	if( err ) Exit( NULL );
+	ForgetSocket( &sockV4 );
+	ForgetSocket( &sockV6 );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	DNSCryptReceiveResponseHandler
+//	MDNSColliderCmd
 //===========================================================================================================================
 
-static void	DNSCryptReceiveResponseHandler( void *inContext )
+static void	_MDNSColliderCmdStopHandler( void *inContext, OSStatus inError );
+
+static void	MDNSColliderCmd( void )
 {
-	OSStatus						err;
-	struct timeval					now;
-	const uint64_t					nowTicks	= UpTicks();
-	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;
-	uint8_t							nonce[ crypto_box_NONCEBYTES ];
-	
-	gettimeofday( &now, NULL );
-	
-	dispatch_source_forget( &context->readSource );
-	
-	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 );
+	OSStatus					err;
+	MDNSColliderRef				collider = NULL;
+	uint8_t *					rdataPtr = NULL;
+	size_t						rdataLen = 0;
+	const char *				ifname;
+	uint32_t					ifIndex;
+	MDNSColliderProtocols		protocols;
+	uint16_t					type;
+	char						ifName[ IF_NAMESIZE + 1 ];
+	uint8_t						name[ kDomainNameLengthMax ];
 	
-	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 ) );
+	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+	require_noerr_quiet( err, exit );
 	
-	if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
+	ifname = if_indextoname( ifIndex, ifName );
+	if( !ifname )
 	{
-		FPrintF( stderr, "DNSCrypt response is too short.\n" );
-		err = kSizeErr;
+		FPrintF( stderr, "error: Invalid interface name or index: %s\n", gInterface );
+		err = kNameErr;
 		goto exit;
 	}
 	
-	hdr = (DNSCryptResponseHeader *) context->msgBuf;
-	
-	if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
+	err = DomainNameFromString( name, gMDNSCollider_Name, NULL );
+	if( err )
 	{
-		FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
-			hdr->resolverMagic,		kDNSCryptResolverMagicLength, INT_MAX,
-			kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
-		err = kValueErr;
+		FPrintF( stderr, "error: Invalid record name: %s\n", gMDNSCollider_Name );
 		goto exit;
 	}
 	
-	if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
+	err = RecordTypeFromArgString( gMDNSCollider_Type, &type );
+	require_noerr_quiet( err, exit );
+	
+	if( gMDNSCollider_RecordData )
 	{
-		FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
-		err = kValueErr;
-		goto exit;
+		err = RecordDataFromArgString( gMDNSCollider_RecordData, &rdataPtr, &rdataLen );
+		require_noerr_quiet( err, exit );
 	}
 	
-	memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
+	err = MDNSColliderCreate( dispatch_get_main_queue(), &collider );
+	require_noerr( err, exit );
 	
-	ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
-	memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
+	err = MDNSColliderSetProgram( collider, gMDNSCollider_Program );
+	if( err )
+	{
+		FPrintF( stderr, "error: Failed to set program string: '%s'\n", gMDNSCollider_Program );
+		goto exit;
+	}
 	
-	plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
-	check( plaintext == ciphertext );
+	err = MDNSColliderSetRecord( collider, name, type, rdataPtr, rdataLen );
+	require_noerr( err, exit );
+	ForgetMem( &rdataPtr );
 	
-	end = context->msgBuf + context->msgLen;
+	protocols = kMDNSColliderProtocol_None;
+	if( gMDNSCollider_UseIPv4 || !gMDNSCollider_UseIPv6 ) protocols |= kMDNSColliderProtocol_IPv4;
+	if( gMDNSCollider_UseIPv6 || !gMDNSCollider_UseIPv4 ) protocols |= kMDNSColliderProtocol_IPv6;
+	MDNSColliderSetProtocols( collider, protocols );
+	MDNSColliderSetInterfaceIndex( collider, ifIndex );
+	MDNSColliderSetStopHandler( collider, _MDNSColliderCmdStopHandler, collider );
 	
-	err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
+	err = MDNSColliderStart( collider );
 	require_noerr( err, exit );
 	
-	response = plaintext + crypto_box_ZEROBYTES;
-	FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
-	Exit( kExitReason_ReceivedResponse );
+	dispatch_main();
 	
 exit:
-	if( err ) Exit( NULL );
+	FreeNullSafe( rdataPtr );
+	CFReleaseNullSafe( collider );
+	if( err ) exit( 1 );
+}
+
+static void	_MDNSColliderCmdStopHandler( void *inContext, OSStatus inError )
+{
+	MDNSColliderRef const		collider = (MDNSColliderRef) inContext;
+	
+	CFRelease( collider );
+	exit( inError ? 1 : 0 );
 }
 
 //===========================================================================================================================
-//	DNSCryptProceed
+//	MDNSQueryPrintPrologue
 //===========================================================================================================================
 
-static void	DNSCryptProceed( void *inContext )
+static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
 {
-	OSStatus					err;
-	DNSCryptContext * const		context = (DNSCryptContext *) inContext;
-	
-	err = DNSCryptProcessCert( context );
-	require_noerr_quiet( err, exit );
-	
-	err = DNSCryptBuildQuery( context );
-	require_noerr_quiet( err, exit );
-	
-	err = DNSCryptSendQuery( context );
-	require_noerr_quiet( err, exit );
+	const int		receiveSecs = inContext->receiveSecs;
 	
-exit:
-	if( err ) Exit( NULL );
+	FPrintF( stdout, "Interface:        %d (%s)\n",		(int32_t) inContext->ifIndex, inContext->ifName );
+	FPrintF( stdout, "Name:             %s\n",			inContext->qnameStr );
+	FPrintF( stdout, "Type:             %s (%u)\n",		RecordTypeToString( inContext->qtype ), inContext->qtype );
+	FPrintF( stdout, "Class:            IN (%s)\n",		inContext->isQU ? "QU" : "QM" );
+	FPrintF( stdout, "Local port:       %d\n",			inContext->localPort );
+	FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
+		inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+	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:       %{du:time}\n",	NULL );
 }
 
 //===========================================================================================================================
-//	DNSCryptProcessCert
+//	MDNSQueryReadHandler
 //===========================================================================================================================
 
-static OSStatus	DNSCryptProcessCert( DNSCryptContext *inContext )
+static void	MDNSQueryReadHandler( void *inContext )
 {
 	OSStatus						err;
-	const DNSCryptCert * const		cert	= (DNSCryptCert *) inContext->certPtr;
-	const uint8_t * const			certEnd	= inContext->certPtr + inContext->certLen;
 	struct timeval					now;
-	time_t							startTimeSecs, endTimeSecs;
-	size_t							signedLen;
-	uint8_t *						tempBuf;
-	unsigned long long				tempLen;
-	
-	DNSCryptPrintCertificate( cert, inContext->certLen );
+	SocketContext * const			sockCtx = (SocketContext *) inContext;
+	MDNSQueryContext * const		context = (MDNSQueryContext *) sockCtx->userContext;
+	size_t							msgLen;
+	sockaddr_ip						fromAddr;
+	Boolean							foundAnswer	= false;
 	
-	if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
-	{
-		FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
-			cert->certMagic,	kDNSCryptCertMagicLength, INT_MAX,
-			kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
-		err = kValueErr;
-		goto exit;
-	}
+	gettimeofday( &now, NULL );
 	
-	startTimeSecs	= (time_t) ReadBig32( cert->startTime );
-	endTimeSecs		= (time_t) ReadBig32( cert->endTime );
+	err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
+		sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+	require_noerr( err, exit );
 	
-	gettimeofday( &now, NULL );
-	if( now.tv_sec < startTimeSecs )
+	if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
 	{
-		FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
-		err = kDateErr;
-		goto exit;
+		const uint8_t *				ptr;
+		const DNSHeader * const		hdr = (DNSHeader *) context->msgBuf;
+		unsigned int				rrCount, i;
+		uint16_t					type, class;
+		uint8_t						name[ kDomainNameLengthMax ];
+		
+		err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
+		require_noerr( err, exit );
+		
+		if( context->qname[ 0 ] == 0 )
+		{
+			err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
+			require_noerr( err, exit );
+		}
+		
+		rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
+		for( i = 0; i < rrCount; ++i )
+		{
+			err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
+			require_noerr( err, exit );
+			
+			if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
+				DomainNameEqual( name, context->qname ) )
+			{
+				foundAnswer = true;
+				break;
+			}
+		}
 	}
-	if( now.tv_sec >= endTimeSecs )
+	if( context->allResponses || foundAnswer )
 	{
-		FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
-		err = kDateErr;
-		goto exit;
+		FPrintF( stdout, "---\n" );
+		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 );
 	}
 	
-	signedLen = (size_t)( certEnd - cert->signature );
-	tempBuf = (uint8_t *) malloc( signedLen );
-	require_action( tempBuf, exit, err = kNoMemoryErr );
-	err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey );
-	free( tempBuf );
-	if( err )
-	{
-		FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
-		err = kAuthenticationErr;
-		goto exit;
-	}
-	
-	memcpy( inContext->serverPublicKey,	cert->publicKey,	crypto_box_PUBLICKEYBYTES );
-	memcpy( inContext->clientMagic,		cert->clientMagic,	kDNSCryptClientMagicLength );
-	
-	err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey );
-	require_noerr( err, exit );
-	
-	inContext->certPtr	= NULL;
-	inContext->certLen	= 0;
-	inContext->msgLen	= 0;
-	
 exit:
-	return( err );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	DNSCryptBuildQuery
+//	PIDToUUIDCmd
 //===========================================================================================================================
 
-static OSStatus	DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen );
-
-static OSStatus	DNSCryptBuildQuery( DNSCryptContext *inContext )
-{
-	OSStatus						err;
-	DNSCryptQueryHeader * const		hdr			= (DNSCryptQueryHeader *) inContext->msgBuf;
-	uint8_t * const					queryPtr	= (uint8_t *)( hdr + 1 );
-	size_t							queryLen;
-	size_t							paddedQueryLen;
-	const uint8_t * const			msgLimit	= inContext->msgBuf + sizeof( inContext->msgBuf );
-	const uint8_t *					padLimit;
-	uint8_t							nonce[ crypto_box_NONCEBYTES ];
-	
-	check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) );
-	
-	inContext->queryID = (uint16_t) Random32();
-	err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname,
-		inContext->qtype, kDNSServiceClass_IN, &queryLen );
-	require_noerr( err, exit );
-	
-	padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
-	if( padLimit > msgLimit ) padLimit = msgLimit;
-	
-	err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
-	require_noerr( err, exit );
-	
-	memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
-	RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
-	memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
-	memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
-	
-	err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
-		paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
-	require_noerr( err, exit );
-	
-	memcpy( hdr->clientMagic,		inContext->clientMagic,		kDNSCryptClientMagicLength );
-	memcpy( hdr->clientPublicKey,	inContext->clientPublicKey,	crypto_box_PUBLICKEYBYTES );
-	memcpy( hdr->clientNonce,		nonce,						kDNSCryptHalfNonceLength );
-	
-	inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
-	
-exit:
-	return( err );
-}
-
-static OSStatus	DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen )
+static void	PIDToUUIDCmd( void )
 {
-	OSStatus		err;
-	size_t			paddedLen;
-	
-	require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr );
-	
-	paddedLen = inMsgLen + kDNSCryptMinPadLength +
-		arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) );
-	paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) );
-	if( paddedLen > inMaxLen ) paddedLen = inMaxLen;
+	OSStatus							err;
+	int									n;
+	struct proc_uniqidentifierinfo		info;
 	
-	inMsgPtr[ inMsgLen ] = 0x80;
-	memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
+	n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
+	require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
 	
-	if( outPaddedLen ) *outPaddedLen = paddedLen;
+	FPrintF( stdout, "%#U\n", info.p_uuid );
 	err = kNoErr;
 	
 exit:
-	return( err );
+	if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//	DNSCryptSendQuery
+//	DNSServerCmd
 //===========================================================================================================================
 
-static OSStatus	DNSCryptSendQuery( DNSCryptContext *inContext )
+typedef struct DNSServerPrivate *		DNSServerRef;
+
+typedef struct
 {
-	OSStatus			err;
-	SocketContext *		sockCtx;
-	SocketRef			sock = kInvalidSocketRef;
-	
-	check( inContext->msgLen > 0 );
-	check( !inContext->readSource );
-	
-	err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock );
-	require_noerr( err, exit );
-	
-	inContext->sendTicks = UpTicks();
-	err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
-	require_noerr( err, exit );
-	
-	err = SocketContextCreate( sock, inContext, &sockCtx );
-	require_noerr( err, exit );
-	sock = kInvalidSocketRef;
-	
-	err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
-		&inContext->readSource );
-	if( err ) ForgetSocketContext( &sockCtx );
-	require_noerr( err, exit );
+	DNSServerRef			server;			// Reference to the DNS server.
+	dispatch_source_t		sigIntSource;	// Dispatch SIGINT source.
+	dispatch_source_t		sigTermSource;	// Dispatch SIGTERM source.
+	const char *			domainOverride;	// If non-NULL, the server is to use this domain instead of "d.test.".
+#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, if any. (If it exits, we exit).
+	Boolean					addedResolver;	// 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.
 	
-	dispatch_resume( inContext->readSource );
+}	DNSServerCmdContext;
+
+typedef enum
+{
+	kDNSServerEvent_Started	= 1,
+	kDNSServerEvent_Stopped	= 2
 	
-exit:
-	ForgetSocket( &sock );
-	return( err );
-}
+}	DNSServerEventType;
 
-//===========================================================================================================================
-//	DNSCryptPrintCertificate
-//===========================================================================================================================
+typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
 
-#define kCertTimeStrBufLen		32
+CFTypeID	DNSServerGetTypeID( void );
+static OSStatus
+	DNSServerCreate(
+		dispatch_queue_t		inQueue,
+		DNSServerEventHandler_f	inEventHandler,
+		void *					inEventContext,
+		unsigned int			inResponseDelayMs,
+		uint32_t				inDefaultTTL,
+		int						inPort,
+		Boolean					inLoopbackOnly,
+		const char *			inDomain,
+		Boolean					inBadUDPMode,
+		DNSServerRef *			outServer );
+static void	DNSServerStart( DNSServerRef inServer );
+static void	DNSServerStop( DNSServerRef inServer );
 
-static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+#define ForgetDNSServer( X )		ForgetCustomEx( X, DNSServerStop, CFRelease )
 
-static void	DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
+static void	DNSServerCmdContextFree( DNSServerCmdContext *inContext );
+static void	DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, 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 )
 {
-	time_t		startTime, endTime;
-	int			extLen;
-	char		timeBuf[ kCertTimeStrBufLen ];
+	OSStatus					err;
+	DNSServerCmdContext *		context = NULL;
 	
-	check( inLen >= kDNSCryptCertMinimumLength );
+	if( gDNSServer_Foreground )
+	{
+		LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+	}
 	
-	startTime	= (time_t) ReadBig32( inCert->startTime );
-	endTime		= (time_t) ReadBig32( inCert->endTime );
+	err = CheckIntegerArgument( gDNSServer_ResponseDelayMs, "response delay (ms)", 0, INT_MAX );
+	require_noerr_quiet( err, exit );
 	
-	FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen );
-	FPrintF( stdout, "Cert Magic:    %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX );
-	FPrintF( stdout, "ES Version:    %u\n",	ReadBig16( inCert->esVersion ) );
-	FPrintF( stdout, "Minor Version: %u\n",	ReadBig16( inCert->minorVersion ) );
-	FPrintF( stdout, "Signature:     %H\n",	inCert->signature, crypto_sign_BYTES / 2, INT_MAX );
-	FPrintF( stdout, "               %H\n",	&inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX );
-	FPrintF( stdout, "Public Key:    %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX );
-	FPrintF( stdout, "Client Magic:  %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX );
-	FPrintF( stdout, "Serial:        %u\n",	ReadBig32( inCert->serial ) );
-	FPrintF( stdout, "Start Time:    %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) );
-	FPrintF( stdout, "End Time:      %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) );
+	err = CheckIntegerArgument( gDNSServer_DefaultTTL, "default TTL", 0, INT32_MAX );
+	require_noerr_quiet( err, exit );
 	
-	if( inLen > kDNSCryptCertMinimumLength )
-	{
-		extLen = (int)( inLen - kDNSCryptCertMinimumLength );
-		FPrintF( stdout, "Extensions:    %.1H\n", inCert->extensions, extLen, extLen );
-	}
-	FPrintF( stdout, "\n" );
-}
-
-static char *	CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
-{
-	struct tm *		tm;
+	err = CheckIntegerArgument( gDNSServer_Port, "port number", -UINT16_MAX, UINT16_MAX );
+	require_noerr_quiet( err, exit );
 	
-	tm = localtime( &inTime );
-	if( !tm )
+	context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->domainOverride	= gDNSServer_DomainOverride;
+	context->loopbackOnly	= gDNSServer_LoopbackOnly ? true : false;
+	
+#if( TARGET_OS_DARWIN )
+	if( gDNSServer_FollowPID )
 	{
-		dlogassert( "localtime() returned a NULL pointer.\n" );
-		*inBuffer = '\0';
+		err = StringToPID( gDNSServer_FollowPID, &context->followPID );
+		if( err || ( context->followPID < 0 ) )
+		{
+			FPrintF( stderr, "error: Invalid follow PID: %s\n", gDNSServer_FollowPID );
+			err = kParamErr;
+			goto exit;
+		}
+		
+		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
 	{
-		strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
+		context->followPID = -1;
 	}
+#endif
 	
-	return( inBuffer );
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->sigIntSource );
+	
+	signal( SIGTERM, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
+	require_noerr( err, exit );
+	dispatch_resume( context->sigTermSource );
+	
+	err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context,
+		(unsigned int) gDNSServer_ResponseDelayMs, (uint32_t) gDNSServer_DefaultTTL, gDNSServer_Port, context->loopbackOnly,
+		context->domainOverride, gDNSServer_BadUDPMode ? true : false, &context->server );
+	require_noerr( err, exit );
+	
+	DNSServerStart( context->server );
+	dispatch_main();
+	
+exit:
+	FPrintF( stderr, "Failed to start DNS server: %#m\n", err );
+	if( context ) DNSServerCmdContextFree( context );
+	if( err ) exit( 1 );
 }
 
-#endif	// DNSSDUTIL_INCLUDE_DNSCRYPT
+//===========================================================================================================================
+//	DNSServerCmdContextFree
+//===========================================================================================================================
+
+static void	DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+{
+	ForgetCF( &inContext->server );
+	dispatch_source_forget( &inContext->sigIntSource );
+	dispatch_source_forget( &inContext->sigTermSource );
+#if( TARGET_OS_DARWIN )
+	dispatch_source_forget( &inContext->processMonitor );
+#endif
+	free( inContext );
+}
 
 //===========================================================================================================================
-//	MDNSQueryCmd
+//	DNSServerCmdEventHandler
 //===========================================================================================================================
 
-typedef struct
+#if( TARGET_OS_DARWIN )
+static OSStatus	_DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort );
+static OSStatus	_DNSServerCmdLoopbackResolverRemove( void );
+#endif
+
+static void	DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext )
 {
-	const char *			qnameStr;							// Name (QNAME) of the record being queried as a C string.
-	dispatch_source_t		readSourceV4;						// Read dispatch source for IPv4 socket.
-	dispatch_source_t		readSourceV6;						// Read dispatch source for IPv6 socket.
-	int						localPort;							// The port number to which the sockets are bound.
-	int						receiveSecs;						// After send, the amount of time to spend receiving.
-	uint32_t				ifIndex;							// Index of the interface over which to send the query.
-	uint16_t				qtype;								// The type (QTYPE) of the record being queried.
-	Boolean					isQU;								// True if the query is QU, i.e., requests unicast responses.
-	Boolean					allResponses;						// True if all mDNS messages received should be printed.
-	Boolean					printRawRData;						// True if RDATA should be printed as hexdumps.
-	Boolean					useIPv4;							// True if the query should be sent via IPv4 multicast.
-	Boolean					useIPv6;							// True if the query should be sent via IPv6 multicast.
-	char					ifName[ IF_NAMESIZE + 1 ];			// Name of the interface over which to send the query.
-	uint8_t					qname[ kDomainNameLengthMax ];		// Buffer to hold the QNAME in DNS label format.
-	uint8_t					msgBuf[ 8940 ];						// Message buffer. 8940 is max size used by mDNSResponder.
-	
-}	MDNSQueryContext;
-
-static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
-static void	MDNSQueryReadHandler( void *inContext );
-
-static void	MDNSQueryCmd( void )
-{
-	OSStatus				err;
-	MDNSQueryContext *		context;
-	struct sockaddr_in		mcastAddr4;
-	struct sockaddr_in6		mcastAddr6;
-	SocketRef				sockV4 = kInvalidSocketRef;
-	SocketRef				sockV6 = kInvalidSocketRef;
-	ssize_t					n;
-	const char *			ifNamePtr;
-	size_t					msgLen;
-	unsigned int			sendCount;
-	
-	// Check command parameters.
-	
-	if( gMDNSQuery_ReceiveSecs < -1 )
-	{
-		FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
-		err = kParamErr;
-		goto exit;
-	}
-	
-	context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
-	require_action( context, exit, err = kNoMemoryErr );
-	
-	context->qnameStr		= gMDNSQuery_Name;
-	context->receiveSecs	= gMDNSQuery_ReceiveSecs;
-	context->isQU			= gMDNSQuery_IsQU		  ? true : false;
-	context->allResponses	= gMDNSQuery_AllResponses ? true : false;
-	context->printRawRData	= gMDNSQuery_RawRData	  ? true : false;
-	context->useIPv4		= ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
-	context->useIPv6		= ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
-	
-	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
-	require_noerr_quiet( err, exit );
-	
-	ifNamePtr = if_indextoname( context->ifIndex, context->ifName );
-	require_action( ifNamePtr, exit, err = kNameErr );
-	
-	err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype );
-	require_noerr( err, exit );
-	
-	// Set up IPv4 socket.
-	
-	if( context->useIPv4 )
-	{
-		err = ServerSocketOpen( AF_INET, SOCK_DGRAM, IPPROTO_UDP,
-			gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
-			&context->localPort, kSocketBufferSize_DontSet, &sockV4 );
-		require_noerr( err, exit );
-		
-		err = SocketSetMulticastInterface( sockV4, ifNamePtr, 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 );
-		
-		memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
-		SIN_LEN_SET( &mcastAddr4 );
-		mcastAddr4.sin_family		= AF_INET;
-		mcastAddr4.sin_port			= htons( kMDNSPort );
-		mcastAddr4.sin_addr.s_addr	= htonl( 0xE00000FB );	// The mDNS IPv4 multicast address is 224.0.0.251
-		
-		if( !context->isQU && ( context->localPort == kMDNSPort ) )
-		{
-			err = SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
-			require_noerr( err, exit );
-		}
-	}
-	
-	// Set up IPv6 socket.
+	OSStatus						err;
+	DNSServerCmdContext * const		context = (DNSServerCmdContext *) inContext;
 	
-	if( context->useIPv6 )
+	if( inType == kDNSServerEvent_Started )
 	{
-		err = ServerSocketOpen( AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
-			gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
-			&context->localPort, kSocketBufferSize_DontSet, &sockV6 );
-		require_noerr( err, exit );
-		
-		err = SocketSetMulticastInterface( sockV6, ifNamePtr, 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 );
-		
-		memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
-		SIN6_LEN_SET( &mcastAddr6 );
-		mcastAddr6.sin6_family	= AF_INET6;
-		mcastAddr6.sin6_port	= htons( kMDNSPort );
-		mcastAddr6.sin6_addr.s6_addr[  0 ] = 0xFF;	// mDNS IPv6 multicast address FF02::FB
-		mcastAddr6.sin6_addr.s6_addr[  1 ] = 0x02;
-		mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0xFB;
+	#if( TARGET_OS_DARWIN )
+		const int		port = (int) inEventData;
 		
-		if( !context->isQU && ( context->localPort == kMDNSPort ) )
-		{
-			err = SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
-			require_noerr( err, exit );
-		}
-	}
-	
-	// Craft mDNS query message.
-	
-	check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
-	err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
-		context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
-	require_noerr( err, exit );
-	
-	// Print prologue.
-	
-	MDNSQueryPrintPrologue( context );
-	
-	// Send mDNS query message.
-	
-	sendCount = 0;
-	if( IsValidSocket( sockV4 ) )
-	{
-		n = sendto( sockV4, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) );
-		err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
+		err = _DNSServerCmdLoopbackResolverAdd( context->domainOverride ? context->domainOverride : "d.test.", port );
 		if( err )
 		{
-			FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
-			ForgetSocket( &sockV4 );
+			ds_ulog( kLogLevelError, "Failed to add loopback resolver to DNS configuration for \"d.test.\" domain: %#m\n",
+				err );
+			if( context->loopbackOnly ) ForgetDNSServer( &context->server );
 		}
 		else
 		{
-			++sendCount;
+			context->addedResolver = true;
 		}
+	#endif
 	}
-	if( IsValidSocket( sockV6 ) )
+	else if( inType == kDNSServerEvent_Stopped )
 	{
-		n = sendto( sockV6, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) );
-		err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
-		if( err )
+		const OSStatus		stopError = (OSStatus) inEventData;
+		
+		if( stopError ) ds_ulog( kLogLevelError, "The server stopped unexpectedly with error: %#m.\n", stopError );
+		
+		err = kNoErr;
+	#if( TARGET_OS_DARWIN )
+		if( context->addedResolver )
 		{
-			FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
-			ForgetSocket( &sockV6 );
+			err = _DNSServerCmdLoopbackResolverRemove();
+			if( err )
+			{
+				ds_ulog( kLogLevelError, "Failed to remove loopback resolver from DNS configuration: %#m\n", err );
+			}
+			else
+			{
+				context->addedResolver = false;
+			}
 		}
-		else
+		else if( context->loopbackOnly )
 		{
-			++sendCount;
+			err = kUnknownErr;
 		}
+	#endif
+		DNSServerCmdContextFree( context );
+		exit( ( stopError || err ) ? 1 : 0 );
 	}
-	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//	_DNSServerCmdLoopbackResolverAdd
+//===========================================================================================================================
+
+static OSStatus	_DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort )
+{
+	OSStatus				err;
+	SCDynamicStoreRef		store;
+	CFPropertyListRef		plist		= NULL;
+	CFStringRef				key			= NULL;
+	const uint32_t			loopbackV4	= htonl( INADDR_LOOPBACK );
+	Boolean					success;
 	
-	// If there's no wait period after the send, then exit.
+	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+	err = map_scerror( store );
+	require_noerr( err, exit );
 	
-	if( context->receiveSecs == 0 ) goto exit;
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO="
+			"["
+				"%s"
+			"]"
+			"%kO="
+			"["
+				"%.4a"
+				"%.16a"
+			"]"
+			"%kO=%i"
+		"}",
+		kSCPropNetDNSSupplementalMatchDomains,	inDomain,
+		kSCPropNetDNSServerAddresses,			&loopbackV4, in6addr_loopback.s6_addr,
+		kSCPropNetDNSServerPort,				inPort );
+	require_noerr( err, exit );
 	
-	// Create dispatch read sources for socket(s).
+	key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+		CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+	require_action( key, exit, err = kUnknownErr );
 	
-	if( IsValidSocket( sockV4 ) )
-	{
-		SocketContext *		sockCtx;
-		
-		err = SocketContextCreate( sockV4, context, &sockCtx );
-		require_noerr( err, exit );
-		sockV4 = kInvalidSocketRef;
-		
-		err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
-			&context->readSourceV4 );
-		if( err ) ForgetSocketContext( &sockCtx );
-		require_noerr( err, exit );
-		
-		dispatch_resume( context->readSourceV4 );
-	}
+	success = SCDynamicStoreSetValue( store, key, plist );
+	require_action( success, exit, err = kUnknownErr );
 	
-	if( IsValidSocket( sockV6 ) )
-	{
-		SocketContext *		sockCtx;
-		
-		err = SocketContextCreate( sockV6, context, &sockCtx );
-		require_noerr( err, exit );
-		sockV6 = kInvalidSocketRef;
-		
-		err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
-			&context->readSourceV6 );
-		if( err ) ForgetSocketContext( &sockCtx );
-		require_noerr( err, exit );
-		
-		dispatch_resume( context->readSourceV6 );
-	}
+exit:
+	CFReleaseNullSafe( store );
+	CFReleaseNullSafe( plist );
+	CFReleaseNullSafe( key );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DNSServerCmdLoopbackResolverRemove
+//===========================================================================================================================
+
+static OSStatus	_DNSServerCmdLoopbackResolverRemove( void )
+{
+	OSStatus				err;
+	SCDynamicStoreRef		store;
+	CFStringRef				key = NULL;
+	Boolean					success;
 	
-	if( context->receiveSecs > 0 )
-	{
-		dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-			Exit );
-	}
-	dispatch_main();
+	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:
-	ForgetSocket( &sockV4 );
-	ForgetSocket( &sockV6 );
-	if( err ) exit( 1 );
+	CFReleaseNullSafe( store );
+	CFReleaseNullSafe( key );
+	return( err );
 }
+#endif
 
 //===========================================================================================================================
-//	MDNSQueryPrintPrologue
+//	DNSServerCmdSigIntHandler
 //===========================================================================================================================
 
-static void	MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
+static void	_DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal );
+
+static void	DNSServerCmdSigIntHandler( void *inContext )
 {
-	const int		receiveSecs = inContext->receiveSecs;
-	
-	FPrintF( stdout, "Interface:        %d (%s)\n",		(int32_t) inContext->ifIndex, inContext->ifName );
-	FPrintF( stdout, "Name:             %s\n",			inContext->qnameStr );
-	FPrintF( stdout, "Type:             %s (%u)\n",		RecordTypeToString( inContext->qtype ), inContext->qtype );
-	FPrintF( stdout, "Class:            IN (%s)\n",		inContext->isQU ? "QU" : "QM" );
-	FPrintF( stdout, "Local port:       %d\n",			inContext->localPort );
-	FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
-		inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
-	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:       %{du:time}\n",	NULL );
+	_DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGINT );
 }
 
 //===========================================================================================================================
-//	MDNSQueryReadHandler
+//	DNSServerCmdSigTermHandler
 //===========================================================================================================================
 
-static void	MDNSQueryReadHandler( void *inContext )
+static void	DNSServerCmdSigTermHandler( void *inContext )
 {
-	OSStatus						err;
-	struct timeval					now;
-	SocketContext * const			sockCtx = (SocketContext *) inContext;
-	MDNSQueryContext * const		context = (MDNSQueryContext *) sockCtx->userContext;
-	size_t							msgLen;
-	sockaddr_ip						fromAddr;
-	Boolean							foundAnswer	= false;
-	
-	gettimeofday( &now, NULL );
-	
-	err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
-		sizeof( fromAddr ), NULL, NULL, NULL, NULL );
-	require_noerr( err, exit );
-	
-	if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
-	{
-		const uint8_t *				ptr;
-		const DNSHeader * const		hdr = (DNSHeader *) context->msgBuf;
-		unsigned int				rrCount, i;
-		uint16_t					type, class;
-		uint8_t						name[ kDomainNameLengthMax ];
-		
-		err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
-		require_noerr( err, exit );
-		
-		if( context->qname[ 0 ] == 0 )
-		{
-			err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
-			require_noerr( err, exit );
-		}
-		
-		rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
-		for( i = 0; i < rrCount; ++i )
-		{
-			err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
-			require_noerr( err, exit );
-			
-			if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
-				DomainNameEqual( name, context->qname ) )
-			{
-				foundAnswer = true;
-				break;
-			}
-		}
-	}
-	if( context->allResponses || foundAnswer )
-	{
-		FPrintF( stdout, "---\n" );
-		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:
-	if( err ) exit( 1 );
+	_DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGTERM );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//	PIDToUUIDCmd
+//	DNSServerCmdFollowedProcessHandler
 //===========================================================================================================================
 
-static void	PIDToUUIDCmd( void )
+static void	DNSServerCmdFollowedProcessHandler( void *inContext )
 {
-	OSStatus							err;
-	int									n;
-	struct proc_uniqidentifierinfo		info;
-	
-	n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
-	require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
-	
-	FPrintF( stdout, "%#U\n", info.p_uuid );
-	err = kNoErr;
+	DNSServerCmdContext * const		context = (DNSServerCmdContext *) inContext;
 	
-exit:
-	if( err ) exit( 1 );
+	if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( context, 0 );
 }
+#endif
 
 //===========================================================================================================================
-//	DNSServerCmd
+//	_DNSServerCmdExternalExit
 //===========================================================================================================================
 
-typedef uint32_t		DNSServerEventType;
-#define kDNSServerEvent_Started		1
-#define kDNSServerEvent_Stopped		2
-
-typedef struct DNSServerPrivate *		DNSServerRef;
+#define SignalNumberToString( X ) (		\
+	( (X) == SIGINT )  ? "SIGINT"  :	\
+	( (X) == SIGTERM ) ? "SIGTERM" :	\
+						 "???" )
 
-typedef struct
+static void	_DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal )
 {
-	DNSServerRef			server;				// Reference to the DNS server.
-	dispatch_source_t		sigIntSource;		// Dispatch SIGINT source.
-	dispatch_source_t		sigTermSource;		// Dispatch SIGTERM source.
+	dispatch_source_forget( &inContext->sigIntSource );
+	dispatch_source_forget( &inContext->sigTermSource );
 #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.
+	dispatch_source_forget( &inContext->processMonitor );
+	
+	if( inSignal == 0 )
+	{
+		ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
+	}
+	else
 #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.
+	{
+		ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
+	}
 	
-}	DNSServerCmdContext;
+	ForgetDNSServer( &inContext->server );
+}
 
-typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext );
+//===========================================================================================================================
+//	DNSServerCreate
+//===========================================================================================================================
+
+#define kDDotTestDomainName		(const uint8_t *) "\x01" "d" "\x04" "test"
+
+typedef struct DNSDelayedResponse		DNSDelayedResponse;
+struct DNSDelayedResponse
+{
+	DNSDelayedResponse *		next;
+	sockaddr_ip					destAddr;
+	uint64_t					targetTicks;
+	uint8_t *					msgPtr;
+	size_t						msgLen;
+};
+
+struct DNSServerPrivate
+{
+	CFRuntimeBase				base;				// CF object base.
+	uint8_t *					domain;				// Parent domain of server's resource records.
+	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.
+	SocketRef					sockUDPv4;
+	SocketRef					sockUDPv6;
+	DNSServerEventHandler_f		eventHandler;
+	void *						eventContext;
+	DNSDelayedResponse *		responseList;
+	dispatch_source_t			responseTimer;
+	unsigned int				responseDelayMs;
+	uint32_t					defaultTTL;
+	uint32_t					serial;				// Serial number for SOA record.
+	int							port;				// Port to use for receiving and sending DNS messages.
+	OSStatus					stopError;
+	Boolean						stopped;
+	Boolean						loopbackOnly;
+	Boolean						badUDPMode;			// True if the server runs in Bad UDP mode.
+};
+
+static void	_DNSServerUDPReadHandler( void *inContext );
+static void	_DNSServerTCPReadHandler( void *inContext );
+static void	_DNSDelayedResponseFree( DNSDelayedResponse *inResponse );
+static void	_DNSDelayedResponseFreeList( DNSDelayedResponse *inList );
+
+CF_CLASS_DEFINE( DNSServer );
 
-CFTypeID	DNSServerGetTypeID( void );
 static OSStatus
 	DNSServerCreate(
 		dispatch_queue_t		inQueue,
 		DNSServerEventHandler_f	inEventHandler,
 		void *					inEventContext,
-		int						inResponseDelayMs,
+		unsigned int			inResponseDelayMs,
+		uint32_t				inDefaultTTL,
+		int						inPort,
 		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 )
+		const char *			inDomain,
+		Boolean					inBadUDPMode,
+		DNSServerRef *			outServer )
 {
-	OSStatus					err;
-	DNSServerCmdContext *		context;
-	
-	context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
-	require_action( context, exit, err = kNoMemoryErr );
-	
-	context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false;
+	OSStatus			err;
+	DNSServerRef		obj = NULL;
 	
-#if( TARGET_OS_DARWIN )
-	if( gDNSServer_FollowPID )
-	{
-		long long		value;
-		
-		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 = 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
+	require_action_quiet( inDefaultTTL <= INT32_MAX, exit, err = kRangeErr );
 	
-	signal( SIGINT, SIG_IGN );
-	err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
-	require_noerr( err, exit );
-	dispatch_resume( context->sigIntSource );
+	CF_OBJECT_CREATE( DNSServer, obj, err, exit );
 	
-	signal( SIGTERM, SIG_IGN );
-	err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
-	require_noerr( err, exit );
-	dispatch_resume( context->sigTermSource );
+	ReplaceDispatchQueue( &obj->queue, inQueue );
+	obj->eventHandler		= inEventHandler;
+	obj->eventContext		= inEventContext;
+	obj->responseDelayMs	= inResponseDelayMs;
+	obj->defaultTTL			= inDefaultTTL;
+	obj->port				= inPort;
+	obj->loopbackOnly		= inLoopbackOnly;
+	obj->badUDPMode			= inBadUDPMode;
 	
-	if( gDNSServer_Foreground )
+	if( inDomain )
 	{
-		LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+		err = StringToDomainName( inDomain, &obj->domain, NULL );
+		require_noerr_quiet( err, exit );
 	}
-	
-	if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) )
+	else
 	{
-		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;
+		err = DomainNameDup( kDDotTestDomainName, &obj->domain, NULL );
+		require_noerr_quiet( err, exit );
 	}
 	
-	err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs,
-		context->loopbackOnly, &context->server );
-	require_noerr( err, exit );
-	
-	DNSServerStart( context->server );
-	dispatch_main();
+	*outServer = obj;
+	obj = NULL;
+	err = kNoErr;
 	
 exit:
-	ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err );
-	if( context ) DNSServerCmdContextFree( context );
-	if( err ) exit( 1 );
+	CFReleaseNullSafe( obj );
+	return( err );
 }
 
 //===========================================================================================================================
-//	DNSServerCmdContextFree
+//	_DNSServerFinalize
 //===========================================================================================================================
 
-static void	DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+static void	_DNSServerFinalize( CFTypeRef inObj )
 {
-	ForgetCF( &inContext->server );
-	dispatch_source_forget( &inContext->sigIntSource );
-	dispatch_source_forget( &inContext->sigTermSource );
-	dispatch_source_forget( &inContext->processMonitor );
-	free( inContext );
+	DNSServerRef const		me = (DNSServerRef) inObj;
+	
+	check( !me->readSourceUDPv4 );
+	check( !me->readSourceUDPv6 );
+	check( !me->readSourceTCPv4 );
+	check( !me->readSourceTCPv6 );
+	check( !me->responseTimer );
+	ForgetMem( &me->domain );
+	dispatch_forget( &me->queue );
 }
 
 //===========================================================================================================================
-//	DNSServerCmdEventHandler
+//	DNSServerStart
 //===========================================================================================================================
 
-#if( TARGET_OS_DARWIN )
-static OSStatus	_DNSServerCmdRegisterResolver( void );
-static OSStatus	_DNSServerCmdUnregisterResolver( void );
-#endif
+static void	_DNSServerStart( void *inContext );
+static void	_DNSServerStop( void *inContext, OSStatus inError );
 
-static void	DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext )
-{
-	DNSServerCmdContext * const		context = (DNSServerCmdContext *) inContext;
-#if( TARGET_OS_DARWIN )
-	OSStatus						err;
-#endif
-	
-	if( inType == kDNSServerEvent_Started )
-	{
-		context->serverStarted = true;
-	#if( TARGET_OS_DARWIN )
-		err = _DNSServerCmdRegisterResolver();
-		if( err )
-		{
-			ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err );
-			if( context->loopbackOnly ) exit( 1 );
-		}
-		else
-		{
-			context->resolverRegistered = true;
-		}
-	#endif
-	}
-	else if( inType == kDNSServerEvent_Stopped )
-	{
-	#if( TARGET_OS_DARWIN )
-		if( context->resolverRegistered )
-		{
-			err = _DNSServerCmdUnregisterResolver();
-			if( err )
-			{
-				ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
-			}
-			else
-			{
-				context->resolverRegistered = false;
-			}
-		}
-		
-		if( !context->calledStop )
-		{
-			ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" );
-			exit( 1 );
-		}
-	#endif
-		DNSServerCmdContextFree( context );
-	}
-}
-
-#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 )
+static void	DNSServerStart( DNSServerRef me )
 {
 	CFRetain( me );
 	dispatch_async_f( me->queue, me, _DNSServerStart );
@@ -7255,16 +7320,23 @@ static void	DNSServerStart( DNSServerRef me )
 static void	_DNSServerStart( void *inContext )
 {
 	OSStatus				err;
+	struct timeval			now;
 	DNSServerRef const		me			= (DNSServerRef) inContext;
 	SocketRef				sock		= kInvalidSocketRef;
 	SocketContext *			sockCtx		= NULL;
 	const uint32_t			loopbackV4	= htonl( INADDR_LOOPBACK );
+	int						year, month, day;
 	
 	// Create IPv4 UDP socket.
+	// Initially, me->port is the port requested by the user. If it's 0, then the user wants any available ephemeral port.
+	// If it's negative, then the user would like a port number equal to its absolute value, but will settle for any
+	// available ephemeral port, if it's not available. The actual port number that was used will be stored in me->port and
+	// used for the remaining sockets.
 	
 	err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL,
-		kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+		me->port, &me->port, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
 	require_noerr( err, exit );
+	check( me->port > 0 );
 	
 	// Create read source for IPv4 UDP socket.
 	
@@ -7276,12 +7348,13 @@ static void	_DNSServerStart( void *inContext )
 		&me->readSourceUDPv4 );
 	require_noerr( err, exit );
 	dispatch_resume( me->readSourceUDPv4 );
+	me->sockUDPv4 = sockCtx->sock;
 	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 );
+		me->port, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
 	require_noerr( err, exit );
 	
 	// Create read source for IPv6 UDP socket.
@@ -7294,12 +7367,13 @@ static void	_DNSServerStart( void *inContext )
 		&me->readSourceUDPv6 );
 	require_noerr( err, exit );
 	dispatch_resume( me->readSourceUDPv6 );
+	me->sockUDPv6 = sockCtx->sock;
 	sockCtx = NULL;
 	
 	// Create IPv4 TCP socket.
 	
 	err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL,
-		kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+		me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
 	require_noerr( err, exit );
 	
 	// Create read source for IPv4 TCP socket.
@@ -7317,7 +7391,7 @@ static void	_DNSServerStart( void *inContext )
 	// Create IPv6 TCP socket.
 	
 	err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL,
-		kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+		me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
 	require_noerr( err, exit );
 	
 	// Create read source for IPv6 TCP socket.
@@ -7332,46 +7406,62 @@ static void	_DNSServerStart( void *inContext )
 	dispatch_resume( me->readSourceTCPv6 );
 	sockCtx = NULL;
 	
-	CFRetain( me );
-	if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, me->eventContext );
+	ds_ulog( kLogLevelInfo, "Server is using port %d.\n", me->port );
+	if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, (uintptr_t) me->port, me->eventContext );
+	
+	// Create the serial number for the server's SOA record in the YYYMMDDnn convention recommended by
+	// <https://tools.ietf.org/html/rfc1912#section-2.2> using the current time.
+	
+	gettimeofday( &now, NULL );
+	SecondsToYMD_HMS( ( INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay ) + now.tv_sec, &year, &month, &day,
+		NULL, NULL, NULL );
+	me->serial = (uint32_t)( ( year * 1000000 ) + ( month * 10000 ) + ( day * 100 ) + 1 );
 	
 exit:
 	ForgetSocket( &sock );
 	if( sockCtx ) SocketContextRelease( sockCtx );
-	if( err ) DNSServerStop( me );
-	CFRelease( me );
+	if( err ) _DNSServerStop( me, err );
 }
 
 //===========================================================================================================================
 //	DNSServerStop
 //===========================================================================================================================
 
-static void	_DNSServerStop( void *inContext );
+static void	_DNSServerUserStop( void *inContext );
 static void	_DNSServerStop2( void *inContext );
 
 static void	DNSServerStop( DNSServerRef me )
 {
 	CFRetain( me );
-	dispatch_async_f( me->queue, me, _DNSServerStop );
+	dispatch_async_f( me->queue, me, _DNSServerUserStop );
 }
 
-static void	_DNSServerStop( void *inContext )
+static void	_DNSServerUserStop( void *inContext )
 {
-	DNSServerRef const			me = (DNSServerRef) inContext;
-	DNSDelayedResponse *		resp;
+	DNSServerRef const		me = (DNSServerRef) inContext;
 	
-	dispatch_source_forget( &me->readSourceUDPv4 );
+	_DNSServerStop( me, kNoErr );
+	CFRelease( me );
+}
+
+static void	_DNSServerStop( void *inContext, OSStatus inError )
+{
+	DNSServerRef const		me = (DNSServerRef) inContext;
+	
+	me->stopError = inError;
+	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 );
+	me->sockUDPv4 = kInvalidSocketRef;
+	me->sockUDPv6 = kInvalidSocketRef;
 	
-	while( ( resp = me->responseList ) != NULL )
+	if( me->responseList )
 	{
-		me->responseList = resp->next;
-		DNSScheduledResponseFree( resp );
+		_DNSDelayedResponseFreeList( me->responseList );
+		me->responseList = NULL;
 	}
-	
 	dispatch_async_f( me->queue, me, _DNSServerStop2 );
 }
 
@@ -7382,30 +7472,62 @@ static void	_DNSServerStop2( void *inContext )
 	if( !me->stopped )
 	{
 		me->stopped = true;
-		if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, me->eventContext );
+		if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, (uintptr_t) me->stopError, me->eventContext );
 		CFRelease( me );
 	}
 	CFRelease( me );
 }
 
 //===========================================================================================================================
+//	_DNSDelayedResponseFree
+//===========================================================================================================================
+
+static void	_DNSDelayedResponseFree( DNSDelayedResponse *inResponse )
+{
+	ForgetMem( &inResponse->msgPtr );
+	free( inResponse );
+}
+
+//===========================================================================================================================
+//	_DNSDelayedResponseFreeList
+//===========================================================================================================================
+
+static void	_DNSDelayedResponseFreeList( DNSDelayedResponse *inList )
+{
+	DNSDelayedResponse *		response;
+	
+	while( ( response = inList ) != NULL )
+	{
+		inList = response->next;
+		_DNSDelayedResponseFree( response );
+	}
+}
+
+//===========================================================================================================================
 //	_DNSServerUDPReadHandler
 //===========================================================================================================================
 
 static OSStatus
 	_DNSServerAnswerQuery(
+		DNSServerRef	inServer,
 		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 _DNSServerAnswerQueryForUDP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+	_DNSServerAnswerQuery( IN_SERVER, 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 )
+#define _DNSServerAnswerQueryForTCP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+	_DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
 
+static OSStatus
+	_DNSServerScheduleDelayedResponse(
+		DNSServerRef			inServer,
+		const struct sockaddr *	inDestAddr,
+		uint8_t *				inMsgPtr,
+		size_t					inMsgLen );
 static void	_DNSServerUDPDelayedSend( void *inContext );
 
 static void	_DNSServerUDPReadHandler( void *inContext )
@@ -7442,44 +7564,16 @@ static void	_DNSServerUDPReadHandler( void *inContext )
 	
 	// Create response.
 	
-	err = _DNSServerAnswerQueryForUDP( msg, (size_t) n, &responsePtr, &responseLen );
+	err = _DNSServerAnswerQueryForUDP( me, 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;
+		err = _DNSServerScheduleDelayedResponse( me, &clientAddr.sa, responsePtr, responseLen );
+		require_noerr( err, exit );
 		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
 	{
@@ -7495,56 +7589,100 @@ exit:
 	return;
 }
 
+static OSStatus
+	_DNSServerScheduleDelayedResponse(
+		DNSServerRef			me,
+		const struct sockaddr *	inDestAddr,
+		uint8_t *				inMsgPtr,
+		size_t					inMsgLen )
+{
+	OSStatus					err;
+	DNSDelayedResponse *		response;
+	DNSDelayedResponse **		responsePtr;
+	DNSDelayedResponse *		newResponse;
+	uint64_t					targetTicks;
+	
+	targetTicks = UpTicks() + MillisecondsToUpTicks( me->responseDelayMs );
+	
+	newResponse = (DNSDelayedResponse *) calloc( 1, sizeof( *newResponse ) );
+	require_action( newResponse, exit, err = kNoMemoryErr );
+	
+	if( !me->responseList || ( targetTicks < me->responseList->targetTicks ) )
+	{
+		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, me, &me->responseTimer );
+		require_noerr( err, exit );
+		dispatch_resume( me->responseTimer );
+	}
+	
+	SockAddrCopy( inDestAddr, &newResponse->destAddr );
+	newResponse->targetTicks	= targetTicks;
+	newResponse->msgPtr			= inMsgPtr;
+	newResponse->msgLen			= inMsgLen;
+	
+	for( responsePtr = &me->responseList; ( response = *responsePtr ) != NULL; responsePtr = &response->next )
+	{
+		if( newResponse->targetTicks < response->targetTicks ) break;
+	}
+	newResponse->next = response;
+	*responsePtr = newResponse;
+	newResponse = NULL;
+	err = kNoErr;
+	
+exit:
+	if( newResponse ) _DNSDelayedResponseFree( newResponse );
+	return( err );
+}
+
 static void	_DNSServerUDPDelayedSend( void *inContext )
 {
 	OSStatus					err;
-	SocketContext * const		sockCtx		= (SocketContext *) inContext;
-	DNSServerRef const			me			= (DNSServerRef) sockCtx->userContext;
-	DNSDelayedResponse *		resp;
+	DNSServerRef const			me			= (DNSServerRef) inContext;
+	DNSDelayedResponse *		response;
+	SocketRef					sock;
 	ssize_t						n;
 	uint64_t					nowTicks;
+	uint64_t					remainingNs;
 	DNSDelayedResponse *		freeList	= NULL;
 	
 	dispatch_source_forget( &me->responseTimer );
 	
 	nowTicks = UpTicks();
-	while( ( resp = me->responseList ) != NULL )
+	while( ( ( response = me->responseList ) != NULL ) && ( response->targetTicks <= nowTicks ) )
 	{
-		if( resp->targetTicks > nowTicks ) break;
-		me->responseList = resp->next;
+		me->responseList = response->next;
 		
 		ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}",
-			resp->msgLen, resp->msgPtr, resp->msgLen );
+			response->msgLen, response->msgPtr, response->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 );
+		sock = ( response->destAddr.sa.sa_family == AF_INET ) ? me->sockUDPv4 : me->sockUDPv6;
+		n = sendto( sock, (char *) response->msgPtr, response->msgLen, 0, &response->destAddr.sa,
+			SockAddrGetSize( &response->destAddr ) );
+		err = map_socket_value_errno( sock, n == (ssize_t) response->msgLen, n );
 		check_noerr( err );
 		
-		resp->next	= freeList;
-		freeList	= resp;
+		response->next	= freeList;
+		freeList		= response;
 		nowTicks = UpTicks();
 	}
 	
-	if( ( resp = me->responseList ) != NULL )
+	if( response )
 	{
-		uint64_t		remainingNs;
-		
-		remainingNs = UpTicksToNanoseconds( resp->targetTicks - nowTicks );
+		check( response->targetTicks > nowTicks );
+		remainingNs = UpTicksToNanoseconds( response->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 );
+			me->queue, _DNSServerUDPDelayedSend, NULL, me, &me->responseTimer );
 		require_noerr( err, exit );
 		dispatch_resume( me->responseTimer );
 	}
 	
 exit:
-	while( ( resp = freeList ) != NULL )
-	{
-		freeList = resp->next;
-		DNSScheduledResponseFree( resp );
-	}
+	if( freeList ) _DNSDelayedResponseFreeList( freeList );
 }
 
 //===========================================================================================================================
@@ -7553,12 +7691,25 @@ exit:
 
 #define kLabelPrefix_Alias			"alias"
 #define kLabelPrefix_AliasTTL		"alias-ttl"
-#define kLabelPrefix_Count			"count"
-#define kLabelPrefix_TTL			"ttl"
+#define kLabelPrefix_Count			"count-"
+#define kLabelPrefix_Tag			"tag-"
+#define kLabelPrefix_TTL			"ttl-"
 #define kLabel_IPv4					"ipv4"
 #define kLabel_IPv6					"ipv6"
+#define kLabelPrefix_SRV			"srv-"
 
 #define kMaxAliasTTLCount		( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
+#define kMaxParsedSRVCount		( kDomainNameLengthMax / ( 1 + sizeof_string( kLabelPrefix_SRV ) + 5 ) )
+
+typedef struct
+{
+	uint16_t			priority;	// Priority from SRV label.
+	uint16_t			weight;		// Weight from SRV label.
+	uint16_t			port;		// Port number from SRV label.
+	uint16_t			targetLen;	// Total length of the target hostname labels that follow an SRV label.
+	const uint8_t *		targetPtr;	// Pointer to the target hostname embedded in a domain name.
+	
+}	ParsedSRV;
 
 static OSStatus
 	_DNSServerInitializeResponseMessage(
@@ -7570,14 +7721,37 @@ static OSStatus
 		unsigned int	inQClass );
 static OSStatus
 	_DNSServerAnswerQueryDynamically(
+		DNSServerRef	inServer,
 		const uint8_t *	inQName,
 		unsigned int	inQType,
 		unsigned int	inQClass,
 		Boolean			inForTCP,
 		DataBuffer *	inDB );
+static Boolean
+	_DNSServerNameIsSRVName(
+		DNSServerRef		inServer,
+		const uint8_t *		inName,
+		const uint8_t **	outDomainPtr,
+		size_t *			outDomainLen,
+		ParsedSRV			inSRVArray[ kMaxParsedSRVCount ],
+		size_t *			outSRVCount );
+static Boolean
+	_DNSServerNameIsHostname(
+		DNSServerRef	inServer,
+		const uint8_t *	inName,
+		uint32_t *		outAliasCount,
+		uint32_t		inAliasTTLs[ kMaxAliasTTLCount ],
+		size_t *		outAliasTTLCount,
+		unsigned int *	outCount,
+		unsigned int *	outRandCount,
+		uint32_t *		outTTL,
+		Boolean *		outHasA,
+		Boolean *		outHasAAAA,
+		Boolean *		outHasSOA );
 
 static OSStatus
 	_DNSServerAnswerQuery(
+		DNSServerRef			me,
 		const uint8_t * const	inQueryPtr,
 		const size_t			inQueryLen,
 		Boolean					inForTCP,
@@ -7629,10 +7803,11 @@ static OSStatus
 	if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired;
 	DNSFlagsSetOpCode( rflags, kDNSOpCode_Query );
 	
+	if( me->badUDPMode && !inForTCP ) msgID = (uint16_t)( msgID + 1 );
 	err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
 	require_noerr( err, exit );
 	
-	err = _DNSServerAnswerQueryDynamically( qname, qtype, qclass, inForTCP, &dataBuf );
+	err = _DNSServerAnswerQueryDynamically( me, qname, qtype, qclass, inForTCP, &dataBuf );
 	if( err )
 	{
 		DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure );
@@ -7657,9 +7832,8 @@ static OSStatus
 		unsigned int	inQType,
 		unsigned int	inQClass )
 {
-	OSStatus					err;
-	DNSHeader					header;
-	DNSQuestionFixedFields		fields;
+	OSStatus		err;
+	DNSHeader		header;
 	
 	DataBuffer_Reset( inDB );
 	
@@ -7671,11 +7845,8 @@ static OSStatus
 	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 ) );
+	err = _DataBuffer_AppendDNSQuestion( inDB, inQName, DomainNameLength( inQName ), (uint16_t) inQType,
+		(uint16_t) inQClass );
 	require_noerr( err, exit );
 	
 exit:
@@ -7684,184 +7855,72 @@ exit:
 
 static OSStatus
 	_DNSServerAnswerQueryDynamically(
+		DNSServerRef			me,
 		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.
+	OSStatus					err;
+	DNSHeader *					hdr;
+	unsigned int				flags, rcode;
+	uint32_t					aliasCount, i;
+	uint32_t					aliasTTLs[ kMaxAliasTTLCount ];
+	size_t						aliasTTLCount;
+	unsigned int				addrCount, randCount;
+	uint32_t					ttl;
+	ParsedSRV					srvArray[ kMaxParsedSRVCount ];
+	size_t						srvCount;
+	const uint8_t *				srvDomainPtr;
+	size_t						srvDomainLen;
+	unsigned int				answerCount;
+	Boolean						notImplemented, truncated;
+	Boolean						useAliasTTLs, nameExists, nameHasA, nameHasAAAA, nameHasSRV, nameHasSOA;
+	uint8_t						namePtr[ 2 ];
+	DNSRecordFixedFields		fields;
+	
+	answerCount	= 0;
+	truncated	= false;
+	nameExists	= false;
+	require_action_quiet( inQClass == kDNSServiceClass_IN, done, notImplemented = true );
+	
+	notImplemented	= false;
+	aliasCount		= 0;
+	nameHasA		= false;
+	nameHasAAAA		= false;
+	nameHasSOA		= false;
+	useAliasTTLs	= false;
+	nameHasSRV		= false;
+	srvDomainLen	= 0;
+	srvCount		= 0;
+	
+	if( _DNSServerNameIsHostname( me, inQName, &aliasCount, aliasTTLs, &aliasTTLCount, &addrCount, &randCount, &ttl,
+		&nameHasA, &nameHasAAAA, &nameHasSOA ) )
+	{
+		check( !( ( aliasCount > 0 ) && ( aliasTTLCount > 0 ) ) );
+		check( ( addrCount >= 1 ) && ( addrCount <= 255 ) );
+		check( ( randCount == 0 ) || ( ( randCount >= addrCount ) && ( randCount <= 255 ) ) );
+		check( nameHasA || nameHasAAAA );
 		
-		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( aliasTTLCount > 0 )
 		{
-			if( nameHasA || nameHasAAAA ) break;	// Valid names have at most one IPv4 or IPv6 label.
-			nameHasAAAA = true;
-			continue;
+			aliasCount		= (uint32_t) aliasTTLCount;
+			useAliasTTLs	= true;
 		}
-		
-		// 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;
+		nameExists = true;
 	}
-	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 )
+	else if( _DNSServerNameIsSRVName( me, inQName, &srvDomainPtr, &srvDomainLen, srvArray, &srvCount ) )
 	{
-		nameHasA	= true;
-		nameHasAAAA	= true;
+		nameHasSRV = true;
+		nameExists = true;
 	}
-	
-	check( ( count >= 1 ) && ( count <= 255 ) );
-	check( ( randCount <= 0 ) || ( ( randCount >= count ) && ( randCount <= 255 ) ) );
+	require_quiet( nameExists, done );
 	
 	if( aliasCount > 0 )
 	{
 		size_t				nameOffset;
-		uint8_t				rdataLabel[ 1 + kDomainLabelLengthMax + 1  ];
+		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
@@ -7876,18 +7935,15 @@ static OSStatus
 		
 		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;
+			size_t			nameLen;
+			size_t			rdataLen;
+			uint32_t		j;
+			uint32_t		aliasTTL;
+			uint8_t			nameLabel[ 1 + kDomainLabelLengthMax ];
 			
 			if( nameOffset <= kDNSCompressionOffsetMax )
 			{
-				namePtr[ 0 ] = (uint8_t)( ( ( nameOffset >> 8 ) & 0x3F ) | 0xC0 );
-				namePtr[ 1 ] = (uint8_t)(     nameOffset        & 0xFF );
-				
+				WriteDNSCompressionPtr( namePtr, nameOffset );
 				nameLen = sizeof( namePtr );
 			}
 			else
@@ -7899,22 +7955,22 @@ static OSStatus
 			if( i >= 2 )
 			{
 				char *				dst = (char *) &rdataLabel[ 1 ];
-				char * const		end = (char *) &rdataLabel[ countof( rdataLabel ) ];
+				char * const		lim = (char *) &rdataLabel[ countof( rdataLabel ) ];
 				
 				if( useAliasTTLs )
 				{
-					err = SNPrintF_Add( &dst, end, kLabelPrefix_AliasTTL );
+					err = SNPrintF_Add( &dst, lim, kLabelPrefix_AliasTTL );
 					require_noerr( err, exit );
 					
 					for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j )
 					{
-						err = SNPrintF_Add( &dst, end, "-%u", aliasTTLs[ j ] );
+						err = SNPrintF_Add( &dst, lim, "-%u", aliasTTLs[ j ] );
 						require_noerr( err, exit );
 					}
 				}
 				else
 				{
-					err = SNPrintF_Add( &dst, end, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
+					err = SNPrintF_Add( &dst, lim, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
 					require_noerr( err, exit );
 				}
 				rdataLabel[ 0 ]	= (uint8_t)( dst - (char *) &rdataLabel[ 1 ] );
@@ -7955,8 +8011,8 @@ static OSStatus
 			
 			// 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 );
+			aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : me->defaultTTL;
+			DNSRecordFixedFieldsSet( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, (uint16_t) rdataLen );
 			err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
 			require_noerr( err, exit );
 			
@@ -7982,30 +8038,36 @@ static OSStatus
 	{
 		// There are no aliases, so initialize the name compression pointer to point to QNAME.
 		
-		namePtr[ 0 ] = 0xC0;
-		namePtr[ 1 ] = kDNSHeaderLength;
+		WriteDNSCompressionPtr( namePtr, kDNSHeaderLength );
 	}
 	
-	if( ( ( inQType == kDNSServiceType_A    ) && nameHasA    ) ||
-		( ( inQType == kDNSServiceType_AAAA ) && nameHasAAAA ) )
+	if( ( inQType == kDNSServiceType_A ) || ( inQType == kDNSServiceType_AAAA ) )
 	{
-		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;
+		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			randIntegers[ 255 ];	// Array for random integers in [1, 255].
+		const int		useBadAddrs = ( me->badUDPMode && !inForTCP ) ? true : false;
 		
 		if( inQType == kDNSServiceType_A )
 		{
+			const uint32_t		baseAddrV4 = useBadAddrs ? kDNSServerBadBaseAddrV4 : kDNSServerBaseAddrV4;
+			
+			require_quiet( nameHasA, done );
+			
 			rdataLen = 4;
-			WriteBig32( rdata, kTestDNSServerBaseAddrV4 );
+			WriteBig32( rdata, baseAddrV4 );
 			lsb = &rdata[ 3 ];
 		}
 		else
 		{
+			const uint8_t * const		baseAddrV6 = useBadAddrs ? kDNSServerBadBaseAddrV6 : kDNSServerBaseAddrV6;
+			
+			require_quiet( nameHasAAAA, done );
+			
 			rdataLen = 16;
-			memcpy( rdata, kTestDNSServerBaseAddrV6, 16 );
+			memcpy( rdata, baseAddrV6, 16 );
 			lsb = &rdata[ 15 ];
 		}
 		
@@ -8013,37 +8075,44 @@ static OSStatus
 		{
 			// Populate the array with all integers between 1 and <randCount>, inclusive.
 			
-			for( i = 0; i < randCount; ++i ) randItegers[ i ] = (uint8_t)( i + 1 );
+			for( i = 0; i < randCount; ++i ) randIntegers[ i ] = (uint8_t)( i + 1 );
+			
+			// Prevent dubious static analyzer warning.
+			// Note: _DNSServerNameIsHostname() already enforces randCount >= addrCount. Also, this require_fatal() check
+			// needs to be placed right before the next for-loop. Any earlier, and the static analyzer warning will persist
+			// for some reason.
+			
+			require_fatal( addrCount <= randCount, "Invalid Count label values: addrCount %u > randCount %u",
+				addrCount, randCount );
 			
-			// Create a contiguous subarray starting at index 0 that contains <count> randomly chosen integers between
+			// Create a contiguous subarray starting at index 0 that contains <addrCount> 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.
+			// 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 )
+			for( i = 0; i < addrCount; ++i )
 			{
-				uint8_t		tmp;
-				int			j;
+				uint8_t			tmp;
+				uint32_t		j;
 				
-				j = (int) RandomRange( i, randCount - 1 );
+				j = RandomRange( i, randCount - 1 );
 				if( i != j )
 				{
-					tmp = randItegers[ i ];
-					randItegers[ i ] = randItegers[ j ];
-					randItegers[ j ] = tmp;
+					tmp = randIntegers[ i ];
+					randIntegers[ i ] = randIntegers[ j ];
+					randIntegers[ j ] = tmp;
 				}
 			}
 		}
 		
 		recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
-		for( i = 0; i < count; ++i )
+		for( i = 0; i < addrCount; ++i )
 		{
 			if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
 			{
 				truncated = true;
 				goto done;
 			}
-			++answerCount;
 			
 			// Set record NAME.
 			
@@ -8052,23 +8121,108 @@ static OSStatus
 			
 			// Set record TYPE, CLASS, TTL, and RDLENGTH.
 			
-			DNSRecordFixedFieldsInit( &fields, inQType, kDNSServiceClass_IN, ttl, rdataLen );
+			DNSRecordFixedFieldsSet( &fields, (uint16_t) inQType, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
 			err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
 			require_noerr( err, exit );
 			
 			// Set record RDATA.
 			
-			*lsb = ( randCount > 0 ) ? randItegers[ i ] : ( *lsb + 1 );
+			*lsb = ( randCount > 0 ) ? randIntegers[ i ] : ( *lsb + 1 );
 			
 			err = DataBuffer_Append( inDB, rdata, rdataLen );
 			require_noerr( err, exit );
+			
+			++answerCount;
+		}
+	}
+	else if( inQType == kDNSServiceType_SRV )
+	{
+		require_quiet( nameHasSRV, done );
+		
+		DNSRecordFixedFieldsSet( &fields, kDNSServiceType_SRV, kDNSServiceClass_IN, me->defaultTTL, 0 );
+		
+		for( i = 0; i < srvCount; ++i )
+		{
+			SRVRecordDataFixedFields		fieldsSRV;
+			size_t							rdataLen;
+			size_t							recordLen;
+			const ParsedSRV * const			srv = &srvArray[ i ];
+			
+			rdataLen  = sizeof( fieldsSRV ) + srvDomainLen + srv->targetLen + 1;
+			recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+			
+			if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+			{
+				truncated = true;
+				goto done;
+			}
+			
+			// Append record NAME.
+			
+			err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+			require_noerr( err, exit );
+			
+			// Append record TYPE, CLASS, TTL, and RDLENGTH.
+			
+			WriteBig16( fields.rdlength, rdataLen );
+			err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+			require_noerr( err, exit );
+			
+			// Append SRV RDATA.
+			
+			SRVRecordDataFixedFieldsSet( &fieldsSRV, srv->priority, srv->weight, srv->port );
+			
+			err = DataBuffer_Append( inDB, &fieldsSRV, sizeof( fieldsSRV ) );
+			require_noerr( err, exit );
+			
+			if( srv->targetLen > 0 )
+			{
+				err = DataBuffer_Append( inDB, srv->targetPtr, srv->targetLen );
+				require_noerr( err, exit );
+			}
+			
+			if( srvDomainLen > 0 )
+			{
+				err = DataBuffer_Append( inDB, srvDomainPtr, srvDomainLen );
+				require_noerr( err, exit );
+			}
+			
+			err = DataBuffer_Append( inDB, "", 1 );	// Append root label.
+			require_noerr( err, exit );
+			
+			++answerCount;
+		}
+	}
+	else if( inQType == kDNSServiceType_SOA )
+	{
+		size_t		nameLen, recordLen;
+		
+		require_quiet( nameHasSOA, done );
+		
+		nameLen	= DomainNameLength( me->domain );
+		if( !inForTCP )
+		{
+			err = AppendSOARecord( NULL, me->domain, nameLen, 0, 0, 0, kRootLabel, kRootLabel, 0, 0, 0, 0, 0, &recordLen );
+			require_noerr( err, exit );
+			
+			if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
+			{
+				truncated = true;
+				goto done;
+			}
 		}
+		
+		err = AppendSOARecord( inDB, me->domain, nameLen, kDNSServiceType_SOA, kDNSServiceClass_IN, me->defaultTTL,
+			kRootLabel, kRootLabel, me->serial, 1 * kSecondsPerDay, 2 * kSecondsPerHour, 1000 * kSecondsPerHour,
+			me->defaultTTL, NULL );
+		require_noerr( err, exit );
+		
+		++answerCount;
 	}
 	
 done:
 	hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
 	flags = DNSHeaderGetFlags( hdr );
-	if( truncated ) flags |= kDNSHeaderFlag_Truncation;
 	if( notImplemented )
 	{
 		rcode = kDNSRCode_NotImplemented;
@@ -8076,6 +8230,7 @@ done:
 	else
 	{
 		flags |= kDNSHeaderFlag_AuthAnswer;
+		if( truncated ) flags |= kDNSHeaderFlag_Truncation;
 		rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
 	}
 	DNSFlagsSetRCode( flags, rcode );
@@ -8087,65 +8242,336 @@ 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 )
+static Boolean
+	_DNSServerNameIsHostname(
+		DNSServerRef	me,
+		const uint8_t *	inName,
+		uint32_t *		outAliasCount,
+		uint32_t		inAliasTTLs[ kMaxAliasTTLCount ],
+		size_t *		outAliasTTLCount,
+		unsigned int *	outCount,
+		unsigned int *	outRandCount,
+		uint32_t *		outTTL,
+		Boolean *		outHasA,
+		Boolean *		outHasAAAA,
+		Boolean *		outHasSOA )
 {
-	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 );
+	OSStatus			err;
+	const uint8_t *		label;
+    const uint8_t *		nextLabel;
+	uint32_t			aliasCount		= 0;	// Arg from Alias label. Valid values are in [2, 2^31 - 1].
+	unsigned int		count			= 0;	// First arg from Count label. Valid values are in [1, 255].
+	unsigned int		randCount		= 0;	// Second arg from Count label. Valid values are in [count, 255].
+	int32_t				ttl				= -1;	// Arg from TTL label. Valid values are in [0, 2^31 - 1].
+	size_t				aliasTTLCount	= 0;	// Count of TTL args from Alias-TTL label.
+	int					hasTagLabel		= false;
+	int					hasIPv4Label	= false;
+	int					hasIPv6Label	= false;
+	int					isNameValid		= false;
+	
+	for( label = inName; label[ 0 ]; label = nextLabel )
+	{
+		uint32_t		arg;
+		
+		nextLabel = &label[ 1 + label[ 0 ] ];
+		
+		// Check if the first label is a valid alias TTL sequence label.
+		
+		if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_AliasTTL ) == 0 ) )
+		{
+			const char *			ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_AliasTTL ) ];
+			const char * const		end = (const char *) nextLabel;
+			const char *			next;
+			
+			check( label[ 0 ] <= kDomainLabelLengthMax );
+			
+			while( ptr < end )
+			{
+				if( *ptr != '-' ) break;
+				++ptr;
+				err = DecimalTextToUInt32( ptr, end, &arg, &next );
+				if( err || ( arg > INT32_MAX ) ) break;	// TTL must be in [0, 2^31 - 1].
+				inAliasTTLs[ aliasTTLCount++ ] = arg;
+				ptr = next;
+			}
+			if( ( aliasTTLCount == 0 ) || ( ptr != end ) ) break;
+		}
+		
+		// Check if the first label is a valid alias label.
+		
+		else if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Alias ) == 0 ) )
+		{
+			const char *			ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Alias ) ];
+			const char * const		end = (const char *) nextLabel;
+			
+			if( ptr < end )
+			{
+				if( *ptr++ != '-' ) break;
+				err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+				if( err || ( arg < 2 ) || ( arg > INT32_MAX ) ) break;	// Alias count must be in [2, 2^31 - 1].
+				aliasCount = arg;
+				if( ptr != end ) break;
+			}
+			else
+			{
+				aliasCount = 1;
+			}
+		}
+		
+		// Check if this label is a valid count label.
+		
+		else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Count ) == 0  )
+		{
+			const char *			ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Count ) ];
+			const char * const		end = (const char *) nextLabel;
+			
+			if( count > 0 ) break;	// Count cannot be specified more than once.
+			
+			err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+			if( err || ( arg < 1 ) || ( arg > 255 ) ) break;	// Count must be in [1, 255].
+			count = (unsigned int) arg;
+			
+			if( ptr < end )
+			{
+				if( *ptr++ != '-' ) break;
+				err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+				if( err || ( arg < (uint32_t) count ) || ( arg > 255 ) ) break;	// Rand count must be in [count, 255].
+				randCount = (unsigned int) arg;
+				if( ptr != end ) break;
+			}
+		}
+		
+		// Check if this label is a valid TTL label.
+		
+		else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_TTL ) == 0  )
+		{
+			const char *			ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_TTL ) ];
+			const char * const		end = (const char *) nextLabel;
+			
+			if( ttl >= 0 ) break;	// TTL cannot be specified more than once.
+			
+			err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+			if( err || ( arg > INT32_MAX ) ) break;	// TTL must be in [0, 2^31 - 1].
+			ttl = (int32_t) arg;
+			if( ptr != end ) break;
+		}
+		
+		// Check if this label is a valid IPv4 label.
+		
+		else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv4 ) == 0 )
+		{
+			if( hasIPv4Label || hasIPv6Label ) break;	// Valid names have at most one IPv4 or IPv6 label.
+			hasIPv4Label = true;
+		}
+		
+		// Check if this label is a valid IPv6 label.
+		
+		else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv6 ) == 0 )
+		{
+			if( hasIPv4Label || hasIPv6Label ) break;	// Valid names have at most one IPv4 or IPv6 label.
+			hasIPv6Label = true;
+		}
+		
+		// Check if this label is a valid tag label.
+		
+		else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Tag ) == 0  )
+		{
+			hasTagLabel = true;
+		}
+		
+		// If this and the remaining labels are equal to "d.test.", then the name exists. Otherwise, this label is invalid.
+		// In both cases, there are no more labels to check.
+		
+		else
+		{
+			if( DomainNameEqual( label, me->domain ) ) isNameValid = true;
+			break;
+		}
+	}
+	require_quiet( isNameValid, exit );
+	
+	if( outAliasCount )		*outAliasCount		= aliasCount;
+	if( outAliasTTLCount )	*outAliasTTLCount	= aliasTTLCount;
+	if( outCount )			*outCount			= ( count > 0 ) ? count : 1;
+	if( outRandCount )		*outRandCount		= randCount;
+	if( outTTL )			*outTTL				= ( ttl >= 0 ) ? ( (uint32_t) ttl ) : me->defaultTTL;
+	if( outHasA )			*outHasA			= ( hasIPv4Label || !hasIPv6Label ) ? true : false;
+	if( outHasAAAA )		*outHasAAAA			= ( hasIPv6Label || !hasIPv4Label ) ? true : false;
+	if( outHasSOA )
+	{
+		*outHasSOA = ( !count && ( ttl < 0 ) && !hasIPv4Label && !hasIPv6Label && !hasTagLabel ) ? true : false;
+	}
+	
+exit:
+	return( isNameValid ? true : false );
+}
+
+static Boolean
+	_DNSServerNameIsSRVName(
+		DNSServerRef		me,
+		const uint8_t *		inName,
+		const uint8_t **	outDomainPtr,
+		size_t *			outDomainLen,
+		ParsedSRV			inSRVArray[ kMaxParsedSRVCount ],
+		size_t *			outSRVCount )
+{
+	OSStatus			err;
+	const uint8_t *		label;
+	const uint8_t *		domainPtr;
+	size_t				domainLen;
+	size_t				srvCount;
+	uint32_t			arg;
+	int					isNameValid = false;
+	
+	label = inName;
+	
+	// Ensure that first label, i.e, the service label, begins with a '_' character.
+	
+	require_quiet( ( label[ 0 ] > 0 ) && ( label[ 1 ] == '_' ), exit );
+	label = NextLabel( label );
+	
+	// Ensure that the second label, i.e., the proto label, begins with a '_' character (usually _tcp or _udp).
+	
+	require_quiet( ( label[ 0 ] > 0 ) && ( label[ 1 ] == '_' ), exit );
+	label = NextLabel( label );
+	
+	// Parse the domain name, if any.
+	
+	domainPtr = label;
+	while( *label )
+	{
+		if( DomainNameEqual( label, me->domain ) ||
+			( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break;
+		label = NextLabel( label );
+	}
+	require_quiet( *label, exit );
+	
+	domainLen = (size_t)( label - domainPtr );
+	
+	// Parse SRV labels, if any.
+	
+	srvCount = 0;
+	while( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 )
+	{
+		const uint8_t * const	nextLabel	= NextLabel( label );
+		const char *			ptr			= (const char *) &label[ 1 + sizeof_string( kLabelPrefix_SRV ) ];
+		const char * const		end			= (const char *) nextLabel;
+		const uint8_t *			target;
+		unsigned int			priority, weight, port;
+		
+		err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+		require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+		priority = (unsigned int) arg;
+		
+		require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+		++ptr;
+		
+		err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+		require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+		weight = (unsigned int) arg;
+		
+		require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+		++ptr;
+		
+		err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+		require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+		port = (unsigned int) arg;
+		
+		require_quiet( ptr == end, exit );
+		
+		target = nextLabel;
+		for( label = nextLabel; *label; label = NextLabel( label ) )
+		{
+			if( DomainNameEqual( label, me->domain ) ||
+				( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break;
+		}
+		require_quiet( *label, exit );
+		
+		if( inSRVArray )
+		{
+			inSRVArray[ srvCount ].priority		= (uint16_t) priority;
+			inSRVArray[ srvCount ].weight		= (uint16_t) weight;
+			inSRVArray[ srvCount ].port			= (uint16_t) port;
+			inSRVArray[ srvCount ].targetPtr	= target;
+			inSRVArray[ srvCount ].targetLen	= (uint16_t)( label - target );
+		}
+		++srvCount;
+	}
+	require_quiet( DomainNameEqual( label, me->domain ), exit );
+	isNameValid = true;
+	
+	if( outDomainPtr )	*outDomainPtr	= domainPtr;
+	if( outDomainLen )	*outDomainLen	= domainLen;
+	if( outSRVCount )	*outSRVCount	= srvCount;
+	
+exit:
+	return( isNameValid ? true : false );
+}
+
+//===========================================================================================================================
+//	_DNSServerTCPReadHandler
+//===========================================================================================================================
+
+typedef struct
+{
+	DNSServerRef			server;			// Reference to DNS server object.
+	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;
+	DNSServerRef const			me			= (DNSServerRef) sockCtx->userContext;
+	TCPConnectionContext *		connection;
+	socklen_t					clientAddrLen;
+	SocketRef					newSock		= kInvalidSocketRef;
+	SocketContext *				newSockCtx	= NULL;
+	
+	connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
+	require_action( connection, exit, err = kNoMemoryErr );
+	
+	CFRetain( me );
+	connection->server = me;
+	
+	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, me->queue, TCPConnectionReadHandler, SocketContextCancelHandler,
+		newSockCtx, &connection->readSource );
+	require_noerr( err, exit );
+	SocketContextRetain( newSockCtx );
+	dispatch_resume( connection->readSource );
+	
+	err = DispatchWriteSourceCreate( newSockCtx->sock, me->queue, TCPConnectionWriteHandler, SocketContextCancelHandler,
+		newSockCtx, &connection->writeSource );
+	require_noerr( err, exit );
 	SocketContextRetain( newSockCtx );
 	connection->writeSuspended = true;
 	connection = NULL;
@@ -8174,6 +8600,7 @@ static void	TCPConnectionContextFree( TCPConnectionContext *inContext )
 {
 	check( !inContext->readSource );
 	check( !inContext->writeSource );
+	ForgetCF( &inContext->server );
 	ForgetMem( &inContext->msgPtr );
 	free( inContext );
 }
@@ -8229,7 +8656,8 @@ static void	TCPConnectionReadHandler( void *inContext )
 	
 	// Create response.
 	
-	err = _DNSServerAnswerQueryForTCP( connection->msgPtr, connection->msgLen, &responsePtr, &responseLen );
+	err = _DNSServerAnswerQueryForTCP( connection->server, connection->msgPtr, connection->msgLen, &responsePtr,
+		&responseLen );
 	require_noerr_quiet( err, exit );
 	
 	// Send response.
@@ -8281,3095 +8709,9340 @@ exit:
 }
 
 //===========================================================================================================================
-//	GAIPerfCmd
+//	MDNSReplierCmd
 //===========================================================================================================================
 
-#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.
+	uint8_t *				hostname;			// Used as the base name for hostnames and service names.
+	uint8_t *				serviceLabel;		// Label containing the base service name.
+	unsigned int			maxInstanceCount;	// Maximum number of service instances and hostnames.
+	uint64_t *				bitmaps;			// Array of 64-bit bitmaps for keeping track of needed responses.
+	size_t					bitmapCount;		// Number of 64-bit bitmaps.
+	dispatch_source_t		readSourceV4;		// Read dispatch source for IPv4 socket.
+	dispatch_source_t		readSourceV6;		// Read dispatch source for IPv6 socket.
+	uint32_t				ifIndex;			// Index of the interface to run on.
+	unsigned int			recordCountA;		// Number of A records per hostname.
+	unsigned int			recordCountAAAA;	// Number of AAAA records per hostname.
+	unsigned int			maxDropCount;		// If > 0, the drop rates apply to only the first <maxDropCount> responses.
+	double					ucastDropRate;		// Probability of dropping a unicast response.
+	double					mcastDropRate;		// Probability of dropping a multicast query or response.
+	uint8_t *				dropCounters;		// If maxDropCount > 0, array of <maxInstanceCount> response drop counters.
+	Boolean					noAdditionals;		// True if responses are to not include additional records.
+	Boolean					useIPv4;			// True if the replier is to use IPv4.
+	Boolean					useIPv6;			// True if the replier is to use IPv6.
+	uint8_t					msgBuf[ kMDNSMessageSizeMax ];	// Buffer for received mDNS message.
+#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, if any. (If it exits, we exit).
+#endif
 	
-}	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 ) )
+}	MDNSReplierContext;
 
-typedef enum
+typedef struct MRResourceRecord		MRResourceRecord;
+struct MRResourceRecord
 {
-	kGAIPerfOutputFormat_JSON	= 1,
-	kGAIPerfOutputFormat_XML	= 2,
-	kGAIPerfOutputFormat_Binary	= 3
-	
-}	GAIPerfOutputFormatType;
+	MRResourceRecord *		next;		// Next item in list.
+	uint8_t *				name;		// Resource record name.
+	uint16_t				type;		// Resource record type.
+	uint16_t				class;		// Resource record class.
+	uint32_t				ttl;		// Resource record TTL.
+	uint16_t				rdlength;	// Resource record data length.
+	uint8_t *				rdata;		// Resource record data.
+	const uint8_t *			target;		// For SRV records, pointer to target in RDATA.
+};
 
-typedef struct
+typedef struct MRNameOffsetItem		MRNameOffsetItem;
+struct MRNameOffsetItem
 {
-	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 );
+	MRNameOffsetItem *	next;		// Next item in list.
+	uint16_t			offset;		// Offset of domain name in response message.
+	uint8_t				name[ 1 ];	// Variable-length array for domain name.
+};
 
-CFTypeID		GAITesterGetTypeID( void );
+#if( TARGET_OS_DARWIN )
+static void		_MDNSReplierFollowedProcessHandler( void *inContext );
+#endif
+static void		_MDNSReplierReadHandler( void *inContext );
 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 );
+	_MDNSReplierAnswerQuery(
+		MDNSReplierContext *	inContext,
+		const uint8_t *			inQueryPtr,
+		size_t					inQueryLen,
+		sockaddr_ip *			inSender,
+		SocketRef				inSock,
+		unsigned int			inIndex );
+static OSStatus
+	_MDNSReplierAnswerListAdd(
+		MDNSReplierContext *	inContext,
+		MRResourceRecord **		inAnswerList,
+		unsigned int			inIndex,
+		const uint8_t *			inName,
+		unsigned int			inType,
+		unsigned int			inClass );
 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 );
+	_MDNSReplierAnswerListRemovePTR(
+		MRResourceRecord **	inAnswerListPtr,
+		const uint8_t *		inName,
+		const uint8_t *		inRData );
 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 );
+	_MDNSReplierSendOrDropResponse(
+		MDNSReplierContext *	inContext,
+		MRResourceRecord *		inAnswerList,
+		sockaddr_ip *			inQuerier,
+		SocketRef				inSock,
+		unsigned int			inIndex,
+		Boolean					inUnicast );
+static OSStatus
+	_MDNSReplierCreateResponse(
+		MDNSReplierContext *	inContext,
+		MRResourceRecord *		inAnswerList,
+		unsigned int			inIndex,
+		uint8_t **				outResponsePtr,
+		size_t *				outResponseLen );
+static OSStatus
+	_MDNSReplierAppendNameToResponse(
+		DataBuffer *		inResponse,
+		const uint8_t *		inName,
+		MRNameOffsetItem **	inNameOffsetListPtr );
+static Boolean
+	_MDNSReplierServiceTypeMatch(
+		const MDNSReplierContext *	inContext,
+		const uint8_t *				inName,
+		unsigned int *				outTXTSize,
+		unsigned int *				outCount );
+static Boolean
+	_MDNSReplierServiceInstanceNameMatch(
+		const MDNSReplierContext *	inContext,
+		const uint8_t *				inName,
+		unsigned int *				outIndex,
+		unsigned int *				outTXTSize,
+		unsigned int *				outCount );
+static Boolean	_MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName );
+static Boolean
+	_MDNSReplierHostnameMatch(
+		const MDNSReplierContext *	inContext,
+		const uint8_t *				inName,
+		unsigned int *				outIndex );
+static OSStatus	_MDNSReplierCreateTXTRecord( const uint8_t *inRecordName, size_t inSize, uint8_t **outTXT );
+static OSStatus
+	_MRResourceRecordCreate(
+		uint8_t *			inName,
+		uint16_t			inType,
+		uint16_t			inClass,
+		uint32_t			inTTL,
+		uint16_t			inRDLength,
+		uint8_t *			inRData,
+		MRResourceRecord **	outRecord );
+static void		_MRResourceRecordFree( MRResourceRecord *inRecord );
+static void		_MRResourceRecordFreeList( MRResourceRecord *inList );
+static OSStatus	_MRNameOffsetItemCreate( const uint8_t *inName, uint16_t inOffset, MRNameOffsetItem **outItem );
+static void		_MRNameOffsetItemFree( MRNameOffsetItem *inItem );
+static void		_MRNameOffsetItemFreeList( MRNameOffsetItem *inList );
 
-#define kGAIPerfTestSuite_Basic			1
-#define kGAIPerfTestSuite_Advanced		2
+ulog_define_ex( "com.apple.dnssdutil", MDNSReplier, kLogLevelInfo, kLogFlags_None, "MDNSReplier", NULL );
+#define mr_ulog( LEVEL, ... )		ulog( &log_category_from_name( MDNSReplier ), (LEVEL), __VA_ARGS__ )
 
-static void	GAIPerfCmd( void )
+static void	MDNSReplierCmd( void )
 {
-	OSStatus				err;
-	GAIPerfContext *		context;
-	int						suiteValue;
+	OSStatus					err;
+	MDNSReplierContext *		context;
+	SocketRef					sockV4	= kInvalidSocketRef;
+	SocketRef					sockV6	= kInvalidSocketRef;
+	const char *				ifname;
+	size_t						len;
+	uint8_t						name[ 1 + kDomainLabelLengthMax + 1 ];
+	char						ifnameBuf[ IF_NAMESIZE + 1 ];
 	
-	context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
-	require_action( context, exit, err = kNoMemoryErr );
+	err = CheckIntegerArgument( gMDNSReplier_MaxInstanceCount, "max instance count", 1, UINT16_MAX );
+	require_noerr_quiet( err, exit );
 	
-	context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-	require_action( context->caseResults, exit, err = kNoMemoryErr );
+	err = CheckIntegerArgument( gMDNSReplier_RecordCountA, "A record count", 0, 255 );
+	require_noerr_quiet( err, exit );
 	
-	context->outputFormat = (GAIPerfOutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
-		"json",		kGAIPerfOutputFormat_JSON,
-		"xml",		kGAIPerfOutputFormat_XML,
-		"binary",	kGAIPerfOutputFormat_Binary,
-		NULL );
+	err = CheckIntegerArgument( gMDNSReplier_RecordCountAAAA, "AAAA record count", 0, 255 );
 	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;
+	err = CheckDoubleArgument( gMDNSReplier_UnicastDropRate, "unicast drop rate", 0.0, 1.0 );
+	require_noerr_quiet( err, exit );
 	
-	if( gGAIPerf_OutputFilePath )
+	err = CheckDoubleArgument( gMDNSReplier_MulticastDropRate, "multicast drop rate", 0.0, 1.0 );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckIntegerArgument( gMDNSReplier_MaxDropCount, "drop count", 0, 255 );
+	require_noerr_quiet( err, exit );
+	
+	if( gMDNSReplier_Foreground )
 	{
-		context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
-		require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+		LogControl( "MDNSReplier:output=file;stdout,MDNSReplier:flags=time;prefix" );
 	}
 	
-	err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
-		kGAIPerfStandardTTL, &context->tester );
-	require_noerr( err, exit );
+	context = (MDNSReplierContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
 	
-	check( gGAIPerf_TestSuite );
-	suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err,
-		"basic",	kGAIPerfTestSuite_Basic,
-		"advanced",	kGAIPerfTestSuite_Advanced,
-		NULL );
-	require_noerr_quiet( err, exit );
+	context->maxInstanceCount	= (unsigned int) gMDNSReplier_MaxInstanceCount;
+	context->recordCountA		= (unsigned int) gMDNSReplier_RecordCountA;
+	context->recordCountAAAA	= (unsigned int) gMDNSReplier_RecordCountAAAA;
+	context->maxDropCount		= (unsigned int) gMDNSReplier_MaxDropCount;
+	context->ucastDropRate		= gMDNSReplier_UnicastDropRate;
+	context->mcastDropRate		= gMDNSReplier_MulticastDropRate;
+	context->noAdditionals		= gMDNSReplier_NoAdditionals ? true : false;
+	context->useIPv4			= ( gMDNSReplier_UseIPv4 || !gMDNSReplier_UseIPv6 ) ? true : false;
+	context->useIPv6			= ( gMDNSReplier_UseIPv6 || !gMDNSReplier_UseIPv4 ) ? true : false;
+	context->bitmapCount		= ( context->maxInstanceCount + 63 ) / 64;
 	
-	switch( suiteValue )
+#if( TARGET_OS_DARWIN )
+	if( gMDNSReplier_FollowPID )
 	{
-		case kGAIPerfTestSuite_Basic:
-			err = GAIPerfAddBasicTestCases( context );
-			require_noerr( err, exit );
-			break;
+		err = StringToPID( gMDNSReplier_FollowPID, &context->followPID );
+		if( err || ( context->followPID < 0 ) )
+		{
+			FPrintF( stderr, "error: Invalid follow PID: %s\n", gMDNSReplier_FollowPID );
+			goto exit;
+		}
 		
-		case kGAIPerfTestSuite_Advanced:
-			err = GAIPerfAddAdvancedTestCases( context );
-			require_noerr( err, exit );
-			break;
+		err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+			_MDNSReplierFollowedProcessHandler, NULL, context, &context->processMonitor );
+		require_noerr( err, exit );
+		dispatch_resume( context->processMonitor );
+	}
+	else
+	{
+		context->followPID = -1;
+	}
+#endif
+	
+	if( context->maxDropCount > 0 )
+	{
+		context->dropCounters = (uint8_t *) calloc( context->maxInstanceCount, sizeof( *context->dropCounters ) );
+		require_action( context->dropCounters, exit, err = kNoMemoryErr );
+	}
+	
+	context->bitmaps = (uint64_t *) calloc( context->bitmapCount, sizeof( *context->bitmaps ) );
+	require_action( context->bitmaps, exit, err = kNoMemoryErr );
+	
+	// Create the base hostname label.
+	
+	len = strlen( gMDNSReplier_Hostname );
+	if( context->maxInstanceCount > 1 )
+	{
+		unsigned int		maxInstanceCount, digitCount;
 		
-		default:
-			err = kValueErr;
-			break;
+		// When there's more than one instance, extra bytes are needed to append " (<instance index>)" or
+		// "-<instance index>" to the base hostname.
+		
+		maxInstanceCount = context->maxInstanceCount;
+		for( digitCount = 0; maxInstanceCount > 0; ++digitCount ) maxInstanceCount /= 10;
+		len += ( 3 + digitCount );
 	}
 	
-	GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context );
-	GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
+	if( len <= kDomainLabelLengthMax )
+	{
+		uint8_t *		dst = &name[ 1 ];
+		uint8_t *		lim = &name[ countof( name ) ];
+		
+		SNPrintF_Add( (char **) &dst, (char *) lim, "%s", gMDNSReplier_Hostname );
+		name[ 0 ] = (uint8_t)( dst - &name[ 1 ] );
+		
+		err = DomainNameDupLower( name, &context->hostname, NULL );
+		require_noerr( err, exit );
+	}
+	else
+	{
+		FPrintF( stderr, "error: Base name \"%s\" is too long for max instance count of %u.\n",
+			gMDNSReplier_Hostname, context->maxInstanceCount );
+		goto exit;
+	}
 	
-	signal( SIGINT, SIG_IGN );
-	err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
-	require_noerr( err, exit );
-	dispatch_resume( context->sigIntSource );
+	// Create the service label.
 	
-	signal( SIGTERM, SIG_IGN );
-	err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
-	require_noerr( err, exit );
-	dispatch_resume( context->sigTermSource );
+	len = strlen( gMDNSReplier_ServiceTypeTag ) + 3;	// We need three extra bytes for the service type prefix "_t-".
+	if( len <= kDomainLabelLengthMax )
+	{
+		uint8_t *		dst = &name[ 1 ];
+		uint8_t *		lim = &name[ countof( name ) ];
+		
+		SNPrintF_Add( (char **) &dst, (char *) lim, "_t-%s", gMDNSReplier_ServiceTypeTag );
+		name[ 0 ] = (uint8_t)( dst - &name[ 1 ] );
+		
+		err = DomainNameDupLower( name, &context->serviceLabel, NULL );
+		require_noerr( err, exit );
+	}
+	else
+	{
+		FPrintF( stderr, "error: Service type tag is too long.\n" );
+		goto exit;
+	}
+	
+	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+	require_noerr_quiet( err, exit );
+	
+	ifname = if_indextoname( context->ifIndex, ifnameBuf );
+	require_action( ifname, exit, err = kNameErr );
+	
+	// Set up IPv4 socket.
+	
+	if( context->useIPv4 )
+	{
+		err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, ifname, context->ifIndex, true, NULL, &sockV4 );
+		require_noerr( err, exit );
+	}
+	
+	// Set up IPv6 socket.
+	
+	if( context->useIPv6 )
+	{
+		err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, ifname, context->ifIndex, true, NULL, &sockV6 );
+		require_noerr( err, exit );
+	}
+	
+	// Create dispatch read sources for socket(s).
+	
+	if( IsValidSocket( sockV4 ) )
+	{
+		SocketContext *		sockCtx;
+		
+		err = SocketContextCreate( sockV4, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV4 = kInvalidSocketRef;
+		
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, _MDNSReplierReadHandler, SocketContextCancelHandler, sockCtx,
+			&context->readSourceV4 );
+		if( err ) ForgetSocketContext( &sockCtx );
+		require_noerr( err, exit );
+		
+		dispatch_resume( context->readSourceV4 );
+	}
+	
+	if( IsValidSocket( sockV6 ) )
+	{
+		SocketContext *		sockCtx;
+		
+		err = SocketContextCreate( sockV6, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV6 = kInvalidSocketRef;
+		
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, _MDNSReplierReadHandler, SocketContextCancelHandler, sockCtx,
+			&context->readSourceV6 );
+		if( err ) ForgetSocketContext( &sockCtx );
+		require_noerr( err, exit );
+		
+		dispatch_resume( context->readSourceV6 );
+	}
 	
-	GAITesterStart( context->tester );
 	dispatch_main();
 	
 exit:
-	if( context ) GAIPerfContextFree( context );
-	if( err ) exit( 1 );
+	ForgetSocket( &sockV4 );
+	ForgetSocket( &sockV6 );
+	exit( 1 );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//	GAIPerfContextFree
+//	_MDNSReplierFollowedProcessHandler
 //===========================================================================================================================
 
-static void	GAIPerfContextFree( GAIPerfContext *inContext )
+static void	_MDNSReplierFollowedProcessHandler( void *inContext )
 {
-	ForgetCF( &inContext->tester );
-	ForgetCF( &inContext->caseResults );
-	ForgetMem( &inContext->outputFilePath );
-	dispatch_source_forget( &inContext->sigIntSource );
-	dispatch_source_forget( &inContext->sigTermSource );
-	free( inContext );
+	MDNSReplierContext * const		context = (MDNSReplierContext *) inContext;
+	
+	if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
+	{
+		mr_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited.\n", (int64_t) context->followPID );
+		exit( 0 );
+	}
 }
+#endif
 
 //===========================================================================================================================
-//	GAIPerfAddAdvancedTestCases
+//	_MDNSReplierReadHandler
 //===========================================================================================================================
 
-#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
+#define ShouldDrop( P )		( ( (P) > 0.0 ) && ( ( (P) >= 1.0 ) || RandomlyTrue( P ) ) )
 
-static OSStatus	GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
+static void	_MDNSReplierReadHandler( void *inContext )
 {
-	OSStatus			err;
-	unsigned int		aliasCount, addressCount, timeLimitMs, i;
-	GAITestCase *		testCase = NULL;
-	char				title[ kTestCaseTitleBufferSize ];
+	OSStatus						err;
+	SocketContext * const			sockCtx = (SocketContext *) inContext;
+	MDNSReplierContext * const		context = (MDNSReplierContext *) sockCtx->userContext;
+	size_t							msgLen;
+	sockaddr_ip						sender;
+	const DNSHeader *				hdr;
+	unsigned int					flags, questionCount, i, j;
+	const uint8_t *					ptr;
+	int								drop, isMetaQuery;
 	
-	aliasCount = 0;
-	while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+	err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &sender, sizeof( sender ),
+		NULL, NULL, NULL, NULL );
+	require_noerr( err, exit );
+	
+	if( msgLen < kDNSHeaderLength )
 	{
-		for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+		mr_ulog( kLogLevelInfo, "Message is too small (%zu < %d).\n", msgLen, kDNSHeaderLength );
+		goto exit;
+	}
+	
+	// Perform header field checks.
+	// The message ID and most flag bits are silently ignored (see <https://tools.ietf.org/html/rfc6762#section-18>).
+	
+	hdr = (DNSHeader *) context->msgBuf;
+	flags = DNSHeaderGetFlags( hdr );
+	require_quiet( ( flags & kDNSHeaderFlag_Response ) == 0, exit );		// Reject responses.
+	require_quiet( DNSFlagsGetOpCode( flags ) == kDNSOpCode_Query, exit );	// Reject opcodes other than standard query.
+	require_quiet( DNSFlagsGetRCode( flags )  == kDNSRCode_NoError, exit );	// Reject non-zero rcodes.
+	
+	drop = ( !context->maxDropCount && ShouldDrop( context->mcastDropRate ) ) ? true : false;
+	
+	mr_ulog( kLogLevelInfo, "Received %zu byte message from %##a%?s:\n\n%#1{du:dnsmsg}",
+		msgLen, &sender, drop, " (dropping)", context->msgBuf, msgLen );
+	
+	// Based on the QNAMEs in the query message, determine from which sets of records we may possibly need answers.
+	
+	questionCount = DNSHeaderGetQuestionCount( hdr );
+	require_quiet( questionCount > 0, exit );
+	
+	memset( context->bitmaps, 0, context->bitmapCount * sizeof_element( context->bitmaps ) );
+	
+	isMetaQuery = false;
+	ptr = (const uint8_t *) &hdr[ 1 ];
+	for( i = 0; i < questionCount; ++i )
+	{
+		unsigned int		count, index;
+		uint16_t			qtype, qclass;
+		uint8_t				qname[ kDomainNameLengthMax ];
+		
+		err = DNSMessageExtractQuestion( context->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
+		require_noerr_quiet( err, exit );
+		
+		if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+		
+		if( _MDNSReplierHostnameMatch( context, qname, &index ) ||
+			_MDNSReplierServiceInstanceNameMatch( context, qname, &index, NULL, 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. 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 )
+			if( ( index >= 1 ) && ( index <= context->maxInstanceCount ) )
 			{
-				err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
-					kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
-				require_noerr( err, exit );
+				context->bitmaps[ ( index - 1 ) / 64 ] |= ( UINT64_C( 1 ) << ( ( index - 1 ) % 64 ) );
 			}
-			
-			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;
+		else if( _MDNSReplierServiceTypeMatch( context, qname, NULL, &count ) )
+		{
+			if( ( count >= 1 ) && ( count <= context->maxInstanceCount ) )
+			{
+				for( j = 0; j < (unsigned int) context->bitmapCount; ++j )
+				{
+					if( count < 64 )
+					{
+						context->bitmaps[ j ] |= ( ( UINT64_C( 1 ) << count ) - 1 );
+						break;
+					}
+					else
+					{
+						context->bitmaps[ j ] = ~UINT64_C( 0 );
+						count -= 64;
+					}
+				}
+			}
+		}
+		else if( _MDNSReplierAboutRecordNameMatch( context, qname ) )
+		{
+			isMetaQuery = true;
+		}
 	}
 	
-	// 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 );
+	// Attempt to answer the query message using selected record sets.
 	
-	err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
-	require_noerr( err, exit );
+	if( isMetaQuery )
+	{
+		err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock, 0 );
+		check_noerr( err );
+	}
+	if( drop ) goto exit;
 	
-	GAITesterAddCase( inContext->tester, testCase );
-	testCase = NULL;
+	for( i = 0; i < context->bitmapCount; ++i )
+	{
+		for( j = 0; ( context->bitmaps[ i ] != 0 ) && ( j < 64 ); ++j )
+		{
+			const uint64_t		bitmask = UINT64_C( 1 ) << j;
+			
+			if( context->bitmaps[ i ] & bitmask )
+			{
+				context->bitmaps[ i ] &= ~bitmask;
+				
+				err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock,
+					( i * 64 ) + j + 1 );
+				check_noerr( err );
+			}
+		}
+	}
 	
 exit:
-	if( testCase ) GAITestCaseFree( testCase );
-	return( err );
+	return;
 }
 
 //===========================================================================================================================
-//	_GAIPerfWriteTestCaseTitle
+//	_MDNSReplierAnswerQuery
 //===========================================================================================================================
 
-#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 )
+static OSStatus
+	_MDNSReplierAnswerQuery(
+		MDNSReplierContext *	inContext,
+		const uint8_t *			inQueryPtr,
+		size_t					inQueryLen,
+		sockaddr_ip *			inSender,
+		SocketRef				inSock,
+		unsigned int			inIndex )
 {
-	OSStatus			err;
-	GAITestCase *		testCase = NULL;
-	char				title[ kTestCaseTitleBufferSize ];
-	unsigned int		timeLimitMs, i;
+	OSStatus				err;
+	const DNSHeader *		hdr;
+	const uint8_t *			ptr;
+	unsigned int			questionCount, answerCount, i;
+	MRResourceRecord *		ucastAnswerList = NULL;
+	MRResourceRecord *		mcastAnswerList = NULL;
 	
-	// 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.
+	require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr );
 	
-	_GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
-		kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
-		inContext->defaultIterCount, true );
+	// Get answers for questions.
 	
-	timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount );
-	err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-	require_noerr( err, exit );
+	check( inQueryLen >= kDNSHeaderLength );
+	hdr = (const DNSHeader *) inQueryPtr;
+	questionCount = DNSHeaderGetQuestionCount( hdr );
 	
-	for( i = 0; i < inContext->defaultIterCount; ++i )
+	ptr = (const uint8_t *) &hdr[ 1 ];
+	for( i = 0; i < questionCount; ++i )
 	{
-		err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
-			kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+		MRResourceRecord **		answerListPtr;
+		uint16_t				qtype, qclass;
+		uint8_t					qname[ kDomainNameLengthMax ];
+		
+		err = DNSMessageExtractQuestion( inQueryPtr, inQueryLen, ptr, qname, &qtype, &qclass, &ptr );
+		require_noerr_quiet( err, exit );
+		
+		if( qclass & kQClassUnicastResponseBit )
+		{
+			qclass &= ~kQClassUnicastResponseBit;
+			answerListPtr = &ucastAnswerList;
+		}
+		else
+		{
+			answerListPtr = &mcastAnswerList;
+		}
+		
+		err = _MDNSReplierAnswerListAdd( inContext, answerListPtr, inIndex, qname, qtype, qclass );
 		require_noerr( err, exit );
 	}
+	require_action_quiet( mcastAnswerList || ucastAnswerList, exit, err = kNoErr );
 	
-	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.
+	// Suppress known answers.
+	// Records in the Answer section of the query message are known answers, so remove them from the answer lists.
+	// See <https://tools.ietf.org/html/rfc6762#section-7.1>.
 	
-	switch( inContext->outputFormat )
+	answerCount = DNSHeaderGetAnswerCount( hdr );
+	for( i = 0; i < answerCount; ++i )
 	{
-		case kGAIPerfOutputFormat_JSON:
-			results = CFCreateJSONData( plist, kJSONFlags_None, NULL );
-			require_action( results, exit, err = kUnknownErr );
-			break;
+		const uint8_t *		rdataPtr;
+		const uint8_t *		recordPtr;
+		uint16_t			type, class;
+		uint8_t				name[ kDomainNameLengthMax ];
+		uint8_t				instance[ kDomainNameLengthMax ];
 		
-		case kGAIPerfOutputFormat_XML:
-			results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL );
-			require_action( results, exit, err = kUnknownErr );
-			break;
+		recordPtr = ptr;
+		err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, ptr, NULL, &type, &class, NULL, NULL, NULL, &ptr );
+		require_noerr_quiet( err, exit );
 		
-		case kGAIPerfOutputFormat_Binary:
-			results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
-			require_action( results, exit, err = kUnknownErr );
-			break;
+		if( ( type != kDNSServiceType_PTR ) || ( class != kDNSServiceClass_IN ) ) continue;
 		
-		default:
-			err = kValueErr;
-			goto exit;
+		err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, recordPtr, name, NULL, NULL, NULL, &rdataPtr, NULL, NULL );
+		require_noerr( err, exit );
+		
+		err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, rdataPtr, instance, NULL );
+		require_noerr_quiet( err, exit );
+		
+		if( ucastAnswerList ) _MDNSReplierAnswerListRemovePTR( &ucastAnswerList, name, instance );
+		if( mcastAnswerList ) _MDNSReplierAnswerListRemovePTR( &mcastAnswerList, name, instance );
 	}
+	require_action_quiet( mcastAnswerList || ucastAnswerList, exit, err = kNoErr );
 	
-	// Write formatted results to file or stdout.
+	// Send or drop responses.
 	
-	if( inContext->outputFilePath )
+	if( ucastAnswerList )
 	{
-		file = fopen( inContext->outputFilePath, "wb" );
-		err = map_global_value_errno( file, file );
+		err = _MDNSReplierSendOrDropResponse( inContext, ucastAnswerList, inSender, inSock, inIndex, true );
 		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 )
+	if( mcastAnswerList )
 	{
-		err = WriteANSIFile( file, "\n", 1 );
+		err = _MDNSReplierSendOrDropResponse( inContext, mcastAnswerList, inSender, inSock, inIndex, false );
 		require_noerr( err, exit );
 	}
+	err = kNoErr;
 	
 exit:
-	CFReleaseNullSafe( plist );
-	CFReleaseNullSafe( results );
-	if( file && ( file != stdout ) ) fclose( file );
-	GAIPerfContextFree( inContext );
-	exit( err ? 1 : 0 );
+	_MRResourceRecordFreeList( ucastAnswerList );
+	_MRResourceRecordFreeList( mcastAnswerList );
+	return( err );
 }
 
 //===========================================================================================================================
-//	GAIPerfResultsHandler
+//	_MDNSReplierAnswerListAdd
 //===========================================================================================================================
 
-// 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 )
+static OSStatus
+	_MDNSReplierAnswerListAdd(
+		MDNSReplierContext *	inContext,
+		MRResourceRecord **		inAnswerList,
+		unsigned int			inIndex,
+		const uint8_t *			inName,
+		unsigned int			inType,
+		unsigned int			inClass )
 {
 	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 )
+	uint8_t *					recordName	= NULL;
+	uint8_t *					rdataPtr	= NULL;
+	size_t						rdataLen;
+	MRResourceRecord *			answer;
+	MRResourceRecord **			answerPtr;
+	const uint8_t * const		hostname	= inContext->hostname;
+	unsigned int				i;
+	uint32_t					index;
+	unsigned int				count, txtSize;
+	
+	require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr );
+	require_action_quiet( inClass == kDNSServiceClass_IN, exit, err = kNoErr );
+	
+	for( answerPtr = inAnswerList; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next )
+	{
+		if( ( answer->type == inType ) && DomainNameEqual( answer->name, inName ) )
 		{
-			namesAreUnique = true;
+			err = kNoErr;
+			goto exit;
 		}
-		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 );
+	// Index 0 is reserved for answering queries about the mdnsreplier, while all other index values up to the maximum
+	// instance count are for answering queries about service instances.
 	
-	GAIPerfStatsInit( &stats );
-	GAIPerfStatsInit( &firstStats );
-	GAIPerfStatsInit( &connStats );
-	
-	sum			= 0.0;
-	firstSum	= 0.0;
-	connSum		= 0.0;
-	for( i = startIndex; i < count; ++i )
+	if( inIndex == 0 )
+	{
+		if( _MDNSReplierAboutRecordNameMatch( inContext, inName ) )
+		{
+			int		listHasTXT = false;
+			
+			if( inType == kDNSServiceType_ANY )
+			{
+				for( answer = *inAnswerList; answer; answer = answer->next )
+				{
+					if( ( answer->type == kDNSServiceType_TXT ) && DomainNameEqual( answer->name, inName ) )
+					{
+						listHasTXT = true;
+						break;
+					}
+				}
+			}
+			
+			if( ( inType == kDNSServiceType_TXT ) || ( ( inType == kDNSServiceType_ANY ) && !listHasTXT ) )
+			{
+				err = DomainNameDupLower( inName, &recordName, NULL );
+				require_noerr( err, exit );
+				
+				err = CreateTXTRecordDataFromString( "ready=yes", ',', &rdataPtr, &rdataLen );
+				require_noerr( err, exit );
+				
+				err = _MRResourceRecordCreate( recordName, kDNSServiceType_TXT, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+					(uint16_t) rdataLen, rdataPtr, &answer );
+				require_noerr( err, exit );
+				recordName	= NULL;
+				rdataPtr	= NULL;
+				
+				*answerPtr = answer;
+			}
+			else if( inType == kDNSServiceType_NSEC )
+			{
+				err = DomainNameDupLower( inName, &recordName, NULL );
+				require_noerr( err, exit );
+				
+				err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_TXT );
+				require_noerr( err, exit );
+				
+				err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+					(uint16_t) rdataLen, rdataPtr, &answer );
+				require_noerr( err, exit );
+				recordName	= NULL;
+				rdataPtr	= NULL;
+				
+				*answerPtr = answer;
+			}
+		}
+	}
+	else if( _MDNSReplierHostnameMatch( inContext, inName, &index ) && ( index == inIndex ) )
 	{
-		value = (double) inResults[ i ].timeUs;
-		if( value < stats.min ) stats.min = value;
-		if( value > stats.max ) stats.max = value;
-		sum += value;
+		int		listHasA	= false;
+		int		listHasAAAA	= false;
 		
-		value = (double) inResults[ i ].firstTimeUs;
-		if( value < firstStats.min ) firstStats.min = value;
-		if( value > firstStats.max ) firstStats.max = value;
-		firstSum += value;
+		if( inType == kDNSServiceType_ANY )
+		{
+			for( answer = *inAnswerList; answer; answer = answer->next )
+			{
+				if( answer->type == kDNSServiceType_A )
+				{
+					if( !listHasA && DomainNameEqual( answer->name, inName ) ) listHasA = true;
+				}
+				else if( answer->type == kDNSServiceType_AAAA )
+				{
+					if( !listHasAAAA && DomainNameEqual( answer->name, inName ) ) listHasAAAA = true;
+				}
+				if( listHasA && listHasAAAA ) break;
+			}
+		}
 		
-		value = (double) inResults[ i ].connectionTimeUs;
-		if( value < connStats.min ) connStats.min = value;
-		if( value > connStats.max ) connStats.max = value;
-		connSum += value;
+		if( ( inType == kDNSServiceType_A ) || ( ( inType == kDNSServiceType_ANY ) && !listHasA ) )
+		{
+			for( i = 1; i <= inContext->recordCountA; ++i )
+			{
+				err = DomainNameDupLower( inName, &recordName, NULL );
+				require_noerr( err, exit );
+				
+				rdataLen = 4;
+				rdataPtr = (uint8_t *) malloc( rdataLen );
+				require_action( rdataPtr, exit, err = kNoMemoryErr );
+				
+				rdataPtr[ 0 ] = 0;
+				WriteBig16( &rdataPtr[ 1 ], inIndex );
+				rdataPtr[ 3 ] = (uint8_t) i;
+				
+				err = _MRResourceRecordCreate( recordName, kDNSServiceType_A, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+					(uint16_t) rdataLen, rdataPtr, &answer );
+				require_noerr( err, exit );
+				recordName	= NULL;
+				rdataPtr	= NULL;
+				
+				*answerPtr = answer;
+				 answerPtr = &answer->next;
+			}
+		}
 		
-		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( ( inType == kDNSServiceType_AAAA ) || ( ( inType == kDNSServiceType_ANY ) && !listHasAAAA ) )
+		{
+			for( i = 1; i <= inContext->recordCountAAAA; ++i )
+			{
+				err = DomainNameDupLower( inName, &recordName, NULL );
+				require_noerr( err, exit );
+				
+				rdataLen = 16;
+				rdataPtr = (uint8_t *) memdup( kMDNSReplierBaseAddrV6, rdataLen );
+				require_action( rdataPtr, exit, err = kNoMemoryErr );
+				
+				WriteBig16( &rdataPtr[ 12 ], inIndex );
+				rdataPtr[ 15 ] = (uint8_t) i;
+				
+				err = _MRResourceRecordCreate( recordName, kDNSServiceType_AAAA, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+					(uint16_t) rdataLen, rdataPtr, &answer );
+				require_noerr( err, exit );
+				recordName	= NULL;
+				rdataPtr	= NULL;
+				
+				*answerPtr = answer;
+				 answerPtr = &answer->next;
+			}
+		}
+		else if( inType == kDNSServiceType_NSEC )
+		{
+			err = DomainNameDupLower( inName, &recordName, NULL );
+			require_noerr( err, exit );
+			
+			if( ( inContext->recordCountA > 0 ) && ( inContext->recordCountAAAA > 0 ) )
+			{
+				err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 2, kDNSServiceType_A, kDNSServiceType_AAAA );
+				require_noerr( err, exit );
+			}
+			else if( inContext->recordCountA > 0 )
+			{
+				err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_A );
+				require_noerr( err, exit );
+			}
+			else if( inContext->recordCountAAAA > 0 )
+			{
+				err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_AAAA );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 0 );
+				require_noerr( err, exit );
+			}
+			
+			err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+				(uint16_t) rdataLen, rdataPtr, &answer );
+			require_noerr( err, exit );
+			recordName	= NULL;
+			rdataPtr	= NULL;
+			
+			*answerPtr = answer;
+		}
 	}
-	
-	if( count > 0 )
+	else if( _MDNSReplierServiceTypeMatch( inContext, inName, NULL, &count ) && ( count >= inIndex ) )
 	{
-		stats.mean		= sum      / count;
-		firstStats.mean	= firstSum / count;
-		connStats.mean	= connSum  / count;
+		int		listHasPTR = false;
 		
-		sum			= 0.0;
-		firstSum	= 0.0;
-		connSum		= 0.0;
-		for( i = startIndex; i < count; ++i )
+		if( inType == kDNSServiceType_ANY )
 		{
-			diff		 = stats.mean - (double) inResults[ i ].timeUs;
-			sum			+= ( diff * diff );
+			for( answer = *inAnswerList; answer; answer = answer->next )
+			{
+				if( ( answer->type == kDNSServiceType_PTR ) && DomainNameEqual( answer->name, inName ) )
+				{
+					listHasPTR = true;
+					break;
+				}
+			}
+		}
+		
+		if( ( inType == kDNSServiceType_PTR ) || ( ( inType == kDNSServiceType_ANY ) && !listHasPTR ) )
+		{
+			size_t				recordNameLen;
+			uint8_t *			ptr;
+			uint8_t *			lim;
 			
-			diff		 = firstStats.mean - (double) inResults[ i ].firstTimeUs;
-			firstSum	+= ( diff * diff );
+			err = DomainNameDupLower( inName, &recordName, &recordNameLen );
+			require_noerr( err, exit );
 			
-			diff		 = connStats.mean - (double) inResults[ i ].connectionTimeUs;
-			connSum		+= ( diff * diff );
+			rdataLen = 1 + hostname[ 0 ] + 10 + recordNameLen;
+			rdataPtr = (uint8_t *) malloc( rdataLen );
+			require_action( rdataPtr, exit, err = kNoMemoryErr );
+			
+			lim = &rdataPtr[ rdataLen ];
+			
+			ptr = &rdataPtr[ 1 ];
+			memcpy( ptr, &hostname[ 1 ], hostname[ 0 ] );
+			ptr += hostname[ 0 ];
+			if( inIndex != 1 ) SNPrintF_Add( (char **) &ptr, (char *) lim, " (%u)", inIndex );
+			rdataPtr[ 0 ] = (uint8_t)( ptr - &rdataPtr[ 1 ] );
+			
+			check( (size_t)( lim - ptr ) >= recordNameLen );
+			memcpy( ptr, recordName, recordNameLen );
+			ptr += recordNameLen;
+			
+			rdataLen = (size_t)( ptr - rdataPtr );
+			
+			err = _MRResourceRecordCreate( recordName, kDNSServiceType_PTR, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+				(uint16_t) rdataLen, rdataPtr, &answer );
+			require_noerr( err, exit );
+			recordName	= NULL;
+			rdataPtr	= NULL;
+			
+			*answerPtr = answer;
 		}
-		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 );
+	else if( _MDNSReplierServiceInstanceNameMatch( inContext, inName, &index, &txtSize, &count ) &&
+		( index == inIndex ) && ( count >= inIndex ) )
+	{
+		int		listHasSRV = false;
+		int		listHasTXT = false;
+		
+		if( inType == kDNSServiceType_ANY )
+		{
+			for( answer = *inAnswerList; answer; answer = answer->next )
+			{
+				if( answer->type == kDNSServiceType_SRV )
+				{
+					if( !listHasSRV && DomainNameEqual( answer->name, inName ) ) listHasSRV = true;
+				}
+				else if( answer->type == kDNSServiceType_TXT )
+				{
+					if( !listHasTXT && DomainNameEqual( answer->name, inName ) ) listHasTXT = true;
+				}
+				if( listHasSRV && listHasTXT ) break;
+			}
+		}
+		
+		if( ( inType == kDNSServiceType_SRV ) || ( ( inType == kDNSServiceType_ANY ) && !listHasSRV ) )
+		{
+			SRVRecordDataFixedFields *		fields;
+			uint8_t *						ptr;
+			uint8_t *						lim;
+			uint8_t *						targetPtr;
+			
+			err = DomainNameDupLower( inName, &recordName, NULL );
+			require_noerr( err, exit );
+			
+			rdataLen = sizeof( SRVRecordDataFixedFields ) + 1 + hostname[ 0 ] + 10 + kLocalNameLen;
+			rdataPtr = (uint8_t *) malloc( rdataLen );
+			require_action( rdataPtr, exit, err = kNoMemoryErr );
+			
+			lim = &rdataPtr[ rdataLen ];
+			
+			fields = (SRVRecordDataFixedFields *) rdataPtr;
+			SRVRecordDataFixedFieldsSet( fields, 0, 0, (uint16_t)( kMDNSReplierPortBase + txtSize ) );
+			
+			targetPtr = (uint8_t *) &fields[ 1 ];
+			
+			ptr = &targetPtr[ 1 ];
+			memcpy( ptr, &hostname[ 1 ], hostname[ 0 ] );
+			ptr += hostname[ 0 ];
+			if( inIndex != 1 ) SNPrintF_Add( (char **) &ptr, (char *) lim, "-%u", inIndex );
+			targetPtr[ 0 ] = (uint8_t)( ptr - &targetPtr[ 1 ] );
+			
+			check( (size_t)( lim - ptr ) >= kLocalNameLen );
+			memcpy( ptr, kLocalName, kLocalNameLen );
+			ptr += kLocalNameLen;
+			
+			rdataLen = (size_t)( ptr - rdataPtr );
+			
+			err = _MRResourceRecordCreate( recordName, kDNSServiceType_SRV, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+				(uint16_t) rdataLen, rdataPtr, &answer );
+			require_noerr( err, exit );
+			recordName	= NULL;
+			rdataPtr	= NULL;
+			
+			*answerPtr = answer;
+			 answerPtr = &answer->next;
+		}
+		
+		if( ( inType == kDNSServiceType_TXT ) || ( ( inType == kDNSServiceType_ANY ) && !listHasTXT ) )
+		{
+			err = DomainNameDupLower( inName, &recordName, NULL );
+			require_noerr( err, exit );
+			
+			rdataLen = txtSize;
+			err = _MDNSReplierCreateTXTRecord( inName, rdataLen, &rdataPtr );
+			require_noerr( err, exit );
+			
+			err = _MRResourceRecordCreate( recordName, kDNSServiceType_TXT, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+				(uint16_t) rdataLen, rdataPtr, &answer );
+			require_noerr( err, exit );
+			recordName	= NULL;
+			rdataPtr	= NULL;
+			
+			*answerPtr = answer;
+		}
+		else if( inType == kDNSServiceType_NSEC )
+		{
+			err = DomainNameDupLower( inName, &recordName, NULL );
+			require_noerr( err, exit );
+			
+			err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 2, kDNSServiceType_TXT, kDNSServiceType_SRV );
+			require_noerr( err, exit );
+			
+			err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+				(uint16_t) rdataLen, rdataPtr, &answer );
+			require_noerr( err, exit );
+			recordName	= NULL;
+			rdataPtr	= NULL;
+			
+			*answerPtr = answer;
+		}
+	}
+	err = kNoErr;
 	
 exit:
-	CFReleaseNullSafe( results );
+	FreeNullSafe( recordName );
+	FreeNullSafe( rdataPtr );
+	return( err );
 }
 
 //===========================================================================================================================
-//	GAIPerfSignalHandler
+//	_MDNSReplierAnswerListRemovePTR
 //===========================================================================================================================
 
-static void	GAIPerfSignalHandler( void *inContext )
+static void
+	_MDNSReplierAnswerListRemovePTR(
+		MRResourceRecord **	inAnswerListPtr,
+		const uint8_t *		inName,
+		const uint8_t *		inRData )
 {
-	GAIPerfContext * const		context = (GAIPerfContext *) inContext;
+	MRResourceRecord *		answer;
+	MRResourceRecord **		answerPtr;
 	
-	context->gotSignal = true;
-	if( context->tester && context->testerStarted )
+	for( answerPtr = inAnswerListPtr; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next )
 	{
-		GAITesterStop( context->tester );
+		if( ( answer->type == kDNSServiceType_PTR ) && ( answer->class == kDNSServiceClass_IN ) &&
+			DomainNameEqual( answer->name, inName ) && DomainNameEqual( answer->rdata, inRData ) ) break;
 	}
-	else
+	if( answer )
 	{
-		exit( 1 );
+		*answerPtr = answer->next;
+		_MRResourceRecordFree( answer );
 	}
 }
 
 //===========================================================================================================================
-//	GAITesterCreate
+//	_MDNSReplierSendOrDropResponse
 //===========================================================================================================================
 
-typedef enum
+static OSStatus
+	_MDNSReplierSendOrDropResponse(
+		MDNSReplierContext *	inContext,
+		MRResourceRecord *		inAnswerList,
+		sockaddr_ip *			inQuerier,
+		SocketRef				inSock,
+		unsigned int			inIndex,
+		Boolean					inUnicast )
 {
-	kGAITestConnType_UseMainConnection		= 1,
-	kGAITestConnType_OwnSharedConnection	= 2
+	OSStatus					err;
+	uint8_t *					responsePtr	= NULL;
+	size_t						responseLen;
+	const struct sockaddr *		destAddr;
+	ssize_t						n;
+	const double				dropRate	= inUnicast ? inContext->ucastDropRate : inContext->mcastDropRate;
+	int							drop;
 	
-}	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.
+	check( inIndex <= inContext->maxInstanceCount );
 	
-	// Variables for current test item.
+	// If maxDropCount > 0, then the drop rates apply only to the first maxDropCount responses. Otherwise, all messages are
+	// subject to their respective drop rate. Also, responses to queries about mDNS replier itself (indicated by index 0),
+	// as opposed to those for service instance records, are never dropped.
 	
-	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;
+	drop = false;
+	if( inIndex > 0 )
+	{
+		if( inContext->maxDropCount > 0 )
+		{
+			uint8_t * const		dropCount = &inContext->dropCounters[ inIndex - 1 ];
+			
+			if( *dropCount < inContext->maxDropCount )
+			{
+				if( ShouldDrop( dropRate ) ) drop = true;
+				*dropCount += 1;
+			}
+		}
+		else if( ShouldDrop( dropRate ) )
+		{
+			drop = true;
+		}
+	}
 	
-	CF_OBJECT_CREATE( GAITester, obj, err, exit );
+	err = _MDNSReplierCreateResponse( inContext, inAnswerList, inIndex, &responsePtr, &responseLen );
+	require_noerr( err, exit );
 	
-	ReplaceDispatchQueue( &obj->queue, inQueue );
-	obj->callDelayMs		= inCallDelayMs;
-	obj->serverPID			= -1;
-	obj->serverDelayMs		= inServerDelayMs;
-	obj->serverDefaultTTL	= inServerDefaultTTL;
+	if( inUnicast )
+	{
+		destAddr = &inQuerier->sa;
+	}
+	else
+	{
+		destAddr = ( inQuerier->sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+	}
 	
-	*outTester = obj;
-	obj = NULL;
-	err = kNoErr;
+	mr_ulog( kLogLevelInfo, "%s %zu byte response to %##a:\n\n%#1{du:dnsmsg}",
+		drop ? "Dropping" : "Sending", responseLen, destAddr, responsePtr, responseLen );
+	
+	if( !drop )
+	{
+		n = sendto( inSock, (char *) responsePtr, responseLen, 0, destAddr, SockAddrGetSize( destAddr ) );
+		err = map_socket_value_errno( inSock, n == (ssize_t) responseLen, n );
+		require_noerr( err, exit );
+	}
 	
 exit:
-	CFReleaseNullSafe( obj );
+	FreeNullSafe( responsePtr );
 	return( err );
 }
 
 //===========================================================================================================================
-//	_GAITesterFinalize
+//	_MDNSReplierCreateResponse
 //===========================================================================================================================
 
-static void	_GAITesterFinalize( CFTypeRef inObj )
+static OSStatus
+	_MDNSReplierCreateResponse(
+		MDNSReplierContext *	inContext,
+		MRResourceRecord *		inAnswerList,
+		unsigned int			inIndex,
+		uint8_t **				outResponsePtr,
+		size_t *				outResponseLen )
 {
-	GAITesterRef const		me = (GAITesterRef) inObj;
-	GAITestCase *			testCase;
+	OSStatus				err;
+	DataBuffer				responseDB;
+	DNSHeader				hdr;
+	MRResourceRecord *		answer;
+	uint8_t *				responsePtr;
+	size_t					responseLen, len;
+	unsigned int			answerCount, recordCount;
+	MRNameOffsetItem *		nameOffsetList = NULL;
 	
-	check( !me->opRef );
-	check( !me->mainRef );
-	check( !me->caseTimer );
-	dispatch_forget( &me->queue );
-	while( ( testCase = me->caseList ) != NULL )
+	DataBuffer_Init( &responseDB, NULL, 0, SIZE_MAX );
+	
+	// The current answers in the answer list will make up the response's Answer Record Section.
+	
+	answerCount = 0;
+	for( answer = inAnswerList; answer; answer = answer->next ) { ++answerCount; }
+	
+	// Unless configured not to, add any additional answers to the answer list for the Additional Record Section.
+	
+	if( !inContext->noAdditionals )
 	{
-		me->caseList = testCase->next;
-		GAITestCaseFree( testCase );
+		for( answer = inAnswerList; answer; answer = answer->next )
+		{
+			switch( answer->type )
+			{
+				case kDNSServiceType_PTR:
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->rdata, kDNSServiceType_SRV,
+						answer->class );
+					require_noerr( err, exit );
+					
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->rdata, kDNSServiceType_TXT,
+						answer->class );
+					require_noerr( err, exit );
+					break;
+				
+				case kDNSServiceType_SRV:
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->target, kDNSServiceType_A,
+						answer->class );
+					require_noerr( err, exit );
+					
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->target, kDNSServiceType_AAAA,
+						answer->class );
+					require_noerr( err, exit );
+					
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+						answer->class );
+					require_noerr( err, exit );
+					break;
+				
+				case kDNSServiceType_TXT:
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+						answer->class );
+					require_noerr( err, exit );
+					break;
+				
+				case kDNSServiceType_A:
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_AAAA,
+						answer->class );
+					require_noerr( err, exit );
+					
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+						answer->class );
+					require_noerr( err, exit );
+					break;
+				
+				case kDNSServiceType_AAAA:
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_A,
+						answer->class );
+					require_noerr( err, exit );
+					
+					err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+						answer->class );
+					require_noerr( err, exit );
+					break;
+				
+				default:
+					break;
+			}
+		}
 	}
-}
-
-//===========================================================================================================================
-//	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 );
+	// Append a provisional header to the response message.
 	
-	argv[ 0 ] = "/bin/sh";
-	argv[ 1 ] = "-c";
-	argv[ 2 ] = command;
-	argv[ 3 ] = NULL;
-	err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ );
+	memset( &hdr, 0, sizeof( hdr ) );
+	DNSHeaderSetFlags( &hdr, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer );
+	
+	err = DataBuffer_Append( &responseDB, &hdr, sizeof( hdr ) );
 	require_noerr( err, exit );
 	
-	me->currentCase = me->caseList;
-	me->currentItem = me->currentCase ? me->currentCase->itemList : NULL;
-	_GAITesterInitializeCurrentTest( me );
+	// Append answers to response message.
+	
+	responseLen = DataBuffer_GetLen( &responseDB );
+	recordCount = 0;
+	for( answer = inAnswerList; answer; answer = answer->next )
+	{
+		DNSRecordFixedFields		fields;
+		unsigned int				class;
+		
+		// Append record NAME.
+		
+		err = _MDNSReplierAppendNameToResponse( &responseDB, answer->name, &nameOffsetList );
+		require_noerr( err, exit );
+		
+		// Append record TYPE, CLASS, TTL, and provisional RDLENGTH.
+		
+		class = answer->class;
+		if( ( answer->type == kDNSServiceType_SRV ) || ( answer->type == kDNSServiceType_TXT )  ||
+			( answer->type == kDNSServiceType_A )   || ( answer->type == kDNSServiceType_AAAA ) ||
+			( answer->type == kDNSServiceType_NSEC ) )
+		{
+			class |= kRRClassCacheFlushBit;
+		}
+		
+		DNSRecordFixedFieldsSet( &fields, answer->type, (uint16_t) class, answer->ttl, (uint16_t) answer->rdlength );
+		err = DataBuffer_Append( &responseDB, &fields, sizeof( fields ) );
+		require_noerr( err, exit );
+		
+		// Append record RDATA.
+		// The RDATA of PTR, SRV, and NSEC records contain domain names, which are subject to name compression.
+		
+		if( ( answer->type == kDNSServiceType_PTR ) || ( answer->type == kDNSServiceType_SRV ) ||
+			( answer->type == kDNSServiceType_NSEC ) )
+		{
+			size_t				rdlength;
+			uint8_t *			rdLengthPtr;
+			const size_t		rdLengthOffset	= DataBuffer_GetLen( &responseDB ) - 2;
+			const size_t		rdataOffset		= DataBuffer_GetLen( &responseDB );
+			
+			if( answer->type == kDNSServiceType_PTR )
+			{
+				err = _MDNSReplierAppendNameToResponse( &responseDB, answer->rdata, &nameOffsetList );
+				require_noerr( err, exit );
+			}
+			else if( answer->type == kDNSServiceType_SRV )
+			{
+				require_fatal( answer->target == &answer->rdata[ 6 ], "Bad SRV record target pointer." );
+				
+				err = DataBuffer_Append( &responseDB, answer->rdata, (size_t)( answer->target - answer->rdata ) );
+				require_noerr( err, exit );
+				
+				err = _MDNSReplierAppendNameToResponse( &responseDB, answer->target, &nameOffsetList );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				const size_t		nameLen = DomainNameLength( answer->rdata );
+				
+				err = _MDNSReplierAppendNameToResponse( &responseDB, answer->rdata, &nameOffsetList );
+				require_noerr( err, exit );
+				
+				require_fatal( answer->rdlength > nameLen, "Bad NSEC record data length." );
+				
+				err = DataBuffer_Append( &responseDB, &answer->rdata[ nameLen ], answer->rdlength - nameLen );
+				require_noerr( err, exit );
+			}
+			
+			// Set the actual RDLENGTH, which may be less than the original due to name compression.
+			
+			rdlength = DataBuffer_GetLen( &responseDB ) - rdataOffset;
+			check( rdlength <= UINT16_MAX );
+			
+			rdLengthPtr = DataBuffer_GetPtr( &responseDB ) + rdLengthOffset;
+			WriteBig16( rdLengthPtr, rdlength );
+		}
+		else
+		{
+			err = DataBuffer_Append( &responseDB, answer->rdata, answer->rdlength );
+			require_noerr( err, exit );
+		}
+		
+		if( DataBuffer_GetLen( &responseDB ) > kMDNSMessageSizeMax ) break;
+		responseLen = DataBuffer_GetLen( &responseDB );
+		++recordCount;
+	}
 	
-	// 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.
+	// Set the response header's Answer and Additional record counts.
+	// Note: recordCount may be less than answerCount if including all answerCount records would cause the size of the
+	// response message to exceed the maximum mDNS message size.
 	
-	CFRetain( me );
-	dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun );
+	if( recordCount <= answerCount )
+	{
+		DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount );
+	}
+	else
+	{
+		DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), answerCount );
+		DNSHeaderSetAdditionalCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount - answerCount );
+	}
 	
-	CFRetain( me );
-	me->started = true;
-	if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext );
+	err = DataBuffer_Detach( &responseDB, &responsePtr, &len );
+	require_noerr( err, exit );
+	
+	if( outResponsePtr ) *outResponsePtr = responsePtr;
+	if( outResponseLen ) *outResponseLen = responseLen;
 	
 exit:
-	if( err ) _GAITesterStop( me );
-	CFRelease( me );
+	_MRNameOffsetItemFreeList( nameOffsetList );
+	DataBuffer_Free( &responseDB );
+	return( err );
 }
 
 //===========================================================================================================================
-//	GAITesterStop
+//	_MDNSReplierAppendNameToResponse
 //===========================================================================================================================
 
-static void	_GAITesterUserStop( void *inContext );
-
-static void	GAITesterStop( GAITesterRef me )
-{
-	CFRetain( me );
-	dispatch_async_f( me->queue, me, _GAITesterUserStop );
-}
-
-static void	_GAITesterUserStop( void *inContext )
+static OSStatus
+	_MDNSReplierAppendNameToResponse(
+		DataBuffer *		inResponse,
+		const uint8_t *		inName,
+		MRNameOffsetItem **	inNameOffsetListPtr )
 {
-	GAITesterRef const		me = (GAITesterRef) inContext;
+	OSStatus				err;
+	const uint8_t *			subname;
+	const uint8_t *			limit;
+	size_t					nameOffset;
+	MRNameOffsetItem *		item;
+	uint8_t					compressionPtr[ 2 ];
 	
-	_GAITesterStop( me );
-	CFRelease( me );
-}
-
-static void	_GAITesterStop( GAITesterRef me )
-{
-	OSStatus		err;
+	nameOffset = DataBuffer_GetLen( inResponse );
 	
-	DNSServiceForget( &me->opRef );
-	DNSServiceForget( &me->mainRef );
-	ForgetPacketCapture( &me->pcap );
-	dispatch_source_forget( &me->caseTimer );
-	if( me->serverPID != -1 )
+	// Find the name's longest subname (more accurately, its longest sub-FQDN) in the name compression list.
+	
+	for( subname = inName; subname[ 0 ] != 0; subname += ( 1 + subname[ 0 ] ) )
 	{
-		err = kill( me->serverPID, SIGTERM );
-		err = map_global_noerr_errno( err );
-		check_noerr( err );
+		for( item = *inNameOffsetListPtr; item; item = item->next )
+		{
+			if( DomainNameEqual( item->name, subname ) ) break;
+		}
+		
+		// If an item was found for this subname, then append a name compression pointer and we're done. Otherwise, append
+		// the subname's first label.
+		
+		if( item )
+		{
+			WriteDNSCompressionPtr( compressionPtr, item->offset );
+			
+			err = DataBuffer_Append( inResponse, compressionPtr, sizeof( compressionPtr ) );
+			require_noerr( err, exit );
+			break;
+		}
+		else
+		{
+			err = DataBuffer_Append( inResponse, subname, 1 + subname[ 0 ] );
+			require_noerr( err, exit );
+		}
 	}
+		
+	// If we made it to the root label, then no subname was able to be compressed. All of the name's labels up to the root
+	// label were appended to the response message, so a root label is needed to terminate the complete name.
 	
-	if( !me->stopped )
+	if( subname[ 0 ] == 0 )
 	{
-		me->stopped = true;
-		if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext );
-		if( me->started ) CFRelease( me );
+		err = DataBuffer_Append( inResponse, "", 1 );
+		require_noerr( err, exit );
 	}
-}
-
-//===========================================================================================================================
-//	GAITesterAddCase
-//===========================================================================================================================
-
-static void	GAITesterAddCase( GAITesterRef me, GAITestCase *inCase )
-{
-	GAITestCase **		ptr;
 	
-	for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {}
-	*ptr = inCase;
+	// Add subnames that weren't able to be compressed and their offsets to the name compression list.
+	
+	limit = subname;
+	for( subname = inName; subname < limit; subname += ( 1 + subname[ 0 ] ) )
+	{
+		const size_t		subnameOffset = nameOffset + (size_t)( subname - inName );
+		
+		if( subnameOffset > kDNSCompressionOffsetMax ) break;
+		
+		err = _MRNameOffsetItemCreate( subname, (uint16_t) subnameOffset, &item );
+		require_noerr( err, exit );
+		
+		item->next = *inNameOffsetListPtr;
+		*inNameOffsetListPtr = item;
+	}
+	err = kNoErr;
+	
+exit:
+	return( err );
 }
 
 //===========================================================================================================================
-//	GAITesterSetEventHandler
+//	_MDNSReplierServiceTypeMatch
 //===========================================================================================================================
 
-static void	GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext )
+static Boolean
+	_MDNSReplierServiceTypeMatch(
+		const MDNSReplierContext *	inContext,
+		const uint8_t *				inName,
+		unsigned int *				outTXTSize,
+		unsigned int *				outCount )
 {
-	me->eventHandler = inEventHandler;
-	me->eventContext = inEventContext;
+	OSStatus					err;
+	const char *				ptr;
+	const char *				end;
+	uint32_t					txtSize, count;
+	const uint8_t * const		serviceLabel	= inContext->serviceLabel;
+	int							nameMatches		= false;
+	
+	require_quiet( inName[ 0 ] >= serviceLabel[ 0 ], exit );
+	if( memicmp( &inName[ 1 ], &serviceLabel[ 1 ], serviceLabel[ 0 ] ) != 0 ) goto exit;
+	
+	ptr = (const char *) &inName[ 1 + serviceLabel[ 0 ] ];
+	end = (const char *) &inName[ 1 + inName[ 0 ] ];
+	
+	require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+	++ptr;
+	
+	err = DecimalTextToUInt32( ptr, end, &txtSize, &ptr );
+	require_noerr_quiet( err, exit );
+	require_quiet( txtSize <= UINT16_MAX, exit );
+	
+	require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+	++ptr;
+	
+	err = DecimalTextToUInt32( ptr, end, &count, &ptr );
+	require_noerr_quiet( err, exit );
+	require_quiet( count <= UINT16_MAX, exit );
+	require_quiet( ptr == end, exit );
+	
+	if( !DomainNameEqual( (const uint8_t *) ptr, (const uint8_t *) "\x04" "_tcp" "\x05" "local" ) ) goto exit;
+	nameMatches = true;
+	
+	if( outTXTSize )	*outTXTSize	= txtSize;
+	if( outCount )		*outCount	= count;
+	
+exit:
+	return( nameMatches ? true : false );
 }
 
 //===========================================================================================================================
-//	GAITesterSetResultsHandler
+//	_MDNSReplierServiceInstanceNameMatch
 //===========================================================================================================================
 
-static void	GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+static Boolean
+	_MDNSReplierServiceInstanceNameMatch(
+		const MDNSReplierContext *	inContext,
+		const uint8_t *				inName,
+		unsigned int *				outIndex,
+		unsigned int *				outTXTSize,
+		unsigned int *				outCount )
 {
-	me->resultsHandler = inResultsHandler;
-	me->resultsContext = inResultsContext;
+	OSStatus					err;
+	const uint8_t *				ptr;
+	const uint8_t *				end;
+	uint32_t					index;
+	unsigned int				txtSize, count;
+	const uint8_t * const		hostname	= inContext->hostname;
+	int							nameMatches	= false;
+	
+	require_quiet( inName[ 0 ] >= hostname[ 0 ], exit );
+	if( memicmp( &inName[ 1 ], &hostname[ 1 ], hostname[ 0 ] ) != 0 ) goto exit;
+	
+	ptr = &inName[ 1 + hostname[ 0 ] ];
+	end = &inName[ 1 + inName[ 0 ] ];
+	if( ptr < end )
+	{
+		require_quiet( ( end - ptr ) >= 2, exit );
+		require_quiet( ( ptr[ 0 ] == ' ' ) && ( ptr[ 1 ] == '(' ), exit );
+		ptr += 2;
+		
+        err = DecimalTextToUInt32( (const char *) ptr, (const char *) end, &index, (const char **) &ptr );
+		require_noerr_quiet( err, exit );
+		require_quiet( ( index >= 2 ) && ( index <= UINT16_MAX ), exit );
+		
+		require_quiet( ( ( end - ptr ) == 1 ) && ( *ptr == ')' ), exit );
+		++ptr;
+	}
+	else
+	{
+		index = 1;
+	}
+	
+	if( !_MDNSReplierServiceTypeMatch( inContext, ptr, &txtSize, &count ) ) goto exit;
+	nameMatches = true;
+	
+	if( outIndex )		*outIndex	= index;
+	if( outTXTSize )	*outTXTSize	= txtSize;
+	if( outCount )		*outCount	= count;
+	
+exit:
+	return( nameMatches ? true : false );
 }
 
 //===========================================================================================================================
-//	_GAITesterRun
+//	_MDNSReplierAboutRecordNameMatch
 //===========================================================================================================================
 
-static void	_GAITesterRun( void *inContext )
+static Boolean	_MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName )
 {
-	OSStatus				err;
-	GAITesterRef const		me		= (GAITesterRef) inContext;
-	GAITestItem *			item;
-	GAITestItemResult *		results	= NULL;
+	const uint8_t *				subname;
+	const uint8_t * const		hostname	= inContext->hostname;
+	int							nameMatches	= false;
 	
-	require_action_quiet( !me->stopped, exit, err = kNoErr );
+	if( strnicmpx( &inName[ 1 ], inName[ 0 ], "about" ) != 0 ) goto exit;
+	subname = NextLabel( inName );
 	
-	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 );
-		}
-	}
+	if( !MemIEqual( &subname[ 1 ], subname[ 0 ], &hostname[ 1 ], hostname[ 0 ] ) ) goto exit;
+	subname = NextLabel( subname );
+	
+	if( !DomainNameEqual( subname, kLocalName ) ) goto exit;
+	nameMatches = true;
 	
 exit:
-	FreeNullSafe( results );
-	if( err ) _GAITesterStop( me );
-	CFRelease( me );
+	return( nameMatches ? true : false );
 }
 
 //===========================================================================================================================
-//	_GAITesterCreatePacketCapture
+//	_MDNSReplierHostnameMatch
 //===========================================================================================================================
 
-static OSStatus	_GAITesterCreatePacketCapture( pcap_t **outPCap )
+static Boolean
+	_MDNSReplierHostnameMatch(
+		const MDNSReplierContext *	inContext,
+		const uint8_t *				inName,
+		unsigned int *				outIndex )
 {
-	OSStatus				err;
-	pcap_t *				pcap;
-	struct bpf_program		program;
-	char					errBuf[ PCAP_ERRBUF_SIZE ];
+	OSStatus					err;
+	const uint8_t *				ptr;
+	const uint8_t *				end;
+	uint32_t					index;
+	const uint8_t * const		hostname	= inContext->hostname;
+	int							nameMatches	= false;
 	
-	pcap = pcap_create( "lo0", errBuf );
-	require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+	require_quiet( inName[ 0 ] >= hostname[ 0 ], exit );
+	if( memicmp( &inName[ 1 ], &hostname[ 1 ], hostname[ 0 ] ) != 0 ) goto exit;
 	
-	err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
-	require_noerr_action( err, exit, err = kUnknownErr );
+	ptr = &inName[ 1 + hostname[ 0 ] ];
+	end = &inName[ 1 + inName[ 0 ] ];
+	if( ptr < end )
+	{
+		require_quiet( *ptr == '-', exit );
+		++ptr;
+		
+		err = DecimalTextToUInt32( (const char *) ptr, (const char *) end, &index, (const char **) &ptr );
+		require_noerr_quiet( err, exit );
+		require_quiet( ( index >= 2 ) && ( index <= UINT16_MAX ), exit );
+		require_quiet( ptr == end, exit );
+	}
+	else
+	{
+		index = 1;
+	}
 	
-	err = pcap_set_snaplen( pcap, 512 );
-	require_noerr_action( err, exit, err = kUnknownErr );
+	if( !DomainNameEqual( ptr, kLocalName ) ) goto exit;
+	nameMatches = true;
 	
-	err = pcap_set_immediate_mode( pcap, 0 );
-	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	if( outIndex ) *outIndex = index;
 	
-	err = pcap_activate( pcap );
-	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+exit:
+	return( nameMatches ? true : false );
+}
+
+//===========================================================================================================================
+//	_MDNSReplierCreateTXTRecord
+//===========================================================================================================================
+
+static OSStatus	_MDNSReplierCreateTXTRecord( const uint8_t *inRecordName, size_t inSize, uint8_t **outTXT )
+{
+	OSStatus		err;
+	uint8_t *		txt;
+	uint8_t *		ptr;
+	size_t			i, wholeCount, remCount;
+	uint32_t		hash;
+	int				n;
+	uint8_t			txtStr[ 16 ];
 	
-	err = pcap_setdirection( pcap, PCAP_D_INOUT );
-	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	require_action_quiet( inSize > 0, exit, err = kSizeErr );
 	
-	err = pcap_setnonblock( pcap, 1, errBuf );
-	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	txt = (uint8_t *) malloc( inSize );
+	require_action( txt, exit, err = kNoMemoryErr );
 	
-	err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
-	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	hash = FNV1( inRecordName, DomainNameLength( inRecordName ) );
 	
-	err = pcap_setfilter( pcap, &program );
-	pcap_freecode( &program );
-	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+	txtStr[ 0 ] = 15;
+	n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash );
+	check( n == 15 );
 	
-	*outPCap = pcap;
-	pcap = NULL;
+	ptr = txt;
+	wholeCount = inSize / 16;
+	for( i = 0; i < wholeCount; ++i )
+	{
+		memcpy( ptr, txtStr, 16 );
+		ptr += 16;
+	}
+	
+	remCount = inSize % 16;
+	if( remCount > 0 )
+	{
+		txtStr[ 0 ] = (uint8_t)( remCount - 1 );
+		memcpy( ptr, txtStr, remCount );
+		ptr += remCount;
+	}
+	check( ptr == &txt[ inSize ] );
+	
+	*outTXT = txt;
+	err = kNoErr;
 	
 exit:
-	if( pcap ) pcap_close( pcap );
 	return( err );
 }
 
 //===========================================================================================================================
-//	_GAITesterTimeout
+//	_MRResourceRecordCreate
 //===========================================================================================================================
 
-static void	_GAITesterTimeout( void *inContext )
+static OSStatus
+	_MRResourceRecordCreate(
+		uint8_t *			inName,
+		uint16_t			inType,
+		uint16_t			inClass,
+		uint32_t			inTTL,
+		uint16_t			inRDLength,
+		uint8_t *			inRData,
+		MRResourceRecord **	outRecord )
 {
-	GAITesterRef const		me = (GAITesterRef) inContext;
+	OSStatus				err;
+	MRResourceRecord *		obj;
+	
+	obj = (MRResourceRecord *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->name		= inName;
+	obj->type		= inType;
+	obj->class		= inClass;
+	obj->ttl		= inTTL;
+	obj->rdlength	= inRDLength;
+	obj->rdata		= inRData;
+	
+	if( inType == kDNSServiceType_SRV )
+	{
+		require_action_quiet( obj->rdlength > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr );
+		obj->target = obj->rdata + sizeof( SRVRecordDataFixedFields );
+	}
 	
-	dispatch_source_forget( &me->caseTimer );
+	*outRecord = obj;
+	obj = NULL;
+	err = kNoErr;
 	
-	_GAITesterCompleteCurrentTest( me, true );
+exit:
+	FreeNullSafe( obj );
+	return( err );
 }
 
 //===========================================================================================================================
-//	_GAITesterAdvanceCurrentItem
+//	_MRResourceRecordFree
 //===========================================================================================================================
 
-static void	_GAITesterAdvanceCurrentItem( GAITesterRef me )
+static void	_MRResourceRecordFree( MRResourceRecord *inRecord )
 {
-	if( me->currentItem )
-	{
-		me->currentItem = me->currentItem->next;
-		_GAITesterInitializeCurrentTest( me );
-	}
+	ForgetMem( &inRecord->name );
+	ForgetMem( &inRecord->rdata );
+	free( inRecord );
 }
 
 //===========================================================================================================================
-//	_GAITesterAdvanceCurrentSet
+//	_MRResourceRecordFreeList
 //===========================================================================================================================
 
-static void	_GAITesterAdvanceCurrentSet( GAITesterRef me )
+static void	_MRResourceRecordFreeList( MRResourceRecord *inList )
 {
-	if( me->currentCase )
+	MRResourceRecord *		record;
+	
+	while( ( record = inList ) != NULL )
 	{
-		me->caseStartTime	= 0;
-		me->caseEndTime		= 0;
-		me->currentCase		= me->currentCase->next;
-		if( me->currentCase )
-		{
-			me->currentItem = me->currentCase->itemList;
-			_GAITesterInitializeCurrentTest( me );
-		}
+		inList = record->next;
+		_MRResourceRecordFree( record );
 	}
 }
 
 //===========================================================================================================================
-//	_GAITesterInitializeCurrentTest
+//	_MRNameOffsetItemCreate
+//===========================================================================================================================
+
+static OSStatus	_MRNameOffsetItemCreate( const uint8_t *inName, uint16_t inOffset, MRNameOffsetItem **outItem )
+{
+	OSStatus				err;
+	MRNameOffsetItem *		obj;
+	size_t					nameLen;
+	
+	require_action_quiet( inOffset <= kDNSCompressionOffsetMax, exit, err = kSizeErr );
+	
+	nameLen = DomainNameLength( inName );
+	obj = (MRNameOffsetItem *) calloc( 1, offsetof( MRNameOffsetItem, name ) + nameLen );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->offset = inOffset;
+	memcpy( obj->name, inName, nameLen );
+	
+	*outItem = obj;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	_MRNameOffsetItemFree
+//===========================================================================================================================
+
+static void	_MRNameOffsetItemFree( MRNameOffsetItem *inItem )
+{
+	free( inItem );
+}
+
+//===========================================================================================================================
+//	_MRNameOffsetItemFreeList
 //===========================================================================================================================
 
-static void	_GAITesterInitializeCurrentTest( GAITesterRef me )
+static void	_MRNameOffsetItemFreeList( MRNameOffsetItem *inList )
 {
-	GAITestItem * const		item = me->currentItem;
+	MRNameOffsetItem *		item;
 	
-	if( item )
+	while( ( item = inList ) != NULL )
 	{
-		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;
+		inList = item->next;
+		_MRNameOffsetItemFree( item );
 	}
 }
 
 //===========================================================================================================================
-//	_GAITesterGetAddrInfoCallback
+//	GAIPerfCmd
 //===========================================================================================================================
 
-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 )
+#define kGAIPerfGAITimeLimitMs		500	// Allow at most 500 ms for a DNSServiceGetAddrInfo() operation to complete.
+#define kGAIPerfStandardTTL			( 1 * kSecondsPerHour )
+
+typedef struct GAITesterPrivate *		GAITesterRef;
+typedef struct GAITestCase				GAITestCase;
+
+typedef struct
 {
-	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();
+	const char *		name;				// Domain name that was resolved.
+	uint64_t			connectionTimeUs;	// Time in microseconds that it took to create a DNS-SD connection.
+	uint64_t			firstTimeUs;		// Time in microseconds that it took to get the first address result.
+	uint64_t			timeUs;				// Time in microseconds that it took to get all expected address results.
+	OSStatus			error;
 	
-	require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
-	require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit );
+}	GAITestItemResult;
+
+typedef void ( *GAITesterStopHandler_f )( void *inContext, OSStatus inError );
+typedef void
+	( *GAITesterResultsHandler_f )(
+		const char *				inCaseTitle,
+		NanoTime64					inCaseStartTime,
+		NanoTime64					inCaseEndTime,
+		const GAITestItemResult *	inResultArray,
+		size_t						inResultCount,
+		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 struct
+{
+	GAITesterRef			tester;				// GAI tester object.
+	CFMutableArrayRef		testCaseResults;	// Array of test case results.
+	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.
+	char *					outputFilePath;		// File to write test results to. If NULL, then write to stdout.
+	OutputFormatType		outputFormat;		// Format of test results output.
+	Boolean					appendNewline;		// True if a newline character should be appended to JSON output.
+	Boolean					skipPathEval;		// True if DNSServiceGetAddrInfo() path evaluation is to be skipped.
+	Boolean					badUDPMode;			// True if the test DNS server is to run in Bad UDP mode.
+	Boolean					testFailed;			// True if at least one test case iteration failed.
 	
-	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;
-}
+}	GAIPerfContext;
 
-//===========================================================================================================================
-//	_GAITesterCompleteCurrentTest
-//===========================================================================================================================
+static void		GAIPerfContextFree( GAIPerfContext *inContext );
+static OSStatus	GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
+static OSStatus	GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
+static void		GAIPerfTesterStopHandler( void *inContext, OSStatus inError );
+static void
+	GAIPerfResultsHandler(
+		const char *				inCaseTitle,
+		NanoTime64					inCaseStartTime,
+		NanoTime64					inCaseEndTime,
+		const GAITestItemResult *	inResultArray,
+		size_t						inResultCount,
+		void *						inContext );
+static void		GAIPerfSignalHandler( void *inContext );
+
+CFTypeID		GAITesterGetTypeID( void );
+static OSStatus
+	GAITesterCreate(
+		dispatch_queue_t	inQueue,
+		int					inCallDelayMs,
+		int					inServerDelayMs,
+		int					inServerDefaultTTL,
+		Boolean				inSkipPathEvaluation,
+		Boolean				inBadUDPMode,
+		GAITesterRef *		outTester );
+static void		GAITesterStart( GAITesterRef inTester );
+static void		GAITesterStop( GAITesterRef inTester );
+static OSStatus	GAITesterAddTestCase( GAITesterRef inTester, GAITestCase *inCase );
+static void
+	GAITesterSetStopHandler(
+		GAITesterRef			inTester,
+		GAITesterStopHandler_f	inEventHandler,
+		void *					inEventContext );
+static void
+	GAITesterSetResultsHandler(
+		GAITesterRef				inTester,
+		GAITesterResultsHandler_f	inResultsHandler,
+		void *						inResultsContext );
 
+static OSStatus	GAITestCaseCreate( const char *inTitle, GAITestCase **outCase );
+static void		GAITestCaseFree( GAITestCase *inCase );
 static OSStatus
-	_GAITesterGetDNSMessageFromPacket(
-		const uint8_t *		inPacketPtr,
-		size_t				inPacketLen,
-		const uint8_t **	outMsgPtr,
-		size_t *			outMsgLen );
+	GAITestCaseAddItem(
+		GAITestCase *	inCase,
+		unsigned int	inAliasCount,
+		unsigned int	inAddressCount,
+		int				inTTL,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		unsigned int	inTimeLimitMs,
+		unsigned int	inItemCount );
+static OSStatus
+	GAITestCaseAddLocalHostItem(
+		GAITestCase *	inCase,
+		GAITestAddrType	inWantAddrs,
+		unsigned int	inTimeLimitMs,
+		unsigned int	inItemCount );
 
-static void	_GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut )
+static void	GAIPerfCmd( void )
 {
 	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 ];
+	GAIPerfContext *		context = NULL;
 	
-	DNSServiceForget( &me->opRef );
-	DNSServiceForget( &me->mainRef );
+	err = CheckRootUser();
+	require_noerr_quiet( err, exit );
 	
-	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;
-	}
+	err = CheckIntegerArgument( gGAIPerf_CallDelayMs, "call delay (ms)", 0, INT_MAX );
+	require_noerr_quiet( err, exit );
 	
-	item = me->currentItem;
-	err = DomainNameFromString( name, item->name, NULL );
-    require_noerr( err, exit );
+	err = CheckIntegerArgument( gGAIPerf_ServerDelayMs, "server delay (ms)", 0, INT_MAX );
+	require_noerr_quiet( 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;
-			}
-		}
-	}
+	err = CheckIntegerArgument( gGAIPerf_IterationCount, "iteration count", 1, INT_MAX );
+	require_noerr_quiet( err, exit );
 	
-	if( tsQA && tsQAAAA )	tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
-	else					tsQ = tsQA ? tsQA : tsQAAAA;
+	context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
 	
-	if( tsRA && tsRAAAA )	tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
-	else					tsR = tsQA ? tsQA : tsQAAAA;
+	context->testCaseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( context->testCaseResults, exit, err = kNoMemoryErr );
 	
-	if( tsQ && tsR )
-	{
-		idleTimeUs = TIMEVAL_USEC64_DIFF( *tsR, *tsQ );
-		if( idleTimeUs < 0 ) idleTimeUs = 0;
-	}
-	else
+	context->callDelayMs		= (unsigned int) gGAIPerf_CallDelayMs;
+	context->serverDelayMs		= (unsigned int) gGAIPerf_ServerDelayMs;
+	context->defaultIterCount	= (unsigned int) gGAIPerf_IterationCount;
+	context->appendNewline		= gGAIPerf_OutputAppendNewline	? true : false;
+	context->skipPathEval		= gGAIPerf_SkipPathEvalulation	? true : false;
+	context->badUDPMode			= gGAIPerf_BadUDPMode			? true : false;
+	
+	if( gGAIPerf_OutputFilePath )
 	{
-		idleTimeUs = 0;
+		context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
+		require_action( context->outputFilePath, exit, err = kNoMemoryErr );
 	}
 	
-	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 ];
+	context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
+		kOutputFormatStr_JSON,		kOutputFormatType_JSON,
+		kOutputFormatStr_XML,		kOutputFormatType_XML,
+		kOutputFormatStr_Binary,	kOutputFormatType_Binary,
+		NULL );
+	require_noerr_quiet( err, exit );
 	
-	nullLink = &inPacketPtr[ 0 ];
-	require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
-	addressFamily = ReadHost32( &nullLink[ 0 ] );
+	err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
+		kGAIPerfStandardTTL, context->skipPathEval, context->badUDPMode, &context->tester );
+	require_noerr( err, exit );
 	
-	ip = &nullLink[ kHeaderSizeNullLink ];
-	if( addressFamily == AF_INET )
+	check( gGAIPerf_TestSuite );
+	if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Basic ) == 0 )
 	{
-		require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
-		ipHeaderLen	= ( ip[ 0 ] & 0x0F ) * 4;
-		protocol	=   ip[ 9 ];
+		err = GAIPerfAddBasicTestCases( context );
+		require_noerr( err, exit );
 	}
-	else if( addressFamily == AF_INET6 )
+	else if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Advanced ) == 0 )
 	{
-		require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
-		ipHeaderLen	= kHeaderSizeIPv6;
-		protocol	= ip[ 6 ];
+		err = GAIPerfAddAdvancedTestCases( context );
+		require_noerr( err, exit );
 	}
 	else
 	{
-		err = kTypeErr;
+		FPrintF( stderr, "error: Invalid test suite name: %s.\n", gGAIPerf_TestSuite );
+		err = kParamErr;
 		goto exit;
 	}
-	require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
-	require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
 	
-	msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+	GAITesterSetStopHandler( context->tester, GAIPerfTesterStopHandler, context );
+	GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
 	
-	*outMsgPtr = msg;
-	*outMsgLen = (size_t)( end - msg );
-	err = kNoErr;
+	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:
-	return( err );
+	if( context ) GAIPerfContextFree( context );
+	exit( 1 );
 }
 
 //===========================================================================================================================
-//	GAITestCaseCreate
+//	GAIPerfContextFree
+//===========================================================================================================================
+
+static void	GAIPerfContextFree( GAIPerfContext *inContext )
+{
+	ForgetCF( &inContext->tester );
+	ForgetCF( &inContext->testCaseResults );
+	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 );
+
+#define kGAIPerfAdvancedTestSuite_MaxAliasCount		4
+#define kGAIPerfAdvancedTestSuite_MaxAddrCount		8
 
-static OSStatus	GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet )
+static OSStatus	GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
 {
 	OSStatus			err;
-	GAITestCase *		obj;
+	unsigned int		aliasCount, addressCount, i;
+	GAITestCase *		testCase = NULL;
+	char				title[ kTestCaseTitleBufferSize ];
 	
-	obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
-	require_action( obj, exit, err = kNoMemoryErr );
+	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 );
+			
+			err = GAITestCaseCreate( title, &testCase );
+			require_noerr( err, exit );
+			
+			for( i = 0; i < inContext->defaultIterCount; ++i )
+			{
+				err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+					kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 );
+				require_noerr( err, exit );
+			}
+			
+			err = GAITesterAddTestCase( inContext->tester, testCase );
+			require_noerr( err, exit );
+			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 );
+			
+			err = GAITestCaseCreate( title, &testCase );
+			require_noerr( err, exit );
+			
+			err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+				kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, inContext->defaultIterCount + 1 );
+			require_noerr( err, exit );
+			
+			err = GAITesterAddTestCase( inContext->tester, testCase );
+			require_noerr( err, exit );
+			testCase = NULL;
+		}
+		
+		aliasCount = ( aliasCount == 0 ) ? 1 : ( 2 * aliasCount );
+	}
 	
-	obj->title = strdup( inTitle );
-	require_action( obj->title, exit, err = kNoMemoryErr );
+	// Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
 	
-	obj->timeLimitMs = inTimeLimitMs;
+	_GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
 	
-	*outSet = obj;
-	obj = NULL;
-	err = kNoErr;
+	err = GAITestCaseCreate( title, &testCase );
+	require_noerr( err, exit );
+	
+	err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+		inContext->defaultIterCount );
+	require_noerr( err, exit );
+	
+	err = GAITesterAddTestCase( inContext->tester, testCase );
+	require_noerr( err, exit );
+	testCase = NULL;
 	
 exit:
-	if( obj ) GAITestCaseFree( obj );
+	if( testCase ) GAITestCaseFree( testCase );
 	return( err );
 }
 
 //===========================================================================================================================
-//	GAITestCaseFree
+//	_GAIPerfWriteTestCaseTitle
 //===========================================================================================================================
 
-static void	GAITestCaseFree( GAITestCase *inCase )
+#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 )
 {
-	GAITestItem *		item;
-	
-	while( ( item = inCase->itemList ) != NULL )
-	{
-		inCase->itemList = item->next;
-		GAITestItemFree( item );
-	}
-	ForgetMem( &inCase->title );
-	free( inCase );
+	SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
+		inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
+		inIterationCount, inIterationsAreUnique, ",unique" );
 }
 
 //===========================================================================================================================
-//	GAITestCaseAddItem
+//	_GAIPerfWriteLocalHostTestCaseTitle
 //===========================================================================================================================
 
-// 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.
+static void
+	_GAIPerfWriteLocalHostTestCaseTitle(
+		char			inBuffer[ kTestCaseTitleBufferSize ],
+		GAITestAddrType	inRequested,
+		unsigned int	inIterationCount )
+{
+	SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
+		GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
+}
 
-#define kUniqueStringCharSet		"abcdefghijklmnopqrstuvwxyz0123456789"
-#define kUniqueStringCharSetLen		sizeof_string( kUniqueStringCharSet )
-#define kUniqueStringLen			6
+//===========================================================================================================================
+//	GAIPerfAddBasicTestCases
+//===========================================================================================================================
 
-static OSStatus
-	GAITestCaseAddItem(
-		GAITestCase *	inCase,
-		unsigned int	inAliasCount,
-		unsigned int	inAddressCount,
-		int				inTTL,
-		GAITestAddrType	inHasAddrs,
-		GAITestAddrType	inWantAddrs,
-		unsigned int	inItemCount )
+#define kGAIPerfBasicTestSuite_AliasCount		2
+#define kGAIPerfBasicTestSuite_AddrCount		4
+
+static OSStatus	GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
 {
 	OSStatus			err;
-	GAITestItem *		item;
-	GAITestItem *		item2;
-	GAITestItem *		newItemList = NULL;
-	GAITestItem **		itemPtr;
-	char *				ptr;
-	char *				end;
+	GAITestCase *		testCase = NULL;
+	char				title[ kTestCaseTitleBufferSize ];
 	unsigned int		i;
-	char				name[ 64 ];
-	char				uniqueStr[ kUniqueStringLen + 1 ];
 	
-	require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+	// 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.
 	
-	// Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+	_GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+		kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+		inContext->defaultIterCount, true );
 	
-	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 );
+	err = GAITestCaseCreate( title, &testCase );
+	require_noerr( err, exit );
 	
-	ptr = &name[ 0 ];
-	end = &name[ countof( name ) ];
+	for( i = 0; i < inContext->defaultIterCount; ++i )
+	{
+		err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+			kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 );
+		require_noerr( err, exit );
+	}
 	
-	// Add Alias label.
+	err = GAITesterAddTestCase( inContext->tester, testCase );
+	require_noerr( err, exit );
+	testCase = NULL;
 	
-	if(      inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
-	else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+	// 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.
 	
-	// Add Count label.
+	_GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+		kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+		inContext->defaultIterCount, false );
 	
-	SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+	err = GAITestCaseCreate( title, &testCase );
+	require_noerr( err, exit );
 	
-	// Add TTL label.
+	err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+		kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+		inContext->defaultIterCount + 1 );
+	require_noerr( err, exit );
 	
-	if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+	err = GAITesterAddTestCase( inContext->tester, testCase );
+	require_noerr( err, exit );
+	testCase = NULL;
 	
-	// Add Tag label.
+	// Test Case #3:
+	// Each iteration resolves localhost to its IPv4 and IPv6 addresses.
 	
-	RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr );
-	SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr );
+	_GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
 	
-	// Add IPv4 or IPv6 label if necessary.
+	err = GAITestCaseCreate( title, &testCase );
+	require_noerr( err, exit );
 	
-	switch( inHasAddrs )
-	{
-		case kGAITestAddrType_IPv4:
-			SNPrintF_Add( &ptr, end, "ipv4." );
-			break;
-		
-		case kGAITestAddrType_IPv6:
-			SNPrintF_Add( &ptr, end, "ipv6." );
-			break;
-	}
+	err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+		inContext->defaultIterCount );
+	require_noerr( err, exit );
 	
-	// Add d.test. labels.
+	err = GAITesterAddTestCase( inContext->tester, testCase );
+	require_noerr( err, exit );
+	testCase = NULL;
 	
-	SNPrintF_Add( &ptr, end, "d.test." );
+exit:
+	if( testCase ) GAITestCaseFree( testCase );
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAIPerfTesterStopHandler
+//===========================================================================================================================
+
+#define kGAIPerfResultsKey_Info				CFSTR( "info" )
+#define kGAIPerfResultsKey_TestCases		CFSTR( "testCases" )
+#define kGAIPerfResultsKey_Success			CFSTR( "success" )
+
+#define kGAIPerfInfoKey_CallDelay			CFSTR( "callDelayMs" )
+#define kGAIPerfInfoKey_ServerDelay			CFSTR( "serverDelayMs" )
+#define kGAIPerfInfoKey_SkippedPathEval		CFSTR( "skippedPathEval" )
+#define kGAIPerfInfoKey_UsedBadUDPMode		CFSTR( "usedBadUPDMode" )
+
+static void	GAIPerfTesterStopHandler( void *inContext, OSStatus inError )
+{
+	OSStatus					err;
+	GAIPerfContext * const		context = (GAIPerfContext *) inContext;
+	CFPropertyListRef			plist;
+	int							exitCode;
 	
-	// Create item.
+	err = inError;
+	require_noerr_quiet( err, exit );
 	
-	err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item );
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO="			// info
+			"{"
+				"%kO=%lli"	// callDelayMs
+				"%kO=%lli"	// serverDelayMs
+				"%kO=%b"	// skippedPathEval
+				"%kO=%b"	// usedBadUPDMode
+			"}"
+			"%kO=%O"		// testCases
+			"%kO=%b"		// success
+		"}",
+		kGAIPerfResultsKey_Info,
+		kGAIPerfInfoKey_CallDelay,			(int64_t) context->callDelayMs,
+		kGAIPerfInfoKey_ServerDelay,		(int64_t) context->serverDelayMs,
+		kGAIPerfInfoKey_SkippedPathEval,	context->skipPathEval,
+		kGAIPerfInfoKey_UsedBadUDPMode,		context->badUDPMode,
+		kGAIPerfResultsKey_TestCases,		context->testCaseResults,
+		kGAIPerfResultsKey_Success,			!context->testFailed );
 	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 );
+	err = OutputPropertyList( plist, context->outputFormat, context->appendNewline, context->outputFilePath );
+	CFRelease( plist );
 	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 );
+	exitCode = err ? 1 : ( context->testFailed ? 2 : 0 );
+	GAIPerfContextFree( context );
+	exit( exitCode );
 }
 
 //===========================================================================================================================
-//	GAITestItemCreate
+//	GAIPerfResultsHandler
 //===========================================================================================================================
 
-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
-//===========================================================================================================================
+// Keys for test case dictionary
 
-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 );
-}
+#define kGAIPerfTestCaseKey_Title				CFSTR( "title" )
+#define kGAIPerfTestCaseKey_StartTime			CFSTR( "startTime" )
+#define kGAIPerfTestCaseKey_EndTime				CFSTR( "endTime" )
+#define kGAIPerfTestCaseKey_Results				CFSTR( "results" )
+#define kGAIPerfTestCaseKey_FirstStats			CFSTR( "firstStats" )
+#define kGAIPerfTestCaseKey_ConnectionStats		CFSTR( "connectionStats" )
+#define kGAIPerfTestCaseKey_Stats				CFSTR( "stats" )
 
-//===========================================================================================================================
-//	GAITestItemFree
-//===========================================================================================================================
+// Keys for test case results array entry dictionaries
 
-static void	GAITestItemFree( GAITestItem *inItem )
-{
-	ForgetMem( &inItem->name );
-	free( inItem );
-}
+#define kGAIPerfTestCaseResultKey_Name					CFSTR( "name" )
+#define kGAIPerfTestCaseResultKey_ConnectionTime		CFSTR( "connectionTimeUs" )
+#define kGAIPerfTestCaseResultKey_FirstTime				CFSTR( "firstTimeUs" )
+#define kGAIPerfTestCaseResultKey_Time					CFSTR( "timeUs" )
 
-//===========================================================================================================================
-//	SSDPDiscoverCmd
-//===========================================================================================================================
+// Keys for test case stats dictionaries
 
-#define kSSDPPort		1900
+#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
 {
-	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.
+	double		min;
+	double		max;
+	double		mean;
+	double		stdDev;
 	
-}	SSDPDiscoverContext;
+}	GAIPerfStats;
 
-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 );
+#define GAIPerfStatsInit( X ) \
+	do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
 
-static void	SSDPDiscoverCmd( void )
+static void
+	GAIPerfResultsHandler(
+		const char *				inCaseTitle,
+		NanoTime64					inCaseStartTime,
+		NanoTime64					inCaseEndTime,
+		const GAITestItemResult *	inResultArray,
+		size_t						inResultCount,
+		void *						inContext )
 {
-	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 );
+	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;
+	size_t							keyValueLen, i;
+	char							keyValue[ 16 ];	// Size must be at least strlen( "name=dynamic" ) + 1 bytes.
+	char							startTimeStr[ 32 ];
+	char							endTimeStr[ 32 ];
+	const GAITestItemResult *		result;
 	
-	// Check command parameters.
+	// 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.
 	
-	if( gSSDPDiscover_ReceiveSecs < -1 )
+	namesAreDynamic	= false;
+	namesAreUnique	= false;
+	ptr = inCaseTitle;
+	while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
 	{
-		FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
-		err = kParamErr;
-		goto exit;
+		if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
+		{
+			namesAreDynamic = true;
+		}
+		else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+		{
+			namesAreUnique = true;
+		}
+		if( namesAreDynamic && namesAreUnique ) break;
 	}
 	
-	// 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 );
+	startIndex = ( ( inResultCount > 0 ) && namesAreDynamic && !namesAreUnique ) ? 1 : 0;
+	results = CFArrayCreateMutable( NULL, (CFIndex)( inResultCount - startIndex ), &kCFTypeArrayCallBacks );
+	require_action( results, exit, err = kNoMemoryErr );
 	
-	// Set up IPv4 socket.
+	GAIPerfStatsInit( &stats );
+	GAIPerfStatsInit( &firstStats );
+	GAIPerfStatsInit( &connStats );
 	
-	if( context->useIPv4 )
+	sum			= 0.0;
+	firstSum	= 0.0;
+	connSum		= 0.0;
+	count		= 0;
+	for( i = startIndex; i < inResultCount; ++i )
 	{
-		int port;
-		err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
-		require_noerr( err, exit );
+		double		value;
 		
-		err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
-		require_noerr( err, exit );
+		result = &inResultArray[ i ];
 		
-		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 = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
+			"{"
+				"%kO=%s"	// name
+				"%kO=%lli"	// connectionTimeUs
+				"%kO=%lli"	// firstTimeUs
+				"%kO=%lli"	// timeUs
+				"%kO=%lli"	// error
+			"}",
+			kGAIPerfTestCaseResultKey_Name,				result->name,
+			kGAIPerfTestCaseResultKey_ConnectionTime,	(int64_t) result->connectionTimeUs,
+			kGAIPerfTestCaseResultKey_FirstTime,		(int64_t) result->firstTimeUs,
+			kGAIPerfTestCaseResultKey_Time,				(int64_t) result->timeUs,
+			CFSTR( "error" ),							(int64_t) result->error );
+		require_noerr( err, exit );
+		
+		if( !result->error )
+		{
+			value = (double) result->timeUs;
+			if( value < stats.min ) stats.min = value;
+			if( value > stats.max ) stats.max = value;
+			sum += value;
+			
+			value = (double) result->firstTimeUs;
+			if( value < firstStats.min ) firstStats.min = value;
+			if( value > firstStats.max ) firstStats.max = value;
+			firstSum += value;
+			
+			value = (double) result->connectionTimeUs;
+			if( value < connStats.min ) connStats.min = value;
+			if( value > connStats.max ) connStats.max = value;
+			connSum += value;
+			
+			++count;
+		}
+		else
+		{
+			context->testFailed = true;
+		}
+	}
+	
+	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 < inResultCount; ++i )
+		{
+			double		diff;
+			
+			result = &inResultArray[ i ];
+			if( result->error ) continue;
+			
+			diff		 = stats.mean - (double) result->timeUs;
+			sum			+= ( diff * diff );
+			
+			diff		 = firstStats.mean - (double) result->firstTimeUs;
+			firstSum	+= ( diff * diff );
+			
+			diff		 = connStats.mean - (double) result->connectionTimeUs;
+			connSum		+= ( diff * diff );
+		}
+		stats.stdDev		= sqrt( sum      / count );
+		firstStats.stdDev	= sqrt( firstSum / count );
+		connStats.stdDev	= sqrt( connSum  / count );
+	}
+	
+	err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->testCaseResults,
+		"{"
+			"%kO=%s"
+			"%kO=%s"
+			"%kO=%s"
+			"%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"
+			"}"
+		"}",
+		kGAIPerfTestCaseKey_Title,			inCaseTitle,
+		kGAIPerfTestCaseKey_StartTime,		_NanoTime64ToDateString( inCaseStartTime, startTimeStr, sizeof( startTimeStr ) ),
+		kGAIPerfTestCaseKey_EndTime,		_NanoTime64ToDateString( inCaseEndTime, endTimeStr, sizeof( endTimeStr ) ),
+		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 );
+	require_noerr( err, exit );
+	
+exit:
+	CFReleaseNullSafe( results );
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	GAIPerfSignalHandler
+//===========================================================================================================================
+
+static void	GAIPerfSignalHandler( void *inContext )
+{
+	GAIPerfContext * const		context = (GAIPerfContext *) inContext;
+	
+	if( !context->tester ) exit( 1 );
+	GAITesterStop( context->tester );
+	context->tester = NULL;
+}
+
+//===========================================================================================================================
+//	GAITesterCreate
+//===========================================================================================================================
+
+// 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 kGAITesterTagStringLen		6
+
+typedef struct GAITestItem		GAITestItem;
+struct GAITestItem
+{
+	GAITestItem *		next;				// Next test item in list.
+	char *				name;				// Domain name to resolve.
+	uint64_t			connectionTimeUs;	// Time in microseconds that it took to create a DNS-SD connection.
+	uint64_t			firstTimeUs;		// Time in microseconds that it took to get the first address result.
+	uint64_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.
+	OSStatus			error;				// Current status/error.
+	unsigned int		timeLimitMs;		// Time limit in milliseconds for the test item's completion.
+	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.
+};
+
+struct GAITesterPrivate
+{
+	CFRuntimeBase					base;				// CF object base.
+	dispatch_queue_t				queue;				// Serial work queue.
+	DNSServiceRef					connection;			// Reference to the shared DNS-SD connection.
+	DNSServiceRef					getAddrInfo;		// 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.
+	NanoTime64						caseStartTime;		// Start time of current test case in Unix time as nanoseconds.
+	NanoTime64						caseEndTime;		// End time of current test case in Unix time as nanoseconds.
+	int								callDelayMs;		// Amount of time to wait before calling DNSServiceGetAddrInfo().
+	Boolean							skipPathEval;		// True if DNSServiceGetAddrInfo() path evaluation is to be skipped.
+	Boolean							stopped;			// True if the tester has been stopped.
+	Boolean							badUDPMode;			// True if the test DNS server is to run in Bad UDP mode.
+	dispatch_source_t				timer;				// Timer for enforcing a test item's time limit.
+	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.
+	GAITesterStopHandler_f			stopHandler;		// User's stop handler.
+	void *							stopContext;		// 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		_GAITesterStartNextTest( GAITesterRef inTester );
+static OSStatus	_GAITesterCreatePacketCapture( pcap_t **outPCap );
+static void		_GAITesterFirstGAITimeout( void *inContext );
+static void		_GAITesterTimeout( void *inContext );
+static void DNSSD_API
+	_GAITesterFirstGAICallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext );
+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, OSStatus inError );
+
+#define ForgetPacketCapture( X )		ForgetCustom( X, pcap_close )
+
+static OSStatus
+	GAITestItemCreate(
+		const char *	inName,
+		unsigned int	inAddressCount,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		unsigned int	inTimeLimitMs,
+		GAITestItem **	outItem );
+static OSStatus	GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem );
+static void		GAITestItemFree( GAITestItem *inItem );
+
+static OSStatus
+	GAITesterCreate(
+		dispatch_queue_t	inQueue,
+		int					inCallDelayMs,
+		int					inServerDelayMs,
+		int					inServerDefaultTTL,
+		Boolean				inSkipPathEvaluation,
+		Boolean				inBadUDPMode,
+		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;
+	obj->skipPathEval		= inSkipPathEvaluation;
+	obj->badUDPMode			= inBadUDPMode;
+	
+	*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->getAddrInfo );
+	check( !me->connection );
+	check( !me->timer );
+	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, OSStatus inError );
+
+static void	GAITesterStart( GAITesterRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _GAITesterStart );
+}
+
+#define kGAITesterFirstGAITimeoutSecs		4
+
+static void	_GAITesterStart( void *inContext )
+{
+	OSStatus				err;
+	GAITesterRef const		me = (GAITesterRef) inContext;
+	DNSServiceFlags			flags;
+	char					name[ 64 ];
+	char					tag[ kGAITesterTagStringLen + 1 ];
+	
+	err = SpawnCommand( &me->serverPID, "dnssdutil server --loopback --follow %lld%?s%?d%?s%?d%?s",
+		(int64_t) getpid(),
+		me->serverDefaultTTL >= 0,	" --defaultTTL ",
+		me->serverDefaultTTL >= 0,	me->serverDefaultTTL,
+		me->serverDelayMs    >= 0,	" --responseDelay ",
+		me->serverDelayMs    >= 0,	me->serverDelayMs,
+		me->badUDPMode,				" --badUDPMode" );
+	require_noerr_quiet( err, exit );
+	
+	SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test",
+		_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+	
+	flags = 0;
+	if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone;
+	
+	err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, name,
+		_GAITesterFirstGAICallback, me );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->getAddrInfo, me->queue );
+	require_noerr( err, exit );
+	
+	err = DispatchTimerOneShotCreate( dispatch_time_seconds( kGAITesterFirstGAITimeoutSecs ),
+		UINT64_C_safe( kGAITesterFirstGAITimeoutSecs ) * kNanosecondsPerSecond / 10, me->queue,
+		_GAITesterFirstGAITimeout, me, &me->timer );
+	require_noerr( err, exit );
+	dispatch_resume( me->timer );
+	
+exit:
+	if( err ) _GAITesterStop( me, err );
+}
+
+//===========================================================================================================================
+//	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, kCanceledErr );
+	CFRelease( me );
+}
+
+static void	_GAITesterStop( GAITesterRef me, OSStatus inError )
+{
+	OSStatus		err;
+	
+	ForgetPacketCapture( &me->pcap );
+	dispatch_source_forget( &me->timer );
+	DNSServiceForget( &me->getAddrInfo );
+	DNSServiceForget( &me->connection );
+	if( me->serverPID != -1 )
+	{
+		err = kill( me->serverPID, SIGTERM );
+		err = map_global_noerr_errno( err );
+		check_noerr( err );
+		me->serverPID = -1;
+	}
+	
+	if( !me->stopped )
+	{
+		me->stopped = true;
+		if( me->stopHandler ) me->stopHandler( me->stopContext, inError );
+		CFRelease( me );
+	}
+}
+
+//===========================================================================================================================
+//	GAITesterAddTestCase
+//===========================================================================================================================
+
+static OSStatus	GAITesterAddTestCase( GAITesterRef me, GAITestCase *inCase )
+{
+	OSStatus			err;
+	GAITestCase **		ptr;
+	
+	require_action_quiet( inCase->itemList, exit, err = kCountErr );
+	
+	for( ptr = &me->caseList; *ptr; ptr = &( *ptr )->next ) {}
+	*ptr = inCase;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITesterSetStopHandler
+//===========================================================================================================================
+
+static void	GAITesterSetStopHandler( GAITesterRef me, GAITesterStopHandler_f inStopHandler, void *inStopContext )
+{
+	me->stopHandler = inStopHandler;
+	me->stopContext = inStopContext;
+}
+
+//===========================================================================================================================
+//	GAITesterSetResultsHandler
+//===========================================================================================================================
+
+static void	GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+{
+	me->resultsHandler = inResultsHandler;
+	me->resultsContext = inResultsContext;
+}
+
+//===========================================================================================================================
+//	_GAITesterStartNextTest
+//===========================================================================================================================
+
+static void	_GAITesterStartNextTest( GAITesterRef me )
+{
+	OSStatus				err;
+	GAITestItem *			item;
+	DNSServiceFlags			flags;
+	DNSServiceProtocol		protocols;
+	int						done = false;
+	
+	if( me->currentItem ) me->currentItem = me->currentItem->next;
+	
+	if( !me->currentItem )
+	{
+		if( me->currentCase )
+		{
+			// No more test items means that the current test case has completed.
+			
+			me->caseEndTime = NanoTimeGetCurrent();
+			
+			if( me->resultsHandler )
+			{
+				size_t					resultCount, i;
+				GAITestItemResult *		resultArray;
+				
+				resultCount	= 0;
+				for( item = me->currentCase->itemList; item; item = item->next ) ++resultCount;
+				check( resultCount > 0 );
+				
+				resultArray = (GAITestItemResult *) calloc( resultCount, sizeof( *resultArray ) );
+				require_action( resultArray, exit, err = kNoMemoryErr );
+				
+				item = me->currentCase->itemList;
+				for( i = 0; i < resultCount; ++i )
+				{
+					resultArray[ i ].name				= item->name;
+					resultArray[ i ].connectionTimeUs	= item->connectionTimeUs;
+					resultArray[ i ].firstTimeUs		= item->firstTimeUs;
+					resultArray[ i ].timeUs				= item->timeUs;
+					resultArray[ i ].error				= item->error;
+					item = item->next;
+				}
+				me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, resultArray, resultCount,
+					me->resultsContext );
+				ForgetMem( &resultArray );
+			}
+			
+			me->currentCase = me->currentCase->next;
+			if( !me->currentCase )
+			{
+				done = true;
+				err = kNoErr;
+				goto exit;
+			}
+		}
+		else
+		{
+			me->currentCase = me->caseList;
+		}
+		require_action_quiet( me->currentCase->itemList, exit, err = kInternalErr );
+		me->currentItem = me->currentCase->itemList;
+	}
+	
+	item = me->currentItem;
+	check( ( item->addressCount >= 1 ) && ( item->addressCount <= 64 ) );
+	
+	if(      !item->wantV4 )			me->bitmapV4 = 0;
+	else if( !item->hasV4 )				me->bitmapV4 = 1;
+	else if(  item->addressCount < 64 )	me->bitmapV4 = ( UINT64_C( 1 ) << item->addressCount ) - 1;
+	else								me->bitmapV4 =  ~UINT64_C( 0 );
+	
+	if(      !item->wantV6 )			me->bitmapV6 = 0;
+	else if( !item->hasV6 )				me->bitmapV6 = 1;
+	else if(  item->addressCount < 64 )	me->bitmapV6 = ( UINT64_C( 1 ) << item->addressCount ) - 1;
+	else								me->bitmapV6 =  ~UINT64_C( 0 );
+	check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
+	me->gotFirstResult = false;
+	
+	// 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 );
+		sleep( 1 );
+		
+		me->caseStartTime	= NanoTimeGetCurrent();
+		me->caseEndTime		= kNanoTime_Invalid;
+	}
+	
+	// Start a packet capture.
+	
+	check( !me->pcap );
+	err = _GAITesterCreatePacketCapture( &me->pcap );
+	require_noerr( err, exit );
+	
+	// Start timer for test item's time limit.
+	
+	check( !me->timer );
+	if( item->timeLimitMs > 0 )
+	{
+		unsigned int		timeLimitMs;
+		
+		timeLimitMs = item->timeLimitMs;
+		if( me->callDelayMs   > 0 ) timeLimitMs += (unsigned int) me->callDelayMs;
+		if( me->serverDelayMs > 0 ) timeLimitMs += (unsigned int) me->serverDelayMs;
+		
+		err = DispatchTimerCreate( dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER,
+			( (uint64_t) timeLimitMs ) * kNanosecondsPerMillisecond / 10,
+			me->queue, _GAITesterTimeout, NULL, me, &me->timer );
+		require_noerr( err, exit );
+		dispatch_resume( me->timer );
+	}
+	
+	// Call DNSServiceGetAddrInfo().
+	
+	if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
+	
+	flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates;
+	if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone;
+	
+	protocols = 0;
+	if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
+	if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
+	
+	me->startTicks = UpTicks();
+	
+	check( !me->connection );
+	err = DNSServiceCreateConnection( &me->connection );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+	require_noerr( err, exit );
+	
+	me->connTicks = UpTicks();
+	
+	check( !me->getAddrInfo );
+	me->getAddrInfo = me->connection;
+	err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, protocols, item->name,
+		_GAITesterGetAddrInfoCallback, me );
+	require_noerr( err, exit );
+	
+exit:
+	if( err || done ) _GAITesterStop( me, err );
+}
+
+//===========================================================================================================================
+//	_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, errBuf );
+	
+	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 );
+}
+
+//===========================================================================================================================
+//	_GAITesterFirstGAITimeout
+//===========================================================================================================================
+
+static void	_GAITesterFirstGAITimeout( void *inContext )
+{
+	GAITesterRef const		me = (GAITesterRef) inContext;
+	
+	_GAITesterStop( me, kNoResourcesErr );
+}
+
+//===========================================================================================================================
+//	_GAITesterTimeout
+//===========================================================================================================================
+
+static void	_GAITesterTimeout( void *inContext )
+{
+	GAITesterRef const		me = (GAITesterRef) inContext;
+	
+	_GAITesterCompleteCurrentTest( me, kTimeoutErr );
+}
+
+//===========================================================================================================================
+//	_GAITesterFirstGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_GAITesterFirstGAICallback(
+		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;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inHostname );
+	Unused( inSockAddr );
+	Unused( inTTL );
+	
+	if( ( inFlags & kDNSServiceFlagsAdd ) && !inError )
+	{
+		dispatch_source_forget( &me->timer );
+		DNSServiceForget( &me->getAddrInfo );
+		
+		_GAITesterStartNextTest( me );
+	}
+}
+
+//===========================================================================================================================
+//	_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 )
+{
+	OSStatus						err;
+	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;
+	int								hasAddr;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inHostname );
+	Unused( inTTL );
+	
+	nowTicks = UpTicks();
+	
+	require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+	
+	// Check if we were expecting an IP address result of this type.
+	
+	if( sip->sa.sa_family == AF_INET )
+	{
+		bitmapPtr	= &me->bitmapV4;
+		hasAddr		= item->hasV4;
+	}
+	else if( sip->sa.sa_family == AF_INET6 )
+	{
+		bitmapPtr	= &me->bitmapV6;
+		hasAddr		= item->hasV6;
+	}
+	else
+	{
+		err = kTypeErr;
+		goto exit;
+	}
+	
+	bitmask = 0;
+	if( hasAddr )
+	{
+		uint32_t		addrOffset;
+		
+		require_noerr_action_quiet( inError, exit, err = inError );
+		
+		if( sip->sa.sa_family == AF_INET )
+		{
+			const uint32_t		addrV4 = ntohl( sip->v4.sin_addr.s_addr );
+			
+			if( strcasecmp( item->name, "localhost." ) == 0 )
+			{
+				if( addrV4 == INADDR_LOOPBACK ) bitmask = 1;
+			}
+			else
+			{
+				addrOffset = addrV4 - kDNSServerBaseAddrV4;
+				if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+				{
+					bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+				}
+			}
+		}
+		else
+		{
+			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;
+			}
+			else if( memcmp( addrV6, kDNSServerBaseAddrV6, 15 ) == 0 )
+			{
+				addrOffset = addrV6[ 15 ];
+				if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+				{
+					bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+				}
+			}
+		}
+	}
+	else
+	{
+		require_action_quiet( inError == kDNSServiceErr_NoSuchRecord, exit, err = inError ? inError : kUnexpectedErr );
+		bitmask = 1;
+	}
+	require_action_quiet( bitmask != 0, exit, err = kValueErr );
+	require_action_quiet( *bitmapPtr & bitmask, exit, err = kDuplicateErr );
+	
+	*bitmapPtr &= ~bitmask;
+	if( !me->gotFirstResult )
+	{
+		me->firstTicks		= nowTicks;
+		me->gotFirstResult	= true;
+	}
+	err = kNoErr;
+	
+exit:
+	if( err || ( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) )
+	{
+		me->endTicks = nowTicks;
+		_GAITesterCompleteCurrentTest( me, err );
+	}
+}
+
+//===========================================================================================================================
+//	_GAITesterCompleteCurrentTest
+//===========================================================================================================================
+
+static OSStatus
+	_GAITesterGetDNSMessageFromPacket(
+		const uint8_t *		inPacketPtr,
+		size_t				inPacketLen,
+		const uint8_t **	outMsgPtr,
+		size_t *			outMsgLen );
+
+static void	_GAITesterCompleteCurrentTest( GAITesterRef me, OSStatus inError )
+{
+	OSStatus				err;
+	GAITestItem * const		item	= me->currentItem;
+	struct timeval			timeStamps[ 4 ];
+	struct timeval *		tsPtr;
+	struct timeval *		tsQA	= NULL;
+	struct timeval *		tsQAAAA	= NULL;
+	struct timeval *		tsRA	= NULL;
+	struct timeval *		tsRAAAA	= NULL;
+	struct timeval *		t1;
+	struct timeval *		t2;
+	int64_t					idleTimeUs;
+	uint8_t					name[ kDomainNameLengthMax ];
+	
+	dispatch_source_forget( &me->timer );
+	DNSServiceForget( &me->getAddrInfo );
+	DNSServiceForget( &me->connection );
+	
+	item->error = inError;
+	if( item->error )
+	{
+		err = kNoErr;
+		goto exit;
+	}
+	
+	err = DomainNameFromString( name, item->name, NULL );
+	require_noerr( err, exit );
+	
+	tsPtr = &timeStamps[ 0 ];
+	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;
+		uint16_t					qtype, qclass;
+		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( DNSMessageExtractQuestion( msgPtr, msgLen, ptr, qname, &qtype, &qclass, NULL ) != kNoErr ) continue;
+		if( qclass != kDNSServiceClass_IN ) continue;
+		if( !DomainNameEqual( qname, name ) ) continue;
+		
+		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;
+			}
+		}
+	}
+	
+	// t1 is the time when the last query was sent.
+	
+	if( tsQA && tsQAAAA )	t1 = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
+	else					t1 = tsQA ? tsQA : tsQAAAA;
+	
+	// t2 is when the first response was received.
+	
+	if( tsRA && tsRAAAA )	t2 = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
+	else					t2 = tsRA ? tsRA : tsRAAAA;
+	
+	if( t1 && t2 )
+	{
+		idleTimeUs = TIMEVAL_USEC64_DIFF( *t2, *t1 );
+		if( idleTimeUs < 0 ) idleTimeUs = 0;
+	}
+	else
+	{
+		idleTimeUs = 0;
+	}
+	
+	item->connectionTimeUs	= UpTicksToMicroseconds( me->connTicks  - me->startTicks );
+	item->firstTimeUs		= UpTicksToMicroseconds( me->firstTicks - me->connTicks  ) - (uint64_t) idleTimeUs;
+	item->timeUs			= UpTicksToMicroseconds( me->endTicks   - me->connTicks  ) - (uint64_t) idleTimeUs;
+	
+exit:
+	ForgetPacketCapture( &me->pcap );
+	if( err )	_GAITesterStop( me, err );
+	else		_GAITesterStartNextTest( 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, GAITestCase **outCase )
+{
+	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 );
+	
+	*outCase = 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
+//===========================================================================================================================
+
+static OSStatus
+	GAITestCaseAddItem(
+		GAITestCase *	inCase,
+		unsigned int	inAliasCount,
+		unsigned int	inAddressCount,
+		int				inTTL,
+		GAITestAddrType	inHasAddrs,
+		GAITestAddrType	inWantAddrs,
+		unsigned int	inTimeLimitMs,
+		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				tag[ kGAITesterTagStringLen + 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.
+	
+	SNPrintF_Add( &ptr, end, "tag-%s.",
+		_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+	
+	// Add IPv4 or IPv6 label if necessary.
+	
+	if(      inHasAddrs == kGAITestAddrType_IPv4 ) SNPrintF_Add( &ptr, end, "ipv4." );
+	else if( inHasAddrs == kGAITestAddrType_IPv6 ) SNPrintF_Add( &ptr, end, "ipv6." );
+	
+	// Finally, add the d.test. labels.
+	
+	SNPrintF_Add( &ptr, end, "d.test." );
+	
+	// Create item.
+	
+	err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, inTimeLimitMs, &item );
+	require_noerr( err, exit );
+	
+	newItemList	= item;
+	itemPtr		= &item->next;
+	
+	// Create repeat items.
+	
+	for( i = 1; i < inItemCount; ++i )
+	{
+		err = GAITestItemDup( 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	inTimeLimitMs,
+		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, inTimeLimitMs, &item );
+	require_noerr( err, exit );
+	
+	newItemList	= item;
+	itemPtr		= &item->next;
+	
+	// Create repeat items.
+	
+	for( i = 1; i < inItemCount; ++i )
+	{
+		err = GAITestItemDup( 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,
+		unsigned int	inTimeLimitMs,
+		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;
+	obj->error			= kInProgressErr;
+	obj->timeLimitMs	= inTimeLimitMs;
+	
+	*outItem = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) GAITestItemFree( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	GAITestItemDup
+//===========================================================================================================================
+
+static OSStatus	GAITestItemDup( 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 );
+}
+
+//===========================================================================================================================
+//	MDNSDiscoveryTestCmd
+//===========================================================================================================================
+
+#define kMDNSDiscoveryTestFirstQueryTimeoutSecs		4
+
+typedef struct
+{
+	DNSServiceRef			query;					// Reference to DNSServiceQueryRecord for replier's "about" TXT record.
+	dispatch_source_t		queryTimer;				// Used to time out the "about" TXT record query.
+	NanoTime64				startTime;				// When the test started.
+	NanoTime64				endTime;				// When the test ended.
+	pid_t					replierPID;				// PID of mDNS replier.
+	uint32_t				ifIndex;				// Index of interface to run the replier on.
+	unsigned int			instanceCount;			// Desired number of service instances.
+	unsigned int			txtSize;				// Desired size of each service instance's TXT record data.
+	unsigned int			recordCountA;			// Desired number of A records per replier hostname.
+	unsigned int			recordCountAAAA;		// Desired number of AAAA records per replier hostname.
+	unsigned int			maxDropCount;			// Replier's --maxDropCount option argument.
+	double					ucastDropRate;			// Replier's probability of dropping a unicast response.
+	double					mcastDropRate;			// Replier's probability of dropping a multicast query or response.
+	Boolean					noAdditionals;			// True if the replier is to not include additional records in responses.
+	Boolean					useIPv4;				// True if the replier is to use IPv4.
+	Boolean					useIPv6;				// True if the replier is to use IPv6.
+	Boolean					flushedCache;			// True if mDNSResponder's record cache was flushed before testing.
+	char *					replierCommand;			// Command used to run the replier.
+	char *					serviceType;			// Type of services to browse for.
+	ServiceBrowserRef		browser;				// Service browser.
+	unsigned int			browseTimeSecs;			// Amount of time to spend browsing in seconds.
+	const char *			outputFilePath;			// File to write test results to. If NULL, then write to stdout.
+	OutputFormatType		outputFormat;			// Format of test results output.
+	Boolean					outputAppendNewline;	// True if a newline character should be appended to JSON output.
+	char					hostname[ 32 + 1 ];		// Base hostname that the replier is to use for instance and host names.
+	char					tag[ 4 + 1 ];			// Tag that the replier is to use in its service types.
+	
+}	MDNSDiscoveryTestContext;
+
+static OSStatus	GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex );
+static void		_MDNSDiscoveryTestFirstQueryTimeout( void *inContext );
+static void DNSSD_API
+	_MDNSDiscoveryTestAboutQueryCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext );
+static void
+	_MDNSDiscoveryTestServiceBrowserCallback(
+		ServiceBrowserResults *	inResults,
+		OSStatus				inError,
+		void *					inContext );
+static Boolean	_MDNSDiscoveryTestTXTRecordIsValid( const uint8_t *inRecordName, const uint8_t *inTXTPtr, size_t inTXTLen );
+
+static void	MDNSDiscoveryTestCmd( void )
+{
+	OSStatus						err;
+	MDNSDiscoveryTestContext *		context;
+	char							queryName[ sizeof_field( MDNSDiscoveryTestContext, hostname ) + 15 ];
+	
+	context = (MDNSDiscoveryTestContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	err = CheckIntegerArgument( gMDNSDiscoveryTest_InstanceCount, "instance count", 1, UINT16_MAX );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckIntegerArgument( gMDNSDiscoveryTest_TXTSize, "TXT size", 1, UINT16_MAX );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckIntegerArgument( gMDNSDiscoveryTest_BrowseTimeSecs, "browse time (seconds)", 1, INT_MAX );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckIntegerArgument( gMDNSDiscoveryTest_RecordCountA, "A record count", 0, 64 );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckIntegerArgument( gMDNSDiscoveryTest_RecordCountAAAA, "AAAA record count", 0, 64 );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckDoubleArgument( gMDNSDiscoveryTest_UnicastDropRate, "unicast drop rate", 0.0, 1.0 );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckDoubleArgument( gMDNSDiscoveryTest_MulticastDropRate, "multicast drop rate", 0.0, 1.0 );
+	require_noerr_quiet( err, exit );
+	
+	err = CheckIntegerArgument( gMDNSDiscoveryTest_MaxDropCount, "drop count", 0, 255 );
+	require_noerr_quiet( err, exit );
+	
+	context->replierPID				= -1;
+	context->instanceCount			= (unsigned int) gMDNSDiscoveryTest_InstanceCount;
+	context->txtSize				= (unsigned int) gMDNSDiscoveryTest_TXTSize;
+	context->browseTimeSecs			= (unsigned int) gMDNSDiscoveryTest_BrowseTimeSecs;
+	context->recordCountA			= (unsigned int) gMDNSDiscoveryTest_RecordCountA;
+	context->recordCountAAAA		= (unsigned int) gMDNSDiscoveryTest_RecordCountAAAA;
+	context->ucastDropRate			= gMDNSDiscoveryTest_UnicastDropRate;
+	context->mcastDropRate			= gMDNSDiscoveryTest_MulticastDropRate;
+	context->maxDropCount			= (unsigned int) gMDNSDiscoveryTest_MaxDropCount;
+	context->outputFilePath			= gMDNSDiscoveryTest_OutputFilePath;
+	context->outputAppendNewline	= gMDNSDiscoveryTest_OutputAppendNewline ? true : false;
+	context->noAdditionals			= gMDNSDiscoveryTest_NoAdditionals       ? true : false;
+	context->useIPv4				= ( gMDNSDiscoveryTest_UseIPv4 || !gMDNSDiscoveryTest_UseIPv6 ) ? true : false;
+	context->useIPv6				= ( gMDNSDiscoveryTest_UseIPv6 || !gMDNSDiscoveryTest_UseIPv4 ) ? true : false;
+	
+	if( gMDNSDiscoveryTest_Interface )
+	{
+		err = InterfaceIndexFromArgString( gMDNSDiscoveryTest_Interface, &context->ifIndex );
+		require_noerr_quiet( err, exit );
+	}
+	else
+	{
+		err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+		require_noerr_quiet( err, exit );
+	}
+	
+	context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gMDNSDiscoveryTest_OutputFormat, &err,
+		kOutputFormatStr_JSON,		kOutputFormatType_JSON,
+		kOutputFormatStr_XML,		kOutputFormatType_XML,
+		kOutputFormatStr_Binary,	kOutputFormatType_Binary,
+		NULL );
+	require_noerr_quiet( err, exit );
+	
+	if( gMDNSDiscoveryTest_FlushCache )
+	{
+		err = CheckRootUser();
+		require_noerr_quiet( err, exit );
+		
+		err = systemf( NULL, "killall -HUP mDNSResponder" );
+		require_noerr( err, exit );
+		sleep( 1 );
+		context->flushedCache = true;
+	}
+	
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( context->hostname ) - 1,
+		context->hostname );
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( context->tag ) - 1, context->tag );
+	
+	ASPrintF( &context->serviceType, "_t-%s-%u-%u._tcp", context->tag, context->txtSize, context->instanceCount );
+	require_action( context->serviceType, exit, err = kUnknownErr );
+	
+	err = ASPrintF( &context->replierCommand,
+		"dnssdutil mdnsreplier --follow %lld --interface %u --hostname %s --tag %s --maxInstanceCount %u "
+		"--countA %u --countAAAA %u --udrop %.1f --mdrop %.1f --maxDropCount %u %?s%?s%?s",
+		(int64_t) getpid(),
+		context->ifIndex,
+		context->hostname,
+		context->tag,
+		context->instanceCount,
+		context->recordCountA,
+		context->recordCountAAAA,
+		context->ucastDropRate,
+		context->mcastDropRate,
+		context->maxDropCount,
+		context->noAdditionals,	" --noAdditionals",
+		context->useIPv4,		" --ipv4",
+		context->useIPv6,		" --ipv6" );
+	require_action_quiet( context->replierCommand, exit, err = kUnknownErr );
+	
+	err = SpawnCommand( &context->replierPID, "%s", context->replierCommand );
+	require_noerr_quiet( err, exit );
+	
+	// Query for the replier's about TXT record. A response means it's fully up and running.
+	
+	SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->hostname );
+	err = DNSServiceQueryRecord( &context->query, kDNSServiceFlagsForceMulticast, context->ifIndex, queryName,
+		kDNSServiceType_TXT, kDNSServiceClass_IN, _MDNSDiscoveryTestAboutQueryCallback, context );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( context->query, dispatch_get_main_queue() );
+	require_noerr( err, exit );
+	
+	err = DispatchTimerCreate( dispatch_time_seconds( kMDNSDiscoveryTestFirstQueryTimeoutSecs ),
+		DISPATCH_TIME_FOREVER, UINT64_C_safe( kMDNSDiscoveryTestFirstQueryTimeoutSecs ) * kNanosecondsPerSecond / 10, NULL,
+		_MDNSDiscoveryTestFirstQueryTimeout, NULL, context, &context->queryTimer );
+	require_noerr( err, exit );
+	dispatch_resume( context->queryTimer );
+	
+	context->startTime = NanoTimeGetCurrent();
+	dispatch_main();
+	
+exit:
+	exit( 1 );
+}
+
+//===========================================================================================================================
+//	_MDNSDiscoveryTestFirstQueryTimeout
+//===========================================================================================================================
+
+static void	_MDNSDiscoveryTestFirstQueryTimeout( void *inContext )
+{
+	MDNSDiscoveryTestContext * const		context = (MDNSDiscoveryTestContext *) inContext;
+	
+	dispatch_source_forget( &context->queryTimer );
+	
+	FPrintF( stderr, "error: Query for mdnsreplier's \"about\" TXT record timed out.\n" );
+	exit( 1 );
+}
+
+//===========================================================================================================================
+//	_MDNSDiscoveryTestAboutQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_MDNSDiscoveryTestAboutQueryCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext )
+{
+	OSStatus								err;
+	MDNSDiscoveryTestContext * const		context = (MDNSDiscoveryTestContext *) inContext;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inFullName );
+	Unused( inType );
+	Unused( inClass );
+	Unused( inRDataLen );
+	Unused( inRDataPtr );
+	Unused( inTTL );
+	
+	err = inError;
+	require_noerr( err, exit );
+	require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
+	
+	DNSServiceForget( &context->query );
+	dispatch_source_forget( &context->queryTimer );
+	
+	err = ServiceBrowserCreate( dispatch_get_main_queue(), 0, "local.", context->browseTimeSecs, false, &context->browser );
+	require_noerr( err, exit );
+	
+	err = ServiceBrowserAddServiceType( context->browser, context->serviceType );
+	require_noerr( err, exit );
+	
+	ServiceBrowserSetCallback( context->browser, _MDNSDiscoveryTestServiceBrowserCallback, context );
+	ServiceBrowserStart( context->browser );
+	
+exit:
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	_MDNSDiscoveryTestServiceBrowserCallback
+//===========================================================================================================================
+
+#define kMDNSDiscoveryTestResultsKey_ReplierInfo					CFSTR( "replierInfo" )
+#define kMDNSDiscoveryTestResultsKey_StartTime						CFSTR( "startTime" )
+#define kMDNSDiscoveryTestResultsKey_EndTime						CFSTR( "endTime" )
+#define kMDNSDiscoveryTestResultsKey_BrowseTimeSecs					CFSTR( "browseTimeSecs" )
+#define kMDNSDiscoveryTestResultsKey_ServiceType					CFSTR( "serviceType" )
+#define kMDNSDiscoveryTestResultsKey_FlushedCache					CFSTR( "flushedCache" )
+#define kMDNSDiscoveryTestResultsKey_UnexpectedInstances			CFSTR( "unexpectedInstances" )
+#define kMDNSDiscoveryTestResultsKey_MissingInstances				CFSTR( "missingInstances" )
+#define kMDNSDiscoveryTestResultsKey_IncorrectInstances				CFSTR( "incorrectInstances" )
+#define kMDNSDiscoveryTestResultsKey_Success						CFSTR( "success" )
+#define kMDNSDiscoveryTestResultsKey_TotalResolveTime				CFSTR( "totalResolveTimeUs" )
+
+#define kMDNSDiscoveryTestReplierInfoKey_Command					CFSTR( "command" )
+#define kMDNSDiscoveryTestReplierInfoKey_InstanceCount				CFSTR( "instanceCount" )
+#define kMDNSDiscoveryTestReplierInfoKey_TXTSize					CFSTR( "txtSize" )
+#define kMDNSDiscoveryTestReplierInfoKey_RecordCountA				CFSTR( "recordCountA" )
+#define kMDNSDiscoveryTestReplierInfoKey_RecordCountAAAA			CFSTR( "recordCountAAAA" )
+#define kMDNSDiscoveryTestReplierInfoKey_Hostname					CFSTR( "hostname" )
+#define kMDNSDiscoveryTestReplierInfoKey_NoAdditionals				CFSTR( "noAdditionals" )
+#define kMDNSDiscoveryTestReplierInfoKey_UnicastDropRate			CFSTR( "ucastDropRate" )
+#define kMDNSDiscoveryTestReplierInfoKey_MulticastDropRate			CFSTR( "mcastDropRate" )
+#define kMDNSDiscoveryTestReplierInfoKey_MaxDropCount				CFSTR( "maxDropCount" )
+
+#define kMDNSDiscoveryTestUnexpectedInstanceKey_Name				CFSTR( "name" )
+#define kMDNSDiscoveryTestUnexpectedInstanceKey_InterfaceIndex		CFSTR( "interfaceIndex" )
+
+#define kMDNSDiscoveryTestIncorrectInstanceKey_Name					CFSTR( "name" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve			CFSTR( "didResolve" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadHostname			CFSTR( "badHostname" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadPort				CFSTR( "badPort" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadTXT				CFSTR( "badTXT" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_UnexpectedAddrs		CFSTR( "unexpectedAddrs" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_MissingAddrs			CFSTR( "missingAddrs" )
+
+static void	_MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext )
+{
+	OSStatus								err;
+	MDNSDiscoveryTestContext * const		context			= (MDNSDiscoveryTestContext *) inContext;
+	const SBRDomain *						domain;
+	const SBRServiceType *					type;
+	const SBRServiceInstance *				instance;
+	const SBRServiceInstance **				instanceArray	= NULL;
+	const SBRIPAddress *					ipaddr;
+	size_t									hostnameLen;
+	const char *							ptr;
+	const char *							end;
+	unsigned int							i;
+	uint32_t								u32;
+	CFMutableArrayRef						unexpectedInstances;
+	CFMutableArrayRef						missingInstances;
+	CFMutableArrayRef						incorrectInstances;
+	CFMutableDictionaryRef					plist			= NULL;
+	CFMutableDictionaryRef					badDict			= NULL;
+	CFMutableArrayRef						unexpectedAddrs	= NULL;
+	CFMutableArrayRef						missingAddrs	= NULL;
+	uint64_t								maxResolveTimeUs;
+	int										success			= false;
+	char									startTimeStr[ 32 ];
+	char									endTimeStr[ 32 ];
+	
+	context->endTime = NanoTimeGetCurrent();
+	
+	err = inError;
+	require_noerr( err, exit );
+	
+	_NanoTime64ToDateString( context->startTime, startTimeStr, sizeof( startTimeStr ) );
+	_NanoTime64ToDateString( context->endTime, endTimeStr, sizeof( endTimeStr ) );
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO="
+			"{"
+				"%kO=%s"	// replierCommand
+				"%kO=%lli"	// txtSize
+				"%kO=%lli"	// instanceCount
+				"%kO=%lli"	// recordCountA
+				"%kO=%lli"	// recordCountAAAA
+				"%kO=%s"	// hostname
+				"%kO=%b"	// noAdditionals
+				"%kO=%f"	// ucastDropRate
+				"%kO=%f"	// mcastDropRate
+				"%kO=%i"	// maxDropCount
+			"}"
+			"%kO=%s"	// startTime
+			"%kO=%s"	// endTime
+			"%kO=%lli"	// browseTimeSecs
+			"%kO=%s"	// serviceType
+			"%kO=%b"	// flushedCache
+			"%kO=[%@]"	// unexpectedInstances
+			"%kO=[%@]"	// missingInstances
+			"%kO=[%@]"	// incorrectInstances
+		"}",
+		kMDNSDiscoveryTestResultsKey_ReplierInfo,
+		kMDNSDiscoveryTestReplierInfoKey_Command,			context->replierCommand,
+		kMDNSDiscoveryTestReplierInfoKey_InstanceCount,		(int64_t) context->instanceCount,
+		kMDNSDiscoveryTestReplierInfoKey_TXTSize,			(int64_t) context->txtSize,
+		kMDNSDiscoveryTestReplierInfoKey_RecordCountA,		(int64_t) context->recordCountA,
+		kMDNSDiscoveryTestReplierInfoKey_RecordCountAAAA,	(int64_t) context->recordCountAAAA,
+		kMDNSDiscoveryTestReplierInfoKey_Hostname,			context->hostname,
+		kMDNSDiscoveryTestReplierInfoKey_NoAdditionals,		context->noAdditionals,
+		kMDNSDiscoveryTestReplierInfoKey_UnicastDropRate,	context->ucastDropRate,
+		kMDNSDiscoveryTestReplierInfoKey_MulticastDropRate,	context->mcastDropRate,
+		kMDNSDiscoveryTestReplierInfoKey_MaxDropCount,		context->maxDropCount,
+		kMDNSDiscoveryTestResultsKey_StartTime,				startTimeStr,
+		kMDNSDiscoveryTestResultsKey_EndTime,				endTimeStr,
+		kMDNSDiscoveryTestResultsKey_BrowseTimeSecs,		(int64_t) context->browseTimeSecs,
+		kMDNSDiscoveryTestResultsKey_ServiceType,			context->serviceType,
+		kMDNSDiscoveryTestResultsKey_FlushedCache,			context->flushedCache,
+		kMDNSDiscoveryTestResultsKey_UnexpectedInstances,	&unexpectedInstances,
+		kMDNSDiscoveryTestResultsKey_MissingInstances,		&missingInstances,
+		kMDNSDiscoveryTestResultsKey_IncorrectInstances,	&incorrectInstances );
+	require_noerr( err, exit );
+	
+	for( domain = inResults->domainList; domain && ( strcasecmp( domain->name, "local.") != 0 ); domain = domain->next ) {}
+	require_action( domain, exit, err = kInternalErr );
+	
+	for( type = domain->typeList; type && ( strcasecmp( type->name, context->serviceType ) != 0 ); type = type->next ) {}
+	require_action( type, exit, err = kInternalErr );
+	
+	instanceArray = (const SBRServiceInstance **) calloc( context->instanceCount, sizeof( *instanceArray ) );
+	require_action( instanceArray, exit, err = kNoMemoryErr );
+	
+	hostnameLen = strlen( context->hostname );
+	for( instance = type->instanceList; instance; instance = instance->next )
+	{
+		unsigned int		instanceNumber = 0;
+		
+		if( strcmp_prefix( instance->name, context->hostname ) == 0 )
+		{
+			ptr = &instance->name[ hostnameLen ];
+			if( ( ptr[ 0 ] == ' ' ) && ( ptr[ 1 ] == '(' ) )
+			{
+				ptr += 2;
+				for( end = ptr; isdigit_safe( *end ); ++end ) {}
+				if( DecimalTextToUInt32( ptr, end, &u32, &ptr ) == kNoErr )
+				{
+					if( ( u32 >= 2 ) && ( u32 <= context->instanceCount ) && ( ptr[ 0 ] == ')' ) && ( ptr[ 1 ] == '\0' ) )
+					{
+						instanceNumber = u32;
+					}
+				}
+			}
+			else if( *ptr == '\0' )
+			{
+				instanceNumber = 1;
+			}
+		}
+		if( ( instanceNumber != 0 ) && ( instance->ifIndex == context->ifIndex ) )
+		{
+			check( !instanceArray[ instanceNumber - 1 ] );
+			instanceArray[ instanceNumber - 1 ] = instance;
+		}
+		else
+		{
+			err = CFPropertyListAppendFormatted( kCFAllocatorDefault, unexpectedInstances,
+				"{"
+					"%kO=%s"
+					"%kO=%lli"
+				"}",
+				kMDNSDiscoveryTestUnexpectedInstanceKey_Name,			instance->name,
+				kMDNSDiscoveryTestUnexpectedInstanceKey_InterfaceIndex,	(int64_t) instance->ifIndex );
+			require_noerr( err, exit );
+		}
+	}
+	
+	maxResolveTimeUs = 0;
+	for( i = 1; i <= context->instanceCount; ++i )
+	{
+		int		isHostnameValid;
+		int		isTXTValid;
+		
+		instance = instanceArray[ i - 1 ];
+		if( !instance )
+		{
+			if( i == 1 )
+			{
+				err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingInstances, "%s", context->hostname );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				char *		instanceName = NULL;
+				
+				ASPrintF( &instanceName, "%s (%u)", context->hostname, i );
+				require_action( instanceName, exit, err = kUnknownErr );
+				
+				err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingInstances, "%s", instanceName );
+				free( instanceName );
+				require_noerr( err, exit );
+			}
+			continue;
+		}
+		
+		if( !instance->hostname )
+		{
+			err = CFPropertyListAppendFormatted( kCFAllocatorDefault, incorrectInstances,
+				"{"
+					"%kO=%s"
+					"%kO=%b"
+				"}",
+				kMDNSDiscoveryTestIncorrectInstanceKey_Name,		instance->name,
+				kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve,	false );
+			require_noerr( err, exit );
+			continue;
+		}
+		
+		badDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+		require_action( badDict, exit, err = kNoMemoryErr );
+		
+		isHostnameValid = false;
+		if( strcmp_prefix( instance->hostname, context->hostname ) == 0 )
+		{
+			ptr = &instance->hostname[ hostnameLen ];
+			if( i == 1 )
+			{
+				if( strcmp( ptr, ".local." ) == 0 ) isHostnameValid = true;
+			}
+			else if( *ptr == '-' )
+			{
+				++ptr;
+				for( end = ptr; isdigit_safe( *end ); ++end ) {}
+				if( DecimalTextToUInt32( ptr, end, &u32, &ptr ) == kNoErr )
+				{
+					if( ( u32 == i ) && ( strcmp( ptr, ".local." ) == 0 ) ) isHostnameValid = true;
+				}
+			}
+		}
+		if( !isHostnameValid )
+		{
+			err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadHostname, instance->hostname,
+				kSizeCString );
+			require_noerr( err, exit );
+		}
+		
+		if( instance->port != (uint16_t)( kMDNSReplierPortBase + context->txtSize ) )
+		{
+			err = CFDictionarySetInt64( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadPort, instance->port );
+			require_noerr( err, exit );
+		}
+		
+		isTXTValid = false;
+		if( instance->txtLen == context->txtSize )
+		{
+			uint8_t		name[ kDomainNameLengthMax ];
+			
+			err = DomainNameFromString( name, instance->name, NULL );
+			require_noerr( err, exit );
+			
+			err = DomainNameAppendString( name, type->name, NULL );
+			require_noerr( err, exit );
+			
+			err = DomainNameAppendString( name, "local", NULL );
+			require_noerr( err, exit );
+			
+			if( _MDNSDiscoveryTestTXTRecordIsValid( name, instance->txtPtr, instance->txtLen ) ) isTXTValid = true;
+		}
+		if( !isTXTValid )
+		{
+			char *		hexStr = NULL;
+			
+			ASPrintF( &hexStr, "%.4H", instance->txtPtr, (int) instance->txtLen, (int) instance->txtLen );
+			require_action( hexStr, exit, err = kUnknownErr );
+			
+			err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadTXT, hexStr, kSizeCString );
+			free( hexStr );
+			require_noerr( err, exit );
+		}
+		
+		if( isHostnameValid )
+		{
+			uint64_t			addrV4Bitmap, addrV6Bitmap, bitmask, resolveTimeUs;
+			unsigned int		j;
+			uint8_t				addrV4[ 4 ];
+			uint8_t				addrV6[ 16 ];
+			
+			if( context->recordCountA < 64 )	addrV4Bitmap = ( UINT64_C( 1 ) << context->recordCountA ) - 1;
+			else								addrV4Bitmap =  ~UINT64_C( 0 );
+			
+			if( context->recordCountAAAA < 64 ) addrV6Bitmap = ( UINT64_C( 1 ) << context->recordCountAAAA ) - 1;
+			else								addrV6Bitmap =  ~UINT64_C( 0 );
+			
+			addrV4[ 0 ] = 0;
+			WriteBig16( &addrV4[ 1 ], i );
+			addrV4[ 3 ] = 0;
+			
+			memcpy( addrV6, kMDNSReplierBaseAddrV6, 16 );
+			WriteBig16( &addrV6[ 12 ], i );
+			
+			unexpectedAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+			require_action( unexpectedAddrs, exit, err = kNoMemoryErr );
+			
+			resolveTimeUs = 0;
+			for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+			{
+				const uint8_t *		addrPtr;
+				unsigned int		lsb;
+				int					isAddrValid = false;
+				
+				if( ipaddr->sip.sa.sa_family == AF_INET )
+				{
+					addrPtr	= (const uint8_t *) &ipaddr->sip.v4.sin_addr.s_addr;
+					lsb		= addrPtr[ 3 ];
+					if( ( memcmp( addrPtr, addrV4, 3 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountA ) )
+					{
+						bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+						addrV4Bitmap &= ~bitmask;
+						isAddrValid = true;
+					}
+				}
+				else if( ipaddr->sip.sa.sa_family == AF_INET6 )
+				{
+					addrPtr	= ipaddr->sip.v6.sin6_addr.s6_addr;
+					lsb		= addrPtr[ 15 ];
+					if( ( memcmp( addrPtr, addrV6, 15 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountAAAA ) )
+					{
+						bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+						addrV6Bitmap &= ~bitmask;
+						isAddrValid = true;
+					}
+				}
+				if( isAddrValid )
+				{
+					if( ipaddr->resolveTimeUs > resolveTimeUs ) resolveTimeUs = ipaddr->resolveTimeUs;
+				}
+				else
+				{
+					err = CFPropertyListAppendFormatted( kCFAllocatorDefault, unexpectedAddrs, "%##a", &ipaddr->sip );
+					require_noerr( err, exit );
+				}
+			}
+			
+			resolveTimeUs += ( instance->discoverTimeUs + instance->resolveTimeUs );
+			if( resolveTimeUs > maxResolveTimeUs ) maxResolveTimeUs = resolveTimeUs;
+			
+			if( CFArrayGetCount( unexpectedAddrs ) > 0 )
+			{
+				CFDictionarySetValue( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_UnexpectedAddrs, unexpectedAddrs );
+			}
+			ForgetCF( &unexpectedAddrs );
+			
+			missingAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+			require_action( missingAddrs, exit, err = kNoMemoryErr );
+			
+			for( j = 1; addrV4Bitmap != 0; ++j )
+			{
+				bitmask = UINT64_C( 1 ) << ( j - 1 );
+				if( addrV4Bitmap & bitmask )
+				{
+					addrV4Bitmap &= ~bitmask;
+					addrV4[ 3 ] = (uint8_t) j;
+					err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.4a", addrV4 );
+					require_noerr( err, exit );
+				}
+			}
+			for( j = 1; addrV6Bitmap != 0; ++j )
+			{
+				bitmask = UINT64_C( 1 ) << ( j - 1 );
+				if( addrV6Bitmap & bitmask )
+				{
+					addrV6Bitmap &= ~bitmask;
+					addrV6[ 15 ] = (uint8_t) j;
+					err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.16a", addrV6 );
+					require_noerr( err, exit );
+				}
+			}
+			
+			if( CFArrayGetCount( missingAddrs ) > 0 )
+			{
+				CFDictionarySetValue( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_MissingAddrs, missingAddrs );
+			}
+			ForgetCF( &missingAddrs );
+		}
+		
+		if( CFDictionaryGetCount( badDict ) > 0 )
+		{
+			err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_Name, instance->name,
+				kSizeCString );
+			require_noerr( err, exit );
+			
+			CFDictionarySetBoolean( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve, true );
+			CFArrayAppendValue( incorrectInstances, badDict );
+		}
+		ForgetCF( &badDict );
+	}
+	
+	if( ( CFArrayGetCount( unexpectedInstances ) == 0 ) &&
+		( CFArrayGetCount( missingInstances )    == 0 ) &&
+		( CFArrayGetCount( incorrectInstances )  == 0 ) )
+	{
+		err = CFDictionarySetInt64( plist, kMDNSDiscoveryTestResultsKey_TotalResolveTime, (int64_t) maxResolveTimeUs );
+		require_noerr( err, exit );
+		success = true;
+	}
+	else
+	{
+		success = false;
+	}
+	CFDictionarySetBoolean( plist, kMDNSDiscoveryTestResultsKey_Success, success );
+	
+	err = OutputPropertyList( plist, context->outputFormat, context->outputAppendNewline, context->outputFilePath );
+	require_noerr_quiet( err, exit );
+	
+exit:
+	ForgetCF( &context->browser );
+	if( context->replierPID != -1 )
+	{
+		kill( context->replierPID, SIGTERM );
+		context->replierPID = -1;
+	}
+	FreeNullSafe( instanceArray );
+	CFReleaseNullSafe( plist );
+	CFReleaseNullSafe( badDict );
+	CFReleaseNullSafe( unexpectedAddrs );
+	CFReleaseNullSafe( missingAddrs );
+	exit( err ? 1 : ( success ? 0 : 2 ) );
+}
+
+//===========================================================================================================================
+//	_MDNSDiscoveryTestTXTRecordIsValid
+//===========================================================================================================================
+
+static Boolean	_MDNSDiscoveryTestTXTRecordIsValid( const uint8_t *inRecordName, const uint8_t *inTXTPtr, size_t inTXTLen )
+{
+	uint32_t			hash;
+	int					n;
+	const uint8_t *		ptr;
+	size_t				i, wholeCount, remCount;
+	uint8_t				txtStr[ 16 ];
+	
+	if( inTXTLen == 0 ) return( false );
+	
+	hash = FNV1( inRecordName, DomainNameLength( inRecordName ) );
+	
+	txtStr[ 0 ] = 15;
+	n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash );
+	check( n == 15 );
+	
+	ptr = inTXTPtr;
+	wholeCount = inTXTLen / 16;
+	for( i = 0; i < wholeCount; ++i )
+	{
+		if( memcmp( ptr, txtStr, 16 ) != 0 ) return( false );
+		ptr += 16;
+	}
+	
+	remCount = inTXTLen % 16;
+	if( remCount > 0 )
+	{
+		txtStr[ 0 ] = (uint8_t)( remCount - 1 );
+		if( memcmp( ptr, txtStr, remCount ) != 0 ) return( false );
+		ptr += remCount;
+	}
+	check( ptr == &inTXTPtr[ inTXTLen ] );
+	return( true );
+}
+
+//===========================================================================================================================
+//	DotLocalTestCmd
+//===========================================================================================================================
+
+#define kDotLocalTestPreparationTimeLimitSecs		5
+#define kDotLocalTestSubTestDurationSecs			5
+
+// Constants for SRV record query subtest.
+
+#define kDotLocalTestSRV_Priority		1
+#define kDotLocalTestSRV_Weight			0
+#define kDotLocalTestSRV_Port			80
+#define kDotLocalTestSRV_TargetName		( (const uint8_t *) "\x03" "www" "\x07" "example" "\x03" "com" )
+#define kDotLocalTestSRV_TargetStr		"www.example.com."
+#define kDotLocalTestSRV_ResultStr		"1 0 80 " kDotLocalTestSRV_TargetStr
+
+typedef enum
+{
+	kDotLocalTestState_Unset				= 0,
+	kDotLocalTestState_Preparing			= 1,
+	kDotLocalTestState_GAIMDNSOnly			= 2,
+	kDotLocalTestState_GAIDNSOnly			= 3,
+	kDotLocalTestState_GAIBoth				= 4,
+	kDotLocalTestState_GAINeither			= 5,
+	kDotLocalTestState_GAINoSuchRecord		= 6,
+	kDotLocalTestState_QuerySRV				= 7,
+	kDotLocalTestState_Done					= 8
+	
+}	DotLocalTestState;
+
+typedef struct
+{
+	const char *			testDesc;			// Description of the current subtest.
+	char *					queryName;			// Query name for GetAddrInfo or QueryRecord operation.
+	dispatch_source_t		timer;				// Timer used for limiting the time for each subtest.
+	NanoTime64				startTime;			// Timestamp of when the subtest started.
+	NanoTime64				endTime;			// Timestamp of when the subtest ended.
+	CFMutableArrayRef		correctResults;		// Operation results that were expected.
+	CFMutableArrayRef		duplicateResults;	// Operation results that were expected, but were already received.
+	CFMutableArrayRef		unexpectedResults;	// Operation results that were unexpected.
+	OSStatus				error;				// Subtest's error code.
+	uint32_t				addrDNSv4;			// If hasDNSv4 is true, the expected DNS IPv4 address for queryName.
+	uint32_t				addrMDNSv4;			// If hasMDNSv4 is true, the expected MDNS IPv4 address for queryName.
+	uint8_t					addrDNSv6[ 16 ];	// If hasDNSv6 is true, the expected DNS IPv6 address for queryName.
+	uint8_t					addrMDNSv6[ 16 ];	// If hasMDNSv6 is true, the expected MDNS IPv6 address for queryName.
+	Boolean					hasDNSv4;			// True if queryName has a DNS IPv4 address.
+	Boolean					hasDNSv6;			// True if queryName has a DNS IPv6 address.
+	Boolean					hasMDNSv4;			// True if queryName has an MDNS IPv4 address.
+	Boolean					hasMDNSv6;			// True if queryName has an MDNS IPv6 address.
+	Boolean					needDNSv4;			// True if operation is expecting, but hasn't received a DNS IPv4 result.
+	Boolean					needDNSv6;			// True if operation is expecting, but hasn't received a DNS IPv6 result.
+	Boolean					needMDNSv4;			// True if operation is expecting, but hasn't received an MDNS IPv4 result.
+	Boolean					needMDNSv6;			// True if operation is expecting, but hasn't received an MDNS IPv6 result.
+	Boolean					needSRV;			// True if operation is expecting, but hasn't received an SRV result.
+	
+}	DotLocalSubtest;
+
+typedef struct
+{
+	dispatch_source_t		timer;				// Timer used for limiting the time for each state/subtest.
+	DotLocalSubtest *		subtest;			// Current subtest's state.
+	DNSServiceRef			connection;			// Shared connection for DNS-SD operations.
+	DNSServiceRef			op;					// Reference for the current DNS-SD operation.
+	DNSServiceRef			op2;				// Reference for mdnsreplier probe query used during preparing state.
+	DNSRecordRef			localSOARef;		// Reference returned by DNSServiceRegisterRecord() for local. SOA record.
+	char *					replierCmd;			// Command used to invoke the mdnsreplier.
+	char *					serverCmd;			// Command used to invoke the test DNS server.
+	CFMutableArrayRef		reportsGAI;			// Reports for subtests that use DNSServiceGetAddrInfo.
+	CFMutableArrayRef		reportsQuerySRV;	// Reports for subtests that use DNSServiceQueryRecord for SRV records.
+	NanoTime64				startTime;			// Timestamp for when the test started.
+	NanoTime64				endTime;			// Timestamp for when the test ended.
+	DotLocalTestState		state;				// The test's current state.
+	pid_t					replierPID;			// PID of spawned mdnsreplier.
+	pid_t					serverPID;			// PID of spawned test DNS server.
+	uint32_t				ifIndex;			// Interface index used for mdnsreplier.
+	char *					outputFilePath;		// File to write test results to. If NULL, then write to stdout.
+	OutputFormatType		outputFormat;		// Format of test results output.
+	Boolean					appendNewline;		// True if a newline character should be appended to JSON output.
+	Boolean					registeredSOA;		// True if the dummy local. SOA record was successfully registered.
+	Boolean					serverIsReady;		// True if response was received for test DNS server probe query.
+	Boolean					replierIsReady;		// True if response was received for mdnsreplier probe query.
+	Boolean					testFailed;			// True if at least one subtest failed.
+	char					labelStr[ 20 + 1 ];	// Unique label string used for for making the query names used by subtests.
+												// The format of this string is "dotlocal-test-<six random chars>".
+}	DotLocalTestContext;
+
+static void	_DotLocalTestStateMachine( DotLocalTestContext *inContext );
+static void DNSSD_API
+	_DotLocalTestProbeQueryRecordCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext );
+static void DNSSD_API
+	_DotLocalTestRegisterRecordCallback(
+		DNSServiceRef		inSDRef,
+		DNSRecordRef		inRecordRef,
+		DNSServiceFlags		inFlags,
+		DNSServiceErrorType	inError,
+		void *				inContext );
+static void	_DotLocalTestTimerHandler( void *inContext );
+static void DNSSD_API
+	_DotLocalTestGAICallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext );
+static void DNSSD_API
+	_DotLocalTestQueryRecordCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext );
+
+static void	DotLocalTestCmd( void )
+{
+	OSStatus					err;
+	DotLocalTestContext *		context;
+	uint8_t *					rdataPtr;
+	size_t						rdataLen;
+	DNSServiceFlags				flags;
+	char						queryName[ 64 ];
+	char						randBuf[ 6 + 1 ];	// Large enough for four and six character random strings below.
+	
+	context = (DotLocalTestContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->startTime	= NanoTimeGetCurrent();
+	context->endTime	= kNanoTime_Invalid;
+	
+	context->state = kDotLocalTestState_Preparing;
+	
+	if( gDotLocalTest_Interface )
+	{
+		err = InterfaceIndexFromArgString( gDotLocalTest_Interface, &context->ifIndex );
+		require_noerr_quiet( err, exit );
+	}
+	else
+	{
+		err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+		require_noerr_quiet( err, exit );
+	}
+	
+	if( gDotLocalTest_OutputFilePath )
+	{
+		context->outputFilePath = strdup( gDotLocalTest_OutputFilePath );
+		require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+	}
+	
+	context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gDotLocalTest_OutputFormat, &err,
+		kOutputFormatStr_JSON,		kOutputFormatType_JSON,
+		kOutputFormatStr_XML,		kOutputFormatType_XML,
+		kOutputFormatStr_Binary,	kOutputFormatType_Binary,
+		NULL );
+	require_noerr_quiet( err, exit );
+	
+	context->appendNewline = gDotLocalTest_OutputAppendNewline ? true : false;
+	
+	context->reportsGAI = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( context->reportsGAI, exit, err = kNoMemoryErr );
+	
+	context->reportsQuerySRV = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( context->reportsQuerySRV, exit, err = kNoMemoryErr );
+	
+	SNPrintF( context->labelStr, sizeof( context->labelStr ), "dotlocal-test-%s",
+		_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 6, randBuf ) );
+	
+	// Spawn an mdnsreplier.
+	
+	err = ASPrintF( &context->replierCmd,
+		"dnssdutil mdnsreplier --follow %lld --interface %u --hostname %s --tag %s --maxInstanceCount 2 --countA 1"
+		" --countAAAA 1",
+		(int64_t) getpid(), context->ifIndex, context->labelStr,
+		_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 4, randBuf ) );
+	require_action_quiet( context->replierCmd, exit, err = kUnknownErr );
+	
+	err = SpawnCommand( &context->replierPID, "%s", context->replierCmd );
+	require_noerr( err, exit );
+	
+	// Spawn a test DNS server
+	
+	err = ASPrintF( &context->serverCmd,
+		"dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --domain %s.local.",
+		(int64_t) getpid(), context->labelStr );
+	require_action_quiet( context->serverCmd, exit, err = kUnknownErr );
+	
+	err = SpawnCommand( &context->serverPID, "%s", context->serverCmd );
+	require_noerr( err, exit );
+	
+	// Create a shared DNS-SD connection.
+	
+	err = DNSServiceCreateConnection( &context->connection );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( context->connection, dispatch_get_main_queue() );
+	require_noerr( err, exit );
+	
+	// Create probe query for DNS server, i.e., query for any name that has an A record.
+	
+	SNPrintF( queryName, sizeof( queryName ), "tag-dotlocal-test-probe.ipv4.%s.local.", context->labelStr );
+	
+	flags = kDNSServiceFlagsShareConnection;
+#if( TARGET_OS_WATCH )
+	flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+	
+	context->op = context->connection;
+	err = DNSServiceQueryRecord( &context->op, flags, kDNSServiceInterfaceIndexAny, queryName, kDNSServiceType_A,
+		kDNSServiceClass_IN, _DotLocalTestProbeQueryRecordCallback, context );
+	require_noerr( err, exit );
+	
+	// Create probe query for mdnsreplier's "about" TXT record.
+	
+	SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->labelStr );
+	
+	flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsForceMulticast;
+#if( TARGET_OS_WATCH )
+	flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+	
+	context->op2 = context->connection;
+	err = DNSServiceQueryRecord( &context->op2, flags, context->ifIndex, queryName, kDNSServiceType_TXT, kDNSServiceClass_IN,
+		_DotLocalTestProbeQueryRecordCallback, context );
+	require_noerr( err, exit );
+	
+	// Register a dummy local. SOA record.
+	
+	err = CreateSOARecordData( kRootLabel, kRootLabel, 1976040101, 1 * kSecondsPerDay, 2 * kSecondsPerHour,
+		1000 * kSecondsPerHour, 2 * kSecondsPerDay, &rdataPtr, &rdataLen );
+	require_noerr( err, exit );
+	
+	err = DNSServiceRegisterRecord( context->connection, &context->localSOARef, kDNSServiceFlagsUnique,
+		kDNSServiceInterfaceIndexLocalOnly, "local.", kDNSServiceType_SOA, kDNSServiceClass_IN, 1,
+		rdataPtr, 1 * kSecondsPerHour, _DotLocalTestRegisterRecordCallback, context );
+	require_noerr( err, exit );
+	
+	// Start timer for probe responses and SOA record registration.
+	
+	err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDotLocalTestPreparationTimeLimitSecs ),
+		INT64_C_safe( kDotLocalTestPreparationTimeLimitSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(),
+		_DotLocalTestTimerHandler, context, &context->timer );
+	require_noerr( err, exit );
+	dispatch_resume( context->timer );
+	
+	dispatch_main();
+	
+exit:
+	if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestStateMachine
+//===========================================================================================================================
+
+static OSStatus	_DotLocalSubtestCreate( DotLocalSubtest **outSubtest );
+static void		_DotLocalSubtestFree( DotLocalSubtest *inSubtest );
+static OSStatus	_DotLocalTestStartSubtest( DotLocalTestContext *inContext );
+static OSStatus	_DotLocalTestFinalizeSubtest( DotLocalTestContext *inContext );
+static void		_DotLocalTestFinalizeAndExit( DotLocalTestContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void	_DotLocalTestStateMachine( DotLocalTestContext *inContext )
+{
+	OSStatus				err;
+	DotLocalTestState		nextState;
+	
+	DNSServiceForget( &inContext->op );
+	DNSServiceForget( &inContext->op2 );
+	dispatch_source_forget( &inContext->timer );
+	
+	switch( inContext->state )
+	{
+		case kDotLocalTestState_Preparing:			nextState = kDotLocalTestState_GAIMDNSOnly;		break;
+		case kDotLocalTestState_GAIMDNSOnly:		nextState = kDotLocalTestState_GAIDNSOnly;		break;
+		case kDotLocalTestState_GAIDNSOnly:			nextState = kDotLocalTestState_GAIBoth;			break;
+		case kDotLocalTestState_GAIBoth:			nextState = kDotLocalTestState_GAINeither;		break;
+		case kDotLocalTestState_GAINeither:			nextState = kDotLocalTestState_GAINoSuchRecord;	break;
+		case kDotLocalTestState_GAINoSuchRecord:	nextState = kDotLocalTestState_QuerySRV;		break;
+		case kDotLocalTestState_QuerySRV:			nextState = kDotLocalTestState_Done;			break;
+		default:									err = kStateErr;								goto exit;
+	}
+	
+	if( inContext->state == kDotLocalTestState_Preparing )
+	{
+		if( !inContext->registeredSOA || !inContext->serverIsReady || !inContext->replierIsReady )
+		{
+			FPrintF( stderr, "Preparation timed out: Registered SOA? %s. Server ready? %s. mdnsreplier ready? %s.\n",
+				YesNoStr( inContext->registeredSOA ),
+				YesNoStr( inContext->serverIsReady ),
+				YesNoStr( inContext->replierIsReady ) );
+			err = kNotPreparedErr;
+			goto exit;
+		}
+	}
+	else
+	{
+		err = _DotLocalTestFinalizeSubtest( inContext );
+		require_noerr( err, exit );
+	}
+	
+	inContext->state = nextState;
+	if( inContext->state == kDotLocalTestState_Done ) _DotLocalTestFinalizeAndExit( inContext );
+	err = _DotLocalTestStartSubtest( inContext );
+	
+exit:
+	if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	_DotLocalSubtestCreate
+//===========================================================================================================================
+
+static OSStatus	_DotLocalSubtestCreate( DotLocalSubtest **outSubtest )
+{
+	OSStatus				err;
+	DotLocalSubtest *		obj;
+	
+	obj = (DotLocalSubtest *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->correctResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( obj->correctResults, exit, err = kNoMemoryErr );
+	
+	obj->duplicateResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( obj->duplicateResults, exit, err = kNoMemoryErr );
+	
+	obj->unexpectedResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+	require_action( obj->unexpectedResults, exit, err = kNoMemoryErr );
+	
+	*outSubtest = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) _DotLocalSubtestFree( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DotLocalSubtestFree
+//===========================================================================================================================
+
+static void	_DotLocalSubtestFree( DotLocalSubtest *inSubtest )
+{
+	ForgetMem( &inSubtest->queryName );
+	ForgetCF( &inSubtest->correctResults );
+	ForgetCF( &inSubtest->duplicateResults );
+	ForgetCF( &inSubtest->unexpectedResults );
+	free( inSubtest );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestStartSubtest
+//===========================================================================================================================
+
+static OSStatus	_DotLocalTestStartSubtest( DotLocalTestContext *inContext )
+{
+	OSStatus				err;
+	DotLocalSubtest *		subtest	= NULL;
+	DNSServiceRef			op		= NULL;
+	DNSServiceFlags			flags;
+	
+	err = _DotLocalSubtestCreate( &subtest );
+	require_noerr( err, exit );
+	
+	if( inContext->state == kDotLocalTestState_GAIMDNSOnly )
+	{
+		ASPrintF( &subtest->queryName, "%s-2.local.", inContext->labelStr );
+		require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+		
+		subtest->hasMDNSv4 = subtest->needMDNSv4 = true;
+		subtest->hasMDNSv6 = subtest->needMDNSv6 = true;
+		
+		subtest->addrMDNSv4 = htonl( 0x00000201 );					// 0.0.2.1
+		memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 );	// 2001:db8:2::2:1
+		subtest->addrMDNSv6[ 13 ] = 2;
+		subtest->addrMDNSv6[ 15 ] = 1;
+		
+		subtest->testDesc = kDotLocalTestSubtestDesc_GAIMDNSOnly;
+	}
+	
+	else if( inContext->state == kDotLocalTestState_GAIDNSOnly )
+	{
+		ASPrintF( &subtest->queryName, "tag-dns-only.%s.local.", inContext->labelStr );
+		require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+		
+		subtest->hasDNSv4 = subtest->needDNSv4 = true;
+		subtest->hasDNSv6 = subtest->needDNSv6 = true;
+		
+		subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );		// 203.0.113.1
+		memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );		// 2001:db8:1::1
+		subtest->addrDNSv6[ 15 ] = 1;
+		
+		subtest->testDesc = kDotLocalTestSubtestDesc_GAIDNSOnly;
+	}
+	
+	else if( inContext->state == kDotLocalTestState_GAIBoth )
+	{
+		ASPrintF( &subtest->queryName, "%s.local.", inContext->labelStr );
+		require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+		
+		subtest->hasDNSv4	= subtest->needDNSv4	= true;
+		subtest->hasDNSv6	= subtest->needDNSv6	= true;
+		subtest->hasMDNSv4	= subtest->needMDNSv4	= true;
+		subtest->hasMDNSv6	= subtest->needMDNSv6	= true;
+		
+		subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );		// 203.0.113.1
+		memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );		// 2001:db8:1::1
+		subtest->addrDNSv6[ 15 ] = 1;
+		
+		subtest->addrMDNSv4 = htonl( 0x00000101 );					// 0.0.1.1
+		memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 );	// 2001:db8:2::1:1
+		subtest->addrMDNSv6[ 13 ] = 1;
+		subtest->addrMDNSv6[ 15 ] = 1;
+		
+		subtest->testDesc = kDotLocalTestSubtestDesc_GAIBoth;
+	}
+	
+	else if( inContext->state == kDotLocalTestState_GAINeither )
+	{
+		ASPrintF( &subtest->queryName, "doesnotexit-%s.local.", inContext->labelStr );
+		require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+		
+		subtest->testDesc = kDotLocalTestSubtestDesc_GAINeither;
+	}
+	
+	else if( inContext->state == kDotLocalTestState_GAINoSuchRecord )
+	{
+		ASPrintF( &subtest->queryName, "doesnotexit-dns.%s.local.", inContext->labelStr );
+		require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+		
+		subtest->hasDNSv4 = subtest->needDNSv4 = true;
+		subtest->hasDNSv6 = subtest->needDNSv6 = true;
+		subtest->testDesc = kDotLocalTestSubtestDesc_GAINoSuchRecord;
+	}
+	
+	else if( inContext->state == kDotLocalTestState_QuerySRV )
+	{
+		ASPrintF( &subtest->queryName, "_http._tcp.srv-%u-%u-%u.%s%s.local.",
+			kDotLocalTestSRV_Priority, kDotLocalTestSRV_Weight, kDotLocalTestSRV_Port, kDotLocalTestSRV_TargetStr,
+			inContext->labelStr );
+		require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+		
+		subtest->needSRV	= true;
+		subtest->testDesc	= kDotLocalTestSubtestDesc_QuerySRV;
+	}
+	
+	else
+	{
+		err = kStateErr;
+		goto exit;
+	}
+	
+	// Start new operation.
+	
+	flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates;
+#if( TARGET_OS_WATCH )
+	flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+	
+	subtest->startTime	= NanoTimeGetCurrent();
+	subtest->endTime	= kNanoTime_Invalid;
+	
+	if( inContext->state == kDotLocalTestState_QuerySRV )
+	{
+		op = inContext->connection;
+		err = DNSServiceQueryRecord( &op, flags, kDNSServiceInterfaceIndexAny, subtest->queryName,
+			kDNSServiceType_SRV, kDNSServiceClass_IN, _DotLocalTestQueryRecordCallback, inContext );
+		require_noerr( err, exit );
+	}
+	else
+	{
+		op = inContext->connection;
+		err = DNSServiceGetAddrInfo( &op, flags, kDNSServiceInterfaceIndexAny,
+			kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, subtest->queryName, _DotLocalTestGAICallback, inContext );
+		require_noerr( err, exit );
+	}
+	
+	// Start timer.
+	
+	check( !inContext->timer );
+	err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDotLocalTestSubTestDurationSecs ),
+		INT64_C_safe( kDotLocalTestSubTestDurationSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(),
+		_DotLocalTestTimerHandler, inContext, &inContext->timer );
+	require_noerr( err, exit );
+	dispatch_resume( inContext->timer );
+	
+	check( !inContext->op );
+	inContext->op = op;
+	op = NULL;
+	
+	check( !inContext->subtest );
+	inContext->subtest = subtest;
+	subtest = NULL;
+	
+exit:
+	if( subtest )	_DotLocalSubtestFree( subtest );
+	if( op )		DNSServiceRefDeallocate( op );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestFinalizeSubtest
+//===========================================================================================================================
+
+#define kDotLocalTestReportKey_StartTime				CFSTR( "startTime" )		// String.
+#define kDotLocalTestReportKey_EndTime					CFSTR( "endTime" )			// String.
+#define kDotLocalTestReportKey_Success					CFSTR( "success" )			// Boolean.
+#define kDotLocalTestReportKey_MDNSReplierCmd			CFSTR( "replierCmd" )		// String.
+#define kDotLocalTestReportKey_DNSServerCmd				CFSTR( "serverCmd" )		// String.
+#define kDotLocalTestReportKey_GetAddrInfoTests			CFSTR( "testsGAI" )			// Array of Dictionaries.
+#define kDotLocalTestReportKey_QuerySRVTests			CFSTR( "testsQuerySRV" )	// Array of Dictionaries.
+#define kDotLocalTestReportKey_Description				CFSTR( "description" )		// String.
+#define kDotLocalTestReportKey_QueryName				CFSTR( "queryName" )		// String.
+#define kDotLocalTestReportKey_Error					CFSTR( "error" )			// Integer.
+#define kDotLocalTestReportKey_Results					CFSTR( "results" )			// Dictionary of Arrays.
+#define kDotLocalTestReportKey_CorrectResults			CFSTR( "correct" )			// Array of Strings
+#define kDotLocalTestReportKey_DuplicateResults			CFSTR( "duplicates" )		// Array of Strings.
+#define kDotLocalTestReportKey_UnexpectedResults		CFSTR( "unexpected" )		// Array of Strings.
+#define kDotLocalTestReportKey_MissingResults			CFSTR( "missing" )			// Array of Strings.
+
+static OSStatus	_DotLocalTestFinalizeSubtest( DotLocalTestContext *inContext )
+{
+	OSStatus					err;
+	DotLocalSubtest *			subtest;
+	CFMutableDictionaryRef		reportDict;
+	CFMutableDictionaryRef		resultsDict;
+	CFMutableArrayRef			missingResults, reportArray;
+	char						startTimeStr[ 32 ];
+	char						endTimeStr[ 32 ];
+	
+	subtest = inContext->subtest;
+	inContext->subtest = NULL;
+	
+	subtest->endTime = NanoTimeGetCurrent();
+	_NanoTime64ToDateString( subtest->startTime, startTimeStr, sizeof( startTimeStr ) );
+	_NanoTime64ToDateString( subtest->endTime, endTimeStr, sizeof( endTimeStr ) );
+	
+	reportDict = NULL;
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &reportDict,
+		"{"
+			"%kO=%s"	// startTime
+			"%kO=%s"	// endTime
+			"%kO=%s"	// queryName
+			"%kO=%s"	// description
+			"%kO={%@}"	// results
+		"}",
+		kDotLocalTestReportKey_StartTime,	startTimeStr,
+		kDotLocalTestReportKey_EndTime,		endTimeStr,
+		kDotLocalTestReportKey_QueryName,	subtest->queryName,
+		kDotLocalTestReportKey_Description,	subtest->testDesc,
+		kDotLocalTestReportKey_Results,		&resultsDict );
+	require_noerr( err, exit );
+	
+	missingResults = NULL;
+	switch( inContext->state )
+	{
+		case kDotLocalTestState_GAIMDNSOnly:
+		case kDotLocalTestState_GAIDNSOnly:
+		case kDotLocalTestState_GAIBoth:
+		case kDotLocalTestState_GAINeither:
+			if( subtest->needDNSv4 || subtest->needDNSv6 || subtest->needMDNSv4 || subtest->needMDNSv6 )
+			{
+				err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+					"["
+						"%.4a"	// Expected DNS IPv4 address
+						"%.16a"	// Expected DNS IPv6 address
+						"%.4a"	// Expected MDNS IPv4 address
+						"%.16a"	// Expected MDNS IPv6 address
+					"]",
+					subtest->needDNSv4  ? &subtest->addrDNSv4  : NULL,
+					subtest->needDNSv6  ?  subtest->addrDNSv6  : NULL,
+					subtest->needMDNSv4 ? &subtest->addrMDNSv4 : NULL,
+					subtest->needMDNSv6 ?  subtest->addrMDNSv6 : NULL );
+				require_noerr( err, exit );
+			}
+			break;
+		
+		case kDotLocalTestState_QuerySRV:
+			if( subtest->needSRV )
+			{
+				err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+					"["
+						"%s"	// Expected SRV record data as a string.
+					"]",
+					kDotLocalTestSRV_ResultStr );
+				require_noerr( err, exit );
+			}
+			break;
+		
+		case kDotLocalTestState_GAINoSuchRecord:
+			if( subtest->needDNSv4 || subtest->needDNSv6 )
+			{
+				err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+					"["
+						"%s" // No Such Record (A)
+						"%s" // No Such Record (AAAA)
+					"]",
+					subtest->needDNSv4 ? kNoSuchRecordAStr    : NULL,
+					subtest->needDNSv6 ? kNoSuchRecordAAAAStr : NULL );
+				require_noerr( err, exit );
+			}
+			break;
+		
+		default:
+			err = kStateErr;
+			goto exit;
+	}
+	
+	CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_CorrectResults, subtest->correctResults );
+	
+	if( missingResults )
+	{
+		CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_MissingResults, missingResults );
+		ForgetCF( &missingResults );
+		if( !subtest->error ) subtest->error = kNotFoundErr;
+	}
+	
+	if( CFArrayGetCount( subtest->unexpectedResults ) > 0 )
+	{
+		CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_UnexpectedResults, subtest->unexpectedResults );
+		if( !subtest->error ) subtest->error = kUnexpectedErr;
+	}
+	
+	if( CFArrayGetCount( subtest->duplicateResults ) > 0 )
+	{
+		CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_DuplicateResults, subtest->duplicateResults );
+		if( !subtest->error ) subtest->error = kDuplicateErr;
+	}
+	
+	if( subtest->error ) inContext->testFailed = true;
+	err = CFDictionarySetInt64( reportDict, kDotLocalTestReportKey_Error, subtest->error );
+	require_noerr( err, exit );
+	
+	reportArray = ( inContext->state == kDotLocalTestState_QuerySRV ) ? inContext->reportsQuerySRV : inContext->reportsGAI;
+	CFArrayAppendValue( reportArray, reportDict );
+	
+exit:
+	_DotLocalSubtestFree( subtest );
+	CFReleaseNullSafe( reportDict );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestFinalizeAndExit
+//===========================================================================================================================
+
+static void	_DotLocalTestFinalizeAndExit( DotLocalTestContext *inContext )
+{
+	OSStatus				err;
+	CFPropertyListRef		plist;
+	char					startTimeStr[ 32 ];
+	char					endTimeStr[ 32 ];
+	
+	check( !inContext->subtest );
+	inContext->endTime = NanoTimeGetCurrent();
+	
+	if( inContext->replierPID != -1 )
+	{
+		kill( inContext->replierPID, SIGTERM );
+		inContext->replierPID = -1;
+	}
+	if( inContext->serverPID != -1 )
+	{
+		kill( inContext->serverPID, SIGTERM );
+		inContext->serverPID = -1;
+	}
+	err = DNSServiceRemoveRecord( inContext->connection, inContext->localSOARef, 0 );
+	require_noerr( err, exit );
+	
+	_NanoTime64ToDateString( inContext->startTime, startTimeStr, sizeof( startTimeStr ) );
+	_NanoTime64ToDateString( inContext->endTime, endTimeStr, sizeof( endTimeStr ) );
+	
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO=%s"	// startTime
+			"%kO=%s"	// endTime
+			"%kO=%O"	// testsGAI
+			"%kO=%O"	// testsQuerySRV
+			"%kO=%b"	// success
+			"%kO=%s"	// replierCmd
+			"%kO=%s"	// serverCmd
+		"}",
+		kDotLocalTestReportKey_StartTime,			startTimeStr,
+		kDotLocalTestReportKey_EndTime,				endTimeStr,
+		kDotLocalTestReportKey_GetAddrInfoTests,	inContext->reportsGAI,
+		kDotLocalTestReportKey_QuerySRVTests,		inContext->reportsQuerySRV,
+		kDotLocalTestReportKey_Success,				inContext->testFailed ? false : true,
+		kDotLocalTestReportKey_MDNSReplierCmd,		inContext->replierCmd,
+		kDotLocalTestReportKey_DNSServerCmd,		inContext->serverCmd );
+	require_noerr( err, exit );
+	
+	ForgetCF( &inContext->reportsGAI );
+	ForgetCF( &inContext->reportsQuerySRV );
+	
+	err = OutputPropertyList( plist, inContext->outputFormat, inContext->appendNewline, inContext->outputFilePath );
+	CFRelease( plist );
+	require_noerr( err, exit );
+	
+	exit( inContext->testFailed ? 2 : 0 );
+	
+exit:
+	ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestProbeQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_DotLocalTestProbeQueryRecordCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext )
+{
+	DotLocalTestContext * const		context = (DotLocalTestContext *) inContext;
+	
+	Unused( inInterfaceIndex );
+	Unused( inFullName );
+	Unused( inType );
+	Unused( inClass );
+	Unused( inRDataLen );
+	Unused( inRDataPtr );
+	Unused( inTTL );
+	
+	check( context->state == kDotLocalTestState_Preparing );
+	
+	require_quiet( ( inFlags & kDNSServiceFlagsAdd ) && !inError, exit );
+	
+	if( inSDRef == context->op )
+	{
+		DNSServiceForget( &context->op );
+		context->serverIsReady = true;
+	}
+	else if( inSDRef == context->op2 )
+	{
+		DNSServiceForget( &context->op2 );
+		context->replierIsReady = true;
+	}
+	
+	if( context->registeredSOA && context->serverIsReady && context->replierIsReady )
+	{
+		_DotLocalTestStateMachine( context );
+	}
+	
+exit:
+	return;
+}
+
+//===========================================================================================================================
+//	_DotLocalTestRegisterRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_DotLocalTestRegisterRecordCallback(
+		DNSServiceRef		inSDRef,
+		DNSRecordRef		inRecordRef,
+		DNSServiceFlags		inFlags,
+		DNSServiceErrorType	inError,
+		void *				inContext )
+{
+	DotLocalTestContext * const		context = (DotLocalTestContext *) inContext;
+	
+	Unused( inSDRef );
+	Unused( inRecordRef );
+	Unused( inFlags );
+	
+	if( inError ) ErrQuit( 1, "error: local. SOA record registration failed: %#m\n", inError );
+	
+	if( !context->registeredSOA )
+	{
+		context->registeredSOA = true;
+		if( context->serverIsReady && context->replierIsReady ) _DotLocalTestStateMachine( context );
+	}
+}
+
+//===========================================================================================================================
+//	_DotLocalTestTimerHandler
+//===========================================================================================================================
+
+static void	_DotLocalTestTimerHandler( void *inContext )
+{
+	_DotLocalTestStateMachine( (DotLocalTestContext *) inContext );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_DotLocalTestGAICallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext )
+{
+	OSStatus						err;
+	DotLocalTestContext * const		context = (DotLocalTestContext *) inContext;
+	DotLocalSubtest * const			subtest	= context->subtest;
+	const sockaddr_ip * const		sip		= (const sockaddr_ip *) inSockAddr;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inHostname );
+	Unused( inTTL );
+	
+	require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+	require_action_quiet( ( sip->sa.sa_family == AF_INET ) || ( sip->sa.sa_family == AF_INET6 ), exit, err = kTypeErr );
+	
+	if( context->state == kDotLocalTestState_GAINoSuchRecord )
+	{
+		if( inError == kDNSServiceErr_NoSuchRecord )
+		{
+			CFMutableArrayRef		array = NULL;	
+			const char *			noSuchRecordStr;
+			
+			if( sip->sa.sa_family == AF_INET )
+			{
+				array = subtest->needDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+				subtest->needDNSv4 = false;
+				
+				noSuchRecordStr = kNoSuchRecordAStr;
+			}
+			else
+			{
+				array = subtest->needDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+				subtest->needDNSv6 = false;
+				
+				noSuchRecordStr = kNoSuchRecordAAAAStr;
+			}
+			err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%s", noSuchRecordStr );
+			require_noerr( err, fatal );
+		}
+		else if( !inError )
+		{
+			err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpectedResults, "%##a", sip );
+			require_noerr( err, fatal );
+		}
+		else
+		{
+			err = inError;
+			goto exit;
+		}
+	}
+	else
+	{
+		if( !inError )
+		{
+			CFMutableArrayRef		array = NULL;	
+			
+			if( sip->sa.sa_family == AF_INET )
+			{
+				const uint32_t		addrV4 = sip->v4.sin_addr.s_addr;
+				
+				if( subtest->hasDNSv4 && ( addrV4 == subtest->addrDNSv4 ) )
+				{
+					array = subtest->needDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+					subtest->needDNSv4 = false;
+				}
+				else if( subtest->hasMDNSv4 && ( addrV4 == subtest->addrMDNSv4 ) )
+				{
+					array = subtest->needMDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+					subtest->needMDNSv4 = false;
+				}
+			}
+			else
+			{
+				const uint8_t * const		addrV6 = sip->v6.sin6_addr.s6_addr;
+				
+				if( subtest->hasDNSv6 && ( memcmp( addrV6, subtest->addrDNSv6, 16 ) == 0 ) )
+				{
+					array = subtest->needDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+					subtest->needDNSv6 = false;
+				}
+				else if( subtest->hasMDNSv6 && ( memcmp( addrV6, subtest->addrMDNSv6, 16 ) == 0 ) )
+				{
+					array = subtest->needMDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+					subtest->needMDNSv6 = false;
+				}
+			}
+			if( !array ) array = subtest->unexpectedResults;
+			err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%##a", sip );
+			require_noerr( err, fatal );
+		}
+		else if( inError == kDNSServiceErr_NoSuchRecord )
+		{
+			err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpectedResults, "%s",
+				( sip->sa.sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr );
+			require_noerr( err, fatal );
+		}
+		else
+		{
+			err = inError;
+			goto exit;
+		}
+	}
+	
+exit:
+	if( err )
+	{
+		subtest->error = err;
+		_DotLocalTestStateMachine( context );
+	}
+	return;
+	
+fatal:
+	ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	_DotLocalTestQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_DotLocalTestQueryRecordCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext )
+{
+	OSStatus								err;
+	DotLocalTestContext * const				context = (DotLocalTestContext *) inContext;
+	DotLocalSubtest * const					subtest = context->subtest;
+	const SRVRecordDataFixedFields *		fields;
+	const uint8_t *							target;
+	const uint8_t *							ptr;
+	const uint8_t *							end;
+	char *									rdataStr;
+	unsigned int							priority, weight, port;
+	CFMutableArrayRef						array;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inFullName );
+	Unused( inTTL );
+	
+	check( context->state == kDotLocalTestState_QuerySRV );
+	
+	err = inError;
+	require_noerr_quiet( err, exit );
+	require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+	require_action_quiet( ( inType == kDNSServiceType_SRV ) && ( inClass == kDNSServiceClass_IN ), exit, err = kTypeErr );
+	require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kSizeErr );
+	
+	fields	= (const SRVRecordDataFixedFields *) inRDataPtr;
+	SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port );
+	target	= (const uint8_t *) &fields[ 1 ];
+	end		= ( (const uint8_t *) inRDataPtr ) + inRDataLen;
+	for( ptr = target; ( ptr < end ) && ( *ptr != 0 ); ptr += ( 1 + *ptr ) ) {}
+	
+	if( ( priority == kDotLocalTestSRV_Priority ) &&
+		( weight   == kDotLocalTestSRV_Weight )   &&
+		( port     == kDotLocalTestSRV_Port )     &&
+		( ptr < end ) && DomainNameEqual( target, kDotLocalTestSRV_TargetName ) )
+	{
+		array = subtest->needSRV ? subtest->correctResults : subtest->duplicateResults;
+		subtest->needSRV = false;
+	}
+	else
+	{
+		array = subtest->unexpectedResults;
+	}
+	
+	rdataStr = NULL;
+	DNSRecordDataToString( inRDataPtr, inRDataLen, kDNSServiceType_SRV, NULL, 0, &rdataStr );
+	if( !rdataStr )
+	{
+		ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, inRDataLen );
+		require_action( rdataStr, fatal, err = kNoMemoryErr );
+	}
+	
+	err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%s", rdataStr );
+	free( rdataStr );
+	require_noerr( err, fatal );
+	
+exit:
+	if( err )
+	{
+		subtest->error = err;
+		_DotLocalTestStateMachine( context );
+	}
+	return;
+	
+fatal:
+	ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	ProbeConflictTestCmd
+//===========================================================================================================================
+
+#define kProbeConflictTestService_DefaultName		"name"
+#define kProbeConflictTestService_Port				60000
+
+#define kProbeConflictTestTXTPtr		"\x13" "PROBE-CONFLICT-TEST"
+#define kProbeConflictTestTXTLen		sizeof_string( kProbeConflictTestTXTPtr )
+
+typedef struct
+{
+	const char *		description;
+	const char *		program;
+	Boolean				expectsRename;
+	
+}	ProbeConflictTestCase;
+
+#define kPCTProgPreWait			"wait 1000;"	// Wait 1 second before sending gratuitous response.
+#define kPCTProgPostWait		"wait 8000;"	// Wait 8 seconds after sending gratuitous response.
+												// This allows ~2.75 seconds for probing and ~5 seconds for a rename.
+
+static const ProbeConflictTestCase		kProbeConflictTestCases[] =
+{
+	// No conflicts
+	
+	{ "No probe conflicts.",                       kPCTProgPreWait "probes n-n-n;"       "send;" kPCTProgPostWait, false },
+	
+	// One multicast probe conflict
+	
+	{ "One multicast probe conflict (1).",         kPCTProgPreWait "probes m;"           "send;" kPCTProgPostWait, false },
+	{ "One multicast probe conflict (2).",         kPCTProgPreWait "probes n-m;"         "send;" kPCTProgPostWait, false },
+	{ "One multicast probe conflict (3).",         kPCTProgPreWait "probes n-n-m;"       "send;" kPCTProgPostWait, false },
+	
+	// One unicast probe conflict
+	
+	{ "One unicast probe conflict (1).",           kPCTProgPreWait "probes u;"           "send;" kPCTProgPostWait, true },
+	{ "One unicast probe conflict (2).",           kPCTProgPreWait "probes n-u;"         "send;" kPCTProgPostWait, true },
+	{ "One unicast probe conflict (3).",           kPCTProgPreWait "probes n-n-u;"       "send;" kPCTProgPostWait, true },
+	
+	// One multicast and one unicast probe conflict
+	
+	{ "Multicast and unicast probe conflict (1).", kPCTProgPreWait "probes m-u;"         "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (2).", kPCTProgPreWait "probes m-n-u;"       "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (3).", kPCTProgPreWait "probes m-n-n-u;"     "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (4).", kPCTProgPreWait "probes n-m-u;"       "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (5).", kPCTProgPreWait "probes n-m-n-u;"     "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (6).", kPCTProgPreWait "probes n-m-n-n-u;"   "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (7).", kPCTProgPreWait "probes n-n-m-u;"     "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (8).", kPCTProgPreWait "probes n-n-m-n-u;"   "send;" kPCTProgPostWait, true },
+	{ "Multicast and unicast probe conflict (9).", kPCTProgPreWait "probes n-n-m-n-n-u;" "send;" kPCTProgPostWait, true },
+	
+	// Two multicast probe conflicts
+	
+	{ "Two multicast probe conflicts (1).",        kPCTProgPreWait "probes m-m;"         "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (2).",        kPCTProgPreWait "probes m-n-m;"       "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (3).",        kPCTProgPreWait "probes m-n-n-m;"     "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (4).",        kPCTProgPreWait "probes n-m-m;"       "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (5).",        kPCTProgPreWait "probes n-m-n-m-n;"   "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (6).",        kPCTProgPreWait "probes n-m-n-n-m;"   "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (7).",        kPCTProgPreWait "probes n-n-m-m;"     "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (8).",        kPCTProgPreWait "probes n-n-m-n-m;"   "send;" kPCTProgPostWait, true },
+	{ "Two multicast probe conflicts (9).",        kPCTProgPreWait "probes n-n-m-n-n-m;" "send;" kPCTProgPostWait, true },
+};
+
+#define kProbeConflictTestCaseCount		countof( kProbeConflictTestCases )
+
+typedef struct
+{
+	DNSServiceRef			registration;	// Test service registration.
+	NanoTime64				testStartTime;	// Test's start time.
+	NanoTime64				startTime;		// Current test case's start time.
+	MDNSColliderRef			collider;		// mDNS collider object.
+	CFMutableArrayRef		results;		// Array of test case results.
+	char *					serviceName;	// Test service's instance name as a string. (malloced)
+	char *					serviceType;	// Test service's service type as a string. (malloced)
+	uint8_t *				recordName;		// FQDN of collider's record (same as test service's SRV+TXT records). (malloced)
+	unsigned int			testCaseIndex;	// Index of the current test case.
+	uint32_t				ifIndex;		// Index of the interface that the collider is to operate on.
+	char *					outputFilePath;	// File to write test results to. If NULL, then write to stdout. (malloced)
+	OutputFormatType		outputFormat;	// Format of test report output.
+	Boolean					appendNewline;	// True if a newline character should be appended to JSON output.
+	Boolean					registered;		// True if the test service instance is currently registered.
+	Boolean					testFailed;		// True if at least one test case failed.
+	
+}	ProbeConflictTestContext;
+
+static void DNSSD_API
+	_ProbeConflictTestRegisterCallback(
+		DNSServiceRef		inSDRef,
+		DNSServiceFlags		inFlags,
+		DNSServiceErrorType	inError,
+		const char *		inName,
+		const char *		inType,
+		const char *		inDomain,
+		void *				inContext );
+static void		_ProbeConflictTestColliderStopHandler( void *inContext, OSStatus inError );
+static OSStatus	_ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext );
+static OSStatus	_ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed );
+static void		_ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void	ProbeConflictTestCmd( void )
+{
+	OSStatus						err;
+	ProbeConflictTestContext *		context;
+	const char *					serviceName;
+	char							tag[ 6 + 1 ];
+	
+	context = (ProbeConflictTestContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	if( gProbeConflictTest_Interface )
+	{
+		err = InterfaceIndexFromArgString( gProbeConflictTest_Interface, &context->ifIndex );
+		require_noerr_quiet( err, exit );
+	}
+	else
+	{
+		err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+		require_noerr_quiet( err, exit );
+	}
+	
+	if( gProbeConflictTest_OutputFilePath )
+	{
+		context->outputFilePath = strdup( gProbeConflictTest_OutputFilePath );
+		require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+	}
+	
+	context->appendNewline	= gProbeConflictTest_OutputAppendNewline ? true : false;
+	context->outputFormat	= (OutputFormatType) CLIArgToValue( "format", gProbeConflictTest_OutputFormat, &err,
+		kOutputFormatStr_JSON,		kOutputFormatType_JSON,
+		kOutputFormatStr_XML,		kOutputFormatType_XML,
+		kOutputFormatStr_Binary,	kOutputFormatType_Binary,
+		NULL );
+	require_noerr_quiet( err, exit );
+	
+	context->results = CFArrayCreateMutable( NULL, kProbeConflictTestCaseCount, &kCFTypeArrayCallBacks );
+	require_action( context->results, exit, err = kNoMemoryErr );
+	
+	serviceName = gProbeConflictTest_UseComputerName ? NULL : kProbeConflictTestService_DefaultName;
+	
+	ASPrintF( &context->serviceType, "_pctest-%s._udp",
+		_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+	require_action( context->serviceType, exit, err = kNoMemoryErr );
+	
+	context->testStartTime = NanoTimeGetCurrent();
+	err = DNSServiceRegister( &context->registration, 0, context->ifIndex, serviceName, context->serviceType, "local.",
+		NULL, htons( kProbeConflictTestService_Port ), 0, NULL, _ProbeConflictTestRegisterCallback, context );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( context->registration, dispatch_get_main_queue() );
+	require_noerr( err, exit );
+	
+	dispatch_main();
+	
+exit:
+	exit( 1 );
+}
+
+//===========================================================================================================================
+//	_ProbeConflictTestRegisterCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_ProbeConflictTestRegisterCallback(
+		DNSServiceRef		inSDRef,
+		DNSServiceFlags		inFlags,
+		DNSServiceErrorType	inError,
+		const char *		inName,
+		const char *		inType,
+		const char *		inDomain,
+		void *				inContext )
+{
+	OSStatus								err;
+	ProbeConflictTestContext * const		context = (ProbeConflictTestContext *) inContext;
+	
+	Unused( inSDRef );
+	Unused( inType );
+	Unused( inDomain );
+	
+	err = inError;
+	require_noerr( err, exit );
+	
+	if( !context->registered )
+	{
+		if( inFlags & kDNSServiceFlagsAdd )
+		{
+			uint8_t *			ptr;
+			size_t				recordNameLen;
+			unsigned int		len;
+			uint8_t				name[ kDomainNameLengthMax ];
+			
+			context->registered = true;
+			
+			FreeNullSafe( context->serviceName );
+			context->serviceName = strdup( inName );
+			require_action( context->serviceName, exit, err = kNoMemoryErr );
+			
+			err = DomainNameFromString( name, context->serviceName, NULL );
+			require_noerr( err, exit );
+			
+			err = DomainNameAppendString( name, context->serviceType, NULL );
+			require_noerr( err, exit );
+			
+			err = DomainNameAppendString( name, "local", NULL );
+			require_noerr( err, exit );
+			
+			ForgetMem( &context->recordName );
+			err = DomainNameDup( name, &context->recordName, &recordNameLen );
+			require_noerr( err, exit );
+			require_fatal( recordNameLen > 0, "Record name length is zero." );	// Prevents dubious static analyzer warning.
+			
+			// Make the first label all caps so that it's easier to spot in system logs.
+			
+			ptr = context->recordName;
+			for( len = *ptr++; len > 0; --len, ++ptr ) *ptr = (uint8_t) toupper_safe( *ptr );
+			
+			err = _ProbeConflictTestStartNextTest( context );
+			require_noerr( err, exit );
+		}
+	}
+	else
+	{
+		if( !( inFlags & kDNSServiceFlagsAdd ) )
+		{
+			context->registered = false;
+			err = _ProbeConflictTestStopCurrentTest( context, true );
+			require_noerr( err, exit );
+		}
+	}
+	err = kNoErr;
+	
+exit:
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	_ProbeConflictTestColliderStopHandler
+//===========================================================================================================================
+
+static void	_ProbeConflictTestColliderStopHandler( void *inContext, OSStatus inError )
+{
+	OSStatus								err;
+	ProbeConflictTestContext * const		context = (ProbeConflictTestContext *) inContext;
+	
+	err = inError;
+	require_noerr_quiet( err, exit );
+	
+	ForgetCF( &context->collider );
+	
+	err = _ProbeConflictTestStopCurrentTest( context, false );
+	require_noerr( err, exit );
+	
+	err = _ProbeConflictTestStartNextTest( context );
+	require_noerr( err, exit );
+	
+exit:
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	_ProbeConflictTestStartNextTest
+//===========================================================================================================================
+
+static OSStatus	_ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext )
+{
+	OSStatus							err;
+	const ProbeConflictTestCase *		testCase;
+	
+	check( !inContext->collider );
+	
+	if( inContext->testCaseIndex < kProbeConflictTestCaseCount )
+	{
+		testCase = &kProbeConflictTestCases[ inContext->testCaseIndex ];
+	}
+	else
+	{
+		_ProbeConflictTestFinalizeAndExit( inContext );
+	}
+	
+	err = MDNSColliderCreate( dispatch_get_main_queue(), &inContext->collider );
+	require_noerr( err, exit );
+	
+	err = MDNSColliderSetProgram( inContext->collider, testCase->program );
+	require_noerr( err, exit );
+	
+	err = MDNSColliderSetRecord( inContext->collider, inContext->recordName, kDNSServiceType_TXT,
+		kProbeConflictTestTXTPtr, kProbeConflictTestTXTLen );
+	require_noerr( err, exit );
+	
+	MDNSColliderSetProtocols( inContext->collider, kMDNSColliderProtocol_IPv4 );
+	MDNSColliderSetInterfaceIndex( inContext->collider, inContext->ifIndex );
+	MDNSColliderSetStopHandler( inContext->collider, _ProbeConflictTestColliderStopHandler, inContext );
+	
+	inContext->startTime = NanoTimeGetCurrent();
+	err = MDNSColliderStart( inContext->collider );
+	require_noerr( err, exit );
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	_ProbeConflictTestStopCurrentTest
+//===========================================================================================================================
+
+#define kProbeConflictTestCaseResultKey_Description			CFSTR( "description" )
+#define kProbeConflictTestCaseResultKey_StartTime			CFSTR( "startTime" )
+#define kProbeConflictTestCaseResultKey_EndTime				CFSTR( "endTime" )
+#define kProbeConflictTestCaseResultKey_ExpectedRename		CFSTR( "expectedRename" )
+#define kProbeConflictTestCaseResultKey_ServiceName			CFSTR( "serviceName" )
+#define kProbeConflictTestCaseResultKey_Passed				CFSTR( "passed" )
+
+static OSStatus	_ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed )
+{
+	OSStatus							err;
+	const ProbeConflictTestCase *		testCase;
+	NanoTime64							endTime;
+	Boolean								passed;
+	char								startTimeStr[ 32 ];
+	char								endTimeStr[ 32 ];
+	
+	endTime = NanoTimeGetCurrent();
+	
+	if( inContext->collider )
+	{
+		MDNSColliderSetStopHandler( inContext->collider, NULL, NULL );
+		MDNSColliderStop( inContext->collider );
+		CFRelease( inContext->collider );
+		inContext->collider = NULL;
+	}
+	
+	testCase = &kProbeConflictTestCases[ inContext->testCaseIndex ];
+	passed = ( ( testCase->expectsRename && inRenamed ) || ( !testCase->expectsRename && !inRenamed ) ) ? true : false;
+	if( !passed ) inContext->testFailed = true;
+	
+	_NanoTime64ToDateString( inContext->startTime, startTimeStr, sizeof( startTimeStr ) );
+	_NanoTime64ToDateString( endTime, endTimeStr, sizeof( endTimeStr ) );
+	
+	err = CFPropertyListAppendFormatted( kCFAllocatorDefault, inContext->results,
+		"{"
+			"%kO=%s"	// description
+			"%kO=%b"	// expectedRename
+			"%kO=%s"	// startTime
+			"%kO=%s"	// endTime
+			"%kO=%s"	// serviceName
+			"%kO=%b"	// passed
+		"}",
+		kProbeConflictTestCaseResultKey_Description,	testCase->description,
+		kProbeConflictTestCaseResultKey_ExpectedRename,	testCase->expectsRename,
+		kProbeConflictTestCaseResultKey_StartTime,		startTimeStr,
+		kProbeConflictTestCaseResultKey_EndTime,		endTimeStr,
+		kProbeConflictTestCaseResultKey_ServiceName,	inContext->serviceName,
+		kProbeConflictTestCaseResultKey_Passed,			passed );
+	require_noerr( err, exit );
+	
+	++inContext->testCaseIndex;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	_ProbeConflictTestFinalizeAndExit
+//===========================================================================================================================
+
+#define kProbeConflictTestReportKey_StartTime		CFSTR( "startTime" )
+#define kProbeConflictTestReportKey_EndTime			CFSTR( "endTime" )
+#define kProbeConflictTestReportKey_ServiceType		CFSTR( "serviceType" )
+#define kProbeConflictTestReportKey_Results			CFSTR( "results" )
+#define kProbeConflictTestReportKey_Passed			CFSTR( "passed" )
+
+static void	_ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext )
+{
+	OSStatus				err;
+	CFPropertyListRef		plist;
+	NanoTime64				endTime;
+	char					startTimeStr[ 32 ];
+	char					endTimeStr[ 32 ];
+	
+	endTime = NanoTimeGetCurrent();
+	
+	check( !inContext->collider );
+	
+	_NanoTime64ToDateString( inContext->testStartTime, startTimeStr, sizeof( startTimeStr ) );
+	_NanoTime64ToDateString( endTime, endTimeStr, sizeof( endTimeStr ) );
+	
+	err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+		"{"
+			"%kO=%s"	// startTime
+			"%kO=%s"	// endTime
+			"%kO=%s"	// serviceType
+			"%kO=%O"	// results
+			"%kO=%b"	// passed
+		"}",
+		kProbeConflictTestReportKey_StartTime,		startTimeStr,
+		kProbeConflictTestReportKey_EndTime,		endTimeStr,
+		kProbeConflictTestReportKey_ServiceType,	inContext->serviceType,
+		kProbeConflictTestReportKey_Results,		inContext->results,
+		kProbeConflictTestReportKey_Passed,			inContext->testFailed ? false : true );
+	require_noerr( err, exit );
+	ForgetCF( &inContext->results );
+	
+	err = OutputPropertyList( plist, inContext->outputFormat, inContext->appendNewline, inContext->outputFilePath );
+	CFRelease( plist );
+	require_noerr( err, exit );
+	
+	exit( inContext->testFailed ? 2 : 0 );
+	
+exit:
+	ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	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 )
+	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 *		sockCtx;
+		
+		err = SocketContextCreate( sockV4, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV4 = kInvalidSocketRef;
+		
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
+			&context->readSourceV4 );
+		if( err ) ForgetSocketContext( &sockCtx );
+		require_noerr( err, exit );
+		
+		dispatch_resume( context->readSourceV4 );
+	}
+	
+	if( IsValidSocket( sockV6 ) )
+	{
+		SocketContext *		sockCtx;
+		
+		err = SocketContextCreate( sockV6, context, &sockCtx );
+		require_noerr( err, exit );
+		sockV6 = kInvalidSocketRef;
+		
+		err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
+			&context->readSourceV6 );
+		if( err ) ForgetSocketContext( &sockCtx );
+		require_noerr( err, exit );
+		
+		dispatch_resume( context->readSourceV6 );
+	}
+	
+	if( context->receiveSecs > 0 )
+	{
+		dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+			Exit );
+	}
+	dispatch_main();
+	
+exit:
+	ForgetSocket( &sockV4 );
+	ForgetSocket( &sockV6 );
+	dispatch_source_forget( &signalSource );
+	if( err ) exit( 1 );
+}
+
+static int	SocketToPortNumber( SocketRef inSock )
+{
+	OSStatus		err;
+	sockaddr_ip		sip;
+	socklen_t		len;
+	
+	len = (socklen_t) sizeof( sip );
+	err = getsockname( inSock, &sip.sa, &len );
+	err = map_socket_noerr_errno( inSock, err );
+	check_noerr( err );
+	return( err ? -1 : SockAddrGetPort( &sip ) );
+}
+
+static OSStatus	WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+{
+	OSStatus		err;
+	
+	err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
+	require_noerr( err, exit );
+	
+	err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
+	require_noerr( err, exit );
+	
+	err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
+	require_noerr( err, exit );
+	
+	err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
+	require_noerr( err, exit );
+	
+	err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
+	require_noerr( err, exit );
+	
+	err = HTTPHeader_Commit( inHeader );
+	require_noerr( err, exit );
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	SSDPDiscoverPrintPrologue
+//===========================================================================================================================
+
+static void	SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+{
+	const int				receiveSecs = inContext->receiveSecs;
+	const char *			ifName;
+	char					ifNameBuf[ IF_NAMESIZE + 1 ];
+	NetTransportType		ifType;
+	
+	ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+	
+	ifType = kNetTransportType_Undefined;
+	if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+	
+	FPrintF( stdout, "Interface:        %s/%d/%s\n",
+		ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
+	FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
+		inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+	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:       %{du:time}\n", NULL );
+}
+
+//===========================================================================================================================
+//	SSDPDiscoverReadHandler
+//===========================================================================================================================
+
+static void	SSDPDiscoverReadHandler( void *inContext )
+{
+	OSStatus						err;
+	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;
+	
+	gettimeofday( &now, NULL );
+	
+	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: %{du:time}\n",	&now );
+	FPrintF( stdout, "Source:       %##a\n", 		&fromAddr );
+	FPrintF( stdout, "Message size: %zu\n",			msgLen );
+	header->len = msgLen;
+	if( HTTPHeader_Validate( header ) )
+	{
+		FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
+		if( header->extraDataLen > 0 )
+		{
+			FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
+		}
+	}
+	else
+	{
+		FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
+		goto exit;
+	}
+	
+exit:
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	HTTPHeader_Validate
+//
+//	Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
+//	This assumes the "buf" and "len" fields are set. The other fields are set by this function.
+//
+//	Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+//===========================================================================================================================
+
+Boolean	HTTPHeader_Validate( HTTPHeader *inHeader )
+{
+	const char *		src;
+	const char *		end;
+	
+	// Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+	
+	require( inHeader->len < sizeof( inHeader->buf ), exit );
+	src = inHeader->buf;
+	end = src + inHeader->len;
+	if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
+	{
+		src += 4;
+	}
+	else
+	{
+		// Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
+		// $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+		
+		for( ;; )
+		{
+			while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
+			if( src >= end ) goto exit;
+			++src;
+			if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
+			{
+				src += 2;
+				break;
+			}
+			else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
+			{
+				src += 1;
+				break;
+			}
+		}
+	}
+	inHeader->extraDataPtr	= src;
+	inHeader->extraDataLen	= (size_t)( end - src );
+	inHeader->len			= (size_t)( src - inHeader->buf );
+	return( true );
+	
+exit:
+	return( false );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//	ResQueryCmd
+//===========================================================================================================================
+
+// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
+SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
+	int,
+	( const char *dname, int class, int type, u_char *answer, int anslen ),
+	( dname, class, type, answer, anslen ) );
+
+// res_query() from libinfo
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
+SOFT_LINK_FUNCTION_EX( info, res_query,
+	int,
+	( const char *dname, int class, int type, u_char *answer, int anslen ),
+	( dname, class, type, answer, anslen ) );
+
+typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+
+static void	ResQueryCmd( void )
+{
+	OSStatus		err;
+	res_query_f		res_query_ptr;
+	int				n;
+	uint16_t		type, class;
+	uint8_t			answer[ 1024 ];
+	
+	// Get pointer to one of the res_query() functions.
+	
+	if( gResQuery_UseLibInfo )
+	{
+		if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+		{
+			FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
+			err = kNotFoundErr;
+			goto exit;
+		}
+		res_query_ptr = soft_res_query;
+	}
+	else
+	{
+		if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+		{
+			FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
+			err = kNotFoundErr;
+			goto exit;
+		}
+		res_query_ptr = soft_res_9_query;
+	}
+	
+	// Get record type.
+	
+	err = RecordTypeFromArgString( gResQuery_Type, &type );
+	require_noerr( err, exit );
+	
+	// Get record class.
+	
+	if( gResQuery_Class )
+	{
+		err = RecordClassFromArgString( gResQuery_Class, &class );
+		require_noerr( err, exit );
+	}
+	else
+	{
+		class = kDNSServiceClass_IN;
+	}
+	
+	// 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: %{du:time}\n",	NULL );
+	FPrintF( stdout, "---\n" );
+	
+	// Call res_query().
+	
+	n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
+	if( n < 0 )
+	{
+		FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+		err = kUnknownErr;
+		goto exit;
+	}
+	
+	// Print result.
+	
+	FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+	
+exit:
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	ResolvDNSQueryCmd
+//===========================================================================================================================
+
+// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
+// avoid including the header file.
+
+typedef void *		dns_handle_t;
+
+SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
+SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
+SOFT_LINK_FUNCTION_EX( resolv, dns_query,
+	int32_t, (
+		dns_handle_t		dns,
+		const char *		name,
+		uint32_t			dnsclass,
+		uint32_t			dnstype,
+		char *				buf,
+		uint32_t			len,
+		struct sockaddr *	from,
+		uint32_t *			fromlen ),
+	( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+
+static void	ResolvDNSQueryCmd( void )
+{
+	OSStatus			err;
+	int					n;
+	dns_handle_t		dns = NULL;
+	uint16_t			type, class;
+	sockaddr_ip			from;
+	uint32_t			fromLen;
+	uint8_t				answer[ 1024 ];
+	
+	// Make sure that the required symbols are available.
+	
+	if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
+	{
+		FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
+		err = kNotFoundErr;
+		goto exit;
+	}
+	
+	if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
+	{
+		FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
+		err = kNotFoundErr;
+		goto exit;
+	}
+	
+	if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
+	{
+		FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
+		err = kNotFoundErr;
+		goto exit;
+	}
+	
+	// Get record type.
+	
+	err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
+	require_noerr( err, exit );
+	
+	// Get record class.
+	
+	if( gResolvDNSQuery_Class )
+	{
+		err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+		require_noerr( err, exit );
+	}
+	else
+	{
+		class = kDNSServiceClass_IN;
+	}
+	
+	// Get dns handle.
+	
+	dns = soft_dns_open( gResolvDNSQuery_Path );
+	if( !dns )
+	{
+		FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
+		err = kUnknownErr;
+		goto exit;
+	}
+	
+	// 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: %{du:time}\n",	NULL );
+	FPrintF( stdout, "---\n" );
+	
+	// Call dns_query().
+	
+	memset( &from, 0, sizeof( from ) );
+	fromLen = (uint32_t) sizeof( from );
+	n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
+		&fromLen );
+	if( n < 0 )
+	{
+		FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+		err = kUnknownErr;
+		goto exit;
+	}
+	
+	// Print result.
+	
+	FPrintF( stdout, "From:         %##a\n", &from );
+	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;
+	
+	// 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;
+	
+	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
+
+//===========================================================================================================================
+//	DaemonVersionCmd
+//===========================================================================================================================
+
+static void	DaemonVersionCmd( void )
+{
+	OSStatus		err;
+	uint32_t		size, version;
+	char			strBuf[ 16 ];
+	
+	size = (uint32_t) sizeof( version );
+	err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
+	require_noerr( err, exit );
+	
+	FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+	
+exit:
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	Exit
+//===========================================================================================================================
+
+static void	Exit( void *inContext )
+{
+	const char * const		reason = (const char *) inContext;
+	
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "End time:   %{du:time}\n", NULL );
+	if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
+	exit( gExitCode );
+}
+
+//===========================================================================================================================
+//	PrintFTimestampHandler
+//===========================================================================================================================
+
+static int
+	PrintFTimestampHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext )
+{
+	struct timeval				now;
+	const struct timeval *		tv;
+	struct tm *					localTime;
+	size_t						len;
+	int							n;
+	char						dateTimeStr[ 32 ];
+	
+	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';
+	
+	n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
+	
+exit:
+	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 );
+}
+
+//===========================================================================================================================
+//	PrintFAddRmvFlagsHandler
+//===========================================================================================================================
+
+static int
+	PrintFAddRmvFlagsHandler(
+		PrintFContext *	inContext,
+		PrintFFormat *	inFormat,
+		PrintFVAList *	inArgs,
+		void *			inUserContext )
+{
+	DNSServiceFlags		flags;
+	int					n;
+	
+	Unused( inUserContext );
+	
+	flags = va_arg( inArgs->args, DNSServiceFlags );
+	require_action_quiet( !inFormat->suppress, exit, n = 0 );
+	
+	n = PrintFCore( inContext, "%08X %s%c%c", flags,
+		( flags & kDNSServiceFlagsAdd )           ? "Add" : "Rmv",
+		( flags & kDNSServiceFlagsMoreComing )    ? '+'   : ' ',
+		( flags & kDNSServiceFlagsExpiredAnswer ) ? '!'   : ' ' );
+	
+exit:
+	return( n );
+}
+
+//===========================================================================================================================
+//	GetDNSSDFlagsFromOpts
+//===========================================================================================================================
+
+static DNSServiceFlags	GetDNSSDFlagsFromOpts( void )
+{
+	DNSServiceFlags		flags;
+	
+	flags = (DNSServiceFlags) gDNSSDFlags;
+	if( flags & kDNSServiceFlagsShareConnection )
+	{
+		FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n",
+			kDNSServiceFlagsShareConnection );
+	}
+	
+	if( gDNSSDFlag_AllowExpiredAnswers )	flags |= kDNSServiceFlagsAllowExpiredAnswers;
+	if( gDNSSDFlag_BrowseDomains )			flags |= kDNSServiceFlagsBrowseDomains;
+	if( gDNSSDFlag_DenyCellular )			flags |= kDNSServiceFlagsDenyCellular;
+	if( gDNSSDFlag_DenyExpensive )			flags |= kDNSServiceFlagsDenyExpensive;
+	if( gDNSSDFlag_ForceMulticast )			flags |= kDNSServiceFlagsForceMulticast;
+	if( gDNSSDFlag_IncludeAWDL )			flags |= kDNSServiceFlagsIncludeAWDL;
+	if( gDNSSDFlag_NoAutoRename )			flags |= kDNSServiceFlagsNoAutoRename;
+	if( gDNSSDFlag_PathEvaluationDone )		flags |= kDNSServiceFlagsPathEvaluationDone;
+	if( gDNSSDFlag_RegistrationDomains )	flags |= kDNSServiceFlagsRegistrationDomains;
+	if( gDNSSDFlag_ReturnIntermediates )	flags |= kDNSServiceFlagsReturnIntermediates;
+	if( gDNSSDFlag_Shared )					flags |= kDNSServiceFlagsShared;
+	if( gDNSSDFlag_SuppressUnusable )		flags |= kDNSServiceFlagsSuppressUnusable;
+	if( gDNSSDFlag_Timeout )				flags |= kDNSServiceFlagsTimeout;
+	if( gDNSSDFlag_UnicastResponse )		flags |= kDNSServiceFlagsUnicastResponse;
+	if( gDNSSDFlag_Unique )					flags |= kDNSServiceFlagsUnique;
+	if( gDNSSDFlag_WakeOnResolve )			flags |= kDNSServiceFlagsWakeOnResolve;
+	
+	return( flags );
+}
+
+//===========================================================================================================================
+//	CreateConnectionFromArgString
+//===========================================================================================================================
+
+static OSStatus
+	CreateConnectionFromArgString(
+		const char *			inString,
+		dispatch_queue_t		inQueue,
+		DNSServiceRef *			outSDRef,
+		ConnectionDesc *		outDesc )
+{
+	OSStatus			err;
+	DNSServiceRef		sdRef = NULL;
+	ConnectionType		type;
+	int32_t				pid = -1;	// Initializing because the analyzer claims pid may be used uninitialized.
+	uint8_t				uuid[ 16 ];
+	
+	if( strcasecmp( inString, kConnectionArg_Normal ) == 0 )
+	{
+		err = DNSServiceCreateConnection( &sdRef );
+		require_noerr( err, exit );
+		type = kConnectionType_Normal;
+	}
+	else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 )
+	{
+		const char * const		pidStr = inString + sizeof_string( kConnectionArgPrefix_PID );
+		
+		err = StringToInt32( pidStr, &pid );
+		if( err )
+		{
+			FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr );
+			err = kParamErr;
+			goto exit;
+		}
+		
+		memset( uuid, 0, sizeof( uuid ) );
+		err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid );
+		if( err )
+		{
+			FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid );
+			goto exit;
+		}
+		type = kConnectionType_DelegatePID;
+	}
+	else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 )
+	{
+		const char * const		uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID );
+		
+		check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) );
+		
+		err = StringToUUID( uuidStr, kSizeCString, false, uuid );
+		if( err )
+		{
+			FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr );
+			err = kParamErr;
+			goto exit;
+		}
+		
+		err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid );
+		if( err )
+		{
+			FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid );
+			goto exit;
+		}
+		type = kConnectionType_DelegateUUID;
+	}
+	else
+	{
+		FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString );
+		err = kParamErr;
+		goto exit;
+	}
+	
+	err = DNSServiceSetDispatchQueue( sdRef, inQueue );
+	require_noerr( err, exit );
+	
+	*outSDRef = sdRef;
+	if( outDesc )
+	{
+		outDesc->type = type;
+		if(      type == kConnectionType_DelegatePID )	outDesc->delegate.pid = pid;
+		else if( type == kConnectionType_DelegateUUID )	memcpy( outDesc->delegate.uuid, uuid, 16 );
+	}
+	sdRef = NULL;
+	
+exit:
+	if( sdRef ) DNSServiceRefDeallocate( sdRef );
+	return( err );
+}
+
+//===========================================================================================================================
+//	InterfaceIndexFromArgString
+//===========================================================================================================================
+
+static OSStatus	InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
+{
+	OSStatus		err;
+	uint32_t		ifIndex;
+	
+	if( inString )
+	{
+		ifIndex = if_nametoindex( inString );
+		if( ifIndex == 0 )
+		{
+			err = StringToUInt32( inString, &ifIndex );
+			if( err )
+			{
+				FPrintF( stderr, "error: Invalid interface value: %s\n", inString );
+				err = kParamErr;
+				goto exit;
+			}
+		}
+	}
+	else
+	{
+		ifIndex	= 0;
+	}
+	
+	*outIndex = ifIndex;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	RecordDataFromArgString
+//===========================================================================================================================
+
+static OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+{
+	OSStatus		err;
+	uint8_t *		dataPtr = NULL;
+	size_t			dataLen;
+	
+	if( 0 ) {}
+	
+	// Domain name
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_Domain );
+		
+		err = StringToDomainName( str, &dataPtr, &dataLen );
+		require_noerr_quiet( err, exit );
+	}
+	
+	// File path
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+	{
+		const char * const		path = inString + sizeof_string( kRDataArgPrefix_File );
+		
+		err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+		require_noerr( err, exit );
+		require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+	}
+	
+	// Hexadecimal string
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_HexString );
+		
+		err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+		require_noerr( err, exit );
+		require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+	}
+	
+	// IPv4 address string
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+		
+		err = StringToARecordData( str, &dataPtr, &dataLen );
+		require_noerr_quiet( err, exit );
+	}
+	
+	// IPv6 address string
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+		
+		err = StringToAAAARecordData( str, &dataPtr, &dataLen );
+		require_noerr_quiet( err, exit );
+	}
+	
+	// SRV record
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_SRV );
+		
+		err = CreateSRVRecordDataFromString( str, &dataPtr, &dataLen );
+		require_noerr( err, exit );
+	}
+	
+	// String with escaped hex and octal bytes
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_String );
+		const char * const		end = str + strlen( str );
+		size_t					copiedLen;
+		size_t					totalLen;
+		Boolean					success;
+		
+		if( str < end )
+		{
+			success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
+			require_action( success, exit, err = kParamErr );
+			require_action( totalLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+			
+			dataLen = totalLen;
+			dataPtr = (uint8_t *) malloc( dataLen );
+			require_action( dataPtr, exit, err = kNoMemoryErr );
+			
+			success = ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL );
+			require_action( success, exit, err = kParamErr );
+			check( copiedLen == dataLen );
+		}
+		else
+		{
+			dataPtr = NULL;
+			dataLen = 0;
+		}
+	}
+	
+	// TXT record
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_TXT );
+		
+		err = CreateTXTRecordDataFromString( str, ',', &dataPtr, &dataLen );
+		require_noerr( err, exit );
+	}
+	
+	// Unrecognized format
+	
+	else
+	{
+		FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
+		err = kParamErr;
+		goto exit;
+	}
+	
+	err = kNoErr;
+	*outDataLen = dataLen;
+	*outDataPtr = dataPtr;
+	dataPtr = NULL;
+	
+exit:
+	FreeNullSafe( dataPtr );
+	return( err );
+}
+
+//===========================================================================================================================
+//	RecordTypeFromArgString
+//===========================================================================================================================
+
+typedef struct
+{
+	uint16_t			value;	// Record type's numeric value.
+	const char *		name;	// Record type's name as a string (e.g., "A", "PTR", "SRV").
+	
+}	RecordType;
+
+static const RecordType		kRecordTypes[] =
+{
+	// Common types.
+	
+	{ kDNSServiceType_A,			"A" },
+	{ kDNSServiceType_AAAA,			"AAAA" },
+	{ kDNSServiceType_PTR,			"PTR" },
+	{ kDNSServiceType_SRV,			"SRV" },
+	{ kDNSServiceType_TXT,			"TXT" },
+	{ kDNSServiceType_CNAME,		"CNAME" },
+	{ kDNSServiceType_SOA,			"SOA" },
+	{ kDNSServiceType_NSEC,			"NSEC" },
+	{ kDNSServiceType_NS,			"NS" },
+	{ kDNSServiceType_MX,			"MX" },
+	{ kDNSServiceType_ANY,			"ANY" },
+	{ kDNSServiceType_OPT,			"OPT" },
+	
+	// Less common types.
+	
+	{ kDNSServiceType_MD,			"MD" },
+	{ kDNSServiceType_NS,			"NS" },
+	{ kDNSServiceType_MD,			"MD" },
+	{ kDNSServiceType_MF,			"MF" },
+	{ kDNSServiceType_MB,			"MB" },
+	{ kDNSServiceType_MG,			"MG" },
+	{ kDNSServiceType_MR,			"MR" },
+	{ kDNSServiceType_NULL,			"NULL" },
+	{ kDNSServiceType_WKS,			"WKS" },
+	{ kDNSServiceType_HINFO,		"HINFO" },
+	{ kDNSServiceType_MINFO,		"MINFO" },
+	{ kDNSServiceType_RP,			"RP" },
+	{ kDNSServiceType_AFSDB,		"AFSDB" },
+	{ kDNSServiceType_X25,			"X25" },
+	{ kDNSServiceType_ISDN,			"ISDN" },
+	{ kDNSServiceType_RT,			"RT" },
+	{ kDNSServiceType_NSAP,			"NSAP" },
+	{ kDNSServiceType_NSAP_PTR,		"NSAP_PTR" },
+	{ kDNSServiceType_SIG,			"SIG" },
+	{ kDNSServiceType_KEY,			"KEY" },
+	{ kDNSServiceType_PX,			"PX" },
+	{ kDNSServiceType_GPOS,			"GPOS" },
+	{ kDNSServiceType_LOC,			"LOC" },
+	{ kDNSServiceType_NXT,			"NXT" },
+	{ kDNSServiceType_EID,			"EID" },
+	{ kDNSServiceType_NIMLOC,		"NIMLOC" },
+	{ kDNSServiceType_ATMA,			"ATMA" },
+	{ kDNSServiceType_NAPTR,		"NAPTR" },
+	{ kDNSServiceType_KX,			"KX" },
+	{ kDNSServiceType_CERT,			"CERT" },
+	{ kDNSServiceType_A6,			"A6" },
+	{ kDNSServiceType_DNAME,		"DNAME" },
+	{ kDNSServiceType_SINK,			"SINK" },
+	{ kDNSServiceType_APL,			"APL" },
+	{ kDNSServiceType_DS,			"DS" },
+	{ kDNSServiceType_SSHFP,		"SSHFP" },
+	{ kDNSServiceType_IPSECKEY,		"IPSECKEY" },
+	{ kDNSServiceType_RRSIG,		"RRSIG" },
+	{ kDNSServiceType_DNSKEY,		"DNSKEY" },
+	{ kDNSServiceType_DHCID,		"DHCID" },
+	{ kDNSServiceType_NSEC3,		"NSEC3" },
+	{ kDNSServiceType_NSEC3PARAM,	"NSEC3PARAM" },
+	{ kDNSServiceType_HIP,			"HIP" },
+	{ kDNSServiceType_SPF,			"SPF" },
+	{ kDNSServiceType_UINFO,		"UINFO" },
+	{ kDNSServiceType_UID,			"UID" },
+	{ kDNSServiceType_GID,			"GID" },
+	{ kDNSServiceType_UNSPEC,		"UNSPEC" },
+	{ kDNSServiceType_TKEY,			"TKEY" },
+	{ kDNSServiceType_TSIG,			"TSIG" },
+	{ kDNSServiceType_IXFR,			"IXFR" },
+	{ kDNSServiceType_AXFR,			"AXFR" },
+	{ kDNSServiceType_MAILB,		"MAILB" },
+	{ kDNSServiceType_MAILA,		"MAILA" }
+};
+
+static OSStatus	RecordTypeFromArgString( const char *inString, uint16_t *outValue )
+{
+	OSStatus						err;
+	int32_t							i32;
+	const RecordType *				type;
+	const RecordType * const		end = kRecordTypes + countof( kRecordTypes );
+	
+	for( type = kRecordTypes; type < end; ++type )
+	{
+		if( strcasecmp( type->name, inString ) == 0 )
+		{
+			*outValue = type->value;
+			return( kNoErr );
+		}
+	}
+	
+	err = StringToInt32( inString, &i32 );
+	require_noerr_quiet( err, exit );
+	require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+	
+	*outValue = (uint16_t) i32;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	RecordClassFromArgString
+//===========================================================================================================================
+
+static OSStatus	RecordClassFromArgString( const char *inString, uint16_t *outValue )
+{
+	OSStatus		err;
+	int32_t			i32;
+	
+	if( strcasecmp( inString, "IN" ) == 0 )
+	{
+		*outValue = kDNSServiceClass_IN;
+		err = kNoErr;
+		goto exit;
+	}
+	
+	err = StringToInt32( inString, &i32 );
+	require_noerr_quiet( err, exit );
+	require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+	
+	*outValue = (uint16_t) i32;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	InterfaceIndexToName
+//===========================================================================================================================
+
+static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
+{
+	switch( inIfIndex )
+	{
+		case kDNSServiceInterfaceIndexAny:
+			strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
+			break;
+		
+		case kDNSServiceInterfaceIndexLocalOnly:
+			strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
+			break;
+		
+		case kDNSServiceInterfaceIndexUnicast:
+			strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
+			break;
+		
+		case kDNSServiceInterfaceIndexP2P:
+			strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
+			break;
+		
+	#if( defined( kDNSServiceInterfaceIndexBLE ) )
+		case kDNSServiceInterfaceIndexBLE:
+			strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
+			break;
+	#endif
+		
+		default:
+		{
+			const char *		name;
+			
+			name = if_indextoname( inIfIndex, inNameBuf );
+			if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
+			break;
+		}
+	}
+	
+	return( inNameBuf );
+}
+
+//===========================================================================================================================
+//	RecordTypeToString
+//===========================================================================================================================
+
+static const char *	RecordTypeToString( unsigned int inValue )
+{
+	const RecordType *				type;
+	const RecordType * const		end = kRecordTypes + countof( kRecordTypes );
+	
+	for( type = kRecordTypes; type < end; ++type )
+	{
+		if( type->value == inValue ) return( type->name );
+	}
+	return( "???" );
+}
+
+//===========================================================================================================================
+//	DNSMessageExtractDomainName
+//===========================================================================================================================
+
+static OSStatus
+	DNSMessageExtractDomainName(
+		const uint8_t *		inMsgPtr,
+		size_t				inMsgLen,
+		const uint8_t *		inNamePtr,
+		uint8_t				inBuf[ kDomainNameLengthMax ],
+		const uint8_t **	outNextPtr )
+{
+	OSStatus					err;
+	const uint8_t *				label;
+	uint8_t						labelLen;
+	const uint8_t *				nextLabel;
+	const uint8_t * const		msgEnd	= inMsgPtr + inMsgLen;
+	uint8_t *					dst		= inBuf;
+	const uint8_t * const		dstLim	= inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL;
+	const uint8_t *				nameEnd	= NULL;
+	
+	require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
+	
+	for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+	{
+		if( labelLen <= kDomainLabelLengthMax )
+		{
+			nextLabel = label + 1 + labelLen;
+			require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
+			if( dst )
+			{
+				require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr );
+				memcpy( dst, label, 1 + labelLen );
+				dst += ( 1 + labelLen );
+			}
+		}
+		else if( IsCompressionByte( labelLen ) )
+		{
+			uint16_t		offset;
+			
+			require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr );
+			if( !nameEnd )
+			{
+				nameEnd = label + 2;
+				if( !dst ) break;
+			}
+			offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] );
+			nextLabel = inMsgPtr + offset;
+			require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
+			require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr );
+		}
+		else
+		{
+			dlogassert( "Unhandled label length 0x%02X\n", labelLen );
+			err = kMalformedErr;
+			goto exit;
+		}
+	}
+	
+	if( dst ) *dst = 0;
+	if( !nameEnd ) nameEnd = label + 1;
+	
+	if( outNextPtr ) *outNextPtr = nameEnd;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSMessageExtractDomainNameString
+//===========================================================================================================================
+
+static OSStatus
+	DNSMessageExtractDomainNameString(
+		const void *		inMsgPtr,
+		size_t				inMsgLen,
+		const void *		inNamePtr,
+		char				inBuf[ kDNSServiceMaxDomainName ],
+		const uint8_t **	outNextPtr )
+{
+	OSStatus			err;
+	const uint8_t *		nextPtr;
+	uint8_t				domainName[ kDomainNameLengthMax ];
+	
+	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr );
+	require_noerr( err, exit );
+	
+	err = DomainNameToString( domainName, NULL, inBuf, NULL );
+	require_noerr( err, exit );
+	
+	if( outNextPtr ) *outNextPtr = nextPtr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSMessageExtractQuestion
+//===========================================================================================================================
+
+static OSStatus
+	DNSMessageExtractQuestion(
+		const uint8_t *		inMsgPtr,
+		size_t				inMsgLen,
+		const uint8_t *		inPtr,
+		uint8_t				inNameBuf[ kDomainNameLengthMax ],
+		uint16_t *			outType,
+		uint16_t *			outClass,
+		const uint8_t **	outPtr )
+{
+	OSStatus							err;
+	const uint8_t * const				msgEnd = &inMsgPtr[ inMsgLen ];
+	const uint8_t *						ptr;
+	const DNSQuestionFixedFields *		fields;
+	
+	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
+	require_noerr_quiet( err, exit );
+	require_action_quiet( (size_t)( msgEnd - ptr ) >= sizeof( DNSQuestionFixedFields ), exit, err = kUnderrunErr );
+	
+	fields = (const DNSQuestionFixedFields *) ptr;
+	if( outType )  *outType  = DNSQuestionFixedFieldsGetType( fields );
+	if( outClass ) *outClass = DNSQuestionFixedFieldsGetClass( fields );
+	if( outPtr )   *outPtr   = (const uint8_t *) &fields[ 1 ];
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSMessageExtractRecord
+//===========================================================================================================================
+
+typedef struct
+{
+	uint8_t		type[ 2 ];
+	uint8_t		class[ 2 ];
+	uint8_t		ttl[ 4 ];
+	uint8_t		rdLength[ 2 ];
+	uint8_t		rdata[ 1 ];
+	
+}	DNSRecordFields;
+
+check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
+
+static OSStatus
+	DNSMessageExtractRecord(
+		const uint8_t *		inMsgPtr,
+		size_t				inMsgLen,
+		const uint8_t *		inPtr,
+		uint8_t				inNameBuf[ kDomainNameLengthMax ],
+		uint16_t *			outType,
+		uint16_t *			outClass,
+		uint32_t *			outTTL,
+		const uint8_t **	outRDataPtr,
+		size_t *			outRDataLen,
+		const uint8_t **	outPtr )
+{
+	OSStatus					err;
+	const uint8_t * const		msgEnd = inMsgPtr + inMsgLen;
+	const uint8_t *				ptr;
+	const DNSRecordFields *		record;
+	size_t						rdLength;
+	
+	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
+	require_noerr_quiet( err, exit );
+	require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr );
+	
+	record = (DNSRecordFields *) ptr;
+	rdLength = ReadBig16( record->rdLength );
+	require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
+	
+	if( outType )		*outType		= ReadBig16( record->type );
+	if( outClass )		*outClass		= ReadBig16( record->class );
+	if( outTTL )		*outTTL			= ReadBig32( record->ttl );
+	if( outRDataPtr )	*outRDataPtr	= record->rdata;
+	if( outRDataLen )	*outRDataLen	= rdLength;
+	if( outPtr )		*outPtr			= record->rdata + rdLength;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSMessageGetAnswerSection
+//===========================================================================================================================
+
+static OSStatus	DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
+{
+	OSStatus				err;
+	unsigned int			questionCount, i;
+	const DNSHeader *		hdr;
+	const uint8_t *			ptr;
+	
+	require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+	
+	hdr = (DNSHeader *) inMsgPtr;
+	questionCount = DNSHeaderGetQuestionCount( hdr );
+	
+	ptr = (const uint8_t *) &hdr[ 1 ];
+	for( i = 0; i < questionCount; ++i )
+	{
+		err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, NULL, NULL, NULL, &ptr );
+		require_noerr( err, exit );
+	}
+	
+	if( outPtr ) *outPtr = ptr;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSRecordDataToString
+//===========================================================================================================================
+
+static OSStatus
+	DNSRecordDataToString(
+		const void *	inRDataPtr,
+		size_t			inRDataLen,
+		unsigned int	inRDataType,
+		const void *	inMsgPtr,
+		size_t			inMsgLen,
+		char **			outString )
+{
+	OSStatus					err;
+	const uint8_t * const		rdataPtr = (uint8_t *) inRDataPtr;
+	const uint8_t * const		rdataEnd = rdataPtr + inRDataLen;
+	char *						rdataStr;
+	const uint8_t *				ptr;
+	int							n;
+	char						domainNameStr[ kDNSServiceMaxDomainName ];
+	
+	rdataStr = NULL;
+	if( inRDataType == kDNSServiceType_A )
+	{
+		require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
+		
+		ASPrintF( &rdataStr, "%.4a", rdataPtr );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	else if( inRDataType == kDNSServiceType_AAAA )
+	{
+		require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+		
+		ASPrintF( &rdataStr, "%.16a", rdataPtr );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
+			( inRDataType == kDNSServiceType_NS ) )
+	{
+		if( inMsgPtr )
+		{
+			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
+			require_noerr( err, exit );
+		}
+		else
+		{
+			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
+			require_noerr( err, exit );
+		}
+		
+		rdataStr = strdup( domainNameStr );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	else if( inRDataType == kDNSServiceType_SRV )
+	{
+		const SRVRecordDataFixedFields *		fields;
+		const uint8_t *							target;
+		unsigned int							priority, weight, port;
+		
+		require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr );
+		
+		fields = (const SRVRecordDataFixedFields *) rdataPtr;
+		SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port );
+		target = (const uint8_t *) &fields[ 1 ];
+		
+		if( inMsgPtr )
+		{
+			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
+			require_noerr( err, exit );
+		}
+		else
+		{
+			err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
+			require_noerr( err, exit );
+		}
+		
+		ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	else if( inRDataType == kDNSServiceType_TXT )
+	{
+		require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
+		
+		if( inRDataLen == 1 )
+		{
+			ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
+			require_action( rdataStr, exit, err = kNoMemoryErr );
+		}
+		else
+		{
+			ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
+			require_action( rdataStr, exit, err = kNoMemoryErr );
+		}
+	}
+	else if( inRDataType == kDNSServiceType_SOA )
+	{
+		uint32_t		serial, refresh, retry, expire, minimum;
+		
+		if( inMsgPtr )
+		{
+			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+			require_noerr( err, exit );
+			
+			require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+			
+			rdataStr = strdup( domainNameStr );
+			require_action( rdataStr, exit, err = kNoMemoryErr );
+			
+			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
+			require_noerr( err, exit );
+		}
+		else
+		{
+			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+			require_noerr( err, exit );
+			
+			rdataStr = strdup( domainNameStr );
+			require_action( rdataStr, exit, err = kNoMemoryErr );
+			
+			err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+			require_noerr( err, exit );
+		}
+		
+		require_action_quiet( ( rdataEnd - ptr ) == sizeof( SOARecordDataFixedFields ), exit, err = kMalformedErr );
+		
+		SOARecordDataFixedFieldsGet( (const SOARecordDataFixedFields *) ptr, &serial, &refresh, &retry, &expire, &minimum );
+		
+		n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
+		require_action( n > 0, exit, err = kUnknownErr );
+	}
+	else if( inRDataType == kDNSServiceType_NSEC )
+	{
+		unsigned int		windowBlock, bitmapLen, i, recordType;
+		const uint8_t *		bitmapPtr;
+		
+		if( inMsgPtr )
+		{
+			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+			require_noerr( err, exit );
+		}
+		else
+		{
+			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+			require_noerr( err, exit );
+		}
+		
+		require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+		
+		rdataStr = strdup( domainNameStr );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+		
+		for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
+		{
+			require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
+			
+			windowBlock	=  ptr[ 0 ];
+			bitmapLen	=  ptr[ 1 ];
+			bitmapPtr	= &ptr[ 2 ];
+			
+			require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
+			require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
+			
+			for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
+			{
+				if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
+				{
+					recordType = ( windowBlock * 256 ) + i;
+					n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
+					require_action( n > 0, exit, err = kUnknownErr );
+				}
+			}
+		}
+	}
+	else if( inRDataType == kDNSServiceType_MX )
+	{
+		uint16_t			preference;
+		const uint8_t *		exchange;
+		
+		require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
+		
+		preference	= ReadBig16( rdataPtr );
+		exchange	= &rdataPtr[ 2 ];
+		
+		if( inMsgPtr )
+		{
+			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
+			require_noerr( err, exit );
+		}
+		else
+		{
+			err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
+			require_noerr( err, exit );
+		}
+		
+		n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
+		require_action( n > 0, exit, err = kUnknownErr );
+	}
+	else
+	{
+		err = kNotHandledErr;
+		goto exit;
+	}
+	
+	check( rdataStr );
+	*outString = rdataStr;
+	rdataStr = NULL;
+	err = kNoErr;
+	
+exit:
+	FreeNullSafe( rdataStr );
+	return( err );
+}
+
+//===========================================================================================================================
+//	DomainNameAppendString
+//===========================================================================================================================
+
+static OSStatus
+	DomainNameAppendString(
+		uint8_t			inDomainName[ kDomainNameLengthMax ],
+		const char *	inString,
+		uint8_t **		outEndPtr )
+{
+	OSStatus					err;
+	const char *				src;
+	uint8_t *					root;
+	const uint8_t * const		nameLim = inDomainName + kDomainNameLengthMax;
+	
+	for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
+	require_action_quiet( root < nameLim, exit, err = kMalformedErr );
+	
+	// If the string is a single dot, denoting the root domain, then there are no non-empty labels.
+	
+	src = inString;
+	if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src;
+	while( *src )
+	{
+		uint8_t * const				label		= root;
+		const uint8_t * const		labelLim	= Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
+		uint8_t *					dst;
+		int							c;
+		size_t						labelLen;
+		
+		dst = &label[ 1 ];
+		while( *src && ( ( c = *src++ ) != '.' ) )
+		{
+			if( c == '\\' )
+			{
+				require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+				c = *src++;
+				if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
+				{
+					const int		decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
+					
+					if( decimal <= 255 )
+					{
+						c = decimal;
+						src += 2;
+					}
+				}
+			}
+			require_action_quiet( dst < labelLim, exit, err = kOverrunErr );
+			*dst++ = (uint8_t) c;
+		}
+		
+		labelLen = (size_t)( dst - &label[ 1 ] );
+		require_action_quiet( labelLen > 0, exit, err = kMalformedErr );
+		
+		label[ 0 ] = (uint8_t) labelLen;
+		root = dst;
+		*root = 0;
+	}
+	
+	if( outEndPtr ) *outEndPtr = root + 1;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DomainNameEqual
+//===========================================================================================================================
+
+static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
+{
+	const uint8_t *		p1 = inName1;
+	const uint8_t *		p2 = inName2;
+	unsigned int		len;
+	
+	for( ;; )
+	{
+		if( ( len = *p1++ ) != *p2++ ) return( false );
+		if( len == 0 ) break;
+		for( ; len > 0; ++p1, ++p2, --len )
+		{
+			if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false );
+		}
+	}
+	return( true );
+}
+
+//===========================================================================================================================
+//	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 );
+}
+
+//===========================================================================================================================
+//	DomainNameDupEx
+//===========================================================================================================================
+
+static OSStatus	DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen )
+{
+	OSStatus			err;
+	uint8_t *			namePtr;
+	const size_t		nameLen = DomainNameLength( inName );
+	
+	if( inLower )
+	{
+		const uint8_t *		src;
+		uint8_t *			dst;
+		unsigned int		len;
+		
+		namePtr = (uint8_t *) malloc( nameLen );
+		require_action( namePtr, exit, err = kNoMemoryErr );
+		
+		src = inName;
+		dst = namePtr;
+		while( ( len = *src ) != 0 )
+		{
+			*dst++ = *src++;
+			while( len-- )
+			{
+				*dst++ = (uint8_t) tolower_safe( *src );
+				++src;
+			}
+		}
+		*dst = 0;
+	}
+	else
+	{
+		namePtr = (uint8_t *) memdup( inName, nameLen );
+		require_action( namePtr, exit, err = kNoMemoryErr );
+	}
+	
+	*outNamePtr = namePtr;
+	if( outNameLen ) *outNameLen = nameLen;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DomainNameFromString
+//===========================================================================================================================
+
+static OSStatus
+	DomainNameFromString(
+		uint8_t			inDomainName[ kDomainNameLengthMax ],
+		const char *	inString,
+		uint8_t **		outEndPtr )
+{
+	inDomainName[ 0 ] = 0;
+	return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+}
+
+//===========================================================================================================================
+//	DomainNameToString
+//===========================================================================================================================
+
+static OSStatus
+	DomainNameToString(
+		const uint8_t *		inDomainName,
+		const uint8_t *		inEnd,
+		char				inBuf[ kDNSServiceMaxDomainName ],
+		const uint8_t **	outNextPtr )
+{
+	OSStatus			err;
+	const uint8_t *		label;
+	uint8_t				labelLen;
+	const uint8_t *		nextLabel;
+	char *				dst;
+	const uint8_t *		src;
+	
+	require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr );
+	
+	// Convert each label up until the root label, i.e., the zero-length label.
+	
+	dst = inBuf;
+	for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+	{
+		require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr );
+		
+		nextLabel = &label[ 1 ] + labelLen;
+		require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr );
+		require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr );
+		
+		for( src = &label[ 1 ]; src < nextLabel; ++src )
+		{
+			if( isprint_safe( *src ) )
+			{
+				if( ( *src == '.' ) || ( *src == '\\' ) ||  ( *src == ' ' ) ) *dst++ = '\\';
+				*dst++ = (char) *src;
+			}
+			else
+			{
+				*dst++ = '\\';
+				*dst++ = '0' + (   *src / 100 );
+				*dst++ = '0' + ( ( *src /  10 ) % 10 );
+				*dst++ = '0' + (   *src         % 10 );
+			}
+		}
+		*dst++ = '.';
+	}
+	
+	// At this point, label points to the root label.
+	// If the root label was the only label, then write a dot for it.
+	
+	if( label == inDomainName ) *dst++ = '.';
+	*dst = '\0';
+	if( outNextPtr ) *outNextPtr = label + 1;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSMessageToText
+//===========================================================================================================================
+
+#define DNSFlagsOpCodeToString( X ) (					\
+	( (X) == kDNSOpCode_Query )			? "Query"	:	\
+	( (X) == kDNSOpCode_InverseQuery )	? "IQuery"	:	\
+	( (X) == kDNSOpCode_Status )		? "Status"	:	\
+	( (X) == kDNSOpCode_Notify )		? "Notify"	:	\
+	( (X) == kDNSOpCode_Update )		? "Update"	:	\
+										  "Unassigned" )
+
+#define DNSFlagsRCodeToString( X ) (						\
+	( (X) == kDNSRCode_NoError )		? "NoError"		:	\
+	( (X) == kDNSRCode_FormatError )	? "FormErr"		:	\
+	( (X) == kDNSRCode_ServerFailure )	? "ServFail"	:	\
+	( (X) == kDNSRCode_NXDomain )		? "NXDomain"	:	\
+	( (X) == kDNSRCode_NotImplemented )	? "NotImp"		:	\
+	( (X) == kDNSRCode_Refused )		? "Refused"		:	\
+										  "???" )
+
+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 *			ptr;
+	unsigned int			id, flags, opcode, rcode;
+	unsigned int			questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
+	uint8_t					name[ kDomainNameLengthMax ];
+	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;
+	id				= DNSHeaderGetID( hdr );
+	flags			= DNSHeaderGetFlags( hdr );
+	questionCount	= DNSHeaderGetQuestionCount( hdr );
+	answerCount		= DNSHeaderGetAnswerCount( hdr );
+	authorityCount	= DNSHeaderGetAuthorityCount( hdr );
+	additionalCount	= DNSHeaderGetAdditionalCount( hdr );
+	opcode			= DNSFlagsGetOpCode( flags );
+	rcode			= DNSFlagsGetRCode( flags );
+	
+	_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 ) );
+	_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 = (const uint8_t *) &hdr[ 1 ];
+	for( i = 0; i < questionCount; ++i )
 	{
-		err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+		uint16_t		qtype, qclass;
+		Boolean			isQU;
+		
+		err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, name, &qtype, &qclass, &ptr );
 		require_noerr( err, exit );
 		
-		err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+		err = DomainNameToString( name, NULL, nameStr, NULL );
 		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 );
+		isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
+		if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
+		
+		if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
+		
+		_Append( "%-30s %2s %?2s%?2u %-5s\n",
+			nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
+			( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
+	}
+	
+	totalRRCount = answerCount + authorityCount + additionalCount;
+	for( i = 0; i < totalRRCount; ++i )
+	{
+		uint16_t			type;
+		uint16_t			class;
+		uint32_t			ttl;
+		const uint8_t *		rdataPtr;
+		size_t				rdataLen;
+		char *				rdataStr;
+		Boolean				cacheFlush;
+		
+		err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
+		require_noerr( err, exit );
+		
+		err = DomainNameToString( name, NULL, nameStr, NULL );
 		require_noerr( err, exit );
+		
+		cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
+		if( inMDNS ) class &= ~kRRClassCacheFlushBit;
+		
+		rdataStr = NULL;
+		if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
+		if( !rdataStr )
+		{
+			ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX );
+			require_action( rdataStr, exit, err = kNoMemoryErr );
+		}
+		
+		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" );
+		
+		_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 );
 	}
+	_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 );
+}
+
+//===========================================================================================================================
+//	WriteDNSQueryMessage
+//===========================================================================================================================
+
+static OSStatus
+	WriteDNSQueryMessage(
+		uint8_t			inMsg[ kDNSQueryMessageMaxLen ],
+		uint16_t		inMsgID,
+		uint16_t		inFlags,
+		const char *	inQName,
+		uint16_t		inQType,
+		uint16_t		inQClass,
+		size_t *		outMsgLen )
+{
+	OSStatus				err;
+	DNSHeader * const		hdr = (DNSHeader *) inMsg;
+	uint8_t *				ptr;
+	size_t					msgLen;
+	
+	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 );
+	
+	DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
+	ptr += 4;
+	
+	msgLen = (size_t)( ptr - inMsg );
+	check( msgLen <= kDNSQueryMessageMaxLen );
+	
+	if( outMsgLen ) *outMsgLen = msgLen;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DispatchSignalSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+	DispatchSignalSourceCreate(
+		int					inSignal,
+		DispatchHandler		inEventHandler,
+		void *				inContext,
+		dispatch_source_t *	outSource )
+{
+	OSStatus				err;
+	dispatch_source_t		source;
+	
+	source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
+	require_action( source, exit, err = kUnknownErr );
+	
+	dispatch_set_context( source, inContext );
+	dispatch_source_set_event_handler_f( source, inEventHandler );
+	
+	*outSource = source;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DispatchSocketSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+	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( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
+	require_action( source, exit, err = kUnknownErr );
+	
+	dispatch_set_context( source, inContext );
+	dispatch_source_set_event_handler_f( source, inEventHandler );
+	dispatch_source_set_cancel_handler_f( source, inCancelHandler );
+	
+	*outSource = source;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	DispatchTimerCreate
+//===========================================================================================================================
+
+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 )
+{
+	OSStatus				err;
+	dispatch_source_t		timer;
+	
+	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 );
+	dispatch_set_context( timer, inContext );
+	dispatch_source_set_event_handler_f( timer, inEventHandler );
+	dispatch_source_set_cancel_handler_f( timer, inCancelHandler );
+	
+	*outTimer = timer;
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	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;
 	
-	// Print prologue.
+	monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
+		inQueue ? inQueue : dispatch_get_main_queue() );
+	require_action( monitor, exit, err = kUnknownErr );
 	
-	SSDPDiscoverPrintPrologue( context );
+	dispatch_set_context( monitor, inContext );
+	dispatch_source_set_event_handler_f( monitor, inEventHandler );
+	dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
 	
-	// Send mDNS query message.
+	*outMonitor = monitor;
+	err = kNoErr;
 	
-	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;
-		}
-	}
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	ServiceTypeDescription
+//===========================================================================================================================
+
+typedef struct
+{
+	const char *		name;			// Name of the service type in two-label "_service._proto" format.
+	const char *		description;	// Description of the service type.
 	
-	if( IsValidSocket( sockV6 ) )
+}	ServiceType;
+
+// A Non-comprehensive table of DNS-SD service types
+
+static const ServiceType		kServiceTypes[] =
+{
+	{ "_acp-sync._tcp",			"AirPort Base Station Sync" },
+	{ "_adisk._tcp",			"Automatic Disk Discovery" },
+	{ "_afpovertcp._tcp",		"Apple File Sharing" },
+	{ "_airdrop._tcp",			"AirDrop" },
+	{ "_airplay._tcp",			"AirPlay" },
+	{ "_airport._tcp",			"AirPort Base Station" },
+	{ "_daap._tcp",				"Digital Audio Access Protocol (iTunes)" },
+	{ "_eppc._tcp",				"Remote AppleEvents" },
+	{ "_ftp._tcp",				"File Transfer Protocol" },
+	{ "_home-sharing._tcp",		"Home Sharing" },
+	{ "_homekit._tcp",			"HomeKit" },
+	{ "_http._tcp",				"World Wide Web HTML-over-HTTP" },
+	{ "_https._tcp",			"HTTP over SSL/TLS" },
+	{ "_ipp._tcp",				"Internet Printing Protocol" },
+	{ "_ldap._tcp",				"Lightweight Directory Access Protocol" },
+	{ "_mediaremotetv._tcp",	"Media Remote" },
+	{ "_net-assistant._tcp",	"Apple Remote Desktop" },
+	{ "_od-master._tcp",		"OpenDirectory Master" },
+	{ "_nfs._tcp",				"Network File System" },
+	{ "_presence._tcp",			"Peer-to-peer messaging / Link-Local Messaging" },
+	{ "_pdl-datastream._tcp",	"Printer Page Description Language Data Stream" },
+	{ "_raop._tcp",				"Remote Audio Output Protocol" },
+	{ "_rfb._tcp",				"Remote Frame Buffer" },
+	{ "_scanner._tcp",			"Bonjour Scanning" },
+	{ "_smb._tcp",				"Server Message Block over TCP/IP" },
+	{ "_sftp-ssh._tcp",			"Secure File Transfer Protocol over SSH" },
+	{ "_sleep-proxy._udp",		"Sleep Proxy Server" },
+	{ "_ssh._tcp",				"SSH Remote Login Protocol" },
+	{ "_teleport._tcp",			"teleport" },
+	{ "_tftp._tcp",				"Trivial File Transfer Protocol" },
+	{ "_workstation._tcp",		"Workgroup Manager" },
+	{ "_webdav._tcp",			"World Wide Web Distributed Authoring and Versioning (WebDAV)" },
+	{ "_webdavs._tcp",			"WebDAV over SSL/TLS" }
+};
+
+static const char *	ServiceTypeDescription( const char *inName )
+{
+	const ServiceType *				serviceType;
+	const ServiceType * const		end = kServiceTypes + countof( kServiceTypes );
+	
+	for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
 	{
-		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( ( stricmp_prefix( inName, serviceType->name ) == 0 ) )
 		{
-			if( gSSDPDiscover_Verbose )
+			const size_t		len = strlen( serviceType->name );
+			
+			if( ( inName[ len ] == '\0' ) || ( strcmp( &inName[ len ], "." ) == 0 ) )
 			{
-				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 );
+				return( serviceType->description );
 			}
-			++sendCount;
 		}
 	}
-	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+	return( NULL );
+}
+
+//===========================================================================================================================
+//	SocketContextCreate
+//===========================================================================================================================
+
+static OSStatus	SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+{
+	OSStatus			err;
+	SocketContext *		context;
 	
-	// If there's no wait period after the send, then exit.
+	context = (SocketContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
 	
-	if( context->receiveSecs == 0 ) goto exit;
+	context->refCount		= 1;
+	context->sock			= inSock;
+	context->userContext	= inUserContext;
 	
-	// Create dispatch read sources for socket(s).
+	*outContext = context;
+	err = kNoErr;
 	
-	if( IsValidSocket( sockV4 ) )
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	SocketContextRetain
+//===========================================================================================================================
+
+static SocketContext *	SocketContextRetain( SocketContext *inContext )
+{
+	++inContext->refCount;
+	return( inContext );
+}
+
+//===========================================================================================================================
+//	SocketContextRelease
+//===========================================================================================================================
+
+static void	SocketContextRelease( SocketContext *inContext )
+{
+	if( --inContext->refCount == 0 )
 	{
-		SocketContext *		sockCtx;
-		
-		err = SocketContextCreate( sockV4, context, &sockCtx );
-		require_noerr( err, exit );
-		sockV4 = kInvalidSocketRef;
-		
-		err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
-			&context->readSourceV4 );
-		if( err ) ForgetSocketContext( &sockCtx );
-		require_noerr( err, exit );
-		
-		dispatch_resume( context->readSourceV4 );
+		ForgetSocket( &inContext->sock );
+		free( inContext );
 	}
+}
+
+//===========================================================================================================================
+//	SocketContextCancelHandler
+//===========================================================================================================================
+
+static void	SocketContextCancelHandler( void *inContext )
+{
+	SocketContextRelease( (SocketContext *) inContext );
+}
+
+//===========================================================================================================================
+//	StringToInt32
+//===========================================================================================================================
+
+static OSStatus	StringToInt32( const char *inString, int32_t *outValue )
+{
+	OSStatus		err;
+	long			value;
+	char *			endPtr;
 	
-	if( IsValidSocket( sockV6 ) )
-	{
-		SocketContext *		sockCtx;
-		
-		err = SocketContextCreate( sockV6, context, &sockCtx );
-		require_noerr( err, exit );
-		sockV6 = kInvalidSocketRef;
-		
-		err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
-			&context->readSourceV6 );
-		if( err ) ForgetSocketContext( &sockCtx );
-		require_noerr( err, exit );
-		
-		dispatch_resume( context->readSourceV6 );
-	}
+	value = strtol( inString, &endPtr, 0 );
+	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+	require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr );
 	
-	if( context->receiveSecs > 0 )
-	{
-		dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-			Exit );
-	}
-	dispatch_main();
+	*outValue = (int32_t) value;
+	err = kNoErr;
 	
-exit:
-	ForgetSocket( &sockV4 );
-	ForgetSocket( &sockV6 );
-	dispatch_source_forget( &signalSource );
-	if( err ) exit( 1 );
+exit:
+	return( err );
 }
 
-static int	SocketToPortNumber( SocketRef inSock )
+//===========================================================================================================================
+//	StringToUInt32
+//===========================================================================================================================
+
+static OSStatus	StringToUInt32( const char *inString, uint32_t *outValue )
 {
 	OSStatus		err;
-	sockaddr_ip		sip;
-	socklen_t		len;
+	uint32_t		value;
+	char *			endPtr;
 	
-	len = (socklen_t) sizeof( sip );
-	err = getsockname( inSock, &sip.sa, &len );
-	err = map_socket_noerr_errno( inSock, err );
-	check_noerr( err );
-	return( err ? -1 : SockAddrGetPort( &sip ) );
+	value = (uint32_t) strtol( inString, &endPtr, 0 );
+	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+	
+	*outValue = value;
+	err = kNoErr;
+	
+exit:
+	return( err );
 }
 
-static OSStatus	WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//	StringToPID
+//===========================================================================================================================
+
+static OSStatus	StringToPID( const char *inString, pid_t *outPID )
 {
 	OSStatus		err;
+	long long		value;
+	char *			endPtr;
 	
-	err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
-	require_noerr( err, exit );
+	set_errno_compat( 0 );
+	value = strtoll( inString, &endPtr, 0 );
+	err = errno_compat();
+	require_noerr_quiet( err, exit );
+	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kMalformedErr );
+	require_action_quiet( value == (pid_t) value, exit, err = kRangeErr );
 	
-	err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
-	require_noerr( err, exit );
+	*outPID = (pid_t) value;
+	err = kNoErr;
 	
-	err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
-	require_noerr( err, exit );
+exit:
+	return( err );
+}
+#endif
+
+//===========================================================================================================================
+//	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;
 	
-	err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
-	require_noerr( err, exit );
+	addrPtr = (uint32_t *) malloc( addrLen );
+	require_action( addrPtr, exit, err = kNoMemoryErr );
 	
-	err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
-	require_noerr( err, exit );
+	err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
+		NULL, NULL, NULL, &end );
+	if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+	require_noerr_quiet( err, exit );
 	
-	err = HTTPHeader_Commit( inHeader );
-	require_noerr( err, exit );
+	*addrPtr = HostToBig32( *addrPtr );
+	
+	*outPtr = (uint8_t *) addrPtr;
+	addrPtr = NULL;
+	*outLen = addrLen;
 	
 exit:
+	FreeNullSafe( addrPtr );
 	return( err );
 }
 
 //===========================================================================================================================
-//	SSDPDiscoverPrintPrologue
+//	StringToAAAARecordData
 //===========================================================================================================================
 
-static void	SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+static OSStatus	StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
 {
-	const int				receiveSecs = inContext->receiveSecs;
-	const char *			ifName;
-	char					ifNameBuf[ IF_NAMESIZE + 1 ];
-	NetTransportType		ifType;
+	OSStatus			err;
+	uint8_t *			addrPtr;
+	const size_t		addrLen = 16;
+	const char *		end;
 	
-	ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+	addrPtr = (uint8_t *) malloc( addrLen );
+	require_action( addrPtr, exit, err = kNoMemoryErr );
 	
-	ifType = kNetTransportType_Undefined;
-	if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+	err = StringToIPv6Address( inString,
+		kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+		addrPtr, NULL, NULL, NULL, &end );
+	if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+	require_noerr_quiet( err, exit );
 	
-	FPrintF( stdout, "Interface:        %s/%d/%s\n",
-		ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
-	FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
-		inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
-	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:       %{du:time}\n", NULL );
+	*outPtr = addrPtr;
+	addrPtr = NULL;
+	*outLen = addrLen;
+	
+exit:
+	FreeNullSafe( addrPtr );
+	return( err );
 }
 
 //===========================================================================================================================
-//	SSDPDiscoverReadHandler
+//	StringToDomainName
 //===========================================================================================================================
 
-static void	SSDPDiscoverReadHandler( void *inContext )
+static OSStatus	StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
 {
-	OSStatus						err;
-	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;
+	OSStatus		err;
+	uint8_t *		namePtr;
+	size_t			nameLen;
+	uint8_t *		end;
+	uint8_t			nameBuf[ kDomainNameLengthMax ];
 	
-	gettimeofday( &now, NULL );
+	err = DomainNameFromString( nameBuf, inString, &end );
+	require_noerr_quiet( err, exit );
 	
-	err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
-		NULL, NULL, NULL, NULL );
-	require_noerr( err, exit );
+	nameLen = (size_t)( end - nameBuf );
+	namePtr = memdup( nameBuf, nameLen );
+	require_action( namePtr, exit, err = kNoMemoryErr );
 	
-	FPrintF( stdout, "---\n" );
-	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 ) )
-	{
-		FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
-		if( header->extraDataLen > 0 )
-		{
-			FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
-		}
-	}
-	else
-	{
-		FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
-		goto exit;
-	}
+	*outPtr = namePtr;
+	namePtr = NULL;
+	if( outLen ) *outLen = nameLen;
 	
 exit:
-	if( err ) exit( 1 );
+	return( err );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//	HTTPHeader_Validate
-//
-//	Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
-//	This assumes the "buf" and "len" fields are set. The other fields are set by this function.
-//
-//	Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+//	GetDefaultDNSServer
 //===========================================================================================================================
 
-Boolean	HTTPHeader_Validate( HTTPHeader *inHeader )
+static OSStatus	GetDefaultDNSServer( sockaddr_ip *outAddr )
 {
-	const char *		src;
-	const char *		end;
+	OSStatus				err;
+	dns_config_t *			config;
+	struct sockaddr *		addr;
+	int32_t					i;
 	
-	// Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+	config = dns_configuration_copy();
+	require_action( config, exit, err = kUnknownErr );
 	
-	require( inHeader->len < sizeof( inHeader->buf ), exit );
-	src = inHeader->buf;
-	end = src + inHeader->len;
-	if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
-	{
-		src += 4;
-	}
-	else
+	addr = NULL;
+	for( i = 0; i < config->n_resolver; ++i )
 	{
-		// Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
-		// $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+		const dns_resolver_t * const		resolver = config->resolver[ i ];
 		
-		for( ;; )
+		if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
 		{
-			while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
-			if( src >= end ) goto exit;
-			++src;
-			if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
-			{
-				src += 2;
-				break;
-			}
-			else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
-			{
-				src += 1;
-				break;
-			}
+			addr = resolver->nameserver[ 0 ];
+			break;
 		}
-	}
-	inHeader->extraDataPtr	= src;
-	inHeader->extraDataLen	= (size_t)( end - src );
-	inHeader->len			= (size_t)( src - inHeader->buf );
-	return( true );
+ 	}
+	require_action_quiet( addr, exit, err = kNotFoundErr );
+	
+	SockAddrCopy( addr, outAddr );
+	err = kNoErr;
 	
 exit:
-	return( false );
+	if( config ) dns_configuration_free( config );
+	return( err );
 }
+#endif
 
-#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//	ResQueryCmd
+//	GetMDNSMulticastAddrV4
 //===========================================================================================================================
 
-// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+static void	_MDNSMulticastAddrV4Init( void *inContext );
 
-SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
-SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
-	int,
-	( const char *dname, int class, int type, u_char *answer, int anslen ),
-	( dname, class, type, answer, anslen ) );
+static const struct sockaddr *	GetMDNSMulticastAddrV4( void )
+{
+	static struct sockaddr_in		sMDNSMulticastAddrV4;
+	static dispatch_once_t			sMDNSMulticastAddrV4InitOnce = 0;
+	
+	dispatch_once_f( &sMDNSMulticastAddrV4InitOnce, &sMDNSMulticastAddrV4, _MDNSMulticastAddrV4Init);
+	return( (const struct sockaddr *) &sMDNSMulticastAddrV4 );
+}
 
-// res_query() from libinfo
+static void	_MDNSMulticastAddrV4Init( void *inContext )
+{
+	struct sockaddr_in * const		addr = (struct sockaddr_in *) inContext;
+	
+	memset( addr, 0, sizeof( *addr ) );
+	SIN_LEN_SET( addr );
+	addr->sin_family		= AF_INET;
+	addr->sin_port			= htons( kMDNSPort );
+	addr->sin_addr.s_addr	= htonl( 0xE00000FB );	// The mDNS IPv4 multicast address is 224.0.0.251
+}
 
-SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
-SOFT_LINK_FUNCTION_EX( info, res_query,
-	int,
-	( const char *dname, int class, int type, u_char *answer, int anslen ),
-	( dname, class, type, answer, anslen ) );
+//===========================================================================================================================
+//	GetMDNSMulticastAddrV6
+//===========================================================================================================================
 
-typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+static void	_MDNSMulticastAddrV6Init( void *inContext );
 
-static void	ResQueryCmd( void )
+static const struct sockaddr *	GetMDNSMulticastAddrV6( void )
 {
-	OSStatus		err;
-	res_query_f		res_query_ptr;
-	int				n;
-	uint16_t		type, class;
-	uint8_t			answer[ 1024 ];
+	static struct sockaddr_in6		sMDNSMulticastAddrV6;
+	static dispatch_once_t			sMDNSMulticastAddrV6InitOnce = 0;
+	
+	dispatch_once_f( &sMDNSMulticastAddrV6InitOnce, &sMDNSMulticastAddrV6, _MDNSMulticastAddrV6Init);
+	return( (const struct sockaddr *) &sMDNSMulticastAddrV6 );
+}
+
+static void	_MDNSMulticastAddrV6Init( void *inContext )
+{
+	struct sockaddr_in6 * const		addr = (struct sockaddr_in6 *) inContext;
 	
-	// Get pointer to one of the res_query() functions.
+	memset( addr, 0, sizeof( *addr ) );
+	SIN6_LEN_SET( addr );
+	addr->sin6_family	= AF_INET6;
+	addr->sin6_port		= htons( kMDNSPort );
+	addr->sin6_addr.s6_addr[  0 ] = 0xFF;	// The mDNS IPv6 multicast address is FF02::FB.
+	addr->sin6_addr.s6_addr[  1 ] = 0x02;
+	addr->sin6_addr.s6_addr[ 15 ] = 0xFB;
+}
+
+//===========================================================================================================================
+//	GetAnyMDNSInterface
+//===========================================================================================================================
+
+static OSStatus	GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex )
+{
+	OSStatus					err;
+	struct ifaddrs *			ifaList;
+	const struct ifaddrs *		ifa;
+	const struct ifaddrs *		ifa2;
+	const char *				ifname		= NULL;
+	const unsigned int			checkFlags	= IFF_UP | IFF_MULTICAST | IFF_LOOPBACK | IFF_POINTOPOINT;
+	const unsigned int			wantFlags	= IFF_UP | IFF_MULTICAST;
+	int							wantFamily;
+	NetTransportType			type;
+	
+	ifaList = NULL;
+	err = getifaddrs( &ifaList );
+	err = map_global_noerr_errno( err );
+	require_noerr( err, exit );
 	
-	if( gResQuery_UseLibInfo )
+	for( ifa = ifaList; ifa; ifa = ifa->ifa_next )
 	{
-		if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+		if( ( ifa->ifa_flags & checkFlags ) != wantFlags )	continue;
+		if( !ifa->ifa_addr || !ifa->ifa_name )				continue;
+		if( ( ifa->ifa_addr->sa_family != AF_INET ) &&
+			( ifa->ifa_addr->sa_family != AF_INET6 ) )		continue;
+		
+		err = SocketGetInterfaceInfo( kInvalidSocketRef, ifa->ifa_name, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &type );
+		check_noerr( err );
+		if( err || ( type == kNetTransportType_AWDL ) )		continue;
+		
+		if( !ifname ) ifname = ifa->ifa_name;
+		wantFamily = ( ifa->ifa_addr->sa_family == AF_INET ) ? AF_INET6 : AF_INET;
+		
+		for( ifa2 = ifa->ifa_next; ifa2; ifa2 = ifa2->ifa_next )
 		{
-			FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
-			err = kNotFoundErr;
-			goto exit;
+			if( ( ifa2->ifa_flags & checkFlags ) != wantFlags )	continue;
+			if( !ifa2->ifa_addr || !ifa2->ifa_name )			continue;
+			if( ifa2->ifa_addr->sa_family != wantFamily )		continue;
+			if( strcmp( ifa2->ifa_name, ifa->ifa_name ) == 0 )	break;
 		}
-		res_query_ptr = soft_res_query;
-	}
-	else
-	{
-		if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+		if( ifa2 )
 		{
-			FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
-			err = kNotFoundErr;
-			goto exit;
+			ifname = ifa->ifa_name;
+			break;
 		}
-		res_query_ptr = soft_res_9_query;
 	}
+	require_action_quiet( ifname, exit, err = kNotFoundErr );
 	
-	// Get record type.
+	if( inNameBuf )	strlcpy( inNameBuf, ifname, IF_NAMESIZE + 1 );
+	if( outIndex )	*outIndex = if_nametoindex( ifname );
 	
-	err = RecordTypeFromArgString( gResQuery_Type, &type );
-	require_noerr( err, exit );
+exit:
+	if( ifaList ) freeifaddrs( ifaList );
+	return( err );
+}
+
+//===========================================================================================================================
+//	CreateMulticastSocket
+//===========================================================================================================================
+
+static OSStatus
+	CreateMulticastSocket(
+		const struct sockaddr *	inAddr,
+		int						inPort,
+		const char *			inIfName,
+		uint32_t				inIfIndex,
+		Boolean					inJoin,
+		int *					outPort,
+		SocketRef *				outSock )
+{
+	OSStatus		err;
+	SocketRef		sock	= kInvalidSocketRef;
+	const int		family	= inAddr->sa_family;
+	int				port;
 	
-	// Get record class.
+	require_action_quiet( ( family == AF_INET ) ||( family == AF_INET6 ), exit, err = kUnsupportedErr );
 	
-	if( gResQuery_Class )
+	err = ServerSocketOpen( family, SOCK_DGRAM, IPPROTO_UDP, inPort, &port, kSocketBufferSize_DontSet, &sock );
+	require_noerr_quiet( err, exit );
+	
+	err = SocketSetMulticastInterface( sock, inIfName, inIfIndex );
+	require_noerr_quiet( err, exit );
+	
+	if( family == AF_INET )
 	{
-		err = RecordClassFromArgString( gResQuery_Class, &class );
-		require_noerr( err, exit );
+		err = setsockopt( sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+		err = map_socket_noerr_errno( sock, err );
+		require_noerr_quiet( err, exit );
 	}
 	else
 	{
-		class = kDNSServiceClass_IN;
+		err = setsockopt( sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+		err = map_socket_noerr_errno( sock, err );
+		require_noerr_quiet( err, exit );
 	}
 	
-	// Print prologue.
+	if( inJoin )
+	{
+		err = SocketJoinMulticast( sock, inAddr, inIfName, inIfIndex );
+		require_noerr_quiet( err, exit );
+	}
 	
-	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" );
+	if( outPort ) *outPort = port;
+	*outSock = sock;
+	sock = kInvalidSocketRef;
 	
-	// Call res_query().
+exit:
+	ForgetSocket( &sock );
+	return( err );
+}
+
+//===========================================================================================================================
+//	DecimalTextToUInt32
+//===========================================================================================================================
+
+static OSStatus	DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint32_t *outValue, const char **outPtr )
+{
+	OSStatus			err;
+	uint64_t			value;
+	const char *		ptr = inSrc;
 	
-	n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
-	if( n < 0 )
+	require_action_quiet( ( ptr < inEnd ) && isdigit_safe( *ptr ), exit, err = kMalformedErr );
+	
+	value = (uint64_t)( *ptr++ - '0' );
+	if( value == 0 )
 	{
-		FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
-		err = kUnknownErr;
-		goto exit;
+		if( ( ptr < inEnd ) && isdigit_safe( *ptr ) )
+		{
+			err = kMalformedErr;
+			goto exit;
+		}
+	}
+	else
+	{
+		while( ( ptr < inEnd ) && isdigit_safe( *ptr ) )
+		{
+			value = ( value * 10 ) + (uint64_t)( *ptr++ - '0' );
+			require_action_quiet( value <= UINT32_MAX, exit, err = kRangeErr );
+		}
 	}
 	
-	// Print result.
-	
-	FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+	*outValue = (uint32_t) value;
+	if( outPtr ) *outPtr = ptr;
+	err = kNoErr;
 	
 exit:
-	if( err ) exit( 1 );
+	return( err );
 }
 
 //===========================================================================================================================
-//	ResolvDNSQueryCmd
+//	CheckIntegerArgument
 //===========================================================================================================================
 
-// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
-// avoid including the header file.
+static OSStatus	CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax )
+{
+	if( ( inArgValue >= inMin ) && ( inArgValue <= inMax ) ) return( kNoErr );
+	
+	FPrintF( stderr, "error: Invalid %s: %d. Valid range is [%d, %d].\n", inArgName, inArgValue, inMin, inMax );
+	return( kRangeErr );
+}
 
-typedef void *		dns_handle_t;
+//===========================================================================================================================
+//	CheckDoubleArgument
+//===========================================================================================================================
 
-SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
-SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
-SOFT_LINK_FUNCTION_EX( resolv, dns_query,
-	int32_t, (
-		dns_handle_t		dns,
-		const char *		name,
-		uint32_t			dnsclass,
-		uint32_t			dnstype,
-		char *				buf,
-		uint32_t			len,
-		struct sockaddr *	from,
-		uint32_t *			fromlen ),
-	( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+static OSStatus	CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax )
+{
+	if( ( inArgValue >= inMin ) && ( inArgValue <= inMax ) ) return( kNoErr );
+	
+	FPrintF( stderr, "error: Invalid %s: %.1f. Valid range is [%.1f, %.1f].\n", inArgName, inArgValue, inMin, inMax );
+	return( kRangeErr );
+}
 
-static void	ResolvDNSQueryCmd( void )
+//===========================================================================================================================
+//	CheckRootUser
+//===========================================================================================================================
+
+static OSStatus	CheckRootUser( void )
 {
-	OSStatus			err;
-	int					n;
-	dns_handle_t		dns = NULL;
-	uint16_t			type, class;
-	sockaddr_ip			from;
-	uint32_t			fromLen;
-	uint8_t				answer[ 1024 ];
+	if( geteuid() == 0 ) return( kNoErr );
 	
-	// Make sure that the required symbols are available.
+	FPrintF( stderr, "error: This command must to be run as root.\n" );
+	return( kPermissionErr );
+}
+
+//===========================================================================================================================
+//	SpawnCommand
+//
+//	Note: Based on systemf() from CoreUtils framework.
+//===========================================================================================================================
+
+extern char **		environ;
+
+static OSStatus	SpawnCommand( pid_t *outPID, const char *inFormat, ... )
+{
+	OSStatus		err;
+	va_list			args;
+	char *			command;
+	char *			argv[ 4 ];
+	pid_t			pid;
 	
-	if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
-	{
-		FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
-		err = kNotFoundErr;
-		goto exit;
-	}
+	command = NULL;
+	va_start( args, inFormat );
+	VASPrintF( &command, inFormat, args );
+	va_end( args );
+	require_action( command, exit, err = kUnknownErr );
 	
-	if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
-	{
-		FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
-		err = kNotFoundErr;
-		goto exit;
-	}
+	argv[ 0 ] = "/bin/sh";
+	argv[ 1 ] = "-c";
+	argv[ 2 ] = command;
+	argv[ 3 ] = NULL;
+	err = posix_spawn( &pid, argv[ 0 ], NULL, NULL, argv, environ );
+	free( command );
+	require_noerr_quiet( err, exit );
 	
-	if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
-	{
-		FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
-		err = kNotFoundErr;
-		goto exit;
-	}
+	if( outPID ) *outPID = pid;
 	
-	// Get record type.
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+//	OutputPropertyList
+//===========================================================================================================================
+
+static OSStatus
+	OutputPropertyList(
+		CFPropertyListRef	inPList,
+		OutputFormatType	inType,
+		Boolean				inAppendNewline,
+		const char *		inOutputFilePath )
+{
+	OSStatus		err;
+	CFDataRef		results = NULL;
+	FILE *			file	= NULL;
 	
-	err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
-	require_noerr( err, exit );
+	// Convert plist to a specific format.
 	
-	// Get record class.
+	switch( inType )
+	{
+		case kOutputFormatType_JSON:
+			results = CFCreateJSONData( inPList, kJSONFlags_None, NULL );
+			require_action( results, exit, err = kUnknownErr );
+			break;
+		
+		case kOutputFormatType_XML:
+			results = CFPropertyListCreateData( NULL, inPList, kCFPropertyListXMLFormat_v1_0, 0, NULL );
+			require_action( results, exit, err = kUnknownErr );
+			break;
+		
+		case kOutputFormatType_Binary:
+			results = CFPropertyListCreateData( NULL, inPList, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
+			require_action( results, exit, err = kUnknownErr );
+			break;
+		
+		default:
+			err = kTypeErr;
+			goto exit;
+	}
 	
-	if( gResolvDNSQuery_Class )
+	// Write formatted results to file or stdout.
+	
+	if( inOutputFilePath )
 	{
-		err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+		file = fopen( inOutputFilePath, "wb" );
+		err = map_global_value_errno( file, file );
 		require_noerr( err, exit );
 	}
 	else
 	{
-		class = kDNSServiceClass_IN;
-	}
-	
-	// Get dns handle.
-	
-	dns = soft_dns_open( gResolvDNSQuery_Path );
-	if( !dns )
-	{
-		FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
-		err = kUnknownErr;
-		goto exit;
+		file = stdout;
 	}
 	
-	// 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: %{du:time}\n",	NULL );
-	FPrintF( stdout, "---\n" );
+	err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
+	require_noerr_quiet( err, exit );
 	
-	// Call dns_query().
+	// Write a trailing newline for JSON-formatted results if requested.
 	
-	memset( &from, 0, sizeof( from ) );
-	fromLen = (uint32_t) sizeof( from );
-	n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
-		&fromLen );
-	if( n < 0 )
+	if( ( inType == kOutputFormatType_JSON ) && inAppendNewline )
 	{
-		FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
-		err = kUnknownErr;
-		goto exit;
+		err = WriteANSIFile( file, "\n", 1 );
+		require_noerr_quiet( err, exit );
 	}
 	
-	// Print result.
-	
-	FPrintF( stdout, "From:         %##a\n", &from );
-	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 );
+	if( file && ( file != stdout ) ) fclose( file );
+	CFReleaseNullSafe( results );
+	return( err );
+}
+
+//===========================================================================================================================
+//	DNSRecordFixedFieldsSet
+//===========================================================================================================================
+
+static void
+	DNSRecordFixedFieldsSet(
+		DNSRecordFixedFields *	inFields,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint32_t				inTTL,
+		uint16_t				inRDLength )
+{
+	WriteBig16( inFields->type,		inType );
+	WriteBig16( inFields->class,	inClass );
+	WriteBig32( inFields->ttl,		inTTL );
+	WriteBig16( inFields->rdlength,	inRDLength );
+}
+
+//===========================================================================================================================
+//	SRVRecordDataFixedFieldsGet
+//===========================================================================================================================
+
+static void
+	SRVRecordDataFixedFieldsGet(
+		const SRVRecordDataFixedFields *	inFields,
+		unsigned int *						outPriority,
+		unsigned int *						outWeight,
+		unsigned int *						outPort )
+{
+	if( outPriority )	*outPriority	= ReadBig16( inFields->priority );
+	if( outWeight )		*outWeight		= ReadBig16( inFields->weight );
+	if( outPort )		*outPort		= ReadBig16( inFields->port );
 }
 
 //===========================================================================================================================
-//	CFHostCmd
+//	SRVRecordDataFixedFieldsSet
 //===========================================================================================================================
 
 static void
-	_CFHostResolveCallback(
-		CFHostRef				inHost,
-		CFHostInfoType			inInfoType,
-		const CFStreamError *	inError,
-		void *					inInfo );
+	SRVRecordDataFixedFieldsSet(
+		SRVRecordDataFixedFields *	inFields,
+		uint16_t					inPriority,
+		uint16_t					inWeight,
+		uint16_t					inPort )
+{
+	WriteBig16( inFields->priority,	inPriority );
+	WriteBig16( inFields->weight,	inWeight );
+	WriteBig16( inFields->port,		inPort );
+}
 
-static void	CFHostCmd( void )
+//===========================================================================================================================
+//	SOARecordDataFixedFieldsGet
+//===========================================================================================================================
+
+static void
+	SOARecordDataFixedFieldsGet(
+		const SOARecordDataFixedFields *	inFields,
+		uint32_t *							outSerial,
+		uint32_t *							outRefresh,
+		uint32_t *							outRetry,
+		uint32_t *							outExpire,
+		uint32_t *							outMinimum )
+{
+	if( outSerial )		*outSerial	= ReadBig32( inFields->serial );
+	if( outRefresh )	*outRefresh	= ReadBig32( inFields->refresh );
+	if( outRetry )		*outRetry	= ReadBig32( inFields->retry );
+	if( outExpire )		*outExpire	= ReadBig32( inFields->expire );
+	if( outMinimum )	*outMinimum	= ReadBig32( inFields->minimum );
+}
+
+//===========================================================================================================================
+//	SOARecordDataFixedFieldsSet
+//===========================================================================================================================
+
+static void
+	SOARecordDataFixedFieldsSet(
+		SOARecordDataFixedFields *	inFields,
+		uint32_t					inSerial,
+		uint32_t					inRefresh,
+		uint32_t					inRetry,
+		uint32_t					inExpire,
+		uint32_t					inMinimum )
+{
+	WriteBig32( inFields->serial,	inSerial );
+	WriteBig32( inFields->refresh,	inRefresh );
+	WriteBig32( inFields->retry,	inRetry );
+	WriteBig32( inFields->expire,	inExpire );
+	WriteBig32( inFields->minimum,	inMinimum );
+}
+
+//===========================================================================================================================
+//	CreateSRVRecordDataFromString
+//===========================================================================================================================
+
+static OSStatus	CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen )
 {
-	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 );
+	OSStatus			err;
+	DataBuffer			dataBuf;
+	const char *		ptr;
+	int					i;
+	uint8_t *			end;
+	uint8_t				target[ kDomainNameLengthMax ];
 	
-	host = CFHostCreateWithName( kCFAllocatorDefault, name );
-	ForgetCF( &name );
-	require_action( host, exit, err = kUnknownErr );
+	DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax );
 	
-	memset( &context, 0, sizeof( context ) );
-	success = CFHostSetClient( host, _CFHostResolveCallback, &context );
-	require_action( success, exit, err = kUnknownErr );
+	// Parse and set the priority, weight, and port values (all three are unsigned 16-bit values).
 	
-	CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+	ptr = inString;
+	for( i = 0; i < 3; ++i )
+	{
+		char *		next;
+		long		value;
+		uint8_t		buf[ 2 ];
+		
+		value = strtol( ptr, &next, 0 );
+		require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr );
+		require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr );
+		ptr = next + 1;
+		
+		WriteBig16( buf, value );
+		
+		err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) );
+		require_noerr( err, exit );
+	}
 	
-	// Print prologue.
+	// Set the target domain name.
 	
-	FPrintF( stdout, "Hostname:   %s\n",			gCFHost_Name );
-	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
-	FPrintF( stdout, "---\n" );
+	err = DomainNameFromString( target, ptr, &end );
+    require_noerr_quiet( err, exit );
 	
-	success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
-	require_action( success, exit, err = kUnknownErr );
-	err = kNoErr;
+	err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) );
+	require_noerr( err, exit );
 	
-	CFRunLoopRun();
+	err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+	require_noerr( err, exit );
 	
 exit:
-	CFReleaseNullSafe( host );
-	if( err ) exit( 1 );
+	DataBuffer_Free( &dataBuf );
+	return( err );
 }
 
-static void	_CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+//===========================================================================================================================
+//	CreateTXTRecordDataFromString
+//===========================================================================================================================
+
+static OSStatus	CreateTXTRecordDataFromString(const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen )
 {
 	OSStatus			err;
-	struct timeval		now;
-	
-	gettimeofday( &now, NULL );
+	DataBuffer			dataBuf;
+	const char *		src;
+	uint8_t				txtStr[ 256 ];	// Buffer for single TXT string: 1 length byte + up to 255 bytes of data.
 	
-	Unused( inInfoType );
-	Unused( inInfo );
+	DataBuffer_Init( &dataBuf, NULL, 0, kDNSRecordDataLengthMax );
 	
-	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
+	src = inString;
+	for( ;; )
 	{
-		CFArrayRef					addresses;
-		CFIndex						count, i;
-		CFDataRef					addrData;
-		const struct sockaddr *		sockAddr;
-		Boolean						wasResolved = false;
-		
-		addresses = CFHostGetAddressing( inHost, &wasResolved );
-		check( wasResolved );
+		uint8_t *					dst = &txtStr[ 1 ];
+		const uint8_t * const		lim = &txtStr[ 256 ];
+		int							c;
 		
-		if( addresses )
+		while( *src && ( *src != inDelimiter ) )
 		{
-			count = CFArrayGetCount( addresses );
-			for( i = 0; i < count; ++i )
+			if( ( c = *src++ ) == '\\' )
 			{
-				addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
-				require_noerr( err, exit );
-				
-				sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
-				FPrintF( stdout, "%##a\n", sockAddr );
+				require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+				c = *src++;
 			}
+			require_action_quiet( dst < lim, exit, err = kOverrunErr );
+			*dst++ = (uint8_t) c;
 		}
-		err = kNoErr;
+		txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
+		err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
+		require_noerr( err, exit );
+		
+		if( *src == '\0' ) break;
+		++src;
 	}
 	
-	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "End time:   %{du:time}\n", &now );
-	
-	if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+	err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+	require_noerr( err, exit );
 	
 exit:
-	exit( err ? 1 : 0 );
+	DataBuffer_Free( &dataBuf );
+	return( err );
 }
 
 //===========================================================================================================================
-//	DNSConfigAddCmd
-//
-//	Note: Based on ajn's supplemental test tool.
+//	CreateNSECRecordData
 //===========================================================================================================================
 
-static void	DNSConfigAddCmd( void )
+DECLARE_QSORT_NUMERIC_COMPARATOR( _QSortCmpUnsigned );
+DEFINE_QSORT_NUMERIC_COMPARATOR( unsigned int, _QSortCmpUnsigned )
+
+#define kNSECBitmapMaxLength		32	// 32 bytes (256 bits). See <https://tools.ietf.org/html/rfc4034#section-4.1.2>.
+
+static OSStatus
+	CreateNSECRecordData(
+		const uint8_t *	inNextDomainName,
+		uint8_t **		outPtr,
+		size_t *		outLen,
+		unsigned int	inTypeCount,
+		... )
 {
-	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;
-	}
+	OSStatus			err;
+	va_list				args;
+	DataBuffer			rdataDB;
+	unsigned int *		array	= NULL;
+	unsigned int		i, type, maxBit, currBlock, bitmapLen;
+	uint8_t				fields[ 2 + kNSECBitmapMaxLength ];
+	uint8_t * const		bitmap	= &fields[ 2 ];
 	
-	// Create dictionary.
+	va_start( args, inTypeCount );
+	DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax );
 	
-	dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
-	require_action( dict, exit, err = kNoMemoryErr );
+	// Append Next Domain Name.
 	
-	// Add DNS server IP addresses.
+	err = DataBuffer_Append( &rdataDB, inNextDomainName, DomainNameLength( inNextDomainName ) );
+	require_noerr( err, exit );
 	
-	array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
-	require_action( array, exit, err = kNoMemoryErr );
+	// Append Type Bit Maps.
 	
-	for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+	maxBit = 0;
+	memset( bitmap, 0, kNSECBitmapMaxLength );
+	if( inTypeCount > 0 )
 	{
-		CFStringRef		addrStr;
+		array = (unsigned int *) malloc( inTypeCount * sizeof_element( array ) );
+		require_action( array, exit, err = kNoMemoryErr );
 		
-		addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
-		require_action( addrStr, exit, err = kUnknownErr );
+		for( i = 0; i < inTypeCount; ++i )
+		{
+			type = va_arg( args, unsigned int );
+			require_action_quiet( type <= UINT16_MAX, exit, err = kRangeErr );
+			array[ i ] = type;
+		}
+		qsort( array, inTypeCount, sizeof_element( array ), _QSortCmpUnsigned );
 		
-		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 )
+		currBlock = array[ 0 ] / 256;
+		for( i = 0; i < inTypeCount; ++i )
 		{
-			CFStringRef		domainStr;
+			const unsigned int		block	= array[ i ] / 256;
+			const unsigned int		bit		= array[ i ] % 256;
 			
-			domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
-			require_action( domainStr, exit, err = kUnknownErr );
-			
-			CFArrayAppendValue( array, domainStr );
-			CFRelease( domainStr );
+			if( block != currBlock )
+			{
+				bitmapLen	= BitArray_MaxBytes( maxBit + 1 );
+				fields[ 0 ] = (uint8_t) currBlock;
+				fields[ 1 ] = (uint8_t) bitmapLen;
+				
+				err = DataBuffer_Append( &rdataDB, fields, 2 + bitmapLen );
+				require_noerr( err, exit );
+				
+				maxBit		= 0;
+				currBlock	= block;
+				memset( bitmap, 0, bitmapLen );
+			}
+			BitArray_SetBit( bitmap, bit );
+			if( bit > maxBit ) maxBit = bit;
 		}
 	}
 	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 );
+		currBlock = 0;
 	}
 	
-	// Set dictionary in dynamic store.
+	bitmapLen	= BitArray_MaxBytes( maxBit + 1 );
+	fields[ 0 ] = (uint8_t) currBlock;
+	fields[ 1 ] = (uint8_t) bitmapLen;
 	
-	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
-	err = map_scerror( store );
+	err = DataBuffer_Append( &rdataDB, fields, 2 + bitmapLen );
 	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 );
+	err = DataBuffer_Detach( &rdataDB, outPtr, outLen );
+	require_noerr( err, exit );
 	
 exit:
-	CFReleaseNullSafe( dict );
-	CFReleaseNullSafe( array );
-	CFReleaseNullSafe( store );
-	CFReleaseNullSafe( key );
-	gExitCode = err ? 1 : 0;
+	va_end( args );
+	DataBuffer_Free( &rdataDB );
+	FreeNullSafe( array );
+	return( err );
 }
 
 //===========================================================================================================================
-//	DNSConfigRemoveCmd
+//	AppendSOARecord
 //===========================================================================================================================
 
-static void	DNSConfigRemoveCmd( void )
-{
-	OSStatus				err;
-	SCDynamicStoreRef		store	= NULL;
-	CFStringRef				key		= NULL;
-	Boolean					success;
+static OSStatus
+	_AppendSOARecordData(
+		DataBuffer *	inDB,
+		const uint8_t *	inMName,
+		const uint8_t *	inRName,
+		uint32_t		inSerial,
+		uint32_t		inRefresh,
+		uint32_t		inRetry,
+		uint32_t		inExpire,
+		uint32_t		inMinimumTTL,
+		size_t *		outLen );
+
+static OSStatus
+	AppendSOARecord(
+		DataBuffer *	inDB,
+		const uint8_t *	inNamePtr,
+		size_t			inNameLen,
+		uint16_t		inType,
+		uint16_t		inClass,
+		uint32_t		inTTL,
+		const uint8_t *	inMName,
+		const uint8_t *	inRName,
+		uint32_t		inSerial,
+		uint32_t		inRefresh,
+		uint32_t		inRetry,
+		uint32_t		inExpire,
+		uint32_t		inMinimumTTL,
+		size_t *		outLen )
+{
+	OSStatus		err;
+	size_t			rdataLen;
+	size_t			rdlengthOffset = 0;
+	uint8_t *		rdlengthPtr;
 	
-	if( geteuid() != 0 )
+	if( inDB )
 	{
-		FPrintF( stderr, "error: This command must to be run as root.\n" );
-		err = kIDErr;
-		goto exit;
+		err = _DataBuffer_AppendDNSRecord( inDB, inNamePtr, inNameLen, inType, inClass, inTTL, NULL, 0 );
+		require_noerr( err, exit );
+		
+		rdlengthOffset = DataBuffer_GetLen( inDB ) - 2;
 	}
 	
-	store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
-	err = map_scerror( store );
+	err = _AppendSOARecordData( inDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, &rdataLen );
 	require_noerr( err, exit );
 	
-	key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
-	require_action( key, exit, err = kUnknownErr );
+	if( inDB )
+	{
+		rdlengthPtr = DataBuffer_GetPtr( inDB ) + rdlengthOffset;
+		WriteBig16( rdlengthPtr, rdataLen );
+	}
 	
-	success = SCDynamicStoreRemoveValue( store, key );
-	require_action( success, exit, err = kUnknownErr );
+	if( outLen ) *outLen = inNameLen + sizeof( DNSRecordFixedFields ) + rdataLen;
+	err = kNoErr;
 	
 exit:
-	CFReleaseNullSafe( store );
-	CFReleaseNullSafe( key );
-	gExitCode = err ? 1 : 0;
+	return( err );
 }
-#endif	// TARGET_OS_DARWIN
-
-//===========================================================================================================================
-//	DaemonVersionCmd
-//===========================================================================================================================
 
-static void	DaemonVersionCmd( void )
+static OSStatus
+	_AppendSOARecordData(
+		DataBuffer *	inDB,
+		const uint8_t *	inMName,
+		const uint8_t *	inRName,
+		uint32_t		inSerial,
+		uint32_t		inRefresh,
+		uint32_t		inRetry,
+		uint32_t		inExpire,
+		uint32_t		inMinimumTTL,
+		size_t *		outLen )
 {
-	OSStatus		err;
-	uint32_t		size, version;
-	char			strBuf[ 16 ];
-	
-	size = (uint32_t) sizeof( version );
-	err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
-	require_noerr( err, exit );
+	OSStatus						err;
+	SOARecordDataFixedFields		fields;
+	const size_t					mnameLen = DomainNameLength( inMName );
+	const size_t					rnameLen = DomainNameLength( inRName );
 	
-	FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+	if( inDB )
+	{
+		err = DataBuffer_Append( inDB, inMName, mnameLen );
+		require_noerr( err, exit );
+		
+		err = DataBuffer_Append( inDB, inRName, rnameLen );
+		require_noerr( err, exit );
+		
+		SOARecordDataFixedFieldsSet( &fields, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL );
+		err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+		require_noerr( err, exit );
+	}
+	if( outLen ) *outLen = mnameLen + rnameLen + sizeof( fields );
+	err = kNoErr;
 	
 exit:
-	if( err ) exit( 1 );
+	return( err );
 }
 
 //===========================================================================================================================
-//	Exit
+//	CreateSOARecordData
 //===========================================================================================================================
 
-static void	Exit( void *inContext )
+static OSStatus
+	CreateSOARecordData(
+		const uint8_t *	inMName,
+		const uint8_t *	inRName,
+		uint32_t		inSerial,
+		uint32_t		inRefresh,
+		uint32_t		inRetry,
+		uint32_t		inExpire,
+		uint32_t		inMinimumTTL,
+		uint8_t **		outPtr,
+		size_t *		outLen )
 {
-	const char * const		reason = (const char *) inContext;
+	OSStatus		err;
+	DataBuffer		rdataDB;
 	
-	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "End time:   %{du:time}\n", NULL );
-	if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
-	exit( gExitCode );
+	DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax );
+	
+	err = _AppendSOARecordData( &rdataDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, NULL );
+	require_noerr( err, exit );
+	
+	err = DataBuffer_Detach( &rdataDB, outPtr, outLen );
+	require_noerr( err, exit );
+	
+exit:
+	DataBuffer_Free( &rdataDB );
+	return( err );
 }
 
 //===========================================================================================================================
-//	PrintFTimestampHandler
+//	_DataBuffer_AppendDNSQuestion
 //===========================================================================================================================
 
-static int
-	PrintFTimestampHandler(
-		PrintFContext *	inContext,
-		PrintFFormat *	inFormat,
-		PrintFVAList *	inArgs,
-		void *			inUserContext )
+static OSStatus
+	_DataBuffer_AppendDNSQuestion(
+		DataBuffer *	inDB,
+		const uint8_t *	inNamePtr,
+		size_t			inNameLen,
+		uint16_t		inType,
+		uint16_t		inClass )
 {
-	struct timeval				now;
-	const struct timeval *		tv;
-	struct tm *					localTime;
-	size_t						len;
-	int							n;
-	char						dateTimeStr[ 32 ];
-	
-	Unused( inUserContext );
-	
-	tv = va_arg( inArgs->args, const struct timeval * );
-	require_action_quiet( !inFormat->suppress, exit, n = 0 );
+	OSStatus					err;
+	DNSQuestionFixedFields		fields;
 	
-	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';
+	err = DataBuffer_Append( inDB, inNamePtr, inNameLen );
+	require_noerr( err, exit );
 	
-	n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
+	DNSQuestionFixedFieldsInit( &fields, inType, inClass );
+	err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+	require_noerr( err, exit );
 	
 exit:
-	return( n );
+	return( err );
 }
 
 //===========================================================================================================================
-//	PrintFDNSMessageHandler
+//	_DataBuffer_AppendDNSRecord
 //===========================================================================================================================
 
-static int
-	PrintFDNSMessageHandler(
-		PrintFContext *	inContext,
-		PrintFFormat *	inFormat,
-		PrintFVAList *	inArgs,
-		void *			inUserContext )
+static OSStatus
+	_DataBuffer_AppendDNSRecord(
+		DataBuffer *	inDB,
+		const uint8_t *	inNamePtr,
+		size_t			inNameLen,
+		uint16_t		inType,
+		uint16_t		inClass,
+		uint32_t		inTTL,
+		const uint8_t *	inRDataPtr,
+		size_t			inRDataLen )
 {
-	OSStatus			err;
-	const void *		msgPtr;
-	size_t				msgLen;
-	char *				text;
-	int					n;
-	Boolean				isMDNS;
-	Boolean				printRawRData;
+	OSStatus					err;
+	DNSRecordFixedFields		fields;
 	
-	Unused( inUserContext );
+	require_action_quiet( inRDataLen < kDNSRecordDataLengthMax, exit, err = kSizeErr );
 	
-	msgPtr = va_arg( inArgs->args, const void * );
-	msgLen = va_arg( inArgs->args, size_t );
-	require_action_quiet( !inFormat->suppress, exit, n = 0 );
+	err = DataBuffer_Append( inDB, inNamePtr, inNameLen );
+	require_noerr( err, exit );
 	
-	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;
-	}
+	DNSRecordFixedFieldsSet( &fields, inType, inClass, inTTL, (uint16_t) inRDataLen );
+	err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+	require_noerr( err, exit );
 	
-	err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
-	if( !err )
-	{
-		n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
-		free( text );
-	}
-	else
+	if( inRDataPtr )
 	{
-		n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+		err = DataBuffer_Append( inDB, inRDataPtr, inRDataLen );
+		require_noerr( err, exit );
 	}
 	
 exit:
-	return( n );
+	return( err );
 }
 
 //===========================================================================================================================
-//	GetDNSSDFlagsFromOpts
+//	_NanoTime64ToDateString
 //===========================================================================================================================
 
-static DNSServiceFlags	GetDNSSDFlagsFromOpts( void )
+static char *	_NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen )
 {
-	DNSServiceFlags		flags;
-	
-	flags = (DNSServiceFlags) gDNSSDFlags;
-	if( flags & kDNSServiceFlagsShareConnection )
-	{
-		FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n",
-			kDNSServiceFlagsShareConnection );
-	}
-	
-	if( gDNSSDFlag_BrowseDomains )			flags |= kDNSServiceFlagsBrowseDomains;
-	if( gDNSSDFlag_DenyCellular )			flags |= kDNSServiceFlagsDenyCellular;
-	if( gDNSSDFlag_DenyExpensive )			flags |= kDNSServiceFlagsDenyExpensive;
-	if( gDNSSDFlag_ForceMulticast )			flags |= kDNSServiceFlagsForceMulticast;
-	if( gDNSSDFlag_IncludeAWDL )			flags |= kDNSServiceFlagsIncludeAWDL;
-	if( gDNSSDFlag_NoAutoRename )			flags |= kDNSServiceFlagsNoAutoRename;
-	if( gDNSSDFlag_PathEvaluationDone )		flags |= kDNSServiceFlagsPathEvaluationDone;
-	if( gDNSSDFlag_RegistrationDomains )	flags |= kDNSServiceFlagsRegistrationDomains;
-	if( gDNSSDFlag_ReturnIntermediates )	flags |= kDNSServiceFlagsReturnIntermediates;
-	if( gDNSSDFlag_Shared )					flags |= kDNSServiceFlagsShared;
-	if( gDNSSDFlag_SuppressUnusable )		flags |= kDNSServiceFlagsSuppressUnusable;
-	if( gDNSSDFlag_Timeout )				flags |= kDNSServiceFlagsTimeout;
-	if( gDNSSDFlag_UnicastResponse )		flags |= kDNSServiceFlagsUnicastResponse;
-	if( gDNSSDFlag_Unique )					flags |= kDNSServiceFlagsUnique;
-	if( gDNSSDFlag_WakeOnResolve )			flags |= kDNSServiceFlagsWakeOnResolve;
+	struct  timeval		tv;
 	
-	return( flags );
+	NanoTimeToTimeVal( inTime, &tv );
+	return( MakeFractionalDateString( &tv, inBuf, inMaxLen ) );
 }
 
 //===========================================================================================================================
-//	CreateConnectionFromArgString
+//	MDNSColliderCreate
 //===========================================================================================================================
 
-static OSStatus
-	CreateConnectionFromArgString(
-		const char *			inString,
-		dispatch_queue_t		inQueue,
-		DNSServiceRef *			outSDRef,
-		ConnectionDesc *		outDesc )
+typedef enum
 {
-	OSStatus			err;
-	DNSServiceRef		sdRef = NULL;
-	ConnectionType		type;
-	int32_t				pid = -1;	// Initializing because the analyzer claims pid may be used uninitialized.
-	uint8_t				uuid[ 16 ];
-	
-	if( strcasecmp( inString, kConnectionArg_Normal ) == 0 )
-	{
-		err = DNSServiceCreateConnection( &sdRef );
-		require_noerr( err, exit );
-		type = kConnectionType_Normal;
-	}
-	else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 )
-	{
-		const char * const		pidStr = inString + sizeof_string( kConnectionArgPrefix_PID );
-		
-		err = StringToInt32( pidStr, &pid );
-		if( err )
-		{
-			FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr );
-			err = kParamErr;
-			goto exit;
-		}
-		
-		memset( uuid, 0, sizeof( uuid ) );
-		err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid );
-		if( err )
-		{
-			FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid );
-			goto exit;
-		}
-		type = kConnectionType_DelegatePID;
-	}
-	else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 )
-	{
-		const char * const		uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID );
-		
-		check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) );
-		
-		err = StringToUUID( uuidStr, kSizeCString, false, uuid );
-		if( err )
-		{
-			FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr );
-			err = kParamErr;
-			goto exit;
-		}
-		
-		err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid );
-		if( err )
-		{
-			FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid );
-			goto exit;
-		}
-		type = kConnectionType_DelegateUUID;
-	}
-	else
-	{
-		FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString );
-		err = kParamErr;
-		goto exit;
-	}
-	
-	err = DNSServiceSetDispatchQueue( sdRef, inQueue );
-	require_noerr( err, exit );
+	kMDNSColliderOpCode_Invalid			= 0,
+	kMDNSColliderOpCode_Send			= 1,
+	kMDNSColliderOpCode_Wait			= 2,
+	kMDNSColliderOpCode_SetProbeActions	= 3,
+	kMDNSColliderOpCode_LoopPush		= 4,
+	kMDNSColliderOpCode_LoopPop			= 5,
+	kMDNSColliderOpCode_Exit			= 6
 	
-	*outSDRef = sdRef;
-	if( outDesc )
-	{
-		outDesc->type = type;
-		if(      type == kConnectionType_DelegatePID )	outDesc->delegate.pid = pid;
-		else if( type == kConnectionType_DelegateUUID )	memcpy( outDesc->delegate.uuid, uuid, 16 );
-	}
-	sdRef = NULL;
-	
-exit:
-	if( sdRef ) DNSServiceRefDeallocate( sdRef );
-	return( err );
-}
+}	MDNSColliderOpCode;
+
+typedef struct
+{
+	MDNSColliderOpCode		opcode;
+	uint32_t				operand;
+	
+}	MDNSCInstruction;
+
+#define kMaxLoopDepth		16
+
+struct MDNSColliderPrivate
+{
+	CFRuntimeBase					base;							// CF object base.
+	dispatch_queue_t				queue;							// Queue for collider's events.
+	dispatch_source_t				readSourceV4;					// Read dispatch source for IPv4 socket.
+	dispatch_source_t				readSourceV6;					// Read dispatch source for IPv6 socket.
+	SocketRef						sockV4;							// IPv4 UDP socket for mDNS.
+	SocketRef						sockV6;							// IPv6 UDP socket for mDNS.
+	uint8_t *						target;							// Record name being targeted. (malloced)
+	uint8_t *						responsePtr;					// Response message pointer. (malloced)
+	size_t							responseLen;					// Response message length.
+	uint8_t *						probePtr;						// Probe query message pointer. (malloced)
+	size_t							probeLen;						// Probe query message length.
+	unsigned int					probeCount;						// Count of probe queries received for collider's record.
+	uint32_t						probeActionMap;					// Bitmap of actions to take for 
+	MDNSCInstruction *				program;						// Program to execute.
+	uint32_t						pc;								// Program's program counter.
+	uint32_t						loopCounts[ kMaxLoopDepth ];	// Stack of loop counters.
+	uint32_t						loopDepth;						// Current loop depth.
+	dispatch_source_t				waitTimer;						// Timer for program's wait commands.
+	uint32_t						interfaceIndex;					// Interface over which to send and receive mDNS msgs.
+	MDNSColliderStopHandler_f		stopHandler;					// User's stop handler.
+	void *							stopContext;					// User's stop handler context.
+	MDNSColliderProtocols			protocols;						// Protocols to use, i.e., IPv4, IPv6.
+	Boolean							stopped;						// True if the collider has been stopped.
+	uint8_t							msgBuf[ kMDNSMessageSizeMax ];	// mDNS message buffer.
+};
 
-//===========================================================================================================================
-//	InterfaceIndexFromArgString
-//===========================================================================================================================
+static void		_MDNSColliderStop( MDNSColliderRef inCollider, OSStatus inError );
+static void		_MDNSColliderReadHandler( void *inContext );
+static void		_MDNSColliderExecuteProgram( void *inContext );
+static OSStatus	_MDNSColliderSendResponse( MDNSColliderRef inCollider, SocketRef inSock, const struct sockaddr *inDest );
+static OSStatus	_MDNSColliderSendProbe( MDNSColliderRef inCollider, SocketRef inSock, const struct sockaddr *inDest );
 
-static OSStatus	InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
+CF_CLASS_DEFINE( MDNSCollider );
+
+ulog_define_ex( "com.apple.dnssdutil", MDNSCollider, kLogLevelInfo, kLogFlags_None, "MDNSCollider", NULL );
+#define mc_ulog( LEVEL, ... )		ulog( &log_category_from_name( MDNSCollider ), (LEVEL), __VA_ARGS__ )
+
+static OSStatus	MDNSColliderCreate( dispatch_queue_t inQueue, MDNSColliderRef *outCollider )
 {
-	OSStatus		err;
-	uint32_t		ifIndex;
+	OSStatus			err;
+	MDNSColliderRef		obj = NULL;
 	
-	if( inString )
-	{
-		ifIndex = if_nametoindex( inString );
-		if( ifIndex == 0 )
-		{
-			err = StringToUInt32( inString, &ifIndex );
-			if( err )
-			{
-				FPrintF( stderr, "Invalid interface value: %s\n", inString );
-				err = kParamErr;
-				goto exit;
-			}
-		}
-	}
-	else
-	{
-		ifIndex	= 0;
-	}
+	CF_OBJECT_CREATE( MDNSCollider, obj, err, exit );
 	
-	*outIndex = ifIndex;
+	ReplaceDispatchQueue( &obj->queue, inQueue );
+	obj->sockV4 = kInvalidSocketRef;
+	obj->sockV6 = kInvalidSocketRef;
+	
+	*outCollider = obj;
 	err = kNoErr;
 	
 exit:
@@ -11377,1221 +18050,1775 @@ exit:
 }
 
 //===========================================================================================================================
-//	RecordDataFromArgString
+//	_MDNSColliderFinalize
 //===========================================================================================================================
 
-#define kRDataMaxLen		UINT16_C( 0xFFFF )
+static void	_MDNSColliderFinalize( CFTypeRef inObj )
+{
+	MDNSColliderRef const		me = (MDNSColliderRef) inObj;
+	
+	check( !me->waitTimer );
+	check( !me->readSourceV4 );
+	check( !me->readSourceV6 );
+	check( !IsValidSocket( me->sockV4 ) );
+	check( !IsValidSocket( me->sockV6 ) );
+	ForgetMem( &me->target );
+	ForgetMem( &me->responsePtr );
+	ForgetMem( &me->probePtr );
+	ForgetMem( &me->program );
+	dispatch_forget( &me->queue );
+}
+
+//===========================================================================================================================
+//	MDNSColliderStart
+//===========================================================================================================================
 
-static OSStatus	StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen );
-static OSStatus	StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen );
+static void	_MDNSColliderStart( void *inContext );
 
-static OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+static OSStatus	MDNSColliderStart( MDNSColliderRef me )
 {
 	OSStatus		err;
-	uint8_t *		dataPtr = NULL;
-	size_t			dataLen;
-	
-	if( 0 ) {}
 	
-	// Domain name
+	require_action_quiet( me->target,         exit, err = kNotPreparedErr );
+	require_action_quiet( me->responsePtr,    exit, err = kNotPreparedErr );
+	require_action_quiet( me->probePtr,       exit, err = kNotPreparedErr );
+	require_action_quiet( me->program,        exit, err = kNotPreparedErr );
+	require_action_quiet( me->interfaceIndex, exit, err = kNotPreparedErr );
+	require_action_quiet( me->protocols,      exit, err = kNotPreparedErr );
 	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
-	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_Domain );
-		
-		err = StringToDomainName( str, &dataPtr, &dataLen );
-		require_noerr_quiet( err, exit );
-	}
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _MDNSColliderStart );
+	err = kNoErr;
 	
-	// File path
+exit:
+	return( err );
+}
+
+static void	_MDNSColliderStart( void *inContext )
+{
+	OSStatus					err;
+	MDNSColliderRef const		me		= (MDNSColliderRef) inContext;
+	SocketRef					sock	= kInvalidSocketRef;
+	SocketContext *				sockCtx	= NULL;
 	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+	if( me->protocols & kMDNSColliderProtocol_IPv4 )
 	{
-		const char * const		path = inString + sizeof_string( kRDataArgPrefix_File );
+		err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
+		require_noerr( err, exit );
 		
-		err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+		err = SocketContextCreate( sock, me, &sockCtx );
 		require_noerr( err, exit );
-		require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
-	}
-	
-	// Hexadecimal string
-	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
-	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_HexString );
+		sock = kInvalidSocketRef;
 		
-		err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+		err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler,
+			sockCtx, &me->readSourceV4 );
 		require_noerr( err, exit );
-		require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
-	}
-	
-	// IPv4 address string
-	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
-	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+		me->sockV4 = sockCtx->sock;
+		sockCtx = NULL;
 		
-		err = StringToARecordData( str, &dataPtr, &dataLen );
-		require_noerr_quiet( err, exit );
+		dispatch_resume( me->readSourceV4 );
 	}
 	
-	// IPv6 address string
-	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+	if( me->protocols & kMDNSColliderProtocol_IPv6 )
 	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+		err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
+		require_noerr( err, exit );
 		
-		err = StringToAAAARecordData( str, &dataPtr, &dataLen );
-		require_noerr_quiet( err, exit );
-	}
-	
-	// SRV record
-	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
-	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_SRV );
+		err = SocketContextCreate( sock, me, &sockCtx );
+		require_noerr( err, exit );
+		sock = kInvalidSocketRef;
 		
-		err = StringToSRVRData( str, &dataPtr, &dataLen );
+		err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler,
+			sockCtx, &me->readSourceV6 );
 		require_noerr( err, exit );
+		me->sockV6 = sockCtx->sock;
+		sockCtx = NULL;
+		
+		dispatch_resume( me->readSourceV6 );
 	}
 	
-	// String with escaped hex and octal bytes
+	_MDNSColliderExecuteProgram( me );
+	err = kNoErr;
 	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
-	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_String );
-		const char * const		end = str + strlen( str );
-		size_t					copiedLen;
-		size_t					totalLen;
-		Boolean					success;
+exit:
+	ForgetSocket( &sock );
+	ForgetSocketContext( &sockCtx );
+	if( err ) _MDNSColliderStop( me, err );
+}
+
+//===========================================================================================================================
+//	MDNSColliderStop
+//===========================================================================================================================
+
+static void	_MDNSColliderUserStop( void *inContext );
+
+static void	MDNSColliderStop( MDNSColliderRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _MDNSColliderUserStop );
+}
+
+static void	_MDNSColliderUserStop( void *inContext )
+{
+	MDNSColliderRef const		me = (MDNSColliderRef) inContext;
+	
+	_MDNSColliderStop( me, kCanceledErr );
+	CFRelease( me );
+}
+
+//===========================================================================================================================
+//	MDNSColliderSetProtocols
+//===========================================================================================================================
+
+static void	MDNSColliderSetProtocols( MDNSColliderRef me, MDNSColliderProtocols inProtocols )
+{
+	me->protocols = inProtocols;
+}
+
+//===========================================================================================================================
+//	MDNSColliderSetInterfaceIndex
+//===========================================================================================================================
+
+static void	MDNSColliderSetInterfaceIndex( MDNSColliderRef me, uint32_t inInterfaceIndex )
+{
+	me->interfaceIndex = inInterfaceIndex;
+}
+
+//===========================================================================================================================
+//	MDNSColliderSetProgram
+//===========================================================================================================================
+
+#define kMDNSColliderProgCmd_Done		"done"
+#define kMDNSColliderProgCmd_Loop		"loop"
+#define kMDNSColliderProgCmd_Send		"send"
+#define kMDNSColliderProgCmd_Probes		"probes"
+#define kMDNSColliderProgCmd_Wait		"wait"
+
+typedef uint32_t		MDNSColliderProbeAction;
+
+#define kMDNSColliderProbeAction_None					0
+#define kMDNSColliderProbeAction_Respond				1
+#define kMDNSColliderProbeAction_RespondUnicast			2
+#define kMDNSColliderProbeAction_RespondMulticast		3
+#define kMDNSColliderProbeAction_Probe					4
+#define kMDNSColliderProbeAction_MaxValue				kMDNSColliderProbeAction_Probe
+
+#define kMDNSColliderProbeActionBits_Count			3
+#define kMDNSColliderProbeActionBits_Mask			( ( 1U << kMDNSColliderProbeActionBits_Count ) - 1 )
+#define kMDNSColliderProbeActionMaxProbeCount		( 32 / kMDNSColliderProbeActionBits_Count )
+
+check_compile_time( kMDNSColliderProbeAction_MaxValue <= kMDNSColliderProbeActionBits_Mask );
+
+static OSStatus	_MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap );
+
+static OSStatus	MDNSColliderSetProgram( MDNSColliderRef me, const char *inProgramStr )
+{
+	OSStatus				err;
+	uint32_t				insCount;
+	unsigned int			loopDepth;
+	const char *			cmd;
+	const char *			end;
+	const char *			next;
+	MDNSCInstruction *		program = NULL;
+	uint32_t				loopStart[ kMaxLoopDepth ];
+	
+	insCount = 0;
+	for( cmd = inProgramStr; *cmd; cmd = next )
+	{
+		for( end = cmd; *end && ( *end != ';' ); ++end ) {}
+		require_action_quiet( end != cmd, exit, err = kMalformedErr );
+		next = ( *end == ';' ) ? ( end + 1 ) : end;
+		++insCount;
+	}
+	
+	program = (MDNSCInstruction *) calloc( insCount + 1, sizeof( *program ) );
+	require_action( program, exit, err = kNoMemoryErr );
+	
+	insCount	= 0;
+	loopDepth	= 0;
+	for( cmd = inProgramStr; *cmd; cmd = next )
+	{
+		size_t							cmdLen;
+		const char *					ptr;
+		const char *					arg;
+		size_t							argLen;
+		uint32_t						value;
+		MDNSCInstruction * const		ins = &program[ insCount ];
 		
-		if( str < end )
+		while( isspace_safe( *cmd ) ) ++cmd;
+		for( end = cmd; *end && ( *end != ';' ); ++end ) {}
+		next = ( *end == ';' ) ? ( end + 1 ) : end;
+		
+		for( ptr = cmd; ( ptr < end ) && !isspace_safe( *ptr ); ++ptr ) {}
+		cmdLen = (size_t)( ptr - cmd );
+		
+		// Done statement
+		
+		if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Done ) == 0 )
 		{
-			success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
-			require_action( success, exit, err = kParamErr );
-			require_action( totalLen <= kRDataMaxLen, exit, err = kSizeErr );
+			while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+			require_action_quiet( ptr == end, exit, err = kMalformedErr );
 			
-			dataLen = totalLen;
-			dataPtr = (uint8_t *) malloc( dataLen );
-			require_action( dataPtr, exit, err = kNoMemoryErr );
+			require_action_quiet( loopDepth > 0, exit, err = kMalformedErr );
 			
-			success = ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL );
-			require_action( success, exit, err = kParamErr );
-			check( copiedLen == dataLen );
+			ins->opcode		= kMDNSColliderOpCode_LoopPop;
+			ins->operand	= loopStart[ --loopDepth ];
 		}
-		else
+		
+		// Loop command
+		
+		else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Loop ) == 0 )
 		{
-			dataPtr = NULL;
-			dataLen = 0;
+			for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+			err = DecimalTextToUInt32( arg, end, &value, &ptr );
+			require_noerr_quiet( err, exit );
+			require_action_quiet( value > 0, exit, err = kValueErr );
+			
+			while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+			require_action_quiet( ptr == end, exit, err = kMalformedErr );
+			
+			ins->opcode 	= kMDNSColliderOpCode_LoopPush;
+			ins->operand	= value;
+			
+			require_action_quiet( loopDepth < kMaxLoopDepth, exit, err = kNoSpaceErr );
+			loopStart[ loopDepth++ ] = insCount + 1;
 		}
-	}
-	
-	// TXT record
-	
-	else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
-	{
-		const char * const		str = inString + sizeof_string( kRDataArgPrefix_TXT );
 		
-		err = StringToTXTRData( str, ',', &dataPtr, &dataLen );
-		require_noerr( err, exit );
-	}
-	
-	// Unrecognized format
-	
-	else
-	{
-		FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
-		err = kParamErr;
-		goto exit;
-	}
-	
-	err = kNoErr;
-	*outDataLen = dataLen;
-	*outDataPtr = dataPtr;
-	dataPtr = NULL;
-	
-exit:
-	FreeNullSafe( dataPtr );
-	return( err );
-}
-
-static OSStatus	StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen )
-{
-	OSStatus			err;
-	DataBuffer			dataBuf;
-	const char *		ptr;
-	int					i;
-	uint8_t *			end;
-	uint8_t				target[ kDomainNameLengthMax ];
-	
-	DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax );
-	
-	// Parse and set the priority, weight, and port values (all three are unsigned 16-bit values).
-	
-	ptr = inString;
-	for( i = 0; i < 3; ++i )
-	{
-		char *		next;
-		long		value;
-		uint8_t		buf[ 2 ];
+		// Probes command
 		
-		value = strtol( ptr, &next, 0 );
-		require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr );
-		require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr );
-		ptr = next + 1;
+		else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Probes ) == 0 )
+		{
+			for( arg = ptr; ( arg < end ) &&  isspace_safe( *arg ); ++arg ) {}
+			for( ptr = arg; ( ptr < end ) && !isspace_safe( *ptr ); ++ptr ) {}
+			argLen = (size_t)( ptr - arg );
+			if( argLen > 0 )
+			{
+				err = _MDNSColliderParseProbeActionString( arg, argLen, &value );
+				require_noerr_quiet( err, exit );
+			}
+			else
+			{
+				value = 0;
+			}
+			
+			while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+			require_action_quiet( ptr == end, exit, err = kMalformedErr );
+			
+			ins->opcode 	= kMDNSColliderOpCode_SetProbeActions;
+			ins->operand	= value;
+		}
 		
-		WriteBig16( buf, value );
+		// Send command
 		
-		err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) );
-		require_noerr( err, exit );
+		else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Send ) == 0 )
+		{
+			while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+			require_action_quiet( ptr == end, exit, err = kMalformedErr );
+			
+			ins->opcode = kMDNSColliderOpCode_Send;
+		}
+		
+		// Wait command
+		
+		else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Wait ) == 0 )
+		{
+			for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+			err = DecimalTextToUInt32( arg, end, &value, &ptr );
+			require_noerr_quiet( err, exit );
+			
+			while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+			require_action_quiet( ptr == end, exit, err = kMalformedErr );
+			
+			ins->opcode		= kMDNSColliderOpCode_Wait;
+			ins->operand	= value;
+		}
+		
+		// Unrecognized command
+		
+		else
+		{
+			err = kCommandErr;
+			goto exit;
+		}
+		++insCount;
 	}
+	require_action_quiet( loopDepth == 0, exit, err = kMalformedErr );
 	
-	// Set the target domain name.
-	
-	err = DomainNameFromString( target, ptr, &end );
-    require_noerr_quiet( err, exit );
-	
-	err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) );
-	require_noerr( err, exit );
+	program[ insCount ].opcode = kMDNSColliderOpCode_Exit;
 	
-	err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
-	require_noerr( err, exit );
+	FreeNullSafe( me->program );
+	me->program = program;
+	program = NULL;
+	err = kNoErr;
 	
 exit:
-	DataBuffer_Free( &dataBuf );
+	FreeNullSafe( program );
 	return( err );
 }
 
-static OSStatus	StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen )
+static OSStatus	_MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap )
 {
-	OSStatus			err;
-	DataBuffer			dataBuf;
-	const char *		src;
-	uint8_t				txtStr[ 256 ];	// Buffer for single TXT string: 1 length byte + up to 255 bytes of data.
-	
-	DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen );
+	OSStatus				err;
+	const char *			ptr;
+	const char * const		end = &inString[ inLen ];
+	uint32_t				bitmap;
+	int						index;
 	
-	src = inString;
-	for( ;; )
+	bitmap	= 0;
+	index	= 0;
+	ptr		= inString;
+	while( ptr < end )
 	{
-		uint8_t *					dst = &txtStr[ 1 ];
-		const uint8_t * const		lim = &txtStr[ 256 ];
-		int							c;
+		int							c, count;
+		MDNSColliderProbeAction		action;
 		
-		while( *src && ( *src != inDelimiter ) )
+		c = *ptr++;
+		if( isdigit_safe( c ) )
 		{
-			if( ( c = *src++ ) == '\\' )
+			count = 0;
+			do
 			{
-				require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
-				c = *src++;
-			}
-			require_action_quiet( dst < lim, exit, err = kOverrunErr );
-			*dst++ = (uint8_t) c;
+				count = ( count * 10 ) + ( c - '0' );
+				require_action_quiet( count <= ( kMDNSColliderProbeActionMaxProbeCount - index ), exit, err = kCountErr );
+				require_action_quiet( ptr < end, exit, err = kUnderrunErr );
+				c = *ptr++;
+				
+			}	while( isdigit_safe( c ) );
+			require_action_quiet( count > 0, exit, err = kCountErr );
+		}
+		else
+		{
+			require_action_quiet( index < kMDNSColliderProbeActionMaxProbeCount, exit, err = kMalformedErr );
+			count = 1;
 		}
-		txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
-		err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
-		require_noerr( err, exit );
 		
-		if( *src == '\0' ) break;
-		++src;
+		switch( c )
+		{
+			case 'n':	action = kMDNSColliderProbeAction_None;				break;
+			case 'r':	action = kMDNSColliderProbeAction_Respond;			break;
+			case 'u':	action = kMDNSColliderProbeAction_RespondUnicast;	break;
+			case 'm':	action = kMDNSColliderProbeAction_RespondMulticast;	break;
+			case 'p':	action = kMDNSColliderProbeAction_Probe;			break;
+			default:	err = kMalformedErr;								goto exit;
+		}
+		if( ptr < end )
+		{
+			c = *ptr++;
+			require_action_quiet( ( c == '-' ) && ( ptr < end ), exit, err = kMalformedErr );
+		}
+		while( count-- > 0 )
+		{
+			bitmap |= ( action << ( index * kMDNSColliderProbeActionBits_Count ) );
+			++index;
+		}
 	}
 	
-	err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
-	require_noerr( err, exit );
+	*outBitmap = bitmap;
+	err = kNoErr;
 	
 exit:
-	DataBuffer_Free( &dataBuf );
 	return( err );
 }
 
 //===========================================================================================================================
-//	RecordTypeFromArgString
+//	MDNSColliderSetStopHandler
 //===========================================================================================================================
 
-typedef struct
+static void	MDNSColliderSetStopHandler( MDNSColliderRef me, MDNSColliderStopHandler_f inStopHandler, void *inStopContext )
 {
-	uint16_t			value;	// Record type's numeric value.
-	const char *		name;	// Record type's name as a string (e.g., "A", "PTR", "SRV").
-	
-}	RecordType;
+	me->stopHandler = inStopHandler;
+	me->stopContext = inStopContext;
+}
 
-static const RecordType		kRecordTypes[] =
+//===========================================================================================================================
+//	MDNSColliderSetRecord
+//===========================================================================================================================
+
+#define kMDNSColliderDummyStr			"\x16" "mdnscollider-sent-this" kLocalStr
+#define kMDNSColliderDummyName			( (const uint8_t *) kMDNSColliderDummyStr )
+#define kMDNSColliderDummyNameLen		sizeof( kMDNSColliderDummyStr )
+
+static OSStatus
+	MDNSColliderSetRecord(
+		MDNSColliderRef	me,
+		const uint8_t *	inName,
+		uint16_t		inType,
+		const void *	inRDataPtr,
+		size_t			inRDataLen )
 {
-	// Common types.
+	OSStatus		err;
+	DataBuffer		msgDB;
+	DNSHeader		header;
+	uint8_t *		targetPtr	= NULL;
+	size_t			targetLen;
+	uint8_t *		responsePtr	= NULL;
+	size_t			responseLen;
+	uint8_t *		probePtr	= NULL;
+	size_t			probeLen;
+	
+	DataBuffer_Init( &msgDB, NULL, 0, kMDNSMessageSizeMax );
+	
+	err = DomainNameDup( inName, &targetPtr, &targetLen );
+	require_noerr_quiet( err, exit );
 	
-	{ kDNSServiceType_A,			"A" },
-	{ kDNSServiceType_AAAA,			"AAAA" },
-	{ kDNSServiceType_PTR,			"PTR" },
-	{ kDNSServiceType_SRV,			"SRV" },
-	{ kDNSServiceType_TXT,			"TXT" },
-	{ kDNSServiceType_CNAME,		"CNAME" },
-	{ kDNSServiceType_SOA,			"SOA" },
-	{ kDNSServiceType_NSEC,			"NSEC" },
-	{ kDNSServiceType_NS,			"NS" },
-	{ kDNSServiceType_MX,			"MX" },
-	{ kDNSServiceType_ANY,			"ANY" },
-	{ kDNSServiceType_OPT,			"OPT" },
+	// Create response message.
 	
-	// Less common types.
+	memset( &header, 0, sizeof( header ) );
+	DNSHeaderSetFlags( &header, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer );
+	DNSHeaderSetAnswerCount( &header, 1 );
 	
-	{ kDNSServiceType_MD,			"MD" },
-	{ kDNSServiceType_NS,			"NS" },
-	{ kDNSServiceType_MD,			"MD" },
-	{ kDNSServiceType_MF,			"MF" },
-	{ kDNSServiceType_MB,			"MB" },
-	{ kDNSServiceType_MG,			"MG" },
-	{ kDNSServiceType_MR,			"MR" },
-	{ kDNSServiceType_NULL,			"NULL" },
-	{ kDNSServiceType_WKS,			"WKS" },
-	{ kDNSServiceType_HINFO,		"HINFO" },
-	{ kDNSServiceType_MINFO,		"MINFO" },
-	{ kDNSServiceType_RP,			"RP" },
-	{ kDNSServiceType_AFSDB,		"AFSDB" },
-	{ kDNSServiceType_X25,			"X25" },
-	{ kDNSServiceType_ISDN,			"ISDN" },
-	{ kDNSServiceType_RT,			"RT" },
-	{ kDNSServiceType_NSAP,			"NSAP" },
-	{ kDNSServiceType_NSAP_PTR,		"NSAP_PTR" },
-	{ kDNSServiceType_SIG,			"SIG" },
-	{ kDNSServiceType_KEY,			"KEY" },
-	{ kDNSServiceType_PX,			"PX" },
-	{ kDNSServiceType_GPOS,			"GPOS" },
-	{ kDNSServiceType_LOC,			"LOC" },
-	{ kDNSServiceType_NXT,			"NXT" },
-	{ kDNSServiceType_EID,			"EID" },
-	{ kDNSServiceType_NIMLOC,		"NIMLOC" },
-	{ kDNSServiceType_ATMA,			"ATMA" },
-	{ kDNSServiceType_NAPTR,		"NAPTR" },
-	{ kDNSServiceType_KX,			"KX" },
-	{ kDNSServiceType_CERT,			"CERT" },
-	{ kDNSServiceType_A6,			"A6" },
-	{ kDNSServiceType_DNAME,		"DNAME" },
-	{ kDNSServiceType_SINK,			"SINK" },
-	{ kDNSServiceType_APL,			"APL" },
-	{ kDNSServiceType_DS,			"DS" },
-	{ kDNSServiceType_SSHFP,		"SSHFP" },
-	{ kDNSServiceType_IPSECKEY,		"IPSECKEY" },
-	{ kDNSServiceType_RRSIG,		"RRSIG" },
-	{ kDNSServiceType_DNSKEY,		"DNSKEY" },
-	{ kDNSServiceType_DHCID,		"DHCID" },
-	{ kDNSServiceType_NSEC3,		"NSEC3" },
-	{ kDNSServiceType_NSEC3PARAM,	"NSEC3PARAM" },
-	{ kDNSServiceType_HIP,			"HIP" },
-	{ kDNSServiceType_SPF,			"SPF" },
-	{ kDNSServiceType_UINFO,		"UINFO" },
-	{ kDNSServiceType_UID,			"UID" },
-	{ kDNSServiceType_GID,			"GID" },
-	{ kDNSServiceType_UNSPEC,		"UNSPEC" },
-	{ kDNSServiceType_TKEY,			"TKEY" },
-	{ kDNSServiceType_TSIG,			"TSIG" },
-	{ kDNSServiceType_IXFR,			"IXFR" },
-	{ kDNSServiceType_AXFR,			"AXFR" },
-	{ kDNSServiceType_MAILB,		"MAILB" },
-	{ kDNSServiceType_MAILA,		"MAILA" }
-};
-
-static OSStatus	RecordTypeFromArgString( const char *inString, uint16_t *outValue )
-{
-	OSStatus						err;
-	int32_t							i32;
-	const RecordType *				type;
-	const RecordType * const		end = kRecordTypes + countof( kRecordTypes );
+	err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
+	require_noerr( err, exit );
 	
-	for( type = kRecordTypes; type < end; ++type )
-	{
-		if( strcasecmp( type->name, inString ) == 0 )
-		{
-			*outValue = type->value;
-			return( kNoErr );
-		}
-	}
+	err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN | kRRClassCacheFlushBit,
+		1976, inRDataPtr, inRDataLen );
+	require_noerr( err, exit );
 	
-	err = StringToInt32( inString, &i32 );
-	require_noerr_quiet( err, exit );
-	require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+	err = DataBuffer_Detach( &msgDB, &responsePtr, &responseLen );
+	require_noerr( err, exit );
 	
-	*outValue = (uint16_t) i32;
+	// Create probe message.
+	
+	memset( &header, 0, sizeof( header ) );
+	DNSHeaderSetQuestionCount( &header, 2 );
+	DNSHeaderSetAuthorityCount( &header, 1 );
+	
+	err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
+	require_noerr( err, exit );
+	
+	err = _DataBuffer_AppendDNSQuestion( &msgDB, targetPtr, targetLen, kDNSServiceType_ANY, kDNSServiceClass_IN );
+	require_noerr( err, exit );
+	
+	err = _DataBuffer_AppendDNSQuestion( &msgDB, kMDNSColliderDummyName, kMDNSColliderDummyNameLen,
+		kDNSServiceType_NULL, kDNSServiceClass_IN );
+	require_noerr( err, exit );
+	
+	err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN,
+		1976, inRDataPtr, inRDataLen );
+	require_noerr( err, exit );
+	
+	err = DataBuffer_Detach( &msgDB, &probePtr, &probeLen );
+	require_noerr( err, exit );
+	
+	FreeNullSafe( me->target );
+	me->target = targetPtr;
+	targetPtr = NULL;
+	
+	FreeNullSafe( me->responsePtr );
+	me->responsePtr = responsePtr;
+	me->responseLen = responseLen;
+	responsePtr = NULL;
+	
+	FreeNullSafe( me->probePtr );
+	me->probePtr = probePtr;
+	me->probeLen = probeLen;
+	probePtr = NULL;
 	
 exit:
+	DataBuffer_Free( &msgDB );
+	FreeNullSafe( targetPtr );
+	FreeNullSafe( responsePtr );
+	FreeNullSafe( probePtr );
 	return( err );
 }
 
 //===========================================================================================================================
-//	RecordClassFromArgString
+//	_MDNSColliderStop
 //===========================================================================================================================
 
-static OSStatus	RecordClassFromArgString( const char *inString, uint16_t *outValue )
+static void	_MDNSColliderStop( MDNSColliderRef me, OSStatus inError )
 {
-	OSStatus		err;
-	int32_t			i32;
+	dispatch_source_forget( &me->waitTimer );
+	dispatch_source_forget( &me->readSourceV4 );
+	dispatch_source_forget( &me->readSourceV6 );
+	me->sockV4 = kInvalidSocketRef;
+	me->sockV6 = kInvalidSocketRef;
 	
-	if( strcasecmp( inString, "IN" ) == 0 )
+	if( !me->stopped )
 	{
-		*outValue = kDNSServiceClass_IN;
-		err = kNoErr;
-		goto exit;
+		me->stopped = true;
+		if( me->stopHandler ) me->stopHandler( me->stopContext, inError );
+		CFRelease( me );
 	}
-	
-	err = StringToInt32( inString, &i32 );
-	require_noerr_quiet( err, exit );
-	require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
-	
-	*outValue = (uint16_t) i32;
-	
-exit:
-	return( err );
 }
 
 //===========================================================================================================================
-//	InterfaceIndexToName
+//	_MDNSColliderReadHandler
 //===========================================================================================================================
 
-static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
+static MDNSColliderProbeAction	_MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber );
+static const char *				_MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction );
+
+static void	_MDNSColliderReadHandler( void *inContext )
 {
-	switch( inIfIndex )
+	OSStatus					err;
+	struct timeval				now;
+	SocketContext * const		sockCtx	= (SocketContext *) inContext;
+	MDNSColliderRef const		me		= (MDNSColliderRef) sockCtx->userContext;
+	size_t						msgLen;
+	sockaddr_ip					sender;
+	const DNSHeader *			hdr;
+	const uint8_t *				ptr;
+	const struct sockaddr *		dest;
+	int							probeFound, probeIsQU;
+	unsigned int				qCount, i;
+	MDNSColliderProbeAction		action;
+	
+	gettimeofday( &now, NULL );
+	
+	err = SocketRecvFrom( sockCtx->sock, me->msgBuf, sizeof( me->msgBuf ), &msgLen, &sender, sizeof( sender ),
+		NULL, NULL, NULL, NULL );
+	require_noerr( err, exit );
+	
+	require_quiet( msgLen >= kDNSHeaderLength, exit );
+	hdr = (const DNSHeader *) me->msgBuf;
+	
+	probeFound	= false;
+	probeIsQU	= false;
+	qCount = DNSHeaderGetQuestionCount( hdr );
+	ptr = (const uint8_t *) &hdr[ 1 ];
+	for( i = 0; i < qCount; ++i )
 	{
-		case kDNSServiceInterfaceIndexAny:
-			strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
-			break;
-		
-		case kDNSServiceInterfaceIndexLocalOnly:
-			strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
-			break;
+		uint16_t		qtype, qclass;
+		uint8_t			qname[ kDomainNameLengthMax ];
 		
-		case kDNSServiceInterfaceIndexUnicast:
-			strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
-			break;
+		err = DNSMessageExtractQuestion( me->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
+		require_noerr_quiet( err, exit );
 		
-		case kDNSServiceInterfaceIndexP2P:
-			strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
+		if( ( qtype == kDNSServiceType_NULL ) && ( qclass == kDNSServiceClass_IN ) &&
+			DomainNameEqual( qname, kMDNSColliderDummyName ) )
+		{
+			probeFound = false;
 			break;
+		}
 		
-	#if( defined( kDNSServiceInterfaceIndexBLE ) )
-		case kDNSServiceInterfaceIndexBLE:
-			strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
-			break;
-	#endif
+		if( qtype != kDNSServiceType_ANY ) continue;
+		if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+		if( !DomainNameEqual( qname, me->target ) ) continue;
 		
-		default:
+		if( !probeFound )
 		{
-			const char *		name;
-			
-			name = if_indextoname( inIfIndex, inNameBuf );
-			if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
-			break;
+			probeFound	= true;
+			probeIsQU	= ( qclass & kQClassUnicastResponseBit ) ? true : false;
 		}
 	}
+	require_quiet( probeFound, exit );
 	
-	return( inNameBuf );
+	++me->probeCount;
+	action = _MDNSColliderGetProbeAction( me->probeActionMap, me->probeCount );
+	
+	mc_ulog( kLogLevelInfo, "Received probe from %##a at %{du:time} (action: %s):\n\n%#1{du:dnsmsg}",
+		&sender, &now, _MDNSColliderProbeActionToString( action ), me->msgBuf, msgLen );
+	
+	if( ( action == kMDNSColliderProbeAction_Respond )			||
+		( action == kMDNSColliderProbeAction_RespondUnicast )	||
+		( action == kMDNSColliderProbeAction_RespondMulticast ) )
+	{
+		if( ( ( action == kMDNSColliderProbeAction_Respond ) && probeIsQU ) ||
+			(   action == kMDNSColliderProbeAction_RespondUnicast ) )
+		{
+			dest = &sender.sa;
+		}
+		else if( ( ( action == kMDNSColliderProbeAction_Respond ) && !probeIsQU ) ||
+				 (   action == kMDNSColliderProbeAction_RespondMulticast ) )
+		{
+			dest = ( sender.sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+		}
+		
+		err = _MDNSColliderSendResponse( me, sockCtx->sock, dest );
+		require_noerr( err, exit );
+	}
+	else if( action == kMDNSColliderProbeAction_Probe )
+	{
+		dest = ( sender.sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+		
+		err = _MDNSColliderSendProbe( me, sockCtx->sock, dest );
+		require_noerr( err, exit );
+	}
+	
+exit:
+	return;
 }
 
-//===========================================================================================================================
-//	RecordTypeToString
-//===========================================================================================================================
+static MDNSColliderProbeAction	_MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber )
+{
+	MDNSColliderProbeAction		action;
+	
+	if( ( inProbeNumber >= 1 ) && ( inProbeNumber <= kMDNSColliderProbeActionMaxProbeCount ) )
+	{
+		action = ( inBitmap >> ( ( inProbeNumber - 1 ) * kMDNSColliderProbeActionBits_Count ) ) &
+			kMDNSColliderProbeActionBits_Mask;
+	}
+	else
+	{
+		action = kMDNSColliderProbeAction_None;
+	}
+	return( action );
+}
 
-static const char *	RecordTypeToString( unsigned int inValue )
+static const char *	_MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction )
 {
-	const RecordType *				type;
-	const RecordType * const		end = kRecordTypes + countof( kRecordTypes );
-	
-	for( type = kRecordTypes; type < end; ++type )
+	switch( inAction )
 	{
-		if( type->value == inValue ) return( type->name );
+		case kMDNSColliderProbeAction_None:				return( "None" );
+		case kMDNSColliderProbeAction_Respond:			return( "Respond" );
+		case kMDNSColliderProbeAction_RespondUnicast:	return( "Respond (unicast)" );
+		case kMDNSColliderProbeAction_RespondMulticast:	return( "Respond (multicast)" );
+		case kMDNSColliderProbeAction_Probe:			return( "Probe" );
+		default:										return( "???" );
 	}
-	return( "???" );
 }
 
 //===========================================================================================================================
-//	DNSMessageExtractDomainName
+//	_MDNSColliderExecuteProgram
 //===========================================================================================================================
 
-#define IsCompressionByte( X )		( ( ( X ) & 0xC0 ) == 0xC0 )
-
-static OSStatus
-	DNSMessageExtractDomainName(
-		const uint8_t *		inMsgPtr,
-		size_t				inMsgLen,
-		const uint8_t *		inNamePtr,
-		uint8_t				inBuf[ kDomainNameLengthMax ],
-		const uint8_t **	outNextPtr )
+static void	_MDNSColliderExecuteProgram( void *inContext )
 {
 	OSStatus					err;
-	const uint8_t *				label;
-	uint8_t						labelLen;
-	const uint8_t *				nextLabel;
-	const uint8_t * const		msgEnd	= inMsgPtr + inMsgLen;
-	uint8_t *					dst		= inBuf;
-	const uint8_t * const		dstLim	= inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL;
-	const uint8_t *				nameEnd	= NULL;
+	MDNSColliderRef const		me = (MDNSColliderRef) inContext;
+	int							stop;
 	
-	require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
+	dispatch_forget( &me->waitTimer );
 	
-	for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+	stop = false;
+	for( ;; )
 	{
-		if( labelLen <= kDomainLabelLengthMax )
-		{
-			nextLabel = label + 1 + labelLen;
-			require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
-			if( dst )
-			{
-				require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr );
-				memcpy( dst, label, 1 + labelLen );
-				dst += ( 1 + labelLen );
-			}
-		}
-		else if( IsCompressionByte( labelLen ) )
+		const MDNSCInstruction * const		ins = &me->program[ me->pc++ ];
+		uint32_t							waitMs;
+		
+		switch( ins->opcode )
 		{
-			uint16_t		offset;
+			case kMDNSColliderOpCode_Send:
+				if( IsValidSocket( me->sockV4 ) )
+				{
+					err = _MDNSColliderSendResponse( me, me->sockV4, GetMDNSMulticastAddrV4() );
+					require_noerr( err, exit );
+				}
+				if( IsValidSocket( me->sockV6 ) )
+				{
+					err = _MDNSColliderSendResponse( me, me->sockV6, GetMDNSMulticastAddrV6() );
+					require_noerr( err, exit );
+				}
+				break;
 			
-			require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr );
-			if( !nameEnd )
-			{
-				nameEnd = label + 2;
-				if( !dst ) break;
-			}
-			offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] );
-			nextLabel = inMsgPtr + offset;
-			require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
-			require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr );
-		}
-		else
-		{
-			dlogassert( "Unhandled label length 0x%02X\n", labelLen );
-			err = kMalformedErr;
-			goto exit;
+			case kMDNSColliderOpCode_Wait:
+				waitMs = ins->operand;
+				if( waitMs > 0 )
+				{
+					err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( waitMs ), 1, me->queue,
+						_MDNSColliderExecuteProgram, me, &me->waitTimer );
+					require_noerr( err, exit );
+					dispatch_resume( me->waitTimer );
+					goto exit;
+				}
+				break;
+			
+			case kMDNSColliderOpCode_SetProbeActions:
+				me->probeCount		= 0;
+				me->probeActionMap	= ins->operand;
+				break;
+			
+			case kMDNSColliderOpCode_LoopPush:
+				check( me->loopDepth < kMaxLoopDepth );
+				me->loopCounts[ me->loopDepth++ ] = ins->operand;
+				break;
+			
+			case kMDNSColliderOpCode_LoopPop:
+				check( me->loopDepth > 0 );
+				if( --me->loopCounts[ me->loopDepth - 1 ] > 0 )
+				{
+					me->pc = ins->operand;
+				}
+				else
+				{
+					--me->loopDepth;
+				}
+				break;
+			
+			case kMDNSColliderOpCode_Exit:
+				stop = true;
+				err	= kNoErr;
+				goto exit;
+			
+			default:
+				dlogassert( "Unhandled opcode %u\n", ins->opcode );
+				err = kCommandErr;
+				goto exit;
 		}
 	}
 	
-	if( dst ) *dst = 0;
-	if( !nameEnd ) nameEnd = label + 1;
-	
-	if( outNextPtr ) *outNextPtr = nameEnd;
-	err = kNoErr;
-	
 exit:
-	return( err );
+	if( err || stop ) _MDNSColliderStop( me, err );
 }
 
 //===========================================================================================================================
-//	DNSMessageExtractDomainNameString
+//	_MDNSColliderSendResponse
 //===========================================================================================================================
 
-static OSStatus
-	DNSMessageExtractDomainNameString(
-		const void *		inMsgPtr,
-		size_t				inMsgLen,
-		const void *		inNamePtr,
-		char				inBuf[ kDNSServiceMaxDomainName ],
-		const uint8_t **	outNextPtr )
+static OSStatus	_MDNSColliderSendResponse( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest )
 {
-	OSStatus			err;
-	const uint8_t *		nextPtr;
-	uint8_t				domainName[ kDomainNameLengthMax ];
-	
-	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr );
-	require_noerr( err, exit );
-	
-	err = DomainNameToString( domainName, NULL, inBuf, NULL );
-	require_noerr( err, exit );
+	OSStatus		err;
+	ssize_t			n;
 	
-	if( outNextPtr ) *outNextPtr = nextPtr;
+	n = sendto( inSock, (char *) me->responsePtr, me->responseLen, 0, inDest, SockAddrGetSize( inDest ) );
+	err = map_socket_value_errno( inSock, n == (ssize_t) me->responseLen, n );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_MDNSColliderSendProbe
+//===========================================================================================================================
+
+static OSStatus	_MDNSColliderSendProbe( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest )
+{
+	OSStatus		err;
+	ssize_t			n;
 	
-exit:
+	n = sendto( inSock, (char *) me->probePtr, me->probeLen, 0, inDest, SockAddrGetSize( inDest ) );
+	err = map_socket_value_errno( inSock, n == (ssize_t) me->probeLen, n );
 	return( err );
 }
 
 //===========================================================================================================================
-//	DNSMessageExtractRecord
+//	ServiceBrowserCreate
 //===========================================================================================================================
 
+typedef struct SBDomain					SBDomain;
+typedef struct SBServiceType			SBServiceType;
+typedef struct SBServiceBrowse			SBServiceBrowse;
+typedef struct SBServiceInstance		SBServiceInstance;
+typedef struct SBIPAddress				SBIPAddress;
+
+struct ServiceBrowserPrivate
+{
+	CFRuntimeBase					base;				// CF object base.
+	dispatch_queue_t				queue;				// Queue for service browser's events.
+	DNSServiceRef					connection;			// Shared connection for DNS-SD ops.
+	DNSServiceRef					domainsQuery;		// Query for recommended browsing domains.
+	char *							domain;				// If non-null, then browsing is limited to this domain.
+	StringListItem *				serviceTypeList;	// If non-null, then browsing is limited to these service types.
+	ServiceBrowserCallback_f		userCallback;		// User's callback. Called when browsing stops.
+	void *							userContext;		// User's callback context.
+	SBDomain *						domainList;			// List of domains and their browse results.
+	dispatch_source_t				stopTimer;			// Timer to stop browsing after browseTimeSecs.
+	uint32_t						ifIndex;			// If non-zero, then browsing is limited to this interface.
+	unsigned int					browseTimeSecs;		// Amount of time to spend browsing in seconds.
+	Boolean							includeAWDL;		// True if the IncludeAWDL flag should be used for DNS-SD ops that
+														// use the "any" interface.
+};
+
+struct SBDomain
+{
+	SBDomain *				next;			// Next domain object in list.
+	ServiceBrowserRef		browser;		// Pointer to parent service browser.
+	char *					name;			// Name of the domain.
+	DNSServiceRef			servicesQuery;	// Query for services (_services._dns-sd._udp.<domain> PTR record) in domain.
+	SBServiceType *			typeList;		// List of service types to browse for in this domain.
+};
+
+struct SBServiceType
+{
+	SBServiceType *			next;		// Next service type object in list.
+	char *					name;		// Name of the service type.
+	SBServiceBrowse *		browseList;	// List of browses for this service type.
+};
+
+struct SBServiceBrowse
+{
+	SBServiceBrowse *		next;			// Next browse object in list.
+	ServiceBrowserRef		browser;		// Pointer to parent service browser.
+	DNSServiceRef			browse;			// Reference to DNSServiceBrowse op.
+	SBServiceInstance *		instanceList;	// List of service instances that were discovered by this browse.
+	uint64_t				startTicks;		// Value of UpTicks() when the browse op began.
+	uint32_t				ifIndex;		// If non-zero, then the browse is limited to this interface.
+};
+
+struct SBServiceInstance
+{
+	SBServiceInstance *		next;				// Next service instance object in list.
+	ServiceBrowserRef		browser;			// Pointer to parent service browser.
+	char *					name;				// Name of the service instance.
+	uint32_t				ifIndex;			// Index of interface over which this service instance was discovered.
+	uint64_t				discoverTimeUs;		// Time it took to discover this service instance in microseconds.
+	DNSServiceRef			resolve;			// Reference to DNSServiceResolve op for this service instance.
+	uint64_t				resolveStartTicks;	// Value of UpTicks() when the DNSServiceResolve op began.
+	uint64_t				resolveTimeUs;		// Time it took to resolve this service instance.
+	char *					hostname;			// Service instance's hostname. Result of DNSServiceResolve.
+	uint16_t				port;				// Service instance's port number. Result of DNSServiceResolve.
+	uint8_t *				txtPtr;				// Service instance's TXT record data. Result of DNSServiceResolve.
+	size_t					txtLen;				// Length of service instance's TXT record data.
+	DNSServiceRef			getAddrInfo;		// Reference to DNSServiceGetAddrInfo op for service instance's hostname.
+	uint64_t				gaiStartTicks;		// Value of UpTicks() when the DNSServiceGetAddrInfo op began.
+	SBIPAddress *			ipaddrList;			// List of IP addresses that the hostname resolved to.
+};
+
+struct SBIPAddress
+{
+	SBIPAddress *		next;			// Next IP address object in list.
+	sockaddr_ip			sip;			// IPv4 or IPv6 address.
+	uint64_t			resolveTimeUs;	// Time it took to resolve this IP address in microseconds.
+};
+
 typedef struct
 {
-	uint8_t		type[ 2 ];
-	uint8_t		class[ 2 ];
-	uint8_t		ttl[ 4 ];
-	uint8_t		rdLength[ 2 ];
-	uint8_t		rdata[ 1 ];
+	SBRDomain *		domainList;	// List of domains in which services were found.
+	int32_t			refCount;	// This object's reference count.
 	
-}	DNSRecordFields;
+}	ServiceBrowserResultsPrivate;
 
-check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
+static void		_ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError );
+static OSStatus	_ServiceBrowserAddDomain( ServiceBrowserRef inBrowser, const char *inDomain );
+static OSStatus	_ServiceBrowserRemoveDomain( ServiceBrowserRef inBrowser, const char *inName );
+static void		_ServiceBrowserTimerHandler( void *inContext );
+static void DNSSD_API
+	_ServiceBrowserDomainsQueryCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext );
+static void DNSSD_API
+	_ServiceBrowserServicesQueryCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext );
+static void DNSSD_API
+	_ServiceBrowserBrowseCallback(
+		DNSServiceRef		inSDRef,
+		DNSServiceFlags		inFlags,
+		uint32_t			inInterfaceIndex,
+		DNSServiceErrorType	inError,
+		const char *		inName,
+		const char *		inRegType,
+		const char *		inDomain,
+		void *				inContext );
+static void DNSSD_API
+	_ServiceBrowserResolveCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		const char *			inHostname,
+		uint16_t				inPort,
+		uint16_t				inTXTLen,
+		const unsigned char *	inTXTPtr,
+		void *					inContext );
+static void DNSSD_API
+	_ServiceBrowserGAICallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext );
+static OSStatus
+	_ServiceBrowserAddServiceType(
+		ServiceBrowserRef	inBrowser,
+		SBDomain *			inDomain,
+		const char *		inName,
+		uint32_t			inIfIndex );
+static OSStatus
+	_ServiceBrowserRemoveServiceType(
+		ServiceBrowserRef	inBrowser,
+		SBDomain *			inDomain,
+		const char *		inName,
+		uint32_t			inIfIndex );
+static OSStatus
+	_ServiceBrowserAddServiceInstance(
+		ServiceBrowserRef	inBrowser,
+		SBServiceBrowse *	inBrowse,
+		uint32_t			inIfIndex,
+		const char *		inName,
+		const char *		inRegType,
+		const char *		inDomain,
+		uint64_t			inDiscoverTimeUs );
+static OSStatus
+	_ServiceBrowserRemoveServiceInstance(
+		ServiceBrowserRef	inBrowser,
+		SBServiceBrowse *	inBrowse,
+		const char *		inName,
+		uint32_t			inIfIndex );
+static OSStatus
+	_ServiceBrowserAddIPAddress(
+		ServiceBrowserRef		inBrowser,
+		SBServiceInstance *		inInstance,
+		const struct sockaddr *	inSockAddr,
+		uint64_t				inResolveTimeUs );
+static OSStatus
+	_ServiceBrowserRemoveIPAddress(
+		ServiceBrowserRef		inBrowser,
+		SBServiceInstance *		inInstance,
+		const struct sockaddr *	inSockAddr );
+static OSStatus	_ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults );
+static OSStatus	_SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain );
+static void		_SBDomainFree( SBDomain *inDomain );
+static OSStatus	_SBServiceTypeCreate( const char *inName, SBServiceType **outType );
+static void		_SBServiceTypeFree( SBServiceType *inType );
+static OSStatus	_SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse );
+static void		_SBServiceBrowseFree( SBServiceBrowse *inBrowse );
+static OSStatus
+	_SBServiceInstanceCreate(
+		const char *			inName,
+		uint32_t				inIfIndex,
+		uint64_t				inDiscoverTimeUs,
+		ServiceBrowserRef		inBrowser,
+		SBServiceInstance **	outInstance );
+static void		_SBServiceInstanceFree( SBServiceInstance *inInstance );
+static OSStatus
+	_SBIPAddressCreate(
+		const struct sockaddr *	inSockAddr,
+		uint64_t				inResolveTimeUs,
+		SBIPAddress **			outIPAddress );
+static void		_SBIPAddressFree( SBIPAddress *inIPAddress );
+static void		_SBIPAddressFreeList( SBIPAddress *inList );
+static OSStatus	_SBRDomainCreate( const char *inName, SBRDomain **outDomain );
+static void		_SBRDomainFree( SBRDomain *inDomain );
+static OSStatus	_SBRServiceTypeCreate( const char *inName, SBRServiceType **outType );
+static void		_SBRServiceTypeFree( SBRServiceType *inType );
+static OSStatus
+	_SBRServiceInstanceCreate(
+		const char *			inName,
+		uint32_t				inInterfaceIndex,
+		const char *			inHostname,
+		uint16_t				inPort,
+		const uint8_t *			inTXTPtr,
+		size_t					inTXTLen,
+		uint64_t				inDiscoverTimeUs,
+		uint64_t				inResolveTimeUs,
+		SBRServiceInstance **	outInstance );
+static void		_SBRServiceInstanceFree( SBRServiceInstance *inInstance );
+static OSStatus
+	_SBRIPAddressCreate(
+		const struct sockaddr *	inSockAddr,
+		uint64_t				inResolveTimeUs,
+		SBRIPAddress **			outIPAddress );
+static void		_SBRIPAddressFree( SBRIPAddress *inIPAddress );
+
+#define ForgetSBIPAddressList( X )		ForgetCustom( X, _SBIPAddressFreeList )
+
+CF_CLASS_DEFINE( ServiceBrowser );
 
 static OSStatus
-	DNSMessageExtractRecord(
-		const uint8_t *		inMsgPtr,
-		size_t				inMsgLen,
-		const uint8_t *		inPtr,
-		uint8_t				inNameBuf[ kDomainNameLengthMax ],
-		uint16_t *			outType,
-		uint16_t *			outClass,
-		uint32_t *			outTTL,
-		const uint8_t **	outRDataPtr,
-		size_t *			outRDataLen,
-		const uint8_t **	outPtr )
+	ServiceBrowserCreate(
+		dispatch_queue_t	inQueue,
+		uint32_t			inInterfaceIndex,
+		const char *		inDomain,
+		unsigned int		inBrowseTimeSecs,
+		Boolean				inIncludeAWDL,
+		ServiceBrowserRef *	outBrowser )
 {
-	OSStatus					err;
-	const uint8_t * const		msgEnd = inMsgPtr + inMsgLen;
-	const uint8_t *				ptr;
-	const DNSRecordFields *		record;
-	size_t						rdLength;
+	OSStatus				err;
+	ServiceBrowserRef		obj;
 	
-	err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
-	require_noerr_quiet( err, exit );
-	require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr );
+	CF_OBJECT_CREATE( ServiceBrowser, obj, err, exit );
 	
-	record = (DNSRecordFields *) ptr;
-	rdLength = ReadBig16( record->rdLength );
-	require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
+	ReplaceDispatchQueue( &obj->queue, inQueue );
+	obj->ifIndex		= inInterfaceIndex;
+	if( inDomain )
+	{
+		obj->domain = strdup( inDomain );
+		require_action( obj->domain, exit, err = kNoMemoryErr );
+	}
+	obj->browseTimeSecs	= inBrowseTimeSecs;
+	obj->includeAWDL	= inIncludeAWDL;
 	
-	if( outType )		*outType		= ReadBig16( record->type );
-	if( outClass )		*outClass		= ReadBig16( record->class );
-	if( outTTL )		*outTTL			= ReadBig32( record->ttl );
-	if( outRDataPtr )	*outRDataPtr	= record->rdata;
-	if( outRDataLen )	*outRDataLen	= rdLength;
-	if( outPtr )		*outPtr			= record->rdata + rdLength;
+	*outBrowser = obj;
+	obj = NULL;
+	err = kNoErr;
 	
 exit:
+	CFReleaseNullSafe( obj );
 	return( err );
 }
 
 //===========================================================================================================================
-//	DNSMessageGetAnswerSection
+//	_ServiceBrowserFinalize
 //===========================================================================================================================
 
-static OSStatus	DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
+static void	_ServiceBrowserFinalize( CFTypeRef inObj )
+{
+	ServiceBrowserRef const		me = (ServiceBrowserRef) inObj;
+	StringListItem *			serviceType;
+	
+	dispatch_forget( &me->queue );
+	check( !me->connection );
+	check( !me->domainsQuery );
+	ForgetMem( &me->domain );
+	while( ( serviceType = me->serviceTypeList ) != NULL )
+	{
+		me->serviceTypeList = serviceType->next;
+		ForgetMem( &serviceType->str );
+		free( serviceType );
+	}
+	check( !me->domainList );
+	check( !me->stopTimer );
+}
+
+//===========================================================================================================================
+//	ServiceBrowserStart
+//===========================================================================================================================
+
+static void	_ServiceBrowserStart( void *inContext );
+
+static void	ServiceBrowserStart( ServiceBrowserRef me )
+{
+	CFRetain( me );
+	dispatch_async_f( me->queue, me, _ServiceBrowserStart );
+}
+
+static void	_ServiceBrowserStart( void *inContext )
 {
 	OSStatus					err;
-	const uint8_t * const		msgEnd	= inMsgPtr + inMsgLen;
-	unsigned int				questionCount, i;
-	const DNSHeader *			hdr;
-	const uint8_t *				ptr;
+	ServiceBrowserRef const		me = (ServiceBrowserRef) inContext;
 	
-	require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+	err = DNSServiceCreateConnection( &me->connection );
+	require_noerr( err, exit );
 	
-	hdr = (DNSHeader *) inMsgPtr;
-	questionCount = DNSHeaderGetQuestionCount( hdr );
+	err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+	require_noerr( err, exit );
 	
-	ptr = (uint8_t *)( hdr + 1 );
-	for( i = 0; i < questionCount; ++i )
+	if( me->domain )
+	{
+		err = _ServiceBrowserAddDomain( me, me->domain );
+		require_noerr( err, exit );
+	}
+	else
 	{
-		err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr );
+		DNSServiceRef		sdRef;
+		
+		sdRef = me->connection;
+		err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
+			"b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, _ServiceBrowserDomainsQueryCallback, me );
 		require_noerr( err, exit );
-		require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr );
-		ptr += 4;
+		
+		me->domainsQuery = sdRef;
 	}
 	
-	if( outPtr ) *outPtr = ptr;
-	err = kNoErr;
+	err = DispatchTimerCreate( dispatch_time_seconds( me->browseTimeSecs ), DISPATCH_TIME_FOREVER,
+		100 * kNanosecondsPerMillisecond, me->queue, _ServiceBrowserTimerHandler, NULL, me, &me->stopTimer );
+	require_noerr( err, exit );
+	dispatch_resume( me->stopTimer );
 	
 exit:
-	return( err );
+	if( err ) _ServiceBrowserStop( me, err );
 }
 
 //===========================================================================================================================
-//	DNSRecordDataToString
+//	ServiceBrowserAddServiceType
 //===========================================================================================================================
 
-static OSStatus
-	DNSRecordDataToString(
-		const void *	inRDataPtr,
-		size_t			inRDataLen,
-		unsigned int	inRDataType,
-		const void *	inMsgPtr,
-		size_t			inMsgLen,
-		char **			outString )
+static OSStatus	ServiceBrowserAddServiceType( ServiceBrowserRef me, const char *inServiceType )
 {
-	OSStatus					err;
-	const uint8_t * const		rdataPtr = (uint8_t *) inRDataPtr;
-	const uint8_t * const		rdataEnd = rdataPtr + inRDataLen;
-	char *						rdataStr;
-	const uint8_t *				ptr;
-	int							n;
-	char						domainNameStr[ kDNSServiceMaxDomainName ];
+	OSStatus				err;
+	StringListItem *		item;
+	StringListItem **		itemPtr;
+	StringListItem *		newItem = NULL;
 	
-	rdataStr = NULL;
-	if( inRDataType == kDNSServiceType_A )
+	for( itemPtr = &me->serviceTypeList; ( item = *itemPtr ) != NULL; itemPtr = &item->next )
 	{
-		require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
-		
-		ASPrintF( &rdataStr, "%.4a", rdataPtr );
-		require_action( rdataStr, exit, err = kNoMemoryErr );
+		if( strcmp( item->str, inServiceType ) == 0 ) break;
 	}
-	else if( inRDataType == kDNSServiceType_AAAA )
+	if( !item )
 	{
-		require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+		newItem = (StringListItem *) calloc( 1, sizeof( *newItem ) );
+		require_action( newItem, exit, err = kNoMemoryErr );
 		
-		ASPrintF( &rdataStr, "%.16a", rdataPtr );
-		require_action( rdataStr, exit, err = kNoMemoryErr );
+		newItem->str = strdup( inServiceType );
+		require_action( newItem->str, exit, err = kNoMemoryErr );
+		
+		*itemPtr = newItem;
+		newItem = NULL;
 	}
-	else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
-			( inRDataType == kDNSServiceType_NS ) )
+	err = kNoErr;
+	
+exit:
+	FreeNullSafe( newItem );
+	return( err );
+}
+
+//===========================================================================================================================
+//	ServiceBrowserSetCallback
+//===========================================================================================================================
+
+static void	ServiceBrowserSetCallback( ServiceBrowserRef me, ServiceBrowserCallback_f inCallback, void *inContext )
+{
+	me->userCallback	= inCallback;
+	me->userContext		= inContext;
+}
+
+//===========================================================================================================================
+//	ServiceBrowserResultsRetain
+//===========================================================================================================================
+
+static void	ServiceBrowserResultsRetain( ServiceBrowserResults *inResults )
+{
+	ServiceBrowserResultsPrivate * const		results = (ServiceBrowserResultsPrivate *) inResults;
+	
+	atomic_add_32( &results->refCount, 1 );
+}
+
+//===========================================================================================================================
+//	ServiceBrowserResultsRelease
+//===========================================================================================================================
+
+static void	ServiceBrowserResultsRelease( ServiceBrowserResults *inResults )
+{
+	ServiceBrowserResultsPrivate * const		results = (ServiceBrowserResultsPrivate *) inResults;
+	SBRDomain *									domain;
+	
+	if( atomic_add_and_fetch_32( &results->refCount, -1 ) == 0 )
 	{
-		if( inMsgPtr )
+		while( ( domain = inResults->domainList ) != NULL )
 		{
-			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
-			require_noerr( err, exit );
+			inResults->domainList = domain->next;
+			_SBRDomainFree( domain );
 		}
-		else
+		free( inResults );
+	}
+}
+
+//===========================================================================================================================
+//	_ServiceBrowserStop
+//===========================================================================================================================
+
+static void	_ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError )
+{
+	OSStatus				err;
+	SBDomain *				d;
+	SBServiceType *			t;
+	SBServiceBrowse *		b;
+	SBServiceInstance *		i;
+	
+	dispatch_source_forget( &me->stopTimer );
+	DNSServiceForget( &me->domainsQuery );
+	for( d = me->domainList; d; d = d->next )
+	{
+		DNSServiceForget( &d->servicesQuery );
+		for( t = d->typeList; t; t = t->next )
 		{
-			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
-			require_noerr( err, exit );
+			for( b = t->browseList; b; b = b->next )
+			{
+				DNSServiceForget( &b->browse );
+				for( i = b->instanceList; i; i = i->next )
+				{
+					DNSServiceForget( &i->resolve );
+					DNSServiceForget( &i->getAddrInfo );
+				}
+			}
 		}
-		
-		rdataStr = strdup( domainNameStr );
-		require_action( rdataStr, exit, err = kNoMemoryErr );
 	}
-	else if( inRDataType == kDNSServiceType_SRV )
+	DNSServiceForget( &me->connection );
+	
+	if( me->userCallback )
 	{
-		uint16_t			priority, weight, port;
-		const uint8_t *		target;
+		ServiceBrowserResults *		results = NULL;
 		
-		require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr );
+		err = _ServiceBrowserCreateResults( me, &results );
+		if( !err ) err = inError;
 		
-		priority	= ReadBig16( rdataPtr );
-		weight		= ReadBig16( rdataPtr + 2 );
-		port		= ReadBig16( rdataPtr + 4 );
-		target		= rdataPtr + 6;
-		
-		if( inMsgPtr )
-		{
-			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
-			require_noerr( err, exit );
-		}
-		else
-		{
-			err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
-			require_noerr( err, exit );
-		}
-		
-		ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
-		require_action( rdataStr, exit, err = kNoMemoryErr );
+		me->userCallback( results, err, me->userContext );
+		me->userCallback	= NULL;
+		me->userContext		= NULL;
+		if( results ) ServiceBrowserResultsRelease( results );
 	}
-	else if( inRDataType == kDNSServiceType_TXT )
+	
+	while( ( d = me->domainList ) != NULL )
 	{
-		require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
-		
-		if( inRDataLen == 1 )
-		{
-			ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
-			require_action( rdataStr, exit, err = kNoMemoryErr );
-		}
-		else
-		{
-			ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
-			require_action( rdataStr, exit, err = kNoMemoryErr );
-		}
+		me->domainList = d->next;
+		_SBDomainFree( d );
 	}
-	else if( inRDataType == kDNSServiceType_SOA )
+	CFRelease( me );
+}
+
+//===========================================================================================================================
+//	_ServiceBrowserAddDomain
+//===========================================================================================================================
+
+static OSStatus	_ServiceBrowserAddDomain( ServiceBrowserRef me, const char *inDomain )
+{
+	OSStatus		err;
+	SBDomain *		domain;
+	SBDomain **		domainPtr;
+	SBDomain *		newDomain = NULL;
+	
+	for( domainPtr = &me->domainList; ( domain = *domainPtr ) != NULL; domainPtr = &domain->next )
 	{
-		uint32_t		serial, refresh, retry, expire, minimum;
+		if( strcasecmp( domain->name, inDomain ) == 0 ) break;
+	}
+	require_action_quiet( !domain, exit, err = kDuplicateErr );
+	
+	err = _SBDomainCreate( inDomain, me, &newDomain );
+	require_noerr_quiet( err, exit );
+	
+	if( me->serviceTypeList )
+	{
+		const StringListItem *		item;
 		
-		if( inMsgPtr )
-		{
-			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
-			require_noerr( err, exit );
-			
-			require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
-			
-			rdataStr = strdup( domainNameStr );
-			require_action( rdataStr, exit, err = kNoMemoryErr );
-			
-			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
-			require_noerr( err, exit );
-		}
-		else
+		for( item = me->serviceTypeList; item; item = item->next )
 		{
-			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
-			require_noerr( err, exit );
-			
-			rdataStr = strdup( domainNameStr );
-			require_action( rdataStr, exit, err = kNoMemoryErr );
-			
-			err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+			err = _ServiceBrowserAddServiceType( me, newDomain, item->str, me->ifIndex );
+			if( err == kDuplicateErr ) err = kNoErr;
 			require_noerr( err, exit );
 		}
-		
-		require_action_quiet( ( ptr + 20 ) == rdataEnd, exit, err = kMalformedErr );
-		
-		serial	= ReadBig32( ptr );
-		refresh	= ReadBig32( ptr +  4 );
-		retry	= ReadBig32( ptr +  8 );
-		expire	= ReadBig32( ptr + 12 );
-		minimum	= ReadBig32( ptr + 16 );
-		
-		n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
-		require_action( n > 0, exit, err = kUnknownErr );
 	}
-	else if( inRDataType == kDNSServiceType_NSEC )
+	else
 	{
-		unsigned int		windowBlock, bitmapLen, i, recordType;
-		const uint8_t *		bitmapPtr;
+		char *				recordName;
+		DNSServiceFlags		flags;
+		DNSServiceRef		sdRef;
 		
-		if( inMsgPtr )
-		{
-			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
-			require_noerr( err, exit );
-		}
-		else
-		{
-			err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
-			require_noerr( err, exit );
-		}
+		ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
+		require_action( recordName, exit, err = kNoMemoryErr );
 		
-		require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+		flags = kDNSServiceFlagsShareConnection;
+		if( ( me->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
 		
-		rdataStr = strdup( domainNameStr );
-		require_action( rdataStr, exit, err = kNoMemoryErr );
+		sdRef = newDomain->browser->connection;
+		err = DNSServiceQueryRecord( &sdRef, flags, me->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
+			_ServiceBrowserServicesQueryCallback, newDomain );
+		free( recordName );
+		require_noerr( err, exit );
 		
-		for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
-		{
-			require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
-			
-			windowBlock	= ptr[ 0 ];
-			bitmapLen	= ptr[ 1 ];
-			bitmapPtr	= &ptr[ 2 ];
-			
-			require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
-			require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
-			
-			for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
-			{
-				if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
-				{
-					recordType = ( windowBlock * 256 ) + i;
-					n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
-					require_action( n > 0, exit, err = kUnknownErr );
-				}
-			}
-		}
+		newDomain->servicesQuery = sdRef;
+	}
+	
+	*domainPtr	= newDomain;
+	newDomain	= NULL;
+	err = kNoErr;
+	
+exit:
+	if( newDomain ) _SBDomainFree( newDomain );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_ServiceBrowserRemoveDomain
+//===========================================================================================================================
+
+static OSStatus	_ServiceBrowserRemoveDomain( ServiceBrowserRef me, const char *inName )
+{
+	OSStatus		err;
+	SBDomain *		domain;
+	SBDomain **		domainPtr;
+	
+	for( domainPtr = &me->domainList; ( domain = *domainPtr ) != NULL; domainPtr = &domain->next )
+	{
+		if( strcasecmp( domain->name, inName ) == 0 ) break;
 	}
-	else if( inRDataType == kDNSServiceType_MX )
+	
+	if( domain )
 	{
-		uint16_t			preference;
-		const uint8_t *		exchange;
-		
-		require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
-		
-		preference	= ReadBig16( rdataPtr );
-		exchange	= &rdataPtr[ 2 ];
-		
-		if( inMsgPtr )
-		{
-			err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
-			require_noerr( err, exit );
-		}
-		else
-		{
-			err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
-			require_noerr( err, exit );
-		}
-		
-		n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
-		require_action( n > 0, exit, err = kUnknownErr );
+		*domainPtr = domain->next;
+		_SBDomainFree( domain );
+		err = kNoErr;
 	}
 	else
 	{
-		err = kNotHandledErr;
-		goto exit;
+		err = kNotFoundErr;
 	}
 	
-	check( rdataStr );
-	*outString = rdataStr;
-	rdataStr = NULL;
-	err = kNoErr;
+	return( err );
+}
+
+//===========================================================================================================================
+//	_ServiceBrowserTimerHandler
+//===========================================================================================================================
+
+static void	_ServiceBrowserTimerHandler( void *inContext )
+{
+	ServiceBrowserRef const		me = (ServiceBrowserRef) inContext;
+	
+	_ServiceBrowserStop( me, kNoErr );
+}
+
+//===========================================================================================================================
+//	_ServiceBrowserDomainsQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_ServiceBrowserDomainsQueryCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext )
+{
+	ServiceBrowserRef const		me = (ServiceBrowserRef) inContext;
+	OSStatus					err;
+	char						domainStr[ kDNSServiceMaxDomainName ];
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inFullName );
+	Unused( inType );
+	Unused( inClass );
+	Unused( inTTL );
+	
+	require_noerr( inError, exit );
+	
+	err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
+	require_noerr( err, exit );
+	
+	if( inFlags & kDNSServiceFlagsAdd )
+	{
+		err = _ServiceBrowserAddDomain( me, domainStr );
+		if( err == kDuplicateErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
+	else
+	{
+		err = _ServiceBrowserRemoveDomain( me, domainStr );
+		if( err == kNotFoundErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
 	
 exit:
-	FreeNullSafe( rdataStr );
-	return( err );
+	return;
 }
 
 //===========================================================================================================================
-//	DomainNameAppendString
+//	_ServiceBrowserServicesQueryCallback
 //===========================================================================================================================
 
-static OSStatus
-	DomainNameAppendString(
-		uint8_t			inDomainName[ kDomainNameLengthMax ],
-		const char *	inString,
-		uint8_t **		outEndPtr )
+static void DNSSD_API
+	_ServiceBrowserServicesQueryCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		uint16_t				inType,
+		uint16_t				inClass,
+		uint16_t				inRDataLen,
+		const void *			inRDataPtr,
+		uint32_t				inTTL,
+		void *					inContext )
 {
 	OSStatus					err;
-	const char *				src;
-	uint8_t *					root;
-	const uint8_t * const		nameLim = inDomainName + kDomainNameLengthMax;
+	SBDomain * const			domain	= (SBDomain *) inContext;
+	ServiceBrowserRef const		me		= domain->browser;
+	const uint8_t *				src;
+	const uint8_t *				end;
+	uint8_t *					dst;
+	int							i;
+	uint8_t						serviceType[ 2 * ( 1 + kDomainLabelLengthMax ) + 1 ];
+	char						serviceTypeStr[ kDNSServiceMaxDomainName ];
 	
-	for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
-	require_action_quiet( root < nameLim, exit, err = kMalformedErr );
+	Unused( inSDRef );
+	Unused( inFullName );
+	Unused( inTTL );
+	Unused( inType );
+	Unused( inClass );
 	
-	// If the string is a single dot, denoting the root domain, then there are no non-empty labels.
+	require_noerr( inError, exit );
 	
-	src = inString;
-	if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src;
-	while( *src )
+	check( inType  == kDNSServiceType_PTR );
+	check( inClass == kDNSServiceClass_IN );
+	
+	// The first two labels of the domain name in the RDATA describe a service type.
+	// See <https://tools.ietf.org/html/rfc6763#section-9>.
+	
+	src = (const uint8_t *) inRDataPtr;
+	end = src + inRDataLen;
+	dst = serviceType;
+	for( i = 0; i < 2; ++i )
 	{
-		uint8_t * const				label		= root;
-		const uint8_t * const		labelLim	= Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
-		uint8_t *					dst;
-		int							c;
-		size_t						labelLen;
+		size_t		labelLen;
 		
-		dst = &label[ 1 ];
-		while( *src && ( ( c = *src++ ) != '.' ) )
-		{
-			if( c == '\\' )
-			{
-				require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
-				c = *src++;
-				if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
-				{
-					const int		decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
-					
-					if( decimal <= 255 )
-					{
-						c = decimal;
-						src += 2;
-					}
-				}
-			}
-			require_action_quiet( dst < labelLim, exit, err = kOverrunErr );
-			*dst++ = (uint8_t) c;
-		}
+		require_action_quiet( ( end - src ) > 0, exit, err = kUnderrunErr );
 		
-		labelLen = (size_t)( dst - &label[ 1 ] );
-		require_action_quiet( labelLen > 0, exit, err = kMalformedErr );
+		labelLen = *src;
+		require_action_quiet( ( labelLen > 0 ) && ( labelLen <= kDomainLabelLengthMax ), exit, err = kMalformedErr );
+		require_action_quiet( ( (size_t)( end - src ) ) >= ( 1 + labelLen ), exit, err = kUnderrunErr );
 		
-		label[ 0 ] = (uint8_t) labelLen;
-		root = dst;
-		*root = 0;
+		memcpy( dst, src, 1 + labelLen );
+		src += 1 + labelLen;
+		dst += 1 + labelLen;
 	}
+	*dst = 0;
 	
-	if( outEndPtr ) *outEndPtr = root + 1;
-	err = kNoErr;
+	err = DomainNameToString( serviceType, NULL, serviceTypeStr, NULL );
+	require_noerr( err, exit );
+	
+	if( inFlags & kDNSServiceFlagsAdd )
+	{
+		err = _ServiceBrowserAddServiceType( me, domain, serviceTypeStr, inInterfaceIndex );
+		if( err == kDuplicateErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
+	else
+	{
+		err = _ServiceBrowserRemoveServiceType( me, domain, serviceTypeStr, inInterfaceIndex );
+		if( err == kNotFoundErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
 	
 exit:
-	return( err );
+	return;
 }
 
 //===========================================================================================================================
-//	DomainNameEqual
+//	_ServiceBrowserBrowseCallback
 //===========================================================================================================================
 
-static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
+static void DNSSD_API
+	_ServiceBrowserBrowseCallback(
+		DNSServiceRef		inSDRef,
+		DNSServiceFlags		inFlags,
+		uint32_t			inInterfaceIndex,
+		DNSServiceErrorType	inError,
+		const char *		inName,
+		const char *		inRegType,
+		const char *		inDomain,
+		void *				inContext )
 {
-	const uint8_t *		p1 = inName1;
-	const uint8_t *		p2 = inName2;
-	unsigned int		len;
+	OSStatus					err;
+	const uint64_t				nowTicks	= UpTicks();
+	SBServiceBrowse * const		browse		= (SBServiceBrowse *) inContext;
+	ServiceBrowserRef const		me			= (ServiceBrowserRef) browse->browser;
 	
-	for( ;; )
+	Unused( inSDRef );
+	
+	require_noerr( inError, exit );
+	
+	if( inFlags & kDNSServiceFlagsAdd )
 	{
-		if( ( len = *p1++ ) != *p2++ ) return( false );
-		if( len == 0 ) break;
-		for( ; len > 0; ++p1, ++p2, --len )
-		{
-			if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false );
-		}
+		err = _ServiceBrowserAddServiceInstance( me, browse, inInterfaceIndex, inName, inRegType, inDomain,
+			UpTicksToMicroseconds( nowTicks - browse->startTicks ) );
+		if( err == kDuplicateErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
+	else
+	{
+		err = _ServiceBrowserRemoveServiceInstance( me, browse, inName, inInterfaceIndex );
+		if( err == kNotFoundErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
+	
+exit:
+	return;
+}
+
+//===========================================================================================================================
+//	_ServiceBrowserResolveCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	_ServiceBrowserResolveCallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inFullName,
+		const char *			inHostname,
+		uint16_t				inPort,
+		uint16_t				inTXTLen,
+		const unsigned char *	inTXTPtr,
+		void *					inContext )
+{
+	OSStatus						err;
+	const uint64_t					nowTicks	= UpTicks();
+	SBServiceInstance * const		instance	= (SBServiceInstance *) inContext;
+	ServiceBrowserRef const			me			= (ServiceBrowserRef) instance->browser;
+	
+	Unused( inSDRef );
+	Unused( inFlags );
+	Unused( inInterfaceIndex );
+	Unused( inFullName );
+	
+	require_noerr( inError, exit );
+	
+	if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
+	{
+		FreeNullSafe( instance->txtPtr );
+		instance->txtPtr = memdup( inTXTPtr, inTXTLen );
+		require_action( instance->txtPtr, exit, err = kNoMemoryErr );
+		
+		instance->txtLen = inTXTLen;
+	}
+	
+	instance->port = ntohs( inPort );
+	
+	if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
+	{
+		DNSServiceRef		sdRef;
+		
+		if( !instance->hostname ) instance->resolveTimeUs = UpTicksToMicroseconds( nowTicks - instance->resolveStartTicks );
+		
+		err = ReplaceString( &instance->hostname, NULL, inHostname, kSizeCString );
+		require_noerr( err, exit );
+		
+		DNSServiceForget( &instance->getAddrInfo );
+		ForgetSBIPAddressList( &instance->ipaddrList );
+		
+		sdRef = me->connection;
+		instance->gaiStartTicks = UpTicks();
+		err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
+			kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, _ServiceBrowserGAICallback, instance );
+		require_noerr( err, exit );
+		
+		instance->getAddrInfo = sdRef;
 	}
-	return( true );
-}
-
-//===========================================================================================================================
-//	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 );
+exit:
+	return;
 }
 
 //===========================================================================================================================
-//	DomainNameFromString
+//	_ServiceBrowserGAICallback
 //===========================================================================================================================
 
-static OSStatus
-	DomainNameFromString(
-		uint8_t			inDomainName[ kDomainNameLengthMax ],
-		const char *	inString,
-		uint8_t **		outEndPtr )
+static void DNSSD_API
+	_ServiceBrowserGAICallback(
+		DNSServiceRef			inSDRef,
+		DNSServiceFlags			inFlags,
+		uint32_t				inInterfaceIndex,
+		DNSServiceErrorType		inError,
+		const char *			inHostname,
+		const struct sockaddr *	inSockAddr,
+		uint32_t				inTTL,
+		void *					inContext )
 {
-	inDomainName[ 0 ] = 0;
-	return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+	OSStatus						err;
+	const uint64_t					nowTicks	= UpTicks();
+	SBServiceInstance * const		instance	= (SBServiceInstance *) inContext;
+	ServiceBrowserRef const			me			= (ServiceBrowserRef) instance->browser;
+	
+	Unused( inSDRef );
+	Unused( inInterfaceIndex );
+	Unused( inHostname );
+	Unused( inTTL );
+	
+	require_noerr( inError, exit );
+	
+	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+	{
+		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+		goto exit;
+	}
+	
+	if( inFlags & kDNSServiceFlagsAdd )
+	{
+		err = _ServiceBrowserAddIPAddress( me, instance, inSockAddr,
+			UpTicksToMicroseconds( nowTicks - instance->gaiStartTicks ) );
+		if( err == kDuplicateErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
+	else
+	{
+		err = _ServiceBrowserRemoveIPAddress( me, instance, inSockAddr );
+		if( err == kNotFoundErr ) err = kNoErr;
+		require_noerr( err, exit );
+	}
+	
+exit:
+	return;
 }
 
 //===========================================================================================================================
-//	DomainNameToString
+//	_ServiceBrowserAddServiceType
 //===========================================================================================================================
 
 static OSStatus
-	DomainNameToString(
-		const uint8_t *		inDomainName,
-		const uint8_t *		inEnd,
-		char				inBuf[ kDNSServiceMaxDomainName ],
-		const uint8_t **	outNextPtr )
+	_ServiceBrowserAddServiceType(
+		ServiceBrowserRef	me,
+		SBDomain *			inDomain,
+		const char *		inName,
+		uint32_t			inIfIndex )
 {
-	OSStatus			err;
-	const uint8_t *		label;
-	uint8_t				labelLen;
-	const uint8_t *		nextLabel;
-	char *				dst;
-	const uint8_t *		src;
-	
-	require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr );
-	
-	// Convert each label up until the root label, i.e., the zero-length label.
+	OSStatus				err;
+	SBServiceType *			type;
+	SBServiceType **		typePtr;
+	SBServiceType *			newType		= NULL;
+	SBServiceBrowse *		browse;
+	SBServiceBrowse **		browsePtr;
+	SBServiceBrowse *		newBrowse	= NULL;
+	DNSServiceRef			sdRef;
+	DNSServiceFlags			flags;
 	
-	dst = inBuf;
-	for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+	for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
 	{
-		require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr );
-		
-		nextLabel = &label[ 1 ] + labelLen;
-		require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr );
-		require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr );
+		if( strcasecmp( type->name, inName ) == 0 ) break;
+	}
+	if( !type )
+	{
+		err = _SBServiceTypeCreate( inName, &newType );
+		require_noerr_quiet( err, exit );
 		
-		for( src = &label[ 1 ]; src < nextLabel; ++src )
-		{
-			if( isprint_safe( *src ) )
-			{
-				if( ( *src == '.' ) || ( *src == '\\' ) ||  ( *src == ' ' ) ) *dst++ = '\\';
-				*dst++ = (char) *src;
-			}
-			else
-			{
-				*dst++ = '\\';
-				*dst++ = '0' + (   *src / 100 );
-				*dst++ = '0' + ( ( *src /  10 ) % 10 );
-				*dst++ = '0' + (   *src         % 10 );
-			}
-		}
-		*dst++ = '.';
+		type = newType;
 	}
 	
-	// At this point, label points to the root label.
-	// If the root label was the only label, then write a dot for it.
+	for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+	{
+		if( browse->ifIndex == inIfIndex ) break;
+	}
+	require_action_quiet( !browse, exit, err = kDuplicateErr );
 	
-	if( label == inDomainName ) *dst++ = '.';
-	*dst = '\0';
-	if( outNextPtr ) *outNextPtr = label + 1;
-	err = kNoErr;
+	err = _SBServiceBrowseCreate( inIfIndex, me, &newBrowse );
+	require_noerr_quiet( err, exit );
+	
+	flags = kDNSServiceFlagsShareConnection;
+	if( ( newBrowse->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+	
+	sdRef = me->connection;
+	newBrowse->startTicks = UpTicks();
+	err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, _ServiceBrowserBrowseCallback,
+		newBrowse );
+	require_noerr( err, exit );
+	
+	newBrowse->browse = sdRef;
+	*browsePtr	= newBrowse;
+	newBrowse	= NULL;
+	
+	if( newType )
+	{
+		*typePtr	= newType;
+		newType		= NULL;
+	}
 	
 exit:
+	if( newBrowse )	_SBServiceBrowseFree( newBrowse );
+	if( newType )	_SBServiceTypeFree( newType );
 	return( err );
 }
 
 //===========================================================================================================================
-//	DNSMessageToText
+//	_ServiceBrowserRemoveServiceType
 //===========================================================================================================================
 
-#define DNSFlagsOpCodeToString( X ) (					\
-	( (X) == kDNSOpCode_Query )			? "Query"	:	\
-	( (X) == kDNSOpCode_InverseQuery )	? "IQuery"	:	\
-	( (X) == kDNSOpCode_Status )		? "Status"	:	\
-	( (X) == kDNSOpCode_Notify )		? "Notify"	:	\
-	( (X) == kDNSOpCode_Update )		? "Update"	:	\
-										  "Unassigned" )
-
-#define DNSFlagsRCodeToString( X ) (						\
-	( (X) == kDNSRCode_NoError )		? "NoError"		:	\
-	( (X) == kDNSRCode_FormatError )	? "FormErr"		:	\
-	( (X) == kDNSRCode_ServerFailure )	? "ServFail"	:	\
-	( (X) == kDNSRCode_NXDomain )		? "NXDomain"	:	\
-	( (X) == kDNSRCode_NotImplemented )	? "NotImp"		:	\
-	( (X) == kDNSRCode_Refused )		? "Refused"		:	\
-										  "???" )
-
 static OSStatus
-	DNSMessageToText(
-		const uint8_t *	inMsgPtr,
-		size_t			inMsgLen,
-		const Boolean	inMDNS,
-		const Boolean	inPrintRaw,
-		char **			outText )
+	_ServiceBrowserRemoveServiceType(
+		ServiceBrowserRef	me,
+		SBDomain *			inDomain,
+		const char *		inName,
+		uint32_t			inIfIndex )
 {
-	OSStatus					err;
-	DataBuffer					dataBuf;
-	size_t						len;
-	const DNSHeader *			hdr;
-	const uint8_t * const		msgEnd = inMsgPtr + inMsgLen;
-	const uint8_t *				ptr;
-	unsigned int				id, flags, opcode, rcode;
-	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;
-	id				= DNSHeaderGetID( hdr );
-	flags			= DNSHeaderGetFlags( hdr );
-	questionCount	= DNSHeaderGetQuestionCount( hdr );
-	answerCount		= DNSHeaderGetAnswerCount( hdr );
-	authorityCount	= DNSHeaderGetAuthorityCount( hdr );
-	additionalCount	= DNSHeaderGetAdditionalCount( hdr );
-	opcode			= DNSFlagsGetOpCode( flags );
-	rcode			= DNSFlagsGetRCode( flags );
-	
-	_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 ) );
-	_Append( "Question count:   %u\n", questionCount );
-	_Append( "Answer count:     %u\n", answerCount );
-	_Append( "Authority count:  %u\n", authorityCount );
-	_Append( "Additional count: %u\n", additionalCount );
+	OSStatus				err;
+	SBServiceType *			type;
+	SBServiceType **		typePtr;
+	SBServiceBrowse *		browse;
+	SBServiceBrowse **		browsePtr;
 	
-	ptr = (const uint8_t *) &hdr[ 1 ];
-	for( i = 0; i < questionCount; ++i )
-	{
-		unsigned int		qtype, qclass;
-		Boolean				isQU;
-		
-		err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr );
-		require_noerr( err, exit );
-		
-		if( ( msgEnd - ptr ) < 4 )
-		{
-			err = kUnderrunErr;
-			goto exit;
-		}
-		
-		qtype	= DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
-		qclass	= DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
-		ptr += 4;
-		
-		isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
-		if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
-		
-		if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
-		
-		_Append( "%s %2s %?2s%?2u %-5s\n",
-			nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
-			( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
-	}
+	Unused( me );
 	
-	totalRRCount = answerCount + authorityCount + additionalCount;
-	for( i = 0; i < totalRRCount; ++i )
+	for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
 	{
-		uint16_t			type;
-		uint16_t			class;
-		uint32_t			ttl;
-		const uint8_t *		rdataPtr;
-		size_t				rdataLen;
-		char *				rdataStr;
-		Boolean				cacheFlush;
-		uint8_t				name[ kDomainNameLengthMax ];
-		
-		err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
-		require_noerr( err, exit );
-		
-		err = DomainNameToString( name, NULL, nameStr, NULL );
-		require_noerr( err, exit );
-		
-		cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
-		if( inMDNS ) class &= ~kRRClassCacheFlushBit;
-		
-		rdataStr = NULL;
-		if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
-		if( !rdataStr )
-		{
-			ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX );
-			require_action( rdataStr, exit, err = kNoMemoryErr );
-		}
-		
-		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" );
-		
-		_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 );
+		if( strcasecmp( type->name, inName ) == 0 ) break;
 	}
-	_Append( "\n" );
+	require_action_quiet( type, exit, err = kNotFoundErr );
 	
-	err = DataBuffer_Append( &dataBuf, "", 1 );
-	require_noerr( err, exit );
+	for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+	{
+		if( browse->ifIndex == inIfIndex ) break;
+	}
+	require_action_quiet( browse, exit, err = kNotFoundErr );
 	
-	err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
-	require_noerr( err, exit );
+	*browsePtr = browse->next;
+	_SBServiceBrowseFree( browse );
+	if( !type->browseList )
+	{
+		*typePtr = type->next;
+		_SBServiceTypeFree( type );
+	}
+	err = kNoErr;
 	
 exit:
-	DataBuffer_Free( &dataBuf );
 	return( err );
 }
 
 //===========================================================================================================================
-//	WriteDNSQueryMessage
+//	_ServiceBrowserAddServiceInstance
 //===========================================================================================================================
 
 static OSStatus
-	WriteDNSQueryMessage(
-		uint8_t			inMsg[ kDNSQueryMessageMaxLen ],
-		uint16_t		inMsgID,
-		uint16_t		inFlags,
-		const char *	inQName,
-		uint16_t		inQType,
-		uint16_t		inQClass,
-		size_t *		outMsgLen )
+	_ServiceBrowserAddServiceInstance(
+		ServiceBrowserRef	me,
+		SBServiceBrowse *	inBrowse,
+		uint32_t			inIfIndex,
+		const char *		inName,
+		const char *		inRegType,
+		const char *		inDomain,
+		uint64_t			inDiscoverTimeUs )
 {
-	OSStatus				err;
-	DNSHeader * const		hdr = (DNSHeader *) inMsg;
-	uint8_t *				ptr;
-	size_t					msgLen;
+	OSStatus					err;
+	DNSServiceRef				sdRef;
+	SBServiceInstance *			instance;
+	SBServiceInstance **		instancePtr;
+	SBServiceInstance *			newInstance	= NULL;
 	
-	memset( hdr, 0, sizeof( *hdr ) );
-	DNSHeaderSetID( hdr, inMsgID );
-	DNSHeaderSetFlags( hdr, inFlags );
-	DNSHeaderSetQuestionCount( hdr, 1 );
+	for( instancePtr = &inBrowse->instanceList; ( instance = *instancePtr ) != NULL; instancePtr = &instance->next )
+	{
+		if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+	}
+	require_action_quiet( !instance, exit, err = kDuplicateErr );
 	
-	ptr = (uint8_t *)( hdr + 1 );
-	err = DomainNameFromString( ptr, inQName, &ptr );
+	err = _SBServiceInstanceCreate( inName, inIfIndex, inDiscoverTimeUs, me, &newInstance );
 	require_noerr_quiet( err, exit );
 	
-	DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
-	ptr += 4;
-	
-	msgLen = (size_t)( ptr - inMsg );
-	check( msgLen <= kDNSQueryMessageMaxLen );
+	sdRef = me->connection;
+	newInstance->resolveStartTicks = UpTicks();
+	err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
+		_ServiceBrowserResolveCallback, newInstance );
+	require_noerr( err, exit );
 	
-	if( outMsgLen ) *outMsgLen = msgLen;
+	newInstance->resolve = sdRef;
+	*instancePtr	= newInstance;
+	newInstance		= NULL;
 	
 exit:
+	if( newInstance ) _SBServiceInstanceFree( newInstance );
 	return( err );
 }
 
 //===========================================================================================================================
-//	DispatchSignalSourceCreate
+//	_ServiceBrowserRemoveServiceInstance
 //===========================================================================================================================
 
 static OSStatus
-	DispatchSignalSourceCreate(
-		int					inSignal,
-		DispatchHandler		inEventHandler,
-		void *				inContext,
-		dispatch_source_t *	outSource )
+	_ServiceBrowserRemoveServiceInstance(
+		ServiceBrowserRef	me,
+		SBServiceBrowse *	inBrowse,
+		const char *		inName,
+		uint32_t			inIfIndex )
 {
-	OSStatus				err;
-	dispatch_source_t		source;
+	OSStatus					err;
+	SBServiceInstance *			instance;
+	SBServiceInstance **		ptr;
 	
-	source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
-	require_action( source, exit, err = kUnknownErr );
+	Unused( me );
 	
-	dispatch_set_context( source, inContext );
-	dispatch_source_set_event_handler_f( source, inEventHandler );
+	for( ptr = &inBrowse->instanceList; ( instance = *ptr ) != NULL; ptr = &instance->next )
+	{
+		if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+	}
+	require_action_quiet( instance, exit, err = kNotFoundErr );
 	
-	*outSource = source;
+	*ptr = instance->next;
+	_SBServiceInstanceFree( instance );
 	err = kNoErr;
 	
 exit:
@@ -12599,63 +19826,72 @@ exit:
 }
 
 //===========================================================================================================================
-//	DispatchSocketSourceCreate
+//	_ServiceBrowserAddIPAddress
 //===========================================================================================================================
 
 static OSStatus
-	DispatchSocketSourceCreate(
-		SocketRef				inSock,
-		dispatch_source_type_t	inType,
-		dispatch_queue_t		inQueue,
-		DispatchHandler			inEventHandler,
-		DispatchHandler			inCancelHandler,
-		void *					inContext,
-		dispatch_source_t *		outSource )
+	_ServiceBrowserAddIPAddress(
+		ServiceBrowserRef		me,
+		SBServiceInstance *		inInstance,
+		const struct sockaddr *	inSockAddr,
+		uint64_t				inResolveTimeUs )
 {
-	OSStatus				err;
-	dispatch_source_t		source;
+	OSStatus			err;
+	SBIPAddress *		ipaddr;
+	SBIPAddress **		ipaddrPtr;
+	SBIPAddress *		newIPAddr = NULL;
 	
-	source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
-	require_action( source, exit, err = kUnknownErr );
+	Unused( me );
 	
-	dispatch_set_context( source, inContext );
-	dispatch_source_set_event_handler_f( source, inEventHandler );
-	dispatch_source_set_cancel_handler_f( source, inCancelHandler );
+	if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+	{
+		dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+		err = kTypeErr;
+		goto exit;
+	}
 	
-	*outSource = source;
+	for( ipaddrPtr = &inInstance->ipaddrList; ( ipaddr = *ipaddrPtr ) != NULL; ipaddrPtr = &ipaddr->next )
+	{
+		if( SockAddrCompareAddr( &ipaddr->sip, inSockAddr ) == 0 ) break;
+	}
+	require_action_quiet( !ipaddr, exit, err = kDuplicateErr );
+	
+	err = _SBIPAddressCreate( inSockAddr, inResolveTimeUs, &newIPAddr );
+	require_noerr_quiet( err, exit );
+	
+	*ipaddrPtr = newIPAddr;
+	newIPAddr = NULL;
 	err = kNoErr;
 	
 exit:
+	if( newIPAddr ) _SBIPAddressFree( newIPAddr );
 	return( err );
 }
 
 //===========================================================================================================================
-//	DispatchTimerCreate
+//	_ServiceBrowserRemoveIPAddress
 //===========================================================================================================================
 
 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 )
+	_ServiceBrowserRemoveIPAddress(
+		ServiceBrowserRef		me,
+		SBServiceInstance *		inInstance,
+		const struct sockaddr *	inSockAddr )
 {
-	OSStatus				err;
-	dispatch_source_t		timer;
+	OSStatus			err;
+	SBIPAddress *		ipaddr;
+	SBIPAddress **		ipaddrPtr;
 	
-	timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
-	require_action( timer, exit, err = kUnknownErr );
+	Unused( me );
 	
-	dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
-	dispatch_set_context( timer, inContext );
-	dispatch_source_set_event_handler_f( timer, inEventHandler );
-	dispatch_source_set_cancel_handler_f( timer, inCancelHandler );
+	for( ipaddrPtr = &inInstance->ipaddrList; ( ipaddr = *ipaddrPtr ) != NULL; ipaddrPtr = &ipaddr->next )
+	{
+		if( SockAddrCompareAddr( &ipaddr->sip.sa, inSockAddr ) == 0 ) break;
+	}
+	require_action_quiet( ipaddr, exit, err = kNotFoundErr );
 	
-	*outTimer = timer;
+	*ipaddrPtr = ipaddr->next;
+	_SBIPAddressFree( ipaddr );
 	err = kNoErr;
 	
 exit:
@@ -12663,116 +19899,186 @@ exit:
 }
 
 //===========================================================================================================================
-//	DispatchProcessMonitorCreate
+//	_ServiceBrowserCreateResults
 //===========================================================================================================================
 
-static OSStatus
-	DispatchProcessMonitorCreate(
-		pid_t				inPID,
-		unsigned long		inFlags,
-		dispatch_queue_t	inQueue,
-		DispatchHandler		inEventHandler,
-		DispatchHandler		inCancelHandler,
-		void *				inContext,
-		dispatch_source_t *	outMonitor )
+static OSStatus	_ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults )
 {
-	OSStatus				err;
-	dispatch_source_t		monitor;
+	OSStatus							err;
+	SBDomain *							d;
+	SBServiceType *						t;
+	SBServiceBrowse *					b;
+	SBServiceInstance *					i;
+	SBIPAddress *						a;
+	ServiceBrowserResultsPrivate *		results;
+	SBRDomain **						domainPtr;
+	
+	results = (ServiceBrowserResultsPrivate *) calloc( 1, sizeof( *results ) );
+	require_action( results, exit, err = kNoMemoryErr );
 	
-	monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
-		inQueue ? inQueue : dispatch_get_main_queue() );
-	require_action( monitor, exit, err = kUnknownErr );
+	results->refCount = 1;
 	
-	dispatch_set_context( monitor, inContext );
-	dispatch_source_set_event_handler_f( monitor, inEventHandler );
-	dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
+	domainPtr = &results->domainList;
+	for( d = me->domainList; d; d = d->next )
+	{
+		SBRDomain *				domain;
+		SBRServiceType **		typePtr;
+		
+		err = _SBRDomainCreate( d->name, &domain );
+		require_noerr_quiet( err, exit );
+		*domainPtr = domain;
+		 domainPtr = &domain->next;
+		
+		typePtr = &domain->typeList;
+		for( t = d->typeList; t; t = t->next )
+		{
+			SBRServiceType *			type;
+			SBRServiceInstance **		instancePtr;
+			
+			err = _SBRServiceTypeCreate( t->name, &type );
+			require_noerr_quiet( err, exit );
+			*typePtr = type;
+			 typePtr = &type->next;
+			
+			instancePtr = &type->instanceList;
+			for( b = t->browseList; b; b = b->next )
+			{
+				for( i = b->instanceList; i; i = i->next )
+				{
+					SBRServiceInstance *		instance;
+					SBRIPAddress **				ipaddrPtr;
+					
+					err = _SBRServiceInstanceCreate( i->name, i->ifIndex, i->hostname, i->port, i->txtPtr, i->txtLen,
+						i->discoverTimeUs, i->resolveTimeUs, &instance );
+					require_noerr_quiet( err, exit );
+					*instancePtr = instance;
+					 instancePtr = &instance->next;
+					
+					ipaddrPtr = &instance->ipaddrList;
+					for( a = i->ipaddrList; a; a = a->next )
+					{
+						SBRIPAddress *		ipaddr;
+						
+						err = _SBRIPAddressCreate( &a->sip.sa, a->resolveTimeUs, &ipaddr );
+						require_noerr_quiet( err, exit );
+						
+						*ipaddrPtr = ipaddr;
+						 ipaddrPtr = &ipaddr->next;
+					}
+				}
+			}
+		}
+	}
 	
-	*outMonitor = monitor;
+	*outResults = (ServiceBrowserResults *) results;
+	results = NULL;
 	err = kNoErr;
 	
 exit:
+	if( results ) ServiceBrowserResultsRelease( (ServiceBrowserResults *) results );
 	return( err );
 }
 
 //===========================================================================================================================
-//	ServiceTypeDescription
+//	_SBDomainCreate
 //===========================================================================================================================
 
-typedef struct
+static OSStatus	_SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain )
 {
-	const char *		name;			// Name of the service type in two-label "_service._proto" format.
-	const char *		description;	// Description of the service type.
+	OSStatus		err;
+	SBDomain *		obj;
 	
-}	ServiceType;
+	obj = (SBDomain *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
+	
+	obj->browser = inBrowser;
+	
+	*outDomain = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) _SBDomainFree( obj );
+	return( err );
+}
 
-// A Non-comprehensive table of DNS-SD service types
+//===========================================================================================================================
+//	_SBDomainFree
+//===========================================================================================================================
 
-static const ServiceType		kServiceTypes[] =
+static void	_SBDomainFree( SBDomain *inDomain )
 {
-	{ "_acp-sync._tcp",			"AirPort Base Station Sync" },
-	{ "_adisk._tcp",			"Automatic Disk Discovery" },
-	{ "_afpovertcp._tcp",		"Apple File Sharing" },
-	{ "_airdrop._tcp",			"AirDrop" },
-	{ "_airplay._tcp",			"AirPlay" },
-	{ "_airport._tcp",			"AirPort Base Station" },
-	{ "_daap._tcp",				"Digital Audio Access Protocol (iTunes)" },
-	{ "_eppc._tcp",				"Remote AppleEvents" },
-	{ "_ftp._tcp",				"File Transfer Protocol" },
-	{ "_home-sharing._tcp",		"Home Sharing" },
-	{ "_homekit._tcp",			"HomeKit" },
-	{ "_http._tcp",				"World Wide Web HTML-over-HTTP" },
-	{ "_https._tcp",			"HTTP over SSL/TLS" },
-	{ "_ipp._tcp",				"Internet Printing Protocol" },
-	{ "_ldap._tcp",				"Lightweight Directory Access Protocol" },
-	{ "_mediaremotetv._tcp",	"Media Remote" },
-	{ "_net-assistant._tcp",	"Apple Remote Desktop" },
-	{ "_od-master._tcp",		"OpenDirectory Master" },
-	{ "_nfs._tcp",				"Network File System" },
-	{ "_presence._tcp",			"Peer-to-peer messaging / Link-Local Messaging" },
-	{ "_pdl-datastream._tcp",	"Printer Page Description Language Data Stream" },
-	{ "_raop._tcp",				"Remote Audio Output Protocol" },
-	{ "_rfb._tcp",				"Remote Frame Buffer" },
-	{ "_scanner._tcp",			"Bonjour Scanning" },
-	{ "_smb._tcp",				"Server Message Block over TCP/IP" },
-	{ "_sftp-ssh._tcp",			"Secure File Transfer Protocol over SSH" },
-	{ "_sleep-proxy._udp",		"Sleep Proxy Server" },
-	{ "_ssh._tcp",				"SSH Remote Login Protocol" },
-	{ "_teleport._tcp",			"teleport" },
-	{ "_tftp._tcp",				"Trivial File Transfer Protocol" },
-	{ "_workstation._tcp",		"Workgroup Manager" },
-	{ "_webdav._tcp",			"World Wide Web Distributed Authoring and Versioning (WebDAV)" },
-	{ "_webdavs._tcp",			"WebDAV over SSL/TLS" }
-};
+	SBServiceType *		type;
+	
+	ForgetMem( &inDomain->name );
+	DNSServiceForget( &inDomain->servicesQuery );
+	while( ( type = inDomain->typeList ) != NULL )
+	{
+		inDomain->typeList = type->next;
+		_SBServiceTypeFree( type );
+	}
+	free( inDomain );
+}
+
+//===========================================================================================================================
+//	_SBServiceTypeCreate
+//===========================================================================================================================
+
+static OSStatus	_SBServiceTypeCreate( const char *inName, SBServiceType **outType )
+{
+	OSStatus			err;
+	SBServiceType *		obj;
+	
+	obj = (SBServiceType *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
+	
+	*outType = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) _SBServiceTypeFree( obj );
+	return( err );
+}
+
+//===========================================================================================================================
+//	_SBServiceTypeFree
+//===========================================================================================================================
 
-static const char *	ServiceTypeDescription( const char *inName )
+static void	_SBServiceTypeFree( SBServiceType *inType )
 {
-	const ServiceType *				serviceType;
-	const ServiceType * const		end = kServiceTypes + countof( kServiceTypes );
+	SBServiceBrowse *		browse;
 	
-	for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
+	ForgetMem( &inType->name );
+	while( ( browse = inType->browseList ) != NULL )
 	{
-		if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description );
+		inType->browseList = browse->next;
+		_SBServiceBrowseFree( browse );
 	}
-	return( NULL );
+	free( inType );
 }
 
 //===========================================================================================================================
-//	SocketContextCreate
+//	_SBServiceBrowseCreate
 //===========================================================================================================================
 
-static OSStatus	SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+static OSStatus	_SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse )
 {
-	OSStatus			err;
-	SocketContext *		context;
-	
-	context = (SocketContext *) calloc( 1, sizeof( *context ) );
-	require_action( context, exit, err = kNoMemoryErr );
+	OSStatus				err;
+	SBServiceBrowse *		obj;
 	
-	context->refCount		= 1;
-	context->sock			= inSock;
-	context->userContext	= inUserContext;
+	obj = (SBServiceBrowse *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
 	
-	*outContext = context;
+	obj->ifIndex = inIfIndex;
+	obj->browser = inBrowser;
+	*outBrowse = obj;
 	err = kNoErr;
 	
 exit:
@@ -12780,52 +20086,87 @@ exit:
 }
 
 //===========================================================================================================================
-//	SocketContextRetain
+//	_SBServiceBrowseFree
 //===========================================================================================================================
 
-static SocketContext *	SocketContextRetain( SocketContext *inContext )
+static void	_SBServiceBrowseFree( SBServiceBrowse *inBrowse )
 {
-	++inContext->refCount;
-	return( inContext );
+	SBServiceInstance *		instance;
+	
+	DNSServiceForget( &inBrowse->browse );
+	while( ( instance = inBrowse->instanceList ) != NULL )
+	{
+		inBrowse->instanceList = instance->next;
+		_SBServiceInstanceFree( instance );
+	}
+	free( inBrowse );
 }
 
 //===========================================================================================================================
-//	SocketContextRelease
+//	_SBServiceInstanceCreate
 //===========================================================================================================================
 
-static void	SocketContextRelease( SocketContext *inContext )
+static OSStatus
+	_SBServiceInstanceCreate(
+		const char *			inName,
+		uint32_t				inIfIndex,
+		uint64_t				inDiscoverTimeUs,
+		ServiceBrowserRef		inBrowser,
+		SBServiceInstance **	outInstance )
 {
-	if( --inContext->refCount == 0 )
-	{
-		ForgetSocket( &inContext->sock );
-		free( inContext );
-	}
+	OSStatus				err;
+	SBServiceInstance *		obj;
+	
+	obj = (SBServiceInstance *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
+	
+	obj->ifIndex		= inIfIndex;
+	obj->discoverTimeUs	= inDiscoverTimeUs;
+	obj->browser		= inBrowser;
+	
+	*outInstance = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( obj ) _SBServiceInstanceFree( obj );
+	return( err );
 }
 
 //===========================================================================================================================
-//	SocketContextCancelHandler
+//	_SBServiceInstanceFree
 //===========================================================================================================================
 
-static void	SocketContextCancelHandler( void *inContext )
+static void	_SBServiceInstanceFree( SBServiceInstance *inInstance )
 {
-	SocketContextRelease( (SocketContext *) inContext );
+	ForgetMem( &inInstance->name );
+	DNSServiceForget( &inInstance->resolve );
+	ForgetMem( &inInstance->hostname );
+	ForgetMem( &inInstance->txtPtr );
+	DNSServiceForget( &inInstance->getAddrInfo );
+	ForgetSBIPAddressList( &inInstance->ipaddrList );
+	free( inInstance );
 }
 
 //===========================================================================================================================
-//	StringToInt32
+//	_SBIPAddressCreate
 //===========================================================================================================================
 
-static OSStatus	StringToInt32( const char *inString, int32_t *outValue )
+static OSStatus	_SBIPAddressCreate( const struct sockaddr *inSockAddr, uint64_t inResolveTimeUs, SBIPAddress **outIPAddress )
 {
-	OSStatus		err;
-	long			value;
-	char *			endPtr;
+	OSStatus			err;
+	SBIPAddress *		obj;
 	
-	value = strtol( inString, &endPtr, 0 );
-	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
-	require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr );
+	obj = (SBIPAddress *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
 	
-	*outValue = (int32_t) value;
+	SockAddrCopy( inSockAddr, &obj->sip );
+	obj->resolveTimeUs = inResolveTimeUs;
+	
+	*outIPAddress = obj;
 	err = kNoErr;
 	
 exit:
@@ -12833,183 +20174,213 @@ exit:
 }
 
 //===========================================================================================================================
-//	StringToUInt32
+//	_SBIPAddressFree
 //===========================================================================================================================
 
-static OSStatus	StringToUInt32( const char *inString, uint32_t *outValue )
+static void _SBIPAddressFree( SBIPAddress *inIPAddress )
 {
-	OSStatus		err;
-	uint32_t		value;
-	char *			endPtr;
-	
-	value = (uint32_t) strtol( inString, &endPtr, 0 );
-	require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
-	
-	*outValue = value;
-	err = kNoErr;
+	free( inIPAddress );
+}
+
+//===========================================================================================================================
+//	_SBIPAddressFreeList
+//===========================================================================================================================
+
+static void	_SBIPAddressFreeList( SBIPAddress *inList )
+{
+	SBIPAddress *		ipaddr;
 	
-exit:
-	return( err );
+	while( ( ipaddr = inList ) != NULL )
+	{
+		inList = ipaddr->next;
+		_SBIPAddressFree( ipaddr );
+	}
 }
 
 //===========================================================================================================================
-//	StringToLongLong
+//	_SBRDomainCreate
 //===========================================================================================================================
 
-static OSStatus	StringToLongLong( const char *inString, long long *outValue )
+static OSStatus	_SBRDomainCreate( const char *inName, SBRDomain **outDomain )
 {
 	OSStatus		err;
-	long long		value;
-	char *			endPtr;
+	SBRDomain *		obj;
 	
-	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 );
+	obj = (SBRDomain *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
 	
-	*outValue = value;
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
+	
+	*outDomain = obj;
+	obj = NULL;
 	err = kNoErr;
 	
 exit:
+	if( obj ) _SBRDomainFree( obj );
 	return( err );
 }
 
 //===========================================================================================================================
-//	StringToARecordData
+//	_SBRDomainFree
 //===========================================================================================================================
 
-static OSStatus	StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+static void	_SBRDomainFree( SBRDomain *inDomain )
 {
-	OSStatus			err;
-	uint32_t *			addrPtr;
-	const size_t		addrLen = sizeof( *addrPtr );
-	const char *		end;
+	SBRServiceType *		type;
 	
-	addrPtr = (uint32_t *) malloc( addrLen );
-	require_action( addrPtr, exit, err = kNoMemoryErr );
+	ForgetMem( &inDomain->name );
+	while( ( type = inDomain->typeList ) != NULL )
+	{
+		inDomain->typeList = type->next;
+		_SBRServiceTypeFree( type );
+	}
+	free( inDomain );
+}
+
+//===========================================================================================================================
+//	_SBRServiceTypeCreate
+//===========================================================================================================================
+
+static OSStatus	_SBRServiceTypeCreate( const char *inName, SBRServiceType **outType )
+{
+	OSStatus				err;
+	SBRServiceType *		obj;
 	
-	err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
-		NULL, NULL, NULL, &end );
-	if( !err && ( *end != '\0' ) ) err = kMalformedErr;
-	require_noerr_quiet( err, exit );
+	obj = (SBRServiceType *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
 	
-	*addrPtr = HostToBig32( *addrPtr );
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
 	
-	*outPtr = (uint8_t *) addrPtr;
-	addrPtr = NULL;
-	*outLen = addrLen;
+	*outType = obj;
+	obj = NULL;
+	err = kNoErr;
 	
 exit:
-	FreeNullSafe( addrPtr );
+	if( obj ) _SBRServiceTypeFree( obj );
 	return( err );
 }
 
 //===========================================================================================================================
-//	StringToAAAARecordData
+//	_SBRServiceTypeFree
 //===========================================================================================================================
 
-static OSStatus	StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+static void	_SBRServiceTypeFree( SBRServiceType *inType )
 {
-	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;
+	SBRServiceInstance *		instance;
 	
-exit:
-	FreeNullSafe( addrPtr );
-	return( err );
+	ForgetMem( &inType->name );
+	while( ( instance = inType->instanceList ) != NULL )
+	{
+		inType->instanceList = instance->next;
+		_SBRServiceInstanceFree( instance );
+	}
+	free( inType );
 }
 
 //===========================================================================================================================
-//	StringToDomainName
+//	_SBRServiceInstanceCreate
 //===========================================================================================================================
 
-static OSStatus	StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
+static OSStatus
+	_SBRServiceInstanceCreate(
+		const char *			inName,
+		uint32_t				inInterfaceIndex,
+		const char *			inHostname,
+		uint16_t				inPort,
+		const uint8_t *			inTXTPtr,
+		size_t					inTXTLen,
+		uint64_t				inDiscoverTimeUs,
+		uint64_t				inResolveTimeUs,
+		SBRServiceInstance **	outInstance )
 {
-	OSStatus		err;
-	uint8_t *		namePtr;
-	size_t			nameLen;
-	uint8_t *		end;
-	uint8_t			nameBuf[ kDomainNameLengthMax ];
+	OSStatus					err;
+	SBRServiceInstance *		obj;
 	
-	err = DomainNameFromString( nameBuf, inString, &end );
-	require_noerr_quiet( err, exit );
+	obj = (SBRServiceInstance *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
 	
-	nameLen = (size_t)( end - nameBuf );
-	namePtr = memdup( nameBuf, nameLen );
-	require_action( namePtr, exit, err = kNoMemoryErr );
+	obj->name = strdup( inName );
+	require_action( obj->name, exit, err = kNoMemoryErr );
 	
-	*outPtr = namePtr;
-	namePtr = NULL;
-	if( outLen ) *outLen = nameLen;
+	if( inHostname )
+	{
+		obj->hostname = strdup( inHostname );
+		require_action( obj->hostname, exit, err = kNoMemoryErr );
+	}
+	if( inTXTLen > 0 )
+	{
+		obj->txtPtr = (uint8_t *) memdup( inTXTPtr, inTXTLen );
+		require_action( obj->txtPtr, exit, err = kNoMemoryErr );
+		obj->txtLen = inTXTLen;
+	}
+	obj->discoverTimeUs	= inDiscoverTimeUs;
+	obj->resolveTimeUs	= inResolveTimeUs;
+	obj->ifIndex		= inInterfaceIndex;
+	obj->port			= inPort;
+	
+	*outInstance = obj;
+	obj = NULL;
+	err = kNoErr;
 	
 exit:
+	if( obj ) _SBRServiceInstanceFree( obj );
 	return( err );
 }
 
-#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//	GetDefaultDNSServer
+//	_SBRServiceInstanceFree
 //===========================================================================================================================
 
-static OSStatus	GetDefaultDNSServer( sockaddr_ip *outAddr )
+static void	_SBRServiceInstanceFree( SBRServiceInstance *inInstance )
 {
-	OSStatus				err;
-	dns_config_t *			config;
-	struct sockaddr *		addr;
-	int32_t					i;
-	
-	config = dns_configuration_copy();
-	require_action( config, exit, err = kUnknownErr );
+	SBRIPAddress *		ipaddr;
 	
-	addr = NULL;
-	for( i = 0; i < config->n_resolver; ++i )
+	ForgetMem( &inInstance->name );
+	ForgetMem( &inInstance->hostname );
+	ForgetMem( &inInstance->txtPtr );
+	while( ( ipaddr = inInstance->ipaddrList ) != NULL )
 	{
-		const dns_resolver_t * const		resolver = config->resolver[ i ];
-		
-		if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
-		{
-			addr = resolver->nameserver[ 0 ];
-			break;
-		}
- 	}
-	require_action_quiet( addr, exit, err = kNotFoundErr );
+		inInstance->ipaddrList = ipaddr->next;
+		_SBRIPAddressFree( ipaddr );
+	}
+	free( inInstance );
+}
+
+//===========================================================================================================================
+//	_SBRIPAddressCreate
+//===========================================================================================================================
+
+static OSStatus
+	_SBRIPAddressCreate(
+		const struct sockaddr *	inSockAddr,
+		uint64_t				inResolveTimeUs,
+		SBRIPAddress **			outIPAddress )
+{
+	OSStatus			err;
+	SBRIPAddress *		obj;
 	
-	SockAddrCopy( addr, outAddr );
+	obj = (SBRIPAddress *) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	SockAddrCopy( inSockAddr, &obj->sip );
+	obj->resolveTimeUs = inResolveTimeUs;
+	
+	*outIPAddress = obj;
 	err = kNoErr;
 	
 exit:
-	if( config ) dns_configuration_free( config );
 	return( err );
 }
-#endif
 
 //===========================================================================================================================
-//	GetCurrentMicroTime
+//	_SBRIPAddressFree
 //===========================================================================================================================
 
-static MicroTime64	GetCurrentMicroTime( void )
+static void	_SBRIPAddressFree( SBRIPAddress *inIPAddress )
 {
-	struct timeval		now;
-	
-	TIMEVAL_ZERO( now );
-	gettimeofday( &now, NULL );
-	
-	return( (MicroTime64) TIMEVAL_USEC64( now ) );
+	free( inIPAddress );
 }
 
 //===========================================================================================================================
@@ -13853,3 +21224,24 @@ int	memicmp( const void *inP1, const void *inP2, size_t inLen )
 	return( 0 );
 }
 #endif
+
+//===========================================================================================================================
+//	FNV1
+//
+//	Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+uint32_t	FNV1( const void *inData, size_t inSize )
+{
+	const uint8_t *				src = (const uint8_t *) inData;
+	const uint8_t * const		end = src + inSize;
+	uint32_t					hash;
+	
+	hash = 0x811c9dc5U;
+	while( src != end )
+	{
+		hash *= 0x01000193;
+		hash ^= *src++;
+	}
+	return( hash );
+}
diff --git a/mDNSResponder/Makefile b/mDNSResponder/Makefile
index 78adbd5..9aaa08e 100644
--- a/mDNSResponder/Makefile
+++ b/mDNSResponder/Makefile
@@ -17,7 +17,7 @@
 
 include $(MAKEFILEPATH)/pb_makefiles/platform.make
 
-MVERS = "mDNSResponder-878.240.1"
+MVERS = "mDNSResponder-878.250.4"
 
 VER =
 ifneq ($(strip $(GCC_VERSION)),)
diff --git a/mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist b/mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist
new file mode 100644
index 0000000..66a8c49
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist
@@ -0,0 +1,627 @@
+<?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>Project</key>
+	<string>mDNSResponder</string>
+	<key>RadarComponents</key>
+	<dict>
+		<key>Name</key>
+		<string>mDNSResponder</string>
+		<key>Version</key>
+		<string>all</string>
+	</dict>
+	<key>Tests</key>
+	<array>
+		<dict>
+			<key>TestName</key>
+			<string>GAIPerf Advanced</string>
+			<key>Description</key>
+			<string>Tests correctness of resolving hostnames via DNS using the GAIPerf Advanced test suite.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>600</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>gaiperf</string>
+				<string>--suite</string>
+				<string>advanced</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--skipPathEval</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 1-1-1</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>1</string>
+				<string>--txtSize</string>
+				<string>1</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>1</string>
+				<string>--countAAAA</string>
+				<string>1</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 1-1-1 (No Additionals)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Responses from mdnsreplier contain no additional answers.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>1</string>
+				<string>--txtSize</string>
+				<string>1</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>1</string>
+				<string>--countAAAA</string>
+				<string>1</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--noAdditionals</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 10-100-2</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>10</string>
+				<string>--txtSize</string>
+				<string>100</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>2</string>
+				<string>--countAAAA</string>
+				<string>2</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 10-100-2 (No Additionals)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Responses from mdnsreplier contain no additonal answers.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>10</string>
+				<string>--txtSize</string>
+				<string>100</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>2</string>
+				<string>--countAAAA</string>
+				<string>2</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--noAdditionals</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 100-500-5</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>100</string>
+				<string>--txtSize</string>
+				<string>500</string>
+				<string>--browseTime</string>
+				<string>5</string>
+				<string>--countA</string>
+				<string>5</string>
+				<string>--countAAAA</string>
+				<string>5</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 100-500-5 (No Additionals)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Responses from mdnsreplier contain no additonal answers.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>100</string>
+				<string>--txtSize</string>
+				<string>500</string>
+				<string>--browseTime</string>
+				<string>5</string>
+				<string>--countA</string>
+				<string>5</string>
+				<string>--countAAAA</string>
+				<string>5</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--noAdditionals</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 1-1-1 (No Cache Flush)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Cache is not flushed beforehand.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>1</string>
+				<string>--txtSize</string>
+				<string>1</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>1</string>
+				<string>--countAAAA</string>
+				<string>1</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 1-1-1 (No Cache Flush, No Additionals)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>1</string>
+				<string>--txtSize</string>
+				<string>1</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>1</string>
+				<string>--countAAAA</string>
+				<string>1</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--noAdditionals</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 10-100-2 (No Cache Flush)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Cache is not flushed beforehand.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>10</string>
+				<string>--txtSize</string>
+				<string>100</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>2</string>
+				<string>--countAAAA</string>
+				<string>2</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 10-100-2 (No Cache Flush, No Additionals)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>10</string>
+				<string>--txtSize</string>
+				<string>100</string>
+				<string>--browseTime</string>
+				<string>3</string>
+				<string>--countA</string>
+				<string>2</string>
+				<string>--countAAAA</string>
+				<string>2</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--noAdditionals</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 100-500-5 (No Cache Flush)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Cache is not flushed beforehand.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>100</string>
+				<string>--txtSize</string>
+				<string>500</string>
+				<string>--browseTime</string>
+				<string>5</string>
+				<string>--countA</string>
+				<string>5</string>
+				<string>--countAAAA</string>
+				<string>5</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery 100-500-5 (No Cache Flush, No Additionals)</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>100</string>
+				<string>--txtSize</string>
+				<string>500</string>
+				<string>--browseTime</string>
+				<string>5</string>
+				<string>--countA</string>
+				<string>5</string>
+				<string>--countAAAA</string>
+				<string>5</string>
+				<string>--ipv4</string>
+				<string>--ipv6</string>
+				<string>--noAdditionals</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery w/Packet Drops 10</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>30</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>10</string>
+				<string>--txtSize</string>
+				<string>100</string>
+				<string>--browseTime</string>
+				<string>16</string>
+				<string>--countA</string>
+				<string>2</string>
+				<string>--countAAAA</string>
+				<string>2</string>
+				<string>--ipv6</string>
+				<string>--udrop</string>
+				<string>0.5</string>
+				<string>--mdrop</string>
+				<string>0.5</string>
+				<string>--maxDropCount</string>
+				<string>3</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNS Discovery w/Packet Drops 100</string>
+			<key>Description</key>
+			<string>Tests mDNS discovery and resolution of 100 service instances with 100-byte TXT records and two pairs of A and AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>30</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>mdnsdiscovery</string>
+				<string>--instanceCount</string>
+				<string>100</string>
+				<string>--txtSize</string>
+				<string>100</string>
+				<string>--browseTime</string>
+				<string>18</string>
+				<string>--countA</string>
+				<string>2</string>
+				<string>--countAAAA</string>
+				<string>2</string>
+				<string>--ipv6</string>
+				<string>--udrop</string>
+				<string>0.5</string>
+				<string>--mdrop</string>
+				<string>0.5</string>
+				<string>--maxDropCount</string>
+				<string>3</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--flushCache</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>DotLocal Queries</string>
+			<key>Description</key>
+			<string>Tests DNS and mDNS queries for domain names in the local domain.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<true/>
+			<key>Timeout</key>
+			<integer>40</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>dotlocal</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>TCP Fallback</string>
+			<key>Description</key>
+			<string>Tests mDNSResponder's TCP fallback mechanism, which is triggered by UDP responses with invalid message IDs that would otherwise be acceptable.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>60</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>gaiperf</string>
+				<string>--suite</string>
+				<string>basic</string>
+				<string>--format</string>
+				<string>json</string>
+				<string>--appendNewLine</string>
+				<string>--skipPathEval</string>
+				<string>--badUDPMode</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>mDNSResponder Leaks</string>
+			<key>Description</key>
+			<string>Checks mDNSResponder for memory leaks.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>10</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/bin/leaks</string>
+				<string>mDNSResponder</string>
+			</array>
+		</dict>
+	</array>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist b/mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist
new file mode 100644
index 0000000..750f16a
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist
@@ -0,0 +1,12 @@
+<?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>com.apple.security.network.client</key>
+	<true/>
+	<key>com.apple.security.network.server</key>
+	<true/>
+	<key>com.apple.SystemConfiguration.SCDynamicStore-write-access</key>
+	<true/>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
index 51fc67a..4714293 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
+++ b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
@@ -6875,7 +6875,7 @@ typedef struct
 #include <IOKit/IOKitLib.h>
 #include <dns_util.h>
 
-mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray)
+mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray, mDNSBool TCPKAOnly, mDNSBool supportsTCPKA)
 {
     mDNS *const m = &mDNSStorage;
     const domainlabel *const tp = (trans == mDNSTransport_UDP) ? (const domainlabel *)"\x4_udp" : (const domainlabel *)"\x4_tcp";
@@ -6884,6 +6884,14 @@ mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray)
     AuthRecord *rr;
     for (rr = m->ResourceRecords; rr; rr=rr->next)
     {
+        mDNSBool isKeepAliveRecord = mDNS_KeepaliveRecord(&rr->resrec);
+        // Skip over all other records if we are registering TCP KeepAlive records only
+        // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive
+        // supportsTCPKA is set to true if both policy and interface allow TCP Keepalive
+        if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA)) {
+            continue;
+        }
+
         if (rr->resrec.rrtype == kDNSType_SRV && SameDomainLabel(ThirdLabel(rr->resrec.name)->c, tp->c))
         {
             if (!portarray)
@@ -7013,6 +7021,7 @@ mDNSlocal void GetProxyRecords(DNSMessage *const msg, uint32_t *const numbytes,
 
             // Skip over all other records if we are registering TCP KeepAlive records only
             // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive
+            // supportsTCPKA is set to true if both policy and interface allow TCP Keepalive
             if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA))
                 continue;
 
@@ -7134,8 +7143,8 @@ mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool
                     mDNSOffloadCmd cmd;
                     mDNSPlatformMemZero(&cmd, sizeof(cmd)); // When compiling 32-bit, make sure top 32 bits of 64-bit pointers get initialized to zero
                     cmd.command       = cmd_mDNSOffloadRR;
-                    cmd.numUDPPorts   = GetPortArray(mDNSTransport_UDP, mDNSNULL);
-                    cmd.numTCPPorts   = GetPortArray(mDNSTransport_TCP, mDNSNULL);
+                    cmd.numUDPPorts   = GetPortArray(mDNSTransport_UDP, mDNSNULL, TCPKAOnly, supportsTCPKA);
+                    cmd.numTCPPorts   = GetPortArray(mDNSTransport_TCP, mDNSNULL, TCPKAOnly, supportsTCPKA);
                     cmd.numRRRecords  = CountProxyRecords(&cmd.rrBufferSize, intf, TCPKAOnly, supportsTCPKA);
                     cmd.compression   = sizeof(DNSMessageHeader);
 
@@ -7151,8 +7160,8 @@ mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool
                            cmd.tcpPorts.ptr, cmd.numTCPPorts);
 
                     if (msg && cmd.rrRecords.ptr) GetProxyRecords(msg, &cmd.rrBufferSize, cmd.rrRecords.ptr, TCPKAOnly, supportsTCPKA);
-                    if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr);
-                    if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr);
+                    if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr, TCPKAOnly, supportsTCPKA);
+                    if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr, TCPKAOnly, supportsTCPKA);
 
                     char outputData[2];
                     size_t outputDataSize = sizeof(outputData);
diff --git a/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj b/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
index bbbd12f..8067d93 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
+++ b/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
@@ -347,6 +347,7 @@
 		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 */; };
+		BDF8BB902208E2A800419B62 /* mDNSResponder.plist in Copy BATS test plist */ = {isa = PBXBuildFile; fileRef = BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */; };
 		D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */ = {isa = PBXBuildFile; fileRef = F5E11B5B04A28126019798ED /* dnssd_ipc.h */; };
 		D284BE580ADD80740027CCDF /* mDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBE9022EAF5A00000109 /* mDNS.c */; };
 		D284BE590ADD80740027CCDF /* uDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F70587CEF6001880B3 /* uDNS.c */; };
@@ -774,6 +775,17 @@
 			name = "Copy AppleInternal Logging Profile";
 			runOnlyForDeploymentPostprocessing = 1;
 		};
+		BDF8BB8A2208E09D00419B62 /* Copy BATS test plist */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 8;
+			dstPath = /AppleInternal/CoreOS/BATS/unit_tests;
+			dstSubfolderSpec = 0;
+			files = (
+				BDF8BB902208E2A800419B62 /* mDNSResponder.plist in Copy BATS test plist */,
+			);
+			name = "Copy BATS test plist";
+			runOnlyForDeploymentPostprocessing = 1;
+		};
 		D284BE6A0ADD80740027CCDF /* CopyFiles */ = {
 			isa = PBXCopyFilesBuildPhase;
 			buildActionMask = 8;
@@ -1006,6 +1018,7 @@
 		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>"; };
+		BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = "<group>"; };
 		D284BE730ADD80740027CCDF /* mDNSResponder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mDNSResponder; sourceTree = BUILT_PRODUCTS_DIR; };
 		D284BEB00ADD80920027CCDF /* dns-sd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "dns-sd"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D284BEBE0ADD809A0027CCDF /* libjdns_sd.jnilib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libjdns_sd.jnilib; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1297,6 +1310,7 @@
 				BD9BA7561EAF929C00658CCF /* Frameworks */,
 				BDB61842206ADB7700AFF600 /* LoggingProfiles */,
 				BD28AE8D207B88F600F0B257 /* Scripts */,
+				BDF8BB8E2208E26E00419B62 /* BATS */,
 			);
 			name = mDNSResponder;
 			sourceTree = "<group>";
@@ -1656,6 +1670,14 @@
 			path = AppleInternal;
 			sourceTree = "<group>";
 		};
+		BDF8BB8E2208E26E00419B62 /* BATS */ = {
+			isa = PBXGroup;
+			children = (
+				BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */,
+			);
+			path = BATS;
+			sourceTree = "<group>";
+		};
 		DB2CC4420662DCE500335AB3 /* Java Support */ = {
 			isa = PBXGroup;
 			children = (
@@ -2203,6 +2225,7 @@
 				8418673D15AB8BFF00BB7F70 /* Copy Base Logging Profile */,
 				BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */,
 				BD28AE8C207B888E00F0B257 /* Copy diagnose scripts */,
+				BDF8BB8A2208E09D00419B62 /* Copy BATS test plist */,
 			);
 			buildRules = (
 			);
@@ -3718,7 +3741,6 @@
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -3763,7 +3785,6 @@
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
 				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
 				CLANG_CXX_LIBRARY = "libc++";
-				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
 				CLANG_WARN_BOOL_CONVERSION = YES;
 				CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -4611,6 +4632,8 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+				CODE_SIGN_ENTITLEMENTS = "dnssdutil-entitlements.plist";
+				CODE_SIGN_IDENTITY = "-";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
@@ -4676,6 +4699,8 @@
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+				CODE_SIGN_ENTITLEMENTS = "dnssdutil-entitlements.plist";
+				CODE_SIGN_IDENTITY = "-";
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
diff --git a/mDNSResponder/mDNSShared/dns_sd.h b/mDNSResponder/mDNSShared/dns_sd.h
index 3495c6e..f76fbbd 100644
--- a/mDNSResponder/mDNSShared/dns_sd.h
+++ b/mDNSResponder/mDNSShared/dns_sd.h
@@ -66,7 +66,7 @@
  */
 
 #ifndef _DNS_SD_H
-#define _DNS_SD_H 8804001
+#define _DNS_SD_H 8805004
 
 #ifdef  __cplusplus
 extern "C" {



More information about the vc mailing list