[PATCH 02/10] mDNSResponder: Update to v878.50.17

Sebastian Huber sebastian.huber at embedded-brains.de
Fri Jun 19 11:02:16 UTC 2020


The sources can be obtained via:

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

Update #4010.
---
 mDNSResponder/Clients/dnssdutil.c             | 1550 ++++++++++++++---
 mDNSResponder/Makefile                        |    2 +-
 mDNSResponder/mDNSCore/DNSCommon.c            |    5 +-
 mDNSResponder/mDNSCore/mDNS.c                 |  133 +-
 mDNSResponder/mDNSCore/uDNS.c                 |   52 +-
 mDNSResponder/mDNSMacOSX/LegacyNATTraversal.c |    3 +-
 mDNSResponder/mDNSShared/CommonServices.h     |    6 +-
 mDNSResponder/mDNSShared/dns_sd.h             |    2 +-
 mDNSResponder/mDNSShared/dnsextd.conf         |   10 +-
 9 files changed, 1412 insertions(+), 351 deletions(-)

diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c
index d1f7c6ca..1bde0daf 100644
--- a/mDNSResponder/Clients/dnssdutil.c
+++ b/mDNSResponder/Clients/dnssdutil.c
@@ -1,5 +1,5 @@
 /*
-	Copyright (c) 2016-2017 Apple Inc. All rights reserved.
+	Copyright (c) 2016-2018 Apple Inc. All rights reserved.
 	
 	dnssdutil is a command-line utility for testing the DNS-SD API.
 */
@@ -9,10 +9,12 @@
 #include <CoreUtils/CommandLineUtils.h>
 #include <CoreUtils/DataBufferUtils.h>
 #include <CoreUtils/DebugServices.h>
+#include <CoreUtils/HTTPUtils.h>
 #include <CoreUtils/MiscUtils.h>
 #include <CoreUtils/NetUtils.h>
 #include <CoreUtils/PrintFUtils.h>
 #include <CoreUtils/RandomNumberUtils.h>
+#include <CoreUtils/SoftLinking.h>
 #include <CoreUtils/StringUtils.h>
 #include <CoreUtils/TickUtils.h>
 #include <dns_sd.h>
@@ -21,6 +23,7 @@
 #if( TARGET_OS_DARWIN )
 	#include <dnsinfo.h>
 	#include <libproc.h>
+	#include <netdb.h>
 	#include <sys/proc_info.h>
 #endif
 
@@ -83,6 +86,8 @@
 #define kDNSServiceProtocolDescriptors	\
 	"\x00" "IPv4\0"						\
 	"\x01" "IPv6\0"						\
+	"\x04" "UDP\0"						\
+	"\x05" "TCP\0"						\
 	"\x00"
 
 // (m)DNS
@@ -165,14 +170,14 @@ static int		gDNSSDFlag_SuppressUnusable		= false;
 static int		gDNSSDFlag_Timeout				= false;
 static int		gDNSSDFlag_UnicastResponse		= false;
 static int		gDNSSDFlag_Unique				= false;
+static int		gDNSSDFlag_WakeOnResolve		= false;
 
 #define DNSSDFlagsOption()								\
 	IntegerOption( 'f', "flags", &gDNSSDFlags, "flags",	\
-		"DNSServiceFlags to use. This value is bitwise ORed with other single flag options.", false )
+		"DNSServiceFlags as an integer. This value is bitwise ORed with other single flag options.", false )
 
-#define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME )									\
-	BooleanOption( SHORT_CHAR, Stringify( FLAG_NAME ), &gDNSSDFlag_ ## FLAG_NAME,	\
-		"Use kDNSServiceFlags" Stringify( FLAG_NAME ) "." )
+#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 )
@@ -186,6 +191,7 @@ static int		gDNSSDFlag_Unique				= false;
 #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
 
@@ -226,7 +232,7 @@ static const char *		gConnectionOpt = kConnectionArg_Normal;
 	"\n"																												\
 	"to specify the delegator by UUID.\n"																				\
 	"\n"																												\
-	"To not use a main connection at all, but instead perform operations on their own connections, use\n"				\
+	"To not use a main connection at all, but instead perform operations on their own implicit connections, use\n"		\
 	"\n"																												\
 	"    --no-connection\n"
 
@@ -234,8 +240,12 @@ static const char *		gConnectionOpt = kConnectionArg_Normal;
 
 // Help text for record data options
 
+#define kRDataArgPrefix_Domain			"domain:"
 #define kRDataArgPrefix_File			"file:"
 #define kRDataArgPrefix_HexString		"hex:"
+#define kRDataArgPrefix_IPv4			"ipv4:"
+#define kRDataArgPrefix_IPv6			"ipv6:"
+#define kRDataArgPrefix_SRV				"srv:"
 #define kRDataArgPrefix_String			"string:"
 #define kRDataArgPrefix_TXT				"txt:"
 
@@ -243,11 +253,15 @@ static const char *		gConnectionOpt = kConnectionArg_Normal;
 #define kRecordDataSection_Text																							\
 	"A record data argument is specified in one of the following formats:\n"											\
 	"\n"																												\
-	"Format                           Syntax                                 Example\n"									\
-	"String                           string:<string>                        string:'\\x09color=red'\n"					\
-	"Hexadecimal string               hex:<hex string>                       hex:c0a80101 or hex:'C0 A8 01 01'\n"		\
-	"TXT record keys and values       txt:<comma-delimited keys and values>  txt:'key1=x,key2=y\\,z,key3'\n"			\
-	"File containing raw record data  file:<file path>                       file:dir/record_data.bin\n"
+	"Format                        Syntax                                   Example\n"									\
+	"Domain name                   domain:<domain name>                     domain:demo._test._tcp.local\n"				\
+	"File containing record data   file:<file path>                         file:/path/to/rdata.bin\n"					\
+	"Hexadecimal string            hex:<hex string>                         hex:c0000201 or hex:'C0 00 02 01'\n"		\
+	"IPv4 address                  ipv4:<IPv4 address>                      ipv4:192.0.2.1\n"							\
+	"IPv6 address                  ipv6:<IPv6 address>                      ipv6:2001:db8::1\n"							\
+	"SRV record                    srv:<priority>,<weight>,<port>,<target>  srv:0,0,64206,example.local\n"				\
+	"String (w/escaped hex bytes)  string:<string>                          string:'\\x09color=red'\n"					\
+	"TXT record keys and values    txt:<comma-delimited keys and values>    txt:'vers=1.0,lang=en\\,es\\,fr,passreq'\n"
 
 #define RecordDataSection()		CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
 
@@ -466,6 +480,7 @@ static CLIOption		kResolveOpts[] =
 	DNSSDFlagsOption_ForceMulticast(),
 	DNSSDFlagsOption_IncludeAWDL(),
 	DNSSDFlagsOption_ReturnIntermediates(),
+	DNSSDFlagsOption_WakeOnResolve(),
 	
 	CLI_OPTION_GROUP( "Operation" ),
 	ConnectionOptions(),
@@ -519,6 +534,9 @@ static int				gGAIPOSIXFlag_V4MappedCFG	= false;
 #if( defined( AI_DEFAULT ) )
 static int				gGAIPOSIXFlag_Default		= false;
 #endif
+#if( defined( AI_UNUSABLE ) )
+static int				gGAIPOSIXFlag_Unusable		= false;
+#endif
 
 static CLIOption		kGetAddrInfoPOSIXOpts[] =
 {
@@ -544,6 +562,9 @@ static CLIOption		kGetAddrInfoPOSIXOpts[] =
 #if( defined( AI_DEFAULT ) )
 	BooleanOption(   0 , "flag-default",		&gGAIPOSIXFlag_Default,		"In hints ai_flags field, set AI_DEFAULT." ),
 #endif
+#if( defined( AI_UNUSABLE ) )
+	BooleanOption(   0 , "flag-unusable",		&gGAIPOSIXFlag_Unusable,	"In hints ai_flags field, set AI_UNUSABLE." ),
+#endif
 	
 	CLI_SECTION( "Notes", "See getaddrinfo(3) man page for more details.\n" ),
 	CLI_OPTION_END()
@@ -577,6 +598,35 @@ static CLIOption		kReverseLookupOpts[] =
 	CLI_OPTION_END()
 };
 
+//===========================================================================================================================
+//	PortMapping Command Options
+//===========================================================================================================================
+
+static int		gPortMapping_ProtocolTCP	= false;
+static int		gPortMapping_ProtocolUDP	= false;
+static int		gPortMapping_InternalPort	= 0;
+static int		gPortMapping_ExternalPort	= 0;
+static int		gPortMapping_TTL			= 0;
+
+static CLIOption		kPortMappingOpts[] =
+{
+	InterfaceOption(),
+	BooleanOption( 0, "tcp",			&gPortMapping_ProtocolTCP,	"Use kDNSServiceProtocol_TCP." ),
+	BooleanOption( 0, "udp",			&gPortMapping_ProtocolUDP,	"Use kDNSServiceProtocol_UDP." ),
+	IntegerOption( 0, "internalPort",	&gPortMapping_InternalPort,	"port number", "Internal port.", false ),
+	IntegerOption( 0, "externalPort",	&gPortMapping_ExternalPort,	"port number", "Requested external port. Use '0' for any external port.", false ),
+	IntegerOption( 0, "ttl",			&gPortMapping_TTL,			"seconds", "Requested TTL (renewal period) in seconds. Use '0' for a default value.", false ),
+	
+	CLI_OPTION_GROUP( "Flags" ),
+	DNSSDFlagsOption(),
+	
+	CLI_OPTION_GROUP( "Operation" ),
+	ConnectionOptions(),
+	
+	ConnectionSection(),
+	CLI_OPTION_END()
+};
+
 //===========================================================================================================================
 //	BrowseAll Command Options
 //===========================================================================================================================
@@ -586,20 +636,20 @@ static char **			gBrowseAll_ServiceTypes			= NULL;
 static size_t			gBrowseAll_ServiceTypesCount	= 0;
 static int				gBrowseAll_IncludeAWDL			= false;
 static int				gBrowseAll_BrowseTimeSecs		= 5;
-static int				gBrowseAll_ConnectTimeLimitSecs	= 5;
+static int				gBrowseAll_MaxConnectTimeSecs	= 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\".", 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", "Specifies the duration of the browse.", false ),
-	IntegerOption( 'c', "connectTimeLimit",	&gBrowseAll_ConnectTimeLimitSecs,	"seconds", "Specifies the max duration of the connect operations.", false ),
+	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 ),
 	CLI_OPTION_END()
 };
 
@@ -731,6 +781,73 @@ static CLIOption		kPIDToUUIDOpts[] =
 	CLI_OPTION_END()
 };
 
+//===========================================================================================================================
+//	SSDP Command Options
+//===========================================================================================================================
+
+static int				gSSDPDiscover_MX			= 1;
+static const char *		gSSDPDiscover_ST			= "ssdp:all";
+static int				gSSDPDiscover_ReceiveSecs	= 1;
+static int				gSSDPDiscover_UseIPv4		= false;
+static int				gSSDPDiscover_UseIPv6		= false;
+static int				gSSDPDiscover_Verbose		= false;
+
+static CLIOption		kSSDPDiscoverOpts[] =
+{
+	StringOption(  'i', "interface",	&gInterface,				"name or index", "Network interface by name or index.", true ),
+	IntegerOption( 'm', "mx",			&gSSDPDiscover_MX,			"seconds", "MX value in search request, i.e., max response delay in seconds. (Default: 1 second)", false ),
+	StringOption(  's', "st",			&gSSDPDiscover_ST,			"string", "ST value in search request, i.e., the search target. (Default: \"ssdp:all\")", false ),
+	IntegerOption( 'r', "receiveTime",	&gSSDPDiscover_ReceiveSecs,	"seconds", "Amount of time to spend receiving responses. -1 means unlimited. (Default: 1 second)", false ),
+	BooleanOption(  0 , "ipv4",			&gSSDPDiscover_UseIPv4,		"Use IPv4, i.e., multicast to 239.255.255.250:1900." ),
+	BooleanOption(  0 , "ipv6",			&gSSDPDiscover_UseIPv6,		"Use IPv6, i.e., multicast to [ff02::c]:1900" ),
+	BooleanOption( 'v', "verbose",		&gSSDPDiscover_Verbose,		"Prints the search request(s) that were sent." ),
+	CLI_OPTION_END()
+};
+
+static void	SSDPDiscoverCmd( void );
+
+static CLIOption		kSSDPOpts[] =
+{
+	Command( "discover", SSDPDiscoverCmd, kSSDPDiscoverOpts, "Crafts and multicasts an SSDP search message.", false ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+//	res_query Command Options
+//===========================================================================================================================
+
+static const char *		gResQuery_Name			= NULL;
+static const char *		gResQuery_Type			= NULL;
+static const char *		gResQuery_Class			= NULL;
+static int				gResQuery_UseLibInfo	= false;
+
+static CLIOption		kResQueryOpts[] =
+{
+	StringOption( 'n', "name",		&gResQuery_Name,		"domain name",	"Full domain name of record to query.", true ),
+	StringOption( 't', "type",		&gResQuery_Type,		"record type",	"Record type by name (e.g., TXT, SRV, etc.) or number.", true ),
+	StringOption( 'c', "class",		&gResQuery_Class,		"record class",	"Record class by name or number. Default class is IN.", false ),
+	BooleanOption( 0 , "libinfo",	&gResQuery_UseLibInfo,	"Use res_query from libinfo instead of libresolv." ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+//	dns_query Command Options
+//===========================================================================================================================
+
+static const char *		gResolvDNSQuery_Name	= NULL;
+static const char *		gResolvDNSQuery_Type	= NULL;
+static const char *		gResolvDNSQuery_Class	= NULL;
+static const char *		gResolvDNSQuery_Path	= NULL;
+
+static CLIOption		kResolvDNSQueryOpts[] =
+{
+	StringOption( 'n', "name",	&gResolvDNSQuery_Name,	"domain name",	"Full domain name of record to query.", true ),
+	StringOption( 't', "type",	&gResolvDNSQuery_Type,	"record type",	"Record type by name (e.g., TXT, SRV, etc.) or number.", true ),
+	StringOption( 'c', "class",	&gResolvDNSQuery_Class,	"record class",	"Record class by name or number. Default class is IN.", false ),
+	StringOption( 'p', "path",	&gResolvDNSQuery_Path,	"file path",	"The path argument to pass to dns_open() before calling dns_query(). Default value is NULL.", false ),
+	CLI_OPTION_END()
+};
+
 //===========================================================================================================================
 //	Command Table
 //===========================================================================================================================
@@ -746,6 +863,7 @@ static void	ResolveCmd( void );
 static void	ReconfirmCmd( void );
 static void	GetAddrInfoPOSIXCmd( void );
 static void	ReverseLookupCmd( void );
+static void	PortMappingCmd( void );
 static void	BrowseAllCmd( void );
 static void	GetAddrInfoStressCmd( void );
 static void	DNSQueryCmd( void );
@@ -754,6 +872,10 @@ static void	DNSCryptCmd( void );
 #endif
 static void	MDNSQueryCmd( void );
 static void	PIDToUUIDCmd( void );
+#if( TARGET_OS_DARWIN )
+static void	ResQueryCmd( void );
+static void	ResolvDNSQueryCmd( void );
+#endif
 static void	DaemonVersionCmd( void );
 
 static CLIOption		kGlobalOpts[] =
@@ -773,7 +895,8 @@ static CLIOption		kGlobalOpts[] =
 	Command( "reconfirm",			ReconfirmCmd,			kReconfirmOpts,			"Uses DNSServiceReconfirmRecord() to reconfirm a record.", false ),
 	Command( "getaddrinfo-posix",	GetAddrInfoPOSIXCmd,	kGetAddrInfoPOSIXOpts,	"Uses getaddrinfo() to resolve a hostname to IP addresses.", false ),
 	Command( "reverseLookup",		ReverseLookupCmd,		kReverseLookupOpts,		"Uses DNSServiceQueryRecord() to perform a reverse IP address lookup.", false ),
-	Command( "browseAll",			BrowseAllCmd,			kBrowseAllOpts,			"Browse and resolve all, or just some, services.", false ),
+	Command( "portMapping",			PortMappingCmd,			kPortMappingOpts,		"Uses DNSServiceNATPortMappingCreate() to create a port mapping.", false ),
+	Command( "browseAll",			BrowseAllCmd,			kBrowseAllOpts,			"Browse and resolve all (or specific) services and, optionally, attempt connections.", false ),
 	
 	// Uncommon commands.
 	
@@ -784,6 +907,11 @@ static CLIOption		kGlobalOpts[] =
 #endif
 	Command( "mDNSQuery",			MDNSQueryCmd,			kMDNSQueryOpts,			"Crafts and sends an mDNS query over the specified interface.", true ),
 	Command( "pid2uuid",			PIDToUUIDCmd,			kPIDToUUIDOpts,			"Prints the UUID of a process.", true ),
+	Command( "ssdp",				NULL,					kSSDPOpts,				"Commands for testing with Simple Service Discovery Protocol (SSDP).", true ),
+#if( TARGET_OS_DARWIN )
+	Command( "res_query",			ResQueryCmd,			kResQueryOpts,			"Uses res_query() from either libresolv or libinfo to query for a record.", true ),
+	Command( "dns_query",			ResolvDNSQueryCmd,		kResolvDNSQueryOpts,	"Uses dns_query() from libresolv to query for a record.", true ),
+#endif
 	Command( "daemonVersion",		DaemonVersionCmd,		NULL,					"Prints the version of the DNS-SD daemon.", true ),
 	
 	CLI_COMMAND_HELP(),
@@ -908,6 +1036,11 @@ static OSStatus
 		const char *	inString,
 		uint8_t **		outEndPtr );
 static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
+static OSStatus
+	DomainNameFromString(
+		uint8_t			inDomainName[ kDomainNameLengthMax ],
+		const char *	inString,
+		uint8_t **		outEndPtr );
 static OSStatus
 	DomainNameToString(
 		const uint8_t *		inDomainName,
@@ -2115,7 +2248,7 @@ static void	RegisterCmd( void )
 	// Start operation.
 	
 	err = DNSServiceRegister( &context->opRef, context->flags, context->ifIndex, context->name, context->type,
-		context->domain, NULL, ntohs( context->port ), (uint16_t) context->txtLen, context->txtPtr,
+		context->domain, NULL, htons( context->port ), (uint16_t) context->txtLen, context->txtPtr,
 		RegisterCallback, context );
 	ForgetMem( &context->txtPtr );
 	require_noerr( err, exit );
@@ -2679,7 +2812,7 @@ static void	ReconfirmCmd( void )
 		require_noerr_quiet( err, exit );
 	}
 	
-	// Get record data.
+	// Get record class.
 	
 	if( gReconfirmRecord_Class )
 	{
@@ -2794,12 +2927,40 @@ static void DNSSD_API
 	( (X) == AF_UNSPEC )	? "unspec"	:	\
 							  "???" )
 
+typedef struct
+{
+    unsigned int		flag;
+    const char *        str;
+
+}   FlagStringPair;
+
+#define CaseFlagStringify( X )		{ (X), # X }
+
+const FlagStringPair		kGAIPOSIXFlagStringPairs[] =
+{
+#if( defined( AI_UNUSABLE ) )
+	CaseFlagStringify( AI_UNUSABLE ),
+#endif
+	CaseFlagStringify( AI_NUMERICSERV ),
+	CaseFlagStringify( AI_V4MAPPED ),
+	CaseFlagStringify( AI_ADDRCONFIG ),
+#if( defined( AI_V4MAPPED_CFG ) )
+	CaseFlagStringify( AI_V4MAPPED_CFG ),
+#endif
+	CaseFlagStringify( AI_ALL ),
+	CaseFlagStringify( AI_NUMERICHOST ),
+	CaseFlagStringify( AI_CANONNAME ),
+	CaseFlagStringify( AI_PASSIVE ),
+	{ 0, NULL }
+};
+
 static void	GetAddrInfoPOSIXCmd( void )
 {
 	OSStatus					err;
 	struct addrinfo				hints;
 	const struct addrinfo *		addrInfo;
 	struct addrinfo *			addrInfoList = NULL;
+	const FlagStringPair *		pair;
 	char						time[ kTimestampBufLen ];
 	
 	memset( &hints, 0, sizeof( hints ) );
@@ -2833,6 +2994,9 @@ static void	GetAddrInfoPOSIXCmd( void )
 #if( defined( AI_DEFAULT ) )
 	if( gGAIPOSIXFlag_Default )		hints.ai_flags |= AI_DEFAULT;
 #endif
+#if( defined( AI_UNUSABLE ) )
+	if( gGAIPOSIXFlag_Unusable )	hints.ai_flags |= AI_UNUSABLE;
+#endif
 	
 	// Print prologue.
 	
@@ -2840,16 +3004,10 @@ static void	GetAddrInfoPOSIXCmd( void )
 	FPrintF( stdout, "Servname:       %s\n",	gGAIPOSIX_ServName );
 	FPrintF( stdout, "Address family: %s\n",	AddressFamilyStr( hints.ai_family ) );
 	FPrintF( stdout, "Flags:          0x%X < ",	hints.ai_flags );
-	if( hints.ai_flags & AI_NUMERICSERV )	FPrintF( stdout, "AI_NUMERICSERV " );
-	if( hints.ai_flags & AI_V4MAPPED )		FPrintF( stdout, "AI_V4MAPPED " );
-	if( hints.ai_flags & AI_ADDRCONFIG )	FPrintF( stdout, "AI_ADDRCONFIG " );
-#if( defined( AI_V4MAPPED_CFG ) )
-	if( hints.ai_flags & AI_V4MAPPED_CFG )	FPrintF( stdout, "AI_V4MAPPED_CFG " );
-#endif
-	if( hints.ai_flags & AI_ALL )			FPrintF( stdout, "AI_ALL " );
-	if( hints.ai_flags & AI_NUMERICHOST )	FPrintF( stdout, "AI_NUMERICHOST " );
-	if( hints.ai_flags & AI_CANONNAME )		FPrintF( stdout, "AI_CANONNAME " );
-	if( hints.ai_flags & AI_PASSIVE )		FPrintF( stdout, "AI_PASSIVE " );
+	for( pair = kGAIPOSIXFlagStringPairs; pair->str != NULL; ++pair )
+	{
+		if( ( (unsigned int) hints.ai_flags ) & pair->flag ) FPrintF( stdout, "%s ", pair->str );
+	}
 	FPrintF( stdout, ">\n" );
 	FPrintF( stdout, "Start time:     %s\n", GetTimestampStr( time ) );
 	FPrintF( stdout, "---\n" );
@@ -2860,7 +3018,7 @@ static void	GetAddrInfoPOSIXCmd( void )
 	GetTimestampStr( time );
 	if( err )
 	{
-		FPrintF( stderr, "Error %#m: %s.\n", err, gai_strerror( err ) );
+		FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
 	}
 	else
 	{
@@ -2896,6 +3054,7 @@ static void	ReverseLookupCmd( void )
 	uint8_t						ipv6Addr[ 16 ];
 	char						recordName[ ( 16 * 4 ) + 9 + 1 ];
 	int							useMainConnection;
+	const char *				endPtr;
 	
 	// Set up SIGINT handler.
 	
@@ -2944,16 +3103,16 @@ static void	ReverseLookupCmd( void )
 	// Create reverse lookup record name.
 	
 	err = StringToIPv4Address( gReverseLookup_IPAddr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix,
-		&ipv4Addr, NULL, NULL, NULL, NULL );
-	if( err )
+		&ipv4Addr, NULL, NULL, NULL, &endPtr );
+	if( err || ( *endPtr != '\0' ) )
 	{
 		char *		dst;
 		int			i;
 		
 		err = StringToIPv6Address( gReverseLookup_IPAddr,
 			kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
-			ipv6Addr, NULL, NULL, NULL, NULL );
-		if( err )
+			ipv6Addr, NULL, NULL, NULL, &endPtr );
+		if( err || ( *endPtr != '\0' ) )
 		{
 			FPrintF( stderr, "Invalid IP address: \"%s\".\n", gReverseLookup_IPAddr );
 			err = kParamErr;
@@ -3019,6 +3178,202 @@ exit:
 	if( err ) exit( 1 );
 }
 
+//===========================================================================================================================
+//	PortMappingCmd
+//===========================================================================================================================
+
+typedef struct
+{
+	DNSServiceRef			mainRef;		// Main sdRef for shared connection.
+	DNSServiceRef			opRef;			// sdRef for the DNSServiceNATPortMappingCreate operation.
+	DNSServiceFlags			flags;			// Flags for DNSServiceNATPortMappingCreate operation.
+	uint32_t				ifIndex;		// Interface index argument for DNSServiceNATPortMappingCreate operation.
+	DNSServiceProtocol		protocols;		// Protocols argument for DNSServiceNATPortMappingCreate operation.
+	uint32_t				ttl;			// TTL argument for DNSServiceNATPortMappingCreate operation.
+	uint16_t				internalPort;	// Internal port argument for DNSServiceNATPortMappingCreate operation.
+	uint16_t				externalPort;	// External port argument for DNSServiceNATPortMappingCreate operation.
+	Boolean					printedHeader;	// True if results header was printed.
+	
+}	PortMappingContext;
+
+static void	PortMappingPrintPrologue( const PortMappingContext *inContext );
+static void	PortMappingContextFree( PortMappingContext *inContext );
+static void DNSSD_API
+	PortMappingCallback(
+		DNSServiceRef		inSDRef,
+		DNSServiceFlags		inFlags,
+		uint32_t			inInterfaceIndex,
+		DNSServiceErrorType	inError,
+		uint32_t			inExternalIPv4Address,
+		DNSServiceProtocol	inProtocol,
+		uint16_t			inInternalPort,
+		uint16_t			inExternalPort,
+		uint32_t			inTTL,
+		void *				inContext );
+
+static void	PortMappingCmd( void )
+{
+	OSStatus					err;
+	PortMappingContext *		context			= NULL;
+	DNSServiceRef				sdRef;
+	dispatch_source_t			signalSource	= NULL;
+	int							useMainConnection;
+	
+	// Set up SIGINT handler.
+	
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+	require_noerr( err, exit );
+	dispatch_resume( signalSource );
+	
+	// Create context.
+	
+	context = (PortMappingContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	// Check command parameters.
+	
+	if( ( gPortMapping_InternalPort < 0 ) || ( gPortMapping_InternalPort > UINT16_MAX ) )
+	{
+		FPrintF( stderr, "Internal port number %d is out-of-range.\n", gPortMapping_InternalPort );
+		err = kParamErr;
+		goto exit;
+	}
+	
+	if( ( gPortMapping_ExternalPort < 0 ) || ( gPortMapping_ExternalPort > UINT16_MAX ) )
+	{
+		FPrintF( stderr, "External port number %d is out-of-range.\n", gPortMapping_ExternalPort );
+		err = kParamErr;
+		goto exit;
+	}
+	
+	// Create main connection.
+	
+	if( gConnectionOpt )
+	{
+		err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
+		require_noerr_quiet( err, exit );
+		useMainConnection = true;
+	}
+	else
+	{
+		useMainConnection = false;
+	}
+	
+	// Get flags.
+	
+	context->flags = GetDNSSDFlagsFromOpts();
+	if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
+	
+	// Get interface index.
+	
+	err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+	require_noerr_quiet( err, exit );
+	
+	// Set remaining parameters.
+	
+	if( gPortMapping_ProtocolTCP ) context->protocols |= kDNSServiceProtocol_TCP;
+	if( gPortMapping_ProtocolUDP ) context->protocols |= kDNSServiceProtocol_UDP;
+	context->ttl			= (uint32_t) gPortMapping_TTL;
+	context->internalPort	= (uint16_t) gPortMapping_InternalPort;
+	context->externalPort	= (uint16_t) gPortMapping_ExternalPort;
+	
+	// Print prologue.
+	
+	PortMappingPrintPrologue( context );
+	
+	// Start operation.
+	
+	if( useMainConnection ) sdRef = context->mainRef;
+	err = DNSServiceNATPortMappingCreate( &sdRef, context->flags, context->ifIndex, context->protocols,
+		htons( context->internalPort ), htons( context->externalPort ), context->ttl, PortMappingCallback, context );
+	require_noerr( err, exit );
+	
+	context->opRef = sdRef;
+	if( !useMainConnection )
+	{
+		err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+		require_noerr( err, exit );
+	}
+	
+	dispatch_main();
+	
+exit:
+	dispatch_source_forget( &signalSource );
+	if( context ) PortMappingContextFree( context );
+	if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//	PortMappingPrintPrologue
+//===========================================================================================================================
+
+static void	PortMappingPrintPrologue( const PortMappingContext *inContext )
+{
+	char		ifName[ kInterfaceNameBufLen ];
+	char		time[ kTimestampBufLen ];
+	
+	InterfaceIndexToName( inContext->ifIndex, ifName );
+	
+	FPrintF( stdout, "Flags:         %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:     %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Protocols:     %#{flags}\n",	inContext->protocols, kDNSServiceProtocolDescriptors );
+	FPrintF( stdout, "Internal Port: %u\n",			inContext->internalPort );
+	FPrintF( stdout, "External Port: %u\n",			inContext->externalPort );
+	FPrintF( stdout, "TTL:           %u%?s\n",		inContext->ttl, !inContext->ttl, " (system will use a default value.)" );
+	FPrintF( stdout, "Start time:    %s\n",			GetTimestampStr( time ) );
+	FPrintF( stdout, "---\n" );
+	
+}
+
+//===========================================================================================================================
+//	PortMappingContextFree
+//===========================================================================================================================
+
+static void	PortMappingContextFree( PortMappingContext *inContext )
+{
+	DNSServiceForget( &inContext->opRef );
+	DNSServiceForget( &inContext->mainRef );
+	free( inContext );
+}
+
+//===========================================================================================================================
+//	PortMappingCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+	PortMappingCallback(
+		DNSServiceRef		inSDRef,
+		DNSServiceFlags		inFlags,
+		uint32_t			inInterfaceIndex,
+		DNSServiceErrorType	inError,
+		uint32_t			inExternalIPv4Address,
+		DNSServiceProtocol	inProtocol,
+		uint16_t			inInternalPort,
+		uint16_t			inExternalPort,
+		uint32_t			inTTL,
+		void *				inContext )
+{
+	PortMappingContext * const		context = (PortMappingContext *) inContext;
+	char							time[ kTimestampBufLen ];
+	char							errorStr[ 128 ];
+	
+	Unused( inSDRef );
+	Unused( inFlags );
+	
+	GetTimestampStr( time );
+	
+	if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " (error: %#m)", inError );
+	if( !context->printedHeader )
+	{
+		FPrintF( stdout, "%-26s  IF %7s %15s %7s %6s Protocol\n", "Timestamp", "IntPort", "ExtAddr", "ExtPort", "TTL" );
+		context->printedHeader = true;
+	}
+	FPrintF( stdout, "%-26s  %2u %7u %15.4a %7u %6u %#{flags}%?s\n",
+		time, inInterfaceIndex, ntohs( inInternalPort), &inExternalIPv4Address, ntohs( inExternalPort ), inTTL,
+		inProtocol, kDNSServiceProtocolDescriptors, inError, errorStr );
+}
+
 //===========================================================================================================================
 //	BrowseAllCmd
 //===========================================================================================================================
@@ -3042,7 +3397,7 @@ typedef struct
 	uint32_t				ifIndex;
 	int						pendingConnectCount;
 	int						browseTimeSecs;
-	int						connectTimeLimitSecs;
+	int						maxConnectTimeSecs;
 	Boolean					includeAWDL;
 	Boolean					useColoredText;
 	
@@ -3276,7 +3631,7 @@ static void	BrowseAllCmd( void )
 	gBrowseAll_ServiceTypes			= NULL;
 	gBrowseAll_ServiceTypesCount	= 0;
 	context->browseTimeSecs			= gBrowseAll_BrowseTimeSecs;
-	context->connectTimeLimitSecs	= gBrowseAll_ConnectTimeLimitSecs;
+	context->maxConnectTimeSecs		= gBrowseAll_MaxConnectTimeSecs;
 	context->includeAWDL			= gBrowseAll_IncludeAWDL ? true : false;
 #if( TARGET_OS_POSIX )
 	context->useColoredText			= isatty( STDOUT_FILENO ) ? true : false;
@@ -3332,8 +3687,8 @@ static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext )
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
-	FPrintF( stdout, "Interface:          %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Service types:      ");
+	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 ] );
@@ -3344,10 +3699,10 @@ static void	BrowseAllPrintPrologue( const BrowseAllContext *inContext )
 	{
 		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, "Connect time limit: %d second%?c\n",
-		inContext->connectTimeLimitSecs, inContext->connectTimeLimitSecs != 1, 's' );
+	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, "Start time:         %s\n", GetTimestampStr( time ) );
 	FPrintF( stdout, "---\n" );
 }
@@ -3704,10 +4059,10 @@ static void	BrowseAllStop( void *inContext )
 	}
 	DNSServiceForget( &context->mainRef );
 	
-	if( ( context->pendingConnectCount > 0 ) && ( context->connectTimeLimitSecs > 0 ) )
+	if( ( context->pendingConnectCount > 0 ) && ( context->maxConnectTimeSecs > 0 ) )
 	{
 		check( !context->exitTimer );
-		err = DispatchTimerCreate( dispatch_time_seconds( context->connectTimeLimitSecs ), DISPATCH_TIME_FOREVER,
+		err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER,
 			100 * kNanosecondsPerMillisecond, BrowseAllExit, NULL, context, &context->exitTimer );
 		require_noerr( err, exit );
 		dispatch_resume( context->exitTimer );
@@ -3764,7 +4119,6 @@ static void	BrowseAllExit( void *inContext )
 				{
 					char		ifname[ IF_NAMESIZE + 1 ];
 					
-					FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
 					FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
 					if( instance->ifIndex == 0 )
 					{
@@ -3809,31 +4163,35 @@ static void	BrowseAllExit( void *inContext )
 							addr->connectError	= kTimeoutErr;
 						}
 						
-						FPrintF( stdout, "%*s" "%-##47a %4llu ms (", Indent( 4 ),
+						FPrintF( stdout, "%*s" "%-##47a %4llu ms", Indent( 4 ),
 							&addr->sip.sa, UpTicksToMilliseconds( addr->foundTicks - instance->getAddrStartTicks ) );
+						if( context->maxConnectTimeSecs <= 0 )
+						{
+							FPrintF( stdout, "\n" );
+							continue;
+						}
 						switch( addr->connectStatus )
 						{
 							case kConnectStatus_None:
-								FPrintF( stdout, "%s", kStatusStr_NoConnectionAttempted );
+								FPrintF( stdout, " (%s)\n", kStatusStr_NoConnectionAttempted );
 								break;
 							
 							case kConnectStatus_Succeeded:
-								FPrintF( stdout, "%s in %.2f ms",
+								FPrintF( stdout, " (%s in %.2f ms)\n",
 									context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect,
 									addr->connectTimeSecs * 1000 );
 								break;
 							
 							case kConnectStatus_Failed:
-								FPrintF( stdout, "%s: %m",
+								FPrintF( stdout, " (%s: %m)\n",
 									context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect,
 									addr->connectError );
 								break;
 							
 							default:
-								FPrintF( stdout, "%s", kStatusStr_Unknown );
+								FPrintF( stdout, " (%s)\n", kStatusStr_Unknown );
 								break;
 						}
-						FPrintF( stdout, ")\n" );
 					}
 					
 					FPrintF( stdout, "\n" );
@@ -4230,7 +4588,7 @@ static OSStatus
 	newAddr->foundTicks	= nowTicks;
 	SockAddrCopy( inSockAddr, &newAddr->sip.sa );
 	
-	if( inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
+	if( ( inContext->maxConnectTimeSecs > 0 ) && inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
 	{
 		char		destination[ kSockAddrStringMaxSize ];
 		
@@ -5114,8 +5472,7 @@ static void	DNSCryptReceiveCertHandler( void *inContext )
 	err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
 	require_noerr( err, exit );
 	
-	targetName[ 0 ] = 0;
-	err = DomainNameAppendString( targetName, context->providerName, NULL );
+	err = DomainNameFromString( targetName, context->providerName, NULL );
 	require_noerr( err, exit );
 	
 	answerCount = DNSHeaderGetAnswerCount( hdr );
@@ -5520,7 +5877,7 @@ typedef struct
 	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 label format.
+	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;
@@ -5594,7 +5951,7 @@ static void	MDNSQueryCmd( void )
 		
 		if( !context->isQU && ( context->localPort == kMDNSPort ) )
 		{
-			SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
+			err = SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
 			require_noerr( err, exit );
 		}
 	}
@@ -5625,7 +5982,7 @@ static void	MDNSQueryCmd( void )
 		
 		if( !context->isQU && ( context->localPort == kMDNSPort ) )
 		{
-			SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
+			err = SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
 			require_noerr( err, exit );
 		}
 	}
@@ -5837,51 +6194,664 @@ exit:
 }
 
 //===========================================================================================================================
-//	DaemonVersionCmd
+//	SSDPDiscoverCmd
 //===========================================================================================================================
 
-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
-//===========================================================================================================================
+#define kSSDPPort		1900
 
-static void	Exit( void *inContext )
+typedef struct
 {
-	const char * const		reason = (const char *) inContext;
-	char					time[ kTimestampBufLen ];
+	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.
 	
-	FPrintF( stdout, "---\n" );
-	FPrintF( stdout, "End time:   %s\n", GetTimestampStr( time ) );
-	if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
-	exit( gExitCode );
-}
+}	SSDPDiscoverContext;
 
-//===========================================================================================================================
-//	GetTimestampStr
-//===========================================================================================================================
+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 char *	GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
+static void	SSDPDiscoverCmd( void )
 {
-	struct timeval		now;
-	struct tm *			tm;
-	size_t				len;
+	OSStatus					err;
+	SSDPDiscoverContext *		context;
+	dispatch_source_t			signalSource	= NULL;
+	SocketRef					sockV4			= kInvalidSocketRef;
+	SocketRef					sockV6			= kInvalidSocketRef;
+	ssize_t						n;
+	int							sendCount;
+	char						time[ kTimestampBufLen ];
 	
-	gettimeofday( &now, NULL );
+	// Set up SIGINT handler.
+	
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+	require_noerr( err, exit );
+	dispatch_resume( signalSource );
+	
+	// Check command parameters.
+	
+	if( gSSDPDiscover_ReceiveSecs < -1 )
+	{
+		FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
+		err = kParamErr;
+		goto exit;
+	}
+	
+	// Create context.
+	
+	context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
+	require_action( context, exit, err = kNoMemoryErr );
+	
+	context->receiveSecs	= gSSDPDiscover_ReceiveSecs;
+	context->useIPv4		= ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
+	context->useIPv6		= ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
+	
+	err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
+	require_noerr_quiet( err, exit );
+	
+	// Set up IPv4 socket.
+	
+	if( context->useIPv4 )
+	{
+		int port;
+		err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
+		require_noerr( err, exit );
+		
+		err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
+		require_noerr( err, exit );
+		
+		err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+		err = map_socket_noerr_errno( sockV4, err );
+		require_noerr( err, exit );
+	}
+	
+	// Set up IPv6 socket.
+	
+	if( context->useIPv6 )
+	{
+		err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+		require_noerr( err, exit );
+		
+		err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+		require_noerr( err, exit );
+		
+		err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+		err = map_socket_noerr_errno( sockV6, err );
+		require_noerr( err, exit );
+	}
+	
+	// Print prologue.
+	
+	SSDPDiscoverPrintPrologue( context );
+	
+	// Send mDNS query message.
+	
+	sendCount = 0;
+	if( IsValidSocket( sockV4 ) )
+	{
+		struct sockaddr_in		mcastAddr4;
+		
+		memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
+		SIN_LEN_SET( &mcastAddr4 );
+		mcastAddr4.sin_family		= AF_INET;
+		mcastAddr4.sin_port			= htons( kSSDPPort );
+		mcastAddr4.sin_addr.s_addr	= htonl( 0xEFFFFFFA );	// 239.255.255.250
+		
+		err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
+		require_noerr( err, exit );
+		
+		n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
+			(socklen_t) sizeof( mcastAddr4 ) );
+		err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
+		if( err )
+		{
+			FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+			ForgetSocket( &sockV4 );
+		}
+		else
+		{
+			if( gSSDPDiscover_Verbose )
+			{
+				GetTimestampStr( time );
+				FPrintF( stdout, "---\n" );
+				FPrintF( stdout, "Send time:    %s\n",		time );
+				FPrintF( stdout, "Source Port:  %d\n",		SocketToPortNumber( sockV4 ) );
+				FPrintF( stdout, "Destination:  %##a\n",	&mcastAddr4 );
+				FPrintF( stdout, "Message size: %zu\n",		context->header.len );
+				FPrintF( stdout, "HTTP header:\n%1{text}",	context->header.buf, context->header.len );
+			}
+			++sendCount;
+		}
+	}
+	
+	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 )
+			{
+				GetTimestampStr( time );
+				FPrintF( stdout, "---\n" );
+				FPrintF( stdout, "Send time:    %s\n",		time );
+				FPrintF( stdout, "Source Port:  %d\n",		SocketToPortNumber( sockV6 ) );
+				FPrintF( stdout, "Destination:  %##a\n",	&mcastAddr6 );
+				FPrintF( stdout, "Message size: %zu\n",		context->header.len );
+				FPrintF( stdout, "HTTP header:\n%1{text}",	context->header.buf, context->header.len );
+			}
+			++sendCount;
+		}
+	}
+	require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+	
+	// If there's no wait period after the send, then exit.
+	
+	if( context->receiveSecs == 0 ) goto exit;
+	
+	// Create dispatch read sources for socket(s).
+	
+	if( IsValidSocket( sockV4 ) )
+	{
+		SocketContext *		sockContext;
+		
+		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+		require_action( sockContext, exit, err = kNoMemoryErr );
+		
+		err = DispatchReadSourceCreate( sockV4, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+			&context->readSourceV4 );
+		if( err ) ForgetMem( &sockContext );
+		require_noerr( err, exit );
+		
+		sockContext->context	= context;
+		sockContext->sock		= sockV4;
+		sockV4 = kInvalidSocketRef;
+		dispatch_resume( context->readSourceV4 );
+	}
+	
+	if( IsValidSocket( sockV6 ) )
+	{
+		SocketContext *		sockContext;
+		
+		sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+		require_action( sockContext, exit, err = kNoMemoryErr );
+		
+		err = DispatchReadSourceCreate( sockV6, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+			&context->readSourceV6 );
+		if( err ) ForgetMem( &sockContext );
+		require_noerr( err, exit );
+		
+		sockContext->context	= context;
+		sockContext->sock		= sockV6;
+		sockV6 = kInvalidSocketRef;
+		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;
+	char					time[ kTimestampBufLen ];
+	
+	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:       %s\n",		GetTimestampStr( time ) );
+}
+
+//===========================================================================================================================
+//	SSDPDiscoverReadHandler
+//===========================================================================================================================
+
+static void	SSDPDiscoverReadHandler( void *inContext )
+{
+	OSStatus						err;
+	SocketContext * const			sockContext	= (SocketContext *) inContext;
+	SSDPDiscoverContext * const		context		= (SSDPDiscoverContext *) sockContext->context;
+	HTTPHeader * const				header		= &context->header;
+	sockaddr_ip						fromAddr;
+	size_t							msgLen;
+	char							time[ kTimestampBufLen ];
+	
+	GetTimestampStr( time );
+	
+	err = SocketRecvFrom( sockContext->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
+		NULL, NULL, NULL, NULL );
+	require_noerr( err, exit );
+	
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "Receive time: %s\n",		time );
+	FPrintF( stdout, "Source:       %##a\n", 	&fromAddr );
+	FPrintF( stdout, "Message size: %zu\n",		msgLen );
+	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;
+	char			time[ kTimestampBufLen ];
+	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: %s\n",		GetTimestampStr( time ) );
+	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", n );
+	PrintUDNSMessage( answer, (size_t) n, false );
+	
+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;
+	char				time[ kTimestampBufLen ];
+	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: %s\n",		GetTimestampStr( time ) );
+	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", n );
+	PrintUDNSMessage( answer, (size_t) n, false );
+	
+exit:
+	if( dns ) soft_dns_free( dns );
+	if( err ) exit( 1 );
+}
+#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;
+	char					time[ kTimestampBufLen ];
+	
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "End time:   %s\n", GetTimestampStr( time ) );
+	if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
+	exit( gExitCode );
+}
+
+//===========================================================================================================================
+//	GetTimestampStr
+//===========================================================================================================================
+
+static char *	GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
+{
+	struct timeval		now;
+	struct tm *			tm;
+	size_t				len;
+	
+	gettimeofday( &now, NULL );
 	tm = localtime( &now.tv_sec );
 	require_action( tm, exit, *inBuffer = '\0' );
 	
@@ -5921,6 +6891,7 @@ static DNSServiceFlags	GetDNSSDFlagsFromOpts( void )
 	if( gDNSSDFlag_Timeout )				flags |= kDNSServiceFlagsTimeout;
 	if( gDNSSDFlag_UnicastResponse )		flags |= kDNSServiceFlagsUnicastResponse;
 	if( gDNSSDFlag_Unique )					flags |= kDNSServiceFlagsUnique;
+	if( gDNSSDFlag_WakeOnResolve )			flags |= kDNSServiceFlagsWakeOnResolve;
 	
 	return( flags );
 }
@@ -6056,105 +7027,253 @@ exit:
 
 #define kRDataMaxLen		UINT16_C( 0xFFFF )
 
+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 OSStatus	RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
 {
 	OSStatus		err;
 	uint8_t *		dataPtr = NULL;
 	size_t			dataLen;
-	DataBuffer		dataBuf;
 	
-	DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen );
+	if( 0 ) {}
+	
+	// Domain name
 	
-	if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
+	else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
 	{
-		const char * const		strPtr = inString + sizeof_string( kRDataArgPrefix_String );
-		const size_t			strLen = strlen( strPtr );
-		size_t					copiedLen;
-		size_t					totalLen;
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_Domain );
+		uint8_t *				end;
+		uint8_t					dname[ kDomainNameLengthMax ];
 		
-		if( strLen > 0 )
-		{
-			require_action( strLen <= kRDataMaxLen, exit, err = kSizeErr );
-			dataPtr = (uint8_t *) malloc( strLen );
-			require_action( dataPtr, exit, err = kNoMemoryErr );
-			
-			copiedLen = 0;
-			ParseQuotedEscapedString( strPtr, strPtr + strLen, "", (char *) dataPtr, strLen, &copiedLen, &totalLen, NULL );
-			check( copiedLen == totalLen );
-			dataLen = copiedLen;
-		}
-		else
-		{
-			dataPtr = NULL;
-			dataLen = 0;
-		}
+		err = DomainNameFromString( dname, str, &end );
+		require_noerr( err, exit );
+		
+		dataLen = (size_t)( end - dname );
+		dataPtr = malloc( dataLen );
+		require_action( dataPtr, exit, err = kNoMemoryErr );
+		
+		memcpy( dataPtr, dname, dataLen );
 	}
-	else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
+	
+	// File path
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
 	{
-		const char * const		strPtr = inString + sizeof_string( kRDataArgPrefix_HexString );
+		const char * const		path = inString + sizeof_string( kRDataArgPrefix_File );
 		
-		err = HexToDataCopy( strPtr, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+		err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
 		require_noerr( err, exit );
 		require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
 	}
-	else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+	
+	// Hexadecimal string
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
 	{
-		const char * const		path = inString + sizeof_string( kRDataArgPrefix_File );
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_HexString );
 		
-		err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+		err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
 		require_noerr( err, exit );
 		require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
 	}
-	else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
+	
+	// IPv4 address string
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+		const char *			end;
+		
+		dataLen = 4;
+		dataPtr = (uint8_t *) malloc( dataLen );
+		require_action( dataPtr, exit, err = kNoMemoryErr );
+		
+		err = StringToIPv4Address( str, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix,
+			(uint32_t *) dataPtr, NULL, NULL, NULL, &end );
+		if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+		require_noerr( err, exit );
+		
+		*( (uint32_t *) dataPtr ) = HostToBig32( *( (uint32_t *) dataPtr ) );
+	}
+	
+	// IPv6 address string
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+		const char *			end;
+		
+		dataLen = 16;
+		dataPtr = (uint8_t *) malloc( dataLen );
+		require_action( dataPtr, exit, err = kNoMemoryErr );
+		
+		err = StringToIPv6Address( str,
+			kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+			dataPtr, NULL, NULL, NULL, &end );
+		if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+		require_noerr( err, exit );
+	}
+	
+	// SRV record
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
+	{
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_SRV );
+		
+		err = StringToSRVRData( str, &dataPtr, &dataLen );
+		require_noerr( err, exit );
+	}
+	
+	// String with escaped hex and octal bytes
+	
+	else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
 	{
-		const char *			strPtr = inString + sizeof_string( kRDataArgPrefix_TXT );
-		const char * const		strEnd = strPtr + strlen( strPtr );
+		const char * const		str = inString + sizeof_string( kRDataArgPrefix_String );
+		const char * const		end = str + strlen( str );
+		size_t					copiedLen;
+		size_t					totalLen;
+		Boolean					success;
 		
-		while( strPtr < strEnd )
+		if( str < end )
 		{
-			size_t		copiedLen, totalLen;
-			uint8_t		kvBuf[ 1 + 255 + 1 ];	// Length byte + max key-value length + 1 for NUL terminator.
+			success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
+			require_action( success, exit, err = kParamErr );
+			require_action( totalLen <= kRDataMaxLen, exit, err = kSizeErr );
 			
-			err = ParseEscapedString( strPtr, strEnd, ',', (char *) &kvBuf[ 1 ], sizeof( kvBuf ) - 1,
-				&copiedLen, &totalLen, &strPtr );
-			require_noerr_quiet( err, exit );
-			check( copiedLen == totalLen );
-			if( totalLen > 255 )
-			{
-				FPrintF( stderr, "TXT key-value pair length %zu is too long (> 255 bytes).\n", totalLen );
-				err = kParamErr;
-				goto exit;
-			}
+			dataLen = totalLen;
+			dataPtr = (uint8_t *) malloc( dataLen );
+			require_action( dataPtr, exit, err = kNoMemoryErr );
 			
-			kvBuf[ 0 ] = (uint8_t) copiedLen;
-			err = DataBuffer_Append( &dataBuf, kvBuf, 1 + kvBuf[ 0 ] );
-			require_noerr( err, exit );
+			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 = DataBuffer_Commit( &dataBuf, NULL, NULL );
-		require_noerr( err, exit );
-		
-		err = DataBuffer_Detach( &dataBuf, &dataPtr, &dataLen );
+		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;
 	
+	err = kNoErr;
 	*outDataLen = dataLen;
 	*outDataPtr = dataPtr;
 	dataPtr = NULL;
 	
 exit:
-	DataBuffer_Free( &dataBuf );
 	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 ];
+		
+		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 );
+	}
+	
+	// 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 );
+	
+	err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+	require_noerr( err, exit );
+	
+exit:
+	DataBuffer_Free( &dataBuf );
+	return( err );
+}
+
+static OSStatus	StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen )
+{
+	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 );
+	
+	src = inString;
+	for( ;; )
+	{
+		uint8_t *					dst = &txtStr[ 1 ];
+		const uint8_t * const		lim = &txtStr[ 256 ];
+		int							c;
+		
+		while( *src && ( *src != inDelimiter ) )
+		{
+			if( ( c = *src++ ) == '\\' )
+			{
+				require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+				c = *src++;
+			}
+			require_action_quiet( dst < lim, exit, err = kOverrunErr );
+			*dst++ = (uint8_t) c;
+		}
+		txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
+		err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
+		require_noerr( err, exit );
+		
+		if( *src == '\0' ) break;
+		++src;
+	}
+	
+	err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+	require_noerr( err, exit );
+	
+exit:
+	DataBuffer_Free( &dataBuf );
+	return( err );
+}
+
 //===========================================================================================================================
 //	RecordTypeFromArgString
 //===========================================================================================================================
@@ -6767,67 +7886,55 @@ static OSStatus
 {
 	OSStatus					err;
 	const char *				src;
-	uint8_t *					dst;
-	const uint8_t * const		nameLimit = inDomainName + kDomainNameLengthMax;
+	uint8_t *					root;
+	const uint8_t * const		nameLim = inDomainName + kDomainNameLengthMax;
 	
-	// Find the root label.
+	for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
+	require_action_quiet( root < nameLim, exit, err = kMalformedErr );
 	
-	for( dst = inDomainName; ( dst < nameLimit ) && *dst; dst += ( 1 + *dst ) ) {}
-	require_action_quiet( dst < nameLimit, exit, err = kMalformedErr );
-	
-	// Append the string's labels one label at a time.
+	// 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		= dst++;
-		const uint8_t * const		labelLimit	= Min( dst + kDomainLabelLengthMax, nameLimit - 1 );
-		
-		// If the first character is a label separator, then the label is empty. Empty non-root labels are not allowed.
+		uint8_t * const				label		= root;
+		const uint8_t * const		labelLim	= Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
+		uint8_t *					dst;
+		int							c;
+		size_t						labelLen;
 		
-		require_action_quiet( *src != '.', exit, err = kMalformedErr );
-		
-		// Write the label characters until the end of the label, a separator or NUL character, is encountered, or until no
-		// more space is available.
-		
-		while( ( *src != '.' ) && ( *src != '\0' ) && ( dst < labelLimit ) )
+		dst = &label[ 1 ];
+		while( *src && ( ( c = *src++ ) != '.' ) )
 		{
-			uint8_t		value;
-			
-			value = (uint8_t) *src++;
-			if( value == '\\' )
+			if( c == '\\' )
 			{
-				if( *src == '\0' ) break;
-				value = (uint8_t) *src++;
-				if( isdigit_safe( value ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
+				require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+				c = *src++;
+				if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
 				{
-					int		decimalValue;
+					const int		decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
 					
-					decimalValue = ( ( value - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
-					if( decimalValue <= 255 )
+					if( decimal <= 255 )
 					{
-						value = (uint8_t) decimalValue;
+						c = decimal;
 						src += 2;
 					}
 				}
 			}
-			*dst++ = value;
-		}
-		if( ( *src == '.' ) || ( *src == '\0' ) )
-		{
-			label[ 0 ] = (uint8_t)( dst - &label[ 1 ] );	// Write the label length.
-			if( *src == '.' ) ++src;						// Advance the pointer past the label separator.
-		}
-		else
-		{
-			label[ 0 ] = 0;
-			err = kOverrunErr;
-			goto exit;
+			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;
 	}
 	
-	*dst++ = 0;	// Write the empty root label.
-	if( outEndPtr ) *outEndPtr = dst;
+	if( outEndPtr ) *outEndPtr = root + 1;
 	err = kNoErr;
 	
 exit:
@@ -6856,6 +7963,20 @@ static Boolean	DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
 	return( true );
 }
 
+//===========================================================================================================================
+//	DomainNameFromString
+//===========================================================================================================================
+
+static OSStatus
+	DomainNameFromString(
+		uint8_t			inDomainName[ kDomainNameLengthMax ],
+		const char *	inString,
+		uint8_t **		outEndPtr )
+{
+	inDomainName[ 0 ] = 0;
+	return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+}
+
 //===========================================================================================================================
 //	DomainNameToString
 //===========================================================================================================================
@@ -7081,8 +8202,7 @@ static OSStatus
 	WriteBig16( hdr->additionalCount,	0 );
 	
 	ptr = (uint8_t *)( hdr + 1 );
-	ptr[ 0 ] = 0;
-	err = DomainNameAppendString( ptr, inQName, &ptr );
+	err = DomainNameFromString( ptr, inQName, &ptr );
 	require_noerr_quiet( err, exit );
 	
 	WriteBig16( ptr, inQType );
@@ -7379,56 +8499,6 @@ exit:
 	return( err );
 }
 
-//===========================================================================================================================
-//	ParseEscapedString
-//
-//	Note: This was copied from CoreUtils because the ParseEscapedString function is currently not exported in the framework.
-//===========================================================================================================================
-
-OSStatus
-	ParseEscapedString( 
-		const char *	inSrc, 
-		const char *	inEnd, 
-		char			inDelimiter, 
-		char *			inBuf, 
-		size_t			inMaxLen, 
-		size_t *		outCopiedLen, 
-		size_t *		outTotalLen, 
-		const char **	outSrc )
-{
-	OSStatus		err;
-	char			c;
-	char *			dst;
-	char *			lim;
-	size_t			len;
-	
-	dst = inBuf;
-	lim = dst + ( ( inMaxLen > 0 ) ? ( inMaxLen - 1 ) : 0 ); // Leave room for null terminator.
-	len = 0;
-	while( ( inSrc < inEnd ) && ( ( c = *inSrc++ ) != inDelimiter ) )
-	{
-		if( c == '\\' )
-		{
-			require_action_quiet( inSrc < inEnd, exit, err = kUnderrunErr );
-			c = *inSrc++;
-		}
-		if( dst < lim )
-		{
-			if( inBuf ) *dst = c;
-			++dst;
-		}
-		++len;
-	}
-	if( inBuf && ( inMaxLen > 0 ) ) *dst = '\0';
-	err = kNoErr;
-	
-exit:
-	if( outCopiedLen )	*outCopiedLen	= (size_t)( dst - inBuf );
-	if( outTotalLen )	*outTotalLen	= len;
-	if( outSrc )		*outSrc			= inSrc;
-	return( err );
-}
-
 //===========================================================================================================================
 //	ParseIPv4Address
 //
diff --git a/mDNSResponder/Makefile b/mDNSResponder/Makefile
index 9d975334..fa0a4e49 100644
--- a/mDNSResponder/Makefile
+++ b/mDNSResponder/Makefile
@@ -16,7 +16,7 @@
 
 include $(MAKEFILEPATH)/pb_makefiles/platform.make
 
-MVERS = "mDNSResponder-878.30.4"
+MVERS = "mDNSResponder-878.50.17"
 
 VER =
 ifneq ($(strip $(GCC_VERSION)),)
diff --git a/mDNSResponder/mDNSCore/DNSCommon.c b/mDNSResponder/mDNSCore/DNSCommon.c
index be8e1065..750c10e6 100644
--- a/mDNSResponder/mDNSCore/DNSCommon.c
+++ b/mDNSResponder/mDNSCore/DNSCommon.c
@@ -3473,9 +3473,8 @@ mDNSexport const mDNSu8 *GetLargeResourceRecord(mDNS *const m, const DNSMessage
     pktrdlength           = (mDNSu16)((mDNSu16)ptr[8] <<  8 | ptr[9]);
 
     // If mDNS record has cache-flush bit set, we mark it unique
-    // For uDNS records, all are implicitly deemed unique (a single DNS server is always
-    // authoritative for the entire RRSet), unless this is a truncated response
-    if (ptr[2] & (kDNSClass_UniqueRRSet >> 8) || (!InterfaceID && !(msg->h.flags.b[0] & kDNSFlag0_TC)))
+    // For uDNS records, all are implicitly deemed unique (a single DNS server is always authoritative for the entire RRSet)
+    if (ptr[2] & (kDNSClass_UniqueRRSet >> 8) || !InterfaceID)
         RecordType |= kDNSRecordTypePacketUniqueMask;
     ptr += 10;
     if (ptr + pktrdlength > end) { debugf("GetLargeResourceRecord: RDATA exceeds end of packet"); return(mDNSNULL); }
diff --git a/mDNSResponder/mDNSCore/mDNS.c b/mDNSResponder/mDNSCore/mDNS.c
index 72375d94..0788ab67 100755
--- a/mDNSResponder/mDNSCore/mDNS.c
+++ b/mDNSResponder/mDNSCore/mDNS.c
@@ -83,7 +83,7 @@ mDNSlocal void BeginSleepProcessing(mDNS *const m);
 mDNSlocal void RetrySPSRegistrations(mDNS *const m);
 mDNSlocal void SendWakeup(mDNS *const m, mDNSInterfaceID InterfaceID, mDNSEthAddr *EthAddr, mDNSOpaque48 *password, mDNSBool unicastOnly);
 mDNSlocal mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
-mDNSlocal void mDNS_PurgeForQuestion(mDNS *const m, DNSQuestion *q);
+mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q);
 mDNSlocal void CheckForDNSSECRecords(mDNS *const m, DNSQuestion *q);
 mDNSlocal void mDNS_SendKeepalives(mDNS *const m);
 mDNSlocal void mDNS_ExtractKeepaliveInfo(AuthRecord *ar, mDNSu32 *timeout, mDNSAddr *laddr, mDNSAddr *raddr, mDNSEthAddr *eth,
@@ -4321,56 +4321,17 @@ mDNSlocal void CacheRecordDeferredAdd(mDNS *const m, CacheRecord *rr)
     m->CurrentQuestion = mDNSNULL;
 }
 
-mDNSlocal mDNSs32 CheckForSoonToExpireRecords(mDNS *const m, const domainname *const name, const mDNSu32 namehash, mDNSBool *purge)
+mDNSlocal mDNSs32 CheckForSoonToExpireRecords(mDNS *const m, const domainname *const name, const mDNSu32 namehash)
 {
-    const mDNSs32 threshhold = m->timenow + mDNSPlatformOneSecond;  // See if there are any records expiring within one second
+    const mDNSs32 threshold = m->timenow + mDNSPlatformOneSecond;  // See if there are any records expiring within one second
     const mDNSs32 start      = m->timenow - 0x10000000;
     mDNSs32 delay = start;
     CacheGroup *cg = CacheGroupForName(m, namehash, name);
     const CacheRecord *rr;
 
-    if (purge)
-        *purge = mDNSfalse;
     for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
     {
-        // If there are records that will expire soon, there are cases that need delayed
-        // delivery of events:
-        //
-        // 1) A new cache entry is about to be added as a replacement. The caller needs to
-        //    deliver a RMV (for the current old entry) followed by ADD (for the new entry).
-        //    It needs to schedule the timer for the next cache expiry (ScheduleNextCacheCheckTime),
-        //    so that the cache entry can be purged (purging causes the RMV followed by ADD)
-        //
-        // 2) A new question is about to be answered and the caller needs to know whether it's
-        //    scheduling should be delayed so that the question is not answered with this record.
-        //    Instead of delivering an ADD (old entry) followed by RMV (old entry) and another ADD
-        //    (new entry), a single ADD can be delivered by delaying the scheduling of the question
-        //    immediately.
-        //
-        // When the unicast cache record is created, it's TTL has been extended beyond its value
-        // given in the resource record (See RRAdjustTTL). If it is in the "extended" time, the
-        // cache is already expired and we set "purge" to indicate that. When "purge" is set, the
-        // return value of the function should be ignored by the callers.
-        //
-        // Note: For case (1), "purge" argument is NULL and hence the following checks are skipped.
-        // It is okay to skip in that case because the cache records have been set to expire almost
-        // immediately and the extended time does not apply.
-        //
-        // Also, if there is already an active question we don't try to optimize as purging the cache
-        // would end up delivering RMV for the active question and hence we avoid that.
-
-        if (purge && !rr->resrec.InterfaceID && !rr->CRActiveQuestion && rr->resrec.rroriginalttl)
-        {
-            mDNSu32 uTTL = RRUnadjustedTTL(rr->resrec.rroriginalttl);
-            if (m->timenow - (rr->TimeRcvd + ((mDNSs32)uTTL * mDNSPlatformOneSecond)) >= 0)
-            {
-                LogInfo("CheckForSoonToExpireRecords: %s: rroriginalttl %u, unadjustedTTL %u, currentTTL %u",
-                    CRDisplayString(m, rr), rr->resrec.rroriginalttl, uTTL, (m->timenow - rr->TimeRcvd)/mDNSPlatformOneSecond);
-                *purge = mDNStrue;
-                continue;
-            }
-        }
-        if (threshhold - RRExpireTime(rr) >= 0)     // If we have records about to expire within a second
+        if (threshold - RRExpireTime(rr) >= 0)     // If we have records about to expire within a second
         {
             if (delay - RRExpireTime(rr) < 0)       // then delay until after they've been deleted
                 delay = RRExpireTime(rr);
@@ -6827,7 +6788,13 @@ mDNSexport void mDNSCoreMachineSleep(mDNS *const m, mDNSBool sleep)
 #endif
             mDNS_ReclaimLockAfterCallback();
         }
-
+#ifdef _LEGACY_NAT_TRAVERSAL_
+        if (m->SSDPSocket)
+        {
+            mDNSPlatformUDPClose(m->SSDPSocket);
+            m->SSDPSocket = mDNSNULL;
+        }
+#endif
         m->SleepState = SleepState_Transferring;
         if (m->SystemWakeOnLANEnabled && m->DelaySleep)
         {
@@ -8963,18 +8930,25 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
     // abort our TCP connection, and not complete the operation, and end up with an incomplete RRSet in our cache.
     // Next time there's a query for this RRSet we'll see answers in our cache, and assume we have the whole RRSet already,
     // and not even do the TCP query.
-    // Accordingly, if we get a uDNS reply with kDNSFlag0_TC set, we bail out and wait for the TCP response containing the entire RRSet.
-    if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC)) return;
+    // Accordingly, if we get a uDNS reply with kDNSFlag0_TC set, we bail out and wait for the TCP response containing the
+    // entire RRSet, with the following exception. If the response contains an answer section and one or more records in
+    // either the authority section or additional section, then that implies that truncation occurred beyond the answer
+    // section, and the answer section is therefore assumed to be complete.
+    //
+    // From section 6.2 of RFC 1035 <https://tools.ietf.org/html/rfc1035>:
+    //    When a response is so long that truncation is required, the truncation
+    //    should start at the end of the response and work forward in the
+    //    datagram.  Thus if there is any data for the authority section, the
+    //    answer section is guaranteed to be unique.
+    if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC) &&
+        ((response->h.numAnswers == 0) || ((response->h.numAuthorities == 0) && (response->h.numAdditionals == 0)))) return;
 
     if (LLQType == uDNS_LLQ_Ignore) return;
 
     // 1. We ignore questions (if any) in mDNS response packets
     // 2. If this is an LLQ response, we handle it much the same
-    // 3. If we get a uDNS UDP response with the TC (truncated) bit set, then we can't treat this
-    //    answer as being the authoritative complete RRSet, and respond by deleting all other
-    //    matching cache records that don't appear in this packet.
     // Otherwise, this is a authoritative uDNS answer, so arrange for any stale records to be purged
-    if (ResponseMCast || LLQType == uDNS_LLQ_Events || (response->h.flags.b[0] & kDNSFlag0_TC))
+    if (ResponseMCast || LLQType == uDNS_LLQ_Events)
         ptr = LocateAnswers(response, end);
     // Otherwise, for one-shot queries, any answers in our cache that are not also contained
     // in this response packet are immediately deemed to be invalid.
@@ -9420,7 +9394,7 @@ mDNSlocal void mDNSCoreReceiveResponse(mDNS *const m,
                 if (AddToCFList)
                     delay = NonZeroTime(m->timenow + mDNSPlatformOneSecond);
                 else
-                    delay = CheckForSoonToExpireRecords(m, m->rec.r.resrec.name, m->rec.r.resrec.namehash, mDNSNULL);
+                    delay = CheckForSoonToExpireRecords(m, m->rec.r.resrec.name, m->rec.r.resrec.namehash);
 
                 // If unique, assume we may have to delay delivery of this 'add' event.
                 // Below, where we walk the CacheFlushRecords list, we either call CacheRecordDeferredAdd()
@@ -9488,6 +9462,7 @@ exit:
         CacheRecord *r1 = CacheFlushRecords, *r2;
         const mDNSu32 slot = HashSlotFromNameHash(r1->resrec.namehash);
         const CacheGroup *cg = CacheGroupForRecord(m, &r1->resrec);
+        mDNSBool purgedRecords = mDNSfalse;
         CacheFlushRecords = CacheFlushRecords->NextInCFList;
         r1->NextInCFList = mDNSNULL;
 
@@ -9569,8 +9544,9 @@ exit:
                         r2->resrec.rroriginalttl = r1->resrec.rroriginalttl;
                     }
                     r2->TimeRcvd = m->timenow;
+                    SetNextCacheCheckTimeForRecord(m, r2);
                 }
-                else                // else, if record is old, mark it to be flushed
+                else if (r2->resrec.InterfaceID) // else, if record is old, mark it to be flushed
                 {
                     verbosedebugf("Cache flush new %p age %d expire in %d %s", r1, m->timenow - r1->TimeRcvd, RRExpireTime(r1) - m->timenow, CRDisplayString(m, r1));
                     verbosedebugf("Cache flush old %p age %d expire in %d %s", r2, m->timenow - r2->TimeRcvd, RRExpireTime(r2) - m->timenow, CRDisplayString(m, r2));
@@ -9608,8 +9584,14 @@ exit:
                         // We use (m->timenow - 1) instead of m->timenow, because we use that to identify records
                         // that we marked for deletion via an explicit DE record
                     }
+                    SetNextCacheCheckTimeForRecord(m, r2);
+                }
+                else
+                {
+                    // Old uDNS records are scheduled to be purged instead of given at most one second to live.
+                    mDNS_PurgeCacheResourceRecord(m, r2);
+                    purgedRecords = mDNStrue;
                 }
-                SetNextCacheCheckTimeForRecord(m, r2);
             }
         }
 
@@ -9637,7 +9619,16 @@ exit:
                 NSECRecords = mDNSNULL;
                 NSECCachePtr = mDNSNULL;
             }
-            r1->DelayDelivery = CheckForSoonToExpireRecords(m, r1->resrec.name, r1->resrec.namehash, mDNSNULL);
+            if (r1->resrec.InterfaceID)
+            {
+                r1->DelayDelivery = CheckForSoonToExpireRecords(m, r1->resrec.name, r1->resrec.namehash);
+            }
+            else
+            {
+                // If uDNS records from an older RRset were scheduled to be purged, then delay delivery slightly to allow
+                // them to be deleted before any ADD events for this record.
+                r1->DelayDelivery = purgedRecords ? NonZeroTime(m->timenow) : 0;
+            }
             // If no longer delaying, deliver answer now, else schedule delivery for the appropriate time
             if (!r1->DelayDelivery) CacheRecordDeferredAdd(m, r1);
             else ScheduleNextCacheCheckTime(m, slot, r1->DelayDelivery);
@@ -11650,7 +11641,7 @@ mDNSlocal mStatus ValidateParameters(mDNS *const m, DNSQuestion *const question)
 }
 
 // InitDNSConfig() is called by InitCommonState() to initialize the DNS configuration of the Question.    
-// These are a subset of the internal uDNS fields. Must be done before ShouldSuppressQuery() & mDNS_PurgeForQuestion()
+// These are a subset of the internal uDNS fields. Must be done before ShouldSuppressQuery() & mDNS_PurgeBeforeResolve()
 mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
 {
     // First reset all DNS Configuration
@@ -11706,9 +11697,8 @@ mDNSlocal void InitDNSConfig(mDNS *const m, DNSQuestion *const question)
 
 // InitCommonState() is called by mDNS_StartQuery_internal() to initialize the common(uDNS/mDNS) internal
 // state fields of the DNS Question. These are independent of the Client layer.
-mDNSlocal mDNSBool InitCommonState(mDNS *const m, DNSQuestion *const question)
+mDNSlocal void InitCommonState(mDNS *const m, DNSQuestion *const question)
 {
-    mDNSBool purge;
     int i;
     mDNSBool isBlocked = mDNSfalse;
 
@@ -11727,7 +11717,7 @@ mDNSlocal mDNSBool InitCommonState(mDNS *const m, DNSQuestion *const question)
     // turned ON which can allocate memory e.g., base64 encoding, in the case of DNSSEC.
     question->ThisQInterval     = InitialQuestionInterval;                  // MUST be > zero for an active question
     question->qnamehash         = DomainNameHashValue(&question->qname);
-    question->DelayAnswering    = CheckForSoonToExpireRecords(m, &question->qname, question->qnamehash, &purge);
+    question->DelayAnswering    = mDNSOpaque16IsZero(question->TargetQID) ? CheckForSoonToExpireRecords(m, &question->qname, question->qnamehash) : 0;
     question->LastQTime         = m->timenow;
     question->ExpectUnicastResp = 0;
     question->LastAnswerPktNum  = m->PktNum;
@@ -11809,7 +11799,6 @@ mDNSlocal mDNSBool InitCommonState(mDNS *const m, DNSQuestion *const question)
     if (question->WakeOnResolve)
     {
         question->WakeOnResolveCount = InitialWakeOnResolveCount;
-        purge = mDNStrue;
     }
 
     for (i=0; i<DupSuppressInfoSize; i++)
@@ -11826,8 +11815,6 @@ mDNSlocal mDNSBool InitCommonState(mDNS *const m, DNSQuestion *const question)
     if (question->DelayAnswering)
         LogInfo("InitCommonState: Delaying answering for %d ticks while cache stabilizes for %##s (%s)",
                  question->DelayAnswering - m->timenow, question->qname.c, DNSTypeName(question->qtype));
-
-    return(purge);
 }
 
 // Excludes the DNS Config fields which are already handled by InitDNSConfig()
@@ -11909,7 +11896,7 @@ mDNSlocal void InitDNSSECProxyState(mDNS *const m, DNSQuestion *const question)
 // Once the question is completely initialized including the duplicate logic, this function
 // is called to finalize the unicast question which requires flushing the cache if needed,
 // activating the query etc.
-mDNSlocal void FinalizeUnicastQuestion(mDNS *const m, DNSQuestion *question, mDNSBool purge)
+mDNSlocal void FinalizeUnicastQuestion(mDNS *const m, DNSQuestion *question)
 {
     // Ensure DNS related info of duplicate question is same as the orig question
     if (question->DuplicateOf)
@@ -11934,14 +11921,7 @@ mDNSlocal void FinalizeUnicastQuestion(mDNS *const m, DNSQuestion *question, mDN
 
     ActivateUnicastQuery(m, question, mDNSfalse);
 
-    // If purge was set above, flush the cache. Need to do this after we set the
-    // DNS server on the question
-    if (purge)
-    {
-        question->DelayAnswering = 0;
-        mDNS_PurgeForQuestion(m, question);
-    }
-    else if (!question->DuplicateOf && DNSSECQuestion(question))
+    if (!question->DuplicateOf && DNSSECQuestion(question))
     {
         // For DNSSEC questions, we need to have the RRSIGs also for verification.
         CheckForDNSSECRecords(m, question);
@@ -11964,7 +11944,6 @@ mDNSexport mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const qu
 {
     DNSQuestion **q;
     mStatus vStatus;
-    mDNSBool purge;
 
     // First check for cache space (can't do queries if there is no cache space allocated)
     if (m->rrcache_size == 0)
@@ -12012,7 +11991,7 @@ mDNSexport mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const qu
     // InitCommonState -> InitDNSConfig) as DNS server selection affects DNSSEC
     // validation.
 
-    purge = InitCommonState(m, question);
+    InitCommonState(m, question);
     InitWABState(question);
     InitLLQState(question);
 #ifdef DNS_PUSH_ENABLED
@@ -12045,7 +12024,7 @@ mDNSexport mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const qu
         // this routine with the question list data structures in an inconsistent state.
         if (!mDNSOpaque16IsZero(question->TargetQID))
         {
-            FinalizeUnicastQuestion(m, question, purge);
+            FinalizeUnicastQuestion(m, question);
         }
         else
         {
@@ -12065,10 +12044,10 @@ mDNSexport mStatus mDNS_StartQuery_internal(mDNS *const m, DNSQuestion *const qu
                 }
             }
 #endif // BONJOUR_ON_DEMAND
-            if (purge)
+            if (question->WakeOnResolve)
             {
                 LogInfo("mDNS_StartQuery_internal: Purging for %##s", question->qname.c);
-                mDNS_PurgeForQuestion(m, question);
+                mDNS_PurgeBeforeResolve(m, question);
             }
         }
     }
@@ -14618,7 +14597,7 @@ mDNSlocal void PurgeOrReconfirmCacheRecord(mDNS *const m, CacheRecord *cr, const
     }
 }
 
-mDNSlocal void mDNS_PurgeForQuestion(mDNS *const m, DNSQuestion *q)
+mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q)
 {
     CacheGroup *const cg = CacheGroupForName(m, q->qnamehash, &q->qname);
     CacheRecord *rp;
@@ -14634,7 +14613,7 @@ mDNSlocal void mDNS_PurgeForQuestion(mDNS *const m, DNSQuestion *q)
     {
         if (SameNameRecordAnswersQuestion(&rp->resrec, q))
         {
-            LogInfo("mDNS_PurgeForQuestion: Flushing %s", CRDisplayString(m, rp));
+            LogInfo("mDNS_PurgeBeforeResolve: Flushing %s", CRDisplayString(m, rp));
             mDNS_PurgeCacheResourceRecord(m, rp);
         }
     }
diff --git a/mDNSResponder/mDNSCore/uDNS.c b/mDNSResponder/mDNSCore/uDNS.c
index 4d011427..64dae891 100755
--- a/mDNSResponder/mDNSCore/uDNS.c
+++ b/mDNSResponder/mDNSCore/uDNS.c
@@ -550,7 +550,7 @@ mDNSlocal mStatus uDNS_RequestAddress(mDNS *m)
     return err;
 }
 
-mDNSlocal mStatus uDNS_SendNATMsg(mDNS *m, NATTraversalInfo *info, mDNSBool usePCP)
+mDNSlocal mStatus uDNS_SendNATMsg(mDNS *m, NATTraversalInfo *info, mDNSBool usePCP, mDNSBool unmapping)
 {
     mStatus err = mStatus_NoError;
 
@@ -649,19 +649,25 @@ mDNSlocal mStatus uDNS_SendNATMsg(mDNS *m, NATTraversalInfo *info, mDNSBool useP
             info->sentNATPMP = mDNSfalse;
 
 #ifdef _LEGACY_NAT_TRAVERSAL_
-            if (mDNSIPPortIsZero(m->UPnPRouterPort) || mDNSIPPortIsZero(m->UPnPSOAPPort))
+            // If an unmapping is being performed, then don't send an LNT discovery message or an LNT port map request.
+            if (!unmapping)
             {
-                LNT_SendDiscoveryMsg(m);
-                debugf("uDNS_SendNATMsg: LNT_SendDiscoveryMsg");
-            }
-            else
-            {
-                mStatus lnterr = LNT_MapPort(m, info);
-                if (lnterr)
-                    LogMsg("uDNS_SendNATMsg: LNT_MapPort returned error %d", lnterr);
+                if (mDNSIPPortIsZero(m->UPnPRouterPort) || mDNSIPPortIsZero(m->UPnPSOAPPort))
+                {
+                    LNT_SendDiscoveryMsg(m);
+                    debugf("uDNS_SendNATMsg: LNT_SendDiscoveryMsg");
+                }
+                else
+                {
+                    mStatus lnterr = LNT_MapPort(m, info);
+                    if (lnterr)
+                        LogMsg("uDNS_SendNATMsg: LNT_MapPort returned error %d", lnterr);
 
-                err = err ? err : lnterr; // PCP error takes precedence
+                    err = err ? err : lnterr; // PCP error takes precedence
+                }
             }
+#else
+            (void)unmapping; // Unused
 #endif // _LEGACY_NAT_TRAVERSAL_
         }
     }
@@ -925,6 +931,16 @@ mDNSexport mStatus mDNS_StopNATOperation_internal(mDNS *m, NATTraversalInfo *tra
         }
     }
 
+    // Even if we DIDN'T make a successful UPnP mapping yet, we might still have a partially-open TCP connection we need to clean up
+    // Before zeroing traversal->RequestedPort below, perform the LNT unmapping, which requires the mapping's external port,
+    // held by the traversal->RequestedPort variable.
+    #ifdef _LEGACY_NAT_TRAVERSAL_
+    {
+        mStatus err = LNT_UnmapPort(m, traversal);
+        if (err) LogMsg("Legacy NAT Traversal - unmap request failed with error %d", err);
+    }
+    #endif // _LEGACY_NAT_TRAVERSAL_
+
     if (traversal->ExpiryTime && unmap)
     {
         traversal->NATLease = 0;
@@ -946,17 +962,9 @@ mDNSexport mStatus mDNS_StopNATOperation_internal(mDNS *m, NATTraversalInfo *tra
         traversal->RequestedPort = zeroIPPort;
         traversal->NewAddress = zerov4Addr;
 
-        uDNS_SendNATMsg(m, traversal, traversal->lastSuccessfulProtocol != NATTProtocolNATPMP);
+        uDNS_SendNATMsg(m, traversal, traversal->lastSuccessfulProtocol != NATTProtocolNATPMP, mDNStrue);
     }
 
-    // Even if we DIDN'T make a successful UPnP mapping yet, we might still have a partially-open TCP connection we need to clean up
-    #ifdef _LEGACY_NAT_TRAVERSAL_
-    {
-        mStatus err = LNT_UnmapPort(m, traversal);
-        if (err) LogMsg("Legacy NAT Traversal - unmap request failed with error %d", err);
-    }
-    #endif // _LEGACY_NAT_TRAVERSAL_
-
     return(mStatus_NoError);
 }
 
@@ -3660,7 +3668,7 @@ mDNSlocal void uDNS_ReceiveNATPMPPacket(mDNS *m, const mDNSInterfaceID Interface
         {
             // Send a NAT-PMP request for this operation as needed
             // and update the state variables
-            uDNS_SendNATMsg(m, n, mDNSfalse);
+            uDNS_SendNATMsg(m, n, mDNSfalse, mDNSfalse);
         }
 
         m->NextScheduledNATOp = m->timenow;
@@ -5003,7 +5011,7 @@ mDNSexport void CheckNATMappings(mDNS *m)
                     cur->retryInterval = NATMAP_INIT_RETRY;
                 }
 
-                uDNS_SendNATMsg(m, cur, mDNStrue); // Will also do UPnP discovery for us, if necessary
+                uDNS_SendNATMsg(m, cur, mDNStrue, mDNSfalse); // Will also do UPnP discovery for us, if necessary
 
                 if (cur->ExpiryTime)                        // If have active mapping then set next renewal time halfway to expiry
                     NATSetNextRenewalTime(m, cur);
diff --git a/mDNSResponder/mDNSMacOSX/LegacyNATTraversal.c b/mDNSResponder/mDNSMacOSX/LegacyNATTraversal.c
index 2c90d929..7de031c4 100644
--- a/mDNSResponder/mDNSMacOSX/LegacyNATTraversal.c
+++ b/mDNSResponder/mDNSMacOSX/LegacyNATTraversal.c
@@ -888,9 +888,10 @@ mDNSexport void LNT_SendDiscoveryMsg(mDNS *m)
         "MX:3\r\n\r\n";
     static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } };
 
-    mDNSu8 *buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
+    mDNSu8 *const buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
     unsigned int bufLen;
 
+    if (m->SleepState != SleepState_Awake) return;
     if (!mDNSIPPortIsZero(m->UPnPRouterPort))
     {
         if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
diff --git a/mDNSResponder/mDNSShared/CommonServices.h b/mDNSResponder/mDNSShared/CommonServices.h
index 7fceac02..fecfb0f3 100644
--- a/mDNSResponder/mDNSShared/CommonServices.h
+++ b/mDNSResponder/mDNSShared/CommonServices.h
@@ -879,7 +879,11 @@ typedef unsigned long int uintptr_t;
         #define false   0
     #endif
 #else
-    #define COMMON_SERVICES_NEEDS_BOOL          ( !defined( __cplusplus ) && !__bool_true_false_are_defined )
+    #if ( !defined( __cplusplus ) && !__bool_true_false_are_defined )
+        #define COMMON_SERVICES_NEEDS_BOOL      1
+    #else
+        #define COMMON_SERVICES_NEEDS_BOOL      0
+    #endif
 #endif
 
 #if ( COMMON_SERVICES_NEEDS_BOOL )
diff --git a/mDNSResponder/mDNSShared/dns_sd.h b/mDNSResponder/mDNSShared/dns_sd.h
index 0b47c394..94237fb3 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 8783004
+#define _DNS_SD_H 8785017
 
 #ifdef  __cplusplus
 extern "C" {
diff --git a/mDNSResponder/mDNSShared/dnsextd.conf b/mDNSResponder/mDNSShared/dnsextd.conf
index 0379580d..d8cac244 100644
--- a/mDNSResponder/mDNSShared/dnsextd.conf
+++ b/mDNSResponder/mDNSShared/dnsextd.conf
@@ -23,7 +23,7 @@
 // network, you might allow anyone to perform updates. To do that, you just
 // permit any and all updates coming from dnsextd on the same machine:
 //
-//   zone "my-dynamic-subdomain.company.com."
+//   zone "my-dynamic-subdomain.example.com."
 //     { type master; file "db.xxx"; allow-update { 127.0.0.1; }; };
 //
 // On a machine connected to the Internet or other large open network,
@@ -32,11 +32,11 @@
 // perform updates in your dynamic zone, like this:
 //
 //   key keyname. { algorithm hmac-md5; secret "abcdefghijklmnopqrstuv=="; };
-//   zone "my-dynamic-subdomain.company.com." in
+//   zone "my-dynamic-subdomain.example.com." in
 //     {
 //     type master;
-//     file "db.my-dynamic-subdomain.company.com";
-//     update-policy { grant * wildcard *.my-dynamic-subdomain.company.com.; };
+//     file "db.my-dynamic-subdomain.example.com";
+//     update-policy { grant * wildcard *.my-dynamic-subdomain.example.com.; };
 //     };
 //
 // You could use a single key which you give to all authorized users, but
@@ -55,6 +55,6 @@ options {
 //	llq	   				port 5352;
 };
 
-zone "my-dynamic-subdomain.company.com." {
+zone "my-dynamic-subdomain.example.com." {
 	type public;
 };
-- 
2.26.2



More information about the devel mailing list