]> git.saurik.com Git - apple/mdnsresponder.git/commitdiff
mDNSResponder-878.250.4.tar.gz macos-10144 v878.250.4
authorApple <opensource@apple.com>
Tue, 18 Jun 2019 01:01:51 +0000 (01:01 +0000)
committerApple <opensource@apple.com>
Tue, 18 Jun 2019 01:01:51 +0000 (01:01 +0000)
Clients/dnssdutil.c
Makefile
mDNSMacOSX/BATS/mDNSResponder.plist [new file with mode: 0644]
mDNSMacOSX/dnssdutil-entitlements.plist [new file with mode: 0644]
mDNSMacOSX/mDNSMacOSX.c
mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
mDNSShared/dns_sd.h

index 206e2b6c314ee93018f0f1968014eb0d35046dcf..16b48752d56f9b6047694a002a13e94a7bea4288 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <CoreUtils/CommonServices.h>  // Include early.
 #include <CoreUtils/AsyncConnection.h>
+#include <CoreUtils/AtomicUtils.h>
 #include <CoreUtils/CFUtils.h>
 #include <CoreUtils/CommandLineUtils.h>
 #include <CoreUtils/DataBufferUtils.h>
@@ -20,6 +21,7 @@
 #include <CoreUtils/SoftLinking.h>
 #include <CoreUtils/StringUtils.h>
 #include <CoreUtils/TickUtils.h>
+#include <CoreUtils/TimeUtils.h>
 #include <dns_sd.h>
 #include <dns_sd_private.h>
 
@@ -93,6 +95,7 @@
        "\x1C" "ServiceIndex\0"                         \
        "\x1D" "DenyExpensive\0"                        \
        "\x1E" "PathEvaluationDone\0"           \
+       "\x1F" "AllowExpiredAnswers\0"          \
        "\x00"
 
 #define kDNSServiceProtocolDescriptors \
 //     DNS
 //===========================================================================================================================
 
-#define kDNSPort                                               53
-#define kDNSCompressionOffsetMax               0x3FFF
-#define kDNSMaxUDPMessageSize                  512
-#define kDNSMaxTCPMessageSize                  UINT16_MAX
+#define kDNSPort                                       53
+#define kDNSMaxUDPMessageSize          512
+#define kDNSMaxTCPMessageSize          UINT16_MAX
 
 #define kDomainLabelLengthMax          63
 #define kDomainNameLengthMax           256
 
+#define kDNSRecordDataLengthMax                UINT16_MAX
+
 typedef struct
 {
        uint8_t         id[ 2 ];
@@ -206,16 +210,47 @@ typedef struct
 
 check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
 
-#define DNSRecordFixedFieldsInit( FIELDS, TYPE, CLASS, TTL, RDLENGTH ) \
-       do                                                                                                                                      \
-       {                                                                                                                                       \
-               WriteBig16( (FIELDS)->type,             TYPE );                                                 \
-               WriteBig16( (FIELDS)->class,    CLASS );                                                \
-               WriteBig32( (FIELDS)->ttl,              TTL );                                                  \
-               WriteBig16( (FIELDS)->rdlength, RDLENGTH );                                             \
-                                                                                                                                               \
+// SRV RDATA fixed-length fields. See <https://tools.ietf.org/html/rfc2782>.
+
+typedef struct
+{
+       uint8_t         priority[ 2 ];
+       uint8_t         weight[ 2 ];
+       uint8_t         port[ 2 ];
+       
+}      SRVRecordDataFixedFields;
+
+check_compile_time( sizeof( SRVRecordDataFixedFields ) == 6 );
+
+// SOA RDATA fixed-length fields. See <https://tools.ietf.org/html/rfc1035#section-3.3.13>.
+
+typedef struct
+{
+       uint8_t         serial[ 4 ];
+       uint8_t         refresh[ 4 ];
+       uint8_t         retry[ 4 ];
+       uint8_t         expire[ 4 ];
+       uint8_t         minimum[ 4 ];
+       
+}      SOARecordDataFixedFields;
+
+check_compile_time( sizeof( SOARecordDataFixedFields ) == 20 );
+
+// DNS message compression. See <https://tools.ietf.org/html/rfc1035#section-4.1.4>.
+
+#define kDNSCompressionOffsetMax               0x3FFF
+
+#define IsCompressionByte( X )         ( ( ( X ) & 0xC0 ) == 0xC0 )
+#define WriteDNSCompressionPtr( PTR, OFFSET )                                                                                  \
+       do                                                                                                                                                                      \
+       {                                                                                                                                                                       \
+               ( (uint8_t *)(PTR) )[ 0 ] = (uint8_t)( ( ( (OFFSET) >> 8 ) & 0x3F ) | 0xC0 );   \
+               ( (uint8_t *)(PTR) )[ 1 ] = (uint8_t)(     (OFFSET)        & 0xFF          );   \
+                                                                                                                                                                               \
        }       while( 0 )
 
+#define NextLabel( LABEL )             ( ( (LABEL)[ 0 ] == 0 ) ? NULL : ( (LABEL) + 1 + (LABEL)[ 0 ] ) )
+
 //===========================================================================================================================
 //     mDNS
 //===========================================================================================================================
@@ -228,18 +263,76 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
 #define kQClassUnicastResponseBit              ( 1U << 15 )
 #define kRRClassCacheFlushBit                  ( 1U << 15 )
 
+// Recommended Resource Record TTL values. See <https://tools.ietf.org/html/rfc6762#section-10>.
+
+#define kMDNSRecordTTL_Host                    120             // TTL for resource records related to a host name, e.g., A, AAAA, SRV, etc.
+#define kMDNSRecordTTL_Other           4500    // TTL for other resource records.
+
+// Maximum mDNS Message Size. See <https://tools.ietf.org/html/rfc6762#section-17>.
+
+#define kMDNSMessageSizeMax            8952    // 9000 B (Ethernet jumbo frame max size) - 40 B (IPv6 header) - 8 B (UDP header)
+
+#define kLocalStr                      "\x05" "local"
+#define kLocalName                     ( (const uint8_t *) kLocalStr )
+#define kLocalNameLen          sizeof( kLocalStr )
+
 //===========================================================================================================================
-//     Test DNS Server
+//     Test Address Blocks
 //===========================================================================================================================
 
 // IPv4 address block 203.0.113.0/24 (TEST-NET-3) is reserved for documentation. See <https://tools.ietf.org/html/rfc5737>.
 
-#define kTestDNSServerBaseAddrV4               UINT32_C( 0xCB007100 )  // 203.0.113.0
+#define kDNSServerBaseAddrV4           UINT32_C( 0xCB007100 )  // 203.0.113.0/24
 
 // IPv6 address block 2001:db8::/32 is reserved for documentation. See <https://tools.ietf.org/html/rfc3849>.
 
-#define kTestDNSServerBaseAddrV6 \
-       ( (const uint8_t *) "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )
+static const uint8_t           kDNSServerBaseAddrV6[] =
+{
+       0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 2001:db8:1::/120
+};
+
+static const uint8_t           kMDNSReplierBaseAddrV6[] =
+{
+       0x20, 0x01, 0x0D, 0xB8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 2001:db8:2::/96
+};
+
+check_compile_time( sizeof( kDNSServerBaseAddrV6 )   == 16 );
+check_compile_time( sizeof( kMDNSReplierBaseAddrV6 ) == 16 );
+
+// Bad IPv4 and IPv6 Address Blocks
+// Used by the DNS server when it needs to respond with intentionally "bad" A/AAAA record data, i.e., IP addresses neither
+// in 203.0.113.0/24 nor 2001:db8:1::/120.
+
+#define kDNSServerBadBaseAddrV4                UINT32_C( 0x00000000 )  // 0.0.0.0/24
+
+static const uint8_t           kDNSServerBadBaseAddrV6[] =
+{
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00  // ::ffff:0:0/120
+};
+
+check_compile_time( sizeof( kDNSServerBadBaseAddrV6 ) == 16 );
+
+//===========================================================================================================================
+//     Misc.
+//===========================================================================================================================
+
+#define kLowerAlphaNumericCharSet                      "abcdefghijklmnopqrstuvwxyz0123456789"
+#define kLowerAlphaNumericCharSetSize          sizeof_string( kLowerAlphaNumericCharSet )
+
+// Note: strcpy_literal() appears in CoreUtils code, but isn't currently defined in framework headers.
+
+#if( !defined( strcpy_literal ) )
+       #define strcpy_literal( DST, SRC )              memcpy( DST, SRC, sizeof( SRC ) )
+#endif
+
+#define _RandomStringExact( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, OUT_STRING ) \
+       RandomString( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, CHAR_COUNT, OUT_STRING )
+
+#define kNoSuchRecordStr                       "No Such Record"
+#define kNoSuchRecordAStr                      "No Such Record (A)"
+#define kNoSuchRecordAAAAStr           "No Such Record (AAAA)"
+
+#define kRootLabel             ( (const uint8_t *) "" )
 
 //===========================================================================================================================
 //     Gerneral Command Options
@@ -266,6 +359,11 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
                (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,                                          \
                (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
 
+#define DoubleOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED )      \
+       CLI_OPTION_DOUBLE_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP,                                                 \
+               (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP,                                          \
+               (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+
 #define BooleanOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP ) \
        CLI_OPTION_BOOLEAN( (SHORT_CHAR), (LONG_NAME), (VAL_PTR), (SHORT_HELP), NULL )
 
@@ -285,6 +383,7 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
 // DNS-SD API flag options
 
 static int             gDNSSDFlags                                             = 0;
+static int             gDNSSDFlag_AllowExpiredAnswers  = false;
 static int             gDNSSDFlag_BrowseDomains                = false;
 static int             gDNSSDFlag_DenyCellular                 = false;
 static int             gDNSSDFlag_DenyExpensive                = false;
@@ -308,19 +407,20 @@ static int                gDNSSDFlag_WakeOnResolve                = false;
 #define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME ) \
        BooleanOption( SHORT_CHAR, # FLAG_NAME, &gDNSSDFlag_ ## FLAG_NAME, "Use kDNSServiceFlags" # FLAG_NAME "." )
 
-#define DNSSDFlagsOption_DenyCellular()                        DNSSDFlagOption( 'C', DenyCellular )
-#define DNSSDFlagsOption_DenyExpensive()               DNSSDFlagOption( 'E', DenyExpensive )
-#define DNSSDFlagsOption_ForceMulticast()              DNSSDFlagOption( 'M', ForceMulticast )
-#define DNSSDFlagsOption_IncludeAWDL()                 DNSSDFlagOption( 'A', IncludeAWDL )
-#define DNSSDFlagsOption_NoAutoRename()                        DNSSDFlagOption( 'N', NoAutoRename )
-#define DNSSDFlagsOption_PathEvalDone()                        DNSSDFlagOption( 'P', PathEvaluationDone )
-#define DNSSDFlagsOption_ReturnIntermediates() DNSSDFlagOption( 'I', ReturnIntermediates )
-#define DNSSDFlagsOption_Shared()                              DNSSDFlagOption( 'S', Shared )
-#define DNSSDFlagsOption_SuppressUnusable()            DNSSDFlagOption( 'S', SuppressUnusable )
-#define DNSSDFlagsOption_Timeout()                             DNSSDFlagOption( 'T', Timeout )
-#define DNSSDFlagsOption_UnicastResponse()             DNSSDFlagOption( 'U', UnicastResponse )
-#define DNSSDFlagsOption_Unique()                              DNSSDFlagOption( 'U', Unique )
-#define DNSSDFlagsOption_WakeOnResolve()               DNSSDFlagOption( 'W', WakeOnResolve )
+#define DNSSDFlagsOption_AllowExpiredAnswers()         DNSSDFlagOption( 'X', AllowExpiredAnswers )
+#define DNSSDFlagsOption_DenyCellular()                                DNSSDFlagOption( 'C', DenyCellular )
+#define DNSSDFlagsOption_DenyExpensive()                       DNSSDFlagOption( 'E', DenyExpensive )
+#define DNSSDFlagsOption_ForceMulticast()                      DNSSDFlagOption( 'M', ForceMulticast )
+#define DNSSDFlagsOption_IncludeAWDL()                         DNSSDFlagOption( 'A', IncludeAWDL )
+#define DNSSDFlagsOption_NoAutoRename()                                DNSSDFlagOption( 'N', NoAutoRename )
+#define DNSSDFlagsOption_PathEvalDone()                                DNSSDFlagOption( 'P', PathEvaluationDone )
+#define DNSSDFlagsOption_ReturnIntermediates()         DNSSDFlagOption( 'I', ReturnIntermediates )
+#define DNSSDFlagsOption_Shared()                                      DNSSDFlagOption( 'S', Shared )
+#define DNSSDFlagsOption_SuppressUnusable()                    DNSSDFlagOption( 'S', SuppressUnusable )
+#define DNSSDFlagsOption_Timeout()                                     DNSSDFlagOption( 'T', Timeout )
+#define DNSSDFlagsOption_UnicastResponse()                     DNSSDFlagOption( 'U', UnicastResponse )
+#define DNSSDFlagsOption_Unique()                                      DNSSDFlagOption( 'U', Unique )
+#define DNSSDFlagsOption_WakeOnResolve()                       DNSSDFlagOption( 'W', WakeOnResolve )
 
 // Interface option
 
@@ -394,6 +494,32 @@ static const char *                gConnectionOpt = kConnectionArg_Normal;
 
 #define RecordDataSection()            CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
 
+//===========================================================================================================================
+//     Output Formatting
+//===========================================================================================================================
+
+#define kOutputFormatStr_JSON          "json"
+#define kOutputFormatStr_XML           "xml"
+#define kOutputFormatStr_Binary                "binary"
+
+typedef enum
+{
+       kOutputFormatType_Invalid       = 0,
+       kOutputFormatType_JSON          = 1,
+       kOutputFormatType_XML           = 2,
+       kOutputFormatType_Binary        = 3
+       
+}      OutputFormatType;
+
+#define FormatOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP, IS_REQUIRED )                        \
+       StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, "format", SHORT_HELP, IS_REQUIRED,      \
+               "\n"                                                                                                                                                    \
+               "Use '" kOutputFormatStr_JSON   "' for JavaScript Object Notation (JSON).\n"    \
+               "Use '" kOutputFormatStr_XML    "' for property list XML version 1.0.\n"                \
+               "Use '" kOutputFormatStr_Binary "' for property list binary version 1.0.\n"             \
+               "\n"                                                                                                                                                    \
+       )
+
 //===========================================================================================================================
 //     Browse Command Options
 //===========================================================================================================================
@@ -444,6 +570,7 @@ static CLIOption            kGetAddrInfoOpts[] =
        
        CLI_OPTION_GROUP( "Flags" ),
        DNSSDFlagsOption(),
+       DNSSDFlagsOption_AllowExpiredAnswers(),
        DNSSDFlagsOption_DenyCellular(),
        DNSSDFlagsOption_DenyExpensive(),
        DNSSDFlagsOption_PathEvalDone(),
@@ -478,15 +605,16 @@ static CLIOption          kQueryRecordOpts[] =
        
        CLI_OPTION_GROUP( "Flags" ),
        DNSSDFlagsOption(),
-       DNSSDFlagsOption_IncludeAWDL(),
+       DNSSDFlagsOption_AllowExpiredAnswers(),
+       DNSSDFlagsOption_DenyCellular(),
+       DNSSDFlagsOption_DenyExpensive(),
        DNSSDFlagsOption_ForceMulticast(),
-       DNSSDFlagsOption_Timeout(),
+       DNSSDFlagsOption_IncludeAWDL(),
+       DNSSDFlagsOption_PathEvalDone(),
        DNSSDFlagsOption_ReturnIntermediates(),
        DNSSDFlagsOption_SuppressUnusable(),
+       DNSSDFlagsOption_Timeout(),
        DNSSDFlagsOption_UnicastResponse(),
-       DNSSDFlagsOption_DenyCellular(),
-       DNSSDFlagsOption_DenyExpensive(),
-       DNSSDFlagsOption_PathEvalDone(),
        
        CLI_OPTION_GROUP( "Operation" ),
        ConnectionOptions(),
@@ -672,7 +800,7 @@ static CLIOption            kGetAddrInfoPOSIXOpts[] =
        StringOption(   'n', "hostname",                        &gGAIPOSIX_HostName,            "hostname", "Domain name to resolve or an IPv4 or IPv6 address.", true ),
        StringOption(   's', "servname",                        &gGAIPOSIX_ServName,            "servname", "Port number in decimal or service name from services(5).", false ),
        
-       CLI_OPTION_GROUP( "Hints " ),
+       CLI_OPTION_GROUP( "Hints" ),
        StringOptionEx( 'f', "family",                          &gGAIPOSIX_Family,                      "address family", "Address family to use for hints ai_family field.", false,
                "\n"
                "Possible address family values are 'inet' for AF_INET, 'inet6' for AF_INET6, or 'unspec' for AF_UNSPEC. If no\n"
@@ -761,23 +889,23 @@ static CLIOption          kPortMappingOpts[] =
 //===========================================================================================================================
 
 static const char *            gBrowseAll_Domain                               = NULL;
-static char **                 gBrowseAll_ServiceTypes                 = NULL;
+static const char **   gBrowseAll_ServiceTypes                 = NULL;
 static size_t                  gBrowseAll_ServiceTypesCount    = 0;
 static int                             gBrowseAll_BrowseTimeSecs               = 5;
-static int                             gBrowseAll_MaxConnectTimeSecs   = 0;
+static int                             gBrowseAll_ConnectTimeout               = 0;
 
 static CLIOption               kBrowseAllOpts[] =
 {
        InterfaceOption(),
-       StringOption(      'd', "domain",       &gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ),
-       MultiStringOption( 't', "type",         &gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\". All services are browsed for if none is specified.", false ),
+       StringOption(      'd', "domain", &gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ),
+       MultiStringOption( 't', "type",   &gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\". All services are browsed for if none is specified.", false ),
        
        CLI_OPTION_GROUP( "Flags" ),
        DNSSDFlagsOption_IncludeAWDL(),
        
        CLI_OPTION_GROUP( "Operation" ),
-       IntegerOption( 'b', "browseTime",               &gBrowseAll_BrowseTimeSecs,             "seconds", "Amount of time to spend browsing. (Default: 5 seconds)", false ),
-       IntegerOption( 'c', "maxConnectTime",   &gBrowseAll_MaxConnectTimeSecs, "seconds", "Max duration of connection attempts. If <= 0, then no connections are attempted. (Default: 0 seconds)", false ),
+       IntegerOption( 'b', "browseTime",     &gBrowseAll_BrowseTimeSecs, "seconds", "Amount of time to spend browsing in seconds. (default: 5)", false ),
+       IntegerOption( 'c', "connectTimeout", &gBrowseAll_ConnectTimeout, "seconds", "Timeout for connection attempts. If <= 0, no connections are attempted. (default: 0)", false ),
        CLI_OPTION_END()
 };
 
@@ -927,6 +1055,119 @@ static CLIOption         kMDNSQueryOpts[] =
        CLI_OPTION_END()
 };
 
+//===========================================================================================================================
+//     MDNSCollider Command Options
+//===========================================================================================================================
+
+#define kMDNSColliderProgramSection_Intro                                                                                                                                                              \
+       "Programs dictate when the collider sends out unsolicited response messages for its record and how the collider\n"      \
+       "ought to react to probe queries that match its record's name, if at all.\n"                                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "For example, suppose that the goal is to cause a specific unique record in the verified state to be renamed.\n"        \
+       "The collider should be invoked such that its record's name is equal to that of the record being targeted. Also,\n"     \
+       "the record's type and data should be such that no record with that name, type, and data combination currently\n"       \
+       "exists. If the mDNS responder that owns the record follows sections 8.1 and 9 of RFC 6762, then the goal can be\n"     \
+       "accomplished with the following program:\n"                                                                                                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "    probes 3r; send; wait 5000\n"                                                                                                                                                                      \
+       "\n"                                                                                                                                                                                                                            \
+       "The first command, 'probes 3r', tells the collider to respond to the next three probe queries that match its\n"        \
+       "record's name. The second command, makes the collider send an unsolicited response message that contains its\n"        \
+       "record in the answer section. The third command makes the collider wait for five seconds before exiting, which\n"      \
+       "is more than enough time for the collider to respond to probe queries.\n"                                                                                      \
+       "\n"                                                                                                                                                                                                                            \
+       "The send command will cause the targeted record to go into the probing state per section 9 since the collider's\n"     \
+       "record conflicts with target record. Per the probes command, the subsequent probe query sent during the probing\n"     \
+       "state will be answered by the collider, which will cause the record to be renamed per section 8.1.\n"
+
+#define kMDNSColliderProgramSection_Probes                                                                                                                                                             \
+       "The probes command defines how the collider ought to react to probe queries that match its record's name.\n"           \
+       "\n"                                                                                                                                                                                                                            \
+       "Usage: probes [<action-string>]\n"                                                                                                                                                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "The syntax for an action-string is\n"                                                                                                                                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "    <action-string> ::= <action> | <action-string> \"-\" <action>\n"                                                                                           \
+       "    <action>        ::= [<repeat-count>] <action-code>\n"                                                                                                                      \
+       "    <repeat-count>  ::= \"1\" | \"2\" | ... | \"10\"\n"                                                                                                                        \
+       "    <action-code>   ::= \"n\" | \"r\" | \"u\" | \"m\" | \"p\"\n"                                                                                                       \
+       "\n"                                                                                                                                                                                                                            \
+       "An expanded action-string is defined as\n"                                                                                                                                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "    <expanded-action-string> ::= <action-code> | <expanded-action-string> \"-\" <action-code>\n"                                       \
+       "\n"                                                                                                                                                                                                                            \
+       "The action-string argument is converted into an expanded-action-string by expanding each action with a\n"                      \
+       "repeat-count into an expanded-action-string consisting of exactly <repeat-count> <action-code>s. For example,\n"       \
+       "2n-r expands to n-n-r. Action-strings that expand to expanded-action-strings with more than 10 action-codes\n"         \
+       "are not allowed.\n"                                                                                                                                                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "When the probes command is executed, it does two things. Firstly, it resets to zero the collider's count of\n"         \
+       "probe queries that match its record's name. Secondly, it defines how the collider ought to react to such probe\n"      \
+       "queries based on the action-string argument. Specifically, the nth action-code in the expanded version of the\n"       \
+       "action-string argument defines how the collider ought to react to the nth received probe query:\n"                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "    Code  Action\n"                                                                                                                                                                                            \
+       "    ----  ------\n"                                                                                                                                                                                            \
+       "    n     Do nothing.\n"                                                                                                                                                                                       \
+       "    r     Respond to the probe query.\n"                                                                                                                                                       \
+       "    u     Respond to the probe query via unicast.\n"                                                                                                                           \
+       "    m     Respond to the probe query via multicast.\n"                                                                                                                         \
+       "    p     Multicast own probe query. (Useful for causing simultaneous probe scenarios.)\n"                                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "Note: If no action is defined for a received probe query, then the collider does nothing, i.e., it doesn't send\n"     \
+       "a response nor does it multicast its own probe query.\n"
+
+#define kMDNSColliderProgramSection_Send                                                                                                                                                               \
+       "The send command multicasts an unsolicited mDNS response containing the collider's record in the answer\n"                     \
+       "section, which can be used to force unique records with the same record name into the probing state.\n"                        \
+       "\n"                                                                                                                                                                                                                            \
+       "Usage: send\n"
+
+#define kMDNSColliderProgramSection_Wait                                                                                                                                                               \
+       "The wait command pauses program execution for the interval of time specified by its argument.\n"                                       \
+       "\n"                                                                                                                                                                                                                            \
+       "Usage: wait <milliseconds>\n"
+
+#define kMDNSColliderProgramSection_Loop                                                                                                                                                               \
+       "The loop command starts a counting loop. The done statement marks the end of the loop body. The loop command's\n"      \
+       "argument specifies the number of loop iterations. Note: Loop nesting is supported up to a depth of 16.\n"                      \
+       "\n"                                                                                                                                                                                                                            \
+       "Usage: loop <non-zero count>; ... ; done\n"                                                                                                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "For example, the following program sends three unsolicited responses at an approximate rate of one per second:\n"      \
+       "\n"                                                                                                                                                                                                                            \
+       "    loop 3; wait 1000; send; done"
+
+#define ConnectionSection()            CLI_SECTION( kConnectionSection_Name, kConnectionSection_Text )
+
+static const char *            gMDNSCollider_Name                      = NULL;
+static const char *            gMDNSCollider_Type                      = NULL;
+static const char *            gMDNSCollider_RecordData        = NULL;
+static int                             gMDNSCollider_UseIPv4           = false;
+static int                             gMDNSCollider_UseIPv6           = false;
+static const char *            gMDNSCollider_Program           = NULL;
+
+static CLIOption               kMDNSColliderOpts[] =
+{
+       StringOption(  'i', "interface", &gInterface,               "name or index", "Network interface by name or index.", true ),
+       StringOption(  'n', "name",      &gMDNSCollider_Name,       "name", "Collider's record name.", true ),
+       StringOption(  't', "type",      &gMDNSCollider_Type,       "type", "Collider's record type.", true ),
+       StringOption(  'd', "data",      &gMDNSCollider_RecordData, "record data", "Collider's record data. See " kRecordDataSection_Name " below.", true ),
+       StringOption(  'p', "program",   &gMDNSCollider_Program,    "program", "Program to execute. See Program section below.", true ),
+       BooleanOption(  0 , "ipv4",      &gMDNSCollider_UseIPv4,    "Use IPv4." ),
+       BooleanOption(  0 , "ipv6",      &gMDNSCollider_UseIPv6,    "Use IPv6." ),
+       
+       RecordDataSection(),
+       CLI_SECTION( "Program",                                 kMDNSColliderProgramSection_Intro ),
+       CLI_SECTION( "Program Command: probes", kMDNSColliderProgramSection_Probes ),
+       CLI_SECTION( "Program Command: send",   kMDNSColliderProgramSection_Send ),
+       CLI_SECTION( "Program Command: wait",   kMDNSColliderProgramSection_Wait ),
+       CLI_SECTION( "Program Command: loop",   kMDNSColliderProgramSection_Loop ),
+       CLI_OPTION_END()
+};
+
+static void    MDNSColliderCmd( void );
+
 //===========================================================================================================================
 //     PIDToUUID Command Options
 //===========================================================================================================================
@@ -945,59 +1186,72 @@ static CLIOption         kPIDToUUIDOpts[] =
 
 #define kDNSServerInfoText_Intro                                                                                                                                                                               \
        "The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n"      \
-       "presence of special labels in the query's QNAME. There are currently seven types of special labels that can be\n"      \
+       "presence of special labels in the query's QNAME. There are currently eight types of special labels that can be\n"      \
        "used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n"      \
-       "IPv4 label, and the IPv6 label.\n"
+       "IPv4 label, the IPv6 label, and SRV labels.\n"                                                                                                                                         \
+       "\n"                                                                                                                                                                                                                            \
+       "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
 
 #define kDNSServerInfoText_NameExistence                                                                                                                                                               \
-       "A name is considered to exist if and only if it ends in d.test., and the other labels, if\n"                                           \
-       "any, consist of\n"                                                                                                                                                                                                     \
+       "A name is considered to exist if it's an Address name or an SRV name.\n"                                                                                       \
+       "\n"                                                                                                                                                                                                                            \
+       "An Address name is defined as a name that ends with d.test., and the other labels, if any, and in no particular\n"     \
+       "order, unless otherwise noted, consist of\n"                                                                                                                                           \
        "\n"                                                                                                                                                                                                                            \
        "    1. at most one Alias or Alias-TTL label as the first label;\n"                                                                                                     \
        "    2. at most one Count label;\n"                                                                                                                                                                     \
-       "    3. zero or more Tag labels;\n"                                                                                                                                                             \
+       "    3. zero or more Tag labels;\n"                                                                                                                                                                     \
        "    4. at most one TTL label; and\n"                                                                                                                                                           \
-       "    5. at most one IPv4 or IPv6 label.\n"
+       "    5. at most one IPv4 or IPv6 label.\n"                                                                                                                                                      \
+       "\n"                                                                                                                                                                                                                            \
+       "An SRV name is defined as a name with the following form:\n"                                                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       " _<service>._<proto>[.<parent domain>][.<SRV label 1>[.<target 1>][.<SRV label 2>[.<target 2>][...]]].d.test.\n"       \
+       "\n"                                                                                                                                                                                                                            \
+       "See \"SRV Names\" for details.\n"
 
 #define kDNSServerInfoText_ResourceRecords                                                                                                                                                             \
-       "Currently, the server only provides CNAME, A, and AAAA records.\n"                                                                                                     \
+       "Currently, the server only supports CNAME, A, AAAA, and SRV records.\n"                                                                                        \
        "\n"                                                                                                                                                                                                                            \
-       "Names that exist and begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n"       \
+       "Address names that begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n"         \
        "names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n"                                                          \
        "\n"                                                                                                                                                                                                                            \
-       "Names that exist and have an IPv4 label have at least one A record, but no AAAA records. Names that exist and\n"       \
-       "have an IPv6 label, have at least one AAAA record, but no A records. All other names that exist have at least\n"       \
-       "one A record and at least one AAAA record. See \"Count Labels\" for how the number of address records for a\n"         \
-       "given name is determined.\n"                                                                                                                                                                           \
+       "A canonical Address name can exclusively be the name of one or more A records, can exclusively be the name or\n"       \
+       "one or more AAAA records, or can be the name of both A and AAAA records. Address names that contain an IPv4\n"         \
+       "label have at least one A record, but no AAAA records. Address names that contain an IPv6 label, have at least\n"      \
+       "one AAAA record, but no A records. All other Address names have at least one A record and at least one AAAA\n"         \
+       "record. See \"Count Labels\" for how the number of address records for a given Address name is determined.\n"          \
        "\n"                                                                                                                                                                                                                            \
        "A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n"      \
-       "2001:db8::/32 block. Both of these address blocks are reserved for documentation.\n"                                                           \
-       "See <https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n"                                                        \
+       "2001:db8:1::/120 block. Both of these address blocks are reserved for documentation. See\n"                                                    \
+       "<https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n"                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "SRV names are names of SRV records.\n"                                                                                                                                                         \
        "\n"                                                                                                                                                                                                                            \
        "Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n"        \
-       "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for records with\n"       \
-       "specific TTL values.\n"
+       "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for CNAME, A, and\n"      \
+       "AAAA records with specific TTL values.\n"
 
 #define kDNSServerInfoText_AliasLabel                                                                                                                                                                  \
-       "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2 .. 2^31 - 1].\n"                            \
+       "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2, 2^31 - 1].\n"                                      \
        "\n"                                                                                                                                                                                                                            \
-       "If QNAME exist and its first label is Alias label \"alias-N\", then the response will contain exactly N CNAME\n"       \
-       "records:\n"                                                                                                                                                                                                            \
+       "If QNAME is an Address name and its first label is Alias label \"alias-N\", then the response will contain\n"          \
+       "exactly N CNAME records:\n"                                                                                                                                                                            \
        "\n"                                                                                                                                                                                                                            \
-       "    1. For each i in [3 .. N], the response will contain a CNAME record whose name is identical to QNAME,\n"           \
-       "       except that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME\n"           \
-       "       record whose name has \"alias-(i - 1)\" as its first label.\n"                                                                                          \
+       "    1. For each i in [3, N], the response will contain a CNAME record whose name is identical to QNAME, except\n"      \
+       "       that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME record whose\n"     \
+       "       name has \"alias-(i - 1)\" as its first label.\n"                                                                                                                       \
        "\n"                                                                                                                                                                                                                            \
        "    2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"       \
        "       is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n"       \
        "       \"alias\" instead.\n"                                                                                                                                                                           \
        "\n"                                                                                                                                                                                                                            \
        "    3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n"       \
-       "       is \"alias\" instead, and whose RDATA is the name identical to QNAME stripped of its first label.\n"            \
+       "       is \"alias\" instead, and whose RDATA is the name identical to QNAME minus its first label.\n"                          \
        "\n"                                                                                                                                                                                                                            \
-       "If QNAME exist and its first label is Alias label \"alias\", then the response will contain a single CNAME\n"          \
-       "record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to QNAME\n"            \
-       "stripped of its first label.\n"                                                                                                                                                                        \
+       "If QNAME is an Address name and its first label is Alias label \"alias\", then the response will contain a\n"          \
+       "single CNAME record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to\n"     \
+       "QNAME minus its first label.\n"                                                                                                                                                                        \
        "\n"                                                                                                                                                                                                                            \
        "Example. A response to a query with a QNAME of alias-3.count-5.d.test will contain the following CNAME\n"                      \
        "records:\n"                                                                                                                                                                                                            \
@@ -1009,12 +1263,12 @@ static CLIOption                kPIDToUUIDOpts[] =
 
 #define kDNSServerInfoText_AliasTTLLabel                                                                                                                                                               \
        "Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n"                          \
-       "[0 .. 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n"      \
+       "[0, 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n"        \
        "\n"                                                                                                                                                                                                                            \
-       "If QNAME exists and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response will contain\n"     \
-       "exactly N CNAME records:\n"                                                                                                                                                                            \
+       "If QNAME is an Address name and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response\n"      \
+       "will contain exactly N CNAME records:\n"                                                                                                                                                       \
        "\n"                                                                                                                                                                                                                            \
-       "    1. For each i in [1 .. N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n"       \
+       "    1. For each i in [1, N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n"         \
        "       except that the first label is \"alias-ttl-T_i...-T_N\" instead, whose TTL value is T_i, and whose RDATA\n"     \
        "       is the name of the other CNAME record whose name has \"alias-ttl-T_(i+1)...-T_N\" as its first label.\n"        \
        "\n"                                                                                                                                                                                                                            \
@@ -1030,14 +1284,14 @@ static CLIOption                kPIDToUUIDOpts[] =
        "    alias-ttl-80.count-5.d.test.                   80    IN CNAME count-5.d.test.\n"
 
 #define kDNSServerInfoText_CountLabel                                                                                                                                                                  \
-       "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1 .. 255] and N_2\n"      \
-       "is an integer in [N_1 .. 255].\n"                                                                                                                                                                      \
+       "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1, 255] and N_2 is\n"     \
+       "an integer in [N_1, 255].\n"                                                                                                                                                                           \
        "\n"                                                                                                                                                                                                                            \
-       "If QNAME exists, contains Count label \"count-N\", and has the type of address records specified by QTYPE, then\n"     \
-       "the response will contain exactly N address records:\n"                                                                                                                        \
+       "If QNAME is an Address name, contains Count label \"count-N\", and has the type of address records specified by\n"     \
+       "QTYPE, then the response will contain exactly N address records:\n"                                                                                            \
        "\n"                                                                                                                                                                                                                            \
-       "    1. For i in [1 .. N], the response will contain an address record of type QTYPE whose name is equal to\n"          \
-       "       QNAME and whose RDATA is an address equal to a constant base address + i.\n"                                                            \
+       "    1. For i in [1, N], the response will contain an address record of type QTYPE whose name is equal to QNAME\n"      \
+       "       and whose RDATA is an address equal to a constant base address + i.\n"                                                                          \
        "\n"                                                                                                                                                                                                                            \
        "    2. The address records will be ordered by the address contained in RDATA in ascending order.\n"                            \
        "\n"                                                                                                                                                                                                                            \
@@ -1048,26 +1302,26 @@ static CLIOption                kPIDToUUIDOpts[] =
        "    count-3.d.test.                                60    IN A     203.0.113.2\n"                                                                       \
        "    count-3.d.test.                                60    IN A     203.0.113.3\n"                                                                       \
        "\n"                                                                                                                                                                                                                            \
-       "If QNAME exists, contains Count label \"count-N_1-N_2\", and has the type of address records specified by\n"           \
-       "QTYPE, then the response will contain exactly N_1 address records:\n"                                                                                          \
+       "If QNAME is an Address name, contains Count label \"count-N_1-N_2\", and has the type of address records\n"            \
+       "specified by QTYPE, then the response will contain exactly N_1 address records:\n"                                                                     \
        "\n"                                                                                                                                                                                                                            \
        "    1. Each of the address records will be of type QTYPE, have name equal to QNAME, and have as its RDATA a\n"         \
-       "       unique address equal to a constant base address + i, where i is a randomly chosen integer in [1 .. N_2].\n"     \
+       "       unique address equal to a constant base address + i, where i is a randomly chosen integer in [1, N_2].\n"       \
        "\n"                                                                                                                                                                                                                            \
        "    2. The order of the address records will be random.\n"                                                                                                                     \
        "\n"                                                                                                                                                                                                                            \
        "Example. A response to a AAAA record query with a QNAME of count-3-100.ttl-20.d.test could contain the\n"                      \
        "following AAAA records:\n"                                                                                                                                                                                     \
        "\n"                                                                                                                                                                                                                            \
-       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8::c\n"                                                                       \
-       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8::3a\n"                                                                      \
-       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8::4f\n"                                                                      \
+       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::c\n"                                                                     \
+       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::3a\n"                                                            \
+       "    count-3-100.ttl-20.d.test.                     20    IN AAAA  2001:db8:1::4f\n"                                                            \
        "\n"                                                                                                                                                                                                                            \
-       "If QNAME exists, but doesn't have the type of address records specified by QTYPE, then the response will\n"            \
-       "contain no address records, regardless of whether it contains a Count label.\n"                                                                        \
+       "If QNAME is an Address name, but doesn't have the type of address records specified by QTYPE, then the response\n"     \
+       "will contain no address records, regardless of whether it contains a Count label.\n"                                                           \
        "\n"                                                                                                                                                                                                                            \
-       "QNAMEs that exist, but don't have a Count label are treated as though they contain a count label equal to\n"           \
-       "\"count-1\".\n"
+       "Address names that don't have a Count label are treated as though they contain a count label equal to\n"                       \
+       "count-1\".\n"
 
 #define kDNSServerInfoText_TagLabel                                                                                                                                                                            \
        "Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n"            \
@@ -1076,10 +1330,10 @@ static CLIOption                kPIDToUUIDOpts[] =
        "to increase the sizes of domain names.\n"
 
 #define kDNSServerInfoText_TTLLabel                                                                                                                                                                            \
-       "TTL labels are of the form \"ttl-T\", where T is an integer in [0 .. 2^31 - 1].\n"                                                                     \
+       "TTL labels are of the form \"ttl-T\", where T is an integer in [0, 2^31 - 1].\n"                                                                       \
        "\n"                                                                                                                                                                                                                            \
-       "If the name specified by QNAME exists, and contains TTL label \"ttl-T\", then all non-CNAME records contained\n"       \
-       "in the response will have a TTL value equal to T.\n"
+       "If QNAME is an Address name and contains TTL label \"ttl-T\", then all non-CNAME records contained in the\n"           \
+       "response will have a TTL value equal to T.\n"
 
 #define kDNSServerInfoText_IPv4Label \
        "The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n"
@@ -1087,25 +1341,58 @@ static CLIOption                kPIDToUUIDOpts[] =
 #define kDNSServerInfoText_IPv6Label \
        "The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"
 
-#define kDNSServerDefaultTTL           60
+#define kDNSServerInfoText_SRVNames                                                                                                                                                                            \
+       "SRV labels are of the form \"srv-R-W-P\", where R, W, and P are integers in [0, 2^16 - 1].\n"                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "After the first two labels, i.e., the service and protocol labels, the sequence of labels, which may be empty,\n"      \
+       "leading up to the the first SRV label, if one exists, or the d.test. labels will be used as a parent domain for\n"     \
+       "the target hostname of each of the SRV name's SRV records.\n"                                                                                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "If QNAME is an SRV name and QTYPE is SRV, then for each SRV label, the response will contain an SRV record with\n"     \
+       "priority R, weight W, port P, and target hostname <target>[.<parent domain>]., where <target> is the sequence\n"       \
+       "of labels, which may be empty, that follows the SRV label leading up to either the next SRV label or the\n"            \
+       "d.test. labels, whichever comes first.\n"                                                                                                                                                      \
+       "\n"                                                                                                                                                                                                                            \
+       "Example. A response to an SRV record query with a QNAME of\n"                                                                                                          \
+       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. will contain the following SRV records:\n"                      \
+       "\n"                                                                                                                                                                                                                            \
+       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   0 0 80 www.example.com.\n"           \
+       "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test.     60    IN SRV   1 0 8080 www.example.com.\n"
+
+#define kDNSServerInfoText_BadUDPMode \
+       "The purpose of Bad UDP mode is to test mDNSResponder's TCP fallback mechanism by which mDNSResponder reissues a\n"     \
+       "UDP query as a TCP query if the UDP response contains the expected QNAME, QTYPE, and QCLASS, but a message ID\n"       \
+       "that's not equal to the query's message ID.\n"                                                                                                                                         \
+       "\n"                                                                                                                                                                                                                            \
+       "This mode is identical to the normal mode except that all responses sent via UDP have a message ID equal to the\n"     \
+       "query's message ID plus one. Also, in this mode, to aid in debugging, A records in responses sent via UDP have\n"      \
+       "IPv4 addresses in the 0.0.0.0/24 block instead of the 203.0.113.0/24 block, i.e., 0.0.0.0 is used as the IPv4\n"       \
+       "base address, and AAAA records in responses sent via UDP have IPv6 addresses in the ::ffff:0:0/120 block\n"            \
+       "instead of the 2001:db8:1::/120 block, i.e., ::ffff:0:0 is used as the IPv6 base address.\n"
 
 static int                             gDNSServer_LoopbackOnly         = false;
 static int                             gDNSServer_Foreground           = false;
 static int                             gDNSServer_ResponseDelayMs      = 0;
-static int                             gDNSServer_DefaultTTL           = kDNSServerDefaultTTL;
+static int                             gDNSServer_DefaultTTL           = 60;
+static int                             gDNSServer_Port                         = kDNSPort;
+static const char *            gDNSServer_DomainOverride       = NULL;
 #if( TARGET_OS_DARWIN )
 static const char *            gDNSServer_FollowPID            = NULL;
 #endif
+static int                             gDNSServer_BadUDPMode           = false;
 
 static CLIOption               kDNSServerOpts[] =
 {
        BooleanOption( 'l', "loopback",      &gDNSServer_LoopbackOnly,    "Bind to to the loopback interface." ),
-       BooleanOption( 'f', "foreground",    &gDNSServer_Foreground,      "Directlog output to stdout instead of system logging." ),
+       BooleanOption( 'f', "foreground",    &gDNSServer_Foreground,      "Direct log output to stdout instead of system logging." ),
        IntegerOption( 'd', "responseDelay", &gDNSServer_ResponseDelayMs, "ms", "The amount of additional delay in milliseconds to apply to responses. (default: 0)", false ),
        IntegerOption(  0 , "defaultTTL",    &gDNSServer_DefaultTTL,      "seconds", "Resource record TTL value to use when unspecified. (default: 60)", false ),
+       IntegerOption( 'p', "port",          &gDNSServer_Port,            "port number", "UDP/TCP port number to use. Use 0 for any port. (default: 53)", false ),
+       StringOption(   0 , "domain",        &gDNSServer_DomainOverride,  "domain", "Used to override 'd.test.' as the server's domain.", false ),
 #if( TARGET_OS_DARWIN )
-       StringOption(   0 , "followPID",     &gDNSServer_FollowPID,       "pid", "Exit when the process (usually the parent proccess) specified by PID exits.", false ),
+       StringOption(   0 , "follow",        &gDNSServer_FollowPID,       "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
 #endif
+       BooleanOption(  0 , "badUDPMode",    &gDNSServer_BadUDPMode,      "Run in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
        
        CLI_SECTION( "Intro",                           kDNSServerInfoText_Intro ),
        CLI_SECTION( "Name Existence",          kDNSServerInfoText_NameExistence ),
@@ -1117,26 +1404,248 @@ static CLIOption               kDNSServerOpts[] =
        CLI_SECTION( "TTL Labels",                      kDNSServerInfoText_TTLLabel ),
        CLI_SECTION( "IPv4 Label",                      kDNSServerInfoText_IPv4Label ),
        CLI_SECTION( "IPv6 Label",                      kDNSServerInfoText_IPv6Label ),
+       CLI_SECTION( "SRV Names",                       kDNSServerInfoText_SRVNames ),
+       CLI_SECTION( "Bad UDP Mode",            kDNSServerInfoText_BadUDPMode ),
        CLI_OPTION_END()
 };
 
 static void    DNSServerCmd( void );
 
+//===========================================================================================================================
+//     MDNSReplier Command Options
+//===========================================================================================================================
+
+#define kMDNSReplierPortBase           50000
+
+#define kMDNSReplierInfoText_Intro                                                                                                                                                                             \
+       "The mDNS replier answers mDNS queries for its authoritative records. These records are of class IN and of types\n"     \
+       "PTR, SRV, TXT, A, and AAAA as described below.\n"                                                                                                                                      \
+       "\n"                                                                                                                                                                                                                            \
+       "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
+
+#define kMDNSReplierInfoText_Parameters                                                                                                                                                                        \
+       "There are five parameters that control the replier's set of authoritative records.\n"                                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "    1. <hostname> is the base name used for service instance names and the names of A and AAAA records. This\n"        \
+       "       parameter is specified with the --hostname option.\n"                                                                                                           \
+       "    2. <tag> is an arbitrary string used to uniquify service types. This parameter is specified with the --tag\n"      \
+       "       option.\n"                                                                                                                                                                                                      \
+       "    3. N_max in an integer in [1, 65535] and limits service types to those that have no more than N_max\n"                     \
+       "       instances. It also limits the number of hostnames to N_max, i.e., <hostname>.local.,\n"                                         \
+       "       <hostname>-1.local., ..., <hostname>-N_max.local. This parameter is specified with the\n"                                       \
+       "       --maxInstanceCount option.\n"                                                                                                                                                           \
+       "    4. N_a is an integer in [1, 255] and the number of A records per hostname. This parameter is specified\n"          \
+       "       with the --countA option.\n"                                                                                                                                                            \
+       "    5. N_aaaa is an integer in [1, 255] and the number of AAAA records per hostname. This parameter is\n"                      \
+       "       specified with the --countAAAA option.\n"
+
+#define kMDNSReplierInfoText_PTR                                                                                                                                                                               \
+       "The replier's authoritative PTR records have names of the form _t-<tag>-<L>-<N>._tcp.local., where L is an\n"          \
+       "integer in [1, 65535], and N is an integer in [1, N_max].\n"                                                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "For a given L and N, the replier has exactly N authoritative PTR records:\n"                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "    1. The first PTR record is defined as\n"                                                                                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                                         \
+       "        TYPE:  PTR\n"                                                                                                                                                                                          \
+       "        CLASS: IN\n"                                                                                                                                                                                           \
+       "        TTL:   4500\n"                                                                                                                                                                                         \
+       "        RDATA: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                      \
+       "\n"                                                                                                                                                                                                                            \
+       "    2. For each i in [2, N], there is one PTR record defined as\n"                                                                                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:  _t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                                         \
+       "        TYPE:  PTR\n"                                                                                                                                                                                          \
+       "        CLASS: IN\n"                                                                                                                                                                                           \
+       "        TTL:   4500\n"                                                                                                                                                                                         \
+       "        RDATA: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"
+
+#define kMDNSReplierInfoText_SRV                                                                                                                                                                               \
+       "The replier's authoritative SRV records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"        \
+       "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"            \
+       "\"<hostname> (<i>)\", where i is in [2, N].\n"                                                                                                                                         \
+       "\n"                                                                                                                                                                                                                            \
+       "For a given L and N, the replier has exactly N authoritative SRV records:\n"                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "    1. The first SRV record is defined as\n"                                                                                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:  <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                                      \
+       "        TYPE:  SRV\n"                                                                                                                                                                                          \
+       "        CLASS: IN\n"                                                                                                                                                                                           \
+       "        TTL:   120\n"                                                                                                                                                                                          \
+       "        RDATA:\n"                                                                                                                                                                                                      \
+       "            Priority: 0\n"                                                                                                                                                                                     \
+       "            Weight:   0\n"                                                                                                                                                                                     \
+       "            Port:     (50000 + L) mod 2^16\n"                                                                                                                                          \
+       "            Target:   <hostname>.local.\n"                                                                                                                                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "    2. For each i in [2, N], there is one SRV record defined as:\n"                                                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:  \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"                                                                                            \
+       "        TYPE:  SRV\n"                                                                                                                                                                                          \
+       "        CLASS: IN\n"                                                                                                                                                                                           \
+       "        TTL:   120\n"                                                                                                                                                                                          \
+       "        RDATA:\n"                                                                                                                                                                                                      \
+       "            Priority: 0\n"                                                                                                                                                                                     \
+       "            Weight:   0\n"                                                                                                                                                                                     \
+       "            Port:     (50000 + L) mod 2^16\n"                                                                                                                                          \
+       "            Target:   <hostname>-<i>.local.\n"
+
+#define kMDNSReplierInfoText_TXT                                                                                                                                                                               \
+       "The replier's authoritative TXT records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n"        \
+       "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n"            \
+       "\"<hostname> (<i>)\", where i is in [2, N].\n"                                                                                                                                         \
+       "\n"                                                                                                                                                                                                                            \
+       "For a given L and N, the replier has exactly N authoritative TXT records:\n"                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "    1. The first TXT record is defined as\n"                                                                                                                                           \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:     <hostname>._t-<tag>-<L>-<N>._tcp.local.\n"                                                                                                           \
+       "        TYPE:     TXT\n"                                                                                                                                                                                       \
+       "        CLASS:    IN\n"                                                                                                                                                                                        \
+       "        TTL:      4500\n"                                                                                                                                                                                      \
+       "        RDLENGTH: L\n"                                                                                                                                                                                         \
+       "        RDATA:    <one or more strings with an aggregate length of L octets>\n"                                                                        \
+       "\n"                                                                                                                                                                                                                            \
+       "    2. For each i in [2, N], there is one TXT record:\n"                                                                                                                       \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:     \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"                                                                                         \
+       "        TYPE:     TXT\n"                                                                                                                                                                                       \
+       "        CLASS:    IN\n"                                                                                                                                                                                        \
+       "        TTL:      4500\n"                                                                                                                                                                                      \
+       "        RDLENGTH: L\n"                                                                                                                                                                                         \
+       "        RDATA:    <one or more strings with an aggregate length of L octets>\n"                                                                        \
+       "\n"                                                                                                                                                                                                                            \
+       "The RDATA of each TXT record is exactly L octets and consists of a repeating series of the 15-byte string\n"           \
+       "\"hash=0x<32-bit FNV-1 hash of the record name as an 8-character hexadecimal string>\". The last instance of\n"        \
+       "the string may be truncated to satisfy the TXT record data's size requirement.\n"
+
+#define kMDNSReplierInfoText_A                                                                                                                                                                                 \
+       "The replier has exactly N_max x N_a authoritative A records:\n"                                                                                                        \
+       "\n"                                                                                                                                                                                                                            \
+       "    1. For each j in [1, N_a], an A record is defined as\n"                                                                                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:     <hostname>.local.\n"                                                                                                                                                         \
+       "        TYPE:     A\n"                                                                                                                                                                                         \
+       "        CLASS:    IN\n"                                                                                                                                                                                        \
+       "        TTL:      120\n"                                                                                                                                                                                       \
+       "        RDLENGTH: 4\n"                                                                                                                                                                                         \
+       "        RDATA:    0.0.1.<j>\n"                                                                                                                                                                         \
+       "\n"                                                                                                                                                                                                                            \
+       "    2. For each i in [2, N_max], for each j in [1, N_a], an A record is defined as\n"                                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:     <hostname>-<i>.local.\n"                                                                                                                                                     \
+       "        TYPE:     A\n"                                                                                                                                                                                         \
+       "        CLASS:    IN\n"                                                                                                                                                                                        \
+       "        TTL:      120\n"                                                                                                                                                                                       \
+       "        RDLENGTH: 4\n"                                                                                                                                                                                         \
+       "        RDATA:    0.<ceil(i / 256)>.<i mod 256>.<j>\n"
+
+#define kMDNSReplierInfoText_AAAA                                                                                                                                                                              \
+       "The replier has exactly N_max x N_aaaa authoritative AAAA records:\n"                                                                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "    1. For each j in [1, N_aaaa], a AAAA record is defined as\n"                                                                                                       \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:     <hostname>.local.\n"                                                                                                                                                         \
+       "        TYPE:     AAAA\n"                                                                                                                                                                                      \
+       "        CLASS:    IN\n"                                                                                                                                                                                        \
+       "        TTL:      120\n"                                                                                                                                                                                       \
+       "        RDLENGTH: 16\n"                                                                                                                                                                                        \
+       "        RDATA:    2001:db8:2::1:<j>\n"                                                                                                                                                         \
+       "\n"                                                                                                                                                                                                                            \
+       "    2. For each i in [2, N_max], for each j in [1, N_aaaa], a AAAA record is defined as\n"                                                     \
+       "\n"                                                                                                                                                                                                                            \
+       "        NAME:     <hostname>-<i>.local.\n"                                                                                                                                                     \
+       "        TYPE:     AAAA\n"                                                                                                                                                                                      \
+       "        CLASS:    IN\n"                                                                                                                                                                                        \
+       "        TTL:      120\n"                                                                                                                                                                                       \
+       "        RDLENGTH: 16\n"                                                                                                                                                                                        \
+       "        RDATA:    2001:db8:2::<i>:<j>\n"
+
+#define kMDNSReplierInfoText_Responses                                                                                                                                                                 \
+       "When generating answers for a query message, any two records pertaining to the same hostname will be grouped\n"        \
+       "together in the same response message, and any two records pertaining to different hostnames will be in\n"                     \
+       "separate response messages.\n"
+
+static const char *            gMDNSReplier_Hostname                   = NULL;
+static const char *            gMDNSReplier_ServiceTypeTag             = NULL;
+static int                             gMDNSReplier_MaxInstanceCount   = 1000;
+static int                             gMDNSReplier_NoAdditionals              = false;
+static int                             gMDNSReplier_RecordCountA               = 1;
+static int                             gMDNSReplier_RecordCountAAAA    = 1;
+static double                  gMDNSReplier_UnicastDropRate    = 0.0;
+static double                  gMDNSReplier_MulticastDropRate  = 0.0;
+static int                             gMDNSReplier_MaxDropCount               = 0;
+static int                             gMDNSReplier_UseIPv4                    = false;
+static int                             gMDNSReplier_UseIPv6                    = false;
+static int                             gMDNSReplier_Foreground                 = false;
+static const char *            gMDNSReplier_FollowPID              = NULL;
+
+static CLIOption               kMDNSReplierOpts[] =
+{
+       StringOption(  'i', "interface",        &gInterface,                     "name or index", "Network interface by name or index.", true ),
+       StringOption(  'n', "hostname",         &gMDNSReplier_Hostname,          "string", "Base name to use for hostnames and service instance names.", true ),
+       StringOption(  't', "tag",              &gMDNSReplier_ServiceTypeTag,    "string", "Tag to use for service types, e.g., _t-<tag>-<TXT size>-<count>._tcp.", true ),
+       IntegerOption( 'c', "maxInstanceCount", &gMDNSReplier_MaxInstanceCount,  "count", "Maximum number of service instances. (default: 1000)", false ),
+       BooleanOption(  0 , "noAdditionals",    &gMDNSReplier_NoAdditionals,     "When answering queries, don't include any additional records." ),
+       IntegerOption(  0 , "countA",           &gMDNSReplier_RecordCountA,      "count", "Number of A records per hostname. (default: 1)", false ),
+       IntegerOption(  0 , "countAAAA",        &gMDNSReplier_RecordCountAAAA,   "count", "Number of AAAA records per hostname. (default: 1)", false ),
+       DoubleOption(   0 , "udrop",            &gMDNSReplier_UnicastDropRate,   "probability", "Probability of dropping a unicast response. (default: 0.0)", false ),
+       DoubleOption(   0 , "mdrop",            &gMDNSReplier_MulticastDropRate, "probability", "Probability of dropping a multicast query or response. (default: 0.0)", false ),
+       IntegerOption(  0 , "maxDropCount",     &gMDNSReplier_MaxDropCount,      "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
+       BooleanOption(  0 , "ipv4",             &gMDNSReplier_UseIPv4,           "Use IPv4." ),
+       BooleanOption(  0 , "ipv6",             &gMDNSReplier_UseIPv6,           "Use IPv6." ),
+       BooleanOption( 'f', "foreground",       &gMDNSReplier_Foreground,        "Direct log output to stdout instead of system logging." ),
+#if( TARGET_OS_DARWIN )
+       StringOption(   0 , "follow",           &gMDNSReplier_FollowPID,         "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
+#endif
+       
+       CLI_SECTION( "Intro",                                                   kMDNSReplierInfoText_Intro ),
+       CLI_SECTION( "Authoritative Record Parameters", kMDNSReplierInfoText_Parameters ),
+       CLI_SECTION( "Authoritative PTR Records",               kMDNSReplierInfoText_PTR ),
+       CLI_SECTION( "Authoritative SRV Records",               kMDNSReplierInfoText_SRV ),
+       CLI_SECTION( "Authoritative TXT Records",               kMDNSReplierInfoText_TXT ),
+       CLI_SECTION( "Authoritative A Records",                 kMDNSReplierInfoText_A ),
+       CLI_SECTION( "Authoritative AAAA Records",              kMDNSReplierInfoText_AAAA ),
+       CLI_SECTION( "Responses",                                               kMDNSReplierInfoText_Responses ),
+       CLI_OPTION_END()
+};
+
+static void    MDNSReplierCmd( void );
+
 //===========================================================================================================================
 //     Test Command Options
 //===========================================================================================================================
 
+#define kTestExitStatusSection_Name            "Exit Status"
+#define kTestExitStatusSection_Text                                                                                                                                                                            \
+       "This test command can exit with one of three status codes:\n"                                                                                                          \
+       "\n"                                                                                                                                                                                                                            \
+       "0 - The test ran to completion and passed.\n"                                                                                                                                          \
+       "1 - A fatal error prevented the test from completing.\n"                                                                                                                       \
+       "2 - The test ran to completion, but it or a subtest failed. See test output for details.\n"                                            \
+       "\n"                                                                                                                                                                                                                            \
+       "Note: The pass/fail status applies to the correctness or results. It does not necessarily imply anything about\n"      \
+       "performance.\n"
+
+#define TestExitStatusSection()                CLI_SECTION( kTestExitStatusSection_Name, kTestExitStatusSection_Text )
+
+#define kGAIPerfTestSuiteName_Basic                    "basic"
+#define kGAIPerfTestSuiteName_Advanced         "advanced"
+
 static const char *            gGAIPerf_TestSuite                              = NULL;
 static int                             gGAIPerf_CallDelayMs                    = 10;
 static int                             gGAIPerf_ServerDelayMs                  = 10;
-static int                             gGAIPerf_DefaultIterCount               = 100;
+static int                             gGAIPerf_SkipPathEvalulation    = false;
+static int                             gGAIPerf_BadUDPMode                             = false;
+static int                             gGAIPerf_IterationCount                 = 100;
 static const char *            gGAIPerf_OutputFilePath                 = NULL;
-static const char *            gGAIPerf_OutputFormat                   = "json";
-static int                             gGAIPerf_OutputAppendNewLine    = false;
+static const char *            gGAIPerf_OutputFormat                   = kOutputFormatStr_JSON;
+static int                             gGAIPerf_OutputAppendNewline    = false;
 
 static void    GAIPerfCmd( void );
 
-#define kGAIPerfSectionTitle_TestSuiteBasic            "Test Suite \"Basic\""
 #define kGAIPerfSectionText_TestSuiteBasic                                                                                                                                                                     \
        "This test suite consists of the following three test cases:\n"                                                                                                                 \
        "\n"                                                                                                                                                                                                                                    \
@@ -1161,7 +1670,6 @@ static void       GAIPerfCmd( void );
        "\n"                                                                                                                                                                                                                                    \
        "Test Case #3: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n"
 
-#define kGAIPerfSectionTitle_TestSuiteAdvanced         "Test Suite \"Advanced\""
 #define kGAIPerfSectionText_TestSuiteAdvanced                                                                                                                                                          \
        "This test suite consists of 33 test cases. Test cases 1 through 32 can be described in the following way\n"                    \
        "\n"                                                                                                                                                                                                                                    \
@@ -1204,30 +1712,150 @@ static CLIOption               kGAIPerfOpts[] =
 {
        StringOptionEx( 's', "suite",         &gGAIPerf_TestSuite,           "name", "Name of the predefined test suite to run.", true,
                "\n"
-               "There are currently two predefined test suites, 'basic' and 'advanced', which are described below.\n"
+               "There are currently two predefined test suites, '" kGAIPerfTestSuiteName_Basic "' and '" kGAIPerfTestSuiteName_Advanced "', which are described below.\n"
                "\n"
        ),
        StringOption(   'o', "output",        &gGAIPerf_OutputFilePath,      "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
-       StringOptionEx( 'f', "format",        &gGAIPerf_OutputFormat,        "format", "Specifies the test results output format. (default: json)", false,
-               "\n"
-               "Use 'json' for JavaScript Object Notation (JSON).\n"
-               "Use 'xml' for property list XML version 1.0.\n"
-               "Use 'binary' for property list binary version 1.0.\n"
-               "\n"
-       ),
-       BooleanOption(  'n', "appendNewline", &gGAIPerf_OutputAppendNewLine, "If the output format is JSON, output a trailing newline character." ),
+       FormatOption(   'f', "format",        &gGAIPerf_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       BooleanOption(  'n', "appendNewline", &gGAIPerf_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
        IntegerOption(   0 , "callDelay",     &gGAIPerf_CallDelayMs,         "ms", "Time to wait before calling DNSServiceGetAddrInfo() in milliseconds. (default: 10)", false ),
-       IntegerOption(   0 , "responseDelay", &gGAIPerf_ServerDelayMs,       "ms", "Additional delay in milliseconds to have the test DNS server apply to responses. (default: 0)", false ),
-       IntegerOption(  'i', "iterations",    &gGAIPerf_DefaultIterCount,    "count", "The default number of test case iterations. (default: 100)", false ),
+       BooleanOption(   0 , "skipPathEval",  &gGAIPerf_SkipPathEvalulation, "Use kDNSServiceFlagsPathEvaluationDone when calling DNSServiceGetAddrInfo()." ),
+       IntegerOption(  'i', "iterations",    &gGAIPerf_IterationCount,      "count", "The number of iterations per test case. (default: 100)", false ),
+       
+       CLI_OPTION_GROUP( "DNS Server Options" ),
+       IntegerOption(   0 , "responseDelay", &gGAIPerf_ServerDelayMs,       "ms", "Additional delay in milliseconds to have the server apply to responses. (default: 10)", false ),
+       BooleanOption(   0 , "badUDPMode",    &gGAIPerf_BadUDPMode,          "Run server in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
+       
+       CLI_SECTION( "Test Suite \"Basic\"",    kGAIPerfSectionText_TestSuiteBasic ),
+       CLI_SECTION( "Test Suite \"Advanced\"", kGAIPerfSectionText_TestSuiteAdvanced ),
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
+static void    MDNSDiscoveryTestCmd( void );
+
+static int                             gMDNSDiscoveryTest_InstanceCount                = 100;
+static int                             gMDNSDiscoveryTest_TXTSize                              = 100;
+static int                             gMDNSDiscoveryTest_BrowseTimeSecs               = 2;
+static int                             gMDNSDiscoveryTest_FlushCache                   = false;
+static char *                  gMDNSDiscoveryTest_Interface                    = NULL;
+static int                             gMDNSDiscoveryTest_NoAdditionals                = false;
+static int                             gMDNSDiscoveryTest_RecordCountA                 = 1;
+static int                             gMDNSDiscoveryTest_RecordCountAAAA              = 1;
+static double                  gMDNSDiscoveryTest_UnicastDropRate              = 0.0;
+static double                  gMDNSDiscoveryTest_MulticastDropRate    = 0.0;
+static int                             gMDNSDiscoveryTest_MaxDropCount                 = 0;
+static int                             gMDNSDiscoveryTest_UseIPv4                              = false;
+static int                             gMDNSDiscoveryTest_UseIPv6                              = false;
+static const char *            gMDNSDiscoveryTest_OutputFormat                 = kOutputFormatStr_JSON;
+static int                             gMDNSDiscoveryTest_OutputAppendNewline  = false;
+static const char *            gMDNSDiscoveryTest_OutputFilePath               = NULL;
+
+static CLIOption               kMDNSDiscoveryTestOpts[] =
+{
+       IntegerOption( 'c', "instanceCount",  &gMDNSDiscoveryTest_InstanceCount,       "count", "Number of service instances to discover. (default: 100)", false ),
+       IntegerOption( 's', "txtSize",        &gMDNSDiscoveryTest_TXTSize,             "bytes", "Desired size of each service instance's TXT record data. (default: 100)", false ),
+       IntegerOption( 'b', "browseTime",     &gMDNSDiscoveryTest_BrowseTimeSecs,      "seconds", "Amount of time to spend browsing in seconds. (default: 2)", false ),
+       BooleanOption(  0 , "flushCache",     &gMDNSDiscoveryTest_FlushCache,          "Flush mDNSResponder's record cache before browsing. Requires root privileges." ),
+       
+       CLI_OPTION_GROUP( "mDNS Replier Parameters" ),
+       StringOption(  'i', "interface",      &gMDNSDiscoveryTest_Interface,           "name or index", "Network interface. If unspecified, any available mDNS-capable interface will be used.", false ),
+       BooleanOption(  0 , "noAdditionals",  &gMDNSDiscoveryTest_NoAdditionals,       "When answering queries, don't include any additional records." ),
+       IntegerOption(  0 , "countA",         &gMDNSDiscoveryTest_RecordCountA,        "count", "Number of A records per hostname. (default: 1)", false ),
+       IntegerOption(  0 , "countAAAA",      &gMDNSDiscoveryTest_RecordCountAAAA,     "count", "Number of AAAA records per hostname. (default: 1)", false ),
+       DoubleOption(   0 , "udrop",          &gMDNSDiscoveryTest_UnicastDropRate,     "probability", "Probability of dropping a unicast response. (default: 0.0)", false ),
+       DoubleOption(   0 , "mdrop",          &gMDNSDiscoveryTest_MulticastDropRate,   "probability", "Probability of dropping a multicast query or response. (default: 0.0)", false ),
+       IntegerOption(  0 , "maxDropCount",   &gMDNSDiscoveryTest_MaxDropCount,        "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
+       BooleanOption(  0 , "ipv4",           &gMDNSDiscoveryTest_UseIPv4,             "Use IPv4." ),
+       BooleanOption(  0 , "ipv6",           &gMDNSDiscoveryTest_UseIPv6,             "Use IPv6." ),
+       
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption(   'f', "format",        &gMDNSDiscoveryTest_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption(   'o', "output",        &gMDNSDiscoveryTest_OutputFilePath,      "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+       BooleanOption(  'n', "appendNewline", &gMDNSDiscoveryTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+       
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
+static void    DotLocalTestCmd( void );
+
+static const char *            gDotLocalTest_Interface                         = NULL;
+static const char *            gDotLocalTest_OutputFormat                      = kOutputFormatStr_JSON;
+static int                             gDotLocalTest_OutputAppendNewline       = false;
+static const char *            gDotLocalTest_OutputFilePath            = NULL;
+
+#define kDotLocalTestSubtestDesc_GAIMDNSOnly   "GAI for a dotlocal name that has only MDNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAIDNSOnly            "GAI for a dotlocal name that has only DNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAIBoth               "GAI for a dotlocal name that has both mDNS and DNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAINeither            "GAI for a dotlocal name that has no A or AAAA records."
+#define kDotLocalTestSubtestDesc_GAINoSuchRecord \
+       "GAI for a dotlocal name that has no A or AAAA records, but is a subdomain name of a search domain."
+#define kDotLocalTestSubtestDesc_QuerySRV              "SRV query for a dotlocal name that has only a DNS SRV record."
+
+#define kDotLocalTestSectionText_Description                                                                                                                                                           \
+       "The goal of the dotlocal test is to verify that mDNSResponder properly handles queries for domain names in the\n"              \
+       "local domain when a local SOA record exists. As part of the test setup, a test DNS server and an mdnsreplier are\n"    \
+       "spawned, and a dummy local SOA record is registered with DNSServiceRegisterRecord(). The server is invoked such\n"             \
+       "that its domain is a second-level subdomain of the local domain, i.e., <some label>.local, while the mdnsreplier is\n" \
+       "invoked such that its base hostname is equal to the server's domain, e.g., if the server's domain is test.local.,\n"   \
+       "then the mdnsreplier's base hostname is test.local.\n"                                                                                                                                 \
+       "\n"                                                                                                                                                                                                                                    \
+       "The dotlocal test consists of six subtests that perform either a DNSServiceGetAddrInfo (GAI) operation for a\n"                \
+       "hostname in the local domain or a DNSServiceQueryRecord operation to query for an SRV record in the local domain:\n"   \
+       "\n"                                                                                                                                                                                                                                    \
+       "1. " kDotLocalTestSubtestDesc_GAIMDNSOnly              "\n"                                                                                                                                    \
+       "2. " kDotLocalTestSubtestDesc_GAIDNSOnly               "\n"                                                                                                                                    \
+       "3. " kDotLocalTestSubtestDesc_GAIBoth                  "\n"                                                                                                                                    \
+       "4. " kDotLocalTestSubtestDesc_GAINeither               "\n"                                                                                                                                    \
+       "5. " kDotLocalTestSubtestDesc_GAINoSuchRecord  "\n"                                                                                                                                    \
+       "6. " kDotLocalTestSubtestDesc_QuerySRV                 "\n"                                                                                                                                    \
+       "\n"                                                                                                                                                                                                                                    \
+       "Each subtest runs for five seconds.\n"
+
+static CLIOption               kDotLocalTestOpts[] =
+{
+       StringOption(  'i', "interface",     &gDotLocalTest_Interface,           "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+       
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption(  'f', "format",        &gDotLocalTest_OutputFormat,        "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption(  'o', "output",        &gDotLocalTest_OutputFilePath,      "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+       BooleanOption( 'n', "appendNewline", &gDotLocalTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+       
+       CLI_SECTION( "Description", kDotLocalTestSectionText_Description ),
+       TestExitStatusSection(),
+       CLI_OPTION_END()
+};
+
+static void    ProbeConflictTestCmd( void );
+
+static const char *            gProbeConflictTest_Interface                    = NULL;
+static int                             gProbeConflictTest_UseComputerName              = false;
+static const char *            gProbeConflictTest_OutputFormat                 = kOutputFormatStr_JSON;
+static int                             gProbeConflictTest_OutputAppendNewline  = false;
+static const char *            gProbeConflictTest_OutputFilePath               = NULL;
+
+static CLIOption               kProbeConflictTestOpts[] =
+{
+       StringOption(  'i', "interface",       &gProbeConflictTest_Interface,           "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+       BooleanOption( 'c', "useComputerName", &gProbeConflictTest_UseComputerName,     "Use the device's \"computer name\" for the test service's name." ),
        
-       CLI_SECTION( kGAIPerfSectionTitle_TestSuiteBasic,               kGAIPerfSectionText_TestSuiteBasic ),
-       CLI_SECTION( kGAIPerfSectionTitle_TestSuiteAdvanced,    kGAIPerfSectionText_TestSuiteAdvanced ),
+       CLI_OPTION_GROUP( "Results" ),
+       FormatOption(  'f', "format",          &gProbeConflictTest_OutputFormat,        "Specifies the test report output format. (default: " kOutputFormatStr_JSON ")", false ),
+       StringOption(  'o', "output",          &gProbeConflictTest_OutputFilePath,      "path", "Path of the file to write test report to instead of standard output (stdout).", false ),
+       BooleanOption( 'n', "appendNewline",   &gProbeConflictTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+       
+       TestExitStatusSection(),
        CLI_OPTION_END()
 };
 
 static CLIOption               kTestOpts[] =
 {
-       Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Run DNSServiceGetAddrInfo() performance tests.", false ),
+       Command( "gaiperf",        GAIPerfCmd,           kGAIPerfOpts,            "Runs DNSServiceGetAddrInfo() performance tests.", false ),
+       Command( "mdnsdiscovery",  MDNSDiscoveryTestCmd, kMDNSDiscoveryTestOpts,  "Tests mDNS service discovery for correctness.", false ),
+       Command( "dotlocal",       DotLocalTestCmd,      kDotLocalTestOpts,       "Tests DNS and mDNS queries for domain names in the local domain.", false ),
+       Command( "probeconflicts", ProbeConflictTestCmd, kProbeConflictTestOpts,  "Tests various probing conflict scenarios.", false ),
+       
        CLI_OPTION_END()
 };
 
@@ -1429,9 +2057,11 @@ static CLIOption         kGlobalOpts[] =
 #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
        Command( "DNSCrypt",                    DNSCryptCmd,                    kDNSCryptOpts,                  "Crafts and sends a DNSCrypt query.", true ),
 #endif
-       Command( "mDNSQuery",                   MDNSQueryCmd,                   kMDNSQueryOpts,                 "Crafts and sends an mDNS query over the specified interface.", true ),
+       Command( "mdnsquery",                   MDNSQueryCmd,                   kMDNSQueryOpts,                 "Crafts and sends an mDNS query over the specified interface.", true ),
+       Command( "mdnscollider",                MDNSColliderCmd,                kMDNSColliderOpts,              "Creates record name collision scenarios.", true ),
        Command( "pid2uuid",                    PIDToUUIDCmd,                   kPIDToUUIDOpts,                 "Prints the UUID of a process.", true ),
        Command( "server",                              DNSServerCmd,                   kDNSServerOpts,                 "DNS server for testing.", true ),
+       Command( "mdnsreplier",                 MDNSReplierCmd,                 kMDNSReplierOpts,               "Responds to mDNS queries for a set of authoritative resource records.", true ),
        Command( "test",                                NULL,                                   kTestOpts,                              "Commands for testing DNS-SD.", true ),
        Command( "ssdp",                                NULL,                                   kSSDPOpts,                              "Commands for testing Simple Service Discovery Protocol (SSDP).", true ),
 #if( TARGET_OS_DARWIN )
@@ -1468,6 +2098,12 @@ static int
                PrintFFormat *  inFormat,
                PrintFVAList *  inArgs,
                void *                  inUserContext );
+static int
+       PrintFAddRmvFlagsHandler(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext );
 
 static DNSServiceFlags GetDNSSDFlagsFromOpts( void );
 
@@ -1522,6 +2158,15 @@ static OSStatus
                const void *            inNamePtr,
                char                            inBuf[ kDNSServiceMaxDomainName ],
                const uint8_t **        outNextPtr );
+static OSStatus
+       DNSMessageExtractQuestion(
+               const uint8_t *         inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t *         inPtr,
+               uint8_t                         inNameBuf[ kDomainNameLengthMax ],
+               uint16_t *                      outType,
+               uint16_t *                      outClass,
+               const uint8_t **        outPtr );
 static OSStatus
        DNSMessageExtractRecord(
                const uint8_t *         inMsgPtr,
@@ -1550,6 +2195,10 @@ static OSStatus
                uint8_t **              outEndPtr );
 static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
 static size_t  DomainNameLength( const uint8_t *inName );
+static OSStatus        DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen );
+#define DomainNameDup( IN_NAME, OUT_NAME, OUT_LEN )                            DomainNameDupEx( IN_NAME, false, OUT_NAME, OUT_LEN )
+#define DomainNameDupLower( IN_NAME, OUT_NAME, OUT_LEN )               DomainNameDupEx( IN_NAME, true, OUT_NAME, OUT_LEN )
+
 static OSStatus
        DomainNameFromString(
                uint8_t                 inDomainName[ kDomainNameLengthMax ],
@@ -1618,6 +2267,10 @@ static OSStatus
                DispatchHandler         inCancelHandler,
                void *                          inContext,
                dispatch_source_t *     outTimer );
+
+#define DispatchTimerOneShotCreate( IN_START, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, IN_CONTEXT, OUT_TIMER )   \
+       DispatchTimerCreate( IN_START, DISPATCH_TIME_FOREVER, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, NULL, IN_CONTEXT, OUT_TIMER )
+
 static OSStatus
        DispatchProcessMonitorCreate(
                pid_t                           inPID,
@@ -1647,7 +2300,9 @@ static void                               SocketContextCancelHandler( void *inContext );
 
 static OSStatus                StringToInt32( const char *inString, int32_t *outValue );
 static OSStatus                StringToUInt32( const char *inString, uint32_t *outValue );
-static OSStatus                StringToLongLong( const char *inString, long long *outValue );
+#if( TARGET_OS_DARWIN )
+static OSStatus                StringToPID( const char *inString, pid_t *outPID );
+#endif
 static OSStatus                StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
 static OSStatus                StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
 static OSStatus                StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen );
@@ -1666,45 +2321,263 @@ static OSStatus
                Boolean                 inNoPortReuse,
                SocketRef *             outSock );
 
-typedef uint64_t               MicroTime64;
+static const struct sockaddr * GetMDNSMulticastAddrV4( void );
+static const struct sockaddr * GetMDNSMulticastAddrV6( void );
+static OSStatus                                        GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex );
 
-static MicroTime64     GetCurrentMicroTime( void );    // Gets the number of milliseconds since 1970-01-01T00:00:00Z
+static OSStatus
+       CreateMulticastSocket(
+               const struct sockaddr * inAddr,
+               int                                             inPort,
+               const char *                    inIfName,
+               uint32_t                                inIfIndex,
+               Boolean                                 inJoin,
+               int *                                   outPort,
+               SocketRef *                             outSock );
+
+static OSStatus        DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint32_t *outValue, const char **outPtr );
+static OSStatus        CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax );
+static OSStatus        CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax );
+static OSStatus        CheckRootUser( void );
+static OSStatus        SpawnCommand( pid_t *outPID, const char *inFormat, ... );
+static OSStatus
+       OutputPropertyList(
+               CFPropertyListRef       inPList,
+               OutputFormatType        inType,
+               Boolean                         inAppendNewline,
+               const char *            inOutputFilePath );
+static void
+       DNSRecordFixedFieldsSet(
+               DNSRecordFixedFields *  inFields,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint32_t                                inTTL,
+               uint16_t                                inRDLength );
+static void
+       SRVRecordDataFixedFieldsGet(
+               const SRVRecordDataFixedFields *        inFields,
+               unsigned int *                                          outPriority,
+               unsigned int *                                          outWeight,
+               unsigned int *                                          outPort );
+static void
+       SRVRecordDataFixedFieldsSet(
+               SRVRecordDataFixedFields *      inFields,
+               uint16_t                                        inPriority,
+               uint16_t                                        inWeight,
+               uint16_t                                        inPort );
+static void
+       SOARecordDataFixedFieldsGet(
+               const SOARecordDataFixedFields *        inFields,
+               uint32_t *                                                      outSerial,
+               uint32_t *                                                      outRefresh,
+               uint32_t *                                                      outRetry,
+               uint32_t *                                                      outExpire,
+               uint32_t *                                                      outMinimum );
+static void
+       SOARecordDataFixedFieldsSet(
+               SOARecordDataFixedFields *      inFields,
+               uint32_t                                        inSerial,
+               uint32_t                                        inRefresh,
+               uint32_t                                        inRetry,
+               uint32_t                                        inExpire,
+               uint32_t                                        inMinimum );
+static OSStatus        CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus        CreateTXTRecordDataFromString( const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen );
+static OSStatus
+       CreateNSECRecordData(
+               const uint8_t * inNextDomainName,
+               uint8_t **              outPtr,
+               size_t *                outLen,
+               unsigned int    inTypeCount,
+               ... );
+static OSStatus
+       AppendSOARecord(
+               DataBuffer *    inDB,
+               const uint8_t * inNamePtr,
+               size_t                  inNameLen,
+               uint16_t                inType,
+               uint16_t                inClass,
+               uint32_t                inTTL,
+               const uint8_t * inMName,
+               const uint8_t * inRName,
+               uint32_t                inSerial,
+               uint32_t                inRefresh,
+               uint32_t                inRetry,
+               uint32_t                inExpire,
+               uint32_t                inMinimumTTL,
+               size_t *                outLen );
+static OSStatus
+       CreateSOARecordData(
+               const uint8_t * inMName,
+               const uint8_t * inRName,
+               uint32_t                inSerial,
+               uint32_t                inRefresh,
+               uint32_t                inRetry,
+               uint32_t                inExpire,
+               uint32_t                inMinimumTTL,
+               uint8_t **              outPtr,
+               size_t *                outLen );
+static OSStatus
+       _DataBuffer_AppendDNSQuestion(
+               DataBuffer *    inDB,
+               const uint8_t * inNamePtr,
+               size_t                  inNameLen,
+               uint16_t                inType,
+               uint16_t                inClass );
+static OSStatus
+       _DataBuffer_AppendDNSRecord(
+               DataBuffer *    inDB,
+               const uint8_t * inNamePtr,
+               size_t                  inNameLen,
+               uint16_t                inType,
+               uint16_t                inClass,
+               uint32_t                inTTL,
+               const uint8_t * inRDataPtr,
+               size_t                  inRDataLen );
+static char *  _NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen );
 
-#define AddRmvString( X )              ( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
-#define Unused( X )                            (void)(X)
+#define Unused( X )            (void)(X)
 
 //===========================================================================================================================
-//     main
+//     MDNSCollider
 //===========================================================================================================================
 
-int    main( int argc, const char **argv )
-{
-       OSStatus                err;
-       
-       // Route DebugServices logging output to stderr.
-       
-       dlog_control( "DebugServices:output=file;stderr" );
-       
-       PrintFRegisterExtension( "du:time",   PrintFTimestampHandler,  NULL );
-       PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL );
-       CLIInit( argc, argv );
-       err = CLIParse( kGlobalOpts, kCLIFlags_None );
-       if( err ) exit( 1 );
-       
-       return( gExitCode );
-}
+typedef struct MDNSColliderPrivate *           MDNSColliderRef;
+
+typedef uint32_t               MDNSColliderProtocols;
+#define kMDNSColliderProtocol_None             0
+#define kMDNSColliderProtocol_IPv4             ( 1 << 0 )
+#define kMDNSColliderProtocol_IPv6             ( 1 << 1 )
+
+typedef void ( *MDNSColliderStopHandler_f )( void *inContext, OSStatus inError );
+
+static OSStatus        MDNSColliderCreate( dispatch_queue_t inQueue, MDNSColliderRef *outCollider );
+static OSStatus        MDNSColliderStart( MDNSColliderRef inCollider );
+static void            MDNSColliderStop( MDNSColliderRef inCollider );
+static void            MDNSColliderSetProtocols( MDNSColliderRef inCollider, MDNSColliderProtocols inProtocols );
+static void            MDNSColliderSetInterfaceIndex( MDNSColliderRef inCollider, uint32_t inInterfaceIndex );
+static OSStatus        MDNSColliderSetProgram( MDNSColliderRef inCollider, const char *inProgramStr );
+static void
+       MDNSColliderSetStopHandler(
+               MDNSColliderRef                         inCollider,
+               MDNSColliderStopHandler_f       inStopHandler,
+               void *                                          inStopContext );
+static OSStatus
+       MDNSColliderSetRecord(
+               MDNSColliderRef inCollider,
+               const uint8_t * inName,
+               uint16_t                inType,
+               const void *    inRDataPtr,
+               size_t                  inRDataLen );
+static CFTypeID        MDNSColliderGetTypeID( void );
 
 //===========================================================================================================================
-//     VersionOptionCallback
+//     ServiceBrowser
 //===========================================================================================================================
 
-static OSStatus        VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
+typedef struct ServiceBrowserPrivate *         ServiceBrowserRef;
+typedef struct ServiceBrowserResults           ServiceBrowserResults;
+typedef struct SBRDomain                                       SBRDomain;
+typedef struct SBRServiceType                          SBRServiceType;
+typedef struct SBRServiceInstance                      SBRServiceInstance;
+typedef struct SBRIPAddress                                    SBRIPAddress;
+
+typedef void ( *ServiceBrowserCallback_f )( ServiceBrowserResults *inResults, OSStatus inError, void *inContext );
+
+struct ServiceBrowserResults
 {
-       const char *            srcVers;
-#if( MDNSRESPONDER_PROJECT )
-       char                            srcStr[ 16 ];
-#endif
-       
+       SBRDomain *             domainList;     // List of domains in which services were found.
+};
+
+struct SBRDomain
+{
+       SBRDomain *                             next;           // Next domain in list.
+       char *                                  name;           // Name of domain represented by this object.
+       SBRServiceType *                typeList;       // List of service types in this domain.
+};
+
+struct SBRServiceType
+{
+       SBRServiceType *                        next;                   // Next service type in list.
+       char *                                          name;                   // Name of service type represented by this object.
+       SBRServiceInstance *            instanceList;   // List of service instances of this service type.
+};
+
+struct SBRServiceInstance
+{
+       SBRServiceInstance *            next;                   // Next service instance in list.
+       char *                                          name;                   // Name of service instance represented by this object.
+       char *                                          hostname;               // Target from service instance's SRV record.
+       uint32_t                                        ifIndex;                // Index of interface over which this service instance was discovered.
+       uint16_t                                        port;                   // Port from service instance's SRV record.
+       uint8_t *                                       txtPtr;                 // Service instance's TXT record data.
+       size_t                                          txtLen;                 // Service instance's TXT record data length.
+       SBRIPAddress *                          ipaddrList;             // List of IP addresses that the hostname resolved to.
+       uint64_t                                        discoverTimeUs; // Time it took to discover this service instance in microseconds.
+       uint64_t                                        resolveTimeUs;  // Time it took to resolve this service instance in microseconds.
+};
+
+struct SBRIPAddress
+{
+       SBRIPAddress *          next;                   // Next IP address in list.
+       sockaddr_ip                     sip;                    // IPv4 or IPv6 address.
+       uint64_t                        resolveTimeUs;  // Time it took to resolve this IP address in microseconds.
+};
+
+static CFTypeID        ServiceBrowserGetTypeID( void );
+static OSStatus
+       ServiceBrowserCreate(
+               dispatch_queue_t        inQueue,
+               uint32_t                        inInterfaceIndex,
+               const char *            inDomain,
+               unsigned int            inBrowseTimeSecs,
+               Boolean                         inIncludeAWDL,
+               ServiceBrowserRef *     outBrowser );
+static void            ServiceBrowserStart( ServiceBrowserRef inBrowser );
+static OSStatus        ServiceBrowserAddServiceType( ServiceBrowserRef inBrowser, const char *inServiceType );
+static void
+       ServiceBrowserSetCallback(
+               ServiceBrowserRef                       inBrowser,
+               ServiceBrowserCallback_f        inCallback,
+               void *                                          inContext );
+static void            ServiceBrowserResultsRetain( ServiceBrowserResults *inResults );
+static void            ServiceBrowserResultsRelease( ServiceBrowserResults *inResults );
+
+#define ForgetServiceBrowserResults( X )               ForgetCustom( X, ServiceBrowserResultsRelease )
+
+//===========================================================================================================================
+//     main
+//===========================================================================================================================
+
+int    main( int argc, const char **argv )
+{
+       OSStatus                err;
+       
+       // Route DebugServices logging output to stderr.
+       
+       dlog_control( "DebugServices:output=file;stderr" );
+       
+       PrintFRegisterExtension( "du:time",    PrintFTimestampHandler,   NULL );
+       PrintFRegisterExtension( "du:dnsmsg",  PrintFDNSMessageHandler,  NULL );
+       PrintFRegisterExtension( "du:arflags", PrintFAddRmvFlagsHandler, NULL );
+       CLIInit( argc, argv );
+       err = CLIParse( kGlobalOpts, kCLIFlags_None );
+       if( err ) exit( 1 );
+       
+       return( gExitCode );
+}
+
+//===========================================================================================================================
+//     VersionOptionCallback
+//===========================================================================================================================
+
+static OSStatus        VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
+{
+       const char *            srcVers;
+#if( MDNSRESPONDER_PROJECT )
+       char                            srcStr[ 16 ];
+#endif
+       
        Unused( inOption );
        Unused( inArg );
        Unused( inUnset );
@@ -2010,11 +2883,11 @@ static void DNSSD_API
        
        if( !context->printedHeader )
        {
-               FPrintF( stdout, "%-26s  A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" );
+               FPrintF( stdout, "%-26s  %-14s IF %-20s %-20s Instance Name\n", "Timestamp", "Flags", "Domain", "Service Type" );
                context->printedHeader = true;
        }
-       FPrintF( stdout, "%{du:time}  %-3s %5X %2d %-20s %-20s %s\n",
-               &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
+       FPrintF( stdout, "%{du:time}  %{du:arflags} %2d %-20s %-20s %s\n",
+               &now, inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
        
        if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
        
@@ -2117,7 +2990,8 @@ static void DNSSD_API
        require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr );
        
        FPrintF( stdout, "%{du:time}  %s %s TXT on interface %d\n    TXT: %#{txt}\n",
-               &now, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
+               &now, ( inFlags & kDNSServiceFlagsAdd ) ? "Add" : "Rmv", inFullName, (int32_t) inInterfaceIndex,
+               inRDataPtr, (size_t) inRDataLen );
        
 exit:
        if( err ) exit( 1 );
@@ -2383,16 +3257,16 @@ static void DNSSD_API
        }
        else
        {
-               addrStr = ( inSockAddr->sa_family == AF_INET ) ? "No Such Record (A)" : "No Such Record (AAAA)";
+               addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr;
        }
        
        if( !context->printedHeader )
        {
-               FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" );
+               FPrintF( stdout, "%-26s  %-14s IF %-32s %-38s %6s\n", "Timestamp", "Flags", "Hostname", "Address", "TTL" );
                context->printedHeader = true;
        }
-       FPrintF( stdout, "%{du:time}  %s %5X %2d %-32s %-38s %6u\n",
-               &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
+       FPrintF( stdout, "%{du:time}  %{du:arflags} %2d %-32s %-38s %6u\n",
+               &now, inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
        
        if( context->oneShotMode )
        {
@@ -2621,11 +3495,7 @@ static void DNSSD_API
                        goto exit;
        }
        
-       if( inError == kDNSServiceErr_NoSuchRecord )
-       {
-               ASPrintF( &rdataStr, "No Such Record" );
-       }
-       else
+       if( inError != kDNSServiceErr_NoSuchRecord )
        {
                if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
                if( !rdataStr )
@@ -2637,12 +3507,14 @@ static void DNSSD_API
        
        if( !context->printedHeader )
        {
-               FPrintF( stdout, "%-26s  A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" );
+               FPrintF( stdout, "%-26s  %-14s IF %-32s %-5s %-5s %6s RData\n",
+                       "Timestamp", "Flags", "Name", "Type", "Class", "TTL" );
                context->printedHeader = true;
        }
-       FPrintF( stdout, "%{du:time}  %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
-               &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
-               ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
+       FPrintF( stdout, "%{du:time}  %{du:arflags} %2d %-32s %-5s %?-5s%?5u %6u %s\n",
+               &now, inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
+               ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL,
+               rdataStr ? rdataStr : kNoSuchRecordStr );
        
        if( context->oneShotMode )
        {
@@ -2916,11 +3788,10 @@ static void DNSSD_API
        
        if( !context->printedHeader )
        {
-               FPrintF( stdout, "%-26s  A/R Flags Service\n", "Timestamp" );
+               FPrintF( stdout, "%-26s  %-14s Service\n", "Timestamp", "Flags" );
                context->printedHeader = true;
        }
-       FPrintF( stdout, "%{du:time}  %-3s %5X %s.%s%s %?#m\n",
-               &now, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
+       FPrintF( stdout, "%{du:time}  %{du:arflags} %s.%s%s %?#m\n", &now, inFlags, inName, inType, inDomain, inError, inError );
        
        require_noerr_action_quiet( inError, exit, err = inError );
        
@@ -3605,6 +4476,8 @@ exit:
 //     ReverseLookupCmd
 //===========================================================================================================================
 
+#define kIP6ARPADomainStr              "ip6.arpa."
+
 static void    ReverseLookupCmd( void )
 {
        OSStatus                                        err;
@@ -3613,7 +4486,7 @@ static void       ReverseLookupCmd( void )
        dispatch_source_t                       signalSource    = NULL;
        uint32_t                                        ipv4Addr;
        uint8_t                                         ipv6Addr[ 16 ];
-       char                                            recordName[ ( 16 * 4 ) + 9 + 1 ];
+       char                                            recordName[ ( 16 * 4 ) + sizeof( kIP6ARPADomainStr ) ];
        int                                                     useMainConnection;
        const char *                            endPtr;
        
@@ -3687,7 +4560,7 @@ static void       ReverseLookupCmd( void )
                        *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ];
                        *dst++ = '.';
                }
-               strcpy( dst, "ip6.arpa." );
+               strcpy_literal( dst, kIP6ARPADomainStr );
                check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) );
        }
        else
@@ -3939,221 +4812,56 @@ static void DNSSD_API
 //     BrowseAllCmd
 //===========================================================================================================================
 
-typedef struct BrowseDomain                    BrowseDomain;
-typedef struct BrowseType                      BrowseType;
-typedef struct BrowseOp                                BrowseOp;
-typedef struct BrowseInstance          BrowseInstance;
-typedef struct BrowseIPAddr                    BrowseIPAddr;
+typedef struct BrowseAllConnection             BrowseAllConnection;
 
 typedef struct
 {
-       int                                             refCount;
-       DNSServiceRef                   mainRef;
-       DNSServiceRef                   domainsQuery;
-       const char *                    domain;
-       BrowseDomain *                  domainList;
-       char **                                 serviceTypes;
-       size_t                                  serviceTypesCount;
-       dispatch_source_t               exitTimer;
-       uint32_t                                ifIndex;
-       int                                             pendingConnectCount;
-       int                                             browseTimeSecs;
-       int                                             maxConnectTimeSecs;
-       Boolean                                 includeAWDL;
-       Boolean                                 useColoredText;
+       ServiceBrowserRef                       browser;                                // Service browser.
+       ServiceBrowserResults *         results;                                // Results from the service browser.
+       BrowseAllConnection *           connectionList;                 // List of connections.
+       dispatch_source_t                       connectionTimer;                // Timer for connection timeout.
+       int                                                     connectionPendingCount; // Number of pending connections.
+       int                                                     connectionTimeoutSecs;  // Timeout value for connections in seconds.
        
 }      BrowseAllContext;
 
-struct BrowseDomain
-{
-       BrowseDomain *                  next;
-       char *                                  name;
-       DNSServiceRef                   servicesQuery;
-       BrowseAllContext *              context;
-       BrowseType *                    typeList;
-};
-
-struct BrowseType
-{
-       BrowseType *            next;
-       char *                          name;
-       BrowseOp *                      browseList;
-};
-
-struct BrowseOp
-{
-       BrowseOp *                              next;
-       BrowseAllContext *              context;
-       DNSServiceRef                   browse;
-       uint64_t                                startTicks;
-       BrowseInstance *                instanceList;
-       uint32_t                                ifIndex;
-       Boolean                                 isTCP;
-};
-
-struct BrowseInstance
-{
-       BrowseInstance *                next;
-       BrowseAllContext *              context;
-       char *                                  name;
-       uint64_t                                foundTicks;
-       DNSServiceRef                   resolve;
-       uint64_t                                resolveStartTicks;
-       uint64_t                                resolveDoneTicks;
-       DNSServiceRef                   getAddr;
-       uint64_t                                getAddrStartTicks;
-       BrowseIPAddr *                  addrList;
-       uint8_t *                               txtPtr;
-       size_t                                  txtLen;
-       char *                                  hostname;
-       uint32_t                                ifIndex;
-       uint16_t                                port;
-       Boolean                                 isTCP;
-};
-
-typedef enum
-{
-       kConnectStatus_None                     = 0,
-       kConnectStatus_Pending          = 1,
-       kConnectStatus_Succeeded        = 2,
-       kConnectStatus_Failed           = 3
-       
-}      ConnectStatus;
-
-struct BrowseIPAddr
+struct BrowseAllConnection
 {
-       BrowseIPAddr *                  next;
-       sockaddr_ip                             sip;
-       int                                             refCount;
-       BrowseAllContext *              context;
-       uint64_t                                foundTicks;
-       AsyncConnectionRef              connection;
-       ConnectStatus                   connectStatus;
-       CFTimeInterval                  connectTimeSecs;
-       OSStatus                                connectError;
+       BrowseAllConnection *           next;                           // Next connection object in list.
+       sockaddr_ip                                     sip;                            // IPv4 or IPv6 address to connect to.
+       uint16_t                                        port;                           // TCP port to connect to.
+       AsyncConnectionRef                      asyncCnx;                       // AsyncConnection object to handle the actual connection.
+       OSStatus                                        status;                         // Status of connection. NoErr means connection succeeded.
+       CFTimeInterval                          connectTimeSecs;        // Time it took to connect in seconds.
+       int32_t                                         refCount;                       // This object's reference count.
+       BrowseAllContext *                      context;                        // Back pointer to parent context.
 };
 
-static void    BrowseAllPrintPrologue( const BrowseAllContext *inContext );
-static void DNSSD_API
-       BrowseAllQueryDomainsCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inFullName,
-               uint16_t                                inType,
-               uint16_t                                inClass,
-               uint16_t                                inRDataLen,
-               const void *                    inRDataPtr,
-               uint32_t                                inTTL,
-               void *                                  inContext );
-static void DNSSD_API
-       BrowseAllQueryCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inFullName,
-               uint16_t                                inType,
-               uint16_t                                inClass,
-               uint16_t                                inRDataLen,
-               const void *                    inRDataPtr,
-               uint32_t                                inTTL,
-               void *                                  inContext );
-static void DNSSD_API
-       BrowseAllBrowseCallback(
-               DNSServiceRef           inSDRef,
-               DNSServiceFlags         inFlags,
-               uint32_t                        inInterfaceIndex,
-               DNSServiceErrorType     inError,
-               const char *            inName,
-               const char *            inRegType,
-               const char *            inDomain,
-               void *                          inContext );
-static void DNSSD_API
-       BrowseAllResolveCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inFullName,
-               const char *                    inHostname,
-               uint16_t                                inPort,
-               uint16_t                                inTXTLen,
-               const unsigned char *   inTXTPtr,
-               void *                                  inContext );
-static void DNSSD_API
-       BrowseAllGAICallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inHostname,
-               const struct sockaddr * inSockAddr,
-               uint32_t                                inTTL,
-               void *                                  inContext );
-static void            BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
-static void            BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
-static void            BrowseAllStop( void *inContext );
-static void            BrowseAllExit( void *inContext );
-static OSStatus        BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName );
-static OSStatus        BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName );
-static void            BrowseAllContextRelease( BrowseAllContext *inContext );
-static OSStatus
-       BrowseAllAddServiceType(
-               BrowseAllContext *      inContext,
-               BrowseDomain *          inDomain,
-               const char *            inName,
-               uint32_t                        inIfIndex,
-               Boolean                         inIncludeAWDL );
-static OSStatus
-       BrowseAllRemoveServiceType(
-               BrowseAllContext *      inContext,
-               BrowseDomain *          inDomain,
-               const char *            inName,
-               uint32_t                        inIfIndex );
-static OSStatus
-       BrowseAllAddServiceInstance(
-               BrowseAllContext *      inContext,
-               BrowseOp *                      inBrowse,
-               const char *            inName,
-               const char *            inRegType,
-               const char *            inDomain,
-               uint32_t                        inIfIndex );
-static OSStatus
-       BrowseAllRemoveServiceInstance(
-               BrowseAllContext *      inContext,
-               BrowseOp *                      inBrowse,
-               const char *            inName,
-               uint32_t                        inIfIndex );
-static OSStatus
-       BrowseAllAddIPAddress(
-               BrowseAllContext *              inContext,
-               BrowseInstance *                inInstance,
-               const struct sockaddr * inSockAddr );
+static void    _BrowseAllContextFree( BrowseAllContext *inContext );
+static void    _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext );
 static OSStatus
-       BrowseAllRemoveIPAddress(
+       _BrowseAllConnectionCreate(
+               const struct sockaddr * inSockAddr,
+               uint16_t                                inPort,
                BrowseAllContext *              inContext,
-               BrowseInstance *                inInstance,
-               const struct sockaddr * inSockAddr );
-static void    BrowseDomainFree( BrowseDomain *inDomain );
-static void    BrowseTypeFree( BrowseType *inType );
-static void    BrowseOpFree( BrowseOp *inBrowse );
-static void    BrowseInstanceFree( BrowseInstance *inInstance );
-static void    BrowseIPAddrRelease( BrowseIPAddr *inAddr );
-static void    BrowseIPAddrReleaseList( BrowseIPAddr *inList );
-
-#define ForgetIPAddressList( X )               ForgetCustom( X, BrowseIPAddrReleaseList )
-#define ForgetBrowseAllContext( X )            ForgetCustom( X, BrowseAllContextRelease )
+               BrowseAllConnection **  outConnection );
+static void _BrowseAllConnectionRetain( BrowseAllConnection *inConnection );
+static void    _BrowseAllConnectionRelease( BrowseAllConnection *inConnection );
+static void    _BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
+static void    _BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
+static void    _BrowseAllExit( void *inContext );
 
-#define kBrowseAllOpenFileMin          4096
+static Boolean _IsServiceTypeTCP( const char *inServiceType );
 
 static void    BrowseAllCmd( void )
 {
        OSStatus                                err;
        BrowseAllContext *              context = NULL;
+       size_t                                  i;
+       uint32_t                                ifIndex;
+       char                                    ifName[ kInterfaceNameBufLen ];
        
-       // Check command parameters.
+       // Check parameters.
        
        if( gBrowseAll_BrowseTimeSecs <= 0 )
        {
@@ -4162,9 +4870,14 @@ static void      BrowseAllCmd( void )
                goto exit;
        }
        
+       context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       context->connectionTimeoutSecs  = gBrowseAll_ConnectTimeout;
 #if( TARGET_OS_POSIX )
-       // Set open file minimum.
+       // Increase the open file descriptor limit for connection sockets.
        
+       if( context->connectionTimeoutSecs > 0 )
        {
                struct rlimit           fdLimits;
                
@@ -4172,9 +4885,9 @@ static void       BrowseAllCmd( void )
                err = map_global_noerr_errno( err );
                require_noerr( err, exit );
                
-               if( fdLimits.rlim_cur < kBrowseAllOpenFileMin )
+               if( fdLimits.rlim_cur < 4096 )
                {
-                       fdLimits.rlim_cur = kBrowseAllOpenFileMin;
+                       fdLimits.rlim_cur = 4096;
                        err = setrlimit( RLIMIT_NOFILE, &fdLimits );
                        err = map_global_noerr_errno( err );
                        require_noerr( err, exit );
@@ -4182,3071 +4895,2423 @@ static void BrowseAllCmd( void )
        }
 #endif
        
-       context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
-       
-       context->refCount                               = 1;
-       context->domain                                 = gBrowseAll_Domain;
-       context->serviceTypes                   = gBrowseAll_ServiceTypes;
-       context->serviceTypesCount              = gBrowseAll_ServiceTypesCount;
-       gBrowseAll_ServiceTypes                 = NULL;
-       gBrowseAll_ServiceTypesCount    = 0;
-       context->browseTimeSecs                 = gBrowseAll_BrowseTimeSecs;
-       context->maxConnectTimeSecs             = gBrowseAll_MaxConnectTimeSecs;
-       context->includeAWDL                    = gDNSSDFlag_IncludeAWDL ? true : false;
-#if( TARGET_OS_POSIX )
-       context->useColoredText                 = isatty( STDOUT_FILENO ) ? true : false;
-#endif
-       
-       err = DNSServiceCreateConnection( &context->mainRef );
-       require_noerr( err, exit );
-       
-       err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
-       require_noerr( err, exit );
-       
-       // Set interface index.
+       // Get interface index.
        
-       err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+       err = InterfaceIndexFromArgString( gInterface, &ifIndex );
        require_noerr_quiet( err, exit );
        
-       BrowseAllPrintPrologue( context );
+       // Print prologue.
        
-       if( context->domain )
+       FPrintF( stdout, "Interface:       %d (%s)\n",  (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+       FPrintF( stdout, "Service types:   ");
+       if( gBrowseAll_ServiceTypesCount > 0 )
        {
-               err = BrowseAllAddDomain( context, context->domain );
-               require_noerr( err, exit );
+               FPrintF( stdout, "%s", gBrowseAll_ServiceTypes[ 0 ] );
+               for( i = 1; i < gBrowseAll_ServiceTypesCount; ++i )
+               {
+                       FPrintF( stdout, ", %s", gBrowseAll_ServiceTypes[ i ] );
+               }
+               FPrintF( stdout, "\n" );
        }
        else
        {
-               DNSServiceRef           sdRef;
-               
-               sdRef = context->mainRef;
-               err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
-                       "b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, BrowseAllQueryDomainsCallback, context );
-               require_noerr( err, exit );
-               
-               context->domainsQuery = sdRef;
+               FPrintF( stdout, "all services\n" );
        }
+       FPrintF( stdout, "Domain:          %s\n", gBrowseAll_Domain ? gBrowseAll_Domain : "default domains" );
+       FPrintF( stdout, "Browse time:     %d second%?c\n", gBrowseAll_BrowseTimeSecs, gBrowseAll_BrowseTimeSecs != 1, 's' );
+       FPrintF( stdout, "Connect timeout: %d second%?c\n",
+               context->connectionTimeoutSecs, context->connectionTimeoutSecs != 1, 's' );
+       FPrintF( stdout, "IncludeAWDL:     %s\n", gDNSSDFlag_IncludeAWDL ? "yes" : "no" );
+       FPrintF( stdout, "Start time:      %{du:time}\n", NULL );
+       FPrintF( stdout, "---\n" );
+       
+       err = ServiceBrowserCreate( dispatch_get_main_queue(), ifIndex, gBrowseAll_Domain,
+               (unsigned int) gBrowseAll_BrowseTimeSecs, gDNSSDFlag_IncludeAWDL ? true : false, &context->browser );
+       require_noerr( err, exit );
        
-       dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop );
+       for( i = 0; i < gBrowseAll_ServiceTypesCount; ++i )
+       {
+               err = ServiceBrowserAddServiceType( context->browser, gBrowseAll_ServiceTypes[ i ] );
+               require_noerr( err, exit );
+       }
+       ServiceBrowserSetCallback( context->browser, _BrowseAllServiceBrowserCallback, context );
+       ServiceBrowserStart( context->browser );
        dispatch_main();
        
 exit:
-       if( context ) BrowseAllContextRelease( context );
+       if( context ) _BrowseAllContextFree( context );
        if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     BrowseAllPrintPrologue
+//     _BrowseAllContextFree
 //===========================================================================================================================
 
-static void    BrowseAllPrintPrologue( const BrowseAllContext *inContext )
+static void    _BrowseAllContextFree( BrowseAllContext *inContext )
 {
-       size_t          i;
-       char            ifName[ kInterfaceNameBufLen ];
-       
-       InterfaceIndexToName( inContext->ifIndex, ifName );
-       
-       FPrintF( stdout, "Interface:        %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
-       FPrintF( stdout, "Service types:    ");
-       if( inContext->serviceTypesCount > 0 )
-       {
-               FPrintF( stdout, "%s", inContext->serviceTypes[ 0 ] );
-               for( i = 1; i < inContext->serviceTypesCount; ++i ) FPrintF( stdout, ", %s", inContext->serviceTypes[ i ] );
-               FPrintF( stdout, "\n" );
-       }
-       else
-       {
-               FPrintF( stdout, "all services\n" );
-       }
-       FPrintF( stdout, "Domain:           %s\n", inContext->domain ? inContext->domain : "default domains" );
-       FPrintF( stdout, "Browse time:      %d second%?c\n", inContext->browseTimeSecs, inContext->browseTimeSecs != 1, 's' );
-       FPrintF( stdout, "Max connect time: %d second%?c\n",
-               inContext->maxConnectTimeSecs, inContext->maxConnectTimeSecs != 1, 's' );
-       FPrintF( stdout, "IncludeAWDL:      %s\n", inContext->includeAWDL ? "YES" : "NO" );
-       FPrintF( stdout, "Start time:       %{du:time}\n", NULL );
-       FPrintF( stdout, "---\n" );
+       check( !inContext->browser );
+       check( !inContext->connectionTimer );
+       check( !inContext->connectionList );
+       ForgetServiceBrowserResults( &inContext->results );
+       free( inContext );
 }
 
 //===========================================================================================================================
-//     BrowseAllQueryDomainsCallback
+//     _BrowseAllServiceBrowserCallback
 //===========================================================================================================================
 
-static void DNSSD_API
-       BrowseAllQueryDomainsCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inFullName,
-               uint16_t                                inType,
-               uint16_t                                inClass,
-               uint16_t                                inRDataLen,
-               const void *                    inRDataPtr,
-               uint32_t                                inTTL,
-               void *                                  inContext )
+#define kDiscardProtocolPort           9
+
+static void    _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext )
 {
        OSStatus                                                err;
        BrowseAllContext * const                context = (BrowseAllContext *) inContext;
-       char                                                    domainStr[ kDNSServiceMaxDomainName ];
+       SBRDomain *                                             domain;
+       SBRServiceType *                                type;
+       SBRServiceInstance *                    instance;
+       SBRIPAddress *                                  ipaddr;
        
-       Unused( inSDRef );
-       Unused( inInterfaceIndex );
-       Unused( inFullName );
-       Unused( inType );
-       Unused( inClass );
-       Unused( inTTL );
+       Unused( inError );
        
-       err = inError;
-       require_noerr( err, exit );
+       require_action( inResults, exit, err = kUnexpectedErr );
        
-       err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
-       require_noerr( err, exit );
+       check( !context->results );
+       context->results = inResults;
+       ServiceBrowserResultsRetain( context->results );
        
-       if( inFlags & kDNSServiceFlagsAdd )
+       check( context->connectionPendingCount == 0 );
+       if( context->connectionTimeoutSecs > 0 )
        {
-               err = BrowseAllAddDomain( context, domainStr );
-               if( err == kDuplicateErr ) err = kNoErr;
+               BrowseAllConnection *                   connection;
+               BrowseAllConnection **                  connectionPtr = &context->connectionList;
+               char                                                    destination[ kSockAddrStringMaxSize ];
+               
+               for( domain = context->results->domainList; domain; domain = domain->next )
+               {
+                       for( type = domain->typeList; type; type = type->next )
+                       {
+                               if( !_IsServiceTypeTCP( type->name ) ) continue;
+                               for( instance = type->instanceList; instance; instance = instance->next )
+                               {
+                                       if( instance->port == kDiscardProtocolPort ) continue;
+                                       for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+                                       {
+                                               err = _BrowseAllConnectionCreate( &ipaddr->sip.sa, instance->port, context, &connection );
+                                               require_noerr( err, exit );
+                                               
+                                               *connectionPtr = connection;
+                                                connectionPtr = &connection->next;
+                                               
+                                               err = SockAddrToString( &ipaddr->sip, kSockAddrStringFlagsNoPort, destination );
+                                               check_noerr( err );
+                                               if( !err )
+                                               {
+                                                       err = AsyncConnection_Connect( &connection->asyncCnx, destination, -instance->port,
+                                                               kAsyncConnectionFlag_P2P, kAsyncConnectionNoTimeout,
+                                                               kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
+                                                               _BrowseAllConnectionProgress, connection, _BrowseAllConnectionHandler, connection,
+                                                               dispatch_get_main_queue() );
+                                                       check_noerr( err );
+                                               }
+                                               if( !err )
+                                               {
+                                                       _BrowseAllConnectionRetain( connection );
+                                                       connection->status = kInProgressErr;
+                                                       ++context->connectionPendingCount;
+                                               }
+                                               else
+                                               {
+                                                       connection->status = err;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       if( context->connectionPendingCount > 0 )
+       {
+               check( !context->connectionTimer );
+               err = DispatchTimerCreate( dispatch_time_seconds( context->connectionTimeoutSecs ), DISPATCH_TIME_FOREVER,
+                       100 * kNanosecondsPerMillisecond, NULL, _BrowseAllExit, NULL, context, &context->connectionTimer );
                require_noerr( err, exit );
+               dispatch_resume( context->connectionTimer );
        }
        else
        {
-               err = BrowseAllRemoveDomain( context, domainStr );
-               if( err == kNotFoundErr ) err = kNoErr;
-               require_noerr( err, exit );
+               dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit );
        }
+       err = kNoErr;
        
 exit:
+       ForgetCF( &context->browser );
        if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     BrowseAllQueryCallback
+//     _BrowseAllConnectionCreate
 //===========================================================================================================================
 
-static void DNSSD_API
-       BrowseAllQueryCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inFullName,
-               uint16_t                                inType,
-               uint16_t                                inClass,
-               uint16_t                                inRDataLen,
-               const void *                    inRDataPtr,
-               uint32_t                                inTTL,
-               void *                                  inContext )
+static OSStatus
+       _BrowseAllConnectionCreate(
+               const struct sockaddr * inSockAddr,
+               uint16_t                                inPort,
+               BrowseAllContext *              inContext,
+               BrowseAllConnection **  outConnection )
 {
        OSStatus                                        err;
-       BrowseDomain * const            domain                  = (BrowseDomain *) inContext;
-       const uint8_t *                         firstLabel;
-       const uint8_t *                         secondLabel;
-       char *                                          serviceTypeStr  = NULL;
-       const uint8_t * const           end                             = ( (uint8_t * ) inRDataPtr ) + inRDataLen;
-       
-       Unused( inSDRef );
-       Unused( inFullName );
-       Unused( inTTL );
-       Unused( inType );
-       Unused( inClass );
-       
-       err = inError;
-       require_noerr( err, exit );
-       
-       check( inType   == kDNSServiceType_PTR );
-       check( inClass  == kDNSServiceClass_IN );
-       require_action( inRDataLen > 0, exit, err = kSizeErr );
-       
-       firstLabel = inRDataPtr;
-       require_action_quiet( ( firstLabel + 1 + firstLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
-       
-       secondLabel = firstLabel + 1 + firstLabel[ 0 ];
-       require_action_quiet( ( secondLabel + 1 + secondLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
-       
-       ASPrintF( &serviceTypeStr, "%#s.%#s", firstLabel, secondLabel );
-       require_action( serviceTypeStr, exit, err = kNoMemoryErr );
-       
-       if( inFlags & kDNSServiceFlagsAdd )
-       {
-               err = BrowseAllAddServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex, false );
-               if( err == kDuplicateErr ) err = kNoErr;
-               require_noerr( err, exit );
-       }
-       else
-       {
-               err = BrowseAllRemoveServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex );
-               if( err == kNotFoundErr ) err = kNoErr;
-               require_noerr( err, exit );
-       }
-       
-exit:
-       FreeNullSafe( serviceTypeStr );
-}
-
-//===========================================================================================================================
-//     BrowseAllBrowseCallback
-//===========================================================================================================================
-
-static void DNSSD_API
-       BrowseAllBrowseCallback(
-               DNSServiceRef           inSDRef,
-               DNSServiceFlags         inFlags,
-               uint32_t                        inInterfaceIndex,
-               DNSServiceErrorType     inError,
-               const char *            inName,
-               const char *            inRegType,
-               const char *            inDomain,
-               void *                          inContext )
-{
-       OSStatus                                err;
-       BrowseOp * const                browse = (BrowseOp *) inContext;
+       BrowseAllConnection *           obj;
        
-       Unused( inSDRef );
+       obj = (BrowseAllConnection *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       err = inError;
-       require_noerr( err, exit );
+       obj->refCount   = 1;
+       SockAddrCopy( inSockAddr, &obj->sip );
+       obj->port               = inPort;
+       obj->context    = inContext;
        
-       if( inFlags & kDNSServiceFlagsAdd )
-       {
-               err = BrowseAllAddServiceInstance( browse->context, browse, inName, inRegType, inDomain, inInterfaceIndex );
-               if( err == kDuplicateErr ) err = kNoErr;
-               require_noerr( err, exit );
-       }
-       else
-       {
-               err = BrowseAllRemoveServiceInstance( browse->context, browse, inName, inInterfaceIndex );
-               if( err == kNotFoundErr ) err = kNoErr;
-               require_noerr( err, exit );
-       }
+       *outConnection = obj;
+       err = kNoErr;
        
 exit:
-       return;
+       return( err );
 }
 
 //===========================================================================================================================
-//     BrowseAllResolveCallback
+//     _BrowseAllConnectionRetain
 //===========================================================================================================================
 
-static void DNSSD_API
-       BrowseAllResolveCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inFullName,
-               const char *                    inHostname,
-               uint16_t                                inPort,
-               uint16_t                                inTXTLen,
-               const unsigned char *   inTXTPtr,
-               void *                                  inContext )
+static void _BrowseAllConnectionRetain( BrowseAllConnection *inConnection )
 {
-       OSStatus                                        err;
-       const uint64_t                          nowTicks        = UpTicks();
-       BrowseInstance * const          instance        = (BrowseInstance *) inContext;
-       
-       Unused( inSDRef );
-       Unused( inFlags );
-       Unused( inInterfaceIndex );
-       Unused( inFullName );
-       
-       err = inError;
-       require_noerr( err, exit );
-       
-       if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
-       {
-               FreeNullSafe( instance->txtPtr );
-               instance->txtPtr = malloc( inTXTLen );
-               require_action( instance->txtPtr, exit, err = kNoMemoryErr );
-               
-               memcpy( instance->txtPtr, inTXTPtr, inTXTLen );
-               instance->txtLen = inTXTLen;
-       }
-       
-       instance->port = ntohs( inPort );
-       
-       if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
-       {
-               DNSServiceRef           sdRef;
-               
-               if( !instance->hostname ) instance->resolveDoneTicks = nowTicks;
-               FreeNullSafe( instance->hostname );
-               instance->hostname = strdup( inHostname );
-               require_action( instance->hostname, exit, err = kNoMemoryErr );
-               
-               DNSServiceForget( &instance->getAddr );
-               ForgetIPAddressList( &instance->addrList );
-               
-               sdRef = instance->context->mainRef;
-               instance->getAddrStartTicks = UpTicks();
-               err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
-                       kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, BrowseAllGAICallback, instance );
-               require_noerr( err, exit );
-               
-               instance->getAddr = sdRef;
-       }
-       
-exit:
-       if( err ) exit( 1 );
+       ++inConnection->refCount;
 }
 
 //===========================================================================================================================
-//     BrowseAllGAICallback
+//     _BrowseAllConnectionRelease
 //===========================================================================================================================
 
-static void DNSSD_API
-       BrowseAllGAICallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inHostname,
-               const struct sockaddr * inSockAddr,
-               uint32_t                                inTTL,
-               void *                                  inContext )
+static void    _BrowseAllConnectionRelease( BrowseAllConnection *inConnection )
 {
-       OSStatus                                        err;
-       BrowseInstance * const          instance = (BrowseInstance *) inContext;
-       
-       Unused( inSDRef );
-       Unused( inInterfaceIndex );
-       Unused( inHostname );
-       Unused( inTTL );
-       
-       err = inError;
-       require_noerr( err, exit );
-       
-       if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
-       {
-               dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
-               goto exit;
-       }
-       
-       if( inFlags & kDNSServiceFlagsAdd )
-       {
-               err = BrowseAllAddIPAddress( instance->context, instance, inSockAddr );
-               if( err == kDuplicateErr ) err = kNoErr;
-               require_noerr( err, exit );
-       }
-       else
-       {
-               err = BrowseAllRemoveIPAddress( instance->context, instance, inSockAddr );
-               if( err == kNotFoundErr ) err = kNoErr;
-               require_noerr( err, exit );
-       }
-       
-exit:
-       return;
+       if( --inConnection->refCount == 0 ) free( inConnection );
 }
 
 //===========================================================================================================================
-//     BrowseAllConnectionProgress
+//     _BrowseAllConnectionProgress
 //===========================================================================================================================
 
-static void    BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
+static void    _BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
 {
-       BrowseIPAddr * const            addr = (BrowseIPAddr *) inArg;
+       BrowseAllConnection * const             connection = (BrowseAllConnection *) inArg;
        
        if( inPhase == kAsyncConnectionPhase_Connected )
        {
                const AsyncConnectedInfo * const                info = (AsyncConnectedInfo *) inDetails;
                
-               addr->connectTimeSecs = info->connectSecs;
+               connection->connectTimeSecs = info->connectSecs;
        }
 }
 
 //===========================================================================================================================
-//     BrowseAllConnectionHandler
+//     _BrowseAllConnectionHandler
 //===========================================================================================================================
 
-static void    BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
+static void    _BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
 {
-       BrowseIPAddr * const                    addr    = (BrowseIPAddr *) inArg;
-       BrowseAllContext * const                context = addr->context;
-       
-       if( inError )
-       {
-               addr->connectStatus     = kConnectStatus_Failed;
-               addr->connectError      = inError;
-       }
-       else
-       {
-               addr->connectStatus = kConnectStatus_Succeeded;
-       }
-       
-       check( context->pendingConnectCount > 0 );
-       if( --context->pendingConnectCount == 0 )
-       {
-               if( context->exitTimer )
-               {
-                       dispatch_source_forget( &context->exitTimer );
-                       dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
-               }
-       }
+       BrowseAllConnection * const             connection      = (BrowseAllConnection *) inArg;
+       BrowseAllContext * const                context         = connection->context;
        
+       connection->status = inError;
        ForgetSocket( &inSock );
-       BrowseIPAddrRelease( addr );
-}
-
-//===========================================================================================================================
-//     BrowseAllStop
-//===========================================================================================================================
-
-static void    BrowseAllStop( void *inContext )
-{
-       OSStatus                                                err;
-       BrowseAllContext * const                context = (BrowseAllContext *) inContext;
-       BrowseDomain *                                  domain;
-       BrowseType *                                    type;
-       BrowseOp *                                              browse;
-       BrowseInstance *                                instance;
-       
-       DNSServiceForget( &context->domainsQuery );
-       for( domain = context->domainList; domain; domain = domain->next )
+       if( context )
        {
-               DNSServiceForget( &domain->servicesQuery );
-               for( type = domain->typeList; type; type = type->next )
+               check( context->connectionPendingCount > 0 );
+               if( ( --context->connectionPendingCount == 0 ) && context->connectionTimer )
                {
-                       for( browse = type->browseList; browse; browse = browse->next )
-                       {
-                               DNSServiceForget( &browse->browse );
-                               for( instance = browse->instanceList; instance; instance = instance->next )
-                               {
-                                       DNSServiceForget( &instance->resolve );
-                                       DNSServiceForget( &instance->getAddr );
-                               }
-                       }
+                       dispatch_source_forget( &context->connectionTimer );
+                       dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit );
                }
        }
-       DNSServiceForget( &context->mainRef );
-       
-       if( ( context->pendingConnectCount > 0 ) && ( context->maxConnectTimeSecs > 0 ) )
-       {
-               check( !context->exitTimer );
-               err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER,
-                       100 * kNanosecondsPerMillisecond, NULL, BrowseAllExit, NULL, context, &context->exitTimer );
-               require_noerr( err, exit );
-               dispatch_resume( context->exitTimer );
-       }
-       else
-       {
-               dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
-       }
-       err = kNoErr;
-       
-exit:
-       if( err ) exit( 1 );
+       _BrowseAllConnectionRelease( connection );
 }
 
 //===========================================================================================================================
-//     BrowseAllExit
+//     _BrowseAllExit
 //===========================================================================================================================
 
-#define kStatusStr_CouldConnect                                        "connected"
-#define kStatusStr_CouldConnectColored                 kANSIGreen kStatusStr_CouldConnect kANSINormal
-#define kStatusStr_CouldNotConnect                             "could not connect"
-#define kStatusStr_CouldNotConnectColored              kANSIRed kStatusStr_CouldNotConnect kANSINormal
-#define kStatusStr_NoConnectionAttempted               "no connection attempted"
-#define kStatusStr_Unknown                                             "unknown"
-
 #define Indent( X )            ( (X) * 4 ), ""
 
-static void    BrowseAllExit( void *inContext )
+static void    _BrowseAllExit( void *inContext )
 {
-       BrowseAllContext * const                context = (BrowseAllContext *) inContext;
-       BrowseDomain *                                  domain;
-       BrowseType *                                    type;
-       BrowseOp *                                              browse;
-       BrowseInstance *                                instance;
-       BrowseIPAddr *                                  addr;
+       BrowseAllContext * const                context         = (BrowseAllContext *) inContext;
+       SBRDomain *                                             domain;
+       SBRServiceType *                                type;
+       SBRServiceInstance *                    instance;
+       SBRIPAddress *                                  ipaddr;
+       char                                                    textBuf[ 512 ];
+#if( TARGET_OS_POSIX )
+       const Boolean                                   useColor        = isatty( STDOUT_FILENO ) ? true : false;
+#endif
        
-       dispatch_source_forget( &context->exitTimer );
+       dispatch_source_forget( &context->connectionTimer );
        
-       for( domain = context->domainList; domain; domain = domain->next )
+       for( domain = context->results->domainList; domain; domain = domain->next )
        {
                FPrintF( stdout, "%s\n\n", domain->name );
                
                for( type = domain->typeList; type; type = type->next )
                {
-                       const char *            desc;
+                       const char *            description;
+                       const Boolean           serviceTypeIsTCP = _IsServiceTypeTCP( type->name );
                        
-                       desc = ServiceTypeDescription( type->name );
-                       if( desc )      FPrintF( stdout, "%*s" "%s (%s)\n\n",   Indent( 1 ), desc, type->name );
-                       else            FPrintF( stdout, "%*s" "%s\n\n",                Indent( 1 ), type->name );
+                       description = ServiceTypeDescription( type->name );
+                       if( description )       FPrintF( stdout, "%*s" "%s (%s)\n\n",   Indent( 1 ), description, type->name );
+                       else                            FPrintF( stdout, "%*s" "%s\n\n",                Indent( 1 ), type->name );
                        
-                       for( browse = type->browseList; browse; browse = browse->next )
+                       for( instance = type->instanceList; instance; instance = instance->next )
                        {
-                               for( instance = browse->instanceList; instance; instance = instance->next )
+                               char *                          dst = textBuf;
+                               char * const            lim = &textBuf[ countof( textBuf ) ];
+                               char                            ifname[ IF_NAMESIZE + 1 ];
+                               
+                               SNPrintF_Add( &dst, lim, "%s via ", instance->name );
+                               if( instance->ifIndex == 0 )
                                {
-                                       char            ifname[ IF_NAMESIZE + 1 ];
+                                       SNPrintF_Add( &dst, lim, "the Internet" );
+                               }
+                               else if( if_indextoname( instance->ifIndex, ifname ) )
+                               {
+                                       NetTransportType                netType;
                                        
-                                       FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
-                                       if( instance->ifIndex == 0 )
-                                       {
-                                               FPrintF( stdout, "the Internet" );
-                                       }
-                                       else if( if_indextoname( instance->ifIndex, ifname ) )
-                                       {
-                                               NetTransportType                netType;
-                                               
-                                               SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                                                       &netType );
-                                               FPrintF( stdout, "%s (%s)",
-                                                       ( netType == kNetTransportType_Ethernet ) ? "ethernet" : NetTransportTypeToString( netType ),
-                                                       ifname );
-                                       }
-                                       else
-                                       {
-                                               FPrintF( stdout, "interface index %u", instance->ifIndex );
-                                       }
-                                       FPrintF( stdout, "\n\n" );
+                                       SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &netType );
+                                       SNPrintF_Add( &dst, lim, "%s (%s)",
+                                               ( netType == kNetTransportType_Ethernet ) ? "Ethernet" : NetTransportTypeToString( netType ),
+                                               ifname );
+                               }
+                               else
+                               {
+                                       SNPrintF_Add( &dst, lim, "interface index %u", instance->ifIndex );
+                               }
+                               FPrintF( stdout, "%*s" "%-55s %4llu.%03llu ms\n\n",
+                                       Indent( 2 ), textBuf, instance->discoverTimeUs / 1000, instance->discoverTimeUs % 1000 );
+                               
+                               if( instance->hostname )
+                               {
+                                       SNPrintF( textBuf, sizeof( textBuf ), "%s:%u", instance->hostname, instance->port );
+                                       FPrintF( stdout, "%*s" "%-51s %4llu.%03llu ms\n",
+                                               Indent( 3 ), textBuf, instance->resolveTimeUs / 1000, instance->resolveTimeUs % 1000 );
+                               }
+                               else
+                               {
+                                       FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
+                               }
+                               
+                               for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+                               {
+                                       BrowseAllConnection *           conn;
+                                       BrowseAllConnection **          connPtr;
                                        
-                                       if( instance->hostname )
-                                       {
-                                               char            buffer[ 256 ];
-                                               
-                                               SNPrintF( buffer, sizeof( buffer ), "%s:%u", instance->hostname, instance->port );
-                                               FPrintF( stdout, "%*s" "%-51s %4llu ms\n", Indent( 3 ), buffer,
-                                                       UpTicksToMilliseconds( instance->resolveDoneTicks - instance->resolveStartTicks ) );
-                                       }
-                                       else
-                                       {
-                                               FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
-                                       }
+                                       FPrintF( stdout, "%*s" "%-##47a %4llu.%03llu ms",
+                                               Indent( 4 ), &ipaddr->sip.sa, ipaddr->resolveTimeUs / 1000, ipaddr->resolveTimeUs % 1000 );
                                        
-                                       for( addr = instance->addrList; addr; addr = addr->next )
+                                       conn = NULL;
+                                       if( serviceTypeIsTCP && ( instance->port != kDiscardProtocolPort ) )
                                        {
-                                               AsyncConnection_Forget( &addr->connection );
-                                               
-                                               if( addr->connectStatus == kConnectStatus_Pending )
-                                               {
-                                                       addr->connectStatus     = kConnectStatus_Failed;
-                                                       addr->connectError      = kTimeoutErr;
-                                               }
-                                               
-                                               FPrintF( stdout, "%*s" "%-##47a %4llu ms", Indent( 4 ),
-                                                       &addr->sip.sa, UpTicksToMilliseconds( addr->foundTicks - instance->getAddrStartTicks ) );
-                                               if( context->maxConnectTimeSecs <= 0 )
+                                               for( connPtr = &context->connectionList; ( conn = *connPtr ) != NULL; connPtr = &conn->next )
                                                {
-                                                       FPrintF( stdout, "\n" );
-                                                       continue;
+                                                       if( ( conn->port == instance->port ) &&
+                                                               ( SockAddrCompareAddr( &conn->sip, &ipaddr->sip ) == 0 ) ) break;
                                                }
-                                               switch( addr->connectStatus )
+                                               if( conn )
                                                {
-                                                       case kConnectStatus_None:
-                                                               FPrintF( stdout, " (%s)\n", kStatusStr_NoConnectionAttempted );
-                                                               break;
-                                                       
-                                                       case kConnectStatus_Succeeded:
-                                                               FPrintF( stdout, " (%s in %.2f ms)\n",
-                                                                       context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect,
-                                                                       addr->connectTimeSecs * 1000 );
-                                                               break;
-                                                       
-                                                       case kConnectStatus_Failed:
-                                                               FPrintF( stdout, " (%s: %m)\n",
-                                                                       context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect,
-                                                                       addr->connectError );
-                                                               break;
-                                                       
-                                                       default:
-                                                               FPrintF( stdout, " (%s)\n", kStatusStr_Unknown );
-                                                               break;
+                                                       if( conn->status == kInProgressErr ) conn->status = kTimeoutErr;
+                                                       *connPtr = conn->next;
+                                                       conn->context = NULL;
+                                                       AsyncConnection_Forget( &conn->asyncCnx );
                                                }
                                        }
                                        
-                                       FPrintF( stdout, "\n" );
-                                       if( instance->txtLen == 0 ) continue;
-                                       
-                                       FPrintF( stdout, "%*s" "TXT record:\n", Indent( 3 ) );
-                                       if( instance->txtLen > 1 )
+                                       if( conn )
                                        {
-                                               FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
+                                               if( conn->status == kNoErr )
+                                               {
+                                                       FPrintF( stdout, " (%sconnected%s in %.3f ms)\n",
+                                                               useColor ? kANSIGreen : "", useColor ? kANSINormal : "", conn->connectTimeSecs * 1000 );
+                                               }
+                                               else
+                                               {
+                                                       FPrintF( stdout, " (%scould not connect%s: %m)\n",
+                                                               useColor ? kANSIRed : "", useColor ? kANSINormal : "", conn->status );
+                                               }
+                                               _BrowseAllConnectionRelease( conn );
                                        }
                                        else
                                        {
-                                               FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
+                                               FPrintF( stdout, " (no connection attempted)\n" );
                                        }
-                                       FPrintF( stdout, "\n" );
                                }
+                               
+                               FPrintF( stdout, "\n" );
+                               if( instance->txtLen == 0 ) continue;
+                               
+                               FPrintF( stdout, "%*s" "TXT record (%zu byte%?c):\n",
+                                       Indent( 3 ), instance->txtLen, instance->txtLen != 1, 's' );
+                               if( instance->txtLen > 1 )
+                               {
+                                       FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
+                               }
+                               else
+                               {
+                                       FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
+                               }
+                               FPrintF( stdout, "\n" );
                        }
                        FPrintF( stdout, "\n" );
                }
        }
        
-       while( ( domain = context->domainList ) != NULL )
-       {
-               context->domainList = domain->next;
-               BrowseDomainFree( domain );
-       }
-       
-       BrowseAllContextRelease( context );
+       _BrowseAllContextFree( context );
        Exit( NULL );
 }
 
 //===========================================================================================================================
-//     BrowseAllAddDomain
+//     _IsServiceTypeTCP
 //===========================================================================================================================
 
-static OSStatus        BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName )
+static Boolean _IsServiceTypeTCP( const char *inServiceType )
 {
        OSStatus                        err;
-       BrowseDomain *          domain;
-       BrowseDomain **         p;
-       BrowseDomain *          newDomain = NULL;
+       const uint8_t *         secondLabel;
+       uint8_t                         name[ kDomainNameLengthMax ];
        
-       for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+       err = DomainNameFromString( name, inServiceType, NULL );
+       if( !err )
        {
-               if( strcasecmp( domain->name, inName ) == 0 ) break;
+               secondLabel = NextLabel( name );
+               if( secondLabel && DomainNameEqual( secondLabel, (const uint8_t *) "\x04" "_tcp" ) ) return( true );
        }
-       require_action_quiet( !domain, exit, err = kDuplicateErr );
-       
-       newDomain = (BrowseDomain *) calloc( 1, sizeof( *newDomain ) );
-       require_action( newDomain, exit, err = kNoMemoryErr );
-       
-       ++inContext->refCount;
-       newDomain->context = inContext;
-       
-       newDomain->name = strdup( inName );
-       require_action( newDomain->name, exit, err = kNoMemoryErr );
-       
-       if( inContext->serviceTypesCount > 0 )
-       {
-               size_t          i;
-               
-               for( i = 0; i < inContext->serviceTypesCount; ++i )
-               {
-                       err = BrowseAllAddServiceType( inContext, newDomain, inContext->serviceTypes[ i ], inContext->ifIndex,
-                               inContext->includeAWDL );
-                       if( err == kDuplicateErr ) err = kNoErr;
-                       require_noerr( err, exit );
-               }
-       }
-       else
-       {
-               char *                          recordName;
-               DNSServiceFlags         flags;
-               DNSServiceRef           sdRef;
-               
-               ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
-               require_action( recordName, exit, err = kNoMemoryErr );
-               
-               flags = kDNSServiceFlagsShareConnection;
-               if( inContext->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
-               
-               sdRef = newDomain->context->mainRef;
-               err = DNSServiceQueryRecord( &sdRef, flags, inContext->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
-                       BrowseAllQueryCallback, newDomain );
-               free( recordName );
-               require_noerr( err, exit );
-               
-               newDomain->servicesQuery = sdRef;
-       }
-       
-       *p = newDomain;
-       newDomain = NULL;
-       err = kNoErr;
-       
-exit:
-       if( newDomain ) BrowseDomainFree( newDomain );
-       return( err );
+       return( false );
 }
 
 //===========================================================================================================================
-//     BrowseAllRemoveDomain
+//     GetNameInfoCmd
 //===========================================================================================================================
 
-static OSStatus        BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName )
+const FlagStringPair           kGetNameInfoFlagStringPairs[] =
 {
-       OSStatus                        err;
-       BrowseDomain *          domain;
-       BrowseDomain **         p;
+       CaseFlagStringify( NI_NUMERICSCOPE ),
+       CaseFlagStringify( NI_DGRAM ),
+       CaseFlagStringify( NI_NUMERICSERV ),
+       CaseFlagStringify( NI_NAMEREQD ),
+       CaseFlagStringify( NI_NUMERICHOST ),
+       CaseFlagStringify( NI_NOFQDN ),
+       { 0, NULL }
+};
+
+static void    GetNameInfoCmd( void )
+{
+       OSStatus                                        err;
+       sockaddr_ip                                     sip;
+       size_t                                          sockAddrLen;
+       unsigned int                            flags;
+       const FlagStringPair *          pair;
+       struct timeval                          now;
+       char                                            host[ NI_MAXHOST ];
+       char                                            serv[ NI_MAXSERV ];
        
-       for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+       err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
+       check_noerr( err );
+       if( err )
        {
-               if( strcasecmp( domain->name, inName ) == 0 ) break;
+               FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+               goto exit;
        }
        
-       if( domain )
+       flags = 0;
+       if( gGetNameInfoFlag_DGram )            flags |= NI_DGRAM;
+       if( gGetNameInfoFlag_NameReqd )         flags |= NI_NAMEREQD;
+       if( gGetNameInfoFlag_NoFQDN )           flags |= NI_NOFQDN;
+       if( gGetNameInfoFlag_NumericHost )      flags |= NI_NUMERICHOST;
+       if( gGetNameInfoFlag_NumericScope )     flags |= NI_NUMERICSCOPE;
+       if( gGetNameInfoFlag_NumericServ )      flags |= NI_NUMERICSERV;
+       
+       // Print prologue.
+       
+       FPrintF( stdout, "SockAddr:   %##a\n",  &sip.sa );
+       FPrintF( stdout, "Flags:      0x%X < ", flags );
+       for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair )
        {
-               *p = domain->next;
-               BrowseDomainFree( domain );
-               err = kNoErr;
+               if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str );
+       }
+       FPrintF( stdout, ">\n" );
+       FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+       FPrintF( stdout, "---\n" );
+       
+       // Call getnameinfo().
+       
+       err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ),
+               (int) flags );
+       gettimeofday( &now, NULL );
+       if( err )
+       {
+               FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
        }
        else
        {
-               err = kNotFoundErr;
+               FPrintF( stdout, "host: %s\n", host );
+               FPrintF( stdout, "serv: %s\n", serv );
        }
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:   %{du:time}\n", &now );
        
-       return( err );
+exit:
+       gExitCode = err ? 1 : 0;
 }
 
 //===========================================================================================================================
-//     BrowseAllContextRelease
+//     GetAddrInfoStressCmd
 //===========================================================================================================================
 
-static void    BrowseAllContextRelease( BrowseAllContext *inContext )
+typedef struct
 {
-       if( --inContext->refCount == 0 )
-       {
-               check( !inContext->domainsQuery );
-               check( !inContext->domainList );
-               check( !inContext->exitTimer );
-               check( !inContext->pendingConnectCount );
-               DNSServiceForget( &inContext->mainRef );
-               if( inContext->serviceTypes )
-               {
-                       StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount );
-                       inContext->serviceTypes                 = NULL;
-                       inContext->serviceTypesCount    = 0;
-               }
-               free( inContext );
-       }
-}
+       DNSServiceRef                   mainRef;
+       DNSServiceRef                   sdRef;
+       DNSServiceFlags                 flags;
+       unsigned int                    interfaceIndex;
+       unsigned int                    connectionNumber;
+       unsigned int                    requestCount;
+       unsigned int                    requestCountMax;
+       unsigned int                    requestCountLimit;
+       unsigned int                    durationMinMs;
+       unsigned int                    durationMaxMs;
+       
+}      GAIStressContext;
 
-//===========================================================================================================================
-//     BrowseAllAddServiceType
-//===========================================================================================================================
+static void    GetAddrInfoStressEvent( void *inContext );
+static void    DNSSD_API
+       GetAddrInfoStressCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
 
-static OSStatus
-       BrowseAllAddServiceType(
-               BrowseAllContext *      inContext,
-               BrowseDomain *          inDomain,
-               const char *            inName,
-               uint32_t                        inIfIndex,
-               Boolean                         inIncludeAWDL )
+static void    GetAddrInfoStressCmd( void )
 {
-       OSStatus                        err;
-       DNSServiceRef           sdRef;
-       DNSServiceFlags         flags;
-       BrowseType *            type;
-       BrowseType **           typePtr;
-       BrowseType *            newType         = NULL;
-       BrowseOp *                      browse;
-       BrowseOp **                     browsePtr;
-       BrowseOp *                      newBrowse       = NULL;
+       OSStatus                                err;
+       GAIStressContext *              context = NULL;
+       int                                             i;
+       DNSServiceFlags                 flags;
+       uint32_t                                ifIndex;
+       char                                    ifName[ kInterfaceNameBufLen ];
        
-       for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+       if( gGAIStress_TestDurationSecs < 0 )
        {
-               if( strcasecmp( type->name, inName ) == 0 ) break;
+               FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
+               err = kParamErr;
+               goto exit;
        }
-       if( !type )
+       if( gGAIStress_ConnectionCount <= 0 )
        {
-               newType = (BrowseType *) calloc( 1, sizeof( *newType ) );
-               require_action( newType, exit, err = kNoMemoryErr );
-               
-               newType->name = strdup( inName );
-               require_action( newType->name, exit, err = kNoMemoryErr );
-               
-               type = newType;
+               FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
+               err = kParamErr;
+               goto exit;
        }
-       
-       for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+       if( gGAIStress_DurationMinMs <= 0 )
        {
-               if( browse->ifIndex == inIfIndex ) break;
+               FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs );
+               err = kParamErr;
+               goto exit;
+       }
+       if( gGAIStress_DurationMaxMs <= 0 )
+       {
+               FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs );
+               err = kParamErr;
+               goto exit;
+       }
+       if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs )
+       {
+               FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n",
+                       gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs );
+               err = kParamErr;
+               goto exit;
+       }
+       if( gGAIStress_RequestCountMax <= 0 )
+       {
+               FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax );
+               err = kParamErr;
+               goto exit;
        }
-       require_action_quiet( !browse, exit, err = kDuplicateErr );
-       
-       newBrowse = (BrowseOp *) calloc( 1, sizeof( *newBrowse ) );
-       require_action( newBrowse, exit, err = kNoMemoryErr );
-       
-       ++inContext->refCount;
-       newBrowse->context      = inContext;
-       newBrowse->ifIndex      = inIfIndex;
-       if( stricmp_suffix( inName, "._tcp" ) == 0 ) newBrowse->isTCP = true;
        
-       flags = kDNSServiceFlagsShareConnection;
-       if( inIncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+       // Set flags.
        
-       newBrowse->startTicks = UpTicks();
+       flags = GetDNSSDFlagsFromOpts();
        
-       sdRef = inContext->mainRef;
-       err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback,
-               newBrowse );
-       require_noerr( err, exit );
+       // Set interface index.
        
-       newBrowse->browse = sdRef;
-       *browsePtr = newBrowse;
-       newBrowse = NULL;
+       err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+       require_noerr_quiet( err, exit );
        
-       if( newType )
+       for( i = 0; i < gGAIStress_ConnectionCount; ++i )
        {
-               *typePtr = newType;
-               newType = NULL;
+               context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
+               require_action( context, exit, err = kNoMemoryErr );
+               
+               context->flags                          = flags;
+               context->interfaceIndex         = ifIndex;
+               context->connectionNumber       = (unsigned int)( i + 1 );
+               context->requestCountMax        = (unsigned int) gGAIStress_RequestCountMax;
+               context->durationMinMs          = (unsigned int) gGAIStress_DurationMinMs;
+               context->durationMaxMs          = (unsigned int) gGAIStress_DurationMaxMs;
+               
+               dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+               context = NULL;
        }
        
-exit:
-       if( newBrowse ) BrowseOpFree( newBrowse );
-       if( newType )   BrowseTypeFree( newType );
-       return( err );
-}
-
-//===========================================================================================================================
-//     BrowseAllRemoveServiceType
-//===========================================================================================================================
-
-static OSStatus
-       BrowseAllRemoveServiceType(
-               BrowseAllContext *      inContext,
-               BrowseDomain *          inDomain,
-               const char *            inName,
-               uint32_t                        inIfIndex )
-{
-       OSStatus                        err;
-       BrowseType *            type;
-       BrowseType **           typePtr;
-       BrowseOp *                      browse;
-       BrowseOp **                     browsePtr;
-       
-       Unused( inContext );
-       
-       for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+       if( gGAIStress_TestDurationSecs > 0 )
        {
-               if( strcasecmp( type->name, inName ) == 0 ) break;
+               dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
        }
-       require_action_quiet( type, exit, err = kNotFoundErr );
        
-       for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+       FPrintF( stdout, "Flags:                %#{flags}\n",   flags, kDNSServiceFlagsDescriptors );
+       FPrintF( stdout, "Interface:            %d (%s)\n",             (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+       FPrintF( stdout, "Test duration:        " );
+       if( gGAIStress_TestDurationSecs == 0 )
        {
-               if( browse->ifIndex == inIfIndex ) break;
+               FPrintF( stdout, "∞\n" );
        }
-       require_action_quiet( browse, exit, err = kNotFoundErr );
-       
-       *browsePtr = browse->next;
-       BrowseOpFree( browse );
-       if( !type->browseList )
+       else
        {
-               *typePtr = type->next;
-               BrowseTypeFree( type );
+               FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
        }
-       err = kNoErr;
+       FPrintF( stdout, "Connection count:     %d\n",                  gGAIStress_ConnectionCount );
+       FPrintF( stdout, "Request duration min: %d ms\n",               gGAIStress_DurationMinMs );
+       FPrintF( stdout, "Request duration max: %d ms\n",               gGAIStress_DurationMaxMs );
+       FPrintF( stdout, "Request count max:    %d\n",                  gGAIStress_RequestCountMax );
+       FPrintF( stdout, "Start time:           %{du:time}\n",  NULL);
+       FPrintF( stdout, "---\n" );
+       
+       dispatch_main();
        
 exit:
-       return( err );
+       FreeNullSafe( context );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     BrowseAllAddServiceInstance
+//     GetAddrInfoStressEvent
 //===========================================================================================================================
 
-static OSStatus
-       BrowseAllAddServiceInstance(
-               BrowseAllContext *      inContext,
-               BrowseOp *                      inBrowse,
-               const char *            inName,
-               const char *            inRegType,
-               const char *            inDomain,
-               uint32_t                        inIfIndex )
+#define kStressRandStrLen              5
+
+#define kLowercaseAlphaCharSet         "abcdefghijklmnopqrstuvwxyz"
+
+static void    GetAddrInfoStressEvent( void *inContext )
 {
-       OSStatus                                err;
-       DNSServiceRef                   sdRef;
-       BrowseInstance *                instance;
-       BrowseInstance **               p;
-       const uint64_t                  nowTicks        = UpTicks();
-       BrowseInstance *                newInstance     = NULL;
+       GAIStressContext * const                context = (GAIStressContext *) inContext;
+       OSStatus                                                err;
+       DNSServiceRef                                   sdRef;
+       unsigned int                                    nextMs;
+       char                                                    randomStr[ kStressRandStrLen + 1 ];
+       char                                                    hostname[ kStressRandStrLen + 4 + 1 ];
+       Boolean                                                 isConnectionNew = false;
+       static Boolean                                  printedHeader   = false;
        
-       for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
+       if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
        {
-               if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+               DNSServiceForget( &context->mainRef );
+               context->sdRef                          = NULL;
+               context->requestCount           = 0;
+               context->requestCountLimit      = RandomRange( 1, context->requestCountMax );
+               
+               err = DNSServiceCreateConnection( &context->mainRef );
+               require_noerr( err, exit );
+               
+               err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
+               require_noerr( err, exit );
+               
+               isConnectionNew = true;
        }
-       require_action_quiet( !instance, exit, err = kDuplicateErr );
        
-       newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) );
-       require_action( newInstance, exit, err = kNoMemoryErr );
+       RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
+       SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
        
-       ++inContext->refCount;
-       newInstance->context    = inContext;
-       newInstance->foundTicks = nowTicks;
-       newInstance->ifIndex    = inIfIndex;
-       newInstance->isTCP              = inBrowse->isTCP;
+       nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
        
-       newInstance->name = strdup( inName );
-       require_action( newInstance->name, exit, err = kNoMemoryErr );
+       if( !printedHeader )
+       {
+               FPrintF( stdout, "%-26s Conn  Hostname Dur (ms)\n", "Timestamp" );
+               printedHeader = true;
+       }
+       FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
+               NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
        
-       sdRef = inContext->mainRef;
-       newInstance->resolveStartTicks = UpTicks();
-       err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
-               BrowseAllResolveCallback, newInstance );
+       DNSServiceForget( &context->sdRef );
+       sdRef = context->mainRef;
+       err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex,
+               kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL );
        require_noerr( err, exit );
+       context->sdRef = sdRef;
        
-       newInstance->resolve = sdRef;
-       *p = newInstance;
-       newInstance = NULL;
+       context->requestCount++;
+       
+       dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
        
 exit:
-       if( newInstance ) BrowseInstanceFree( newInstance );
-       return( err );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     BrowseAllRemoveServiceInstance
+//     GetAddrInfoStressCallback
 //===========================================================================================================================
 
-static OSStatus
-       BrowseAllRemoveServiceInstance(
-               BrowseAllContext *      inContext,
-               BrowseOp *                      inBrowse,
-               const char *            inName,
-               uint32_t                        inIfIndex )
+static void DNSSD_API
+       GetAddrInfoStressCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
 {
-       OSStatus                                err;
-       BrowseInstance *                instance;
-       BrowseInstance **               p;
-       
+       Unused( inSDRef );
+       Unused( inFlags );
+       Unused( inInterfaceIndex );
+       Unused( inError );
+       Unused( inHostname );
+       Unused( inSockAddr );
+       Unused( inTTL );
        Unused( inContext );
-       
-       for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
-       {
-               if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
-       }
-       require_action_quiet( instance, exit, err = kNotFoundErr );
-       
-       *p = instance->next;
-       BrowseInstanceFree( instance );
-       err = kNoErr;
-       
-exit:
-       return( err );
 }
 
 //===========================================================================================================================
-//     BrowseAllAddIPAddress
+//     DNSQueryCmd
 //===========================================================================================================================
 
-#define kDiscardProtocolPort   9
+typedef struct
+{
+       sockaddr_ip                             serverAddr;
+       uint64_t                                sendTicks;
+       uint8_t *                               msgPtr;
+       size_t                                  msgLen;
+       size_t                                  msgOffset;
+       const char *                    name;
+       dispatch_source_t               readSource;
+       SocketRef                               sock;
+       int                                             timeLimitSecs;
+       uint16_t                                queryID;
+       uint16_t                                type;
+       Boolean                                 haveTCPLen;
+       Boolean                                 useTCP;
+       Boolean                                 printRawRData;  // True if RDATA results are not to be formatted.
+       uint8_t                                 msgBuf[ 512 ];
+       
+}      DNSQueryContext;
+
+static void    DNSQueryPrintPrologue( const DNSQueryContext *inContext );
+static void    DNSQueryReadHandler( void *inContext );
+static void    DNSQueryCancelHandler( void *inContext );
 
-static OSStatus
-       BrowseAllAddIPAddress(
-               BrowseAllContext *              inContext,
-               BrowseInstance *                inInstance,
-               const struct sockaddr * inSockAddr )
+static void    DNSQueryCmd( void )
 {
-       OSStatus                        err;
-       BrowseIPAddr *          addr;
-       BrowseIPAddr **         p;
-       const uint64_t          nowTicks        = UpTicks();
-       BrowseIPAddr *          newAddr         = NULL;
+       OSStatus                                err;
+       DNSQueryContext *               context = NULL;
+       uint8_t *                               msgPtr;
+       size_t                                  msgLen, sendLen;
        
-       if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+       // Check command parameters.
+       
+       if( gDNSQuery_TimeLimitSecs < -1 )
        {
-               dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
-               err = kTypeErr;
+               FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs );
+               err = kParamErr;
+               goto exit;
+       }
+       if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
+       {
+               FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
+               err = kParamErr;
                goto exit;
        }
        
-       for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+       // Create context.
+       
+       context = (DNSQueryContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       context->name                   = gDNSQuery_Name;
+       context->sock                   = kInvalidSocketRef;
+       context->timeLimitSecs  = gDNSQuery_TimeLimitSecs;
+       context->queryID                = (uint16_t) Random32();
+       context->useTCP                 = gDNSQuery_UseTCP       ? true : false;
+       context->printRawRData  = gDNSQuery_RawRData ? true : false;
+       
+#if( TARGET_OS_DARWIN )
+       if( gDNSQuery_Server )
+#endif
+       {
+               err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+               require_noerr( err, exit );
+       }
+#if( TARGET_OS_DARWIN )
+       else
+       {
+               err = GetDefaultDNSServer( &context->serverAddr );
+               require_noerr( err, exit );
+       }
+#endif
+       if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
+       
+       err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
+       require_noerr( err, exit );
+       
+       // Write query message.
+       
+       check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+       
+       msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
+       err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
+               kDNSServiceClass_IN, &msgLen );
+       require_noerr( err, exit );
+       check( msgLen <= UINT16_MAX );
+       
+       if( context->useTCP )
+       {
+               WriteBig16( context->msgBuf, msgLen );
+               sendLen = 2 + msgLen;
+       }
+       else
        {
-               if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break;
+               sendLen = msgLen;
        }
-       require_action_quiet( !addr, exit, err = kDuplicateErr );
        
-       newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) );
-       require_action( newAddr, exit, err = kNoMemoryErr );
+       DNSQueryPrintPrologue( context );
        
-       ++inContext->refCount;
-       newAddr->refCount       = 1;
-       newAddr->context        = inContext;
-       newAddr->foundTicks     = nowTicks;
-       SockAddrCopy( inSockAddr, &newAddr->sip.sa );
+       if( gDNSQuery_Verbose )
+       {
+               FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
+               FPrintF( stdout, "---\n" );
+       }
        
-       if( ( inContext->maxConnectTimeSecs > 0 ) && inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
+       if( context->useTCP )
        {
-               char            destination[ kSockAddrStringMaxSize ];
+               // Create TCP socket.
                
-               err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination );
+               context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
+               err = map_socket_creation_errno( context->sock );
                require_noerr( err, exit );
                
-               err = AsyncConnection_Connect( &newAddr->connection, destination, -inInstance->port, kAsyncConnectionFlag_P2P,
-                       kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
-                       BrowseAllConnectionProgress, newAddr, BrowseAllConnectionHandler, newAddr, dispatch_get_main_queue() );
+               err = SocketConnect( context->sock, &context->serverAddr, 5 );
                require_noerr( err, exit );
+       }
+       else
+       {
+               // Create UDP socket.
                
-               ++newAddr->refCount;
-               newAddr->connectStatus = kConnectStatus_Pending;
-               ++inContext->pendingConnectCount;
+               err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
+               require_noerr( err, exit );
        }
        
-       *p = newAddr;
-       newAddr = NULL;
-       err = kNoErr;
+       context->sendTicks = UpTicks();
+       err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+       require_noerr( err, exit );
        
-exit:
-       if( newAddr ) BrowseIPAddrRelease( newAddr );
-       return( err );
-}
-
-//===========================================================================================================================
-//     BrowseAllRemoveIPAddress
-//===========================================================================================================================
-
-static OSStatus
-       BrowseAllRemoveIPAddress(
-               BrowseAllContext *              inContext,
-               BrowseInstance *                inInstance,
-               const struct sockaddr * inSockAddr )
-{
-       OSStatus                        err;
-       BrowseIPAddr *          addr;
-       BrowseIPAddr **         p;
+       if( context->timeLimitSecs == 0 ) goto exit;
        
-       Unused( inContext );
+       err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
+               &context->readSource );
+       require_noerr( err, exit );
+       dispatch_resume( context->readSource );
        
-       for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+       if( context->timeLimitSecs > 0 )
        {
-               if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break;
+               dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+                       Exit );
        }
-       require_action_quiet( addr, exit, err = kNotFoundErr );
-       
-       *p = addr->next;
-       BrowseIPAddrRelease( addr );
-       err = kNoErr;
+       dispatch_main();
        
 exit:
-       return( err );
-}
-
-//===========================================================================================================================
-//     BrowseDomainFree
-//===========================================================================================================================
-
-static void    BrowseDomainFree( BrowseDomain *inDomain )
-{
-       BrowseType *            type;
-       
-       ForgetBrowseAllContext( &inDomain->context );
-       ForgetMem( &inDomain->name );
-       DNSServiceForget( &inDomain->servicesQuery );
-       while( ( type = inDomain->typeList ) != NULL )
+       if( context )
        {
-               inDomain->typeList = type->next;
-               BrowseTypeFree( type );
+               dispatch_source_forget( &context->readSource );
+               ForgetSocket( &context->sock );
+               free( context );
        }
-       free( inDomain );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     BrowseTypeFree
+//     DNSQueryPrintPrologue
 //===========================================================================================================================
 
-static void    BrowseTypeFree( BrowseType *inType )
+static void    DNSQueryPrintPrologue( const DNSQueryContext *inContext )
 {
-       BrowseOp *              browse;
+       const int               timeLimitSecs = inContext->timeLimitSecs;
        
-       ForgetMem( &inType->name );
-       while( ( browse = inType->browseList ) != NULL )
-       {
-               inType->browseList = browse->next;
-               BrowseOpFree( browse );
-       }
-       free( inType );
+       FPrintF( stdout, "Name:        %s\n",           inContext->name );
+       FPrintF( stdout, "Type:        %s (%u)\n",      RecordTypeToString( inContext->type ), inContext->type );
+       FPrintF( stdout, "Server:      %##a\n",         &inContext->serverAddr );
+       FPrintF( stdout, "Transport:   %s\n",           inContext->useTCP ? "TCP" : "UDP" );
+       FPrintF( stdout, "Time limit:  " );
+       if( timeLimitSecs >= 0 )        FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
+       else                                            FPrintF( stdout, "∞\n" );
+       FPrintF( stdout, "Start time:  %{du:time}\n", NULL );
+       FPrintF( stdout, "---\n" );
 }
 
 //===========================================================================================================================
-//     BrowseOpFree
+//     DNSQueryReadHandler
 //===========================================================================================================================
 
-static void    BrowseOpFree( BrowseOp *inBrowse )
+static void    DNSQueryReadHandler( void *inContext )
 {
-       BrowseInstance *                instance;
+       OSStatus                                        err;
+       struct timeval                          now;
+       const uint64_t                          nowTicks        = UpTicks();
+       DNSQueryContext * const         context         = (DNSQueryContext *) inContext;
        
-       ForgetBrowseAllContext( &inBrowse->context );
-       DNSServiceForget( &inBrowse->browse );
-       while( ( instance = inBrowse->instanceList ) != NULL )
+       gettimeofday( &now, NULL );
+       
+       if( context->useTCP )
        {
-               inBrowse->instanceList = instance->next;
-               BrowseInstanceFree( instance );
+               if( !context->haveTCPLen )
+               {
+                       err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset );
+                       if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
+                       require_noerr( err, exit );
+                       
+                       context->msgOffset      = 0;
+                       context->msgLen         = ReadBig16( context->msgBuf );
+                       context->haveTCPLen     = true;
+                       if( context->msgLen <= sizeof( context->msgBuf ) )
+                       {
+                               context->msgPtr = context->msgBuf;
+                       }
+                       else
+                       {
+                               context->msgPtr = (uint8_t *) malloc( context->msgLen );
+                               require_action( context->msgPtr, exit, err = kNoMemoryErr );
+                       }
+               }
+               
+               err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset );
+               if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
+               require_noerr( err, exit );
+               context->msgOffset      = 0;
+               context->haveTCPLen     = false;
        }
-       free( inBrowse );
+       else
+       {
+               sockaddr_ip             fromAddr;
+               
+               context->msgPtr = context->msgBuf;
+               err = SocketRecvFrom( context->sock, context->msgPtr, sizeof( context->msgBuf ), &context->msgLen, &fromAddr,
+                       sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+               require_noerr( err, exit );
+               
+               check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+       }
+       
+       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
+       FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
+       FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
+       FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
+       
+       if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+       {
+               Exit( kExitReason_ReceivedResponse );
+       }
+       
+exit:
+       if( err ) dispatch_source_forget( &context->readSource );
 }
 
 //===========================================================================================================================
-//     BrowseInstanceFree
+//     DNSQueryCancelHandler
 //===========================================================================================================================
 
-static void    BrowseInstanceFree( BrowseInstance *inInstance )
+static void    DNSQueryCancelHandler( void *inContext )
 {
-       ForgetBrowseAllContext( &inInstance->context );
-       ForgetMem( &inInstance->name );
-       DNSServiceForget( &inInstance->resolve );
-       DNSServiceForget( &inInstance->getAddr );
-       ForgetMem( &inInstance->txtPtr );
-       ForgetMem( &inInstance->hostname );
-       ForgetIPAddressList( &inInstance->addrList );
-       free( inInstance );
+       DNSQueryContext * const         context = (DNSQueryContext *) inContext;
+       
+       check( !context->readSource );
+       ForgetSocket( &context->sock );
+       if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr );
+       free( context );
+       dispatch_async_f( dispatch_get_main_queue(), NULL, Exit );
 }
 
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
 //===========================================================================================================================
-//     BrowseIPAddrRelease
+//     DNSCryptCmd
 //===========================================================================================================================
 
-static void BrowseIPAddrRelease( BrowseIPAddr *inAddr )
+#define kDNSCryptPort          443
+
+#define kDNSCryptMinPadLength                          8
+#define kDNSCryptMaxPadLength                          256
+#define kDNSCryptBlockSize                                     64
+#define kDNSCryptCertMinimumLength                     124
+#define kDNSCryptClientMagicLength                     8
+#define kDNSCryptResolverMagicLength           8
+#define kDNSCryptHalfNonceLength                       12
+#define kDNSCryptCertMagicLength                       4
+
+check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES );
+
+static const uint8_t           kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' };
+static const uint8_t           kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] =
 {
-       AsyncConnection_Forget( &inAddr->connection );
-       if( --inAddr->refCount == 0 )
-       {
-               ForgetBrowseAllContext( &inAddr->context );
-               free( inAddr );
-       }
-}
+       0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
+};
 
-//===========================================================================================================================
-//     BrowseIPAddrReleaseList
-//===========================================================================================================================
+typedef struct
+{
+       uint8_t         certMagic[ kDNSCryptCertMagicLength ];
+       uint8_t         esVersion[ 2 ];
+       uint8_t         minorVersion[ 2 ];
+       uint8_t         signature[ crypto_sign_BYTES ];
+       uint8_t         publicKey[ crypto_box_PUBLICKEYBYTES ];
+       uint8_t         clientMagic[ kDNSCryptClientMagicLength ];
+       uint8_t         serial[ 4 ];
+       uint8_t         startTime[ 4 ];
+       uint8_t         endTime[ 4 ];
+       uint8_t         extensions[ 1 ];        // Variably-sized extension data.
+       
+}      DNSCryptCert;
+
+check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength );
+
+typedef struct
+{
+       uint8_t         clientMagic[ kDNSCryptClientMagicLength ];
+       uint8_t         clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
+       uint8_t         clientNonce[ kDNSCryptHalfNonceLength ];
+       uint8_t         poly1305MAC[ 16 ];
+       
+}      DNSCryptQueryHeader;
 
-static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
+check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 );
+check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES );
+check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) ==
+       offsetof( DNSCryptQueryHeader, poly1305MAC ) );
+
+typedef struct
 {
-       BrowseIPAddr *          addr;
+       uint8_t         resolverMagic[ kDNSCryptResolverMagicLength ];
+       uint8_t         clientNonce[ kDNSCryptHalfNonceLength ];
+       uint8_t         resolverNonce[ kDNSCryptHalfNonceLength ];
+       uint8_t         poly1305MAC[ 16 ];
        
-       while( ( addr = inList ) != NULL )
-       {
-               inList = addr->next;
-               BrowseIPAddrRelease( addr );
-       }
-}
+}      DNSCryptResponseHeader;
 
-//===========================================================================================================================
-//     GetNameInfoCmd
-//===========================================================================================================================
+check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 );
+check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES );
+check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) ==
+       sizeof( DNSCryptResponseHeader ) );
 
-const FlagStringPair           kGetNameInfoFlagStringPairs[] =
+typedef struct
 {
-       CaseFlagStringify( NI_NUMERICSCOPE ),
-       CaseFlagStringify( NI_DGRAM ),
-       CaseFlagStringify( NI_NUMERICSERV ),
-       CaseFlagStringify( NI_NAMEREQD ),
-       CaseFlagStringify( NI_NUMERICHOST ),
-       CaseFlagStringify( NI_NOFQDN ),
-       { 0, NULL }
-};
+       sockaddr_ip                             serverAddr;
+       uint64_t                                sendTicks;
+       const char *                    providerName;
+       const char *                    qname;
+       const uint8_t *                 certPtr;
+       size_t                                  certLen;
+       dispatch_source_t               readSource;
+       size_t                                  msgLen;
+       int                                             timeLimitSecs;
+       uint16_t                                queryID;
+       uint16_t                                qtype;
+       Boolean                                 printRawRData;
+       uint8_t                                 serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ];
+       uint8_t                                 serverPublicKey[ crypto_box_PUBLICKEYBYTES ];
+       uint8_t                                 clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
+       uint8_t                                 clientSecretKey[ crypto_box_SECRETKEYBYTES ];
+       uint8_t                                 clientMagic[ kDNSCryptClientMagicLength ];
+       uint8_t                                 clientNonce[ kDNSCryptHalfNonceLength ];
+       uint8_t                                 nmKey[ crypto_box_BEFORENMBYTES ];
+       uint8_t                                 msgBuf[ 512 ];
+       
+}      DNSCryptContext;
 
-static void    GetNameInfoCmd( void )
+static void            DNSCryptReceiveCertHandler( void *inContext );
+static void            DNSCryptReceiveResponseHandler( void *inContext );
+static void            DNSCryptProceed( void *inContext );
+static OSStatus        DNSCryptProcessCert( DNSCryptContext *inContext );
+static OSStatus        DNSCryptBuildQuery( DNSCryptContext *inContext );
+static OSStatus        DNSCryptSendQuery( DNSCryptContext *inContext );
+static void            DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen );
+
+static void    DNSCryptCmd( void )
 {
-       OSStatus                                        err;
-       sockaddr_ip                                     sip;
-       size_t                                          sockAddrLen;
-       unsigned int                            flags;
-       const FlagStringPair *          pair;
-       struct timeval                          now;
-       char                                            host[ NI_MAXHOST ];
-       char                                            serv[ NI_MAXSERV ];
+       OSStatus                                err;
+       DNSCryptContext *               context         = NULL;
+       size_t                                  writtenBytes;
+       size_t                                  totalBytes;
+       SocketContext *                 sockCtx;
+       SocketRef                               sock            = kInvalidSocketRef;
+       const char *                    ptr;
        
-       err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
-       check_noerr( err );
-       if( err )
+       // Check command parameters.
+       
+       if( gDNSCrypt_TimeLimitSecs < -1 )
        {
-               FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+               FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
+               err = kParamErr;
                goto exit;
        }
        
-       flags = 0;
-       if( gGetNameInfoFlag_DGram )            flags |= NI_DGRAM;
-       if( gGetNameInfoFlag_NameReqd )         flags |= NI_NAMEREQD;
-       if( gGetNameInfoFlag_NoFQDN )           flags |= NI_NOFQDN;
-       if( gGetNameInfoFlag_NumericHost )      flags |= NI_NUMERICHOST;
-       if( gGetNameInfoFlag_NumericScope )     flags |= NI_NUMERICSCOPE;
-       if( gGetNameInfoFlag_NumericServ )      flags |= NI_NUMERICSERV;
+       // Create context.
        
-       // Print prologue.
+       context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
        
-       FPrintF( stdout, "SockAddr:   %##a\n",  &sip.sa );
-       FPrintF( stdout, "Flags:      0x%X < ", flags );
-       for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair )
-       {
-               if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str );
-       }
-       FPrintF( stdout, ">\n" );
-       FPrintF( stdout, "Start time: %{du:time}\n", NULL );
-       FPrintF( stdout, "---\n" );
+       context->providerName   = gDNSCrypt_ProviderName;
+       context->qname                  = gDNSCrypt_Name;
+       context->timeLimitSecs  = gDNSCrypt_TimeLimitSecs;
+       context->printRawRData  = gDNSCrypt_RawRData ? true : false;
        
-       // Call getnameinfo().
+       err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
+       require_noerr( err, exit );
        
-       err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ),
-               (int) flags );
-       gettimeofday( &now, NULL );
-       if( err )
-       {
-               FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+       err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags,
+               context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr );
+       if( err || ( *ptr != '\0' ) )
+       {
+               FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey );
+               goto exit;
        }
-       else
+       else if( totalBytes != sizeof( context->serverPublicSignKey ) )
        {
-               FPrintF( stdout, "host: %s\n", host );
-               FPrintF( stdout, "serv: %s\n", serv );
+               FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
+                       totalBytes, sizeof( context->serverPublicSignKey ) );
+               err = kSizeErr;
+               goto exit;
        }
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "End time:   %{du:time}\n", &now );
+       check( writtenBytes == totalBytes );
+       
+       err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+       require_noerr( err, exit );
+       if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
+       
+       err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
+       require_noerr( err, exit );
+       
+       // Write query message.
+       
+       context->queryID = (uint16_t) Random32();
+       err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName,
+               kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen );
+       require_noerr( err, exit );
+       
+       // Create UDP socket.
+       
+       err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
+       require_noerr( err, exit );
+       
+       // Send DNS query.
+       
+       context->sendTicks = UpTicks();
+       err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
+       require_noerr( err, exit );
+       
+       err = SocketContextCreate( sock, context, &sockCtx );
+       require_noerr( err, exit );
+       sock = kInvalidSocketRef;
+       
+       err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
+               &context->readSource );
+       if( err ) ForgetSocketContext( &sockCtx );
+       require_noerr( err, exit );
+       
+       dispatch_resume( context->readSource );
+       
+       if( context->timeLimitSecs > 0 )
+       {
+               dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+                       Exit );
+       }
+       dispatch_main();
        
 exit:
-       gExitCode = err ? 1 : 0;
+       if( context ) free( context );
+       ForgetSocket( &sock );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     GetAddrInfoStressCmd
+//     DNSCryptReceiveCertHandler
 //===========================================================================================================================
 
-typedef struct
+static void    DNSCryptReceiveCertHandler( void *inContext )
 {
-       DNSServiceRef                   mainRef;
-       DNSServiceRef                   sdRef;
-       DNSServiceFlags                 flags;
-       unsigned int                    interfaceIndex;
-       unsigned int                    connectionNumber;
-       unsigned int                    requestCount;
-       unsigned int                    requestCountMax;
-       unsigned int                    requestCountLimit;
-       unsigned int                    durationMinMs;
-       unsigned int                    durationMaxMs;
+       OSStatus                                        err;
+       struct timeval                          now;
+       const uint64_t                          nowTicks        = UpTicks();
+       SocketContext * const           sockCtx         = (SocketContext *) inContext;
+       DNSCryptContext * const         context         = (DNSCryptContext *) sockCtx->userContext;
+       const DNSHeader *                       hdr;
+       sockaddr_ip                                     fromAddr;
+       const uint8_t *                         ptr;
+       const uint8_t *                         txtPtr;
+       size_t                                          txtLen;
+       unsigned int                            answerCount, i;
+       uint8_t                                         targetName[ kDomainNameLengthMax ];
        
-}      GAIStressContext;
-
-static void    GetAddrInfoStressEvent( void *inContext );
-static void    DNSSD_API
-       GetAddrInfoStressCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inHostname,
-               const struct sockaddr * inSockAddr,
-               uint32_t                                inTTL,
-               void *                                  inContext );
-
-static void    GetAddrInfoStressCmd( void )
-{
-       OSStatus                                err;
-       GAIStressContext *              context = NULL;
-       int                                             i;
-       DNSServiceFlags                 flags;
-       uint32_t                                ifIndex;
-       char                                    ifName[ kInterfaceNameBufLen ];
+       gettimeofday( &now, NULL );
        
-       if( gGAIStress_TestDurationSecs < 0 )
-       {
-               FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
-               err = kParamErr;
-               goto exit;
-       }
-       if( gGAIStress_ConnectionCount <= 0 )
-       {
-               FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
-               err = kParamErr;
-               goto exit;
-       }
-       if( gGAIStress_DurationMinMs <= 0 )
-       {
-               FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs );
-               err = kParamErr;
-               goto exit;
-       }
-       if( gGAIStress_DurationMaxMs <= 0 )
-       {
-               FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs );
-               err = kParamErr;
-               goto exit;
-       }
-       if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs )
-       {
-               FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n",
-                       gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs );
-               err = kParamErr;
-               goto exit;
-       }
-       if( gGAIStress_RequestCountMax <= 0 )
-       {
-               FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax );
-               err = kParamErr;
-               goto exit;
-       }
+       dispatch_source_forget( &context->readSource );
        
-       // Set flags.
+       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+               &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+       require_noerr( err, exit );
+       check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
        
-       flags = GetDNSSDFlagsFromOpts();
+       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
+       FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
+       FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
+       FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
        
-       // Set interface index.
+       require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
        
-       err = InterfaceIndexFromArgString( gInterface, &ifIndex );
-       require_noerr_quiet( err, exit );
+       hdr = (DNSHeader *) context->msgBuf;
+       require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
        
-       for( i = 0; i < gGAIStress_ConnectionCount; ++i )
+       err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
+       require_noerr( err, exit );
+       
+       err = DomainNameFromString( targetName, context->providerName, NULL );
+       require_noerr( err, exit );
+       
+       answerCount = DNSHeaderGetAnswerCount( hdr );
+       for( i = 0; i < answerCount; ++i )
        {
-               context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
-               require_action( context, exit, err = kNoMemoryErr );
+               uint16_t                type;
+               uint16_t                class;
+               uint8_t                 name[ kDomainNameLengthMax ];
                
-               context->flags                          = flags;
-               context->interfaceIndex         = ifIndex;
-               context->connectionNumber       = (unsigned int)( i + 1 );
-               context->requestCountMax        = (unsigned int) gGAIStress_RequestCountMax;
-               context->durationMinMs          = (unsigned int) gGAIStress_DurationMinMs;
-               context->durationMaxMs          = (unsigned int) gGAIStress_DurationMaxMs;
+               err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
+                       &ptr );
+               require_noerr( err, exit );
                
-               dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
-               context = NULL;
-       }
-       
-       if( gGAIStress_TestDurationSecs > 0 )
-       {
-               dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
+               if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
+               {
+                       break;
+               }
        }
        
-       FPrintF( stdout, "Flags:                %#{flags}\n",   flags, kDNSServiceFlagsDescriptors );
-       FPrintF( stdout, "Interface:            %d (%s)\n",             (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
-       FPrintF( stdout, "Test duration:        " );
-       if( gGAIStress_TestDurationSecs == 0 )
+       if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
        {
-               FPrintF( stdout, "∞\n" );
+               FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
+               err = kSizeErr;
+               goto exit;
        }
-       else
+       if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
        {
-               FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
+               FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
+               err = kSizeErr;
+               goto exit;
        }
-       FPrintF( stdout, "Connection count:     %d\n",                  gGAIStress_ConnectionCount );
-       FPrintF( stdout, "Request duration min: %d ms\n",               gGAIStress_DurationMinMs );
-       FPrintF( stdout, "Request duration max: %d ms\n",               gGAIStress_DurationMaxMs );
-       FPrintF( stdout, "Request count max:    %d\n",                  gGAIStress_RequestCountMax );
-       FPrintF( stdout, "Start time:           %{du:time}\n",  NULL);
-       FPrintF( stdout, "---\n" );
        
-       dispatch_main();
+       context->certLen = txtPtr[ 0 ];
+       context->certPtr = &txtPtr[ 1 ];
+       
+       dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
        
 exit:
-       FreeNullSafe( context );
-       if( err ) exit( 1 );
+       if( err ) Exit( NULL );
 }
 
 //===========================================================================================================================
-//     GetAddrInfoStressEvent
+//     DNSCryptReceiveResponseHandler
 //===========================================================================================================================
 
-#define kStressRandStrLen              5
-
-#define kLowercaseAlphaCharSet         "abcdefghijklmnopqrstuvwxyz"
-
-static void    GetAddrInfoStressEvent( void *inContext )
+static void    DNSCryptReceiveResponseHandler( void *inContext )
 {
-       GAIStressContext * const                context = (GAIStressContext *) inContext;
        OSStatus                                                err;
-       DNSServiceRef                                   sdRef;
-       unsigned int                                    nextMs;
-       char                                                    randomStr[ kStressRandStrLen + 1 ];
-       char                                                    hostname[ kStressRandStrLen + 4 + 1 ];
-       Boolean                                                 isConnectionNew = false;
-       static Boolean                                  printedHeader   = false;
-       
-       if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
-       {
-               DNSServiceForget( &context->mainRef );
-               context->sdRef                          = NULL;
-               context->requestCount           = 0;
-               context->requestCountLimit      = RandomRange( 1, context->requestCountMax );
-               
-               err = DNSServiceCreateConnection( &context->mainRef );
-               require_noerr( err, exit );
-               
-               err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
-               require_noerr( err, exit );
-               
-               isConnectionNew = true;
+       struct timeval                                  now;
+       const uint64_t                                  nowTicks        = UpTicks();
+       SocketContext * const                   sockCtx         = (SocketContext *) inContext;
+       DNSCryptContext * const                 context         = (DNSCryptContext *) sockCtx->userContext;
+       sockaddr_ip                                             fromAddr;
+       DNSCryptResponseHeader *                hdr;
+       const uint8_t *                                 end;
+       uint8_t *                                               ciphertext;
+       uint8_t *                                               plaintext;
+       const uint8_t *                                 response;
+       uint8_t                                                 nonce[ crypto_box_NONCEBYTES ];
+       
+       gettimeofday( &now, NULL );
+       
+       dispatch_source_forget( &context->readSource );
+       
+       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+               &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+       require_noerr( err, exit );
+       check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+       
+       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
+       FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
+       FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
+       FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+       
+       if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
+       {
+               FPrintF( stderr, "DNSCrypt response is too short.\n" );
+               err = kSizeErr;
+               goto exit;
        }
        
-       RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
-       SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
+       hdr = (DNSCryptResponseHeader *) context->msgBuf;
        
-       nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
+       if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
+       {
+               FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
+                       hdr->resolverMagic,             kDNSCryptResolverMagicLength, INT_MAX,
+                       kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
+               err = kValueErr;
+               goto exit;
+       }
        
-       if( !printedHeader )
+       if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
        {
-               FPrintF( stdout, "%-26s Conn  Hostname Dur (ms)\n", "Timestamp" );
-               printedHeader = true;
+               FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
+               err = kValueErr;
+               goto exit;
        }
-       FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
-               NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
        
-       DNSServiceForget( &context->sdRef );
-       sdRef = context->mainRef;
-       err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex,
-               kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL );
-       require_noerr( err, exit );
-       context->sdRef = sdRef;
+       memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
        
-       context->requestCount++;
+       ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
+       memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
        
-       dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+       plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
+       check( plaintext == ciphertext );
+       
+       end = context->msgBuf + context->msgLen;
+       
+       err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
+       require_noerr( err, exit );
+       
+       response = plaintext + crypto_box_ZEROBYTES;
+       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
+       Exit( kExitReason_ReceivedResponse );
        
 exit:
-       if( err ) exit( 1 );
+       if( err ) Exit( NULL );
 }
 
 //===========================================================================================================================
-//     GetAddrInfoStressCallback
+//     DNSCryptProceed
 //===========================================================================================================================
 
-static void DNSSD_API
-       GetAddrInfoStressCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inHostname,
-               const struct sockaddr * inSockAddr,
-               uint32_t                                inTTL,
-               void *                                  inContext )
+static void    DNSCryptProceed( void *inContext )
 {
-       Unused( inSDRef );
-       Unused( inFlags );
-       Unused( inInterfaceIndex );
-       Unused( inError );
-       Unused( inHostname );
-       Unused( inSockAddr );
-       Unused( inTTL );
-       Unused( inContext );
+       OSStatus                                        err;
+       DNSCryptContext * const         context = (DNSCryptContext *) inContext;
+       
+       err = DNSCryptProcessCert( context );
+       require_noerr_quiet( err, exit );
+       
+       err = DNSCryptBuildQuery( context );
+       require_noerr_quiet( err, exit );
+       
+       err = DNSCryptSendQuery( context );
+       require_noerr_quiet( err, exit );
+       
+exit:
+       if( err ) Exit( NULL );
 }
 
 //===========================================================================================================================
-//     DNSQueryCmd
+//     DNSCryptProcessCert
 //===========================================================================================================================
 
-typedef struct
-{
-       sockaddr_ip                             serverAddr;
-       uint64_t                                sendTicks;
-       uint8_t *                               msgPtr;
-       size_t                                  msgLen;
-       size_t                                  msgOffset;
-       const char *                    name;
-       dispatch_source_t               readSource;
-       SocketRef                               sock;
-       int                                             timeLimitSecs;
-       uint16_t                                queryID;
-       uint16_t                                type;
-       Boolean                                 haveTCPLen;
-       Boolean                                 useTCP;
-       Boolean                                 printRawRData;  // True if RDATA results are not to be formatted.
-       uint8_t                                 msgBuf[ 512 ];
-       
-}      DNSQueryContext;
-
-static void    DNSQueryPrintPrologue( const DNSQueryContext *inContext );
-static void    DNSQueryReadHandler( void *inContext );
-static void    DNSQueryCancelHandler( void *inContext );
-
-static void    DNSQueryCmd( void )
+static OSStatus        DNSCryptProcessCert( DNSCryptContext *inContext )
 {
-       OSStatus                                err;
-       DNSQueryContext *               context = NULL;
-       uint8_t *                               msgPtr;
-       size_t                                  msgLen, sendLen;
+       OSStatus                                                err;
+       const DNSCryptCert * const              cert    = (DNSCryptCert *) inContext->certPtr;
+       const uint8_t * const                   certEnd = inContext->certPtr + inContext->certLen;
+       struct timeval                                  now;
+       time_t                                                  startTimeSecs, endTimeSecs;
+       size_t                                                  signedLen;
+       uint8_t *                                               tempBuf;
+       unsigned long long                              tempLen;
        
-       // Check command parameters.
+       DNSCryptPrintCertificate( cert, inContext->certLen );
        
-       if( gDNSQuery_TimeLimitSecs < -1 )
+       if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
        {
-               FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs );
-               err = kParamErr;
+               FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
+                       cert->certMagic,        kDNSCryptCertMagicLength, INT_MAX,
+                       kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
+               err = kValueErr;
                goto exit;
        }
-       if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
+       
+       startTimeSecs   = (time_t) ReadBig32( cert->startTime );
+       endTimeSecs             = (time_t) ReadBig32( cert->endTime );
+       
+       gettimeofday( &now, NULL );
+       if( now.tv_sec < startTimeSecs )
        {
-               FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
-               err = kParamErr;
+               FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
+               err = kDateErr;
                goto exit;
        }
-       
-       // Create context.
-       
-       context = (DNSQueryContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
-       
-       context->name                   = gDNSQuery_Name;
-       context->sock                   = kInvalidSocketRef;
-       context->timeLimitSecs  = gDNSQuery_TimeLimitSecs;
-       context->queryID                = (uint16_t) Random32();
-       context->useTCP                 = gDNSQuery_UseTCP       ? true : false;
-       context->printRawRData  = gDNSQuery_RawRData ? true : false;
-       
-#if( TARGET_OS_DARWIN )
-       if( gDNSQuery_Server )
-#endif
+       if( now.tv_sec >= endTimeSecs )
        {
-               err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
-               require_noerr( err, exit );
+               FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
+               err = kDateErr;
+               goto exit;
        }
-#if( TARGET_OS_DARWIN )
-       else
+       
+       signedLen = (size_t)( certEnd - cert->signature );
+       tempBuf = (uint8_t *) malloc( signedLen );
+       require_action( tempBuf, exit, err = kNoMemoryErr );
+       err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey );
+       free( tempBuf );
+       if( err )
        {
-               err = GetDefaultDNSServer( &context->serverAddr );
-               require_noerr( err, exit );
+               FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
+               err = kAuthenticationErr;
+               goto exit;
        }
-#endif
-       if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
        
-       err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
+       memcpy( inContext->serverPublicKey,     cert->publicKey,        crypto_box_PUBLICKEYBYTES );
+       memcpy( inContext->clientMagic,         cert->clientMagic,      kDNSCryptClientMagicLength );
+       
+       err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey );
        require_noerr( err, exit );
        
-       // Write query message.
+       inContext->certPtr      = NULL;
+       inContext->certLen      = 0;
+       inContext->msgLen       = 0;
        
-       check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSCryptBuildQuery
+//===========================================================================================================================
+
+static OSStatus        DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen );
+
+static OSStatus        DNSCryptBuildQuery( DNSCryptContext *inContext )
+{
+       OSStatus                                                err;
+       DNSCryptQueryHeader * const             hdr                     = (DNSCryptQueryHeader *) inContext->msgBuf;
+       uint8_t * const                                 queryPtr        = (uint8_t *)( hdr + 1 );
+       size_t                                                  queryLen;
+       size_t                                                  paddedQueryLen;
+       const uint8_t * const                   msgLimit        = inContext->msgBuf + sizeof( inContext->msgBuf );
+       const uint8_t *                                 padLimit;
+       uint8_t                                                 nonce[ crypto_box_NONCEBYTES ];
        
-       msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
-       err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
-               kDNSServiceClass_IN, &msgLen );
+       check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) );
+       
+       inContext->queryID = (uint16_t) Random32();
+       err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname,
+               inContext->qtype, kDNSServiceClass_IN, &queryLen );
        require_noerr( err, exit );
-       check( msgLen <= UINT16_MAX );
        
-       if( context->useTCP )
-       {
-               WriteBig16( context->msgBuf, msgLen );
-               sendLen = 2 + msgLen;
-       }
-       else
-       {
-               sendLen = msgLen;
-       }
-       
-       DNSQueryPrintPrologue( context );
+       padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
+       if( padLimit > msgLimit ) padLimit = msgLimit;
        
-       if( gDNSQuery_Verbose )
-       {
-               FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
-               FPrintF( stdout, "---\n" );
-       }
+       err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
+       require_noerr( err, exit );
        
-       if( context->useTCP )
-       {
-               // Create TCP socket.
-               
-               context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
-               err = map_socket_creation_errno( context->sock );
-               require_noerr( err, exit );
-               
-               err = SocketConnect( context->sock, &context->serverAddr, 5 );
-               require_noerr( err, exit );
-       }
-       else
-       {
-               // Create UDP socket.
-               
-               err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
-               require_noerr( err, exit );
-       }
+       memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
+       RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
+       memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
+       memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
        
-       context->sendTicks = UpTicks();
-       err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+       err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
+               paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
        require_noerr( err, exit );
        
-       if( context->timeLimitSecs == 0 ) goto exit;
+       memcpy( hdr->clientMagic,               inContext->clientMagic,         kDNSCryptClientMagicLength );
+       memcpy( hdr->clientPublicKey,   inContext->clientPublicKey,     crypto_box_PUBLICKEYBYTES );
+       memcpy( hdr->clientNonce,               nonce,                                          kDNSCryptHalfNonceLength );
        
-       err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
-               &context->readSource );
-       require_noerr( err, exit );
-       dispatch_resume( context->readSource );
+       inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
        
-       if( context->timeLimitSecs > 0 )
-       {
-               dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-                       Exit );
-       }
-       dispatch_main();
+exit:
+       return( err );
+}
+
+static OSStatus        DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen )
+{
+       OSStatus                err;
+       size_t                  paddedLen;
+       
+       require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr );
+       
+       paddedLen = inMsgLen + kDNSCryptMinPadLength +
+               arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) );
+       paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) );
+       if( paddedLen > inMaxLen ) paddedLen = inMaxLen;
+       
+       inMsgPtr[ inMsgLen ] = 0x80;
+       memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
+       
+       if( outPaddedLen ) *outPaddedLen = paddedLen;
+       err = kNoErr;
        
 exit:
-       if( context )
-       {
-               dispatch_source_forget( &context->readSource );
-               ForgetSocket( &context->sock );
-               free( context );
-       }
-       if( err ) exit( 1 );
+       return( err );
 }
 
 //===========================================================================================================================
-//     DNSQueryPrintPrologue
+//     DNSCryptSendQuery
 //===========================================================================================================================
 
-static void    DNSQueryPrintPrologue( const DNSQueryContext *inContext )
+static OSStatus        DNSCryptSendQuery( DNSCryptContext *inContext )
 {
-       const int               timeLimitSecs = inContext->timeLimitSecs;
+       OSStatus                        err;
+       SocketContext *         sockCtx;
+       SocketRef                       sock = kInvalidSocketRef;
        
-       FPrintF( stdout, "Name:        %s\n",           inContext->name );
-       FPrintF( stdout, "Type:        %s (%u)\n",      RecordTypeToString( inContext->type ), inContext->type );
-       FPrintF( stdout, "Server:      %##a\n",         &inContext->serverAddr );
-       FPrintF( stdout, "Transport:   %s\n",           inContext->useTCP ? "TCP" : "UDP" );
-       FPrintF( stdout, "Time limit:  " );
-       if( timeLimitSecs >= 0 )        FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
-       else                                            FPrintF( stdout, "∞\n" );
-       FPrintF( stdout, "Start time:  %{du:time}\n", NULL );
-       FPrintF( stdout, "---\n" );
+       check( inContext->msgLen > 0 );
+       check( !inContext->readSource );
+       
+       err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock );
+       require_noerr( err, exit );
+       
+       inContext->sendTicks = UpTicks();
+       err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
+       require_noerr( err, exit );
+       
+       err = SocketContextCreate( sock, inContext, &sockCtx );
+       require_noerr( err, exit );
+       sock = kInvalidSocketRef;
+       
+       err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
+               &inContext->readSource );
+       if( err ) ForgetSocketContext( &sockCtx );
+       require_noerr( err, exit );
+       
+       dispatch_resume( inContext->readSource );
+       
+exit:
+       ForgetSocket( &sock );
+       return( err );
 }
 
 //===========================================================================================================================
-//     DNSQueryReadHandler
+//     DNSCryptPrintCertificate
 //===========================================================================================================================
 
-static void    DNSQueryReadHandler( void *inContext )
+#define kCertTimeStrBufLen             32
+
+static char *  CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+
+static void    DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
 {
-       OSStatus                                        err;
-       struct timeval                          now;
-       const uint64_t                          nowTicks        = UpTicks();
-       DNSQueryContext * const         context         = (DNSQueryContext *) inContext;
+       time_t          startTime, endTime;
+       int                     extLen;
+       char            timeBuf[ kCertTimeStrBufLen ];
        
-       gettimeofday( &now, NULL );
+       check( inLen >= kDNSCryptCertMinimumLength );
        
-       if( context->useTCP )
+       startTime       = (time_t) ReadBig32( inCert->startTime );
+       endTime         = (time_t) ReadBig32( inCert->endTime );
+       
+       FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen );
+       FPrintF( stdout, "Cert Magic:    %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX );
+       FPrintF( stdout, "ES Version:    %u\n", ReadBig16( inCert->esVersion ) );
+       FPrintF( stdout, "Minor Version: %u\n", ReadBig16( inCert->minorVersion ) );
+       FPrintF( stdout, "Signature:     %H\n", inCert->signature, crypto_sign_BYTES / 2, INT_MAX );
+       FPrintF( stdout, "               %H\n", &inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX );
+       FPrintF( stdout, "Public Key:    %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX );
+       FPrintF( stdout, "Client Magic:  %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX );
+       FPrintF( stdout, "Serial:        %u\n", ReadBig32( inCert->serial ) );
+       FPrintF( stdout, "Start Time:    %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) );
+       FPrintF( stdout, "End Time:      %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) );
+       
+       if( inLen > kDNSCryptCertMinimumLength )
        {
-               if( !context->haveTCPLen )
-               {
-                       err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset );
-                       if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
-                       require_noerr( err, exit );
-                       
-                       context->msgOffset      = 0;
-                       context->msgLen         = ReadBig16( context->msgBuf );
-                       context->haveTCPLen     = true;
-                       if( context->msgLen <= sizeof( context->msgBuf ) )
-                       {
-                               context->msgPtr = context->msgBuf;
-                       }
-                       else
-                       {
-                               context->msgPtr = (uint8_t *) malloc( context->msgLen );
-                               require_action( context->msgPtr, exit, err = kNoMemoryErr );
-                       }
-               }
-               
-               err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset );
-               if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
-               require_noerr( err, exit );
-               context->msgOffset      = 0;
-               context->haveTCPLen     = false;
+               extLen = (int)( inLen - kDNSCryptCertMinimumLength );
+               FPrintF( stdout, "Extensions:    %.1H\n", inCert->extensions, extLen, extLen );
        }
-       else
+       FPrintF( stdout, "\n" );
+}
+
+static char *  CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
+{
+       struct tm *             tm;
+       
+       tm = localtime( &inTime );
+       if( !tm )
        {
-               sockaddr_ip             fromAddr;
-               
-               context->msgPtr = context->msgBuf;
-               err = SocketRecvFrom( context->sock, context->msgPtr, sizeof( context->msgBuf ), &context->msgLen, &fromAddr,
-                       sizeof( fromAddr ), NULL, NULL, NULL, NULL );
-               require_noerr( err, exit );
-               
-               check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+               dlogassert( "localtime() returned a NULL pointer.\n" );
+               *inBuffer = '\0';
        }
-       
-       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
-       FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
-       FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
-       FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
-       
-       if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+       else
        {
-               Exit( kExitReason_ReceivedResponse );
+               strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
        }
        
-exit:
-       if( err ) dispatch_source_forget( &context->readSource );
+       return( inBuffer );
 }
 
+#endif // DNSSDUTIL_INCLUDE_DNSCRYPT
+
 //===========================================================================================================================
-//     DNSQueryCancelHandler
+//     MDNSQueryCmd
 //===========================================================================================================================
 
-static void    DNSQueryCancelHandler( void *inContext )
+typedef struct
 {
-       DNSQueryContext * const         context = (DNSQueryContext *) inContext;
+       const char *                    qnameStr;                                                       // Name (QNAME) of the record being queried as a C string.
+       dispatch_source_t               readSourceV4;                                           // Read dispatch source for IPv4 socket.
+       dispatch_source_t               readSourceV6;                                           // Read dispatch source for IPv6 socket.
+       int                                             localPort;                                                      // The port number to which the sockets are bound.
+       int                                             receiveSecs;                                            // After send, the amount of time to spend receiving.
+       uint32_t                                ifIndex;                                                        // Index of the interface over which to send the query.
+       uint16_t                                qtype;                                                          // The type (QTYPE) of the record being queried.
+       Boolean                                 isQU;                                                           // True if the query is QU, i.e., requests unicast responses.
+       Boolean                                 allResponses;                                           // True if all mDNS messages received should be printed.
+       Boolean                                 printRawRData;                                          // True if RDATA should be printed as hexdumps.
+       Boolean                                 useIPv4;                                                        // True if the query should be sent via IPv4 multicast.
+       Boolean                                 useIPv6;                                                        // True if the query should be sent via IPv6 multicast.
+       char                                    ifName[ IF_NAMESIZE + 1 ];                      // Name of the interface over which to send the query.
+       uint8_t                                 qname[ kDomainNameLengthMax ];          // Buffer to hold the QNAME in DNS label format.
+       uint8_t                                 msgBuf[ kMDNSMessageSizeMax ];          // mDNS message buffer.
        
-       check( !context->readSource );
-       ForgetSocket( &context->sock );
-       if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr );
-       free( context );
-       dispatch_async_f( dispatch_get_main_queue(), NULL, Exit );
-}
+}      MDNSQueryContext;
 
-#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
-//===========================================================================================================================
-//     DNSCryptCmd
-//===========================================================================================================================
-
-#define kDNSCryptPort          443
-
-#define kDNSCryptMinPadLength                          8
-#define kDNSCryptMaxPadLength                          256
-#define kDNSCryptBlockSize                                     64
-#define kDNSCryptCertMinimumLength                     124
-#define kDNSCryptClientMagicLength                     8
-#define kDNSCryptResolverMagicLength           8
-#define kDNSCryptHalfNonceLength                       12
-#define kDNSCryptCertMagicLength                       4
-
-check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES );
-
-static const uint8_t           kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' };
-static const uint8_t           kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] =
-{
-       0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
-};
-
-typedef struct
-{
-       uint8_t         certMagic[ kDNSCryptCertMagicLength ];
-       uint8_t         esVersion[ 2 ];
-       uint8_t         minorVersion[ 2 ];
-       uint8_t         signature[ crypto_sign_BYTES ];
-       uint8_t         publicKey[ crypto_box_PUBLICKEYBYTES ];
-       uint8_t         clientMagic[ kDNSCryptClientMagicLength ];
-       uint8_t         serial[ 4 ];
-       uint8_t         startTime[ 4 ];
-       uint8_t         endTime[ 4 ];
-       uint8_t         extensions[ 1 ];        // Variably-sized extension data.
-       
-}      DNSCryptCert;
-
-check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength );
-
-typedef struct
-{
-       uint8_t         clientMagic[ kDNSCryptClientMagicLength ];
-       uint8_t         clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
-       uint8_t         clientNonce[ kDNSCryptHalfNonceLength ];
-       uint8_t         poly1305MAC[ 16 ];
-       
-}      DNSCryptQueryHeader;
-
-check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 );
-check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES );
-check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) ==
-       offsetof( DNSCryptQueryHeader, poly1305MAC ) );
-
-typedef struct
-{
-       uint8_t         resolverMagic[ kDNSCryptResolverMagicLength ];
-       uint8_t         clientNonce[ kDNSCryptHalfNonceLength ];
-       uint8_t         resolverNonce[ kDNSCryptHalfNonceLength ];
-       uint8_t         poly1305MAC[ 16 ];
-       
-}      DNSCryptResponseHeader;
-
-check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 );
-check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES );
-check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) ==
-       sizeof( DNSCryptResponseHeader ) );
-
-typedef struct
-{
-       sockaddr_ip                             serverAddr;
-       uint64_t                                sendTicks;
-       const char *                    providerName;
-       const char *                    qname;
-       const uint8_t *                 certPtr;
-       size_t                                  certLen;
-       dispatch_source_t               readSource;
-       size_t                                  msgLen;
-       int                                             timeLimitSecs;
-       uint16_t                                queryID;
-       uint16_t                                qtype;
-       Boolean                                 printRawRData;
-       uint8_t                                 serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ];
-       uint8_t                                 serverPublicKey[ crypto_box_PUBLICKEYBYTES ];
-       uint8_t                                 clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
-       uint8_t                                 clientSecretKey[ crypto_box_SECRETKEYBYTES ];
-       uint8_t                                 clientMagic[ kDNSCryptClientMagicLength ];
-       uint8_t                                 clientNonce[ kDNSCryptHalfNonceLength ];
-       uint8_t                                 nmKey[ crypto_box_BEFORENMBYTES ];
-       uint8_t                                 msgBuf[ 512 ];
-       
-}      DNSCryptContext;
-
-static void            DNSCryptReceiveCertHandler( void *inContext );
-static void            DNSCryptReceiveResponseHandler( void *inContext );
-static void            DNSCryptProceed( void *inContext );
-static OSStatus        DNSCryptProcessCert( DNSCryptContext *inContext );
-static OSStatus        DNSCryptBuildQuery( DNSCryptContext *inContext );
-static OSStatus        DNSCryptSendQuery( DNSCryptContext *inContext );
-static void            DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen );
+static void    MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
+static void    MDNSQueryReadHandler( void *inContext );
 
-static void    DNSCryptCmd( void )
+static void    MDNSQueryCmd( void )
 {
        OSStatus                                err;
-       DNSCryptContext *               context         = NULL;
-       size_t                                  writtenBytes;
-       size_t                                  totalBytes;
-       SocketContext *                 sockCtx;
-       SocketRef                               sock            = kInvalidSocketRef;
-       const char *                    ptr;
+       MDNSQueryContext *              context;
+       SocketRef                               sockV4 = kInvalidSocketRef;
+       SocketRef                               sockV6 = kInvalidSocketRef;
+       ssize_t                                 n;
+       const char *                    ifname;
+       size_t                                  msgLen;
+       unsigned int                    sendCount;
        
        // Check command parameters.
        
-       if( gDNSCrypt_TimeLimitSecs < -1 )
+       if( gMDNSQuery_ReceiveSecs < -1 )
        {
-               FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
+               FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
                err = kParamErr;
                goto exit;
        }
        
-       // Create context.
-       
-       context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+       context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
        require_action( context, exit, err = kNoMemoryErr );
        
-       context->providerName   = gDNSCrypt_ProviderName;
-       context->qname                  = gDNSCrypt_Name;
-       context->timeLimitSecs  = gDNSCrypt_TimeLimitSecs;
-       context->printRawRData  = gDNSCrypt_RawRData ? true : false;
-       
-       err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
-       require_noerr( err, exit );
+       context->qnameStr               = gMDNSQuery_Name;
+       context->receiveSecs    = gMDNSQuery_ReceiveSecs;
+       context->isQU                   = gMDNSQuery_IsQU                 ? true : false;
+       context->allResponses   = gMDNSQuery_AllResponses ? true : false;
+       context->printRawRData  = gMDNSQuery_RawRData     ? true : false;
+       context->useIPv4                = ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
+       context->useIPv6                = ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
        
-       err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags,
-               context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr );
-       if( err || ( *ptr != '\0' ) )
-       {
-               FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey );
-               goto exit;
-       }
-       else if( totalBytes != sizeof( context->serverPublicSignKey ) )
-       {
-               FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
-                       totalBytes, sizeof( context->serverPublicSignKey ) );
-               err = kSizeErr;
-               goto exit;
-       }
-       check( writtenBytes == totalBytes );
+       err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+       require_noerr_quiet( err, exit );
        
-       err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
-       require_noerr( err, exit );
-       if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
+       ifname = if_indextoname( context->ifIndex, context->ifName );
+       require_action( ifname, exit, err = kNameErr );
        
-       err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
+       err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype );
        require_noerr( err, exit );
        
-       // Write query message.
+       // Set up IPv4 socket.
        
-       context->queryID = (uint16_t) Random32();
-       err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName,
-               kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen );
-       require_noerr( err, exit );
+       if( context->useIPv4 )
+       {
+               err = CreateMulticastSocket( GetMDNSMulticastAddrV4(),
+                       gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+                       ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV4 );
+               require_noerr( err, exit );
+       }
        
-       // Create UDP socket.
+       // Set up IPv6 socket.
        
-       err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
-       require_noerr( err, exit );
+       if( context->useIPv6 )
+       {
+               err = CreateMulticastSocket( GetMDNSMulticastAddrV6(),
+                       gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+                       ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV6 );
+               require_noerr( err, exit );
+       }
        
-       // Send DNS query.
+       // Craft mDNS query message.
        
-       context->sendTicks = UpTicks();
-       err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
+       check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
+       err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
+               context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
        require_noerr( err, exit );
        
-       err = SocketContextCreate( sock, context, &sockCtx );
-       require_noerr( err, exit );
-       sock = kInvalidSocketRef;
+       // Print prologue.
        
-       err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
-               &context->readSource );
-       if( err ) ForgetSocketContext( &sockCtx );
-       require_noerr( err, exit );
+       MDNSQueryPrintPrologue( context );
        
-       dispatch_resume( context->readSource );
+       // Send mDNS query message.
        
-       if( context->timeLimitSecs > 0 )
+       sendCount = 0;
+       if( IsValidSocket( sockV4 ) )
        {
-               dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-                       Exit );
+               const struct sockaddr * const           mcastAddr4 = GetMDNSMulticastAddrV4();
+               
+               n = sendto( sockV4, context->msgBuf, msgLen, 0, mcastAddr4, SockAddrGetSize( mcastAddr4 ) );
+               err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
+               if( err )
+               {
+                       FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+                       ForgetSocket( &sockV4 );
+               }
+               else
+               {
+                       ++sendCount;
+               }
        }
-       dispatch_main();
+       if( IsValidSocket( sockV6 ) )
+       {
+               const struct sockaddr * const           mcastAddr6 = GetMDNSMulticastAddrV6();
+               
+               n = sendto( sockV6, context->msgBuf, msgLen, 0, mcastAddr6, SockAddrGetSize( mcastAddr6 ) );
+               err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
+               if( err )
+               {
+                       FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+                       ForgetSocket( &sockV6 );
+               }
+               else
+               {
+                       ++sendCount;
+               }
+       }
+       require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
        
-exit:
-       if( context ) free( context );
-       ForgetSocket( &sock );
-       if( err ) exit( 1 );
-}
-
-//===========================================================================================================================
-//     DNSCryptReceiveCertHandler
-//===========================================================================================================================
-
-static void    DNSCryptReceiveCertHandler( void *inContext )
-{
-       OSStatus                                        err;
-       struct timeval                          now;
-       const uint64_t                          nowTicks        = UpTicks();
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       DNSCryptContext * const         context         = (DNSCryptContext *) sockCtx->userContext;
-       const DNSHeader *                       hdr;
-       sockaddr_ip                                     fromAddr;
-       const uint8_t *                         ptr;
-       const uint8_t *                         txtPtr;
-       size_t                                          txtLen;
-       unsigned int                            answerCount, i;
-       uint8_t                                         targetName[ kDomainNameLengthMax ];
+       // If there's no wait period after the send, then exit.
        
-       gettimeofday( &now, NULL );
+       if( context->receiveSecs == 0 ) goto exit;
        
-       dispatch_source_forget( &context->readSource );
+       // Create dispatch read sources for socket(s).
        
-       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
-               &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
-       require_noerr( err, exit );
-       check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
-       
-       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
-       FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
-       FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
-       FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
-       
-       require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
-       
-       hdr = (DNSHeader *) context->msgBuf;
-       require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
-       
-       err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
-       require_noerr( err, exit );
-       
-       err = DomainNameFromString( targetName, context->providerName, NULL );
-       require_noerr( err, exit );
-       
-       answerCount = DNSHeaderGetAnswerCount( hdr );
-       for( i = 0; i < answerCount; ++i )
+       if( IsValidSocket( sockV4 ) )
        {
-               uint16_t                type;
-               uint16_t                class;
-               uint8_t                 name[ kDomainNameLengthMax ];
+               SocketContext *         sockCtx;
                
-               err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
-                       &ptr );
+               err = SocketContextCreate( sockV4, context, &sockCtx );
                require_noerr( err, exit );
+               sockV4 = kInvalidSocketRef;
                
-               if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
-               {
-                       break;
-               }
+               err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
+                       &context->readSourceV4 );
+               if( err ) ForgetSocketContext( &sockCtx );
+               require_noerr( err, exit );
+               
+               dispatch_resume( context->readSourceV4 );
        }
        
-       if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
+       if( IsValidSocket( sockV6 ) )
        {
-               FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
-               err = kSizeErr;
-               goto exit;
+               SocketContext *         sockCtx;
+               
+               err = SocketContextCreate( sockV6, context, &sockCtx );
+               require_noerr( err, exit );
+               sockV6 = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
+                       &context->readSourceV6 );
+               if( err ) ForgetSocketContext( &sockCtx );
+               require_noerr( err, exit );
+               
+               dispatch_resume( context->readSourceV6 );
        }
-       if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
+       
+       if( context->receiveSecs > 0 )
        {
-               FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
-               err = kSizeErr;
-               goto exit;
+               dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+                       Exit );
        }
-       
-       context->certLen = txtPtr[ 0 ];
-       context->certPtr = &txtPtr[ 1 ];
-       
-       dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
+       dispatch_main();
        
 exit:
-       if( err ) Exit( NULL );
+       ForgetSocket( &sockV4 );
+       ForgetSocket( &sockV6 );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     DNSCryptReceiveResponseHandler
+//     MDNSColliderCmd
 //===========================================================================================================================
 
-static void    DNSCryptReceiveResponseHandler( void *inContext )
+static void    _MDNSColliderCmdStopHandler( void *inContext, OSStatus inError );
+
+static void    MDNSColliderCmd( void )
 {
-       OSStatus                                                err;
-       struct timeval                                  now;
-       const uint64_t                                  nowTicks        = UpTicks();
-       SocketContext * const                   sockCtx         = (SocketContext *) inContext;
-       DNSCryptContext * const                 context         = (DNSCryptContext *) sockCtx->userContext;
-       sockaddr_ip                                             fromAddr;
-       DNSCryptResponseHeader *                hdr;
-       const uint8_t *                                 end;
-       uint8_t *                                               ciphertext;
-       uint8_t *                                               plaintext;
-       const uint8_t *                                 response;
-       uint8_t                                                 nonce[ crypto_box_NONCEBYTES ];
-       
-       gettimeofday( &now, NULL );
-       
-       dispatch_source_forget( &context->readSource );
-       
-       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
-               &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
-       require_noerr( err, exit );
-       check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+       OSStatus                                        err;
+       MDNSColliderRef                         collider = NULL;
+       uint8_t *                                       rdataPtr = NULL;
+       size_t                                          rdataLen = 0;
+       const char *                            ifname;
+       uint32_t                                        ifIndex;
+       MDNSColliderProtocols           protocols;
+       uint16_t                                        type;
+       char                                            ifName[ IF_NAMESIZE + 1 ];
+       uint8_t                                         name[ kDomainNameLengthMax ];
        
-       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
-       FPrintF( stdout, "Source:       %##a\n",                &context->serverAddr );
-       FPrintF( stdout, "Message size: %zu\n",                 context->msgLen );
-       FPrintF( stdout, "RTT:          %llu ms\n\n",   UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+       err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+       require_noerr_quiet( err, exit );
        
-       if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
+       ifname = if_indextoname( ifIndex, ifName );
+       if( !ifname )
        {
-               FPrintF( stderr, "DNSCrypt response is too short.\n" );
-               err = kSizeErr;
+               FPrintF( stderr, "error: Invalid interface name or index: %s\n", gInterface );
+               err = kNameErr;
                goto exit;
        }
        
-       hdr = (DNSCryptResponseHeader *) context->msgBuf;
-       
-       if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
+       err = DomainNameFromString( name, gMDNSCollider_Name, NULL );
+       if( err )
        {
-               FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
-                       hdr->resolverMagic,             kDNSCryptResolverMagicLength, INT_MAX,
-                       kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
-               err = kValueErr;
+               FPrintF( stderr, "error: Invalid record name: %s\n", gMDNSCollider_Name );
                goto exit;
        }
        
-       if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
+       err = RecordTypeFromArgString( gMDNSCollider_Type, &type );
+       require_noerr_quiet( err, exit );
+       
+       if( gMDNSCollider_RecordData )
        {
-               FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
-               err = kValueErr;
-               goto exit;
+               err = RecordDataFromArgString( gMDNSCollider_RecordData, &rdataPtr, &rdataLen );
+               require_noerr_quiet( err, exit );
        }
        
-       memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
+       err = MDNSColliderCreate( dispatch_get_main_queue(), &collider );
+       require_noerr( err, exit );
        
-       ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
-       memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
+       err = MDNSColliderSetProgram( collider, gMDNSCollider_Program );
+       if( err )
+       {
+               FPrintF( stderr, "error: Failed to set program string: '%s'\n", gMDNSCollider_Program );
+               goto exit;
+       }
        
-       plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
-       check( plaintext == ciphertext );
+       err = MDNSColliderSetRecord( collider, name, type, rdataPtr, rdataLen );
+       require_noerr( err, exit );
+       ForgetMem( &rdataPtr );
        
-       end = context->msgBuf + context->msgLen;
+       protocols = kMDNSColliderProtocol_None;
+       if( gMDNSCollider_UseIPv4 || !gMDNSCollider_UseIPv6 ) protocols |= kMDNSColliderProtocol_IPv4;
+       if( gMDNSCollider_UseIPv6 || !gMDNSCollider_UseIPv4 ) protocols |= kMDNSColliderProtocol_IPv6;
+       MDNSColliderSetProtocols( collider, protocols );
+       MDNSColliderSetInterfaceIndex( collider, ifIndex );
+       MDNSColliderSetStopHandler( collider, _MDNSColliderCmdStopHandler, collider );
        
-       err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
+       err = MDNSColliderStart( collider );
        require_noerr( err, exit );
        
-       response = plaintext + crypto_box_ZEROBYTES;
-       FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
-       Exit( kExitReason_ReceivedResponse );
+       dispatch_main();
        
 exit:
-       if( err ) Exit( NULL );
+       FreeNullSafe( rdataPtr );
+       CFReleaseNullSafe( collider );
+       if( err ) exit( 1 );
+}
+
+static void    _MDNSColliderCmdStopHandler( void *inContext, OSStatus inError )
+{
+       MDNSColliderRef const           collider = (MDNSColliderRef) inContext;
+       
+       CFRelease( collider );
+       exit( inError ? 1 : 0 );
 }
 
 //===========================================================================================================================
-//     DNSCryptProceed
+//     MDNSQueryPrintPrologue
 //===========================================================================================================================
 
-static void    DNSCryptProceed( void *inContext )
+static void    MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
 {
-       OSStatus                                        err;
-       DNSCryptContext * const         context = (DNSCryptContext *) inContext;
-       
-       err = DNSCryptProcessCert( context );
-       require_noerr_quiet( err, exit );
-       
-       err = DNSCryptBuildQuery( context );
-       require_noerr_quiet( err, exit );
-       
-       err = DNSCryptSendQuery( context );
-       require_noerr_quiet( err, exit );
+       const int               receiveSecs = inContext->receiveSecs;
        
-exit:
-       if( err ) Exit( NULL );
+       FPrintF( stdout, "Interface:        %d (%s)\n",         (int32_t) inContext->ifIndex, inContext->ifName );
+       FPrintF( stdout, "Name:             %s\n",                      inContext->qnameStr );
+       FPrintF( stdout, "Type:             %s (%u)\n",         RecordTypeToString( inContext->qtype ), inContext->qtype );
+       FPrintF( stdout, "Class:            IN (%s)\n",         inContext->isQU ? "QU" : "QM" );
+       FPrintF( stdout, "Local port:       %d\n",                      inContext->localPort );
+       FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
+               inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+       FPrintF( stdout, "Receive duration: " );
+       if( receiveSecs >= 0 )  FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
+       else                                    FPrintF( stdout, "∞\n" );
+       FPrintF( stdout, "Start time:       %{du:time}\n",      NULL );
 }
 
 //===========================================================================================================================
-//     DNSCryptProcessCert
+//     MDNSQueryReadHandler
 //===========================================================================================================================
 
-static OSStatus        DNSCryptProcessCert( DNSCryptContext *inContext )
+static void    MDNSQueryReadHandler( void *inContext )
 {
        OSStatus                                                err;
-       const DNSCryptCert * const              cert    = (DNSCryptCert *) inContext->certPtr;
-       const uint8_t * const                   certEnd = inContext->certPtr + inContext->certLen;
        struct timeval                                  now;
-       time_t                                                  startTimeSecs, endTimeSecs;
-       size_t                                                  signedLen;
-       uint8_t *                                               tempBuf;
-       unsigned long long                              tempLen;
-       
-       DNSCryptPrintCertificate( cert, inContext->certLen );
+       SocketContext * const                   sockCtx = (SocketContext *) inContext;
+       MDNSQueryContext * const                context = (MDNSQueryContext *) sockCtx->userContext;
+       size_t                                                  msgLen;
+       sockaddr_ip                                             fromAddr;
+       Boolean                                                 foundAnswer     = false;
        
-       if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
-       {
-               FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
-                       cert->certMagic,        kDNSCryptCertMagicLength, INT_MAX,
-                       kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
-               err = kValueErr;
-               goto exit;
-       }
+       gettimeofday( &now, NULL );
        
-       startTimeSecs   = (time_t) ReadBig32( cert->startTime );
-       endTimeSecs             = (time_t) ReadBig32( cert->endTime );
+       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
+               sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+       require_noerr( err, exit );
        
-       gettimeofday( &now, NULL );
-       if( now.tv_sec < startTimeSecs )
+       if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
        {
-               FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
-               err = kDateErr;
-               goto exit;
+               const uint8_t *                         ptr;
+               const DNSHeader * const         hdr = (DNSHeader *) context->msgBuf;
+               unsigned int                            rrCount, i;
+               uint16_t                                        type, class;
+               uint8_t                                         name[ kDomainNameLengthMax ];
+               
+               err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
+               require_noerr( err, exit );
+               
+               if( context->qname[ 0 ] == 0 )
+               {
+                       err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               
+               rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
+               for( i = 0; i < rrCount; ++i )
+               {
+                       err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
+                       require_noerr( err, exit );
+                       
+                       if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
+                               DomainNameEqual( name, context->qname ) )
+                       {
+                               foundAnswer = true;
+                               break;
+                       }
+               }
        }
-       if( now.tv_sec >= endTimeSecs )
+       if( context->allResponses || foundAnswer )
        {
-               FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
-               err = kDateErr;
-               goto exit;
+               FPrintF( stdout, "---\n" );
+               FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
+               FPrintF( stdout, "Source:       %##a\n",                &fromAddr );
+               FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
+                       msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
        }
        
-       signedLen = (size_t)( certEnd - cert->signature );
-       tempBuf = (uint8_t *) malloc( signedLen );
-       require_action( tempBuf, exit, err = kNoMemoryErr );
-       err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey );
-       free( tempBuf );
-       if( err )
-       {
-               FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
-               err = kAuthenticationErr;
-               goto exit;
-       }
-       
-       memcpy( inContext->serverPublicKey,     cert->publicKey,        crypto_box_PUBLICKEYBYTES );
-       memcpy( inContext->clientMagic,         cert->clientMagic,      kDNSCryptClientMagicLength );
-       
-       err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey );
-       require_noerr( err, exit );
-       
-       inContext->certPtr      = NULL;
-       inContext->certLen      = 0;
-       inContext->msgLen       = 0;
-       
 exit:
-       return( err );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     DNSCryptBuildQuery
+//     PIDToUUIDCmd
 //===========================================================================================================================
 
-static OSStatus        DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen );
-
-static OSStatus        DNSCryptBuildQuery( DNSCryptContext *inContext )
-{
-       OSStatus                                                err;
-       DNSCryptQueryHeader * const             hdr                     = (DNSCryptQueryHeader *) inContext->msgBuf;
-       uint8_t * const                                 queryPtr        = (uint8_t *)( hdr + 1 );
-       size_t                                                  queryLen;
-       size_t                                                  paddedQueryLen;
-       const uint8_t * const                   msgLimit        = inContext->msgBuf + sizeof( inContext->msgBuf );
-       const uint8_t *                                 padLimit;
-       uint8_t                                                 nonce[ crypto_box_NONCEBYTES ];
-       
-       check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) );
-       
-       inContext->queryID = (uint16_t) Random32();
-       err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname,
-               inContext->qtype, kDNSServiceClass_IN, &queryLen );
-       require_noerr( err, exit );
-       
-       padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
-       if( padLimit > msgLimit ) padLimit = msgLimit;
-       
-       err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
-       require_noerr( err, exit );
-       
-       memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
-       RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
-       memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
-       memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
-       
-       err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
-               paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
-       require_noerr( err, exit );
-       
-       memcpy( hdr->clientMagic,               inContext->clientMagic,         kDNSCryptClientMagicLength );
-       memcpy( hdr->clientPublicKey,   inContext->clientPublicKey,     crypto_box_PUBLICKEYBYTES );
-       memcpy( hdr->clientNonce,               nonce,                                          kDNSCryptHalfNonceLength );
-       
-       inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
-       
-exit:
-       return( err );
-}
-
-static OSStatus        DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen )
+static void    PIDToUUIDCmd( void )
 {
-       OSStatus                err;
-       size_t                  paddedLen;
-       
-       require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr );
-       
-       paddedLen = inMsgLen + kDNSCryptMinPadLength +
-               arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) );
-       paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) );
-       if( paddedLen > inMaxLen ) paddedLen = inMaxLen;
+       OSStatus                                                        err;
+       int                                                                     n;
+       struct proc_uniqidentifierinfo          info;
        
-       inMsgPtr[ inMsgLen ] = 0x80;
-       memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
+       n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
+       require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
        
-       if( outPaddedLen ) *outPaddedLen = paddedLen;
+       FPrintF( stdout, "%#U\n", info.p_uuid );
        err = kNoErr;
        
 exit:
-       return( err );
+       if( err ) exit( 1 );
 }
 
 //===========================================================================================================================
-//     DNSCryptSendQuery
+//     DNSServerCmd
 //===========================================================================================================================
 
-static OSStatus        DNSCryptSendQuery( DNSCryptContext *inContext )
+typedef struct DNSServerPrivate *              DNSServerRef;
+
+typedef struct
 {
-       OSStatus                        err;
-       SocketContext *         sockCtx;
-       SocketRef                       sock = kInvalidSocketRef;
-       
-       check( inContext->msgLen > 0 );
-       check( !inContext->readSource );
-       
-       err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock );
-       require_noerr( err, exit );
-       
-       inContext->sendTicks = UpTicks();
-       err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
-       require_noerr( err, exit );
-       
-       err = SocketContextCreate( sock, inContext, &sockCtx );
-       require_noerr( err, exit );
-       sock = kInvalidSocketRef;
-       
-       err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
-               &inContext->readSource );
-       if( err ) ForgetSocketContext( &sockCtx );
-       require_noerr( err, exit );
+       DNSServerRef                    server;                 // Reference to the DNS server.
+       dispatch_source_t               sigIntSource;   // Dispatch SIGINT source.
+       dispatch_source_t               sigTermSource;  // Dispatch SIGTERM source.
+       const char *                    domainOverride; // If non-NULL, the server is to use this domain instead of "d.test.".
+#if( TARGET_OS_DARWIN )
+       dispatch_source_t               processMonitor; // Process monitor source for process being followed, if any.
+       pid_t                                   followPID;              // PID of process being followed, if any. (If it exits, we exit).
+       Boolean                                 addedResolver;  // True if system DNS settings contains a resolver entry for server.
+#endif
+       Boolean                                 loopbackOnly;   // True if the server should be bound to the loopback interface.
        
-       dispatch_resume( inContext->readSource );
+}      DNSServerCmdContext;
+
+typedef enum
+{
+       kDNSServerEvent_Started = 1,
+       kDNSServerEvent_Stopped = 2
        
-exit:
-       ForgetSocket( &sock );
-       return( err );
-}
+}      DNSServerEventType;
 
-//===========================================================================================================================
-//     DNSCryptPrintCertificate
-//===========================================================================================================================
+typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
 
-#define kCertTimeStrBufLen             32
+CFTypeID       DNSServerGetTypeID( void );
+static OSStatus
+       DNSServerCreate(
+               dispatch_queue_t                inQueue,
+               DNSServerEventHandler_f inEventHandler,
+               void *                                  inEventContext,
+               unsigned int                    inResponseDelayMs,
+               uint32_t                                inDefaultTTL,
+               int                                             inPort,
+               Boolean                                 inLoopbackOnly,
+               const char *                    inDomain,
+               Boolean                                 inBadUDPMode,
+               DNSServerRef *                  outServer );
+static void    DNSServerStart( DNSServerRef inServer );
+static void    DNSServerStop( DNSServerRef inServer );
 
-static char *  CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+#define ForgetDNSServer( X )           ForgetCustomEx( X, DNSServerStop, CFRelease )
 
-static void    DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
+static void    DNSServerCmdContextFree( DNSServerCmdContext *inContext );
+static void    DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
+static void    DNSServerCmdSigIntHandler( void *inContext );
+static void    DNSServerCmdSigTermHandler( void *inContext );
+#if( TARGET_OS_DARWIN )
+static void    DNSServerCmdFollowedProcessHandler( void *inContext );
+#endif
+
+ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
+#define ds_ulog( LEVEL, ... )          ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
+
+static void    DNSServerCmd( void )
 {
-       time_t          startTime, endTime;
-       int                     extLen;
-       char            timeBuf[ kCertTimeStrBufLen ];
+       OSStatus                                        err;
+       DNSServerCmdContext *           context = NULL;
        
-       check( inLen >= kDNSCryptCertMinimumLength );
+       if( gDNSServer_Foreground )
+       {
+               LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+       }
        
-       startTime       = (time_t) ReadBig32( inCert->startTime );
-       endTime         = (time_t) ReadBig32( inCert->endTime );
+       err = CheckIntegerArgument( gDNSServer_ResponseDelayMs, "response delay (ms)", 0, INT_MAX );
+       require_noerr_quiet( err, exit );
        
-       FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen );
-       FPrintF( stdout, "Cert Magic:    %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX );
-       FPrintF( stdout, "ES Version:    %u\n", ReadBig16( inCert->esVersion ) );
-       FPrintF( stdout, "Minor Version: %u\n", ReadBig16( inCert->minorVersion ) );
-       FPrintF( stdout, "Signature:     %H\n", inCert->signature, crypto_sign_BYTES / 2, INT_MAX );
-       FPrintF( stdout, "               %H\n", &inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX );
-       FPrintF( stdout, "Public Key:    %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX );
-       FPrintF( stdout, "Client Magic:  %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX );
-       FPrintF( stdout, "Serial:        %u\n", ReadBig32( inCert->serial ) );
-       FPrintF( stdout, "Start Time:    %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) );
-       FPrintF( stdout, "End Time:      %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) );
+       err = CheckIntegerArgument( gDNSServer_DefaultTTL, "default TTL", 0, INT32_MAX );
+       require_noerr_quiet( err, exit );
        
-       if( inLen > kDNSCryptCertMinimumLength )
-       {
-               extLen = (int)( inLen - kDNSCryptCertMinimumLength );
-               FPrintF( stdout, "Extensions:    %.1H\n", inCert->extensions, extLen, extLen );
-       }
-       FPrintF( stdout, "\n" );
-}
-
-static char *  CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
-{
-       struct tm *             tm;
+       err = CheckIntegerArgument( gDNSServer_Port, "port number", -UINT16_MAX, UINT16_MAX );
+       require_noerr_quiet( err, exit );
        
-       tm = localtime( &inTime );
-       if( !tm )
+       context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       context->domainOverride = gDNSServer_DomainOverride;
+       context->loopbackOnly   = gDNSServer_LoopbackOnly ? true : false;
+       
+#if( TARGET_OS_DARWIN )
+       if( gDNSServer_FollowPID )
        {
-               dlogassert( "localtime() returned a NULL pointer.\n" );
-               *inBuffer = '\0';
+               err = StringToPID( gDNSServer_FollowPID, &context->followPID );
+               if( err || ( context->followPID < 0 ) )
+               {
+                       FPrintF( stderr, "error: Invalid follow PID: %s\n", gDNSServer_FollowPID );
+                       err = kParamErr;
+                       goto exit;
+               }
+               
+               err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+                       DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
+               require_noerr( err, exit );
+               dispatch_resume( context->processMonitor );
        }
        else
        {
-               strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
+               context->followPID = -1;
        }
+#endif
        
-       return( inBuffer );
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
+       require_noerr( err, exit );
+       dispatch_resume( context->sigIntSource );
+       
+       signal( SIGTERM, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
+       require_noerr( err, exit );
+       dispatch_resume( context->sigTermSource );
+       
+       err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context,
+               (unsigned int) gDNSServer_ResponseDelayMs, (uint32_t) gDNSServer_DefaultTTL, gDNSServer_Port, context->loopbackOnly,
+               context->domainOverride, gDNSServer_BadUDPMode ? true : false, &context->server );
+       require_noerr( err, exit );
+       
+       DNSServerStart( context->server );
+       dispatch_main();
+       
+exit:
+       FPrintF( stderr, "Failed to start DNS server: %#m\n", err );
+       if( context ) DNSServerCmdContextFree( context );
+       if( err ) exit( 1 );
 }
 
-#endif // DNSSDUTIL_INCLUDE_DNSCRYPT
+//===========================================================================================================================
+//     DNSServerCmdContextFree
+//===========================================================================================================================
+
+static void    DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+{
+       ForgetCF( &inContext->server );
+       dispatch_source_forget( &inContext->sigIntSource );
+       dispatch_source_forget( &inContext->sigTermSource );
+#if( TARGET_OS_DARWIN )
+       dispatch_source_forget( &inContext->processMonitor );
+#endif
+       free( inContext );
+}
 
 //===========================================================================================================================
-//     MDNSQueryCmd
+//     DNSServerCmdEventHandler
 //===========================================================================================================================
 
-typedef struct
+#if( TARGET_OS_DARWIN )
+static OSStatus        _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort );
+static OSStatus        _DNSServerCmdLoopbackResolverRemove( void );
+#endif
+
+static void    DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext )
 {
-       const char *                    qnameStr;                                                       // Name (QNAME) of the record being queried as a C string.
-       dispatch_source_t               readSourceV4;                                           // Read dispatch source for IPv4 socket.
-       dispatch_source_t               readSourceV6;                                           // Read dispatch source for IPv6 socket.
-       int                                             localPort;                                                      // The port number to which the sockets are bound.
-       int                                             receiveSecs;                                            // After send, the amount of time to spend receiving.
-       uint32_t                                ifIndex;                                                        // Index of the interface over which to send the query.
-       uint16_t                                qtype;                                                          // The type (QTYPE) of the record being queried.
-       Boolean                                 isQU;                                                           // True if the query is QU, i.e., requests unicast responses.
-       Boolean                                 allResponses;                                           // True if all mDNS messages received should be printed.
-       Boolean                                 printRawRData;                                          // True if RDATA should be printed as hexdumps.
-       Boolean                                 useIPv4;                                                        // True if the query should be sent via IPv4 multicast.
-       Boolean                                 useIPv6;                                                        // True if the query should be sent via IPv6 multicast.
-       char                                    ifName[ IF_NAMESIZE + 1 ];                      // Name of the interface over which to send the query.
-       uint8_t                                 qname[ kDomainNameLengthMax ];          // Buffer to hold the QNAME in DNS label format.
-       uint8_t                                 msgBuf[ 8940 ];                                         // Message buffer. 8940 is max size used by mDNSResponder.
-       
-}      MDNSQueryContext;
-
-static void    MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
-static void    MDNSQueryReadHandler( void *inContext );
-
-static void    MDNSQueryCmd( void )
-{
-       OSStatus                                err;
-       MDNSQueryContext *              context;
-       struct sockaddr_in              mcastAddr4;
-       struct sockaddr_in6             mcastAddr6;
-       SocketRef                               sockV4 = kInvalidSocketRef;
-       SocketRef                               sockV6 = kInvalidSocketRef;
-       ssize_t                                 n;
-       const char *                    ifNamePtr;
-       size_t                                  msgLen;
-       unsigned int                    sendCount;
-       
-       // Check command parameters.
-       
-       if( gMDNSQuery_ReceiveSecs < -1 )
-       {
-               FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
-               err = kParamErr;
-               goto exit;
-       }
-       
-       context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
-       
-       context->qnameStr               = gMDNSQuery_Name;
-       context->receiveSecs    = gMDNSQuery_ReceiveSecs;
-       context->isQU                   = gMDNSQuery_IsQU                 ? true : false;
-       context->allResponses   = gMDNSQuery_AllResponses ? true : false;
-       context->printRawRData  = gMDNSQuery_RawRData     ? true : false;
-       context->useIPv4                = ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
-       context->useIPv6                = ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
-       
-       err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
-       require_noerr_quiet( err, exit );
-       
-       ifNamePtr = if_indextoname( context->ifIndex, context->ifName );
-       require_action( ifNamePtr, exit, err = kNameErr );
-       
-       err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype );
-       require_noerr( err, exit );
-       
-       // Set up IPv4 socket.
-       
-       if( context->useIPv4 )
-       {
-               err = ServerSocketOpen( AF_INET, SOCK_DGRAM, IPPROTO_UDP,
-                       gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
-                       &context->localPort, kSocketBufferSize_DontSet, &sockV4 );
-               require_noerr( err, exit );
-               
-               err = SocketSetMulticastInterface( sockV4, ifNamePtr, context->ifIndex );
-               require_noerr( err, exit );
-               
-               err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
-               err = map_socket_noerr_errno( sockV4, err );
-               require_noerr( err, exit );
-               
-               memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
-               SIN_LEN_SET( &mcastAddr4 );
-               mcastAddr4.sin_family           = AF_INET;
-               mcastAddr4.sin_port                     = htons( kMDNSPort );
-               mcastAddr4.sin_addr.s_addr      = htonl( 0xE00000FB );  // The mDNS IPv4 multicast address is 224.0.0.251
-               
-               if( !context->isQU && ( context->localPort == kMDNSPort ) )
-               {
-                       err = SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
-                       require_noerr( err, exit );
-               }
-       }
-       
-       // Set up IPv6 socket.
+       OSStatus                                                err;
+       DNSServerCmdContext * const             context = (DNSServerCmdContext *) inContext;
        
-       if( context->useIPv6 )
+       if( inType == kDNSServerEvent_Started )
        {
-               err = ServerSocketOpen( AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
-                       gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
-                       &context->localPort, kSocketBufferSize_DontSet, &sockV6 );
-               require_noerr( err, exit );
-               
-               err = SocketSetMulticastInterface( sockV6, ifNamePtr, context->ifIndex );
-               require_noerr( err, exit );
-               
-               err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
-               err = map_socket_noerr_errno( sockV6, err );
-               require_noerr( err, exit );
-               
-               memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
-               SIN6_LEN_SET( &mcastAddr6 );
-               mcastAddr6.sin6_family  = AF_INET6;
-               mcastAddr6.sin6_port    = htons( kMDNSPort );
-               mcastAddr6.sin6_addr.s6_addr[  0 ] = 0xFF;      // mDNS IPv6 multicast address FF02::FB
-               mcastAddr6.sin6_addr.s6_addr[  1 ] = 0x02;
-               mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0xFB;
+       #if( TARGET_OS_DARWIN )
+               const int               port = (int) inEventData;
                
-               if( !context->isQU && ( context->localPort == kMDNSPort ) )
-               {
-                       err = SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
-                       require_noerr( err, exit );
-               }
-       }
-       
-       // Craft mDNS query message.
-       
-       check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
-       err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
-               context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
-       require_noerr( err, exit );
-       
-       // Print prologue.
-       
-       MDNSQueryPrintPrologue( context );
-       
-       // Send mDNS query message.
-       
-       sendCount = 0;
-       if( IsValidSocket( sockV4 ) )
-       {
-               n = sendto( sockV4, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) );
-               err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
+               err = _DNSServerCmdLoopbackResolverAdd( context->domainOverride ? context->domainOverride : "d.test.", port );
                if( err )
                {
-                       FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
-                       ForgetSocket( &sockV4 );
+                       ds_ulog( kLogLevelError, "Failed to add loopback resolver to DNS configuration for \"d.test.\" domain: %#m\n",
+                               err );
+                       if( context->loopbackOnly ) ForgetDNSServer( &context->server );
                }
                else
                {
-                       ++sendCount;
+                       context->addedResolver = true;
                }
+       #endif
        }
-       if( IsValidSocket( sockV6 ) )
+       else if( inType == kDNSServerEvent_Stopped )
        {
-               n = sendto( sockV6, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) );
-               err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
-               if( err )
+               const OSStatus          stopError = (OSStatus) inEventData;
+               
+               if( stopError ) ds_ulog( kLogLevelError, "The server stopped unexpectedly with error: %#m.\n", stopError );
+               
+               err = kNoErr;
+       #if( TARGET_OS_DARWIN )
+               if( context->addedResolver )
                {
-                       FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
-                       ForgetSocket( &sockV6 );
+                       err = _DNSServerCmdLoopbackResolverRemove();
+                       if( err )
+                       {
+                               ds_ulog( kLogLevelError, "Failed to remove loopback resolver from DNS configuration: %#m\n", err );
+                       }
+                       else
+                       {
+                               context->addedResolver = false;
+                       }
                }
-               else
+               else if( context->loopbackOnly )
                {
-                       ++sendCount;
+                       err = kUnknownErr;
                }
+       #endif
+               DNSServerCmdContextFree( context );
+               exit( ( stopError || err ) ? 1 : 0 );
        }
-       require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//     _DNSServerCmdLoopbackResolverAdd
+//===========================================================================================================================
+
+static OSStatus        _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort )
+{
+       OSStatus                                err;
+       SCDynamicStoreRef               store;
+       CFPropertyListRef               plist           = NULL;
+       CFStringRef                             key                     = NULL;
+       const uint32_t                  loopbackV4      = htonl( INADDR_LOOPBACK );
+       Boolean                                 success;
        
-       // If there's no wait period after the send, then exit.
+       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+       err = map_scerror( store );
+       require_noerr( err, exit );
        
-       if( context->receiveSecs == 0 ) goto exit;
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO="
+                       "["
+                               "%s"
+                       "]"
+                       "%kO="
+                       "["
+                               "%.4a"
+                               "%.16a"
+                       "]"
+                       "%kO=%i"
+               "}",
+               kSCPropNetDNSSupplementalMatchDomains,  inDomain,
+               kSCPropNetDNSServerAddresses,                   &loopbackV4, in6addr_loopback.s6_addr,
+               kSCPropNetDNSServerPort,                                inPort );
+       require_noerr( err, exit );
        
-       // Create dispatch read sources for socket(s).
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+               CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+       require_action( key, exit, err = kUnknownErr );
        
-       if( IsValidSocket( sockV4 ) )
-       {
-               SocketContext *         sockCtx;
-               
-               err = SocketContextCreate( sockV4, context, &sockCtx );
-               require_noerr( err, exit );
-               sockV4 = kInvalidSocketRef;
-               
-               err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
-                       &context->readSourceV4 );
-               if( err ) ForgetSocketContext( &sockCtx );
-               require_noerr( err, exit );
-               
-               dispatch_resume( context->readSourceV4 );
-       }
+       success = SCDynamicStoreSetValue( store, key, plist );
+       require_action( success, exit, err = kUnknownErr );
        
-       if( IsValidSocket( sockV6 ) )
-       {
-               SocketContext *         sockCtx;
-               
-               err = SocketContextCreate( sockV6, context, &sockCtx );
-               require_noerr( err, exit );
-               sockV6 = kInvalidSocketRef;
-               
-               err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
-                       &context->readSourceV6 );
-               if( err ) ForgetSocketContext( &sockCtx );
-               require_noerr( err, exit );
-               
-               dispatch_resume( context->readSourceV6 );
-       }
+exit:
+       CFReleaseNullSafe( store );
+       CFReleaseNullSafe( plist );
+       CFReleaseNullSafe( key );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _DNSServerCmdLoopbackResolverRemove
+//===========================================================================================================================
+
+static OSStatus        _DNSServerCmdLoopbackResolverRemove( void )
+{
+       OSStatus                                err;
+       SCDynamicStoreRef               store;
+       CFStringRef                             key = NULL;
+       Boolean                                 success;
        
-       if( context->receiveSecs > 0 )
-       {
-               dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-                       Exit );
-       }
-       dispatch_main();
+       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+       err = map_scerror( store );
+       require_noerr( err, exit );
+       
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+               CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+       require_action( key, exit, err = kUnknownErr );
+       
+       success = SCDynamicStoreRemoveValue( store, key );
+       require_action( success, exit, err = kUnknownErr );
        
 exit:
-       ForgetSocket( &sockV4 );
-       ForgetSocket( &sockV6 );
-       if( err ) exit( 1 );
+       CFReleaseNullSafe( store );
+       CFReleaseNullSafe( key );
+       return( err );
 }
+#endif
 
 //===========================================================================================================================
-//     MDNSQueryPrintPrologue
+//     DNSServerCmdSigIntHandler
 //===========================================================================================================================
 
-static void    MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
+static void    _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal );
+
+static void    DNSServerCmdSigIntHandler( void *inContext )
 {
-       const int               receiveSecs = inContext->receiveSecs;
-       
-       FPrintF( stdout, "Interface:        %d (%s)\n",         (int32_t) inContext->ifIndex, inContext->ifName );
-       FPrintF( stdout, "Name:             %s\n",                      inContext->qnameStr );
-       FPrintF( stdout, "Type:             %s (%u)\n",         RecordTypeToString( inContext->qtype ), inContext->qtype );
-       FPrintF( stdout, "Class:            IN (%s)\n",         inContext->isQU ? "QU" : "QM" );
-       FPrintF( stdout, "Local port:       %d\n",                      inContext->localPort );
-       FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
-               inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
-       FPrintF( stdout, "Receive duration: " );
-       if( receiveSecs >= 0 )  FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
-       else                                    FPrintF( stdout, "∞\n" );
-       FPrintF( stdout, "Start time:       %{du:time}\n",      NULL );
+       _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGINT );
 }
 
 //===========================================================================================================================
-//     MDNSQueryReadHandler
+//     DNSServerCmdSigTermHandler
 //===========================================================================================================================
 
-static void    MDNSQueryReadHandler( void *inContext )
+static void    DNSServerCmdSigTermHandler( void *inContext )
 {
-       OSStatus                                                err;
-       struct timeval                                  now;
-       SocketContext * const                   sockCtx = (SocketContext *) inContext;
-       MDNSQueryContext * const                context = (MDNSQueryContext *) sockCtx->userContext;
-       size_t                                                  msgLen;
-       sockaddr_ip                                             fromAddr;
-       Boolean                                                 foundAnswer     = false;
-       
-       gettimeofday( &now, NULL );
-       
-       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
-               sizeof( fromAddr ), NULL, NULL, NULL, NULL );
-       require_noerr( err, exit );
-       
-       if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
-       {
-               const uint8_t *                         ptr;
-               const DNSHeader * const         hdr = (DNSHeader *) context->msgBuf;
-               unsigned int                            rrCount, i;
-               uint16_t                                        type, class;
-               uint8_t                                         name[ kDomainNameLengthMax ];
-               
-               err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
-               require_noerr( err, exit );
-               
-               if( context->qname[ 0 ] == 0 )
-               {
-                       err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               
-               rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
-               for( i = 0; i < rrCount; ++i )
-               {
-                       err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
-                       require_noerr( err, exit );
-                       
-                       if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
-                               DomainNameEqual( name, context->qname ) )
-                       {
-                               foundAnswer = true;
-                               break;
-                       }
-               }
-       }
-       if( context->allResponses || foundAnswer )
-       {
-               FPrintF( stdout, "---\n" );
-               FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
-               FPrintF( stdout, "Source:       %##a\n",                &fromAddr );
-               FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
-                       msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
-       }
-       
-exit:
-       if( err ) exit( 1 );
+       _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGTERM );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//     PIDToUUIDCmd
+//     DNSServerCmdFollowedProcessHandler
 //===========================================================================================================================
 
-static void    PIDToUUIDCmd( void )
+static void    DNSServerCmdFollowedProcessHandler( void *inContext )
 {
-       OSStatus                                                        err;
-       int                                                                     n;
-       struct proc_uniqidentifierinfo          info;
-       
-       n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
-       require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
-       
-       FPrintF( stdout, "%#U\n", info.p_uuid );
-       err = kNoErr;
+       DNSServerCmdContext * const             context = (DNSServerCmdContext *) inContext;
        
-exit:
-       if( err ) exit( 1 );
+       if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( context, 0 );
 }
+#endif
 
 //===========================================================================================================================
-//     DNSServerCmd
+//     _DNSServerCmdExternalExit
 //===========================================================================================================================
 
-typedef uint32_t               DNSServerEventType;
-#define kDNSServerEvent_Started                1
-#define kDNSServerEvent_Stopped                2
-
-typedef struct DNSServerPrivate *              DNSServerRef;
+#define SignalNumberToString( X ) (            \
+       ( (X) == SIGINT )  ? "SIGINT"  :        \
+       ( (X) == SIGTERM ) ? "SIGTERM" :        \
+                                                "???" )
 
-typedef struct
+static void    _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal )
 {
-       DNSServerRef                    server;                         // Reference to the DNS server.
-       dispatch_source_t               sigIntSource;           // Dispatch SIGINT source.
-       dispatch_source_t               sigTermSource;          // Dispatch SIGTERM source.
+       dispatch_source_forget( &inContext->sigIntSource );
+       dispatch_source_forget( &inContext->sigTermSource );
 #if( TARGET_OS_DARWIN )
-       dispatch_source_t               processMonitor;         // Process monitor source for process being followed, if any.
-       pid_t                                   followPID;                      // PID of process being followed (we exit when they exit), if any.
-       Boolean                                 resolverRegistered;     // True if system DNS settings contains a resolver entry for server.
+       dispatch_source_forget( &inContext->processMonitor );
+       
+       if( inSignal == 0 )
+       {
+               ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
+       }
+       else
 #endif
-       Boolean                                 loopbackOnly;           // True if the server should be bound to the loopback interface.
-       Boolean                                 serverStarted;          // True if the server was successfully started.
-       Boolean                                 calledStop;                     // True if the server was explicitly stopped.
+       {
+               ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
+       }
        
-}      DNSServerCmdContext;
+       ForgetDNSServer( &inContext->server );
+}
 
-typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext );
+//===========================================================================================================================
+//     DNSServerCreate
+//===========================================================================================================================
+
+#define kDDotTestDomainName            (const uint8_t *) "\x01" "d" "\x04" "test"
+
+typedef struct DNSDelayedResponse              DNSDelayedResponse;
+struct DNSDelayedResponse
+{
+       DNSDelayedResponse *            next;
+       sockaddr_ip                                     destAddr;
+       uint64_t                                        targetTicks;
+       uint8_t *                                       msgPtr;
+       size_t                                          msgLen;
+};
+
+struct DNSServerPrivate
+{
+       CFRuntimeBase                           base;                           // CF object base.
+       uint8_t *                                       domain;                         // Parent domain of server's resource records.
+       dispatch_queue_t                        queue;                          // Queue for DNS server's events.
+       dispatch_source_t                       readSourceUDPv4;        // Read source for IPv4 UDP socket.
+       dispatch_source_t                       readSourceUDPv6;        // Read source for IPv6 UDP socket.
+       dispatch_source_t                       readSourceTCPv4;        // Read source for IPv4 TCP socket.
+       dispatch_source_t                       readSourceTCPv6;        // Read source for IPv6 TCP socket.
+       SocketRef                                       sockUDPv4;
+       SocketRef                                       sockUDPv6;
+       DNSServerEventHandler_f         eventHandler;
+       void *                                          eventContext;
+       DNSDelayedResponse *            responseList;
+       dispatch_source_t                       responseTimer;
+       unsigned int                            responseDelayMs;
+       uint32_t                                        defaultTTL;
+       uint32_t                                        serial;                         // Serial number for SOA record.
+       int                                                     port;                           // Port to use for receiving and sending DNS messages.
+       OSStatus                                        stopError;
+       Boolean                                         stopped;
+       Boolean                                         loopbackOnly;
+       Boolean                                         badUDPMode;                     // True if the server runs in Bad UDP mode.
+};
+
+static void    _DNSServerUDPReadHandler( void *inContext );
+static void    _DNSServerTCPReadHandler( void *inContext );
+static void    _DNSDelayedResponseFree( DNSDelayedResponse *inResponse );
+static void    _DNSDelayedResponseFreeList( DNSDelayedResponse *inList );
+
+CF_CLASS_DEFINE( DNSServer );
 
-CFTypeID       DNSServerGetTypeID( void );
 static OSStatus
        DNSServerCreate(
                dispatch_queue_t                inQueue,
                DNSServerEventHandler_f inEventHandler,
                void *                                  inEventContext,
-               int                                             inResponseDelayMs,
+               unsigned int                    inResponseDelayMs,
+               uint32_t                                inDefaultTTL,
+               int                                             inPort,
                Boolean                                 inLoopbackOnly,
-               DNSServerRef *                  outServer );
-static void    DNSServerStart( DNSServerRef inServer );
-static void    DNSServerStop( DNSServerRef inServer );
-
-static void    DNSServerCmdContextFree( DNSServerCmdContext *inContext );
-static void    DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext );
-static void    DNSServerCmdSigIntHandler( void *inContext );
-static void    DNSServerCmdSigTermHandler( void *inContext );
-#if( TARGET_OS_DARWIN )
-static void    DNSServerCmdFollowedProcessHandler( void *inContext );
-#endif
-
-ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
-#define ds_ulog( LEVEL, ... )          ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
-
-static void    DNSServerCmd( void )
+               const char *                    inDomain,
+               Boolean                                 inBadUDPMode,
+               DNSServerRef *                  outServer )
 {
-       OSStatus                                        err;
-       DNSServerCmdContext *           context;
-       
-       context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
-       
-       context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false;
+       OSStatus                        err;
+       DNSServerRef            obj = NULL;
        
-#if( TARGET_OS_DARWIN )
-       if( gDNSServer_FollowPID )
-       {
-               long long               value;
-               
-               err = StringToLongLong( gDNSServer_FollowPID, &value );
-               if( !err && ( value < 0 ) ) err = kValueErr;
-               if( err )
-               {
-                       FPrintF( stderr, "Invalid followPID argument \"%s\".\n", gDNSServer_FollowPID );
-                       goto exit;
-               }
-               context->followPID = (pid_t) value;
-               
-               err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
-                       DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
-               require_noerr( err, exit );
-               dispatch_resume( context->processMonitor );
-       }
-       else
-       {
-               context->followPID = -1;
-       }
-#endif
+       require_action_quiet( inDefaultTTL <= INT32_MAX, exit, err = kRangeErr );
        
-       signal( SIGINT, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
-       require_noerr( err, exit );
-       dispatch_resume( context->sigIntSource );
+       CF_OBJECT_CREATE( DNSServer, obj, err, exit );
        
-       signal( SIGTERM, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
-       require_noerr( err, exit );
-       dispatch_resume( context->sigTermSource );
+       ReplaceDispatchQueue( &obj->queue, inQueue );
+       obj->eventHandler               = inEventHandler;
+       obj->eventContext               = inEventContext;
+       obj->responseDelayMs    = inResponseDelayMs;
+       obj->defaultTTL                 = inDefaultTTL;
+       obj->port                               = inPort;
+       obj->loopbackOnly               = inLoopbackOnly;
+       obj->badUDPMode                 = inBadUDPMode;
        
-       if( gDNSServer_Foreground )
+       if( inDomain )
        {
-               LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+               err = StringToDomainName( inDomain, &obj->domain, NULL );
+               require_noerr_quiet( err, exit );
        }
-       
-       if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) )
+       else
        {
-               ds_ulog( kLogLevelError, "The default TTL %d provided by user is out-of-range. Will use %d instead.\n",
-                       gDNSServer_DefaultTTL, kDNSServerDefaultTTL );
-               gDNSServer_DefaultTTL = kDNSServerDefaultTTL;
+               err = DomainNameDup( kDDotTestDomainName, &obj->domain, NULL );
+               require_noerr_quiet( err, exit );
        }
        
-       err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs,
-               context->loopbackOnly, &context->server );
-       require_noerr( err, exit );
-       
-       DNSServerStart( context->server );
-       dispatch_main();
+       *outServer = obj;
+       obj = NULL;
+       err = kNoErr;
        
 exit:
-       ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err );
-       if( context ) DNSServerCmdContextFree( context );
-       if( err ) exit( 1 );
+       CFReleaseNullSafe( obj );
+       return( err );
 }
 
 //===========================================================================================================================
-//     DNSServerCmdContextFree
+//     _DNSServerFinalize
 //===========================================================================================================================
 
-static void    DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+static void    _DNSServerFinalize( CFTypeRef inObj )
 {
-       ForgetCF( &inContext->server );
-       dispatch_source_forget( &inContext->sigIntSource );
-       dispatch_source_forget( &inContext->sigTermSource );
-       dispatch_source_forget( &inContext->processMonitor );
-       free( inContext );
+       DNSServerRef const              me = (DNSServerRef) inObj;
+       
+       check( !me->readSourceUDPv4 );
+       check( !me->readSourceUDPv6 );
+       check( !me->readSourceTCPv4 );
+       check( !me->readSourceTCPv6 );
+       check( !me->responseTimer );
+       ForgetMem( &me->domain );
+       dispatch_forget( &me->queue );
 }
 
 //===========================================================================================================================
-//     DNSServerCmdEventHandler
+//     DNSServerStart
 //===========================================================================================================================
 
-#if( TARGET_OS_DARWIN )
-static OSStatus        _DNSServerCmdRegisterResolver( void );
-static OSStatus        _DNSServerCmdUnregisterResolver( void );
-#endif
+static void    _DNSServerStart( void *inContext );
+static void    _DNSServerStop( void *inContext, OSStatus inError );
 
-static void    DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext )
-{
-       DNSServerCmdContext * const             context = (DNSServerCmdContext *) inContext;
-#if( TARGET_OS_DARWIN )
-       OSStatus                                                err;
-#endif
-       
-       if( inType == kDNSServerEvent_Started )
-       {
-               context->serverStarted = true;
-       #if( TARGET_OS_DARWIN )
-               err = _DNSServerCmdRegisterResolver();
-               if( err )
-               {
-                       ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err );
-                       if( context->loopbackOnly ) exit( 1 );
-               }
-               else
-               {
-                       context->resolverRegistered = true;
-               }
-       #endif
-       }
-       else if( inType == kDNSServerEvent_Stopped )
-       {
-       #if( TARGET_OS_DARWIN )
-               if( context->resolverRegistered )
-               {
-                       err = _DNSServerCmdUnregisterResolver();
-                       if( err )
-                       {
-                               ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
-                       }
-                       else
-                       {
-                               context->resolverRegistered = false;
-                       }
-               }
-               
-               if( !context->calledStop )
-               {
-                       ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" );
-                       exit( 1 );
-               }
-       #endif
-               DNSServerCmdContextFree( context );
-       }
-}
-
-#if( TARGET_OS_DARWIN )
-//===========================================================================================================================
-//     _DNSServerCmdRegisterResolver
-//===========================================================================================================================
-
-static OSStatus        _DNSServerCmdRegisterResolver( void )
-{
-       OSStatus                                err;
-       SCDynamicStoreRef               store;
-       CFPropertyListRef               plist           = NULL;
-       CFStringRef                             key                     = NULL;
-       const uint32_t                  loopbackV4      = htonl( INADDR_LOOPBACK );
-       Boolean                                 success;
-       
-       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
-       err = map_scerror( store );
-       require_noerr( err, exit );
-       
-       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
-               "{"
-                       "%kO="
-                       "["
-                               "%s"
-                       "]"
-                       "%kO="
-                       "["
-                               "%.4a"
-                               "%.16a"
-                       "]"
-               "}",
-               kSCPropNetDNSSupplementalMatchDomains, "d.test.",
-               kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr );
-       require_noerr( err, exit );
-       
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
-               CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
-       require_action( key, exit, err = kUnknownErr );
-       
-       success = SCDynamicStoreSetValue( store, key, plist );
-       require_action( success, exit, err = kUnknownErr );
-       
-exit:
-       CFReleaseNullSafe( store );
-       CFReleaseNullSafe( plist );
-       CFReleaseNullSafe( key );
-       return( err );
-}
-
-//===========================================================================================================================
-//     _DNSServerCmdUnregisterResolver
-//===========================================================================================================================
-
-static OSStatus        _DNSServerCmdUnregisterResolver( void )
-{
-       OSStatus                                err;
-       SCDynamicStoreRef               store;
-       CFStringRef                             key = NULL;
-       Boolean                                 success;
-       
-       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
-       err = map_scerror( store );
-       require_noerr( err, exit );
-       
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
-               CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
-       require_action( key, exit, err = kUnknownErr );
-       
-       success = SCDynamicStoreRemoveValue( store, key );
-       require_action( success, exit, err = kUnknownErr );
-       
-exit:
-       CFReleaseNullSafe( store );
-       CFReleaseNullSafe( key );
-       return( err );
-}
-#endif
-
-//===========================================================================================================================
-//     DNSServerCmdSigIntHandler
-//===========================================================================================================================
-
-static void    _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal );
-
-static void    DNSServerCmdSigIntHandler( void *inContext )
-{
-       _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGINT );
-}
-
-//===========================================================================================================================
-//     DNSServerCmdSigTermHandler
-//===========================================================================================================================
-
-static void    DNSServerCmdSigTermHandler( void *inContext )
-{
-       _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGTERM );
-}
-
-#if( TARGET_OS_DARWIN )
-//===========================================================================================================================
-//     DNSServerCmdFollowedProcessHandler
-//===========================================================================================================================
-
-static void    DNSServerCmdFollowedProcessHandler( void *inContext )
-{
-       DNSServerCmdContext * const             context = (DNSServerCmdContext *) inContext;
-       
-       if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
-       {
-               _DNSServerCmdExternalExit( context, 0 );
-       }
-}
-#endif
-
-//===========================================================================================================================
-//     _DNSServerCmdExternalExit
-//===========================================================================================================================
-
-#define SignalNumberToString( X ) (            \
-       ( (X) == SIGINT )  ? "SIGINT"  :        \
-       ( (X) == SIGTERM ) ? "SIGTERM" :        \
-                                                "???" )
-
-static void    _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal )
-{
-       OSStatus                err;
-       
-#if( TARGET_OS_DARWIN )
-       if( inSignal == 0 )
-       {
-               ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
-       }
-       else
-#endif
-       {
-               ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
-       }
-       
-#if( TARGET_OS_DARWIN )
-       if( inContext->resolverRegistered )
-       {
-               err = _DNSServerCmdUnregisterResolver();
-               if( err )
-               {
-                       ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
-                       goto exit;
-               }
-               inContext->resolverRegistered = false;
-       }
-#endif
-       if( inContext->serverStarted )
-       {
-               DNSServerStop( inContext->server );
-               inContext->calledStop = true;
-       }
-       err = kNoErr;
-       
-exit:
-       exit( err ? 1 : 0 );
-}
-
-//===========================================================================================================================
-//     DNSServerCreate
-//===========================================================================================================================
-
-typedef struct DNSDelayedResponse              DNSDelayedResponse;
-struct DNSDelayedResponse
-{
-       DNSDelayedResponse *            next;
-       sockaddr_ip                                     clientAddr;
-       uint64_t                                        targetTicks;
-       uint8_t *                                       msgPtr;
-       size_t                                          msgLen;
-};
-
-#define DNSScheduledResponseFree( X )          do { ForgetMem( &(X)->msgPtr ) ; free( X ); } while( 0 )
-
-struct DNSServerPrivate
-{
-       CFRuntimeBase                           base;                           // CF object base.
-       dispatch_queue_t                        queue;                          // Queue for DNS server's events.
-       dispatch_source_t                       readSourceUDPv4;        // Read source for IPv4 UDP socket.
-       dispatch_source_t                       readSourceUDPv6;        // Read source for IPv6 UDP socket.
-       dispatch_source_t                       readSourceTCPv4;        // Read source for IPv4 TCP socket.
-       dispatch_source_t                       readSourceTCPv6;        // Read source for IPv6 TCP socket.
-       DNSServerEventHandler_f         eventHandler;
-       void *                                          eventContext;
-       DNSDelayedResponse *            responseList;
-       int                                                     responseDelayMs;
-       dispatch_source_t                       responseTimer;
-       Boolean                                         loopbackOnly;
-       Boolean                                         stopped;
-};
-
-CF_CLASS_DEFINE( DNSServer );
-
-static OSStatus
-       DNSServerCreate(
-               dispatch_queue_t                inQueue,
-               DNSServerEventHandler_f inEventHandler,
-               void *                                  inEventContext,
-               int                                             inResponseDelayMs,
-               Boolean                                 inLoopbackOnly,
-               DNSServerRef *                  outServer )
-{
-       OSStatus                        err;
-       DNSServerRef            obj = NULL;
-       
-       CF_OBJECT_CREATE( DNSServer, obj, err, exit );
-       
-       ReplaceDispatchQueue( &obj->queue, inQueue );
-       obj->eventHandler               = inEventHandler;
-       obj->eventContext               = inEventContext;
-       obj->responseDelayMs    = inResponseDelayMs;
-       if( inLoopbackOnly ) obj->loopbackOnly = true;
-       
-       *outServer = obj;
-       obj = NULL;
-       err = kNoErr;
-       
-exit:
-       CFReleaseNullSafe( obj );
-       return( err );
-}
-
-//===========================================================================================================================
-//     _DNSServerFinalize
-//===========================================================================================================================
-
-static void    _DNSServerFinalize( CFTypeRef inObj )
-{
-       DNSServerRef const              me = (DNSServerRef) inObj;
-       
-       check( !me->readSourceUDPv4 );
-       check( !me->readSourceUDPv6 );
-       check( !me->readSourceTCPv4 );
-       check( !me->readSourceTCPv6 );
-       check( !me->responseTimer );
-       dispatch_forget( &me->queue );
-}
-
-//===========================================================================================================================
-//     DNSServerStart
-//===========================================================================================================================
-
-static void    _DNSServerStart( void *inContext );
-static void    _DNSServerUDPReadHandler( void *inContext );
-static void    _DNSServerTCPReadHandler( void *inContext );
-
-static void    DNSServerStart( DNSServerRef me )
+static void    DNSServerStart( DNSServerRef me )
 {
        CFRetain( me );
        dispatch_async_f( me->queue, me, _DNSServerStart );
@@ -7255,16 +7320,23 @@ static void     DNSServerStart( DNSServerRef me )
 static void    _DNSServerStart( void *inContext )
 {
        OSStatus                                err;
+       struct timeval                  now;
        DNSServerRef const              me                      = (DNSServerRef) inContext;
        SocketRef                               sock            = kInvalidSocketRef;
        SocketContext *                 sockCtx         = NULL;
        const uint32_t                  loopbackV4      = htonl( INADDR_LOOPBACK );
+       int                                             year, month, day;
        
        // Create IPv4 UDP socket.
+       // Initially, me->port is the port requested by the user. If it's 0, then the user wants any available ephemeral port.
+       // If it's negative, then the user would like a port number equal to its absolute value, but will settle for any
+       // available ephemeral port, if it's not available. The actual port number that was used will be stored in me->port and
+       // used for the remaining sockets.
        
        err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL,
-               kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+               me->port, &me->port, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
        require_noerr( err, exit );
+       check( me->port > 0 );
        
        // Create read source for IPv4 UDP socket.
        
@@ -7276,12 +7348,13 @@ static void     _DNSServerStart( void *inContext )
                &me->readSourceUDPv4 );
        require_noerr( err, exit );
        dispatch_resume( me->readSourceUDPv4 );
+       me->sockUDPv4 = sockCtx->sock;
        sockCtx = NULL;
        
        // Create IPv6 UDP socket.
        
        err = _ServerSocketOpenEx2( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &in6addr_loopback : NULL,
-               kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+               me->port, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
        require_noerr( err, exit );
        
        // Create read source for IPv6 UDP socket.
@@ -7294,12 +7367,13 @@ static void     _DNSServerStart( void *inContext )
                &me->readSourceUDPv6 );
        require_noerr( err, exit );
        dispatch_resume( me->readSourceUDPv6 );
+       me->sockUDPv6 = sockCtx->sock;
        sockCtx = NULL;
        
        // Create IPv4 TCP socket.
        
        err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL,
-               kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+               me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
        require_noerr( err, exit );
        
        // Create read source for IPv4 TCP socket.
@@ -7317,7 +7391,7 @@ static void       _DNSServerStart( void *inContext )
        // Create IPv6 TCP socket.
        
        err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL,
-               kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+               me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
        require_noerr( err, exit );
        
        // Create read source for IPv6 TCP socket.
@@ -7332,46 +7406,62 @@ static void     _DNSServerStart( void *inContext )
        dispatch_resume( me->readSourceTCPv6 );
        sockCtx = NULL;
        
-       CFRetain( me );
-       if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, me->eventContext );
+       ds_ulog( kLogLevelInfo, "Server is using port %d.\n", me->port );
+       if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, (uintptr_t) me->port, me->eventContext );
+       
+       // Create the serial number for the server's SOA record in the YYYMMDDnn convention recommended by
+       // <https://tools.ietf.org/html/rfc1912#section-2.2> using the current time.
+       
+       gettimeofday( &now, NULL );
+       SecondsToYMD_HMS( ( INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay ) + now.tv_sec, &year, &month, &day,
+               NULL, NULL, NULL );
+       me->serial = (uint32_t)( ( year * 1000000 ) + ( month * 10000 ) + ( day * 100 ) + 1 );
        
 exit:
        ForgetSocket( &sock );
        if( sockCtx ) SocketContextRelease( sockCtx );
-       if( err ) DNSServerStop( me );
-       CFRelease( me );
+       if( err ) _DNSServerStop( me, err );
 }
 
 //===========================================================================================================================
 //     DNSServerStop
 //===========================================================================================================================
 
-static void    _DNSServerStop( void *inContext );
+static void    _DNSServerUserStop( void *inContext );
 static void    _DNSServerStop2( void *inContext );
 
 static void    DNSServerStop( DNSServerRef me )
 {
        CFRetain( me );
-       dispatch_async_f( me->queue, me, _DNSServerStop );
+       dispatch_async_f( me->queue, me, _DNSServerUserStop );
 }
 
-static void    _DNSServerStop( void *inContext )
+static void    _DNSServerUserStop( void *inContext )
 {
-       DNSServerRef const                      me = (DNSServerRef) inContext;
-       DNSDelayedResponse *            resp;
+       DNSServerRef const              me = (DNSServerRef) inContext;
        
-       dispatch_source_forget( &me->readSourceUDPv4 );
+       _DNSServerStop( me, kNoErr );
+       CFRelease( me );
+}
+
+static void    _DNSServerStop( void *inContext, OSStatus inError )
+{
+       DNSServerRef const              me = (DNSServerRef) inContext;
+       
+       me->stopError = inError;
+       dispatch_source_forget( &me->readSourceUDPv4 );
        dispatch_source_forget( &me->readSourceUDPv6 );
        dispatch_source_forget( &me->readSourceTCPv4 );
        dispatch_source_forget( &me->readSourceTCPv6 );
        dispatch_source_forget( &me->responseTimer );
+       me->sockUDPv4 = kInvalidSocketRef;
+       me->sockUDPv6 = kInvalidSocketRef;
        
-       while( ( resp = me->responseList ) != NULL )
+       if( me->responseList )
        {
-               me->responseList = resp->next;
-               DNSScheduledResponseFree( resp );
+               _DNSDelayedResponseFreeList( me->responseList );
+               me->responseList = NULL;
        }
-       
        dispatch_async_f( me->queue, me, _DNSServerStop2 );
 }
 
@@ -7382,30 +7472,62 @@ static void     _DNSServerStop2( void *inContext )
        if( !me->stopped )
        {
                me->stopped = true;
-               if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, me->eventContext );
+               if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, (uintptr_t) me->stopError, me->eventContext );
                CFRelease( me );
        }
        CFRelease( me );
 }
 
+//===========================================================================================================================
+//     _DNSDelayedResponseFree
+//===========================================================================================================================
+
+static void    _DNSDelayedResponseFree( DNSDelayedResponse *inResponse )
+{
+       ForgetMem( &inResponse->msgPtr );
+       free( inResponse );
+}
+
+//===========================================================================================================================
+//     _DNSDelayedResponseFreeList
+//===========================================================================================================================
+
+static void    _DNSDelayedResponseFreeList( DNSDelayedResponse *inList )
+{
+       DNSDelayedResponse *            response;
+       
+       while( ( response = inList ) != NULL )
+       {
+               inList = response->next;
+               _DNSDelayedResponseFree( response );
+       }
+}
+
 //===========================================================================================================================
 //     _DNSServerUDPReadHandler
 //===========================================================================================================================
 
 static OSStatus
        _DNSServerAnswerQuery(
+               DNSServerRef    inServer,
                const uint8_t * inQueryPtr,
                size_t                  inQueryLen,
                Boolean                 inForTCP,
                uint8_t **              outResponsePtr,
                size_t *                outResponseLen );
 
-#define _DNSServerAnswerQueryForUDP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
-       _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+#define _DNSServerAnswerQueryForUDP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+       _DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
 
-#define _DNSServerAnswerQueryForTCP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
-       _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+#define _DNSServerAnswerQueryForTCP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+       _DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
 
+static OSStatus
+       _DNSServerScheduleDelayedResponse(
+               DNSServerRef                    inServer,
+               const struct sockaddr * inDestAddr,
+               uint8_t *                               inMsgPtr,
+               size_t                                  inMsgLen );
 static void    _DNSServerUDPDelayedSend( void *inContext );
 
 static void    _DNSServerUDPReadHandler( void *inContext )
@@ -7442,44 +7564,16 @@ static void     _DNSServerUDPReadHandler( void *inContext )
        
        // Create response.
        
-       err = _DNSServerAnswerQueryForUDP( msg, (size_t) n, &responsePtr, &responseLen );
+       err = _DNSServerAnswerQueryForUDP( me, msg, (size_t) n, &responsePtr, &responseLen );
        require_noerr_quiet( err, exit );
        
        // Schedule response.
        
        if( me->responseDelayMs > 0 )
        {
-               DNSDelayedResponse *            resp;
-               DNSDelayedResponse **           ptr;
-               DNSDelayedResponse *            newResp;
-               
-               newResp = (DNSDelayedResponse *) calloc( 1, sizeof( *newResp ) );
-               require_action( newResp, exit, err = kNoMemoryErr );
-               
-               SockAddrCopy( &clientAddr, &newResp->clientAddr );
-               newResp->targetTicks    = UpTicks() + MillisecondsToUpTicks( (uint64_t) me->responseDelayMs );
-               newResp->msgLen                 = responseLen;
-               newResp->msgPtr                 = responsePtr;
+               err = _DNSServerScheduleDelayedResponse( me, &clientAddr.sa, responsePtr, responseLen );
+               require_noerr( err, exit );
                responsePtr = NULL;
-               
-               for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next )
-               {
-                       if( newResp->targetTicks < resp->targetTicks ) break;
-               }
-               
-               newResp->next = resp;
-               *ptr = newResp;
-               
-               if( me->responseList == newResp )
-               {
-                       dispatch_source_forget( &me->responseTimer );
-                       
-                       err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
-                               ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue,
-                               _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
-                       require_noerr( err, exit );
-                       dispatch_resume( me->responseTimer );
-               }
        }
        else
        {
@@ -7495,56 +7589,100 @@ exit:
        return;
 }
 
+static OSStatus
+       _DNSServerScheduleDelayedResponse(
+               DNSServerRef                    me,
+               const struct sockaddr * inDestAddr,
+               uint8_t *                               inMsgPtr,
+               size_t                                  inMsgLen )
+{
+       OSStatus                                        err;
+       DNSDelayedResponse *            response;
+       DNSDelayedResponse **           responsePtr;
+       DNSDelayedResponse *            newResponse;
+       uint64_t                                        targetTicks;
+       
+       targetTicks = UpTicks() + MillisecondsToUpTicks( me->responseDelayMs );
+       
+       newResponse = (DNSDelayedResponse *) calloc( 1, sizeof( *newResponse ) );
+       require_action( newResponse, exit, err = kNoMemoryErr );
+       
+       if( !me->responseList || ( targetTicks < me->responseList->targetTicks ) )
+       {
+               dispatch_source_forget( &me->responseTimer );
+               
+               err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
+                       ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue, _DNSServerUDPDelayedSend,
+                       NULL, me, &me->responseTimer );
+               require_noerr( err, exit );
+               dispatch_resume( me->responseTimer );
+       }
+       
+       SockAddrCopy( inDestAddr, &newResponse->destAddr );
+       newResponse->targetTicks        = targetTicks;
+       newResponse->msgPtr                     = inMsgPtr;
+       newResponse->msgLen                     = inMsgLen;
+       
+       for( responsePtr = &me->responseList; ( response = *responsePtr ) != NULL; responsePtr = &response->next )
+       {
+               if( newResponse->targetTicks < response->targetTicks ) break;
+       }
+       newResponse->next = response;
+       *responsePtr = newResponse;
+       newResponse = NULL;
+       err = kNoErr;
+       
+exit:
+       if( newResponse ) _DNSDelayedResponseFree( newResponse );
+       return( err );
+}
+
 static void    _DNSServerUDPDelayedSend( void *inContext )
 {
        OSStatus                                        err;
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       DNSServerRef const                      me                      = (DNSServerRef) sockCtx->userContext;
-       DNSDelayedResponse *            resp;
+       DNSServerRef const                      me                      = (DNSServerRef) inContext;
+       DNSDelayedResponse *            response;
+       SocketRef                                       sock;
        ssize_t                                         n;
        uint64_t                                        nowTicks;
+       uint64_t                                        remainingNs;
        DNSDelayedResponse *            freeList        = NULL;
        
        dispatch_source_forget( &me->responseTimer );
        
        nowTicks = UpTicks();
-       while( ( resp = me->responseList ) != NULL )
+       while( ( ( response = me->responseList ) != NULL ) && ( response->targetTicks <= nowTicks ) )
        {
-               if( resp->targetTicks > nowTicks ) break;
-               me->responseList = resp->next;
+               me->responseList = response->next;
                
                ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}",
-                       resp->msgLen, resp->msgPtr, resp->msgLen );
+                       response->msgLen, response->msgPtr, response->msgLen );
                
-               n = sendto( sockCtx->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->clientAddr.sa,
-                       SockAddrGetSize( &resp->clientAddr ) );
-               err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) resp->msgLen, n );
+               sock = ( response->destAddr.sa.sa_family == AF_INET ) ? me->sockUDPv4 : me->sockUDPv6;
+               n = sendto( sock, (char *) response->msgPtr, response->msgLen, 0, &response->destAddr.sa,
+                       SockAddrGetSize( &response->destAddr ) );
+               err = map_socket_value_errno( sock, n == (ssize_t) response->msgLen, n );
                check_noerr( err );
                
-               resp->next      = freeList;
-               freeList        = resp;
+               response->next  = freeList;
+               freeList                = response;
                nowTicks = UpTicks();
        }
        
-       if( ( resp = me->responseList ) != NULL )
+       if( response )
        {
-               uint64_t                remainingNs;
-               
-               remainingNs = UpTicksToNanoseconds( resp->targetTicks - nowTicks );
+               check( response->targetTicks > nowTicks );
+               remainingNs = UpTicksToNanoseconds( response->targetTicks - nowTicks );
                if( remainingNs > INT64_MAX ) remainingNs = INT64_MAX;
                
                err = DispatchTimerCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) remainingNs ), DISPATCH_TIME_FOREVER, 0,
-                       me->queue, _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
+                       me->queue, _DNSServerUDPDelayedSend, NULL, me, &me->responseTimer );
                require_noerr( err, exit );
                dispatch_resume( me->responseTimer );
        }
        
 exit:
-       while( ( resp = freeList ) != NULL )
-       {
-               freeList = resp->next;
-               DNSScheduledResponseFree( resp );
-       }
+       if( freeList ) _DNSDelayedResponseFreeList( freeList );
 }
 
 //===========================================================================================================================
@@ -7553,12 +7691,25 @@ exit:
 
 #define kLabelPrefix_Alias                     "alias"
 #define kLabelPrefix_AliasTTL          "alias-ttl"
-#define kLabelPrefix_Count                     "count"
-#define kLabelPrefix_TTL                       "ttl"
+#define kLabelPrefix_Count                     "count-"
+#define kLabelPrefix_Tag                       "tag-"
+#define kLabelPrefix_TTL                       "ttl-"
 #define kLabel_IPv4                                    "ipv4"
 #define kLabel_IPv6                                    "ipv6"
+#define kLabelPrefix_SRV                       "srv-"
 
 #define kMaxAliasTTLCount              ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
+#define kMaxParsedSRVCount             ( kDomainNameLengthMax / ( 1 + sizeof_string( kLabelPrefix_SRV ) + 5 ) )
+
+typedef struct
+{
+       uint16_t                        priority;       // Priority from SRV label.
+       uint16_t                        weight;         // Weight from SRV label.
+       uint16_t                        port;           // Port number from SRV label.
+       uint16_t                        targetLen;      // Total length of the target hostname labels that follow an SRV label.
+       const uint8_t *         targetPtr;      // Pointer to the target hostname embedded in a domain name.
+       
+}      ParsedSRV;
 
 static OSStatus
        _DNSServerInitializeResponseMessage(
@@ -7570,14 +7721,37 @@ static OSStatus
                unsigned int    inQClass );
 static OSStatus
        _DNSServerAnswerQueryDynamically(
+               DNSServerRef    inServer,
                const uint8_t * inQName,
                unsigned int    inQType,
                unsigned int    inQClass,
                Boolean                 inForTCP,
                DataBuffer *    inDB );
+static Boolean
+       _DNSServerNameIsSRVName(
+               DNSServerRef            inServer,
+               const uint8_t *         inName,
+               const uint8_t **        outDomainPtr,
+               size_t *                        outDomainLen,
+               ParsedSRV                       inSRVArray[ kMaxParsedSRVCount ],
+               size_t *                        outSRVCount );
+static Boolean
+       _DNSServerNameIsHostname(
+               DNSServerRef    inServer,
+               const uint8_t * inName,
+               uint32_t *              outAliasCount,
+               uint32_t                inAliasTTLs[ kMaxAliasTTLCount ],
+               size_t *                outAliasTTLCount,
+               unsigned int *  outCount,
+               unsigned int *  outRandCount,
+               uint32_t *              outTTL,
+               Boolean *               outHasA,
+               Boolean *               outHasAAAA,
+               Boolean *               outHasSOA );
 
 static OSStatus
        _DNSServerAnswerQuery(
+               DNSServerRef                    me,
                const uint8_t * const   inQueryPtr,
                const size_t                    inQueryLen,
                Boolean                                 inForTCP,
@@ -7629,10 +7803,11 @@ static OSStatus
        if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired;
        DNSFlagsSetOpCode( rflags, kDNSOpCode_Query );
        
+       if( me->badUDPMode && !inForTCP ) msgID = (uint16_t)( msgID + 1 );
        err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
        require_noerr( err, exit );
        
-       err = _DNSServerAnswerQueryDynamically( qname, qtype, qclass, inForTCP, &dataBuf );
+       err = _DNSServerAnswerQueryDynamically( me, qname, qtype, qclass, inForTCP, &dataBuf );
        if( err )
        {
                DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure );
@@ -7657,9 +7832,8 @@ static OSStatus
                unsigned int    inQType,
                unsigned int    inQClass )
 {
-       OSStatus                                        err;
-       DNSHeader                                       header;
-       DNSQuestionFixedFields          fields;
+       OSStatus                err;
+       DNSHeader               header;
        
        DataBuffer_Reset( inDB );
        
@@ -7671,11 +7845,8 @@ static OSStatus
        err = DataBuffer_Append( inDB, &header, sizeof( header ) );
        require_noerr( err, exit );
        
-       err = DataBuffer_Append( inDB, inQName, DomainNameLength( inQName ) );
-       require_noerr( err, exit );
-       
-       DNSQuestionFixedFieldsInit( &fields, inQType, inQClass );
-       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+       err = _DataBuffer_AppendDNSQuestion( inDB, inQName, DomainNameLength( inQName ), (uint16_t) inQType,
+               (uint16_t) inQClass );
        require_noerr( err, exit );
        
 exit:
@@ -7684,184 +7855,72 @@ exit:
 
 static OSStatus
        _DNSServerAnswerQueryDynamically(
+               DNSServerRef                    me,
                const uint8_t * const   inQName,
                const unsigned int              inQType,
                const unsigned int              inQClass,
                const Boolean                   inForTCP,
                DataBuffer * const              inDB )
 {
-       OSStatus                        err;                                                    // General-purpose error variable.
-       const uint8_t *         labelPtr;                                               // QNAME label pointer.
-       size_t                          labelLen;                                               // QNAME label length.
-       DNSHeader *                     hdr;                                                    // Response header pointer.
-       unsigned int            flags;                                                  // Response header flags.
-       unsigned int            rcode;                                                  // Response header response code.
-       unsigned int            answerCount             = 0;                    // Number of answers contained in response.
-       int32_t                         aliasCount              = -1;                   // Arg from "alias" label. Valid values are in [2 .. 2^31 - 1].
-       int                                     count                   = -1;                   // First arg from "count" label. Valid values are in [1 .. 255].
-       int                                     randCount               = -1;                   // Second arg from "count" label. Valid values are in [1 .. 255].
-       int32_t                         ttl                             = -1;                   // Arg from "ttl" label. Valid values are in [0 .. 2^31 - 1].
-       uint32_t                        aliasTTLs[ kMaxAliasTTLCount ]; // Args from "alias-ttl" label. Valid values are in [0 .. 2^31 - 1].
-       int                                     i;                                                              // General-purpose array index.
-       Boolean                         useAliasTTLs    = false;                // True if QNAME contained a valid "alias-ttl" label.
-       Boolean                         nameExists              = false;                // True if name specified by QNAME exists.
-       Boolean                         nameHasA                = false;                // True if name specified by QNAME has an A record.
-       Boolean                         nameHasAAAA             = false;                // True if name specified by QNAME has a AAAA record.
-       Boolean                         notImplemented  = false;                // True if the kind of the query is not supported.
-       Boolean                         truncated               = false;                // True if the response message is truncated.
-       uint8_t                         namePtr[ 2 ];                                   // Name compression pointer.
-       
-       if( inQClass != kDNSServiceClass_IN )
-       {
-               notImplemented = true;
-               goto done;
-       }
-       
-       for( labelPtr = inQName; ( labelLen = *labelPtr ) != 0; labelPtr += ( 1 + labelLen ) )
-       {
-               const char * const              labelStr = (const char *) &labelPtr[ 1 ];
-               const char *                    next;
-               long long                               arg;
-               int                                             n;
-               
-               require_action( labelLen <= kDomainNameLengthMax, exit, err = kUnexpectedErr );
-               
-               // Check if the first label is a valid alias TTL sequence label.
-               
-               if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_AliasTTL ) == 0 ) )
-               {
-                       const char *                    src                     = &labelStr[ sizeof_string( kLabelPrefix_AliasTTL ) ];
-                       const char * const              end                     = &labelStr[ labelLen ];
-                       int                                             argCount        = 0;
-                       
-                       while( src < end )
-                       {
-                               n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
-                               if( n != 1 ) break;
-                               if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1).
-                               aliasTTLs[ argCount++ ] = (uint32_t) arg;
-                               src = next;
-                       }
-                       if( ( argCount > 0 ) && ( src == end ) )
-                       {
-                               aliasCount              = argCount;
-                               useAliasTTLs    = true;
-                               continue;
-                       }
-               }
-               
-               // Check if the first label is a valid alias label.
-               
-               if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Alias ) == 0 ) )
-               {
-                       const char *                    src = &labelStr[ sizeof_string( kLabelPrefix_Alias ) ];
-                       const char * const              end = &labelStr[ labelLen ];
-                       
-                       if( src == end )
-                       {
-                               aliasCount = 1;
-                               continue;
-                       }
-                       
-                       n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
-                       if( ( n == 1 ) && ( next == end ) )
-                       {
-                               if( ( arg < 2 ) || ( arg > INT32_MAX ) ) break; // Alias count must be >= 2 and <= (2^31 - 1).
-                               aliasCount = (int32_t) arg;
-                               continue;
-                       }
-               }
-               
-               // Check if the label is a valid count label.
-               
-               if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Count ) == 0  )
-               {
-                       const char *                    src = &labelStr[ sizeof_string( kLabelPrefix_Count ) ];
-                       const char * const              end = &labelStr[ labelLen ];
-                       
-                       n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
-                       if( n == 1 )
-                       {
-                               if( count > 0 ) break;                                          // Count cannot be specified more than once.
-                               if( ( arg < 1 ) || ( arg > 255 ) ) break;       // Count must be >= 1 and <= 255.
-                               count = (int) arg;
-                               
-                               src = next;
-                               if( src < end )
-                               {
-                                       n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
-                                       if( ( n != 1 ) || ( next != end ) ) break;
-                                       if( ( arg < count ) || ( arg > 255 ) ) break;   // Rand count must be >= count and <= 255.
-                                       randCount = (int) arg;
-                               }
-                               continue;
-                       }
-               }
-               
-               // Check if the label is a valid tag label.
-               
-               if( strnicmp_prefix( labelStr, labelLen, "tag-" ) == 0  ) continue;
-               
-               // Check if the label is a valid TTL label.
-               
-               if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_TTL ) == 0  )
-               {
-                       const char *                    src = &labelStr[ sizeof_string( kLabelPrefix_TTL ) ];
-                       const char * const              end = &labelStr[ labelLen ];
-                       
-                       n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
-                       if( ( n == 1 ) && ( next == end ) )
-                       {
-                               if( ttl >= 0 ) break;                                                   // TTL cannot be specified more than once.
-                               if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1).
-                               ttl = (int32_t) arg;
-                               continue;
-                       }
-               }
-               
-               // Check if the label is a valid IPv4 or IPv6 label.
+       OSStatus                                        err;
+       DNSHeader *                                     hdr;
+       unsigned int                            flags, rcode;
+       uint32_t                                        aliasCount, i;
+       uint32_t                                        aliasTTLs[ kMaxAliasTTLCount ];
+       size_t                                          aliasTTLCount;
+       unsigned int                            addrCount, randCount;
+       uint32_t                                        ttl;
+       ParsedSRV                                       srvArray[ kMaxParsedSRVCount ];
+       size_t                                          srvCount;
+       const uint8_t *                         srvDomainPtr;
+       size_t                                          srvDomainLen;
+       unsigned int                            answerCount;
+       Boolean                                         notImplemented, truncated;
+       Boolean                                         useAliasTTLs, nameExists, nameHasA, nameHasAAAA, nameHasSRV, nameHasSOA;
+       uint8_t                                         namePtr[ 2 ];
+       DNSRecordFixedFields            fields;
+       
+       answerCount     = 0;
+       truncated       = false;
+       nameExists      = false;
+       require_action_quiet( inQClass == kDNSServiceClass_IN, done, notImplemented = true );
+       
+       notImplemented  = false;
+       aliasCount              = 0;
+       nameHasA                = false;
+       nameHasAAAA             = false;
+       nameHasSOA              = false;
+       useAliasTTLs    = false;
+       nameHasSRV              = false;
+       srvDomainLen    = 0;
+       srvCount                = 0;
+       
+       if( _DNSServerNameIsHostname( me, inQName, &aliasCount, aliasTTLs, &aliasTTLCount, &addrCount, &randCount, &ttl,
+               &nameHasA, &nameHasAAAA, &nameHasSOA ) )
+       {
+               check( !( ( aliasCount > 0 ) && ( aliasTTLCount > 0 ) ) );
+               check( ( addrCount >= 1 ) && ( addrCount <= 255 ) );
+               check( ( randCount == 0 ) || ( ( randCount >= addrCount ) && ( randCount <= 255 ) ) );
+               check( nameHasA || nameHasAAAA );
                
-               if( MemIEqual( labelStr, labelLen, kLabel_IPv4, sizeof_string( kLabel_IPv4 ) ) )
-               {
-                       if( nameHasA || nameHasAAAA ) break;    // Valid names have at most one IPv4 or IPv6 label.
-                       nameHasA = true;
-                       continue;
-               }
-               if( MemIEqual( labelStr, labelLen, kLabel_IPv6, sizeof_string( kLabel_IPv6 ) ) )
+               if( aliasTTLCount > 0 )
                {
-                       if( nameHasA || nameHasAAAA ) break;    // Valid names have at most one IPv4 or IPv6 label.
-                       nameHasAAAA = true;
-                       continue;
+                       aliasCount              = (uint32_t) aliasTTLCount;
+                       useAliasTTLs    = true;
                }
-               
-               // If the remaining labels are equal to "d.test.", the name exists.
-               
-               if( DomainNameEqual( labelPtr, (const uint8_t *) "\x01" "d" "\x04" "test" ) ) nameExists = true;
-               break;
+               nameExists = true;
        }
-       require_quiet( nameExists, done );
-       
-       // Set default values for count and TTL, if those labels were present.
-       
-       if( count <= 0 ) count = 1;
-       check( ( gDNSServer_DefaultTTL >= 0 ) && ( gDNSServer_DefaultTTL <= INT32_MAX ) );
-       if( ttl < 0 ) ttl = gDNSServer_DefaultTTL;
-       
-       // Names that don't specify v4 or v6 have both A and AAAA records.
-       
-       if( !nameHasA && !nameHasAAAA )
+       else if( _DNSServerNameIsSRVName( me, inQName, &srvDomainPtr, &srvDomainLen, srvArray, &srvCount ) )
        {
-               nameHasA        = true;
-               nameHasAAAA     = true;
+               nameHasSRV = true;
+               nameExists = true;
        }
-       
-       check( ( count >= 1 ) && ( count <= 255 ) );
-       check( ( randCount <= 0 ) || ( ( randCount >= count ) && ( randCount <= 255 ) ) );
+       require_quiet( nameExists, done );
        
        if( aliasCount > 0 )
        {
                size_t                          nameOffset;
-               uint8_t                         rdataLabel[ 1 + kDomainLabelLengthMax + 1  ];
+               uint8_t                         rdataLabel[ 1 + kDomainLabelLengthMax + 1 ];
                
                // If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-<N>". superPtr is a name
                // compression pointer to the second label of QNAME, i.e., the immediate superdomain name of QNAME. It's used for
@@ -7876,18 +7935,15 @@ static OSStatus
                
                for( i = aliasCount; i >= 1; --i )
                {
-                       size_t                                          nameLen;
-                       size_t                                          rdataLen;
-                       int                                                     j;
-                       uint32_t                                        aliasTTL;
-                       uint8_t                                         nameLabel[ 1 + kDomainLabelLengthMax + 1  ];
-                       DNSRecordFixedFields            fields;
+                       size_t                  nameLen;
+                       size_t                  rdataLen;
+                       uint32_t                j;
+                       uint32_t                aliasTTL;
+                       uint8_t                 nameLabel[ 1 + kDomainLabelLengthMax ];
                        
                        if( nameOffset <= kDNSCompressionOffsetMax )
                        {
-                               namePtr[ 0 ] = (uint8_t)( ( ( nameOffset >> 8 ) & 0x3F ) | 0xC0 );
-                               namePtr[ 1 ] = (uint8_t)(     nameOffset        & 0xFF );
-                               
+                               WriteDNSCompressionPtr( namePtr, nameOffset );
                                nameLen = sizeof( namePtr );
                        }
                        else
@@ -7899,22 +7955,22 @@ static OSStatus
                        if( i >= 2 )
                        {
                                char *                          dst = (char *) &rdataLabel[ 1 ];
-                               char * const            end = (char *) &rdataLabel[ countof( rdataLabel ) ];
+                               char * const            lim = (char *) &rdataLabel[ countof( rdataLabel ) ];
                                
                                if( useAliasTTLs )
                                {
-                                       err = SNPrintF_Add( &dst, end, kLabelPrefix_AliasTTL );
+                                       err = SNPrintF_Add( &dst, lim, kLabelPrefix_AliasTTL );
                                        require_noerr( err, exit );
                                        
                                        for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j )
                                        {
-                                               err = SNPrintF_Add( &dst, end, "-%u", aliasTTLs[ j ] );
+                                               err = SNPrintF_Add( &dst, lim, "-%u", aliasTTLs[ j ] );
                                                require_noerr( err, exit );
                                        }
                                }
                                else
                                {
-                                       err = SNPrintF_Add( &dst, end, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
+                                       err = SNPrintF_Add( &dst, lim, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
                                        require_noerr( err, exit );
                                }
                                rdataLabel[ 0 ] = (uint8_t)( dst - (char *) &rdataLabel[ 1 ] );
@@ -7955,8 +8011,8 @@ static OSStatus
                        
                        // Set CNAME record's TYPE, CLASS, TTL, and RDLENGTH.
                        
-                       aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : ( (uint32_t) gDNSServer_DefaultTTL );
-                       DNSRecordFixedFieldsInit( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, rdataLen );
+                       aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : me->defaultTTL;
+                       DNSRecordFixedFieldsSet( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, (uint16_t) rdataLen );
                        err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
                        require_noerr( err, exit );
                        
@@ -7982,30 +8038,36 @@ static OSStatus
        {
                // There are no aliases, so initialize the name compression pointer to point to QNAME.
                
-               namePtr[ 0 ] = 0xC0;
-               namePtr[ 1 ] = kDNSHeaderLength;
+               WriteDNSCompressionPtr( namePtr, kDNSHeaderLength );
        }
        
-       if( ( ( inQType == kDNSServiceType_A    ) && nameHasA    ) ||
-               ( ( inQType == kDNSServiceType_AAAA ) && nameHasAAAA ) )
+       if( ( inQType == kDNSServiceType_A ) || ( inQType == kDNSServiceType_AAAA ) )
        {
-               uint8_t *                                       lsb;                            // Pointer to the least significant byte of record data.
-               size_t                                          recordLen;                      // Length of the entire record.
-               size_t                                          rdataLen;                       // Length of record's RDATA.
-               uint8_t                                         rdata[ 16 ];            // A buffer that's big enough for either A or AAAA RDATA.
-               uint8_t                                         randItegers[ 255 ];     // Array for random integers in [1 .. 255].
-               DNSRecordFixedFields            fields;
+               uint8_t *               lsb;                                    // Pointer to the least significant byte of record data.
+               size_t                  recordLen;                              // Length of the entire record.
+               size_t                  rdataLen;                               // Length of record's RDATA.
+               uint8_t                 rdata[ 16 ];                    // A buffer that's big enough for either A or AAAA RDATA.
+               uint8_t                 randIntegers[ 255 ];    // Array for random integers in [1, 255].
+               const int               useBadAddrs = ( me->badUDPMode && !inForTCP ) ? true : false;
                
                if( inQType == kDNSServiceType_A )
                {
+                       const uint32_t          baseAddrV4 = useBadAddrs ? kDNSServerBadBaseAddrV4 : kDNSServerBaseAddrV4;
+                       
+                       require_quiet( nameHasA, done );
+                       
                        rdataLen = 4;
-                       WriteBig32( rdata, kTestDNSServerBaseAddrV4 );
+                       WriteBig32( rdata, baseAddrV4 );
                        lsb = &rdata[ 3 ];
                }
                else
                {
+                       const uint8_t * const           baseAddrV6 = useBadAddrs ? kDNSServerBadBaseAddrV6 : kDNSServerBaseAddrV6;
+                       
+                       require_quiet( nameHasAAAA, done );
+                       
                        rdataLen = 16;
-                       memcpy( rdata, kTestDNSServerBaseAddrV6, 16 );
+                       memcpy( rdata, baseAddrV6, 16 );
                        lsb = &rdata[ 15 ];
                }
                
@@ -8013,37 +8075,44 @@ static OSStatus
                {
                        // Populate the array with all integers between 1 and <randCount>, inclusive.
                        
-                       for( i = 0; i < randCount; ++i ) randItegers[ i ] = (uint8_t)( i + 1 );
+                       for( i = 0; i < randCount; ++i ) randIntegers[ i ] = (uint8_t)( i + 1 );
+                       
+                       // Prevent dubious static analyzer warning.
+                       // Note: _DNSServerNameIsHostname() already enforces randCount >= addrCount. Also, this require_fatal() check
+                       // needs to be placed right before the next for-loop. Any earlier, and the static analyzer warning will persist
+                       // for some reason.
+                       
+                       require_fatal( addrCount <= randCount, "Invalid Count label values: addrCount %u > randCount %u",
+                               addrCount, randCount );
                        
-                       // Create a contiguous subarray starting at index 0 that contains <count> randomly chosen integers between
+                       // Create a contiguous subarray starting at index 0 that contains <addrCount> randomly chosen integers between
                        // 1 and <randCount>, inclusive.
-                       // Loop invariant 1: Array elements with indexes in [0 .. i - 1] have been randomly chosen.
-                       // Loop invariant 2: Array elements with indexes in [i .. randCount - 1] are candidates for being chosen.
+                       // Loop invariant 1: Array elements with indexes in [0, i - 1] have been randomly chosen.
+                       // Loop invariant 2: Array elements with indexes in [i, randCount - 1] are candidates for being chosen.
                        
-                       for( i = 0; i < count; ++i )
+                       for( i = 0; i < addrCount; ++i )
                        {
-                               uint8_t         tmp;
-                               int                     j;
+                               uint8_t                 tmp;
+                               uint32_t                j;
                                
-                               j = (int) RandomRange( i, randCount - 1 );
+                               j = RandomRange( i, randCount - 1 );
                                if( i != j )
                                {
-                                       tmp = randItegers[ i ];
-                                       randItegers[ i ] = randItegers[ j ];
-                                       randItegers[ j ] = tmp;
+                                       tmp = randIntegers[ i ];
+                                       randIntegers[ i ] = randIntegers[ j ];
+                                       randIntegers[ j ] = tmp;
                                }
                        }
                }
                
                recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
-               for( i = 0; i < count; ++i )
+               for( i = 0; i < addrCount; ++i )
                {
                        if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
                        {
                                truncated = true;
                                goto done;
                        }
-                       ++answerCount;
                        
                        // Set record NAME.
                        
@@ -8052,23 +8121,108 @@ static OSStatus
                        
                        // Set record TYPE, CLASS, TTL, and RDLENGTH.
                        
-                       DNSRecordFixedFieldsInit( &fields, inQType, kDNSServiceClass_IN, ttl, rdataLen );
+                       DNSRecordFixedFieldsSet( &fields, (uint16_t) inQType, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
                        err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
                        require_noerr( err, exit );
                        
                        // Set record RDATA.
                        
-                       *lsb = ( randCount > 0 ) ? randItegers[ i ] : ( *lsb + 1 );
+                       *lsb = ( randCount > 0 ) ? randIntegers[ i ] : ( *lsb + 1 );
                        
                        err = DataBuffer_Append( inDB, rdata, rdataLen );
                        require_noerr( err, exit );
+                       
+                       ++answerCount;
+               }
+       }
+       else if( inQType == kDNSServiceType_SRV )
+       {
+               require_quiet( nameHasSRV, done );
+               
+               DNSRecordFixedFieldsSet( &fields, kDNSServiceType_SRV, kDNSServiceClass_IN, me->defaultTTL, 0 );
+               
+               for( i = 0; i < srvCount; ++i )
+               {
+                       SRVRecordDataFixedFields                fieldsSRV;
+                       size_t                                                  rdataLen;
+                       size_t                                                  recordLen;
+                       const ParsedSRV * const                 srv = &srvArray[ i ];
+                       
+                       rdataLen  = sizeof( fieldsSRV ) + srvDomainLen + srv->targetLen + 1;
+                       recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+                       
+                       if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+                       {
+                               truncated = true;
+                               goto done;
+                       }
+                       
+                       // Append record NAME.
+                       
+                       err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+                       require_noerr( err, exit );
+                       
+                       // Append record TYPE, CLASS, TTL, and RDLENGTH.
+                       
+                       WriteBig16( fields.rdlength, rdataLen );
+                       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+                       require_noerr( err, exit );
+                       
+                       // Append SRV RDATA.
+                       
+                       SRVRecordDataFixedFieldsSet( &fieldsSRV, srv->priority, srv->weight, srv->port );
+                       
+                       err = DataBuffer_Append( inDB, &fieldsSRV, sizeof( fieldsSRV ) );
+                       require_noerr( err, exit );
+                       
+                       if( srv->targetLen > 0 )
+                       {
+                               err = DataBuffer_Append( inDB, srv->targetPtr, srv->targetLen );
+                               require_noerr( err, exit );
+                       }
+                       
+                       if( srvDomainLen > 0 )
+                       {
+                               err = DataBuffer_Append( inDB, srvDomainPtr, srvDomainLen );
+                               require_noerr( err, exit );
+                       }
+                       
+                       err = DataBuffer_Append( inDB, "", 1 ); // Append root label.
+                       require_noerr( err, exit );
+                       
+                       ++answerCount;
+               }
+       }
+       else if( inQType == kDNSServiceType_SOA )
+       {
+               size_t          nameLen, recordLen;
+               
+               require_quiet( nameHasSOA, done );
+               
+               nameLen = DomainNameLength( me->domain );
+               if( !inForTCP )
+               {
+                       err = AppendSOARecord( NULL, me->domain, nameLen, 0, 0, 0, kRootLabel, kRootLabel, 0, 0, 0, 0, 0, &recordLen );
+                       require_noerr( err, exit );
+                       
+                       if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
+                       {
+                               truncated = true;
+                               goto done;
+                       }
                }
+               
+               err = AppendSOARecord( inDB, me->domain, nameLen, kDNSServiceType_SOA, kDNSServiceClass_IN, me->defaultTTL,
+                       kRootLabel, kRootLabel, me->serial, 1 * kSecondsPerDay, 2 * kSecondsPerHour, 1000 * kSecondsPerHour,
+                       me->defaultTTL, NULL );
+               require_noerr( err, exit );
+               
+               ++answerCount;
        }
        
 done:
        hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
        flags = DNSHeaderGetFlags( hdr );
-       if( truncated ) flags |= kDNSHeaderFlag_Truncation;
        if( notImplemented )
        {
                rcode = kDNSRCode_NotImplemented;
@@ -8076,6 +8230,7 @@ done:
        else
        {
                flags |= kDNSHeaderFlag_AuthAnswer;
+               if( truncated ) flags |= kDNSHeaderFlag_Truncation;
                rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
        }
        DNSFlagsSetRCode( flags, rcode );
@@ -8087,65 +8242,336 @@ exit:
        return( err );
 }
 
-//===========================================================================================================================
-//     _DNSServerTCPReadHandler
-//===========================================================================================================================
-
-typedef struct
-{
-       sockaddr_ip                             clientAddr;             // Client's address.
-       dispatch_source_t               readSource;             // Dispatch read source for client socket.
-       dispatch_source_t               writeSource;    // Dispatch write source for client socket.
-       size_t                                  offset;                 // Offset into receive buffer.
-       void *                                  msgPtr;                 // Pointer to dynamically allocated message buffer.
-       size_t                                  msgLen;                 // Length of message buffer.
-       Boolean                                 readSuspended;  // True if the read source is currently suspended.
-       Boolean                                 writeSuspended; // True if the write source is currently suspended.
-       Boolean                                 receivedLength; // True if receiving DNS message as opposed to the message length.
-       uint8_t                                 lenBuf[ 2 ];    // Buffer for two-octet message length field.
-       iovec_t                                 iov[ 2 ];               // IO vector for writing response message.
-       iovec_t *                               iovPtr;                 // Vector pointer for SocketWriteData().
-       int                                             iovCount;               // Vector count for SocketWriteData().
-       
-}      TCPConnectionContext;
-
-static void    TCPConnectionStop( TCPConnectionContext *inContext );
-static void    TCPConnectionContextFree( TCPConnectionContext *inContext );
-static void    TCPConnectionReadHandler( void *inContext );
-static void    TCPConnectionWriteHandler( void *inContext );
-
-#define        TCPConnectionForget( X )                ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
-
-static void    _DNSServerTCPReadHandler( void *inContext )
+static Boolean
+       _DNSServerNameIsHostname(
+               DNSServerRef    me,
+               const uint8_t * inName,
+               uint32_t *              outAliasCount,
+               uint32_t                inAliasTTLs[ kMaxAliasTTLCount ],
+               size_t *                outAliasTTLCount,
+               unsigned int *  outCount,
+               unsigned int *  outRandCount,
+               uint32_t *              outTTL,
+               Boolean *               outHasA,
+               Boolean *               outHasAAAA,
+               Boolean *               outHasSOA )
 {
-       OSStatus                                        err;
-       SocketContext * const           sockCtx         = (SocketContext *) inContext;
-       TCPConnectionContext *          connection;
-       socklen_t                                       clientAddrLen;
-       SocketRef                                       newSock         = kInvalidSocketRef;
-       SocketContext *                         newSockCtx      = NULL;
-       
-       connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
-       require_action( connection, exit, err = kNoMemoryErr );
-       
-       clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
-       newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
-       err = map_socket_creation_errno( newSock );
-       require_noerr( err, exit );
-       
-       err = SocketContextCreate( newSock, connection, &newSockCtx );
-       require_noerr( err, exit );
-       newSock = kInvalidSocketRef;
-       
-       err = DispatchReadSourceCreate( newSockCtx->sock, NULL, TCPConnectionReadHandler, SocketContextCancelHandler,
-               newSockCtx, &connection->readSource );
-       require_noerr( err, exit );
-       SocketContextRetain( newSockCtx );
-       dispatch_resume( connection->readSource );
-       
-       err = DispatchWriteSourceCreate( newSockCtx->sock, NULL, TCPConnectionWriteHandler, SocketContextCancelHandler,
-               newSockCtx, &connection->writeSource );
-       require_noerr( err, exit );
+       OSStatus                        err;
+       const uint8_t *         label;
+    const uint8_t *            nextLabel;
+       uint32_t                        aliasCount              = 0;    // Arg from Alias label. Valid values are in [2, 2^31 - 1].
+       unsigned int            count                   = 0;    // First arg from Count label. Valid values are in [1, 255].
+       unsigned int            randCount               = 0;    // Second arg from Count label. Valid values are in [count, 255].
+       int32_t                         ttl                             = -1;   // Arg from TTL label. Valid values are in [0, 2^31 - 1].
+       size_t                          aliasTTLCount   = 0;    // Count of TTL args from Alias-TTL label.
+       int                                     hasTagLabel             = false;
+       int                                     hasIPv4Label    = false;
+       int                                     hasIPv6Label    = false;
+       int                                     isNameValid             = false;
+       
+       for( label = inName; label[ 0 ]; label = nextLabel )
+       {
+               uint32_t                arg;
+               
+               nextLabel = &label[ 1 + label[ 0 ] ];
+               
+               // Check if the first label is a valid alias TTL sequence label.
+               
+               if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_AliasTTL ) == 0 ) )
+               {
+                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_AliasTTL ) ];
+                       const char * const              end = (const char *) nextLabel;
+                       const char *                    next;
+                       
+                       check( label[ 0 ] <= kDomainLabelLengthMax );
+                       
+                       while( ptr < end )
+                       {
+                               if( *ptr != '-' ) break;
+                               ++ptr;
+                               err = DecimalTextToUInt32( ptr, end, &arg, &next );
+                               if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
+                               inAliasTTLs[ aliasTTLCount++ ] = arg;
+                               ptr = next;
+                       }
+                       if( ( aliasTTLCount == 0 ) || ( ptr != end ) ) break;
+               }
+               
+               // Check if the first label is a valid alias label.
+               
+               else if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Alias ) == 0 ) )
+               {
+                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Alias ) ];
+                       const char * const              end = (const char *) nextLabel;
+                       
+                       if( ptr < end )
+                       {
+                               if( *ptr++ != '-' ) break;
+                               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                               if( err || ( arg < 2 ) || ( arg > INT32_MAX ) ) break;  // Alias count must be in [2, 2^31 - 1].
+                               aliasCount = arg;
+                               if( ptr != end ) break;
+                       }
+                       else
+                       {
+                               aliasCount = 1;
+                       }
+               }
+               
+               // Check if this label is a valid count label.
+               
+               else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Count ) == 0  )
+               {
+                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Count ) ];
+                       const char * const              end = (const char *) nextLabel;
+                       
+                       if( count > 0 ) break;  // Count cannot be specified more than once.
+                       
+                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                       if( err || ( arg < 1 ) || ( arg > 255 ) ) break;        // Count must be in [1, 255].
+                       count = (unsigned int) arg;
+                       
+                       if( ptr < end )
+                       {
+                               if( *ptr++ != '-' ) break;
+                               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                               if( err || ( arg < (uint32_t) count ) || ( arg > 255 ) ) break; // Rand count must be in [count, 255].
+                               randCount = (unsigned int) arg;
+                               if( ptr != end ) break;
+                       }
+               }
+               
+               // Check if this label is a valid TTL label.
+               
+               else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_TTL ) == 0  )
+               {
+                       const char *                    ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_TTL ) ];
+                       const char * const              end = (const char *) nextLabel;
+                       
+                       if( ttl >= 0 ) break;   // TTL cannot be specified more than once.
+                       
+                       err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+                       if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
+                       ttl = (int32_t) arg;
+                       if( ptr != end ) break;
+               }
+               
+               // Check if this label is a valid IPv4 label.
+               
+               else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv4 ) == 0 )
+               {
+                       if( hasIPv4Label || hasIPv6Label ) break;       // Valid names have at most one IPv4 or IPv6 label.
+                       hasIPv4Label = true;
+               }
+               
+               // Check if this label is a valid IPv6 label.
+               
+               else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv6 ) == 0 )
+               {
+                       if( hasIPv4Label || hasIPv6Label ) break;       // Valid names have at most one IPv4 or IPv6 label.
+                       hasIPv6Label = true;
+               }
+               
+               // Check if this label is a valid tag label.
+               
+               else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Tag ) == 0  )
+               {
+                       hasTagLabel = true;
+               }
+               
+               // If this and the remaining labels are equal to "d.test.", then the name exists. Otherwise, this label is invalid.
+               // In both cases, there are no more labels to check.
+               
+               else
+               {
+                       if( DomainNameEqual( label, me->domain ) ) isNameValid = true;
+                       break;
+               }
+       }
+       require_quiet( isNameValid, exit );
+       
+       if( outAliasCount )             *outAliasCount          = aliasCount;
+       if( outAliasTTLCount )  *outAliasTTLCount       = aliasTTLCount;
+       if( outCount )                  *outCount                       = ( count > 0 ) ? count : 1;
+       if( outRandCount )              *outRandCount           = randCount;
+       if( outTTL )                    *outTTL                         = ( ttl >= 0 ) ? ( (uint32_t) ttl ) : me->defaultTTL;
+       if( outHasA )                   *outHasA                        = ( hasIPv4Label || !hasIPv6Label ) ? true : false;
+       if( outHasAAAA )                *outHasAAAA                     = ( hasIPv6Label || !hasIPv4Label ) ? true : false;
+       if( outHasSOA )
+       {
+               *outHasSOA = ( !count && ( ttl < 0 ) && !hasIPv4Label && !hasIPv6Label && !hasTagLabel ) ? true : false;
+       }
+       
+exit:
+       return( isNameValid ? true : false );
+}
+
+static Boolean
+       _DNSServerNameIsSRVName(
+               DNSServerRef            me,
+               const uint8_t *         inName,
+               const uint8_t **        outDomainPtr,
+               size_t *                        outDomainLen,
+               ParsedSRV                       inSRVArray[ kMaxParsedSRVCount ],
+               size_t *                        outSRVCount )
+{
+       OSStatus                        err;
+       const uint8_t *         label;
+       const uint8_t *         domainPtr;
+       size_t                          domainLen;
+       size_t                          srvCount;
+       uint32_t                        arg;
+       int                                     isNameValid = false;
+       
+       label = inName;
+       
+       // Ensure that first label, i.e, the service label, begins with a '_' character.
+       
+       require_quiet( ( label[ 0 ] > 0 ) && ( label[ 1 ] == '_' ), exit );
+       label = NextLabel( label );
+       
+       // Ensure that the second label, i.e., the proto label, begins with a '_' character (usually _tcp or _udp).
+       
+       require_quiet( ( label[ 0 ] > 0 ) && ( label[ 1 ] == '_' ), exit );
+       label = NextLabel( label );
+       
+       // Parse the domain name, if any.
+       
+       domainPtr = label;
+       while( *label )
+       {
+               if( DomainNameEqual( label, me->domain ) ||
+                       ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break;
+               label = NextLabel( label );
+       }
+       require_quiet( *label, exit );
+       
+       domainLen = (size_t)( label - domainPtr );
+       
+       // Parse SRV labels, if any.
+       
+       srvCount = 0;
+       while( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 )
+       {
+               const uint8_t * const   nextLabel       = NextLabel( label );
+               const char *                    ptr                     = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_SRV ) ];
+               const char * const              end                     = (const char *) nextLabel;
+               const uint8_t *                 target;
+               unsigned int                    priority, weight, port;
+               
+               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+               require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+               priority = (unsigned int) arg;
+               
+               require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+               ++ptr;
+               
+               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+               require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+               weight = (unsigned int) arg;
+               
+               require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+               ++ptr;
+               
+               err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+               require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+               port = (unsigned int) arg;
+               
+               require_quiet( ptr == end, exit );
+               
+               target = nextLabel;
+               for( label = nextLabel; *label; label = NextLabel( label ) )
+               {
+                       if( DomainNameEqual( label, me->domain ) ||
+                               ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break;
+               }
+               require_quiet( *label, exit );
+               
+               if( inSRVArray )
+               {
+                       inSRVArray[ srvCount ].priority         = (uint16_t) priority;
+                       inSRVArray[ srvCount ].weight           = (uint16_t) weight;
+                       inSRVArray[ srvCount ].port                     = (uint16_t) port;
+                       inSRVArray[ srvCount ].targetPtr        = target;
+                       inSRVArray[ srvCount ].targetLen        = (uint16_t)( label - target );
+               }
+               ++srvCount;
+       }
+       require_quiet( DomainNameEqual( label, me->domain ), exit );
+       isNameValid = true;
+       
+       if( outDomainPtr )      *outDomainPtr   = domainPtr;
+       if( outDomainLen )      *outDomainLen   = domainLen;
+       if( outSRVCount )       *outSRVCount    = srvCount;
+       
+exit:
+       return( isNameValid ? true : false );
+}
+
+//===========================================================================================================================
+//     _DNSServerTCPReadHandler
+//===========================================================================================================================
+
+typedef struct
+{
+       DNSServerRef                    server;                 // Reference to DNS server object.
+       sockaddr_ip                             clientAddr;             // Client's address.
+       dispatch_source_t               readSource;             // Dispatch read source for client socket.
+       dispatch_source_t               writeSource;    // Dispatch write source for client socket.
+       size_t                                  offset;                 // Offset into receive buffer.
+       void *                                  msgPtr;                 // Pointer to dynamically allocated message buffer.
+       size_t                                  msgLen;                 // Length of message buffer.
+       Boolean                                 readSuspended;  // True if the read source is currently suspended.
+       Boolean                                 writeSuspended; // True if the write source is currently suspended.
+       Boolean                                 receivedLength; // True if receiving DNS message as opposed to the message length.
+       uint8_t                                 lenBuf[ 2 ];    // Buffer for two-octet message length field.
+       iovec_t                                 iov[ 2 ];               // IO vector for writing response message.
+       iovec_t *                               iovPtr;                 // Vector pointer for SocketWriteData().
+       int                                             iovCount;               // Vector count for SocketWriteData().
+       
+}      TCPConnectionContext;
+
+static void    TCPConnectionStop( TCPConnectionContext *inContext );
+static void    TCPConnectionContextFree( TCPConnectionContext *inContext );
+static void    TCPConnectionReadHandler( void *inContext );
+static void    TCPConnectionWriteHandler( void *inContext );
+
+#define        TCPConnectionForget( X )                ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
+
+static void    _DNSServerTCPReadHandler( void *inContext )
+{
+       OSStatus                                        err;
+       SocketContext * const           sockCtx         = (SocketContext *) inContext;
+       DNSServerRef const                      me                      = (DNSServerRef) sockCtx->userContext;
+       TCPConnectionContext *          connection;
+       socklen_t                                       clientAddrLen;
+       SocketRef                                       newSock         = kInvalidSocketRef;
+       SocketContext *                         newSockCtx      = NULL;
+       
+       connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
+       require_action( connection, exit, err = kNoMemoryErr );
+       
+       CFRetain( me );
+       connection->server = me;
+       
+       clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
+       newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
+       err = map_socket_creation_errno( newSock );
+       require_noerr( err, exit );
+       
+       err = SocketContextCreate( newSock, connection, &newSockCtx );
+       require_noerr( err, exit );
+       newSock = kInvalidSocketRef;
+       
+       err = DispatchReadSourceCreate( newSockCtx->sock, me->queue, TCPConnectionReadHandler, SocketContextCancelHandler,
+               newSockCtx, &connection->readSource );
+       require_noerr( err, exit );
+       SocketContextRetain( newSockCtx );
+       dispatch_resume( connection->readSource );
+       
+       err = DispatchWriteSourceCreate( newSockCtx->sock, me->queue, TCPConnectionWriteHandler, SocketContextCancelHandler,
+               newSockCtx, &connection->writeSource );
+       require_noerr( err, exit );
        SocketContextRetain( newSockCtx );
        connection->writeSuspended = true;
        connection = NULL;
@@ -8174,6 +8600,7 @@ static void       TCPConnectionContextFree( TCPConnectionContext *inContext )
 {
        check( !inContext->readSource );
        check( !inContext->writeSource );
+       ForgetCF( &inContext->server );
        ForgetMem( &inContext->msgPtr );
        free( inContext );
 }
@@ -8229,7 +8656,8 @@ static void       TCPConnectionReadHandler( void *inContext )
        
        // Create response.
        
-       err = _DNSServerAnswerQueryForTCP( connection->msgPtr, connection->msgLen, &responsePtr, &responseLen );
+       err = _DNSServerAnswerQueryForTCP( connection->server, connection->msgPtr, connection->msgLen, &responsePtr,
+               &responseLen );
        require_noerr_quiet( err, exit );
        
        // Send response.
@@ -8281,3095 +8709,9340 @@ exit:
 }
 
 //===========================================================================================================================
-//     GAIPerfCmd
+//     MDNSReplierCmd
 //===========================================================================================================================
 
-#define kGAIPerfStandardTTL            ( 1 * kSecondsPerHour )
-
-typedef struct GAITesterPrivate *              GAITesterRef;
-typedef struct GAITestCase                             GAITestCase;
-
-typedef uint32_t               GAITesterEventType;
-#define kGAITesterEvent_Started                1
-#define kGAITesterEvent_Stopped                2
-
 typedef struct
 {
-       const char *            name;                           // Domain name that was resolved.
-       int64_t                         connectionTimeUs;       // Time in microseconds that it took to create a DNS-SD connection.
-       int64_t                         firstTimeUs;            // Time in microseconds that it took to get the first address result.
-       int64_t                         timeUs;                         // Time in microseconds that it took to get all expected address results.
+       uint8_t *                               hostname;                       // Used as the base name for hostnames and service names.
+       uint8_t *                               serviceLabel;           // Label containing the base service name.
+       unsigned int                    maxInstanceCount;       // Maximum number of service instances and hostnames.
+       uint64_t *                              bitmaps;                        // Array of 64-bit bitmaps for keeping track of needed responses.
+       size_t                                  bitmapCount;            // Number of 64-bit bitmaps.
+       dispatch_source_t               readSourceV4;           // Read dispatch source for IPv4 socket.
+       dispatch_source_t               readSourceV6;           // Read dispatch source for IPv6 socket.
+       uint32_t                                ifIndex;                        // Index of the interface to run on.
+       unsigned int                    recordCountA;           // Number of A records per hostname.
+       unsigned int                    recordCountAAAA;        // Number of AAAA records per hostname.
+       unsigned int                    maxDropCount;           // If > 0, the drop rates apply to only the first <maxDropCount> responses.
+       double                                  ucastDropRate;          // Probability of dropping a unicast response.
+       double                                  mcastDropRate;          // Probability of dropping a multicast query or response.
+       uint8_t *                               dropCounters;           // If maxDropCount > 0, array of <maxInstanceCount> response drop counters.
+       Boolean                                 noAdditionals;          // True if responses are to not include additional records.
+       Boolean                                 useIPv4;                        // True if the replier is to use IPv4.
+       Boolean                                 useIPv6;                        // True if the replier is to use IPv6.
+       uint8_t                                 msgBuf[ kMDNSMessageSizeMax ];  // Buffer for received mDNS message.
+#if( TARGET_OS_DARWIN )
+       dispatch_source_t               processMonitor;         // Process monitor source for process being followed, if any.
+       pid_t                                   followPID;                      // PID of process being followed, if any. (If it exits, we exit).
+#endif
        
-}      GAITestItemResult;
-
-typedef void ( *GAITesterEventHandler_f )( GAITesterEventType inType, void *inContext );
-typedef void
-       ( *GAITesterResultsHandler_f )(
-               const char *                            inCaseTitle,
-               MicroTime64                                     inCaseStartTime,
-               MicroTime64                                     inCaseEndTime,
-               const GAITestItemResult *       inResults,
-               size_t                                          inResultCount,
-               size_t                                          inItemCount,
-               void *                                          inContext );
-
-typedef unsigned int           GAITestAddrType;
-#define kGAITestAddrType_None          0
-#define kGAITestAddrType_IPv4          ( 1U << 0 )
-#define kGAITestAddrType_IPv6          ( 1U << 1 )
-#define kGAITestAddrType_Both          ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 )
-
-#define GAITestAddrTypeIsValid( X ) \
-       ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) )
+}      MDNSReplierContext;
 
-typedef enum
+typedef struct MRResourceRecord                MRResourceRecord;
+struct MRResourceRecord
 {
-       kGAIPerfOutputFormat_JSON       = 1,
-       kGAIPerfOutputFormat_XML        = 2,
-       kGAIPerfOutputFormat_Binary     = 3
-       
-}      GAIPerfOutputFormatType;
+       MRResourceRecord *              next;           // Next item in list.
+       uint8_t *                               name;           // Resource record name.
+       uint16_t                                type;           // Resource record type.
+       uint16_t                                class;          // Resource record class.
+       uint32_t                                ttl;            // Resource record TTL.
+       uint16_t                                rdlength;       // Resource record data length.
+       uint8_t *                               rdata;          // Resource record data.
+       const uint8_t *                 target;         // For SRV records, pointer to target in RDATA.
+};
 
-typedef struct
+typedef struct MRNameOffsetItem                MRNameOffsetItem;
+struct MRNameOffsetItem
 {
-       GAITesterRef                            tester;                         // GAI tester object.
-       CFMutableArrayRef                       caseResults;            // Array of test case results.
-       char *                                          outputFilePath;         // File to write test results to. If NULL, then write to stdout.
-       GAIPerfOutputFormatType         outputFormat;           // Format of test results output.
-       unsigned int                            callDelayMs;            // Amount of time to wait before calling DNSServiceGetAddrInfo().
-       unsigned int                            serverDelayMs;          // Amount of additional time to have server delay its responses.
-       unsigned int                            defaultIterCount;       // Default test case iteration count.
-       dispatch_source_t                       sigIntSource;           // Dispatch source for SIGINT.
-       dispatch_source_t                       sigTermSource;          // Dispatch source for SIGTERM.
-       Boolean                                         gotSignal;                      // True if SIGINT or SIGTERM was caught.
-       Boolean                                         testerStarted;          // True if the GAI tester was started.
-       Boolean                                         appendNewLine;          // True if a newline character should be appended to JSON output.
-       
-}      GAIPerfContext;
-
-static void            GAIPerfContextFree( GAIPerfContext *inContext );
-static OSStatus        GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
-static OSStatus        GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
-static void            GAIPerfEventHandler( GAITesterEventType inType, void *inContext );
-static void
-       GAIPerfResultsHandler(
-               const char *                            inCaseTitle,
-               MicroTime64                                     inCaseStartTime,
-               MicroTime64                                     inCaseEndTime,
-               const GAITestItemResult *       inResults,
-               size_t                                          inResultCount,
-               size_t                                          inItemCount,
-               void *                                          inContext );
-static void            GAIPerfSignalHandler( void *inContext );
+       MRNameOffsetItem *      next;           // Next item in list.
+       uint16_t                        offset;         // Offset of domain name in response message.
+       uint8_t                         name[ 1 ];      // Variable-length array for domain name.
+};
 
-CFTypeID               GAITesterGetTypeID( void );
+#if( TARGET_OS_DARWIN )
+static void            _MDNSReplierFollowedProcessHandler( void *inContext );
+#endif
+static void            _MDNSReplierReadHandler( void *inContext );
 static OSStatus
-       GAITesterCreate(
-               dispatch_queue_t        inQueue,
-               int                                     inCallDelayMs,
-               int                                     inServerDelayMs,
-               int                                     inServerDefaultTTL,
-               GAITesterRef *          outTester );
-static void            GAITesterStart( GAITesterRef inTester );
-static void            GAITesterStop( GAITesterRef inTester );
-static void            GAITesterAddCase( GAITesterRef inTester, GAITestCase *inCase );
-static void
-       GAITesterSetEventHandler(
-               GAITesterRef                    inTester,
-               GAITesterEventHandler_f inEventHandler,
-               void *                                  inEventContext );
+       _MDNSReplierAnswerQuery(
+               MDNSReplierContext *    inContext,
+               const uint8_t *                 inQueryPtr,
+               size_t                                  inQueryLen,
+               sockaddr_ip *                   inSender,
+               SocketRef                               inSock,
+               unsigned int                    inIndex );
+static OSStatus
+       _MDNSReplierAnswerListAdd(
+               MDNSReplierContext *    inContext,
+               MRResourceRecord **             inAnswerList,
+               unsigned int                    inIndex,
+               const uint8_t *                 inName,
+               unsigned int                    inType,
+               unsigned int                    inClass );
 static void
-       GAITesterSetResultsHandler(
-               GAITesterRef                            inTester,
-               GAITesterResultsHandler_f       inResultsHandler,
-               void *                                          inResultsContext );
-
-static OSStatus        GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet );
-static void            GAITestCaseFree( GAITestCase *inCase );
+       _MDNSReplierAnswerListRemovePTR(
+               MRResourceRecord **     inAnswerListPtr,
+               const uint8_t *         inName,
+               const uint8_t *         inRData );
 static OSStatus
-       GAITestCaseAddItem(
-               GAITestCase *   inCase,
-               unsigned int    inAliasCount,
-               unsigned int    inAddressCount,
-               int                             inTTL,
-               GAITestAddrType inHasAddrs,
-               GAITestAddrType inWantAddrs,
-               unsigned int    inItemCount );
-static OSStatus        GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount );
+       _MDNSReplierSendOrDropResponse(
+               MDNSReplierContext *    inContext,
+               MRResourceRecord *              inAnswerList,
+               sockaddr_ip *                   inQuerier,
+               SocketRef                               inSock,
+               unsigned int                    inIndex,
+               Boolean                                 inUnicast );
+static OSStatus
+       _MDNSReplierCreateResponse(
+               MDNSReplierContext *    inContext,
+               MRResourceRecord *              inAnswerList,
+               unsigned int                    inIndex,
+               uint8_t **                              outResponsePtr,
+               size_t *                                outResponseLen );
+static OSStatus
+       _MDNSReplierAppendNameToResponse(
+               DataBuffer *            inResponse,
+               const uint8_t *         inName,
+               MRNameOffsetItem **     inNameOffsetListPtr );
+static Boolean
+       _MDNSReplierServiceTypeMatch(
+               const MDNSReplierContext *      inContext,
+               const uint8_t *                         inName,
+               unsigned int *                          outTXTSize,
+               unsigned int *                          outCount );
+static Boolean
+       _MDNSReplierServiceInstanceNameMatch(
+               const MDNSReplierContext *      inContext,
+               const uint8_t *                         inName,
+               unsigned int *                          outIndex,
+               unsigned int *                          outTXTSize,
+               unsigned int *                          outCount );
+static Boolean _MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName );
+static Boolean
+       _MDNSReplierHostnameMatch(
+               const MDNSReplierContext *      inContext,
+               const uint8_t *                         inName,
+               unsigned int *                          outIndex );
+static OSStatus        _MDNSReplierCreateTXTRecord( const uint8_t *inRecordName, size_t inSize, uint8_t **outTXT );
+static OSStatus
+       _MRResourceRecordCreate(
+               uint8_t *                       inName,
+               uint16_t                        inType,
+               uint16_t                        inClass,
+               uint32_t                        inTTL,
+               uint16_t                        inRDLength,
+               uint8_t *                       inRData,
+               MRResourceRecord **     outRecord );
+static void            _MRResourceRecordFree( MRResourceRecord *inRecord );
+static void            _MRResourceRecordFreeList( MRResourceRecord *inList );
+static OSStatus        _MRNameOffsetItemCreate( const uint8_t *inName, uint16_t inOffset, MRNameOffsetItem **outItem );
+static void            _MRNameOffsetItemFree( MRNameOffsetItem *inItem );
+static void            _MRNameOffsetItemFreeList( MRNameOffsetItem *inList );
 
-#define kGAIPerfTestSuite_Basic                        1
-#define kGAIPerfTestSuite_Advanced             2
+ulog_define_ex( "com.apple.dnssdutil", MDNSReplier, kLogLevelInfo, kLogFlags_None, "MDNSReplier", NULL );
+#define mr_ulog( LEVEL, ... )          ulog( &log_category_from_name( MDNSReplier ), (LEVEL), __VA_ARGS__ )
 
-static void    GAIPerfCmd( void )
+static void    MDNSReplierCmd( void )
 {
-       OSStatus                                err;
-       GAIPerfContext *                context;
-       int                                             suiteValue;
+       OSStatus                                        err;
+       MDNSReplierContext *            context;
+       SocketRef                                       sockV4  = kInvalidSocketRef;
+       SocketRef                                       sockV6  = kInvalidSocketRef;
+       const char *                            ifname;
+       size_t                                          len;
+       uint8_t                                         name[ 1 + kDomainLabelLengthMax + 1 ];
+       char                                            ifnameBuf[ IF_NAMESIZE + 1 ];
        
-       context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
+       err = CheckIntegerArgument( gMDNSReplier_MaxInstanceCount, "max instance count", 1, UINT16_MAX );
+       require_noerr_quiet( err, exit );
        
-       context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
-       require_action( context->caseResults, exit, err = kNoMemoryErr );
+       err = CheckIntegerArgument( gMDNSReplier_RecordCountA, "A record count", 0, 255 );
+       require_noerr_quiet( err, exit );
        
-       context->outputFormat = (GAIPerfOutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
-               "json",         kGAIPerfOutputFormat_JSON,
-               "xml",          kGAIPerfOutputFormat_XML,
-               "binary",       kGAIPerfOutputFormat_Binary,
-               NULL );
+       err = CheckIntegerArgument( gMDNSReplier_RecordCountAAAA, "AAAA record count", 0, 255 );
        require_noerr_quiet( err, exit );
        
-       context->callDelayMs            = ( gGAIPerf_CallDelayMs >= 0 ) ? (unsigned int) gGAIPerf_CallDelayMs : 0;
-       context->serverDelayMs          = ( gGAIPerf_ServerDelayMs >= 0 ) ? (unsigned int) gGAIPerf_ServerDelayMs : 0;
-       context->defaultIterCount       = ( gGAIPerf_DefaultIterCount >= 0 ) ? (unsigned int) gGAIPerf_DefaultIterCount : 0;
-       context->appendNewLine          = gGAIPerf_OutputAppendNewLine ? true : false;
+       err = CheckDoubleArgument( gMDNSReplier_UnicastDropRate, "unicast drop rate", 0.0, 1.0 );
+       require_noerr_quiet( err, exit );
        
-       if( gGAIPerf_OutputFilePath )
+       err = CheckDoubleArgument( gMDNSReplier_MulticastDropRate, "multicast drop rate", 0.0, 1.0 );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckIntegerArgument( gMDNSReplier_MaxDropCount, "drop count", 0, 255 );
+       require_noerr_quiet( err, exit );
+       
+       if( gMDNSReplier_Foreground )
        {
-               context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
-               require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+               LogControl( "MDNSReplier:output=file;stdout,MDNSReplier:flags=time;prefix" );
        }
        
-       err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
-               kGAIPerfStandardTTL, &context->tester );
-       require_noerr( err, exit );
+       context = (MDNSReplierContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
        
-       check( gGAIPerf_TestSuite );
-       suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err,
-               "basic",        kGAIPerfTestSuite_Basic,
-               "advanced",     kGAIPerfTestSuite_Advanced,
-               NULL );
-       require_noerr_quiet( err, exit );
+       context->maxInstanceCount       = (unsigned int) gMDNSReplier_MaxInstanceCount;
+       context->recordCountA           = (unsigned int) gMDNSReplier_RecordCountA;
+       context->recordCountAAAA        = (unsigned int) gMDNSReplier_RecordCountAAAA;
+       context->maxDropCount           = (unsigned int) gMDNSReplier_MaxDropCount;
+       context->ucastDropRate          = gMDNSReplier_UnicastDropRate;
+       context->mcastDropRate          = gMDNSReplier_MulticastDropRate;
+       context->noAdditionals          = gMDNSReplier_NoAdditionals ? true : false;
+       context->useIPv4                        = ( gMDNSReplier_UseIPv4 || !gMDNSReplier_UseIPv6 ) ? true : false;
+       context->useIPv6                        = ( gMDNSReplier_UseIPv6 || !gMDNSReplier_UseIPv4 ) ? true : false;
+       context->bitmapCount            = ( context->maxInstanceCount + 63 ) / 64;
        
-       switch( suiteValue )
+#if( TARGET_OS_DARWIN )
+       if( gMDNSReplier_FollowPID )
        {
-               case kGAIPerfTestSuite_Basic:
-                       err = GAIPerfAddBasicTestCases( context );
-                       require_noerr( err, exit );
-                       break;
+               err = StringToPID( gMDNSReplier_FollowPID, &context->followPID );
+               if( err || ( context->followPID < 0 ) )
+               {
+                       FPrintF( stderr, "error: Invalid follow PID: %s\n", gMDNSReplier_FollowPID );
+                       goto exit;
+               }
                
-               case kGAIPerfTestSuite_Advanced:
-                       err = GAIPerfAddAdvancedTestCases( context );
-                       require_noerr( err, exit );
-                       break;
+               err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+                       _MDNSReplierFollowedProcessHandler, NULL, context, &context->processMonitor );
+               require_noerr( err, exit );
+               dispatch_resume( context->processMonitor );
+       }
+       else
+       {
+               context->followPID = -1;
+       }
+#endif
+       
+       if( context->maxDropCount > 0 )
+       {
+               context->dropCounters = (uint8_t *) calloc( context->maxInstanceCount, sizeof( *context->dropCounters ) );
+               require_action( context->dropCounters, exit, err = kNoMemoryErr );
+       }
+       
+       context->bitmaps = (uint64_t *) calloc( context->bitmapCount, sizeof( *context->bitmaps ) );
+       require_action( context->bitmaps, exit, err = kNoMemoryErr );
+       
+       // Create the base hostname label.
+       
+       len = strlen( gMDNSReplier_Hostname );
+       if( context->maxInstanceCount > 1 )
+       {
+               unsigned int            maxInstanceCount, digitCount;
                
-               default:
-                       err = kValueErr;
-                       break;
+               // When there's more than one instance, extra bytes are needed to append " (<instance index>)" or
+               // "-<instance index>" to the base hostname.
+               
+               maxInstanceCount = context->maxInstanceCount;
+               for( digitCount = 0; maxInstanceCount > 0; ++digitCount ) maxInstanceCount /= 10;
+               len += ( 3 + digitCount );
        }
        
-       GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context );
-       GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
+       if( len <= kDomainLabelLengthMax )
+       {
+               uint8_t *               dst = &name[ 1 ];
+               uint8_t *               lim = &name[ countof( name ) ];
+               
+               SNPrintF_Add( (char **) &dst, (char *) lim, "%s", gMDNSReplier_Hostname );
+               name[ 0 ] = (uint8_t)( dst - &name[ 1 ] );
+               
+               err = DomainNameDupLower( name, &context->hostname, NULL );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               FPrintF( stderr, "error: Base name \"%s\" is too long for max instance count of %u.\n",
+                       gMDNSReplier_Hostname, context->maxInstanceCount );
+               goto exit;
+       }
        
-       signal( SIGINT, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
-       require_noerr( err, exit );
-       dispatch_resume( context->sigIntSource );
+       // Create the service label.
        
-       signal( SIGTERM, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
-       require_noerr( err, exit );
-       dispatch_resume( context->sigTermSource );
+       len = strlen( gMDNSReplier_ServiceTypeTag ) + 3;        // We need three extra bytes for the service type prefix "_t-".
+       if( len <= kDomainLabelLengthMax )
+       {
+               uint8_t *               dst = &name[ 1 ];
+               uint8_t *               lim = &name[ countof( name ) ];
+               
+               SNPrintF_Add( (char **) &dst, (char *) lim, "_t-%s", gMDNSReplier_ServiceTypeTag );
+               name[ 0 ] = (uint8_t)( dst - &name[ 1 ] );
+               
+               err = DomainNameDupLower( name, &context->serviceLabel, NULL );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               FPrintF( stderr, "error: Service type tag is too long.\n" );
+               goto exit;
+       }
+       
+       err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+       require_noerr_quiet( err, exit );
+       
+       ifname = if_indextoname( context->ifIndex, ifnameBuf );
+       require_action( ifname, exit, err = kNameErr );
+       
+       // Set up IPv4 socket.
+       
+       if( context->useIPv4 )
+       {
+               err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, ifname, context->ifIndex, true, NULL, &sockV4 );
+               require_noerr( err, exit );
+       }
+       
+       // Set up IPv6 socket.
+       
+       if( context->useIPv6 )
+       {
+               err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, ifname, context->ifIndex, true, NULL, &sockV6 );
+               require_noerr( err, exit );
+       }
+       
+       // Create dispatch read sources for socket(s).
+       
+       if( IsValidSocket( sockV4 ) )
+       {
+               SocketContext *         sockCtx;
+               
+               err = SocketContextCreate( sockV4, context, &sockCtx );
+               require_noerr( err, exit );
+               sockV4 = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, NULL, _MDNSReplierReadHandler, SocketContextCancelHandler, sockCtx,
+                       &context->readSourceV4 );
+               if( err ) ForgetSocketContext( &sockCtx );
+               require_noerr( err, exit );
+               
+               dispatch_resume( context->readSourceV4 );
+       }
+       
+       if( IsValidSocket( sockV6 ) )
+       {
+               SocketContext *         sockCtx;
+               
+               err = SocketContextCreate( sockV6, context, &sockCtx );
+               require_noerr( err, exit );
+               sockV6 = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, NULL, _MDNSReplierReadHandler, SocketContextCancelHandler, sockCtx,
+                       &context->readSourceV6 );
+               if( err ) ForgetSocketContext( &sockCtx );
+               require_noerr( err, exit );
+               
+               dispatch_resume( context->readSourceV6 );
+       }
        
-       GAITesterStart( context->tester );
        dispatch_main();
        
 exit:
-       if( context ) GAIPerfContextFree( context );
-       if( err ) exit( 1 );
+       ForgetSocket( &sockV4 );
+       ForgetSocket( &sockV6 );
+       exit( 1 );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//     GAIPerfContextFree
+//     _MDNSReplierFollowedProcessHandler
 //===========================================================================================================================
 
-static void    GAIPerfContextFree( GAIPerfContext *inContext )
+static void    _MDNSReplierFollowedProcessHandler( void *inContext )
 {
-       ForgetCF( &inContext->tester );
-       ForgetCF( &inContext->caseResults );
-       ForgetMem( &inContext->outputFilePath );
-       dispatch_source_forget( &inContext->sigIntSource );
-       dispatch_source_forget( &inContext->sigTermSource );
-       free( inContext );
+       MDNSReplierContext * const              context = (MDNSReplierContext *) inContext;
+       
+       if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
+       {
+               mr_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited.\n", (int64_t) context->followPID );
+               exit( 0 );
+       }
 }
+#endif
 
 //===========================================================================================================================
-//     GAIPerfAddAdvancedTestCases
+//     _MDNSReplierReadHandler
 //===========================================================================================================================
 
-#define kTestCaseTitleBufferSize               128
-
-static void
-       _GAIPerfWriteTestCaseTitle(
-               char                    inBuffer[ kTestCaseTitleBufferSize ],
-               unsigned int    inCNAMERecordCount,
-               unsigned int    inARecordCount,
-               unsigned int    inAAAARecordCount,
-               GAITestAddrType inRequested,
-               unsigned int    inIterationCount,
-               Boolean                 inIterationsAreUnique );
-static void
-       _GAIPerfWriteLocalHostTestCaseTitle(
-               char                    inBuffer[ kTestCaseTitleBufferSize ],
-               GAITestAddrType inRequested,
-               unsigned int    inIterationCount );
-static unsigned int
-       _GAIPerfTimeLimitMs(
-               unsigned int    inCallDelayMs,
-               unsigned int    inServerDelayMs,
-               unsigned int    inIterationCount );
-
-#define kGAIPerfAdvancedTestSuite_MaxAliasCount                4
-#define kGAIPerfAdvancedTestSuite_MaxAddrCount         8
+#define ShouldDrop( P )                ( ( (P) > 0.0 ) && ( ( (P) >= 1.0 ) || RandomlyTrue( P ) ) )
 
-static OSStatus        GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
+static void    _MDNSReplierReadHandler( void *inContext )
 {
-       OSStatus                        err;
-       unsigned int            aliasCount, addressCount, timeLimitMs, i;
-       GAITestCase *           testCase = NULL;
-       char                            title[ kTestCaseTitleBufferSize ];
+       OSStatus                                                err;
+       SocketContext * const                   sockCtx = (SocketContext *) inContext;
+       MDNSReplierContext * const              context = (MDNSReplierContext *) sockCtx->userContext;
+       size_t                                                  msgLen;
+       sockaddr_ip                                             sender;
+       const DNSHeader *                               hdr;
+       unsigned int                                    flags, questionCount, i, j;
+       const uint8_t *                                 ptr;
+       int                                                             drop, isMetaQuery;
        
-       aliasCount = 0;
-       while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+       err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &sender, sizeof( sender ),
+               NULL, NULL, NULL, NULL );
+       require_noerr( err, exit );
+       
+       if( msgLen < kDNSHeaderLength )
        {
-               for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+               mr_ulog( kLogLevelInfo, "Message is too small (%zu < %d).\n", msgLen, kDNSHeaderLength );
+               goto exit;
+       }
+       
+       // Perform header field checks.
+       // The message ID and most flag bits are silently ignored (see <https://tools.ietf.org/html/rfc6762#section-18>).
+       
+       hdr = (DNSHeader *) context->msgBuf;
+       flags = DNSHeaderGetFlags( hdr );
+       require_quiet( ( flags & kDNSHeaderFlag_Response ) == 0, exit );                // Reject responses.
+       require_quiet( DNSFlagsGetOpCode( flags ) == kDNSOpCode_Query, exit );  // Reject opcodes other than standard query.
+       require_quiet( DNSFlagsGetRCode( flags )  == kDNSRCode_NoError, exit ); // Reject non-zero rcodes.
+       
+       drop = ( !context->maxDropCount && ShouldDrop( context->mcastDropRate ) ) ? true : false;
+       
+       mr_ulog( kLogLevelInfo, "Received %zu byte message from %##a%?s:\n\n%#1{du:dnsmsg}",
+               msgLen, &sender, drop, " (dropping)", context->msgBuf, msgLen );
+       
+       // Based on the QNAMEs in the query message, determine from which sets of records we may possibly need answers.
+       
+       questionCount = DNSHeaderGetQuestionCount( hdr );
+       require_quiet( questionCount > 0, exit );
+       
+       memset( context->bitmaps, 0, context->bitmapCount * sizeof_element( context->bitmaps ) );
+       
+       isMetaQuery = false;
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       for( i = 0; i < questionCount; ++i )
+       {
+               unsigned int            count, index;
+               uint16_t                        qtype, qclass;
+               uint8_t                         qname[ kDomainNameLengthMax ];
+               
+               err = DNSMessageExtractQuestion( context->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+               
+               if( _MDNSReplierHostnameMatch( context, qname, &index ) ||
+                       _MDNSReplierServiceInstanceNameMatch( context, qname, &index, NULL, NULL ) )
                {
-                       // Add a test case to resolve a domain name with
-                       //
-                       //     <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
-                       //
-                       // to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which
-                       // requires server queries.
-                       
-                       _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
-                               inContext->defaultIterCount, true );
-                       
-                       timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs,
-                               inContext->defaultIterCount );
-                       err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-                       require_noerr( err, exit );
-                       
-                       for( i = 0; i < inContext->defaultIterCount; ++i )
+                       if( ( index >= 1 ) && ( index <= context->maxInstanceCount ) )
                        {
-                               err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
-                                       kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
-                               require_noerr( err, exit );
+                               context->bitmaps[ ( index - 1 ) / 64 ] |= ( UINT64_C( 1 ) << ( ( index - 1 ) % 64 ) );
                        }
-                       
-                       GAITesterAddCase( inContext->tester, testCase );
-                       testCase = NULL;
-                       
-                       // Add a test case to resolve a domain name with
-                       //
-                       //     <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
-                       //
-                       // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server
-                       // query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should
-                       // ideally require no server queries, i.e., the results should come from the cache.
-                       
-                       _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
-                               inContext->defaultIterCount, false );
-                       
-                       timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
-                               _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
-                       err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-                       require_noerr( err, exit );
-                       
-                       err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
-                               kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
-                       require_noerr( err, exit );
-                       
-                       GAITesterAddCase( inContext->tester, testCase );
-                       testCase = NULL;
                }
-               
-               if( aliasCount == 0 )   aliasCount  = 1;
-               else                                    aliasCount *= 2;
+               else if( _MDNSReplierServiceTypeMatch( context, qname, NULL, &count ) )
+               {
+                       if( ( count >= 1 ) && ( count <= context->maxInstanceCount ) )
+                       {
+                               for( j = 0; j < (unsigned int) context->bitmapCount; ++j )
+                               {
+                                       if( count < 64 )
+                                       {
+                                               context->bitmaps[ j ] |= ( ( UINT64_C( 1 ) << count ) - 1 );
+                                               break;
+                                       }
+                                       else
+                                       {
+                                               context->bitmaps[ j ] = ~UINT64_C( 0 );
+                                               count -= 64;
+                                       }
+                               }
+                       }
+               }
+               else if( _MDNSReplierAboutRecordNameMatch( context, qname ) )
+               {
+                       isMetaQuery = true;
+               }
        }
        
-       // Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
-       
-       _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
-       
-       timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
-       err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-       require_noerr( err, exit );
+       // Attempt to answer the query message using selected record sets.
        
-       err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
-       require_noerr( err, exit );
+       if( isMetaQuery )
+       {
+               err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock, 0 );
+               check_noerr( err );
+       }
+       if( drop ) goto exit;
        
-       GAITesterAddCase( inContext->tester, testCase );
-       testCase = NULL;
+       for( i = 0; i < context->bitmapCount; ++i )
+       {
+               for( j = 0; ( context->bitmaps[ i ] != 0 ) && ( j < 64 ); ++j )
+               {
+                       const uint64_t          bitmask = UINT64_C( 1 ) << j;
+                       
+                       if( context->bitmaps[ i ] & bitmask )
+                       {
+                               context->bitmaps[ i ] &= ~bitmask;
+                               
+                               err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock,
+                                       ( i * 64 ) + j + 1 );
+                               check_noerr( err );
+                       }
+               }
+       }
        
 exit:
-       if( testCase ) GAITestCaseFree( testCase );
-       return( err );
+       return;
 }
 
 //===========================================================================================================================
-//     _GAIPerfWriteTestCaseTitle
+//     _MDNSReplierAnswerQuery
 //===========================================================================================================================
 
-#define GAITestAddrTypeToRequestKeyValue( X ) (                                \
-       ( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6"        :       \
-       ( (X) == kGAITestAddrType_IPv4 ) ? "ipv4"                       :       \
-       ( (X) == kGAITestAddrType_IPv6 ) ? "ipv6"                       :       \
-                                                                          "" )
-
-static void
-       _GAIPerfWriteTestCaseTitle(
-               char                    inBuffer[ kTestCaseTitleBufferSize ],
-               unsigned int    inCNAMERecordCount,
-               unsigned int    inARecordCount,
-               unsigned int    inAAAARecordCount,
-               GAITestAddrType inRequested,
-               unsigned int    inIterationCount,
-               Boolean                 inIterationsAreUnique )
-{
-       SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
-               inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
-               inIterationCount, inIterationsAreUnique, ",unique" );
-}
-
-//===========================================================================================================================
-//     _GAIPerfWriteLocalHostTestCaseTitle
-//===========================================================================================================================
-
-static void
-       _GAIPerfWriteLocalHostTestCaseTitle(
-               char                    inBuffer[ kTestCaseTitleBufferSize ],
-               GAITestAddrType inRequested,
-               unsigned int    inIterationCount )
-{
-       SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
-               GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
-}
-
-//===========================================================================================================================
-//     _GAIPerfTimeLimitMs
-//===========================================================================================================================
-
-static unsigned int
-       _GAIPerfTimeLimitMs(
-               unsigned int    inCallDelayMs,
-               unsigned int    inServerDelayMs,
-               unsigned int    inIterationCount )
-{
-       // Allow each iteration 20 ms to complete (in addition to the call and server delay times).
-       
-       return( ( inCallDelayMs + inServerDelayMs + 20 ) * inIterationCount );
-}
-
-//===========================================================================================================================
-//     GAIPerfAddBasicTestCases
-//===========================================================================================================================
-
-#define kGAIPerfBasicTestSuite_AliasCount              2
-#define kGAIPerfBasicTestSuite_AddrCount               4
-
-static OSStatus        GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
+static OSStatus
+       _MDNSReplierAnswerQuery(
+               MDNSReplierContext *    inContext,
+               const uint8_t *                 inQueryPtr,
+               size_t                                  inQueryLen,
+               sockaddr_ip *                   inSender,
+               SocketRef                               inSock,
+               unsigned int                    inIndex )
 {
-       OSStatus                        err;
-       GAITestCase *           testCase = NULL;
-       char                            title[ kTestCaseTitleBufferSize ];
-       unsigned int            timeLimitMs, i;
+       OSStatus                                err;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       unsigned int                    questionCount, answerCount, i;
+       MRResourceRecord *              ucastAnswerList = NULL;
+       MRResourceRecord *              mcastAnswerList = NULL;
        
-       // Test Case #1:
-       // Resolve a domain name with
-       //
-       //     2 CNAME records, 4 A records, and 4 AAAA records
-       //
-       // to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server
-       // queries.
+       require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr );
        
-       _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
-               kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
-               inContext->defaultIterCount, true );
+       // Get answers for questions.
        
-       timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount );
-       err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-       require_noerr( err, exit );
+       check( inQueryLen >= kDNSHeaderLength );
+       hdr = (const DNSHeader *) inQueryPtr;
+       questionCount = DNSHeaderGetQuestionCount( hdr );
        
-       for( i = 0; i < inContext->defaultIterCount; ++i )
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       for( i = 0; i < questionCount; ++i )
        {
-               err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
-                       kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+               MRResourceRecord **             answerListPtr;
+               uint16_t                                qtype, qclass;
+               uint8_t                                 qname[ kDomainNameLengthMax ];
+               
+               err = DNSMessageExtractQuestion( inQueryPtr, inQueryLen, ptr, qname, &qtype, &qclass, &ptr );
+               require_noerr_quiet( err, exit );
+               
+               if( qclass & kQClassUnicastResponseBit )
+               {
+                       qclass &= ~kQClassUnicastResponseBit;
+                       answerListPtr = &ucastAnswerList;
+               }
+               else
+               {
+                       answerListPtr = &mcastAnswerList;
+               }
+               
+               err = _MDNSReplierAnswerListAdd( inContext, answerListPtr, inIndex, qname, qtype, qclass );
                require_noerr( err, exit );
        }
+       require_action_quiet( mcastAnswerList || ucastAnswerList, exit, err = kNoErr );
        
-       GAITesterAddCase( inContext->tester, testCase );
-       testCase = NULL;
-       
-       // Test Case #2:
-       // Resolve a domain name with
-       //
-       //     2 CNAME records, 4 A records, and 4 AAAA records
-       //
-       // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which
-       // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary
-       // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache.
-       
-       _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
-               kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
-               inContext->defaultIterCount, false );
-       
-       timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
-               _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
-       err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-       require_noerr( err, exit );
-       
-       err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
-               kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
-       require_noerr( err, exit );
-       
-       GAITesterAddCase( inContext->tester, testCase );
-       testCase = NULL;
-       
-       // Test Case #3:
-       // Each iteration resolves localhost to its IPv4 and IPv6 addresses.
-       
-       _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
-       
-       timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
-       err = GAITestCaseCreate( title, timeLimitMs, &testCase );
-       require_noerr( err, exit );
-       
-       err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
-       require_noerr( err, exit );
-       
-       GAITesterAddCase( inContext->tester, testCase );
-       testCase = NULL;
-       
-exit:
-       if( testCase ) GAITestCaseFree( testCase );
-       return( err );
-}
-
-//===========================================================================================================================
-//     GAIPerfEventHandler
-//===========================================================================================================================
-
-static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) ATTRIBUTE_NORETURN;
-
-static void    GAIPerfEventHandler( GAITesterEventType inType, void *inContext )
-{
-       GAIPerfContext * const          context = (GAIPerfContext *) inContext;
-       
-       if( inType == kGAITesterEvent_Started )
-       {
-               context->testerStarted = true;
-       }
-       else if( inType == kGAITesterEvent_Stopped )
-       {
-               if( context->gotSignal ) exit( 1 );
-               _GAIPerfOutputResultsAndExit( context );
-       }
-}
-
-//===========================================================================================================================
-//     _GAIPerfOutputResultsAndExit
-//===========================================================================================================================
-
-#define kGAIPerfResultsKey_TestCases           CFSTR( "testCases" )
-#define kGAIPerfResultsKey_Info                                CFSTR( "info" )
-
-#define kGAIPerfInfoKey_CallDelay              CFSTR( "callDelayMs" )
-#define kGAIPerfInfoKey_ServerDelay            CFSTR( "serverDelayMs" )
-
-static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext )
-{
-       OSStatus                                err;
-       CFPropertyListRef               plist   = NULL;
-       CFDataRef                               results = NULL;
-       FILE *                                  file    = NULL;
-       
-       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
-               "{"
-                       "%kO=%O"
-                       "%kO="
-                       "{"
-                               "%kO=%lli"
-                               "%kO=%lli"
-                       "}"
-               "}",
-               kGAIPerfResultsKey_TestCases,   inContext->caseResults,
-               kGAIPerfResultsKey_Info,
-               kGAIPerfInfoKey_CallDelay,              (int64_t) inContext->callDelayMs,
-               kGAIPerfInfoKey_ServerDelay,    (int64_t) inContext->serverDelayMs );
-       require_noerr( err, exit );
-       
-       // Convert results to a specific format.
+       // Suppress known answers.
+       // Records in the Answer section of the query message are known answers, so remove them from the answer lists.
+       // See <https://tools.ietf.org/html/rfc6762#section-7.1>.
        
-       switch( inContext->outputFormat )
+       answerCount = DNSHeaderGetAnswerCount( hdr );
+       for( i = 0; i < answerCount; ++i )
        {
-               case kGAIPerfOutputFormat_JSON:
-                       results = CFCreateJSONData( plist, kJSONFlags_None, NULL );
-                       require_action( results, exit, err = kUnknownErr );
-                       break;
+               const uint8_t *         rdataPtr;
+               const uint8_t *         recordPtr;
+               uint16_t                        type, class;
+               uint8_t                         name[ kDomainNameLengthMax ];
+               uint8_t                         instance[ kDomainNameLengthMax ];
                
-               case kGAIPerfOutputFormat_XML:
-                       results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL );
-                       require_action( results, exit, err = kUnknownErr );
-                       break;
+               recordPtr = ptr;
+               err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, ptr, NULL, &type, &class, NULL, NULL, NULL, &ptr );
+               require_noerr_quiet( err, exit );
                
-               case kGAIPerfOutputFormat_Binary:
-                       results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
-                       require_action( results, exit, err = kUnknownErr );
-                       break;
+               if( ( type != kDNSServiceType_PTR ) || ( class != kDNSServiceClass_IN ) ) continue;
                
-               default:
-                       err = kValueErr;
-                       goto exit;
+               err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, recordPtr, name, NULL, NULL, NULL, &rdataPtr, NULL, NULL );
+               require_noerr( err, exit );
+               
+               err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, rdataPtr, instance, NULL );
+               require_noerr_quiet( err, exit );
+               
+               if( ucastAnswerList ) _MDNSReplierAnswerListRemovePTR( &ucastAnswerList, name, instance );
+               if( mcastAnswerList ) _MDNSReplierAnswerListRemovePTR( &mcastAnswerList, name, instance );
        }
+       require_action_quiet( mcastAnswerList || ucastAnswerList, exit, err = kNoErr );
        
-       // Write formatted results to file or stdout.
+       // Send or drop responses.
        
-       if( inContext->outputFilePath )
+       if( ucastAnswerList )
        {
-               file = fopen( inContext->outputFilePath, "wb" );
-               err = map_global_value_errno( file, file );
+               err = _MDNSReplierSendOrDropResponse( inContext, ucastAnswerList, inSender, inSock, inIndex, true );
                require_noerr( err, exit );
        }
-       else
-       {
-               file = stdout;
-       }
-       
-       err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
-       require_noerr( err, exit );
-       
-       // Write a trailing newline for JSON-formatted results if requested.
        
-       if( ( inContext->outputFormat == kGAIPerfOutputFormat_JSON ) && inContext->appendNewLine )
+       if( mcastAnswerList )
        {
-               err = WriteANSIFile( file, "\n", 1 );
+               err = _MDNSReplierSendOrDropResponse( inContext, mcastAnswerList, inSender, inSock, inIndex, false );
                require_noerr( err, exit );
        }
+       err = kNoErr;
        
 exit:
-       CFReleaseNullSafe( plist );
-       CFReleaseNullSafe( results );
-       if( file && ( file != stdout ) ) fclose( file );
-       GAIPerfContextFree( inContext );
-       exit( err ? 1 : 0 );
+       _MRResourceRecordFreeList( ucastAnswerList );
+       _MRResourceRecordFreeList( mcastAnswerList );
+       return( err );
 }
 
 //===========================================================================================================================
-//     GAIPerfResultsHandler
+//     _MDNSReplierAnswerListAdd
 //===========================================================================================================================
 
-// Keys for test case dictionary
-
-#define kGAIPerfTestCaseKey_Title                              CFSTR( "title" )
-#define kGAIPerfTestCaseKey_StartTime                  CFSTR( "startTimeUs" )
-#define kGAIPerfTestCaseKey_EndTime                            CFSTR( "endTimeUs" )
-#define kGAIPerfTestCaseKey_Results                            CFSTR( "results" )
-#define kGAIPerfTestCaseKey_FirstStats                 CFSTR( "firstStats" )
-#define kGAIPerfTestCaseKey_ConnectionStats            CFSTR( "connectionStats" )
-#define kGAIPerfTestCaseKey_Stats                              CFSTR( "stats" )
-#define kGAIPerfTestCaseKey_TimedOut                   CFSTR( "timedOut" )
-
-// Keys for test case results array entry dictionaries
-
-#define kGAIPerfTestCaseResultKey_Name                                 CFSTR( "name" )
-#define kGAIPerfTestCaseResultKey_ConnectionTime               CFSTR( "connectionTimeUs" )
-#define kGAIPerfTestCaseResultKey_FirstTime                            CFSTR( "firstTimeUs" )
-#define kGAIPerfTestCaseResultKey_Time                                 CFSTR( "timeUs" )
-
-// Keys for test case stats dictionaries
-
-#define kGAIPerfTestCaseStatsKey_Count         CFSTR( "count" )
-#define kGAIPerfTestCaseStatsKey_Min           CFSTR( "min" )
-#define kGAIPerfTestCaseStatsKey_Max           CFSTR( "max" )
-#define kGAIPerfTestCaseStatsKey_Mean          CFSTR( "mean" )
-#define kGAIPerfTestCaseStatsKey_StdDev                CFSTR( "sd" )
-
-typedef struct
-{
-       double          min;
-       double          max;
-       double          mean;
-       double          stdDev;
-       
-}      GAIPerfStats;
-
-#define GAIPerfStatsInit( X ) \
-       do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
-
-static void
-       GAIPerfResultsHandler(
-               const char *                            inCaseTitle,
-               MicroTime64                                     inCaseStartTime,
-               MicroTime64                                     inCaseEndTime,
-               const GAITestItemResult *       inResults,
-               size_t                                          inResultCount,
-               size_t                                          inItemCount,
-               void *                                          inContext )
+static OSStatus
+       _MDNSReplierAnswerListAdd(
+               MDNSReplierContext *    inContext,
+               MRResourceRecord **             inAnswerList,
+               unsigned int                    inIndex,
+               const uint8_t *                 inName,
+               unsigned int                    inType,
+               unsigned int                    inClass )
 {
        OSStatus                                        err;
-       GAIPerfContext * const          context = (GAIPerfContext *) inContext;
-       int                                                     namesAreDynamic, namesAreUnique;
-       const char *                            ptr;
-       size_t                                          count, startIndex;
-       CFMutableArrayRef                       results = NULL;
-       GAIPerfStats                            stats, firstStats, connStats;
-       double                                          sum, firstSum, connSum, value, diff;
-       size_t                                          keyValueLen, i;
-       char                                            keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes.
-       
-       // If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value
-       // pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with the
-       // domain name's CNAME, A, and AAAA records.
-       
-       namesAreDynamic = false;
-       namesAreUnique  = false;
-       ptr = inCaseTitle;
-       while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
-       {
-               if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
-               {
-                       namesAreDynamic = true;
-               }
-               else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+       uint8_t *                                       recordName      = NULL;
+       uint8_t *                                       rdataPtr        = NULL;
+       size_t                                          rdataLen;
+       MRResourceRecord *                      answer;
+       MRResourceRecord **                     answerPtr;
+       const uint8_t * const           hostname        = inContext->hostname;
+       unsigned int                            i;
+       uint32_t                                        index;
+       unsigned int                            count, txtSize;
+       
+       require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr );
+       require_action_quiet( inClass == kDNSServiceClass_IN, exit, err = kNoErr );
+       
+       for( answerPtr = inAnswerList; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next )
+       {
+               if( ( answer->type == inType ) && DomainNameEqual( answer->name, inName ) )
                {
-                       namesAreUnique = true;
+                       err = kNoErr;
+                       goto exit;
                }
-               if( namesAreDynamic && namesAreUnique ) break;
-       }
-       
-       if( namesAreDynamic && !namesAreUnique && ( inItemCount > 0 ) )
-       {
-               count           = ( inResultCount > 0 ) ? ( inResultCount - 1 ) : 0;
-               startIndex      = 1;
-       }
-       else
-       {
-               count           = inResultCount;
-               startIndex      = 0;
        }
        
-       results = CFArrayCreateMutable( NULL, (CFIndex) count, &kCFTypeArrayCallBacks );
-       require_action( results, exit, err = kNoMemoryErr );
+       // Index 0 is reserved for answering queries about the mdnsreplier, while all other index values up to the maximum
+       // instance count are for answering queries about service instances.
        
-       GAIPerfStatsInit( &stats );
-       GAIPerfStatsInit( &firstStats );
-       GAIPerfStatsInit( &connStats );
-       
-       sum                     = 0.0;
-       firstSum        = 0.0;
-       connSum         = 0.0;
-       for( i = startIndex; i < count; ++i )
+       if( inIndex == 0 )
+       {
+               if( _MDNSReplierAboutRecordNameMatch( inContext, inName ) )
+               {
+                       int             listHasTXT = false;
+                       
+                       if( inType == kDNSServiceType_ANY )
+                       {
+                               for( answer = *inAnswerList; answer; answer = answer->next )
+                               {
+                                       if( ( answer->type == kDNSServiceType_TXT ) && DomainNameEqual( answer->name, inName ) )
+                                       {
+                                               listHasTXT = true;
+                                               break;
+                                       }
+                               }
+                       }
+                       
+                       if( ( inType == kDNSServiceType_TXT ) || ( ( inType == kDNSServiceType_ANY ) && !listHasTXT ) )
+                       {
+                               err = DomainNameDupLower( inName, &recordName, NULL );
+                               require_noerr( err, exit );
+                               
+                               err = CreateTXTRecordDataFromString( "ready=yes", ',', &rdataPtr, &rdataLen );
+                               require_noerr( err, exit );
+                               
+                               err = _MRResourceRecordCreate( recordName, kDNSServiceType_TXT, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+                                       (uint16_t) rdataLen, rdataPtr, &answer );
+                               require_noerr( err, exit );
+                               recordName      = NULL;
+                               rdataPtr        = NULL;
+                               
+                               *answerPtr = answer;
+                       }
+                       else if( inType == kDNSServiceType_NSEC )
+                       {
+                               err = DomainNameDupLower( inName, &recordName, NULL );
+                               require_noerr( err, exit );
+                               
+                               err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_TXT );
+                               require_noerr( err, exit );
+                               
+                               err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+                                       (uint16_t) rdataLen, rdataPtr, &answer );
+                               require_noerr( err, exit );
+                               recordName      = NULL;
+                               rdataPtr        = NULL;
+                               
+                               *answerPtr = answer;
+                       }
+               }
+       }
+       else if( _MDNSReplierHostnameMatch( inContext, inName, &index ) && ( index == inIndex ) )
        {
-               value = (double) inResults[ i ].timeUs;
-               if( value < stats.min ) stats.min = value;
-               if( value > stats.max ) stats.max = value;
-               sum += value;
+               int             listHasA        = false;
+               int             listHasAAAA     = false;
                
-               value = (double) inResults[ i ].firstTimeUs;
-               if( value < firstStats.min ) firstStats.min = value;
-               if( value > firstStats.max ) firstStats.max = value;
-               firstSum += value;
+               if( inType == kDNSServiceType_ANY )
+               {
+                       for( answer = *inAnswerList; answer; answer = answer->next )
+                       {
+                               if( answer->type == kDNSServiceType_A )
+                               {
+                                       if( !listHasA && DomainNameEqual( answer->name, inName ) ) listHasA = true;
+                               }
+                               else if( answer->type == kDNSServiceType_AAAA )
+                               {
+                                       if( !listHasAAAA && DomainNameEqual( answer->name, inName ) ) listHasAAAA = true;
+                               }
+                               if( listHasA && listHasAAAA ) break;
+                       }
+               }
                
-               value = (double) inResults[ i ].connectionTimeUs;
-               if( value < connStats.min ) connStats.min = value;
-               if( value > connStats.max ) connStats.max = value;
-               connSum += value;
+               if( ( inType == kDNSServiceType_A ) || ( ( inType == kDNSServiceType_ANY ) && !listHasA ) )
+               {
+                       for( i = 1; i <= inContext->recordCountA; ++i )
+                       {
+                               err = DomainNameDupLower( inName, &recordName, NULL );
+                               require_noerr( err, exit );
+                               
+                               rdataLen = 4;
+                               rdataPtr = (uint8_t *) malloc( rdataLen );
+                               require_action( rdataPtr, exit, err = kNoMemoryErr );
+                               
+                               rdataPtr[ 0 ] = 0;
+                               WriteBig16( &rdataPtr[ 1 ], inIndex );
+                               rdataPtr[ 3 ] = (uint8_t) i;
+                               
+                               err = _MRResourceRecordCreate( recordName, kDNSServiceType_A, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+                                       (uint16_t) rdataLen, rdataPtr, &answer );
+                               require_noerr( err, exit );
+                               recordName      = NULL;
+                               rdataPtr        = NULL;
+                               
+                               *answerPtr = answer;
+                                answerPtr = &answer->next;
+                       }
+               }
                
-               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
-                       "{"
-                               "%kO=%s"
-                               "%kO=%lli"
-                               "%kO=%lli"
-                               "%kO=%lli"
-                       "}",
-                       kGAIPerfTestCaseResultKey_Name,                         inResults[ i ].name,
-                       kGAIPerfTestCaseResultKey_ConnectionTime,       inResults[ i ].connectionTimeUs,
-                       kGAIPerfTestCaseResultKey_FirstTime,            inResults[ i ].firstTimeUs,
-                       kGAIPerfTestCaseResultKey_Time,                         inResults[ i ].timeUs );
-               require_noerr( err, exit );
+               if( ( inType == kDNSServiceType_AAAA ) || ( ( inType == kDNSServiceType_ANY ) && !listHasAAAA ) )
+               {
+                       for( i = 1; i <= inContext->recordCountAAAA; ++i )
+                       {
+                               err = DomainNameDupLower( inName, &recordName, NULL );
+                               require_noerr( err, exit );
+                               
+                               rdataLen = 16;
+                               rdataPtr = (uint8_t *) memdup( kMDNSReplierBaseAddrV6, rdataLen );
+                               require_action( rdataPtr, exit, err = kNoMemoryErr );
+                               
+                               WriteBig16( &rdataPtr[ 12 ], inIndex );
+                               rdataPtr[ 15 ] = (uint8_t) i;
+                               
+                               err = _MRResourceRecordCreate( recordName, kDNSServiceType_AAAA, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+                                       (uint16_t) rdataLen, rdataPtr, &answer );
+                               require_noerr( err, exit );
+                               recordName      = NULL;
+                               rdataPtr        = NULL;
+                               
+                               *answerPtr = answer;
+                                answerPtr = &answer->next;
+                       }
+               }
+               else if( inType == kDNSServiceType_NSEC )
+               {
+                       err = DomainNameDupLower( inName, &recordName, NULL );
+                       require_noerr( err, exit );
+                       
+                       if( ( inContext->recordCountA > 0 ) && ( inContext->recordCountAAAA > 0 ) )
+                       {
+                               err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 2, kDNSServiceType_A, kDNSServiceType_AAAA );
+                               require_noerr( err, exit );
+                       }
+                       else if( inContext->recordCountA > 0 )
+                       {
+                               err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_A );
+                               require_noerr( err, exit );
+                       }
+                       else if( inContext->recordCountAAAA > 0 )
+                       {
+                               err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_AAAA );
+                               require_noerr( err, exit );
+                       }
+                       else
+                       {
+                               err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 0 );
+                               require_noerr( err, exit );
+                       }
+                       
+                       err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+                               (uint16_t) rdataLen, rdataPtr, &answer );
+                       require_noerr( err, exit );
+                       recordName      = NULL;
+                       rdataPtr        = NULL;
+                       
+                       *answerPtr = answer;
+               }
        }
-       
-       if( count > 0 )
+       else if( _MDNSReplierServiceTypeMatch( inContext, inName, NULL, &count ) && ( count >= inIndex ) )
        {
-               stats.mean              = sum      / count;
-               firstStats.mean = firstSum / count;
-               connStats.mean  = connSum  / count;
+               int             listHasPTR = false;
                
-               sum                     = 0.0;
-               firstSum        = 0.0;
-               connSum         = 0.0;
-               for( i = startIndex; i < count; ++i )
+               if( inType == kDNSServiceType_ANY )
                {
-                       diff             = stats.mean - (double) inResults[ i ].timeUs;
-                       sum                     += ( diff * diff );
+                       for( answer = *inAnswerList; answer; answer = answer->next )
+                       {
+                               if( ( answer->type == kDNSServiceType_PTR ) && DomainNameEqual( answer->name, inName ) )
+                               {
+                                       listHasPTR = true;
+                                       break;
+                               }
+                       }
+               }
+               
+               if( ( inType == kDNSServiceType_PTR ) || ( ( inType == kDNSServiceType_ANY ) && !listHasPTR ) )
+               {
+                       size_t                          recordNameLen;
+                       uint8_t *                       ptr;
+                       uint8_t *                       lim;
                        
-                       diff             = firstStats.mean - (double) inResults[ i ].firstTimeUs;
-                       firstSum        += ( diff * diff );
+                       err = DomainNameDupLower( inName, &recordName, &recordNameLen );
+                       require_noerr( err, exit );
                        
-                       diff             = connStats.mean - (double) inResults[ i ].connectionTimeUs;
-                       connSum         += ( diff * diff );
+                       rdataLen = 1 + hostname[ 0 ] + 10 + recordNameLen;
+                       rdataPtr = (uint8_t *) malloc( rdataLen );
+                       require_action( rdataPtr, exit, err = kNoMemoryErr );
+                       
+                       lim = &rdataPtr[ rdataLen ];
+                       
+                       ptr = &rdataPtr[ 1 ];
+                       memcpy( ptr, &hostname[ 1 ], hostname[ 0 ] );
+                       ptr += hostname[ 0 ];
+                       if( inIndex != 1 ) SNPrintF_Add( (char **) &ptr, (char *) lim, " (%u)", inIndex );
+                       rdataPtr[ 0 ] = (uint8_t)( ptr - &rdataPtr[ 1 ] );
+                       
+                       check( (size_t)( lim - ptr ) >= recordNameLen );
+                       memcpy( ptr, recordName, recordNameLen );
+                       ptr += recordNameLen;
+                       
+                       rdataLen = (size_t)( ptr - rdataPtr );
+                       
+                       err = _MRResourceRecordCreate( recordName, kDNSServiceType_PTR, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+                               (uint16_t) rdataLen, rdataPtr, &answer );
+                       require_noerr( err, exit );
+                       recordName      = NULL;
+                       rdataPtr        = NULL;
+                       
+                       *answerPtr = answer;
                }
-               stats.stdDev            = sqrt( sum      / count );
-               firstStats.stdDev       = sqrt( firstSum / count );
-               connStats.stdDev        = sqrt( connSum  / count );
        }
-       
-       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->caseResults,
-               "{"
-                       "%kO=%s"
-                       "%kO=%lli"
-                       "%kO=%lli"
-                       "%kO=%O"
-                       "%kO="
-                       "{"
-                               "%kO=%lli"
-                               "%kO=%f"
-                               "%kO=%f"
-                               "%kO=%f"
-                               "%kO=%f"
-                       "}"
-                       "%kO="
-                       "{"
-                               "%kO=%lli"
-                               "%kO=%f"
-                               "%kO=%f"
-                               "%kO=%f"
-                               "%kO=%f"
-                       "}"
-                       "%kO="
-                       "{"
-                               "%kO=%lli"
-                               "%kO=%f"
-                               "%kO=%f"
-                               "%kO=%f"
-                               "%kO=%f"
-                       "}"
-                       "%kO=%b"
-               "}",
-               kGAIPerfTestCaseKey_Title,                      inCaseTitle,
-               kGAIPerfTestCaseKey_StartTime,          (int64_t) inCaseStartTime,
-               kGAIPerfTestCaseKey_EndTime,            (int64_t) inCaseEndTime,
-               kGAIPerfTestCaseKey_Results,            results,
-               kGAIPerfTestCaseKey_Stats,
-               kGAIPerfTestCaseStatsKey_Count,         (int64_t) count,
-               kGAIPerfTestCaseStatsKey_Min,           stats.min,
-               kGAIPerfTestCaseStatsKey_Max,           stats.max,
-               kGAIPerfTestCaseStatsKey_Mean,          stats.mean,
-               kGAIPerfTestCaseStatsKey_StdDev,        stats.stdDev,
-               kGAIPerfTestCaseKey_FirstStats,
-               kGAIPerfTestCaseStatsKey_Count,         (int64_t) count,
-               kGAIPerfTestCaseStatsKey_Min,           firstStats.min,
-               kGAIPerfTestCaseStatsKey_Max,           firstStats.max,
-               kGAIPerfTestCaseStatsKey_Mean,          firstStats.mean,
-               kGAIPerfTestCaseStatsKey_StdDev,        firstStats.stdDev,
-               kGAIPerfTestCaseKey_ConnectionStats,
-               kGAIPerfTestCaseStatsKey_Count,         (int64_t) count,
-               kGAIPerfTestCaseStatsKey_Min,           connStats.min,
-               kGAIPerfTestCaseStatsKey_Max,           connStats.max,
-               kGAIPerfTestCaseStatsKey_Mean,          connStats.mean,
-               kGAIPerfTestCaseStatsKey_StdDev,        connStats.stdDev,
-               kGAIPerfTestCaseKey_TimedOut,           ( inResultCount < inItemCount ) ? true : false );
-       require_noerr( err, exit );
+       else if( _MDNSReplierServiceInstanceNameMatch( inContext, inName, &index, &txtSize, &count ) &&
+               ( index == inIndex ) && ( count >= inIndex ) )
+       {
+               int             listHasSRV = false;
+               int             listHasTXT = false;
+               
+               if( inType == kDNSServiceType_ANY )
+               {
+                       for( answer = *inAnswerList; answer; answer = answer->next )
+                       {
+                               if( answer->type == kDNSServiceType_SRV )
+                               {
+                                       if( !listHasSRV && DomainNameEqual( answer->name, inName ) ) listHasSRV = true;
+                               }
+                               else if( answer->type == kDNSServiceType_TXT )
+                               {
+                                       if( !listHasTXT && DomainNameEqual( answer->name, inName ) ) listHasTXT = true;
+                               }
+                               if( listHasSRV && listHasTXT ) break;
+                       }
+               }
+               
+               if( ( inType == kDNSServiceType_SRV ) || ( ( inType == kDNSServiceType_ANY ) && !listHasSRV ) )
+               {
+                       SRVRecordDataFixedFields *              fields;
+                       uint8_t *                                               ptr;
+                       uint8_t *                                               lim;
+                       uint8_t *                                               targetPtr;
+                       
+                       err = DomainNameDupLower( inName, &recordName, NULL );
+                       require_noerr( err, exit );
+                       
+                       rdataLen = sizeof( SRVRecordDataFixedFields ) + 1 + hostname[ 0 ] + 10 + kLocalNameLen;
+                       rdataPtr = (uint8_t *) malloc( rdataLen );
+                       require_action( rdataPtr, exit, err = kNoMemoryErr );
+                       
+                       lim = &rdataPtr[ rdataLen ];
+                       
+                       fields = (SRVRecordDataFixedFields *) rdataPtr;
+                       SRVRecordDataFixedFieldsSet( fields, 0, 0, (uint16_t)( kMDNSReplierPortBase + txtSize ) );
+                       
+                       targetPtr = (uint8_t *) &fields[ 1 ];
+                       
+                       ptr = &targetPtr[ 1 ];
+                       memcpy( ptr, &hostname[ 1 ], hostname[ 0 ] );
+                       ptr += hostname[ 0 ];
+                       if( inIndex != 1 ) SNPrintF_Add( (char **) &ptr, (char *) lim, "-%u", inIndex );
+                       targetPtr[ 0 ] = (uint8_t)( ptr - &targetPtr[ 1 ] );
+                       
+                       check( (size_t)( lim - ptr ) >= kLocalNameLen );
+                       memcpy( ptr, kLocalName, kLocalNameLen );
+                       ptr += kLocalNameLen;
+                       
+                       rdataLen = (size_t)( ptr - rdataPtr );
+                       
+                       err = _MRResourceRecordCreate( recordName, kDNSServiceType_SRV, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+                               (uint16_t) rdataLen, rdataPtr, &answer );
+                       require_noerr( err, exit );
+                       recordName      = NULL;
+                       rdataPtr        = NULL;
+                       
+                       *answerPtr = answer;
+                        answerPtr = &answer->next;
+               }
+               
+               if( ( inType == kDNSServiceType_TXT ) || ( ( inType == kDNSServiceType_ANY ) && !listHasTXT ) )
+               {
+                       err = DomainNameDupLower( inName, &recordName, NULL );
+                       require_noerr( err, exit );
+                       
+                       rdataLen = txtSize;
+                       err = _MDNSReplierCreateTXTRecord( inName, rdataLen, &rdataPtr );
+                       require_noerr( err, exit );
+                       
+                       err = _MRResourceRecordCreate( recordName, kDNSServiceType_TXT, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+                               (uint16_t) rdataLen, rdataPtr, &answer );
+                       require_noerr( err, exit );
+                       recordName      = NULL;
+                       rdataPtr        = NULL;
+                       
+                       *answerPtr = answer;
+               }
+               else if( inType == kDNSServiceType_NSEC )
+               {
+                       err = DomainNameDupLower( inName, &recordName, NULL );
+                       require_noerr( err, exit );
+                       
+                       err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 2, kDNSServiceType_TXT, kDNSServiceType_SRV );
+                       require_noerr( err, exit );
+                       
+                       err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+                               (uint16_t) rdataLen, rdataPtr, &answer );
+                       require_noerr( err, exit );
+                       recordName      = NULL;
+                       rdataPtr        = NULL;
+                       
+                       *answerPtr = answer;
+               }
+       }
+       err = kNoErr;
        
 exit:
-       CFReleaseNullSafe( results );
+       FreeNullSafe( recordName );
+       FreeNullSafe( rdataPtr );
+       return( err );
 }
 
 //===========================================================================================================================
-//     GAIPerfSignalHandler
+//     _MDNSReplierAnswerListRemovePTR
 //===========================================================================================================================
 
-static void    GAIPerfSignalHandler( void *inContext )
+static void
+       _MDNSReplierAnswerListRemovePTR(
+               MRResourceRecord **     inAnswerListPtr,
+               const uint8_t *         inName,
+               const uint8_t *         inRData )
 {
-       GAIPerfContext * const          context = (GAIPerfContext *) inContext;
+       MRResourceRecord *              answer;
+       MRResourceRecord **             answerPtr;
        
-       context->gotSignal = true;
-       if( context->tester && context->testerStarted )
+       for( answerPtr = inAnswerListPtr; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next )
        {
-               GAITesterStop( context->tester );
+               if( ( answer->type == kDNSServiceType_PTR ) && ( answer->class == kDNSServiceClass_IN ) &&
+                       DomainNameEqual( answer->name, inName ) && DomainNameEqual( answer->rdata, inRData ) ) break;
        }
-       else
+       if( answer )
        {
-               exit( 1 );
+               *answerPtr = answer->next;
+               _MRResourceRecordFree( answer );
        }
 }
 
 //===========================================================================================================================
-//     GAITesterCreate
+//     _MDNSReplierSendOrDropResponse
 //===========================================================================================================================
 
-typedef enum
+static OSStatus
+       _MDNSReplierSendOrDropResponse(
+               MDNSReplierContext *    inContext,
+               MRResourceRecord *              inAnswerList,
+               sockaddr_ip *                   inQuerier,
+               SocketRef                               inSock,
+               unsigned int                    inIndex,
+               Boolean                                 inUnicast )
 {
-       kGAITestConnType_UseMainConnection              = 1,
-       kGAITestConnType_OwnSharedConnection    = 2
+       OSStatus                                        err;
+       uint8_t *                                       responsePtr     = NULL;
+       size_t                                          responseLen;
+       const struct sockaddr *         destAddr;
+       ssize_t                                         n;
+       const double                            dropRate        = inUnicast ? inContext->ucastDropRate : inContext->mcastDropRate;
+       int                                                     drop;
        
-}      GAITestConnType;
-
-typedef struct GAITestItem             GAITestItem;
-struct GAITestItem
-{
-       GAITestItem *           next;                           // Next test item in list.
-       char *                          name;                           // Domain name to resolve.
-       int64_t                         connectionTimeUs;       // Time in microseconds that it took to create a DNS-SD connection.
-       int64_t                         firstTimeUs;            // Time in microseconds that it took to get the first address result.
-       int64_t                         timeUs;                         // Time in microseconds that it took to get all expected address results.
-       unsigned int            addressCount;           // Address count of the domain name, i.e., the Count label argument.
-       Boolean                         hasV4;                          // True if the domain name has one or more IPv4 addresses.
-       Boolean                         hasV6;                          // True if the domain name has one or more IPv6 addresses.
-       Boolean                         wantV4;                         // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses.
-       Boolean                         wantV6;                         // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses.
-};
-
-struct GAITestCase
-{
-       GAITestCase *           next;                   // Next test case in list.
-       GAITestItem *           itemList;               // List of test items.
-       char *                          title;                  // Title of the test case.
-       unsigned int            timeLimitMs;    // Time limit in milliseconds for the test case's completion.
-};
-
-struct GAITesterPrivate
-{
-       CFRuntimeBase                                   base;                           // CF object base.
-       dispatch_queue_t                                queue;                          // Serial work queue.
-       DNSServiceRef                                   mainRef;                        // Reference to the main shared DNS-SD connection.
-       DNSServiceRef                                   opRef;                          // Reference to the current DNSServiceGetAddrInfo operation.
-       GAITestCase *                                   caseList;                       // List of test cases.
-       GAITestCase *                                   currentCase;            // Pointer to the current test case.
-       GAITestItem *                                   currentItem;            // Pointer to the current test item.
-       MicroTime64                                             caseStartTime;          // Start time of current test case in Unix time as microseconds.
-       MicroTime64                                             caseEndTime;            // End time of current test case in Unix time as microseconds.
-       Boolean                                                 started;                        // True if the tester has been successfully started.
-       Boolean                                                 stopped;                        // True if the tester has been stopped.
-       int                                                             callDelayMs;            // Amount of time to wait before calling DNSServiceGetAddrInfo().
-       dispatch_source_t                               caseTimer;                      // Timer for enforcing a test case time limits.
-       pcap_t *                                                pcap;                           // Captures traffic between mDNSResponder and test DNS server.
-       pid_t                                                   serverPID;                      // PID of the test DNS server.
-       int                                                             serverDelayMs;          // Additional time to have the server delay its responses by.
-       int                                                             serverDefaultTTL;       // Default TTL for the server's records.
-       GAITesterEventHandler_f                 eventHandler;           // User's event handler.
-       void *                                                  eventContext;           // User's event handler context.
-       GAITesterResultsHandler_f               resultsHandler;         // User's results handler.
-       void *                                                  resultsContext;         // User's results handler context.
+       check( inIndex <= inContext->maxInstanceCount );
        
-       // Variables for current test item.
+       // If maxDropCount > 0, then the drop rates apply only to the first maxDropCount responses. Otherwise, all messages are
+       // subject to their respective drop rate. Also, responses to queries about mDNS replier itself (indicated by index 0),
+       // as opposed to those for service instance records, are never dropped.
        
-       uint64_t                                                bitmapV4;               // Bitmap of IPv4 results that have yet to be received.
-       uint64_t                                                bitmapV6;               // Bitmap of IPv6 results that have yet to be received.
-       uint64_t                                                startTicks;             // Start ticks of DNSServiceGetAddrInfo().
-       uint64_t                                                connTicks;              // Ticks when the connection was created.
-       uint64_t                                                firstTicks;             // Ticks when the first DNSServiceGetAddrInfo result was received.
-       uint64_t                                                endTicks;               // Ticks when the last DNSServiceGetAddrInfo result was received.
-       Boolean                                                 gotFirstResult; // True if the first result has been received.
-};
-
-CF_CLASS_DEFINE( GAITester );
-
-static void            _GAITesterRun( void *inContext );
-static OSStatus        _GAITesterCreatePacketCapture( pcap_t **outPCap );
-static void            _GAITesterTimeout( void *inContext );
-static void            _GAITesterAdvanceCurrentItem( GAITesterRef inTester );
-static void            _GAITesterAdvanceCurrentSet( GAITesterRef inTester );
-static void            _GAITesterInitializeCurrentTest( GAITesterRef inTester );
-static void DNSSD_API
-       _GAITesterGetAddrInfoCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inHostname,
-               const struct sockaddr * inSockAddr,
-               uint32_t                                inTTL,
-               void *                                  inContext );
-static void            _GAITesterCompleteCurrentTest( GAITesterRef inTester, Boolean inTimedOut );
-
-#define ForgetPacketCapture( X )               ForgetCustom( X, pcap_close )
-
-static OSStatus
-       GAITestItemCreate(
-               const char *    inName,
-               unsigned int    inAddressCount,
-               GAITestAddrType inHasAddrs,
-               GAITestAddrType inWantAddrs,
-               GAITestItem **  outItem );
-static OSStatus        GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem );
-static void            GAITestItemFree( GAITestItem *inItem );
-
-static OSStatus
-       GAITesterCreate(
-               dispatch_queue_t        inQueue,
-               int                                     inCallDelayMs,
-               int                                     inServerDelayMs,
-               int                                     inServerDefaultTTL,
-               GAITesterRef *          outTester )
-{
-       OSStatus                        err;
-       GAITesterRef            obj = NULL;
+       drop = false;
+       if( inIndex > 0 )
+       {
+               if( inContext->maxDropCount > 0 )
+               {
+                       uint8_t * const         dropCount = &inContext->dropCounters[ inIndex - 1 ];
+                       
+                       if( *dropCount < inContext->maxDropCount )
+                       {
+                               if( ShouldDrop( dropRate ) ) drop = true;
+                               *dropCount += 1;
+                       }
+               }
+               else if( ShouldDrop( dropRate ) )
+               {
+                       drop = true;
+               }
+       }
        
-       CF_OBJECT_CREATE( GAITester, obj, err, exit );
+       err = _MDNSReplierCreateResponse( inContext, inAnswerList, inIndex, &responsePtr, &responseLen );
+       require_noerr( err, exit );
        
-       ReplaceDispatchQueue( &obj->queue, inQueue );
-       obj->callDelayMs                = inCallDelayMs;
-       obj->serverPID                  = -1;
-       obj->serverDelayMs              = inServerDelayMs;
-       obj->serverDefaultTTL   = inServerDefaultTTL;
+       if( inUnicast )
+       {
+               destAddr = &inQuerier->sa;
+       }
+       else
+       {
+               destAddr = ( inQuerier->sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+       }
        
-       *outTester = obj;
-       obj = NULL;
-       err = kNoErr;
+       mr_ulog( kLogLevelInfo, "%s %zu byte response to %##a:\n\n%#1{du:dnsmsg}",
+               drop ? "Dropping" : "Sending", responseLen, destAddr, responsePtr, responseLen );
+       
+       if( !drop )
+       {
+               n = sendto( inSock, (char *) responsePtr, responseLen, 0, destAddr, SockAddrGetSize( destAddr ) );
+               err = map_socket_value_errno( inSock, n == (ssize_t) responseLen, n );
+               require_noerr( err, exit );
+       }
        
 exit:
-       CFReleaseNullSafe( obj );
+       FreeNullSafe( responsePtr );
        return( err );
 }
 
 //===========================================================================================================================
-//     _GAITesterFinalize
+//     _MDNSReplierCreateResponse
 //===========================================================================================================================
 
-static void    _GAITesterFinalize( CFTypeRef inObj )
+static OSStatus
+       _MDNSReplierCreateResponse(
+               MDNSReplierContext *    inContext,
+               MRResourceRecord *              inAnswerList,
+               unsigned int                    inIndex,
+               uint8_t **                              outResponsePtr,
+               size_t *                                outResponseLen )
 {
-       GAITesterRef const              me = (GAITesterRef) inObj;
-       GAITestCase *                   testCase;
+       OSStatus                                err;
+       DataBuffer                              responseDB;
+       DNSHeader                               hdr;
+       MRResourceRecord *              answer;
+       uint8_t *                               responsePtr;
+       size_t                                  responseLen, len;
+       unsigned int                    answerCount, recordCount;
+       MRNameOffsetItem *              nameOffsetList = NULL;
        
-       check( !me->opRef );
-       check( !me->mainRef );
-       check( !me->caseTimer );
-       dispatch_forget( &me->queue );
-       while( ( testCase = me->caseList ) != NULL )
+       DataBuffer_Init( &responseDB, NULL, 0, SIZE_MAX );
+       
+       // The current answers in the answer list will make up the response's Answer Record Section.
+       
+       answerCount = 0;
+       for( answer = inAnswerList; answer; answer = answer->next ) { ++answerCount; }
+       
+       // Unless configured not to, add any additional answers to the answer list for the Additional Record Section.
+       
+       if( !inContext->noAdditionals )
        {
-               me->caseList = testCase->next;
-               GAITestCaseFree( testCase );
+               for( answer = inAnswerList; answer; answer = answer->next )
+               {
+                       switch( answer->type )
+                       {
+                               case kDNSServiceType_PTR:
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->rdata, kDNSServiceType_SRV,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->rdata, kDNSServiceType_TXT,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       break;
+                               
+                               case kDNSServiceType_SRV:
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->target, kDNSServiceType_A,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->target, kDNSServiceType_AAAA,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       break;
+                               
+                               case kDNSServiceType_TXT:
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       break;
+                               
+                               case kDNSServiceType_A:
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_AAAA,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       break;
+                               
+                               case kDNSServiceType_AAAA:
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_A,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       
+                                       err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+                                               answer->class );
+                                       require_noerr( err, exit );
+                                       break;
+                               
+                               default:
+                                       break;
+                       }
+               }
        }
-}
-
-//===========================================================================================================================
-//     GAITesterStart
-//===========================================================================================================================
-
-static void    _GAITesterStart( void *inContext );
-static void    _GAITesterStop( GAITesterRef me );
-
-static void    GAITesterStart( GAITesterRef me )
-{
-       CFRetain( me );
-       dispatch_async_f( me->queue, me, _GAITesterStart );
-}
-
-extern char **         environ;
-
-static void    _GAITesterStart( void *inContext )
-{
-       OSStatus                                err;
-       GAITesterRef const              me = (GAITesterRef) inContext;
-       char *                                  argv[ 4 ];
-       char *                                  ptr;
-       char *                                  end;
-       char                                    command[ 128 ];
        
-       ptr = &command[ 0 ];
-       end = &command[ countof( command ) ];
-       SNPrintF_Add( &ptr, end, "dnssdutil server --loopback --followPID %lld", (int64_t) getpid() );
-       if( me->serverDefaultTTL >= 0 ) SNPrintF_Add( &ptr, end, " --defaultTTL %d", me->serverDefaultTTL );
-       if( me->serverDelayMs    >= 0 ) SNPrintF_Add( &ptr, end, " --responseDelay %d", me->serverDelayMs );
+       // Append a provisional header to the response message.
        
-       argv[ 0 ] = "/bin/sh";
-       argv[ 1 ] = "-c";
-       argv[ 2 ] = command;
-       argv[ 3 ] = NULL;
-       err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ );
+       memset( &hdr, 0, sizeof( hdr ) );
+       DNSHeaderSetFlags( &hdr, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer );
+       
+       err = DataBuffer_Append( &responseDB, &hdr, sizeof( hdr ) );
        require_noerr( err, exit );
        
-       me->currentCase = me->caseList;
-       me->currentItem = me->currentCase ? me->currentCase->itemList : NULL;
-       _GAITesterInitializeCurrentTest( me );
+       // Append answers to response message.
+       
+       responseLen = DataBuffer_GetLen( &responseDB );
+       recordCount = 0;
+       for( answer = inAnswerList; answer; answer = answer->next )
+       {
+               DNSRecordFixedFields            fields;
+               unsigned int                            class;
+               
+               // Append record NAME.
+               
+               err = _MDNSReplierAppendNameToResponse( &responseDB, answer->name, &nameOffsetList );
+               require_noerr( err, exit );
+               
+               // Append record TYPE, CLASS, TTL, and provisional RDLENGTH.
+               
+               class = answer->class;
+               if( ( answer->type == kDNSServiceType_SRV ) || ( answer->type == kDNSServiceType_TXT )  ||
+                       ( answer->type == kDNSServiceType_A )   || ( answer->type == kDNSServiceType_AAAA ) ||
+                       ( answer->type == kDNSServiceType_NSEC ) )
+               {
+                       class |= kRRClassCacheFlushBit;
+               }
+               
+               DNSRecordFixedFieldsSet( &fields, answer->type, (uint16_t) class, answer->ttl, (uint16_t) answer->rdlength );
+               err = DataBuffer_Append( &responseDB, &fields, sizeof( fields ) );
+               require_noerr( err, exit );
+               
+               // Append record RDATA.
+               // The RDATA of PTR, SRV, and NSEC records contain domain names, which are subject to name compression.
+               
+               if( ( answer->type == kDNSServiceType_PTR ) || ( answer->type == kDNSServiceType_SRV ) ||
+                       ( answer->type == kDNSServiceType_NSEC ) )
+               {
+                       size_t                          rdlength;
+                       uint8_t *                       rdLengthPtr;
+                       const size_t            rdLengthOffset  = DataBuffer_GetLen( &responseDB ) - 2;
+                       const size_t            rdataOffset             = DataBuffer_GetLen( &responseDB );
+                       
+                       if( answer->type == kDNSServiceType_PTR )
+                       {
+                               err = _MDNSReplierAppendNameToResponse( &responseDB, answer->rdata, &nameOffsetList );
+                               require_noerr( err, exit );
+                       }
+                       else if( answer->type == kDNSServiceType_SRV )
+                       {
+                               require_fatal( answer->target == &answer->rdata[ 6 ], "Bad SRV record target pointer." );
+                               
+                               err = DataBuffer_Append( &responseDB, answer->rdata, (size_t)( answer->target - answer->rdata ) );
+                               require_noerr( err, exit );
+                               
+                               err = _MDNSReplierAppendNameToResponse( &responseDB, answer->target, &nameOffsetList );
+                               require_noerr( err, exit );
+                       }
+                       else
+                       {
+                               const size_t            nameLen = DomainNameLength( answer->rdata );
+                               
+                               err = _MDNSReplierAppendNameToResponse( &responseDB, answer->rdata, &nameOffsetList );
+                               require_noerr( err, exit );
+                               
+                               require_fatal( answer->rdlength > nameLen, "Bad NSEC record data length." );
+                               
+                               err = DataBuffer_Append( &responseDB, &answer->rdata[ nameLen ], answer->rdlength - nameLen );
+                               require_noerr( err, exit );
+                       }
+                       
+                       // Set the actual RDLENGTH, which may be less than the original due to name compression.
+                       
+                       rdlength = DataBuffer_GetLen( &responseDB ) - rdataOffset;
+                       check( rdlength <= UINT16_MAX );
+                       
+                       rdLengthPtr = DataBuffer_GetPtr( &responseDB ) + rdLengthOffset;
+                       WriteBig16( rdLengthPtr, rdlength );
+               }
+               else
+               {
+                       err = DataBuffer_Append( &responseDB, answer->rdata, answer->rdlength );
+                       require_noerr( err, exit );
+               }
+               
+               if( DataBuffer_GetLen( &responseDB ) > kMDNSMessageSizeMax ) break;
+               responseLen = DataBuffer_GetLen( &responseDB );
+               ++recordCount;
+       }
        
-       // Hack: The first tester run is delayed for three seconds to allow the test DNS server to start up.
-       // A better way to handle this is to issue an asynchronous query for something in the d.test. domain. As soon as an
-       // expected response is received, the server can be considered to be up and running.
+       // Set the response header's Answer and Additional record counts.
+       // Note: recordCount may be less than answerCount if including all answerCount records would cause the size of the
+       // response message to exceed the maximum mDNS message size.
        
-       CFRetain( me );
-       dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun );
+       if( recordCount <= answerCount )
+       {
+               DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount );
+       }
+       else
+       {
+               DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), answerCount );
+               DNSHeaderSetAdditionalCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount - answerCount );
+       }
        
-       CFRetain( me );
-       me->started = true;
-       if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext );
+       err = DataBuffer_Detach( &responseDB, &responsePtr, &len );
+       require_noerr( err, exit );
+       
+       if( outResponsePtr ) *outResponsePtr = responsePtr;
+       if( outResponseLen ) *outResponseLen = responseLen;
        
 exit:
-       if( err ) _GAITesterStop( me );
-       CFRelease( me );
+       _MRNameOffsetItemFreeList( nameOffsetList );
+       DataBuffer_Free( &responseDB );
+       return( err );
 }
 
 //===========================================================================================================================
-//     GAITesterStop
+//     _MDNSReplierAppendNameToResponse
 //===========================================================================================================================
 
-static void    _GAITesterUserStop( void *inContext );
-
-static void    GAITesterStop( GAITesterRef me )
-{
-       CFRetain( me );
-       dispatch_async_f( me->queue, me, _GAITesterUserStop );
-}
-
-static void    _GAITesterUserStop( void *inContext )
+static OSStatus
+       _MDNSReplierAppendNameToResponse(
+               DataBuffer *            inResponse,
+               const uint8_t *         inName,
+               MRNameOffsetItem **     inNameOffsetListPtr )
 {
-       GAITesterRef const              me = (GAITesterRef) inContext;
+       OSStatus                                err;
+       const uint8_t *                 subname;
+       const uint8_t *                 limit;
+       size_t                                  nameOffset;
+       MRNameOffsetItem *              item;
+       uint8_t                                 compressionPtr[ 2 ];
        
-       _GAITesterStop( me );
-       CFRelease( me );
-}
-
-static void    _GAITesterStop( GAITesterRef me )
-{
-       OSStatus                err;
+       nameOffset = DataBuffer_GetLen( inResponse );
        
-       DNSServiceForget( &me->opRef );
-       DNSServiceForget( &me->mainRef );
-       ForgetPacketCapture( &me->pcap );
-       dispatch_source_forget( &me->caseTimer );
-       if( me->serverPID != -1 )
+       // Find the name's longest subname (more accurately, its longest sub-FQDN) in the name compression list.
+       
+       for( subname = inName; subname[ 0 ] != 0; subname += ( 1 + subname[ 0 ] ) )
        {
-               err = kill( me->serverPID, SIGTERM );
-               err = map_global_noerr_errno( err );
-               check_noerr( err );
+               for( item = *inNameOffsetListPtr; item; item = item->next )
+               {
+                       if( DomainNameEqual( item->name, subname ) ) break;
+               }
+               
+               // If an item was found for this subname, then append a name compression pointer and we're done. Otherwise, append
+               // the subname's first label.
+               
+               if( item )
+               {
+                       WriteDNSCompressionPtr( compressionPtr, item->offset );
+                       
+                       err = DataBuffer_Append( inResponse, compressionPtr, sizeof( compressionPtr ) );
+                       require_noerr( err, exit );
+                       break;
+               }
+               else
+               {
+                       err = DataBuffer_Append( inResponse, subname, 1 + subname[ 0 ] );
+                       require_noerr( err, exit );
+               }
        }
+               
+       // If we made it to the root label, then no subname was able to be compressed. All of the name's labels up to the root
+       // label were appended to the response message, so a root label is needed to terminate the complete name.
        
-       if( !me->stopped )
+       if( subname[ 0 ] == 0 )
        {
-               me->stopped = true;
-               if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext );
-               if( me->started ) CFRelease( me );
+               err = DataBuffer_Append( inResponse, "", 1 );
+               require_noerr( err, exit );
        }
-}
-
-//===========================================================================================================================
-//     GAITesterAddCase
-//===========================================================================================================================
-
-static void    GAITesterAddCase( GAITesterRef me, GAITestCase *inCase )
-{
-       GAITestCase **          ptr;
        
-       for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {}
-       *ptr = inCase;
+       // Add subnames that weren't able to be compressed and their offsets to the name compression list.
+       
+       limit = subname;
+       for( subname = inName; subname < limit; subname += ( 1 + subname[ 0 ] ) )
+       {
+               const size_t            subnameOffset = nameOffset + (size_t)( subname - inName );
+               
+               if( subnameOffset > kDNSCompressionOffsetMax ) break;
+               
+               err = _MRNameOffsetItemCreate( subname, (uint16_t) subnameOffset, &item );
+               require_noerr( err, exit );
+               
+               item->next = *inNameOffsetListPtr;
+               *inNameOffsetListPtr = item;
+       }
+       err = kNoErr;
+       
+exit:
+       return( err );
 }
 
 //===========================================================================================================================
-//     GAITesterSetEventHandler
+//     _MDNSReplierServiceTypeMatch
 //===========================================================================================================================
 
-static void    GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext )
+static Boolean
+       _MDNSReplierServiceTypeMatch(
+               const MDNSReplierContext *      inContext,
+               const uint8_t *                         inName,
+               unsigned int *                          outTXTSize,
+               unsigned int *                          outCount )
 {
-       me->eventHandler = inEventHandler;
-       me->eventContext = inEventContext;
+       OSStatus                                        err;
+       const char *                            ptr;
+       const char *                            end;
+       uint32_t                                        txtSize, count;
+       const uint8_t * const           serviceLabel    = inContext->serviceLabel;
+       int                                                     nameMatches             = false;
+       
+       require_quiet( inName[ 0 ] >= serviceLabel[ 0 ], exit );
+       if( memicmp( &inName[ 1 ], &serviceLabel[ 1 ], serviceLabel[ 0 ] ) != 0 ) goto exit;
+       
+       ptr = (const char *) &inName[ 1 + serviceLabel[ 0 ] ];
+       end = (const char *) &inName[ 1 + inName[ 0 ] ];
+       
+       require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+       ++ptr;
+       
+       err = DecimalTextToUInt32( ptr, end, &txtSize, &ptr );
+       require_noerr_quiet( err, exit );
+       require_quiet( txtSize <= UINT16_MAX, exit );
+       
+       require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+       ++ptr;
+       
+       err = DecimalTextToUInt32( ptr, end, &count, &ptr );
+       require_noerr_quiet( err, exit );
+       require_quiet( count <= UINT16_MAX, exit );
+       require_quiet( ptr == end, exit );
+       
+       if( !DomainNameEqual( (const uint8_t *) ptr, (const uint8_t *) "\x04" "_tcp" "\x05" "local" ) ) goto exit;
+       nameMatches = true;
+       
+       if( outTXTSize )        *outTXTSize     = txtSize;
+       if( outCount )          *outCount       = count;
+       
+exit:
+       return( nameMatches ? true : false );
 }
 
 //===========================================================================================================================
-//     GAITesterSetResultsHandler
+//     _MDNSReplierServiceInstanceNameMatch
 //===========================================================================================================================
 
-static void    GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+static Boolean
+       _MDNSReplierServiceInstanceNameMatch(
+               const MDNSReplierContext *      inContext,
+               const uint8_t *                         inName,
+               unsigned int *                          outIndex,
+               unsigned int *                          outTXTSize,
+               unsigned int *                          outCount )
 {
-       me->resultsHandler = inResultsHandler;
-       me->resultsContext = inResultsContext;
+       OSStatus                                        err;
+       const uint8_t *                         ptr;
+       const uint8_t *                         end;
+       uint32_t                                        index;
+       unsigned int                            txtSize, count;
+       const uint8_t * const           hostname        = inContext->hostname;
+       int                                                     nameMatches     = false;
+       
+       require_quiet( inName[ 0 ] >= hostname[ 0 ], exit );
+       if( memicmp( &inName[ 1 ], &hostname[ 1 ], hostname[ 0 ] ) != 0 ) goto exit;
+       
+       ptr = &inName[ 1 + hostname[ 0 ] ];
+       end = &inName[ 1 + inName[ 0 ] ];
+       if( ptr < end )
+       {
+               require_quiet( ( end - ptr ) >= 2, exit );
+               require_quiet( ( ptr[ 0 ] == ' ' ) && ( ptr[ 1 ] == '(' ), exit );
+               ptr += 2;
+               
+        err = DecimalTextToUInt32( (const char *) ptr, (const char *) end, &index, (const char **) &ptr );
+               require_noerr_quiet( err, exit );
+               require_quiet( ( index >= 2 ) && ( index <= UINT16_MAX ), exit );
+               
+               require_quiet( ( ( end - ptr ) == 1 ) && ( *ptr == ')' ), exit );
+               ++ptr;
+       }
+       else
+       {
+               index = 1;
+       }
+       
+       if( !_MDNSReplierServiceTypeMatch( inContext, ptr, &txtSize, &count ) ) goto exit;
+       nameMatches = true;
+       
+       if( outIndex )          *outIndex       = index;
+       if( outTXTSize )        *outTXTSize     = txtSize;
+       if( outCount )          *outCount       = count;
+       
+exit:
+       return( nameMatches ? true : false );
 }
 
 //===========================================================================================================================
-//     _GAITesterRun
+//     _MDNSReplierAboutRecordNameMatch
 //===========================================================================================================================
 
-static void    _GAITesterRun( void *inContext )
+static Boolean _MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName )
 {
-       OSStatus                                err;
-       GAITesterRef const              me              = (GAITesterRef) inContext;
-       GAITestItem *                   item;
-       GAITestItemResult *             results = NULL;
+       const uint8_t *                         subname;
+       const uint8_t * const           hostname        = inContext->hostname;
+       int                                                     nameMatches     = false;
        
-       require_action_quiet( !me->stopped, exit, err = kNoErr );
+       if( strnicmpx( &inName[ 1 ], inName[ 0 ], "about" ) != 0 ) goto exit;
+       subname = NextLabel( inName );
        
-       for( ;; )
-       {
-               item = me->currentItem;
-               if( item )
-               {
-                       DNSServiceProtocol              protocols;
-                       
-                       check( !me->opRef );
-                       check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
-                       
-                       // Perform preliminary tasks if this is the start of a new test case.
-                       
-                       if( item == me->currentCase->itemList )
-                       {
-                               // Flush mDNSResponder's cache.
-                               
-                               err = systemf( NULL, "killall -HUP mDNSResponder" );
-                               require_noerr( err, exit );
-                               usleep( kMicrosecondsPerSecond );
-                               
-                               // Start a packet capture.
-                               
-                               check( !me->pcap );
-                               err = _GAITesterCreatePacketCapture( &me->pcap );
-                               require_noerr( err, exit );
-                               
-                               // Start the test case time limit timer.
-                               
-                               check( !me->caseTimer );
-                               if( me->currentCase->timeLimitMs > 0 )
-                               {
-                                       const int64_t           timeLimitSecs = ( me->currentCase->timeLimitMs + 999 ) / 1000;
-                                       
-                                       err = DispatchTimerCreate( dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER,
-                                               ( (uint64_t) timeLimitSecs ) * kNanosecondsPerSecond / 10,
-                                               me->queue, _GAITesterTimeout, NULL, me, &me->caseTimer );
-                                       require_noerr( err, exit );
-                                       dispatch_resume( me->caseTimer );
-                               }
-                               
-                               me->caseStartTime = GetCurrentMicroTime();
-                       }
-                       
-                       // Call DNSServiceGetAddrInfo().
-                       
-                       if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
-                       
-                       protocols = 0;
-                       if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
-                       if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
-                       
-                       check( !me->mainRef );
-                       me->startTicks = UpTicks();
-                       
-                       err = DNSServiceCreateConnection( &me->mainRef );
-                       require_noerr( err, exit );
-                       
-                       err = DNSServiceSetDispatchQueue( me->mainRef, me->queue );
-                       require_noerr( err, exit );
-                       
-                       me->connTicks = UpTicks();
-                       
-                       me->opRef = me->mainRef;
-                       err = DNSServiceGetAddrInfo( &me->opRef, kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates,
-                               kDNSServiceInterfaceIndexAny, protocols, item->name, _GAITesterGetAddrInfoCallback, me );
-                       require_noerr( err, exit );
-                       break;
-               }
-               else
-               {
-                       // No more test items means that this test case has completed (or timed out).
-                       
-                       me->caseEndTime = GetCurrentMicroTime();
-                       dispatch_source_forget( &me->caseTimer );
-                       ForgetPacketCapture( &me->pcap );
-                       
-                       if( me->resultsHandler )
-                       {
-                               size_t          resultCount, itemCount, i;
-                               int                     timedOut;
-                               
-                               itemCount       = 0;
-                               resultCount     = 0;
-                               timedOut        = false;
-                               for( item = me->currentCase->itemList; item; item = item->next )
-                               {
-                                       if( !timedOut )
-                                       {
-                                               if( item->timeUs < 0 )
-                                               {
-                                                       timedOut = true;
-                                               }
-                                               else
-                                               {
-                                                       ++resultCount;
-                                               }
-                                       }
-                                       ++itemCount;
-                               }
-                               if( resultCount > 0 )
-                               {
-                                       results = (GAITestItemResult *) calloc( resultCount, sizeof( *results ) );
-                                       require_action( results, exit, err = kNoMemoryErr );
-                                       
-                                       item = me->currentCase->itemList;
-                                       for( i = 0; i < resultCount; ++i )
-                                       {
-                                               results[ i ].name                               = item->name;
-                                               results[ i ].connectionTimeUs   = item->connectionTimeUs;
-                                               results[ i ].firstTimeUs                = item->firstTimeUs;
-                                               results[ i ].timeUs                             = item->timeUs;
-                                               item = item->next;
-                                       }
-                               }
-                               me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, results, resultCount,
-                                       itemCount, me->resultsContext );
-                               ForgetMem( &results );
-                       }
-                       
-                       _GAITesterAdvanceCurrentSet( me );
-                       require_action_quiet( me->currentCase, exit, err = kEndingErr );
-               }
-       }
+       if( !MemIEqual( &subname[ 1 ], subname[ 0 ], &hostname[ 1 ], hostname[ 0 ] ) ) goto exit;
+       subname = NextLabel( subname );
+       
+       if( !DomainNameEqual( subname, kLocalName ) ) goto exit;
+       nameMatches = true;
        
 exit:
-       FreeNullSafe( results );
-       if( err ) _GAITesterStop( me );
-       CFRelease( me );
+       return( nameMatches ? true : false );
 }
 
 //===========================================================================================================================
-//     _GAITesterCreatePacketCapture
+//     _MDNSReplierHostnameMatch
 //===========================================================================================================================
 
-static OSStatus        _GAITesterCreatePacketCapture( pcap_t **outPCap )
+static Boolean
+       _MDNSReplierHostnameMatch(
+               const MDNSReplierContext *      inContext,
+               const uint8_t *                         inName,
+               unsigned int *                          outIndex )
 {
-       OSStatus                                err;
-       pcap_t *                                pcap;
-       struct bpf_program              program;
-       char                                    errBuf[ PCAP_ERRBUF_SIZE ];
+       OSStatus                                        err;
+       const uint8_t *                         ptr;
+       const uint8_t *                         end;
+       uint32_t                                        index;
+       const uint8_t * const           hostname        = inContext->hostname;
+       int                                                     nameMatches     = false;
        
-       pcap = pcap_create( "lo0", errBuf );
-       require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+       require_quiet( inName[ 0 ] >= hostname[ 0 ], exit );
+       if( memicmp( &inName[ 1 ], &hostname[ 1 ], hostname[ 0 ] ) != 0 ) goto exit;
        
-       err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
-       require_noerr_action( err, exit, err = kUnknownErr );
+       ptr = &inName[ 1 + hostname[ 0 ] ];
+       end = &inName[ 1 + inName[ 0 ] ];
+       if( ptr < end )
+       {
+               require_quiet( *ptr == '-', exit );
+               ++ptr;
+               
+               err = DecimalTextToUInt32( (const char *) ptr, (const char *) end, &index, (const char **) &ptr );
+               require_noerr_quiet( err, exit );
+               require_quiet( ( index >= 2 ) && ( index <= UINT16_MAX ), exit );
+               require_quiet( ptr == end, exit );
+       }
+       else
+       {
+               index = 1;
+       }
        
-       err = pcap_set_snaplen( pcap, 512 );
-       require_noerr_action( err, exit, err = kUnknownErr );
+       if( !DomainNameEqual( ptr, kLocalName ) ) goto exit;
+       nameMatches = true;
        
-       err = pcap_set_immediate_mode( pcap, 0 );
-       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       if( outIndex ) *outIndex = index;
        
-       err = pcap_activate( pcap );
-       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+exit:
+       return( nameMatches ? true : false );
+}
+
+//===========================================================================================================================
+//     _MDNSReplierCreateTXTRecord
+//===========================================================================================================================
+
+static OSStatus        _MDNSReplierCreateTXTRecord( const uint8_t *inRecordName, size_t inSize, uint8_t **outTXT )
+{
+       OSStatus                err;
+       uint8_t *               txt;
+       uint8_t *               ptr;
+       size_t                  i, wholeCount, remCount;
+       uint32_t                hash;
+       int                             n;
+       uint8_t                 txtStr[ 16 ];
        
-       err = pcap_setdirection( pcap, PCAP_D_INOUT );
-       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       require_action_quiet( inSize > 0, exit, err = kSizeErr );
        
-       err = pcap_setnonblock( pcap, 1, errBuf );
-       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       txt = (uint8_t *) malloc( inSize );
+       require_action( txt, exit, err = kNoMemoryErr );
        
-       err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
-       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       hash = FNV1( inRecordName, DomainNameLength( inRecordName ) );
        
-       err = pcap_setfilter( pcap, &program );
-       pcap_freecode( &program );
-       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       txtStr[ 0 ] = 15;
+       n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash );
+       check( n == 15 );
        
-       *outPCap = pcap;
-       pcap = NULL;
+       ptr = txt;
+       wholeCount = inSize / 16;
+       for( i = 0; i < wholeCount; ++i )
+       {
+               memcpy( ptr, txtStr, 16 );
+               ptr += 16;
+       }
+       
+       remCount = inSize % 16;
+       if( remCount > 0 )
+       {
+               txtStr[ 0 ] = (uint8_t)( remCount - 1 );
+               memcpy( ptr, txtStr, remCount );
+               ptr += remCount;
+       }
+       check( ptr == &txt[ inSize ] );
+       
+       *outTXT = txt;
+       err = kNoErr;
        
 exit:
-       if( pcap ) pcap_close( pcap );
        return( err );
 }
 
 //===========================================================================================================================
-//     _GAITesterTimeout
+//     _MRResourceRecordCreate
 //===========================================================================================================================
 
-static void    _GAITesterTimeout( void *inContext )
+static OSStatus
+       _MRResourceRecordCreate(
+               uint8_t *                       inName,
+               uint16_t                        inType,
+               uint16_t                        inClass,
+               uint32_t                        inTTL,
+               uint16_t                        inRDLength,
+               uint8_t *                       inRData,
+               MRResourceRecord **     outRecord )
 {
-       GAITesterRef const              me = (GAITesterRef) inContext;
+       OSStatus                                err;
+       MRResourceRecord *              obj;
+       
+       obj = (MRResourceRecord *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->name               = inName;
+       obj->type               = inType;
+       obj->class              = inClass;
+       obj->ttl                = inTTL;
+       obj->rdlength   = inRDLength;
+       obj->rdata              = inRData;
+       
+       if( inType == kDNSServiceType_SRV )
+       {
+               require_action_quiet( obj->rdlength > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr );
+               obj->target = obj->rdata + sizeof( SRVRecordDataFixedFields );
+       }
        
-       dispatch_source_forget( &me->caseTimer );
+       *outRecord = obj;
+       obj = NULL;
+       err = kNoErr;
        
-       _GAITesterCompleteCurrentTest( me, true );
+exit:
+       FreeNullSafe( obj );
+       return( err );
 }
 
 //===========================================================================================================================
-//     _GAITesterAdvanceCurrentItem
+//     _MRResourceRecordFree
 //===========================================================================================================================
 
-static void    _GAITesterAdvanceCurrentItem( GAITesterRef me )
+static void    _MRResourceRecordFree( MRResourceRecord *inRecord )
 {
-       if( me->currentItem )
-       {
-               me->currentItem = me->currentItem->next;
-               _GAITesterInitializeCurrentTest( me );
-       }
+       ForgetMem( &inRecord->name );
+       ForgetMem( &inRecord->rdata );
+       free( inRecord );
 }
 
 //===========================================================================================================================
-//     _GAITesterAdvanceCurrentSet
+//     _MRResourceRecordFreeList
 //===========================================================================================================================
 
-static void    _GAITesterAdvanceCurrentSet( GAITesterRef me )
+static void    _MRResourceRecordFreeList( MRResourceRecord *inList )
 {
-       if( me->currentCase )
+       MRResourceRecord *              record;
+       
+       while( ( record = inList ) != NULL )
        {
-               me->caseStartTime       = 0;
-               me->caseEndTime         = 0;
-               me->currentCase         = me->currentCase->next;
-               if( me->currentCase )
-               {
-                       me->currentItem = me->currentCase->itemList;
-                       _GAITesterInitializeCurrentTest( me );
-               }
+               inList = record->next;
+               _MRResourceRecordFree( record );
        }
 }
 
 //===========================================================================================================================
-//     _GAITesterInitializeCurrentTest
+//     _MRNameOffsetItemCreate
+//===========================================================================================================================
+
+static OSStatus        _MRNameOffsetItemCreate( const uint8_t *inName, uint16_t inOffset, MRNameOffsetItem **outItem )
+{
+       OSStatus                                err;
+       MRNameOffsetItem *              obj;
+       size_t                                  nameLen;
+       
+       require_action_quiet( inOffset <= kDNSCompressionOffsetMax, exit, err = kSizeErr );
+       
+       nameLen = DomainNameLength( inName );
+       obj = (MRNameOffsetItem *) calloc( 1, offsetof( MRNameOffsetItem, name ) + nameLen );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->offset = inOffset;
+       memcpy( obj->name, inName, nameLen );
+       
+       *outItem = obj;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     _MRNameOffsetItemFree
+//===========================================================================================================================
+
+static void    _MRNameOffsetItemFree( MRNameOffsetItem *inItem )
+{
+       free( inItem );
+}
+
+//===========================================================================================================================
+//     _MRNameOffsetItemFreeList
 //===========================================================================================================================
 
-static void    _GAITesterInitializeCurrentTest( GAITesterRef me )
+static void    _MRNameOffsetItemFreeList( MRNameOffsetItem *inList )
 {
-       GAITestItem * const             item = me->currentItem;
+       MRNameOffsetItem *              item;
        
-       if( item )
+       while( ( item = inList ) != NULL )
        {
-               check( item->addressCount > 0 );
-               if( item->wantV4 )
-               {
-                       me->bitmapV4 = item->hasV4 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
-               }
-               else
-               {
-                       me->bitmapV4 = 0;
-               }
-               
-               if( item->wantV6 )
-               {
-                       me->bitmapV6 = item->hasV6 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
-               }
-               else
-               {
-                       me->bitmapV6 = 0;
-               }
-               me->gotFirstResult = false;
+               inList = item->next;
+               _MRNameOffsetItemFree( item );
        }
 }
 
 //===========================================================================================================================
-//     _GAITesterGetAddrInfoCallback
+//     GAIPerfCmd
 //===========================================================================================================================
 
-static void DNSSD_API
-       _GAITesterGetAddrInfoCallback(
-               DNSServiceRef                   inSDRef,
-               DNSServiceFlags                 inFlags,
-               uint32_t                                inInterfaceIndex,
-               DNSServiceErrorType             inError,
-               const char *                    inHostname,
-               const struct sockaddr * inSockAddr,
-               uint32_t                                inTTL,
-               void *                                  inContext )
+#define kGAIPerfGAITimeLimitMs         500     // Allow at most 500 ms for a DNSServiceGetAddrInfo() operation to complete.
+#define kGAIPerfStandardTTL                    ( 1 * kSecondsPerHour )
+
+typedef struct GAITesterPrivate *              GAITesterRef;
+typedef struct GAITestCase                             GAITestCase;
+
+typedef struct
 {
-       GAITesterRef const                              me              = (GAITesterRef) inContext;
-       GAITestItem * const                             item    = me->currentItem;
-       const sockaddr_ip * const               sip             = (const sockaddr_ip *) inSockAddr;
-       uint64_t                                                nowTicks;
-       uint64_t *                                              bitmapPtr;
-       uint64_t                                                bitmask;
-       unsigned int                                    addrOffset;
-       
-       Unused( inSDRef );
-       Unused( inInterfaceIndex );
-       Unused( inHostname );
-       Unused( inTTL );
-       
-       nowTicks = UpTicks();
+       const char *            name;                           // Domain name that was resolved.
+       uint64_t                        connectionTimeUs;       // Time in microseconds that it took to create a DNS-SD connection.
+       uint64_t                        firstTimeUs;            // Time in microseconds that it took to get the first address result.
+       uint64_t                        timeUs;                         // Time in microseconds that it took to get all expected address results.
+       OSStatus                        error;
        
-       require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
-       require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit );
+}      GAITestItemResult;
+
+typedef void ( *GAITesterStopHandler_f )( void *inContext, OSStatus inError );
+typedef void
+       ( *GAITesterResultsHandler_f )(
+               const char *                            inCaseTitle,
+               NanoTime64                                      inCaseStartTime,
+               NanoTime64                                      inCaseEndTime,
+               const GAITestItemResult *       inResultArray,
+               size_t                                          inResultCount,
+               void *                                          inContext );
+
+typedef unsigned int           GAITestAddrType;
+#define kGAITestAddrType_None          0
+#define kGAITestAddrType_IPv4          ( 1U << 0 )
+#define kGAITestAddrType_IPv6          ( 1U << 1 )
+#define kGAITestAddrType_Both          ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 )
+
+#define GAITestAddrTypeIsValid( X ) \
+       ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) )
+
+typedef struct
+{
+       GAITesterRef                    tester;                         // GAI tester object.
+       CFMutableArrayRef               testCaseResults;        // Array of test case results.
+       unsigned int                    callDelayMs;            // Amount of time to wait before calling DNSServiceGetAddrInfo().
+       unsigned int                    serverDelayMs;          // Amount of additional time to have server delay its responses.
+       unsigned int                    defaultIterCount;       // Default test case iteration count.
+       dispatch_source_t               sigIntSource;           // Dispatch source for SIGINT.
+       dispatch_source_t               sigTermSource;          // Dispatch source for SIGTERM.
+       char *                                  outputFilePath;         // File to write test results to. If NULL, then write to stdout.
+       OutputFormatType                outputFormat;           // Format of test results output.
+       Boolean                                 appendNewline;          // True if a newline character should be appended to JSON output.
+       Boolean                                 skipPathEval;           // True if DNSServiceGetAddrInfo() path evaluation is to be skipped.
+       Boolean                                 badUDPMode;                     // True if the test DNS server is to run in Bad UDP mode.
+       Boolean                                 testFailed;                     // True if at least one test case iteration failed.
        
-       bitmapPtr       = NULL;
-       bitmask         = 0;
-       if( ( sip->sa.sa_family == AF_INET ) && item->wantV4 )
-       {
-               if( item->hasV4 )
-               {
-                       if( !inError )
-                       {
-                               const uint32_t          addrV4 = ntohl( sip->v4.sin_addr.s_addr );
-                               
-                               if( strcasecmp( item->name, "localhost." ) == 0 )
-                               {
-                                       if( addrV4 == INADDR_LOOPBACK )
-                                       {
-                                               bitmask         = 1;
-                                               bitmapPtr       = &me->bitmapV4;
-                                       }
-                               }
-                               else
-                               {
-                                       addrOffset = addrV4 - kTestDNSServerBaseAddrV4;
-                                       if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
-                                       {
-                                               bitmask         = UINT64_C( 1 ) << ( addrOffset - 1 );
-                                               bitmapPtr       = &me->bitmapV4;
-                                       }
-                               }
-                       }
-               }
-               else if( inError == kDNSServiceErr_NoSuchRecord )
-               {
-                       bitmask         = 1;
-                       bitmapPtr       = &me->bitmapV4;
-               }
-       }
-       else if( ( sip->sa.sa_family == AF_INET6 ) && item->wantV6 )
-       {
-               if( item->hasV6 )
-               {
-                       if( !inError )
-                       {
-                               const uint8_t * const           addrV6 = sip->v6.sin6_addr.s6_addr;
-                               
-                               if( strcasecmp( item->name, "localhost." ) == 0 )
-                               {
-                                       if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 )
-                                       {
-                                               bitmask         = 1;
-                                               bitmapPtr       = &me->bitmapV6;
-                                       }
-                               }
-                               else if( memcmp( addrV6, kTestDNSServerBaseAddrV6, 15 ) == 0 )
-                               {
-                                       addrOffset = addrV6[ 15 ];
-                                       if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
-                                       {
-                                               bitmask         = UINT64_C( 1 ) << ( addrOffset - 1 );
-                                               bitmapPtr       = &me->bitmapV6;
-                                       }
-                               }
-                       }
-               }
-               else if( inError == kDNSServiceErr_NoSuchRecord )
-               {
-                       bitmask         = 1;
-                       bitmapPtr       = &me->bitmapV6;
-               }
-       }
-       
-       if( bitmapPtr && ( *bitmapPtr & bitmask ) )
-       {
-               *bitmapPtr &= ~bitmask;
-               if( !me->gotFirstResult )
-               {
-                       me->firstTicks          = nowTicks;
-                       me->gotFirstResult      = true;
-               }
-               
-               if( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) )
-               {
-                       me->endTicks = nowTicks;
-                       _GAITesterCompleteCurrentTest( me, false );
-               }
-       }
-       
-exit:
-       return;
-}
+}      GAIPerfContext;
 
-//===========================================================================================================================
-//     _GAITesterCompleteCurrentTest
-//===========================================================================================================================
+static void            GAIPerfContextFree( GAIPerfContext *inContext );
+static OSStatus        GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
+static OSStatus        GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
+static void            GAIPerfTesterStopHandler( void *inContext, OSStatus inError );
+static void
+       GAIPerfResultsHandler(
+               const char *                            inCaseTitle,
+               NanoTime64                                      inCaseStartTime,
+               NanoTime64                                      inCaseEndTime,
+               const GAITestItemResult *       inResultArray,
+               size_t                                          inResultCount,
+               void *                                          inContext );
+static void            GAIPerfSignalHandler( void *inContext );
+
+CFTypeID               GAITesterGetTypeID( void );
+static OSStatus
+       GAITesterCreate(
+               dispatch_queue_t        inQueue,
+               int                                     inCallDelayMs,
+               int                                     inServerDelayMs,
+               int                                     inServerDefaultTTL,
+               Boolean                         inSkipPathEvaluation,
+               Boolean                         inBadUDPMode,
+               GAITesterRef *          outTester );
+static void            GAITesterStart( GAITesterRef inTester );
+static void            GAITesterStop( GAITesterRef inTester );
+static OSStatus        GAITesterAddTestCase( GAITesterRef inTester, GAITestCase *inCase );
+static void
+       GAITesterSetStopHandler(
+               GAITesterRef                    inTester,
+               GAITesterStopHandler_f  inEventHandler,
+               void *                                  inEventContext );
+static void
+       GAITesterSetResultsHandler(
+               GAITesterRef                            inTester,
+               GAITesterResultsHandler_f       inResultsHandler,
+               void *                                          inResultsContext );
 
+static OSStatus        GAITestCaseCreate( const char *inTitle, GAITestCase **outCase );
+static void            GAITestCaseFree( GAITestCase *inCase );
 static OSStatus
-       _GAITesterGetDNSMessageFromPacket(
-               const uint8_t *         inPacketPtr,
-               size_t                          inPacketLen,
-               const uint8_t **        outMsgPtr,
-               size_t *                        outMsgLen );
+       GAITestCaseAddItem(
+               GAITestCase *   inCase,
+               unsigned int    inAliasCount,
+               unsigned int    inAddressCount,
+               int                             inTTL,
+               GAITestAddrType inHasAddrs,
+               GAITestAddrType inWantAddrs,
+               unsigned int    inTimeLimitMs,
+               unsigned int    inItemCount );
+static OSStatus
+       GAITestCaseAddLocalHostItem(
+               GAITestCase *   inCase,
+               GAITestAddrType inWantAddrs,
+               unsigned int    inTimeLimitMs,
+               unsigned int    inItemCount );
 
-static void    _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut )
+static void    GAIPerfCmd( void )
 {
        OSStatus                                err;
-       GAITestItem *                   item;
-       struct timeval *                tsQA    = NULL;
-       struct timeval *                tsQAAAA = NULL;
-       struct timeval *                tsRA    = NULL;
-       struct timeval *                tsRAAAA = NULL;
-       struct timeval                  timeStamps[ 4 ];
-       struct timeval *                tsPtr   = &timeStamps[ 0 ];
-       struct timeval *                tsQ;
-       struct timeval *                tsR;
-       int64_t                                 idleTimeUs;
-       uint8_t                                 name[ kDomainNameLengthMax ];
+       GAIPerfContext *                context = NULL;
        
-       DNSServiceForget( &me->opRef );
-       DNSServiceForget( &me->mainRef );
+       err = CheckRootUser();
+       require_noerr_quiet( err, exit );
        
-       if( inTimedOut )
-       {
-               for( item = me->currentItem; item; item = item->next )
-               {
-                       item->firstTimeUs       = -1;
-                       item->timeUs            = -1;
-               }
-               me->currentItem = NULL;
-               
-               CFRetain( me );
-               dispatch_async_f( me->queue, me, _GAITesterRun );
-               return;
-       }
+       err = CheckIntegerArgument( gGAIPerf_CallDelayMs, "call delay (ms)", 0, INT_MAX );
+       require_noerr_quiet( err, exit );
        
-       item = me->currentItem;
-       err = DomainNameFromString( name, item->name, NULL );
-    require_noerr( err, exit );
+       err = CheckIntegerArgument( gGAIPerf_ServerDelayMs, "server delay (ms)", 0, INT_MAX );
+       require_noerr_quiet( err, exit );
        
-       for( ;; )
-       {
-               int                                                                     status;
-               struct pcap_pkthdr *                            pktHdr;
-               const uint8_t *                                         packet;
-               const uint8_t *                                         msgPtr;
-               size_t                                                          msgLen;
-               const DNSHeader *                                       hdr;
-               unsigned int                                            flags;
-               const uint8_t *                                         ptr;
-               const DNSQuestionFixedFields *          qfields;
-               unsigned int                                            qtype;
-               uint8_t                                                         qname[ kDomainNameLengthMax ];
-               
-               status = pcap_next_ex( me->pcap, &pktHdr, &packet );
-               if( status != 1 ) break;
-               if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue;
-               if( msgLen < kDNSHeaderLength ) continue;
-               
-               hdr = (const DNSHeader *) msgPtr;
-               flags = DNSHeaderGetFlags( hdr );
-               if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue;
-               if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue;
-               
-               ptr = (const uint8_t *) &hdr[ 1 ];
-               if( DNSMessageExtractDomainName( msgPtr, msgLen, ptr, qname, &ptr ) != kNoErr ) continue;
-               if( !DomainNameEqual( qname, name ) ) continue;
-               
-               qfields = (const DNSQuestionFixedFields *) ptr;
-               if( DNSQuestionFixedFieldsGetClass( qfields ) != kDNSServiceClass_IN ) continue;
-               
-               qtype = DNSQuestionFixedFieldsGetType( qfields );
-               if( item->wantV4 && ( qtype == kDNSServiceType_A ) )
-               {
-                       if( flags & kDNSHeaderFlag_Response )
-                       {
-                               if( tsQA && !tsRA )
-                               {
-                                       tsRA  = tsPtr++;
-                                       *tsRA = pktHdr->ts;
-                               }
-                       }
-                       else if( !tsQA )
-                       {
-                               tsQA  = tsPtr++;
-                               *tsQA = pktHdr->ts;
-                       }
-               }
-               else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) )
-               {
-                       if( flags & kDNSHeaderFlag_Response )
-                       {
-                               if( tsQAAAA && !tsRAAAA )
-                               {
-                                       tsRAAAA  = tsPtr++;
-                                       *tsRAAAA = pktHdr->ts;
-                               }
-                       }
-                       else if( !tsQAAAA )
-                       {
-                               tsQAAAA  = tsPtr++;
-                               *tsQAAAA = pktHdr->ts;
-                       }
-               }
-       }
+       err = CheckIntegerArgument( gGAIPerf_IterationCount, "iteration count", 1, INT_MAX );
+       require_noerr_quiet( err, exit );
        
-       if( tsQA && tsQAAAA )   tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
-       else                                    tsQ = tsQA ? tsQA : tsQAAAA;
+       context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
        
-       if( tsRA && tsRAAAA )   tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
-       else                                    tsR = tsQA ? tsQA : tsQAAAA;
+       context->testCaseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       require_action( context->testCaseResults, exit, err = kNoMemoryErr );
        
-       if( tsQ && tsR )
-       {
-               idleTimeUs = TIMEVAL_USEC64_DIFF( *tsR, *tsQ );
-               if( idleTimeUs < 0 ) idleTimeUs = 0;
-       }
-       else
+       context->callDelayMs            = (unsigned int) gGAIPerf_CallDelayMs;
+       context->serverDelayMs          = (unsigned int) gGAIPerf_ServerDelayMs;
+       context->defaultIterCount       = (unsigned int) gGAIPerf_IterationCount;
+       context->appendNewline          = gGAIPerf_OutputAppendNewline  ? true : false;
+       context->skipPathEval           = gGAIPerf_SkipPathEvalulation  ? true : false;
+       context->badUDPMode                     = gGAIPerf_BadUDPMode                   ? true : false;
+       
+       if( gGAIPerf_OutputFilePath )
        {
-               idleTimeUs = 0;
+               context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
+               require_action( context->outputFilePath, exit, err = kNoMemoryErr );
        }
        
-       item->connectionTimeUs  = (int64_t)  UpTicksToMicroseconds( me->connTicks  - me->startTicks );
-       item->firstTimeUs               = (int64_t)( UpTicksToMicroseconds( me->firstTicks - me->connTicks  ) - (uint64_t) idleTimeUs );
-       item->timeUs                    = (int64_t)( UpTicksToMicroseconds( me->endTicks   - me->connTicks  ) - (uint64_t) idleTimeUs );
-       
-       _GAITesterAdvanceCurrentItem( me );
-       CFRetain( me );
-       dispatch_async_f( me->queue, me, _GAITesterRun );
-       
-exit:
-       if( err ) _GAITesterStop( me );
-}
-
-//===========================================================================================================================
-//     _GAITesterGetDNSMessageFromPacket
-//===========================================================================================================================
-
-#define kHeaderSizeNullLink             4
-#define kHeaderSizeIPv4Min             20
-#define kHeaderSizeIPv6                        40
-#define kHeaderSizeUDP                  8
-
-#define kIPProtocolUDP         0x11
-
-static OSStatus
-       _GAITesterGetDNSMessageFromPacket(
-               const uint8_t *         inPacketPtr,
-               size_t                          inPacketLen,
-               const uint8_t **        outMsgPtr,
-               size_t *                        outMsgLen )
-{
-       OSStatus                                        err;
-       const uint8_t *                         nullLink;
-       uint32_t                                        addressFamily;
-       const uint8_t *                         ip;
-       int                                                     ipHeaderLen;
-       int                                                     protocol;
-       const uint8_t *                         msg;
-       const uint8_t * const           end = &inPacketPtr[ inPacketLen ];
+       context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
+               kOutputFormatStr_JSON,          kOutputFormatType_JSON,
+               kOutputFormatStr_XML,           kOutputFormatType_XML,
+               kOutputFormatStr_Binary,        kOutputFormatType_Binary,
+               NULL );
+       require_noerr_quiet( err, exit );
        
-       nullLink = &inPacketPtr[ 0 ];
-       require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
-       addressFamily = ReadHost32( &nullLink[ 0 ] );
+       err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
+               kGAIPerfStandardTTL, context->skipPathEval, context->badUDPMode, &context->tester );
+       require_noerr( err, exit );
        
-       ip = &nullLink[ kHeaderSizeNullLink ];
-       if( addressFamily == AF_INET )
+       check( gGAIPerf_TestSuite );
+       if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Basic ) == 0 )
        {
-               require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
-               ipHeaderLen     = ( ip[ 0 ] & 0x0F ) * 4;
-               protocol        =   ip[ 9 ];
+               err = GAIPerfAddBasicTestCases( context );
+               require_noerr( err, exit );
        }
-       else if( addressFamily == AF_INET6 )
+       else if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Advanced ) == 0 )
        {
-               require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
-               ipHeaderLen     = kHeaderSizeIPv6;
-               protocol        = ip[ 6 ];
+               err = GAIPerfAddAdvancedTestCases( context );
+               require_noerr( err, exit );
        }
        else
        {
-               err = kTypeErr;
+               FPrintF( stderr, "error: Invalid test suite name: %s.\n", gGAIPerf_TestSuite );
+               err = kParamErr;
                goto exit;
        }
-       require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
-       require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
        
-       msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+       GAITesterSetStopHandler( context->tester, GAIPerfTesterStopHandler, context );
+       GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
        
-       *outMsgPtr = msg;
-       *outMsgLen = (size_t)( end - msg );
-       err = kNoErr;
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
+       require_noerr( err, exit );
+       dispatch_resume( context->sigIntSource );
+       
+       signal( SIGTERM, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
+       require_noerr( err, exit );
+       dispatch_resume( context->sigTermSource );
+       
+       GAITesterStart( context->tester );
+       dispatch_main();
        
 exit:
-       return( err );
+       if( context ) GAIPerfContextFree( context );
+       exit( 1 );
 }
 
 //===========================================================================================================================
-//     GAITestCaseCreate
+//     GAIPerfContextFree
+//===========================================================================================================================
+
+static void    GAIPerfContextFree( GAIPerfContext *inContext )
+{
+       ForgetCF( &inContext->tester );
+       ForgetCF( &inContext->testCaseResults );
+       ForgetMem( &inContext->outputFilePath );
+       dispatch_source_forget( &inContext->sigIntSource );
+       dispatch_source_forget( &inContext->sigTermSource );
+       free( inContext );
+}
+
 //===========================================================================================================================
+//     GAIPerfAddAdvancedTestCases
+//===========================================================================================================================
+
+#define kTestCaseTitleBufferSize               128
+
+static void
+       _GAIPerfWriteTestCaseTitle(
+               char                    inBuffer[ kTestCaseTitleBufferSize ],
+               unsigned int    inCNAMERecordCount,
+               unsigned int    inARecordCount,
+               unsigned int    inAAAARecordCount,
+               GAITestAddrType inRequested,
+               unsigned int    inIterationCount,
+               Boolean                 inIterationsAreUnique );
+static void
+       _GAIPerfWriteLocalHostTestCaseTitle(
+               char                    inBuffer[ kTestCaseTitleBufferSize ],
+               GAITestAddrType inRequested,
+               unsigned int    inIterationCount );
+
+#define kGAIPerfAdvancedTestSuite_MaxAliasCount                4
+#define kGAIPerfAdvancedTestSuite_MaxAddrCount         8
 
-static OSStatus        GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet )
+static OSStatus        GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
 {
        OSStatus                        err;
-       GAITestCase *           obj;
+       unsigned int            aliasCount, addressCount, i;
+       GAITestCase *           testCase = NULL;
+       char                            title[ kTestCaseTitleBufferSize ];
        
-       obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
-       require_action( obj, exit, err = kNoMemoryErr );
+       aliasCount = 0;
+       while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+       {
+               for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+               {
+                       // Add a test case to resolve a domain name with
+                       //
+                       //     <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+                       //
+                       // to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which
+                       // requires server queries.
+                       
+                       _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+                               inContext->defaultIterCount, true );
+                       
+                       err = GAITestCaseCreate( title, &testCase );
+                       require_noerr( err, exit );
+                       
+                       for( i = 0; i < inContext->defaultIterCount; ++i )
+                       {
+                               err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+                                       kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 );
+                               require_noerr( err, exit );
+                       }
+                       
+                       err = GAITesterAddTestCase( inContext->tester, testCase );
+                       require_noerr( err, exit );
+                       testCase = NULL;
+                       
+                       // Add a test case to resolve a domain name with
+                       //
+                       //     <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+                       //
+                       // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server
+                       // query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should
+                       // ideally require no server queries, i.e., the results should come from the cache.
+                       
+                       _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+                               inContext->defaultIterCount, false );
+                       
+                       err = GAITestCaseCreate( title, &testCase );
+                       require_noerr( err, exit );
+                       
+                       err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+                               kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, inContext->defaultIterCount + 1 );
+                       require_noerr( err, exit );
+                       
+                       err = GAITesterAddTestCase( inContext->tester, testCase );
+                       require_noerr( err, exit );
+                       testCase = NULL;
+               }
+               
+               aliasCount = ( aliasCount == 0 ) ? 1 : ( 2 * aliasCount );
+       }
        
-       obj->title = strdup( inTitle );
-       require_action( obj->title, exit, err = kNoMemoryErr );
+       // Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
        
-       obj->timeLimitMs = inTimeLimitMs;
+       _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
        
-       *outSet = obj;
-       obj = NULL;
-       err = kNoErr;
+       err = GAITestCaseCreate( title, &testCase );
+       require_noerr( err, exit );
+       
+       err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+               inContext->defaultIterCount );
+       require_noerr( err, exit );
+       
+       err = GAITesterAddTestCase( inContext->tester, testCase );
+       require_noerr( err, exit );
+       testCase = NULL;
        
 exit:
-       if( obj ) GAITestCaseFree( obj );
+       if( testCase ) GAITestCaseFree( testCase );
        return( err );
 }
 
 //===========================================================================================================================
-//     GAITestCaseFree
+//     _GAIPerfWriteTestCaseTitle
 //===========================================================================================================================
 
-static void    GAITestCaseFree( GAITestCase *inCase )
+#define GAITestAddrTypeToRequestKeyValue( X ) (                                \
+       ( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6"        :       \
+       ( (X) == kGAITestAddrType_IPv4 ) ? "ipv4"                       :       \
+       ( (X) == kGAITestAddrType_IPv6 ) ? "ipv6"                       :       \
+                                                                          "" )
+
+static void
+       _GAIPerfWriteTestCaseTitle(
+               char                    inBuffer[ kTestCaseTitleBufferSize ],
+               unsigned int    inCNAMERecordCount,
+               unsigned int    inARecordCount,
+               unsigned int    inAAAARecordCount,
+               GAITestAddrType inRequested,
+               unsigned int    inIterationCount,
+               Boolean                 inIterationsAreUnique )
 {
-       GAITestItem *           item;
-       
-       while( ( item = inCase->itemList ) != NULL )
-       {
-               inCase->itemList = item->next;
-               GAITestItemFree( item );
-       }
-       ForgetMem( &inCase->title );
-       free( inCase );
+       SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
+               inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
+               inIterationCount, inIterationsAreUnique, ",unique" );
 }
 
 //===========================================================================================================================
-//     GAITestCaseAddItem
+//     _GAIPerfWriteLocalHostTestCaseTitle
 //===========================================================================================================================
 
-// A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336
-// possible strings to use in the Tag label.
+static void
+       _GAIPerfWriteLocalHostTestCaseTitle(
+               char                    inBuffer[ kTestCaseTitleBufferSize ],
+               GAITestAddrType inRequested,
+               unsigned int    inIterationCount )
+{
+       SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
+               GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
+}
 
-#define kUniqueStringCharSet           "abcdefghijklmnopqrstuvwxyz0123456789"
-#define kUniqueStringCharSetLen                sizeof_string( kUniqueStringCharSet )
-#define kUniqueStringLen                       6
+//===========================================================================================================================
+//     GAIPerfAddBasicTestCases
+//===========================================================================================================================
 
-static OSStatus
-       GAITestCaseAddItem(
-               GAITestCase *   inCase,
-               unsigned int    inAliasCount,
-               unsigned int    inAddressCount,
-               int                             inTTL,
-               GAITestAddrType inHasAddrs,
-               GAITestAddrType inWantAddrs,
-               unsigned int    inItemCount )
+#define kGAIPerfBasicTestSuite_AliasCount              2
+#define kGAIPerfBasicTestSuite_AddrCount               4
+
+static OSStatus        GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
 {
        OSStatus                        err;
-       GAITestItem *           item;
-       GAITestItem *           item2;
-       GAITestItem *           newItemList = NULL;
-       GAITestItem **          itemPtr;
-       char *                          ptr;
-       char *                          end;
+       GAITestCase *           testCase = NULL;
+       char                            title[ kTestCaseTitleBufferSize ];
        unsigned int            i;
-       char                            name[ 64 ];
-       char                            uniqueStr[ kUniqueStringLen + 1 ];
        
-       require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+       // Test Case #1:
+       // Resolve a domain name with
+       //
+       //     2 CNAME records, 4 A records, and 4 AAAA records
+       //
+       // to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server
+       // queries.
        
-       // Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+       _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+               kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+               inContext->defaultIterCount, true );
        
-       require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr );
-       require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr );
-       require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+       err = GAITestCaseCreate( title, &testCase );
+       require_noerr( err, exit );
        
-       ptr = &name[ 0 ];
-       end = &name[ countof( name ) ];
+       for( i = 0; i < inContext->defaultIterCount; ++i )
+       {
+               err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+                       kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 );
+               require_noerr( err, exit );
+       }
        
-       // Add Alias label.
+       err = GAITesterAddTestCase( inContext->tester, testCase );
+       require_noerr( err, exit );
+       testCase = NULL;
        
-       if(      inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
-       else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+       // Test Case #2:
+       // Resolve a domain name with
+       //
+       //     2 CNAME records, 4 A records, and 4 AAAA records
+       //
+       // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which
+       // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary
+       // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache.
        
-       // Add Count label.
+       _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+               kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+               inContext->defaultIterCount, false );
        
-       SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+       err = GAITestCaseCreate( title, &testCase );
+       require_noerr( err, exit );
        
-       // Add TTL label.
+       err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+               kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+               inContext->defaultIterCount + 1 );
+       require_noerr( err, exit );
        
-       if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+       err = GAITesterAddTestCase( inContext->tester, testCase );
+       require_noerr( err, exit );
+       testCase = NULL;
        
-       // Add Tag label.
+       // Test Case #3:
+       // Each iteration resolves localhost to its IPv4 and IPv6 addresses.
        
-       RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr );
-       SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr );
+       _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
        
-       // Add IPv4 or IPv6 label if necessary.
+       err = GAITestCaseCreate( title, &testCase );
+       require_noerr( err, exit );
        
-       switch( inHasAddrs )
-       {
-               case kGAITestAddrType_IPv4:
-                       SNPrintF_Add( &ptr, end, "ipv4." );
-                       break;
-               
-               case kGAITestAddrType_IPv6:
-                       SNPrintF_Add( &ptr, end, "ipv6." );
-                       break;
-       }
+       err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+               inContext->defaultIterCount );
+       require_noerr( err, exit );
        
-       // Add d.test. labels.
+       err = GAITesterAddTestCase( inContext->tester, testCase );
+       require_noerr( err, exit );
+       testCase = NULL;
        
-       SNPrintF_Add( &ptr, end, "d.test." );
+exit:
+       if( testCase ) GAITestCaseFree( testCase );
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAIPerfTesterStopHandler
+//===========================================================================================================================
+
+#define kGAIPerfResultsKey_Info                                CFSTR( "info" )
+#define kGAIPerfResultsKey_TestCases           CFSTR( "testCases" )
+#define kGAIPerfResultsKey_Success                     CFSTR( "success" )
+
+#define kGAIPerfInfoKey_CallDelay                      CFSTR( "callDelayMs" )
+#define kGAIPerfInfoKey_ServerDelay                    CFSTR( "serverDelayMs" )
+#define kGAIPerfInfoKey_SkippedPathEval                CFSTR( "skippedPathEval" )
+#define kGAIPerfInfoKey_UsedBadUDPMode         CFSTR( "usedBadUPDMode" )
+
+static void    GAIPerfTesterStopHandler( void *inContext, OSStatus inError )
+{
+       OSStatus                                        err;
+       GAIPerfContext * const          context = (GAIPerfContext *) inContext;
+       CFPropertyListRef                       plist;
+       int                                                     exitCode;
        
-       // Create item.
+       err = inError;
+       require_noerr_quiet( err, exit );
        
-       err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item );
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO="                  // info
+                       "{"
+                               "%kO=%lli"      // callDelayMs
+                               "%kO=%lli"      // serverDelayMs
+                               "%kO=%b"        // skippedPathEval
+                               "%kO=%b"        // usedBadUPDMode
+                       "}"
+                       "%kO=%O"                // testCases
+                       "%kO=%b"                // success
+               "}",
+               kGAIPerfResultsKey_Info,
+               kGAIPerfInfoKey_CallDelay,                      (int64_t) context->callDelayMs,
+               kGAIPerfInfoKey_ServerDelay,            (int64_t) context->serverDelayMs,
+               kGAIPerfInfoKey_SkippedPathEval,        context->skipPathEval,
+               kGAIPerfInfoKey_UsedBadUDPMode,         context->badUDPMode,
+               kGAIPerfResultsKey_TestCases,           context->testCaseResults,
+               kGAIPerfResultsKey_Success,                     !context->testFailed );
        require_noerr( err, exit );
        
-       newItemList     = item;
-       itemPtr         = &item->next;
-       
-       // Create repeat items.
-       
-       for( i = 1; i < inItemCount; ++i )
-       {
-               err = GAITestItemDuplicate( item, &item2 );
-               require_noerr( err, exit );
-               
-               *itemPtr        = item2;
-               itemPtr         = &item2->next;
-       }
-       
-       // Append to test case's item list.
-       
-       for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
-       *itemPtr        = newItemList;
-       newItemList     = NULL;
-       
-exit:
-       while( ( item = newItemList ) != NULL )
-       {
-               newItemList = item->next;
-               GAITestItemFree( item );
-       }
-       return( err );
-}
-
-//===========================================================================================================================
-//     GAITestCaseAddLocalHostItem
-//===========================================================================================================================
-
-static OSStatus        GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount )
-{
-       OSStatus                        err;
-       GAITestItem *           item;
-       GAITestItem *           item2;
-       GAITestItem *           newItemList = NULL;
-       GAITestItem **          itemPtr;
-       unsigned int            i;
-       
-       require_action_quiet( inItemCount > 1, exit, err = kNoErr );
-       
-       err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, &item );
+       err = OutputPropertyList( plist, context->outputFormat, context->appendNewline, context->outputFilePath );
+       CFRelease( plist );
        require_noerr( err, exit );
        
-       newItemList     = item;
-       itemPtr         = &item->next;
-       
-       // Create repeat items.
-       
-       for( i = 1; i < inItemCount; ++i )
-       {
-               err = GAITestItemDuplicate( item, &item2 );
-               require_noerr( err, exit );
-               
-               *itemPtr        = item2;
-               itemPtr         = &item2->next;
-       }
-       
-       for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
-       *itemPtr        = newItemList;
-       newItemList     = NULL;
-       
 exit:
-       while( ( item = newItemList ) != NULL )
-       {
-               newItemList = item->next;
-               GAITestItemFree( item );
-       }
-       return( err );
+       exitCode = err ? 1 : ( context->testFailed ? 2 : 0 );
+       GAIPerfContextFree( context );
+       exit( exitCode );
 }
 
 //===========================================================================================================================
-//     GAITestItemCreate
+//     GAIPerfResultsHandler
 //===========================================================================================================================
 
-static OSStatus
-       GAITestItemCreate(
-               const char *    inName,
-               unsigned int    inAddressCount,
-               GAITestAddrType inHasAddrs,
-               GAITestAddrType inWantAddrs,
-               GAITestItem **  outItem )
-{
-       OSStatus                        err;
-       GAITestItem *           obj = NULL;
-       
-       require_action_quiet( inAddressCount >= 1, exit, err = kCountErr );
-       require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
-       require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr );
-       
-       obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
-       require_action( obj, exit, err = kNoMemoryErr );
-       
-       obj->name = strdup( inName );
-       require_action( obj->name, exit, err = kNoMemoryErr );
-       
-       obj->addressCount       = inAddressCount;
-       obj->hasV4                      = ( inHasAddrs  & kGAITestAddrType_IPv4 ) ? true : false;
-       obj->hasV6                      = ( inHasAddrs  & kGAITestAddrType_IPv6 ) ? true : false;
-       obj->wantV4                     = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false;
-       obj->wantV6                     = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false;
-       
-       *outItem = obj;
-       obj = NULL;
-       err = kNoErr;
-       
-exit:
-       if( obj ) GAITestItemFree( obj );
-       return( err );
-}
-
-//===========================================================================================================================
-//     GAITestItemDuplicate
-//===========================================================================================================================
+// Keys for test case dictionary
 
-static OSStatus        GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem )
-{
-       OSStatus                        err;
-       GAITestItem *           obj;
-       
-       obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
-       require_action( obj, exit, err = kNoMemoryErr );
-       
-       *obj = *inItem;
-       obj->next = NULL;
-       if( inItem->name )
-       {
-               obj->name = strdup( inItem->name );
-               require_action( obj->name, exit, err = kNoMemoryErr );
-       }
-       
-       *outItem = obj;
-       obj = NULL;
-       err = kNoErr;
-       
-exit:
-       if( obj ) GAITestItemFree( obj );
-       return( err );
-}
+#define kGAIPerfTestCaseKey_Title                              CFSTR( "title" )
+#define kGAIPerfTestCaseKey_StartTime                  CFSTR( "startTime" )
+#define kGAIPerfTestCaseKey_EndTime                            CFSTR( "endTime" )
+#define kGAIPerfTestCaseKey_Results                            CFSTR( "results" )
+#define kGAIPerfTestCaseKey_FirstStats                 CFSTR( "firstStats" )
+#define kGAIPerfTestCaseKey_ConnectionStats            CFSTR( "connectionStats" )
+#define kGAIPerfTestCaseKey_Stats                              CFSTR( "stats" )
 
-//===========================================================================================================================
-//     GAITestItemFree
-//===========================================================================================================================
+// Keys for test case results array entry dictionaries
 
-static void    GAITestItemFree( GAITestItem *inItem )
-{
-       ForgetMem( &inItem->name );
-       free( inItem );
-}
+#define kGAIPerfTestCaseResultKey_Name                                 CFSTR( "name" )
+#define kGAIPerfTestCaseResultKey_ConnectionTime               CFSTR( "connectionTimeUs" )
+#define kGAIPerfTestCaseResultKey_FirstTime                            CFSTR( "firstTimeUs" )
+#define kGAIPerfTestCaseResultKey_Time                                 CFSTR( "timeUs" )
 
-//===========================================================================================================================
-//     SSDPDiscoverCmd
-//===========================================================================================================================
+// Keys for test case stats dictionaries
 
-#define kSSDPPort              1900
+#define kGAIPerfTestCaseStatsKey_Count         CFSTR( "count" )
+#define kGAIPerfTestCaseStatsKey_Min           CFSTR( "min" )
+#define kGAIPerfTestCaseStatsKey_Max           CFSTR( "max" )
+#define kGAIPerfTestCaseStatsKey_Mean          CFSTR( "mean" )
+#define kGAIPerfTestCaseStatsKey_StdDev                CFSTR( "sd" )
 
 typedef struct
 {
-       HTTPHeader                              header;                 // HTTP header object for sending and receiving.
-       dispatch_source_t               readSourceV4;   // Read dispatch source for IPv4 socket.
-       dispatch_source_t               readSourceV6;   // Read dispatch source for IPv6 socket.
-       int                                             receiveSecs;    // After send, the amount of time to spend receiving.
-       uint32_t                                ifindex;                // Index of the interface over which to send the query.
-       Boolean                                 useIPv4;                // True if the query should be sent via IPv4 multicast.
-       Boolean                                 useIPv6;                // True if the query should be sent via IPv6 multicast.
+       double          min;
+       double          max;
+       double          mean;
+       double          stdDev;
        
-}      SSDPDiscoverContext;
+}      GAIPerfStats;
 
-static void            SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext );
-static void            SSDPDiscoverReadHandler( void *inContext );
-static int             SocketToPortNumber( SocketRef inSock );
-static OSStatus        WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST );
+#define GAIPerfStatsInit( X ) \
+       do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
 
-static void    SSDPDiscoverCmd( void )
+static void
+       GAIPerfResultsHandler(
+               const char *                            inCaseTitle,
+               NanoTime64                                      inCaseStartTime,
+               NanoTime64                                      inCaseEndTime,
+               const GAITestItemResult *       inResultArray,
+               size_t                                          inResultCount,
+               void *                                          inContext )
 {
-       OSStatus                                        err;
-       struct timeval                          now;
-       SSDPDiscoverContext *           context;
-       dispatch_source_t                       signalSource    = NULL;
-       SocketRef                                       sockV4                  = kInvalidSocketRef;
-       SocketRef                                       sockV6                  = kInvalidSocketRef;
-       ssize_t                                         n;
-       int                                                     sendCount;
-       
-       // Set up SIGINT handler.
-       
-       signal( SIGINT, SIG_IGN );
-       err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
-       require_noerr( err, exit );
-       dispatch_resume( signalSource );
+       OSStatus                                                err;
+       GAIPerfContext * const                  context = (GAIPerfContext *) inContext;
+       int                                                             namesAreDynamic, namesAreUnique;
+       const char *                                    ptr;
+       size_t                                                  count, startIndex;
+       CFMutableArrayRef                               results = NULL;
+       GAIPerfStats                                    stats, firstStats, connStats;
+       double                                                  sum, firstSum, connSum;
+       size_t                                                  keyValueLen, i;
+       char                                                    keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes.
+       char                                                    startTimeStr[ 32 ];
+       char                                                    endTimeStr[ 32 ];
+       const GAITestItemResult *               result;
        
-       // Check command parameters.
+       // If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value
+       // pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with
+       // the domain name's CNAME, A, and AAAA records.
        
-       if( gSSDPDiscover_ReceiveSecs < -1 )
+       namesAreDynamic = false;
+       namesAreUnique  = false;
+       ptr = inCaseTitle;
+       while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
        {
-               FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
-               err = kParamErr;
-               goto exit;
+               if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
+               {
+                       namesAreDynamic = true;
+               }
+               else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+               {
+                       namesAreUnique = true;
+               }
+               if( namesAreDynamic && namesAreUnique ) break;
        }
        
-       // Create context.
-       
-       context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
-       
-       context->receiveSecs    = gSSDPDiscover_ReceiveSecs;
-       context->useIPv4                = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
-       context->useIPv6                = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
-       
-       err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
-       require_noerr_quiet( err, exit );
+       startIndex = ( ( inResultCount > 0 ) && namesAreDynamic && !namesAreUnique ) ? 1 : 0;
+       results = CFArrayCreateMutable( NULL, (CFIndex)( inResultCount - startIndex ), &kCFTypeArrayCallBacks );
+       require_action( results, exit, err = kNoMemoryErr );
        
-       // Set up IPv4 socket.
+       GAIPerfStatsInit( &stats );
+       GAIPerfStatsInit( &firstStats );
+       GAIPerfStatsInit( &connStats );
        
-       if( context->useIPv4 )
+       sum                     = 0.0;
+       firstSum        = 0.0;
+       connSum         = 0.0;
+       count           = 0;
+       for( i = startIndex; i < inResultCount; ++i )
        {
-               int port;
-               err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
-               require_noerr( err, exit );
+               double          value;
                
-               err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
-               require_noerr( err, exit );
+               result = &inResultArray[ i ];
                
-               err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
-               err = map_socket_noerr_errno( sockV4, err );
+               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
+                       "{"
+                               "%kO=%s"        // name
+                               "%kO=%lli"      // connectionTimeUs
+                               "%kO=%lli"      // firstTimeUs
+                               "%kO=%lli"      // timeUs
+                               "%kO=%lli"      // error
+                       "}",
+                       kGAIPerfTestCaseResultKey_Name,                         result->name,
+                       kGAIPerfTestCaseResultKey_ConnectionTime,       (int64_t) result->connectionTimeUs,
+                       kGAIPerfTestCaseResultKey_FirstTime,            (int64_t) result->firstTimeUs,
+                       kGAIPerfTestCaseResultKey_Time,                         (int64_t) result->timeUs,
+                       CFSTR( "error" ),                                                       (int64_t) result->error );
+               require_noerr( err, exit );
+               
+               if( !result->error )
+               {
+                       value = (double) result->timeUs;
+                       if( value < stats.min ) stats.min = value;
+                       if( value > stats.max ) stats.max = value;
+                       sum += value;
+                       
+                       value = (double) result->firstTimeUs;
+                       if( value < firstStats.min ) firstStats.min = value;
+                       if( value > firstStats.max ) firstStats.max = value;
+                       firstSum += value;
+                       
+                       value = (double) result->connectionTimeUs;
+                       if( value < connStats.min ) connStats.min = value;
+                       if( value > connStats.max ) connStats.max = value;
+                       connSum += value;
+                       
+                       ++count;
+               }
+               else
+               {
+                       context->testFailed = true;
+               }
+       }
+       
+       if( count > 0 )
+       {
+               stats.mean              = sum      / count;
+               firstStats.mean = firstSum / count;
+               connStats.mean  = connSum  / count;
+               
+               sum                     = 0.0;
+               firstSum        = 0.0;
+               connSum         = 0.0;
+               for( i = startIndex; i < inResultCount; ++i )
+               {
+                       double          diff;
+                       
+                       result = &inResultArray[ i ];
+                       if( result->error ) continue;
+                       
+                       diff             = stats.mean - (double) result->timeUs;
+                       sum                     += ( diff * diff );
+                       
+                       diff             = firstStats.mean - (double) result->firstTimeUs;
+                       firstSum        += ( diff * diff );
+                       
+                       diff             = connStats.mean - (double) result->connectionTimeUs;
+                       connSum         += ( diff * diff );
+               }
+               stats.stdDev            = sqrt( sum      / count );
+               firstStats.stdDev       = sqrt( firstSum / count );
+               connStats.stdDev        = sqrt( connSum  / count );
+       }
+       
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->testCaseResults,
+               "{"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%s"
+                       "%kO=%O"
+                       "%kO="
+                       "{"
+                               "%kO=%lli"
+                               "%kO=%f"
+                               "%kO=%f"
+                               "%kO=%f"
+                               "%kO=%f"
+                       "}"
+                       "%kO="
+                       "{"
+                               "%kO=%lli"
+                               "%kO=%f"
+                               "%kO=%f"
+                               "%kO=%f"
+                               "%kO=%f"
+                       "}"
+                       "%kO="
+                       "{"
+                               "%kO=%lli"
+                               "%kO=%f"
+                               "%kO=%f"
+                               "%kO=%f"
+                               "%kO=%f"
+                       "}"
+               "}",
+               kGAIPerfTestCaseKey_Title,                      inCaseTitle,
+               kGAIPerfTestCaseKey_StartTime,          _NanoTime64ToDateString( inCaseStartTime, startTimeStr, sizeof( startTimeStr ) ),
+               kGAIPerfTestCaseKey_EndTime,            _NanoTime64ToDateString( inCaseEndTime, endTimeStr, sizeof( endTimeStr ) ),
+               kGAIPerfTestCaseKey_Results,            results,
+               kGAIPerfTestCaseKey_Stats,
+               kGAIPerfTestCaseStatsKey_Count,         (int64_t) count,
+               kGAIPerfTestCaseStatsKey_Min,           stats.min,
+               kGAIPerfTestCaseStatsKey_Max,           stats.max,
+               kGAIPerfTestCaseStatsKey_Mean,          stats.mean,
+               kGAIPerfTestCaseStatsKey_StdDev,        stats.stdDev,
+               kGAIPerfTestCaseKey_FirstStats,
+               kGAIPerfTestCaseStatsKey_Count,         (int64_t) count,
+               kGAIPerfTestCaseStatsKey_Min,           firstStats.min,
+               kGAIPerfTestCaseStatsKey_Max,           firstStats.max,
+               kGAIPerfTestCaseStatsKey_Mean,          firstStats.mean,
+               kGAIPerfTestCaseStatsKey_StdDev,        firstStats.stdDev,
+               kGAIPerfTestCaseKey_ConnectionStats,
+               kGAIPerfTestCaseStatsKey_Count,         (int64_t) count,
+               kGAIPerfTestCaseStatsKey_Min,           connStats.min,
+               kGAIPerfTestCaseStatsKey_Max,           connStats.max,
+               kGAIPerfTestCaseStatsKey_Mean,          connStats.mean,
+               kGAIPerfTestCaseStatsKey_StdDev,        connStats.stdDev );
+       require_noerr( err, exit );
+       
+exit:
+       CFReleaseNullSafe( results );
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     GAIPerfSignalHandler
+//===========================================================================================================================
+
+static void    GAIPerfSignalHandler( void *inContext )
+{
+       GAIPerfContext * const          context = (GAIPerfContext *) inContext;
+       
+       if( !context->tester ) exit( 1 );
+       GAITesterStop( context->tester );
+       context->tester = NULL;
+}
+
+//===========================================================================================================================
+//     GAITesterCreate
+//===========================================================================================================================
+
+// A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336
+// possible strings to use in the Tag label.
+
+#define kGAITesterTagStringLen         6
+
+typedef struct GAITestItem             GAITestItem;
+struct GAITestItem
+{
+       GAITestItem *           next;                           // Next test item in list.
+       char *                          name;                           // Domain name to resolve.
+       uint64_t                        connectionTimeUs;       // Time in microseconds that it took to create a DNS-SD connection.
+       uint64_t                        firstTimeUs;            // Time in microseconds that it took to get the first address result.
+       uint64_t                        timeUs;                         // Time in microseconds that it took to get all expected address results.
+       unsigned int            addressCount;           // Address count of the domain name, i.e., the Count label argument.
+       OSStatus                        error;                          // Current status/error.
+       unsigned int            timeLimitMs;            // Time limit in milliseconds for the test item's completion.
+       Boolean                         hasV4;                          // True if the domain name has one or more IPv4 addresses.
+       Boolean                         hasV6;                          // True if the domain name has one or more IPv6 addresses.
+       Boolean                         wantV4;                         // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses.
+       Boolean                         wantV6;                         // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses.
+};
+
+struct GAITestCase
+{
+       GAITestCase *           next;           // Next test case in list.
+       GAITestItem *           itemList;       // List of test items.
+       char *                          title;          // Title of the test case.
+};
+
+struct GAITesterPrivate
+{
+       CFRuntimeBase                                   base;                           // CF object base.
+       dispatch_queue_t                                queue;                          // Serial work queue.
+       DNSServiceRef                                   connection;                     // Reference to the shared DNS-SD connection.
+       DNSServiceRef                                   getAddrInfo;            // Reference to the current DNSServiceGetAddrInfo operation.
+       GAITestCase *                                   caseList;                       // List of test cases.
+       GAITestCase *                                   currentCase;            // Pointer to the current test case.
+       GAITestItem *                                   currentItem;            // Pointer to the current test item.
+       NanoTime64                                              caseStartTime;          // Start time of current test case in Unix time as nanoseconds.
+       NanoTime64                                              caseEndTime;            // End time of current test case in Unix time as nanoseconds.
+       int                                                             callDelayMs;            // Amount of time to wait before calling DNSServiceGetAddrInfo().
+       Boolean                                                 skipPathEval;           // True if DNSServiceGetAddrInfo() path evaluation is to be skipped.
+       Boolean                                                 stopped;                        // True if the tester has been stopped.
+       Boolean                                                 badUDPMode;                     // True if the test DNS server is to run in Bad UDP mode.
+       dispatch_source_t                               timer;                          // Timer for enforcing a test item's time limit.
+       pcap_t *                                                pcap;                           // Captures traffic between mDNSResponder and test DNS server.
+       pid_t                                                   serverPID;                      // PID of the test DNS server.
+       int                                                             serverDelayMs;          // Additional time to have the server delay its responses by.
+       int                                                             serverDefaultTTL;       // Default TTL for the server's records.
+       GAITesterStopHandler_f                  stopHandler;            // User's stop handler.
+       void *                                                  stopContext;            // User's event handler context.
+       GAITesterResultsHandler_f               resultsHandler;         // User's results handler.
+       void *                                                  resultsContext;         // User's results handler context.
+       
+       // Variables for current test item.
+       
+       uint64_t                                                bitmapV4;               // Bitmap of IPv4 results that have yet to be received.
+       uint64_t                                                bitmapV6;               // Bitmap of IPv6 results that have yet to be received.
+       uint64_t                                                startTicks;             // Start ticks of DNSServiceGetAddrInfo().
+       uint64_t                                                connTicks;              // Ticks when the connection was created.
+       uint64_t                                                firstTicks;             // Ticks when the first DNSServiceGetAddrInfo result was received.
+       uint64_t                                                endTicks;               // Ticks when the last DNSServiceGetAddrInfo result was received.
+       Boolean                                                 gotFirstResult; // True if the first result has been received.
+};
+
+CF_CLASS_DEFINE( GAITester );
+
+static void            _GAITesterStartNextTest( GAITesterRef inTester );
+static OSStatus        _GAITesterCreatePacketCapture( pcap_t **outPCap );
+static void            _GAITesterFirstGAITimeout( void *inContext );
+static void            _GAITesterTimeout( void *inContext );
+static void DNSSD_API
+       _GAITesterFirstGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void DNSSD_API
+       _GAITesterGetAddrInfoCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void            _GAITesterCompleteCurrentTest( GAITesterRef inTester, OSStatus inError );
+
+#define ForgetPacketCapture( X )               ForgetCustom( X, pcap_close )
+
+static OSStatus
+       GAITestItemCreate(
+               const char *    inName,
+               unsigned int    inAddressCount,
+               GAITestAddrType inHasAddrs,
+               GAITestAddrType inWantAddrs,
+               unsigned int    inTimeLimitMs,
+               GAITestItem **  outItem );
+static OSStatus        GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem );
+static void            GAITestItemFree( GAITestItem *inItem );
+
+static OSStatus
+       GAITesterCreate(
+               dispatch_queue_t        inQueue,
+               int                                     inCallDelayMs,
+               int                                     inServerDelayMs,
+               int                                     inServerDefaultTTL,
+               Boolean                         inSkipPathEvaluation,
+               Boolean                         inBadUDPMode,
+               GAITesterRef *          outTester )
+{
+       OSStatus                        err;
+       GAITesterRef            obj = NULL;
+       
+       CF_OBJECT_CREATE( GAITester, obj, err, exit );
+       
+       ReplaceDispatchQueue( &obj->queue, inQueue );
+       obj->callDelayMs                = inCallDelayMs;
+       obj->serverPID                  = -1;
+       obj->serverDelayMs              = inServerDelayMs;
+       obj->serverDefaultTTL   = inServerDefaultTTL;
+       obj->skipPathEval               = inSkipPathEvaluation;
+       obj->badUDPMode                 = inBadUDPMode;
+       
+       *outTester = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       CFReleaseNullSafe( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _GAITesterFinalize
+//===========================================================================================================================
+
+static void    _GAITesterFinalize( CFTypeRef inObj )
+{
+       GAITesterRef const              me = (GAITesterRef) inObj;
+       GAITestCase *                   testCase;
+       
+       check( !me->getAddrInfo );
+       check( !me->connection );
+       check( !me->timer );
+       dispatch_forget( &me->queue );
+       while( ( testCase = me->caseList ) != NULL )
+       {
+               me->caseList = testCase->next;
+               GAITestCaseFree( testCase );
+       }
+}
+
+//===========================================================================================================================
+//     GAITesterStart
+//===========================================================================================================================
+
+static void    _GAITesterStart( void *inContext );
+static void    _GAITesterStop( GAITesterRef me, OSStatus inError );
+
+static void    GAITesterStart( GAITesterRef me )
+{
+       CFRetain( me );
+       dispatch_async_f( me->queue, me, _GAITesterStart );
+}
+
+#define kGAITesterFirstGAITimeoutSecs          4
+
+static void    _GAITesterStart( void *inContext )
+{
+       OSStatus                                err;
+       GAITesterRef const              me = (GAITesterRef) inContext;
+       DNSServiceFlags                 flags;
+       char                                    name[ 64 ];
+       char                                    tag[ kGAITesterTagStringLen + 1 ];
+       
+       err = SpawnCommand( &me->serverPID, "dnssdutil server --loopback --follow %lld%?s%?d%?s%?d%?s",
+               (int64_t) getpid(),
+               me->serverDefaultTTL >= 0,      " --defaultTTL ",
+               me->serverDefaultTTL >= 0,      me->serverDefaultTTL,
+               me->serverDelayMs    >= 0,      " --responseDelay ",
+               me->serverDelayMs    >= 0,      me->serverDelayMs,
+               me->badUDPMode,                         " --badUDPMode" );
+       require_noerr_quiet( err, exit );
+       
+       SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       
+       flags = 0;
+       if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone;
+       
+       err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, name,
+               _GAITesterFirstGAICallback, me );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( me->getAddrInfo, me->queue );
+       require_noerr( err, exit );
+       
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kGAITesterFirstGAITimeoutSecs ),
+               UINT64_C_safe( kGAITesterFirstGAITimeoutSecs ) * kNanosecondsPerSecond / 10, me->queue,
+               _GAITesterFirstGAITimeout, me, &me->timer );
+       require_noerr( err, exit );
+       dispatch_resume( me->timer );
+       
+exit:
+       if( err ) _GAITesterStop( me, err );
+}
+
+//===========================================================================================================================
+//     GAITesterStop
+//===========================================================================================================================
+
+static void    _GAITesterUserStop( void *inContext );
+
+static void    GAITesterStop( GAITesterRef me )
+{
+       CFRetain( me );
+       dispatch_async_f( me->queue, me, _GAITesterUserStop );
+}
+
+static void    _GAITesterUserStop( void *inContext )
+{
+       GAITesterRef const              me = (GAITesterRef) inContext;
+       
+       _GAITesterStop( me, kCanceledErr );
+       CFRelease( me );
+}
+
+static void    _GAITesterStop( GAITesterRef me, OSStatus inError )
+{
+       OSStatus                err;
+       
+       ForgetPacketCapture( &me->pcap );
+       dispatch_source_forget( &me->timer );
+       DNSServiceForget( &me->getAddrInfo );
+       DNSServiceForget( &me->connection );
+       if( me->serverPID != -1 )
+       {
+               err = kill( me->serverPID, SIGTERM );
+               err = map_global_noerr_errno( err );
+               check_noerr( err );
+               me->serverPID = -1;
+       }
+       
+       if( !me->stopped )
+       {
+               me->stopped = true;
+               if( me->stopHandler ) me->stopHandler( me->stopContext, inError );
+               CFRelease( me );
+       }
+}
+
+//===========================================================================================================================
+//     GAITesterAddTestCase
+//===========================================================================================================================
+
+static OSStatus        GAITesterAddTestCase( GAITesterRef me, GAITestCase *inCase )
+{
+       OSStatus                        err;
+       GAITestCase **          ptr;
+       
+       require_action_quiet( inCase->itemList, exit, err = kCountErr );
+       
+       for( ptr = &me->caseList; *ptr; ptr = &( *ptr )->next ) {}
+       *ptr = inCase;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITesterSetStopHandler
+//===========================================================================================================================
+
+static void    GAITesterSetStopHandler( GAITesterRef me, GAITesterStopHandler_f inStopHandler, void *inStopContext )
+{
+       me->stopHandler = inStopHandler;
+       me->stopContext = inStopContext;
+}
+
+//===========================================================================================================================
+//     GAITesterSetResultsHandler
+//===========================================================================================================================
+
+static void    GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+{
+       me->resultsHandler = inResultsHandler;
+       me->resultsContext = inResultsContext;
+}
+
+//===========================================================================================================================
+//     _GAITesterStartNextTest
+//===========================================================================================================================
+
+static void    _GAITesterStartNextTest( GAITesterRef me )
+{
+       OSStatus                                err;
+       GAITestItem *                   item;
+       DNSServiceFlags                 flags;
+       DNSServiceProtocol              protocols;
+       int                                             done = false;
+       
+       if( me->currentItem ) me->currentItem = me->currentItem->next;
+       
+       if( !me->currentItem )
+       {
+               if( me->currentCase )
+               {
+                       // No more test items means that the current test case has completed.
+                       
+                       me->caseEndTime = NanoTimeGetCurrent();
+                       
+                       if( me->resultsHandler )
+                       {
+                               size_t                                  resultCount, i;
+                               GAITestItemResult *             resultArray;
+                               
+                               resultCount     = 0;
+                               for( item = me->currentCase->itemList; item; item = item->next ) ++resultCount;
+                               check( resultCount > 0 );
+                               
+                               resultArray = (GAITestItemResult *) calloc( resultCount, sizeof( *resultArray ) );
+                               require_action( resultArray, exit, err = kNoMemoryErr );
+                               
+                               item = me->currentCase->itemList;
+                               for( i = 0; i < resultCount; ++i )
+                               {
+                                       resultArray[ i ].name                           = item->name;
+                                       resultArray[ i ].connectionTimeUs       = item->connectionTimeUs;
+                                       resultArray[ i ].firstTimeUs            = item->firstTimeUs;
+                                       resultArray[ i ].timeUs                         = item->timeUs;
+                                       resultArray[ i ].error                          = item->error;
+                                       item = item->next;
+                               }
+                               me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, resultArray, resultCount,
+                                       me->resultsContext );
+                               ForgetMem( &resultArray );
+                       }
+                       
+                       me->currentCase = me->currentCase->next;
+                       if( !me->currentCase )
+                       {
+                               done = true;
+                               err = kNoErr;
+                               goto exit;
+                       }
+               }
+               else
+               {
+                       me->currentCase = me->caseList;
+               }
+               require_action_quiet( me->currentCase->itemList, exit, err = kInternalErr );
+               me->currentItem = me->currentCase->itemList;
+       }
+       
+       item = me->currentItem;
+       check( ( item->addressCount >= 1 ) && ( item->addressCount <= 64 ) );
+       
+       if(      !item->wantV4 )                        me->bitmapV4 = 0;
+       else if( !item->hasV4 )                         me->bitmapV4 = 1;
+       else if(  item->addressCount < 64 )     me->bitmapV4 = ( UINT64_C( 1 ) << item->addressCount ) - 1;
+       else                                                            me->bitmapV4 =  ~UINT64_C( 0 );
+       
+       if(      !item->wantV6 )                        me->bitmapV6 = 0;
+       else if( !item->hasV6 )                         me->bitmapV6 = 1;
+       else if(  item->addressCount < 64 )     me->bitmapV6 = ( UINT64_C( 1 ) << item->addressCount ) - 1;
+       else                                                            me->bitmapV6 =  ~UINT64_C( 0 );
+       check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
+       me->gotFirstResult = false;
+       
+       // Perform preliminary tasks if this is the start of a new test case.
+       
+       if( item == me->currentCase->itemList )
+       {
+               // Flush mDNSResponder's cache.
+               
+               err = systemf( NULL, "killall -HUP mDNSResponder" );
+               require_noerr( err, exit );
+               sleep( 1 );
+               
+               me->caseStartTime       = NanoTimeGetCurrent();
+               me->caseEndTime         = kNanoTime_Invalid;
+       }
+       
+       // Start a packet capture.
+       
+       check( !me->pcap );
+       err = _GAITesterCreatePacketCapture( &me->pcap );
+       require_noerr( err, exit );
+       
+       // Start timer for test item's time limit.
+       
+       check( !me->timer );
+       if( item->timeLimitMs > 0 )
+       {
+               unsigned int            timeLimitMs;
+               
+               timeLimitMs = item->timeLimitMs;
+               if( me->callDelayMs   > 0 ) timeLimitMs += (unsigned int) me->callDelayMs;
+               if( me->serverDelayMs > 0 ) timeLimitMs += (unsigned int) me->serverDelayMs;
+               
+               err = DispatchTimerCreate( dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER,
+                       ( (uint64_t) timeLimitMs ) * kNanosecondsPerMillisecond / 10,
+                       me->queue, _GAITesterTimeout, NULL, me, &me->timer );
+               require_noerr( err, exit );
+               dispatch_resume( me->timer );
+       }
+       
+       // Call DNSServiceGetAddrInfo().
+       
+       if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
+       
+       flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates;
+       if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone;
+       
+       protocols = 0;
+       if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
+       if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
+       
+       me->startTicks = UpTicks();
+       
+       check( !me->connection );
+       err = DNSServiceCreateConnection( &me->connection );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+       require_noerr( err, exit );
+       
+       me->connTicks = UpTicks();
+       
+       check( !me->getAddrInfo );
+       me->getAddrInfo = me->connection;
+       err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, protocols, item->name,
+               _GAITesterGetAddrInfoCallback, me );
+       require_noerr( err, exit );
+       
+exit:
+       if( err || done ) _GAITesterStop( me, err );
+}
+
+//===========================================================================================================================
+//     _GAITesterCreatePacketCapture
+//===========================================================================================================================
+
+static OSStatus        _GAITesterCreatePacketCapture( pcap_t **outPCap )
+{
+       OSStatus                                err;
+       pcap_t *                                pcap;
+       struct bpf_program              program;
+       char                                    errBuf[ PCAP_ERRBUF_SIZE ];
+       
+       pcap = pcap_create( "lo0", errBuf );
+       require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+       
+       err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
+       require_noerr_action( err, exit, err = kUnknownErr );
+       
+       err = pcap_set_snaplen( pcap, 512 );
+       require_noerr_action( err, exit, err = kUnknownErr );
+       
+       err = pcap_set_immediate_mode( pcap, 0 );
+       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       
+       err = pcap_activate( pcap );
+       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       
+       err = pcap_setdirection( pcap, PCAP_D_INOUT );
+       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       
+       err = pcap_setnonblock( pcap, 1, errBuf );
+       require_noerr_action_string( err, exit, err = kUnknownErr, errBuf );
+       
+       err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
+       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       
+       err = pcap_setfilter( pcap, &program );
+       pcap_freecode( &program );
+       require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+       
+       *outPCap = pcap;
+       pcap = NULL;
+       
+exit:
+       if( pcap ) pcap_close( pcap );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _GAITesterFirstGAITimeout
+//===========================================================================================================================
+
+static void    _GAITesterFirstGAITimeout( void *inContext )
+{
+       GAITesterRef const              me = (GAITesterRef) inContext;
+       
+       _GAITesterStop( me, kNoResourcesErr );
+}
+
+//===========================================================================================================================
+//     _GAITesterTimeout
+//===========================================================================================================================
+
+static void    _GAITesterTimeout( void *inContext )
+{
+       GAITesterRef const              me = (GAITesterRef) inContext;
+       
+       _GAITesterCompleteCurrentTest( me, kTimeoutErr );
+}
+
+//===========================================================================================================================
+//     _GAITesterFirstGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _GAITesterFirstGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       GAITesterRef const              me = (GAITesterRef) inContext;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inHostname );
+       Unused( inSockAddr );
+       Unused( inTTL );
+       
+       if( ( inFlags & kDNSServiceFlagsAdd ) && !inError )
+       {
+               dispatch_source_forget( &me->timer );
+               DNSServiceForget( &me->getAddrInfo );
+               
+               _GAITesterStartNextTest( me );
+       }
+}
+
+//===========================================================================================================================
+//     _GAITesterGetAddrInfoCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _GAITesterGetAddrInfoCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       OSStatus                                                err;
+       GAITesterRef const                              me              = (GAITesterRef) inContext;
+       GAITestItem * const                             item    = me->currentItem;
+       const sockaddr_ip * const               sip             = (const sockaddr_ip *) inSockAddr;
+       uint64_t                                                nowTicks;
+       uint64_t *                                              bitmapPtr;
+       uint64_t                                                bitmask;
+       int                                                             hasAddr;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inHostname );
+       Unused( inTTL );
+       
+       nowTicks = UpTicks();
+       
+       require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+       
+       // Check if we were expecting an IP address result of this type.
+       
+       if( sip->sa.sa_family == AF_INET )
+       {
+               bitmapPtr       = &me->bitmapV4;
+               hasAddr         = item->hasV4;
+       }
+       else if( sip->sa.sa_family == AF_INET6 )
+       {
+               bitmapPtr       = &me->bitmapV6;
+               hasAddr         = item->hasV6;
+       }
+       else
+       {
+               err = kTypeErr;
+               goto exit;
+       }
+       
+       bitmask = 0;
+       if( hasAddr )
+       {
+               uint32_t                addrOffset;
+               
+               require_noerr_action_quiet( inError, exit, err = inError );
+               
+               if( sip->sa.sa_family == AF_INET )
+               {
+                       const uint32_t          addrV4 = ntohl( sip->v4.sin_addr.s_addr );
+                       
+                       if( strcasecmp( item->name, "localhost." ) == 0 )
+                       {
+                               if( addrV4 == INADDR_LOOPBACK ) bitmask = 1;
+                       }
+                       else
+                       {
+                               addrOffset = addrV4 - kDNSServerBaseAddrV4;
+                               if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+                               {
+                                       bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+                               }
+                       }
+               }
+               else
+               {
+                       const uint8_t * const           addrV6 = sip->v6.sin6_addr.s6_addr;
+                       
+                       if( strcasecmp( item->name, "localhost." ) == 0 )
+                       {
+                               if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 ) bitmask = 1;
+                       }
+                       else if( memcmp( addrV6, kDNSServerBaseAddrV6, 15 ) == 0 )
+                       {
+                               addrOffset = addrV6[ 15 ];
+                               if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+                               {
+                                       bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+                               }
+                       }
+               }
+       }
+       else
+       {
+               require_action_quiet( inError == kDNSServiceErr_NoSuchRecord, exit, err = inError ? inError : kUnexpectedErr );
+               bitmask = 1;
+       }
+       require_action_quiet( bitmask != 0, exit, err = kValueErr );
+       require_action_quiet( *bitmapPtr & bitmask, exit, err = kDuplicateErr );
+       
+       *bitmapPtr &= ~bitmask;
+       if( !me->gotFirstResult )
+       {
+               me->firstTicks          = nowTicks;
+               me->gotFirstResult      = true;
+       }
+       err = kNoErr;
+       
+exit:
+       if( err || ( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) )
+       {
+               me->endTicks = nowTicks;
+               _GAITesterCompleteCurrentTest( me, err );
+       }
+}
+
+//===========================================================================================================================
+//     _GAITesterCompleteCurrentTest
+//===========================================================================================================================
+
+static OSStatus
+       _GAITesterGetDNSMessageFromPacket(
+               const uint8_t *         inPacketPtr,
+               size_t                          inPacketLen,
+               const uint8_t **        outMsgPtr,
+               size_t *                        outMsgLen );
+
+static void    _GAITesterCompleteCurrentTest( GAITesterRef me, OSStatus inError )
+{
+       OSStatus                                err;
+       GAITestItem * const             item    = me->currentItem;
+       struct timeval                  timeStamps[ 4 ];
+       struct timeval *                tsPtr;
+       struct timeval *                tsQA    = NULL;
+       struct timeval *                tsQAAAA = NULL;
+       struct timeval *                tsRA    = NULL;
+       struct timeval *                tsRAAAA = NULL;
+       struct timeval *                t1;
+       struct timeval *                t2;
+       int64_t                                 idleTimeUs;
+       uint8_t                                 name[ kDomainNameLengthMax ];
+       
+       dispatch_source_forget( &me->timer );
+       DNSServiceForget( &me->getAddrInfo );
+       DNSServiceForget( &me->connection );
+       
+       item->error = inError;
+       if( item->error )
+       {
+               err = kNoErr;
+               goto exit;
+       }
+       
+       err = DomainNameFromString( name, item->name, NULL );
+       require_noerr( err, exit );
+       
+       tsPtr = &timeStamps[ 0 ];
+       for( ;; )
+       {
+               int                                                     status;
+               struct pcap_pkthdr *            pktHdr;
+               const uint8_t *                         packet;
+               const uint8_t *                         msgPtr;
+               size_t                                          msgLen;
+               const DNSHeader *                       hdr;
+               unsigned int                            flags;
+               const uint8_t *                         ptr;
+               uint16_t                                        qtype, qclass;
+               uint8_t                                         qname[ kDomainNameLengthMax ];
+               
+               status = pcap_next_ex( me->pcap, &pktHdr, &packet );
+               if( status != 1 ) break;
+               if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue;
+               if( msgLen < kDNSHeaderLength ) continue;
+               
+               hdr = (const DNSHeader *) msgPtr;
+               flags = DNSHeaderGetFlags( hdr );
+               if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue;
+               if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue;
+               
+               ptr = (const uint8_t *) &hdr[ 1 ];
+               if( DNSMessageExtractQuestion( msgPtr, msgLen, ptr, qname, &qtype, &qclass, NULL ) != kNoErr ) continue;
+               if( qclass != kDNSServiceClass_IN ) continue;
+               if( !DomainNameEqual( qname, name ) ) continue;
+               
+               if( item->wantV4 && ( qtype == kDNSServiceType_A ) )
+               {
+                       if( flags & kDNSHeaderFlag_Response )
+                       {
+                               if( tsQA && !tsRA )
+                               {
+                                       tsRA  = tsPtr++;
+                                       *tsRA = pktHdr->ts;
+                               }
+                       }
+                       else if( !tsQA )
+                       {
+                               tsQA  = tsPtr++;
+                               *tsQA = pktHdr->ts;
+                       }
+               }
+               else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) )
+               {
+                       if( flags & kDNSHeaderFlag_Response )
+                       {
+                               if( tsQAAAA && !tsRAAAA )
+                               {
+                                       tsRAAAA  = tsPtr++;
+                                       *tsRAAAA = pktHdr->ts;
+                               }
+                       }
+                       else if( !tsQAAAA )
+                       {
+                               tsQAAAA  = tsPtr++;
+                               *tsQAAAA = pktHdr->ts;
+                       }
+               }
+       }
+       
+       // t1 is the time when the last query was sent.
+       
+       if( tsQA && tsQAAAA )   t1 = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
+       else                                    t1 = tsQA ? tsQA : tsQAAAA;
+       
+       // t2 is when the first response was received.
+       
+       if( tsRA && tsRAAAA )   t2 = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
+       else                                    t2 = tsRA ? tsRA : tsRAAAA;
+       
+       if( t1 && t2 )
+       {
+               idleTimeUs = TIMEVAL_USEC64_DIFF( *t2, *t1 );
+               if( idleTimeUs < 0 ) idleTimeUs = 0;
+       }
+       else
+       {
+               idleTimeUs = 0;
+       }
+       
+       item->connectionTimeUs  = UpTicksToMicroseconds( me->connTicks  - me->startTicks );
+       item->firstTimeUs               = UpTicksToMicroseconds( me->firstTicks - me->connTicks  ) - (uint64_t) idleTimeUs;
+       item->timeUs                    = UpTicksToMicroseconds( me->endTicks   - me->connTicks  ) - (uint64_t) idleTimeUs;
+       
+exit:
+       ForgetPacketCapture( &me->pcap );
+       if( err )       _GAITesterStop( me, err );
+       else            _GAITesterStartNextTest( me );
+}
+
+//===========================================================================================================================
+//     _GAITesterGetDNSMessageFromPacket
+//===========================================================================================================================
+
+#define kHeaderSizeNullLink             4
+#define kHeaderSizeIPv4Min             20
+#define kHeaderSizeIPv6                        40
+#define kHeaderSizeUDP                  8
+
+#define kIPProtocolUDP         0x11
+
+static OSStatus
+       _GAITesterGetDNSMessageFromPacket(
+               const uint8_t *         inPacketPtr,
+               size_t                          inPacketLen,
+               const uint8_t **        outMsgPtr,
+               size_t *                        outMsgLen )
+{
+       OSStatus                                        err;
+       const uint8_t *                         nullLink;
+       uint32_t                                        addressFamily;
+       const uint8_t *                         ip;
+       int                                                     ipHeaderLen;
+       int                                                     protocol;
+       const uint8_t *                         msg;
+       const uint8_t * const           end = &inPacketPtr[ inPacketLen ];
+       
+       nullLink = &inPacketPtr[ 0 ];
+       require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
+       addressFamily = ReadHost32( &nullLink[ 0 ] );
+       
+       ip = &nullLink[ kHeaderSizeNullLink ];
+       if( addressFamily == AF_INET )
+       {
+               require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
+               ipHeaderLen     = ( ip[ 0 ] & 0x0F ) * 4;
+               protocol        =   ip[ 9 ];
+       }
+       else if( addressFamily == AF_INET6 )
+       {
+               require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
+               ipHeaderLen     = kHeaderSizeIPv6;
+               protocol        = ip[ 6 ];
+       }
+       else
+       {
+               err = kTypeErr;
+               goto exit;
+       }
+       require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
+       require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
+       
+       msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+       
+       *outMsgPtr = msg;
+       *outMsgLen = (size_t)( end - msg );
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITestCaseCreate
+//===========================================================================================================================
+
+static OSStatus        GAITestCaseCreate( const char *inTitle, GAITestCase **outCase )
+{
+       OSStatus                        err;
+       GAITestCase *           obj;
+       
+       obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->title = strdup( inTitle );
+       require_action( obj->title, exit, err = kNoMemoryErr );
+       
+       *outCase = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) GAITestCaseFree( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITestCaseFree
+//===========================================================================================================================
+
+static void    GAITestCaseFree( GAITestCase *inCase )
+{
+       GAITestItem *           item;
+       
+       while( ( item = inCase->itemList ) != NULL )
+       {
+               inCase->itemList = item->next;
+               GAITestItemFree( item );
+       }
+       ForgetMem( &inCase->title );
+       free( inCase );
+}
+
+//===========================================================================================================================
+//     GAITestCaseAddItem
+//===========================================================================================================================
+
+static OSStatus
+       GAITestCaseAddItem(
+               GAITestCase *   inCase,
+               unsigned int    inAliasCount,
+               unsigned int    inAddressCount,
+               int                             inTTL,
+               GAITestAddrType inHasAddrs,
+               GAITestAddrType inWantAddrs,
+               unsigned int    inTimeLimitMs,
+               unsigned int    inItemCount )
+{
+       OSStatus                        err;
+       GAITestItem *           item;
+       GAITestItem *           item2;
+       GAITestItem *           newItemList = NULL;
+       GAITestItem **          itemPtr;
+       char *                          ptr;
+       char *                          end;
+       unsigned int            i;
+       char                            name[ 64 ];
+       char                            tag[ kGAITesterTagStringLen + 1 ];
+       
+       require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+       
+       // Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+       
+       require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr );
+       require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr );
+       require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+       
+       ptr = &name[ 0 ];
+       end = &name[ countof( name ) ];
+       
+       // Add Alias label.
+       
+       if(      inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
+       else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+       
+       // Add Count label.
+       
+       SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+       
+       // Add TTL label.
+       
+       if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+       
+       // Add Tag label.
+       
+       SNPrintF_Add( &ptr, end, "tag-%s.",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       
+       // Add IPv4 or IPv6 label if necessary.
+       
+       if(      inHasAddrs == kGAITestAddrType_IPv4 ) SNPrintF_Add( &ptr, end, "ipv4." );
+       else if( inHasAddrs == kGAITestAddrType_IPv6 ) SNPrintF_Add( &ptr, end, "ipv6." );
+       
+       // Finally, add the d.test. labels.
+       
+       SNPrintF_Add( &ptr, end, "d.test." );
+       
+       // Create item.
+       
+       err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, inTimeLimitMs, &item );
+       require_noerr( err, exit );
+       
+       newItemList     = item;
+       itemPtr         = &item->next;
+       
+       // Create repeat items.
+       
+       for( i = 1; i < inItemCount; ++i )
+       {
+               err = GAITestItemDup( item, &item2 );
+               require_noerr( err, exit );
+               
+               *itemPtr        = item2;
+               itemPtr         = &item2->next;
+       }
+       
+       // Append to test case's item list.
+       
+       for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+       *itemPtr        = newItemList;
+       newItemList     = NULL;
+       
+exit:
+       while( ( item = newItemList ) != NULL )
+       {
+               newItemList = item->next;
+               GAITestItemFree( item );
+       }
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITestCaseAddLocalHostItem
+//===========================================================================================================================
+
+static OSStatus
+       GAITestCaseAddLocalHostItem(
+               GAITestCase *   inCase,
+               GAITestAddrType inWantAddrs,
+               unsigned int    inTimeLimitMs,
+               unsigned int    inItemCount )
+{
+       OSStatus                        err;
+       GAITestItem *           item;
+       GAITestItem *           item2;
+       GAITestItem *           newItemList = NULL;
+       GAITestItem **          itemPtr;
+       unsigned int            i;
+       
+       require_action_quiet( inItemCount > 1, exit, err = kNoErr );
+       
+       err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, inTimeLimitMs, &item );
+       require_noerr( err, exit );
+       
+       newItemList     = item;
+       itemPtr         = &item->next;
+       
+       // Create repeat items.
+       
+       for( i = 1; i < inItemCount; ++i )
+       {
+               err = GAITestItemDup( item, &item2 );
+               require_noerr( err, exit );
+               
+               *itemPtr        = item2;
+               itemPtr         = &item2->next;
+       }
+       
+       for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+       *itemPtr        = newItemList;
+       newItemList     = NULL;
+       
+exit:
+       while( ( item = newItemList ) != NULL )
+       {
+               newItemList = item->next;
+               GAITestItemFree( item );
+       }
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITestItemCreate
+//===========================================================================================================================
+
+static OSStatus
+       GAITestItemCreate(
+               const char *    inName,
+               unsigned int    inAddressCount,
+               GAITestAddrType inHasAddrs,
+               GAITestAddrType inWantAddrs,
+               unsigned int    inTimeLimitMs,
+               GAITestItem **  outItem )
+{
+       OSStatus                        err;
+       GAITestItem *           obj = NULL;
+       
+       require_action_quiet( inAddressCount >= 1, exit, err = kCountErr );
+       require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+       require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr );
+       
+       obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
+       
+       obj->addressCount       = inAddressCount;
+       obj->hasV4                      = ( inHasAddrs  & kGAITestAddrType_IPv4 ) ? true : false;
+       obj->hasV6                      = ( inHasAddrs  & kGAITestAddrType_IPv6 ) ? true : false;
+       obj->wantV4                     = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false;
+       obj->wantV6                     = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false;
+       obj->error                      = kInProgressErr;
+       obj->timeLimitMs        = inTimeLimitMs;
+       
+       *outItem = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) GAITestItemFree( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITestItemDup
+//===========================================================================================================================
+
+static OSStatus        GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem )
+{
+       OSStatus                        err;
+       GAITestItem *           obj;
+       
+       obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       *obj = *inItem;
+       obj->next = NULL;
+       if( inItem->name )
+       {
+               obj->name = strdup( inItem->name );
+               require_action( obj->name, exit, err = kNoMemoryErr );
+       }
+       
+       *outItem = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) GAITestItemFree( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+//     GAITestItemFree
+//===========================================================================================================================
+
+static void    GAITestItemFree( GAITestItem *inItem )
+{
+       ForgetMem( &inItem->name );
+       free( inItem );
+}
+
+//===========================================================================================================================
+//     MDNSDiscoveryTestCmd
+//===========================================================================================================================
+
+#define kMDNSDiscoveryTestFirstQueryTimeoutSecs                4
+
+typedef struct
+{
+       DNSServiceRef                   query;                                  // Reference to DNSServiceQueryRecord for replier's "about" TXT record.
+       dispatch_source_t               queryTimer;                             // Used to time out the "about" TXT record query.
+       NanoTime64                              startTime;                              // When the test started.
+       NanoTime64                              endTime;                                // When the test ended.
+       pid_t                                   replierPID;                             // PID of mDNS replier.
+       uint32_t                                ifIndex;                                // Index of interface to run the replier on.
+       unsigned int                    instanceCount;                  // Desired number of service instances.
+       unsigned int                    txtSize;                                // Desired size of each service instance's TXT record data.
+       unsigned int                    recordCountA;                   // Desired number of A records per replier hostname.
+       unsigned int                    recordCountAAAA;                // Desired number of AAAA records per replier hostname.
+       unsigned int                    maxDropCount;                   // Replier's --maxDropCount option argument.
+       double                                  ucastDropRate;                  // Replier's probability of dropping a unicast response.
+       double                                  mcastDropRate;                  // Replier's probability of dropping a multicast query or response.
+       Boolean                                 noAdditionals;                  // True if the replier is to not include additional records in responses.
+       Boolean                                 useIPv4;                                // True if the replier is to use IPv4.
+       Boolean                                 useIPv6;                                // True if the replier is to use IPv6.
+       Boolean                                 flushedCache;                   // True if mDNSResponder's record cache was flushed before testing.
+       char *                                  replierCommand;                 // Command used to run the replier.
+       char *                                  serviceType;                    // Type of services to browse for.
+       ServiceBrowserRef               browser;                                // Service browser.
+       unsigned int                    browseTimeSecs;                 // Amount of time to spend browsing in seconds.
+       const char *                    outputFilePath;                 // File to write test results to. If NULL, then write to stdout.
+       OutputFormatType                outputFormat;                   // Format of test results output.
+       Boolean                                 outputAppendNewline;    // True if a newline character should be appended to JSON output.
+       char                                    hostname[ 32 + 1 ];             // Base hostname that the replier is to use for instance and host names.
+       char                                    tag[ 4 + 1 ];                   // Tag that the replier is to use in its service types.
+       
+}      MDNSDiscoveryTestContext;
+
+static OSStatus        GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex );
+static void            _MDNSDiscoveryTestFirstQueryTimeout( void *inContext );
+static void DNSSD_API
+       _MDNSDiscoveryTestAboutQueryCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void
+       _MDNSDiscoveryTestServiceBrowserCallback(
+               ServiceBrowserResults * inResults,
+               OSStatus                                inError,
+               void *                                  inContext );
+static Boolean _MDNSDiscoveryTestTXTRecordIsValid( const uint8_t *inRecordName, const uint8_t *inTXTPtr, size_t inTXTLen );
+
+static void    MDNSDiscoveryTestCmd( void )
+{
+       OSStatus                                                err;
+       MDNSDiscoveryTestContext *              context;
+       char                                                    queryName[ sizeof_field( MDNSDiscoveryTestContext, hostname ) + 15 ];
+       
+       context = (MDNSDiscoveryTestContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       err = CheckIntegerArgument( gMDNSDiscoveryTest_InstanceCount, "instance count", 1, UINT16_MAX );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckIntegerArgument( gMDNSDiscoveryTest_TXTSize, "TXT size", 1, UINT16_MAX );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckIntegerArgument( gMDNSDiscoveryTest_BrowseTimeSecs, "browse time (seconds)", 1, INT_MAX );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckIntegerArgument( gMDNSDiscoveryTest_RecordCountA, "A record count", 0, 64 );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckIntegerArgument( gMDNSDiscoveryTest_RecordCountAAAA, "AAAA record count", 0, 64 );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckDoubleArgument( gMDNSDiscoveryTest_UnicastDropRate, "unicast drop rate", 0.0, 1.0 );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckDoubleArgument( gMDNSDiscoveryTest_MulticastDropRate, "multicast drop rate", 0.0, 1.0 );
+       require_noerr_quiet( err, exit );
+       
+       err = CheckIntegerArgument( gMDNSDiscoveryTest_MaxDropCount, "drop count", 0, 255 );
+       require_noerr_quiet( err, exit );
+       
+       context->replierPID                             = -1;
+       context->instanceCount                  = (unsigned int) gMDNSDiscoveryTest_InstanceCount;
+       context->txtSize                                = (unsigned int) gMDNSDiscoveryTest_TXTSize;
+       context->browseTimeSecs                 = (unsigned int) gMDNSDiscoveryTest_BrowseTimeSecs;
+       context->recordCountA                   = (unsigned int) gMDNSDiscoveryTest_RecordCountA;
+       context->recordCountAAAA                = (unsigned int) gMDNSDiscoveryTest_RecordCountAAAA;
+       context->ucastDropRate                  = gMDNSDiscoveryTest_UnicastDropRate;
+       context->mcastDropRate                  = gMDNSDiscoveryTest_MulticastDropRate;
+       context->maxDropCount                   = (unsigned int) gMDNSDiscoveryTest_MaxDropCount;
+       context->outputFilePath                 = gMDNSDiscoveryTest_OutputFilePath;
+       context->outputAppendNewline    = gMDNSDiscoveryTest_OutputAppendNewline ? true : false;
+       context->noAdditionals                  = gMDNSDiscoveryTest_NoAdditionals       ? true : false;
+       context->useIPv4                                = ( gMDNSDiscoveryTest_UseIPv4 || !gMDNSDiscoveryTest_UseIPv6 ) ? true : false;
+       context->useIPv6                                = ( gMDNSDiscoveryTest_UseIPv6 || !gMDNSDiscoveryTest_UseIPv4 ) ? true : false;
+       
+       if( gMDNSDiscoveryTest_Interface )
+       {
+               err = InterfaceIndexFromArgString( gMDNSDiscoveryTest_Interface, &context->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       
+       context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gMDNSDiscoveryTest_OutputFormat, &err,
+               kOutputFormatStr_JSON,          kOutputFormatType_JSON,
+               kOutputFormatStr_XML,           kOutputFormatType_XML,
+               kOutputFormatStr_Binary,        kOutputFormatType_Binary,
+               NULL );
+       require_noerr_quiet( err, exit );
+       
+       if( gMDNSDiscoveryTest_FlushCache )
+       {
+               err = CheckRootUser();
+               require_noerr_quiet( err, exit );
+               
+               err = systemf( NULL, "killall -HUP mDNSResponder" );
+               require_noerr( err, exit );
+               sleep( 1 );
+               context->flushedCache = true;
+       }
+       
+       _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( context->hostname ) - 1,
+               context->hostname );
+       _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( context->tag ) - 1, context->tag );
+       
+       ASPrintF( &context->serviceType, "_t-%s-%u-%u._tcp", context->tag, context->txtSize, context->instanceCount );
+       require_action( context->serviceType, exit, err = kUnknownErr );
+       
+       err = ASPrintF( &context->replierCommand,
+               "dnssdutil mdnsreplier --follow %lld --interface %u --hostname %s --tag %s --maxInstanceCount %u "
+               "--countA %u --countAAAA %u --udrop %.1f --mdrop %.1f --maxDropCount %u %?s%?s%?s",
+               (int64_t) getpid(),
+               context->ifIndex,
+               context->hostname,
+               context->tag,
+               context->instanceCount,
+               context->recordCountA,
+               context->recordCountAAAA,
+               context->ucastDropRate,
+               context->mcastDropRate,
+               context->maxDropCount,
+               context->noAdditionals, " --noAdditionals",
+               context->useIPv4,               " --ipv4",
+               context->useIPv6,               " --ipv6" );
+       require_action_quiet( context->replierCommand, exit, err = kUnknownErr );
+       
+       err = SpawnCommand( &context->replierPID, "%s", context->replierCommand );
+       require_noerr_quiet( err, exit );
+       
+       // Query for the replier's about TXT record. A response means it's fully up and running.
+       
+       SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->hostname );
+       err = DNSServiceQueryRecord( &context->query, kDNSServiceFlagsForceMulticast, context->ifIndex, queryName,
+               kDNSServiceType_TXT, kDNSServiceClass_IN, _MDNSDiscoveryTestAboutQueryCallback, context );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( context->query, dispatch_get_main_queue() );
+       require_noerr( err, exit );
+       
+       err = DispatchTimerCreate( dispatch_time_seconds( kMDNSDiscoveryTestFirstQueryTimeoutSecs ),
+               DISPATCH_TIME_FOREVER, UINT64_C_safe( kMDNSDiscoveryTestFirstQueryTimeoutSecs ) * kNanosecondsPerSecond / 10, NULL,
+               _MDNSDiscoveryTestFirstQueryTimeout, NULL, context, &context->queryTimer );
+       require_noerr( err, exit );
+       dispatch_resume( context->queryTimer );
+       
+       context->startTime = NanoTimeGetCurrent();
+       dispatch_main();
+       
+exit:
+       exit( 1 );
+}
+
+//===========================================================================================================================
+//     _MDNSDiscoveryTestFirstQueryTimeout
+//===========================================================================================================================
+
+static void    _MDNSDiscoveryTestFirstQueryTimeout( void *inContext )
+{
+       MDNSDiscoveryTestContext * const                context = (MDNSDiscoveryTestContext *) inContext;
+       
+       dispatch_source_forget( &context->queryTimer );
+       
+       FPrintF( stderr, "error: Query for mdnsreplier's \"about\" TXT record timed out.\n" );
+       exit( 1 );
+}
+
+//===========================================================================================================================
+//     _MDNSDiscoveryTestAboutQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _MDNSDiscoveryTestAboutQueryCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       OSStatus                                                                err;
+       MDNSDiscoveryTestContext * const                context = (MDNSDiscoveryTestContext *) inContext;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inFullName );
+       Unused( inType );
+       Unused( inClass );
+       Unused( inRDataLen );
+       Unused( inRDataPtr );
+       Unused( inTTL );
+       
+       err = inError;
+       require_noerr( err, exit );
+       require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
+       
+       DNSServiceForget( &context->query );
+       dispatch_source_forget( &context->queryTimer );
+       
+       err = ServiceBrowserCreate( dispatch_get_main_queue(), 0, "local.", context->browseTimeSecs, false, &context->browser );
+       require_noerr( err, exit );
+       
+       err = ServiceBrowserAddServiceType( context->browser, context->serviceType );
+       require_noerr( err, exit );
+       
+       ServiceBrowserSetCallback( context->browser, _MDNSDiscoveryTestServiceBrowserCallback, context );
+       ServiceBrowserStart( context->browser );
+       
+exit:
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     _MDNSDiscoveryTestServiceBrowserCallback
+//===========================================================================================================================
+
+#define kMDNSDiscoveryTestResultsKey_ReplierInfo                                       CFSTR( "replierInfo" )
+#define kMDNSDiscoveryTestResultsKey_StartTime                                         CFSTR( "startTime" )
+#define kMDNSDiscoveryTestResultsKey_EndTime                                           CFSTR( "endTime" )
+#define kMDNSDiscoveryTestResultsKey_BrowseTimeSecs                                    CFSTR( "browseTimeSecs" )
+#define kMDNSDiscoveryTestResultsKey_ServiceType                                       CFSTR( "serviceType" )
+#define kMDNSDiscoveryTestResultsKey_FlushedCache                                      CFSTR( "flushedCache" )
+#define kMDNSDiscoveryTestResultsKey_UnexpectedInstances                       CFSTR( "unexpectedInstances" )
+#define kMDNSDiscoveryTestResultsKey_MissingInstances                          CFSTR( "missingInstances" )
+#define kMDNSDiscoveryTestResultsKey_IncorrectInstances                                CFSTR( "incorrectInstances" )
+#define kMDNSDiscoveryTestResultsKey_Success                                           CFSTR( "success" )
+#define kMDNSDiscoveryTestResultsKey_TotalResolveTime                          CFSTR( "totalResolveTimeUs" )
+
+#define kMDNSDiscoveryTestReplierInfoKey_Command                                       CFSTR( "command" )
+#define kMDNSDiscoveryTestReplierInfoKey_InstanceCount                         CFSTR( "instanceCount" )
+#define kMDNSDiscoveryTestReplierInfoKey_TXTSize                                       CFSTR( "txtSize" )
+#define kMDNSDiscoveryTestReplierInfoKey_RecordCountA                          CFSTR( "recordCountA" )
+#define kMDNSDiscoveryTestReplierInfoKey_RecordCountAAAA                       CFSTR( "recordCountAAAA" )
+#define kMDNSDiscoveryTestReplierInfoKey_Hostname                                      CFSTR( "hostname" )
+#define kMDNSDiscoveryTestReplierInfoKey_NoAdditionals                         CFSTR( "noAdditionals" )
+#define kMDNSDiscoveryTestReplierInfoKey_UnicastDropRate                       CFSTR( "ucastDropRate" )
+#define kMDNSDiscoveryTestReplierInfoKey_MulticastDropRate                     CFSTR( "mcastDropRate" )
+#define kMDNSDiscoveryTestReplierInfoKey_MaxDropCount                          CFSTR( "maxDropCount" )
+
+#define kMDNSDiscoveryTestUnexpectedInstanceKey_Name                           CFSTR( "name" )
+#define kMDNSDiscoveryTestUnexpectedInstanceKey_InterfaceIndex         CFSTR( "interfaceIndex" )
+
+#define kMDNSDiscoveryTestIncorrectInstanceKey_Name                                    CFSTR( "name" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve                      CFSTR( "didResolve" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadHostname                     CFSTR( "badHostname" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadPort                         CFSTR( "badPort" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadTXT                          CFSTR( "badTXT" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_UnexpectedAddrs         CFSTR( "unexpectedAddrs" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_MissingAddrs                    CFSTR( "missingAddrs" )
+
+static void    _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext )
+{
+       OSStatus                                                                err;
+       MDNSDiscoveryTestContext * const                context                 = (MDNSDiscoveryTestContext *) inContext;
+       const SBRDomain *                                               domain;
+       const SBRServiceType *                                  type;
+       const SBRServiceInstance *                              instance;
+       const SBRServiceInstance **                             instanceArray   = NULL;
+       const SBRIPAddress *                                    ipaddr;
+       size_t                                                                  hostnameLen;
+       const char *                                                    ptr;
+       const char *                                                    end;
+       unsigned int                                                    i;
+       uint32_t                                                                u32;
+       CFMutableArrayRef                                               unexpectedInstances;
+       CFMutableArrayRef                                               missingInstances;
+       CFMutableArrayRef                                               incorrectInstances;
+       CFMutableDictionaryRef                                  plist                   = NULL;
+       CFMutableDictionaryRef                                  badDict                 = NULL;
+       CFMutableArrayRef                                               unexpectedAddrs = NULL;
+       CFMutableArrayRef                                               missingAddrs    = NULL;
+       uint64_t                                                                maxResolveTimeUs;
+       int                                                                             success                 = false;
+       char                                                                    startTimeStr[ 32 ];
+       char                                                                    endTimeStr[ 32 ];
+       
+       context->endTime = NanoTimeGetCurrent();
+       
+       err = inError;
+       require_noerr( err, exit );
+       
+       _NanoTime64ToDateString( context->startTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToDateString( context->endTime, endTimeStr, sizeof( endTimeStr ) );
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO="
+                       "{"
+                               "%kO=%s"        // replierCommand
+                               "%kO=%lli"      // txtSize
+                               "%kO=%lli"      // instanceCount
+                               "%kO=%lli"      // recordCountA
+                               "%kO=%lli"      // recordCountAAAA
+                               "%kO=%s"        // hostname
+                               "%kO=%b"        // noAdditionals
+                               "%kO=%f"        // ucastDropRate
+                               "%kO=%f"        // mcastDropRate
+                               "%kO=%i"        // maxDropCount
+                       "}"
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // endTime
+                       "%kO=%lli"      // browseTimeSecs
+                       "%kO=%s"        // serviceType
+                       "%kO=%b"        // flushedCache
+                       "%kO=[%@]"      // unexpectedInstances
+                       "%kO=[%@]"      // missingInstances
+                       "%kO=[%@]"      // incorrectInstances
+               "}",
+               kMDNSDiscoveryTestResultsKey_ReplierInfo,
+               kMDNSDiscoveryTestReplierInfoKey_Command,                       context->replierCommand,
+               kMDNSDiscoveryTestReplierInfoKey_InstanceCount,         (int64_t) context->instanceCount,
+               kMDNSDiscoveryTestReplierInfoKey_TXTSize,                       (int64_t) context->txtSize,
+               kMDNSDiscoveryTestReplierInfoKey_RecordCountA,          (int64_t) context->recordCountA,
+               kMDNSDiscoveryTestReplierInfoKey_RecordCountAAAA,       (int64_t) context->recordCountAAAA,
+               kMDNSDiscoveryTestReplierInfoKey_Hostname,                      context->hostname,
+               kMDNSDiscoveryTestReplierInfoKey_NoAdditionals,         context->noAdditionals,
+               kMDNSDiscoveryTestReplierInfoKey_UnicastDropRate,       context->ucastDropRate,
+               kMDNSDiscoveryTestReplierInfoKey_MulticastDropRate,     context->mcastDropRate,
+               kMDNSDiscoveryTestReplierInfoKey_MaxDropCount,          context->maxDropCount,
+               kMDNSDiscoveryTestResultsKey_StartTime,                         startTimeStr,
+               kMDNSDiscoveryTestResultsKey_EndTime,                           endTimeStr,
+               kMDNSDiscoveryTestResultsKey_BrowseTimeSecs,            (int64_t) context->browseTimeSecs,
+               kMDNSDiscoveryTestResultsKey_ServiceType,                       context->serviceType,
+               kMDNSDiscoveryTestResultsKey_FlushedCache,                      context->flushedCache,
+               kMDNSDiscoveryTestResultsKey_UnexpectedInstances,       &unexpectedInstances,
+               kMDNSDiscoveryTestResultsKey_MissingInstances,          &missingInstances,
+               kMDNSDiscoveryTestResultsKey_IncorrectInstances,        &incorrectInstances );
+       require_noerr( err, exit );
+       
+       for( domain = inResults->domainList; domain && ( strcasecmp( domain->name, "local.") != 0 ); domain = domain->next ) {}
+       require_action( domain, exit, err = kInternalErr );
+       
+       for( type = domain->typeList; type && ( strcasecmp( type->name, context->serviceType ) != 0 ); type = type->next ) {}
+       require_action( type, exit, err = kInternalErr );
+       
+       instanceArray = (const SBRServiceInstance **) calloc( context->instanceCount, sizeof( *instanceArray ) );
+       require_action( instanceArray, exit, err = kNoMemoryErr );
+       
+       hostnameLen = strlen( context->hostname );
+       for( instance = type->instanceList; instance; instance = instance->next )
+       {
+               unsigned int            instanceNumber = 0;
+               
+               if( strcmp_prefix( instance->name, context->hostname ) == 0 )
+               {
+                       ptr = &instance->name[ hostnameLen ];
+                       if( ( ptr[ 0 ] == ' ' ) && ( ptr[ 1 ] == '(' ) )
+                       {
+                               ptr += 2;
+                               for( end = ptr; isdigit_safe( *end ); ++end ) {}
+                               if( DecimalTextToUInt32( ptr, end, &u32, &ptr ) == kNoErr )
+                               {
+                                       if( ( u32 >= 2 ) && ( u32 <= context->instanceCount ) && ( ptr[ 0 ] == ')' ) && ( ptr[ 1 ] == '\0' ) )
+                                       {
+                                               instanceNumber = u32;
+                                       }
+                               }
+                       }
+                       else if( *ptr == '\0' )
+                       {
+                               instanceNumber = 1;
+                       }
+               }
+               if( ( instanceNumber != 0 ) && ( instance->ifIndex == context->ifIndex ) )
+               {
+                       check( !instanceArray[ instanceNumber - 1 ] );
+                       instanceArray[ instanceNumber - 1 ] = instance;
+               }
+               else
+               {
+                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, unexpectedInstances,
+                               "{"
+                                       "%kO=%s"
+                                       "%kO=%lli"
+                               "}",
+                               kMDNSDiscoveryTestUnexpectedInstanceKey_Name,                   instance->name,
+                               kMDNSDiscoveryTestUnexpectedInstanceKey_InterfaceIndex, (int64_t) instance->ifIndex );
+                       require_noerr( err, exit );
+               }
+       }
+       
+       maxResolveTimeUs = 0;
+       for( i = 1; i <= context->instanceCount; ++i )
+       {
+               int             isHostnameValid;
+               int             isTXTValid;
+               
+               instance = instanceArray[ i - 1 ];
+               if( !instance )
+               {
+                       if( i == 1 )
+                       {
+                               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingInstances, "%s", context->hostname );
+                               require_noerr( err, exit );
+                       }
+                       else
+                       {
+                               char *          instanceName = NULL;
+                               
+                               ASPrintF( &instanceName, "%s (%u)", context->hostname, i );
+                               require_action( instanceName, exit, err = kUnknownErr );
+                               
+                               err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingInstances, "%s", instanceName );
+                               free( instanceName );
+                               require_noerr( err, exit );
+                       }
+                       continue;
+               }
+               
+               if( !instance->hostname )
+               {
+                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, incorrectInstances,
+                               "{"
+                                       "%kO=%s"
+                                       "%kO=%b"
+                               "}",
+                               kMDNSDiscoveryTestIncorrectInstanceKey_Name,            instance->name,
+                               kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve,      false );
+                       require_noerr( err, exit );
+                       continue;
+               }
+               
+               badDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+               require_action( badDict, exit, err = kNoMemoryErr );
+               
+               isHostnameValid = false;
+               if( strcmp_prefix( instance->hostname, context->hostname ) == 0 )
+               {
+                       ptr = &instance->hostname[ hostnameLen ];
+                       if( i == 1 )
+                       {
+                               if( strcmp( ptr, ".local." ) == 0 ) isHostnameValid = true;
+                       }
+                       else if( *ptr == '-' )
+                       {
+                               ++ptr;
+                               for( end = ptr; isdigit_safe( *end ); ++end ) {}
+                               if( DecimalTextToUInt32( ptr, end, &u32, &ptr ) == kNoErr )
+                               {
+                                       if( ( u32 == i ) && ( strcmp( ptr, ".local." ) == 0 ) ) isHostnameValid = true;
+                               }
+                       }
+               }
+               if( !isHostnameValid )
+               {
+                       err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadHostname, instance->hostname,
+                               kSizeCString );
+                       require_noerr( err, exit );
+               }
+               
+               if( instance->port != (uint16_t)( kMDNSReplierPortBase + context->txtSize ) )
+               {
+                       err = CFDictionarySetInt64( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadPort, instance->port );
+                       require_noerr( err, exit );
+               }
+               
+               isTXTValid = false;
+               if( instance->txtLen == context->txtSize )
+               {
+                       uint8_t         name[ kDomainNameLengthMax ];
+                       
+                       err = DomainNameFromString( name, instance->name, NULL );
+                       require_noerr( err, exit );
+                       
+                       err = DomainNameAppendString( name, type->name, NULL );
+                       require_noerr( err, exit );
+                       
+                       err = DomainNameAppendString( name, "local", NULL );
+                       require_noerr( err, exit );
+                       
+                       if( _MDNSDiscoveryTestTXTRecordIsValid( name, instance->txtPtr, instance->txtLen ) ) isTXTValid = true;
+               }
+               if( !isTXTValid )
+               {
+                       char *          hexStr = NULL;
+                       
+                       ASPrintF( &hexStr, "%.4H", instance->txtPtr, (int) instance->txtLen, (int) instance->txtLen );
+                       require_action( hexStr, exit, err = kUnknownErr );
+                       
+                       err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadTXT, hexStr, kSizeCString );
+                       free( hexStr );
+                       require_noerr( err, exit );
+               }
+               
+               if( isHostnameValid )
+               {
+                       uint64_t                        addrV4Bitmap, addrV6Bitmap, bitmask, resolveTimeUs;
+                       unsigned int            j;
+                       uint8_t                         addrV4[ 4 ];
+                       uint8_t                         addrV6[ 16 ];
+                       
+                       if( context->recordCountA < 64 )        addrV4Bitmap = ( UINT64_C( 1 ) << context->recordCountA ) - 1;
+                       else                                                            addrV4Bitmap =  ~UINT64_C( 0 );
+                       
+                       if( context->recordCountAAAA < 64 ) addrV6Bitmap = ( UINT64_C( 1 ) << context->recordCountAAAA ) - 1;
+                       else                                                            addrV6Bitmap =  ~UINT64_C( 0 );
+                       
+                       addrV4[ 0 ] = 0;
+                       WriteBig16( &addrV4[ 1 ], i );
+                       addrV4[ 3 ] = 0;
+                       
+                       memcpy( addrV6, kMDNSReplierBaseAddrV6, 16 );
+                       WriteBig16( &addrV6[ 12 ], i );
+                       
+                       unexpectedAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+                       require_action( unexpectedAddrs, exit, err = kNoMemoryErr );
+                       
+                       resolveTimeUs = 0;
+                       for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+                       {
+                               const uint8_t *         addrPtr;
+                               unsigned int            lsb;
+                               int                                     isAddrValid = false;
+                               
+                               if( ipaddr->sip.sa.sa_family == AF_INET )
+                               {
+                                       addrPtr = (const uint8_t *) &ipaddr->sip.v4.sin_addr.s_addr;
+                                       lsb             = addrPtr[ 3 ];
+                                       if( ( memcmp( addrPtr, addrV4, 3 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountA ) )
+                                       {
+                                               bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+                                               addrV4Bitmap &= ~bitmask;
+                                               isAddrValid = true;
+                                       }
+                               }
+                               else if( ipaddr->sip.sa.sa_family == AF_INET6 )
+                               {
+                                       addrPtr = ipaddr->sip.v6.sin6_addr.s6_addr;
+                                       lsb             = addrPtr[ 15 ];
+                                       if( ( memcmp( addrPtr, addrV6, 15 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountAAAA ) )
+                                       {
+                                               bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+                                               addrV6Bitmap &= ~bitmask;
+                                               isAddrValid = true;
+                                       }
+                               }
+                               if( isAddrValid )
+                               {
+                                       if( ipaddr->resolveTimeUs > resolveTimeUs ) resolveTimeUs = ipaddr->resolveTimeUs;
+                               }
+                               else
+                               {
+                                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, unexpectedAddrs, "%##a", &ipaddr->sip );
+                                       require_noerr( err, exit );
+                               }
+                       }
+                       
+                       resolveTimeUs += ( instance->discoverTimeUs + instance->resolveTimeUs );
+                       if( resolveTimeUs > maxResolveTimeUs ) maxResolveTimeUs = resolveTimeUs;
+                       
+                       if( CFArrayGetCount( unexpectedAddrs ) > 0 )
+                       {
+                               CFDictionarySetValue( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_UnexpectedAddrs, unexpectedAddrs );
+                       }
+                       ForgetCF( &unexpectedAddrs );
+                       
+                       missingAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+                       require_action( missingAddrs, exit, err = kNoMemoryErr );
+                       
+                       for( j = 1; addrV4Bitmap != 0; ++j )
+                       {
+                               bitmask = UINT64_C( 1 ) << ( j - 1 );
+                               if( addrV4Bitmap & bitmask )
+                               {
+                                       addrV4Bitmap &= ~bitmask;
+                                       addrV4[ 3 ] = (uint8_t) j;
+                                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.4a", addrV4 );
+                                       require_noerr( err, exit );
+                               }
+                       }
+                       for( j = 1; addrV6Bitmap != 0; ++j )
+                       {
+                               bitmask = UINT64_C( 1 ) << ( j - 1 );
+                               if( addrV6Bitmap & bitmask )
+                               {
+                                       addrV6Bitmap &= ~bitmask;
+                                       addrV6[ 15 ] = (uint8_t) j;
+                                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.16a", addrV6 );
+                                       require_noerr( err, exit );
+                               }
+                       }
+                       
+                       if( CFArrayGetCount( missingAddrs ) > 0 )
+                       {
+                               CFDictionarySetValue( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_MissingAddrs, missingAddrs );
+                       }
+                       ForgetCF( &missingAddrs );
+               }
+               
+               if( CFDictionaryGetCount( badDict ) > 0 )
+               {
+                       err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_Name, instance->name,
+                               kSizeCString );
+                       require_noerr( err, exit );
+                       
+                       CFDictionarySetBoolean( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve, true );
+                       CFArrayAppendValue( incorrectInstances, badDict );
+               }
+               ForgetCF( &badDict );
+       }
+       
+       if( ( CFArrayGetCount( unexpectedInstances ) == 0 ) &&
+               ( CFArrayGetCount( missingInstances )    == 0 ) &&
+               ( CFArrayGetCount( incorrectInstances )  == 0 ) )
+       {
+               err = CFDictionarySetInt64( plist, kMDNSDiscoveryTestResultsKey_TotalResolveTime, (int64_t) maxResolveTimeUs );
+               require_noerr( err, exit );
+               success = true;
+       }
+       else
+       {
+               success = false;
+       }
+       CFDictionarySetBoolean( plist, kMDNSDiscoveryTestResultsKey_Success, success );
+       
+       err = OutputPropertyList( plist, context->outputFormat, context->outputAppendNewline, context->outputFilePath );
+       require_noerr_quiet( err, exit );
+       
+exit:
+       ForgetCF( &context->browser );
+       if( context->replierPID != -1 )
+       {
+               kill( context->replierPID, SIGTERM );
+               context->replierPID = -1;
+       }
+       FreeNullSafe( instanceArray );
+       CFReleaseNullSafe( plist );
+       CFReleaseNullSafe( badDict );
+       CFReleaseNullSafe( unexpectedAddrs );
+       CFReleaseNullSafe( missingAddrs );
+       exit( err ? 1 : ( success ? 0 : 2 ) );
+}
+
+//===========================================================================================================================
+//     _MDNSDiscoveryTestTXTRecordIsValid
+//===========================================================================================================================
+
+static Boolean _MDNSDiscoveryTestTXTRecordIsValid( const uint8_t *inRecordName, const uint8_t *inTXTPtr, size_t inTXTLen )
+{
+       uint32_t                        hash;
+       int                                     n;
+       const uint8_t *         ptr;
+       size_t                          i, wholeCount, remCount;
+       uint8_t                         txtStr[ 16 ];
+       
+       if( inTXTLen == 0 ) return( false );
+       
+       hash = FNV1( inRecordName, DomainNameLength( inRecordName ) );
+       
+       txtStr[ 0 ] = 15;
+       n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash );
+       check( n == 15 );
+       
+       ptr = inTXTPtr;
+       wholeCount = inTXTLen / 16;
+       for( i = 0; i < wholeCount; ++i )
+       {
+               if( memcmp( ptr, txtStr, 16 ) != 0 ) return( false );
+               ptr += 16;
+       }
+       
+       remCount = inTXTLen % 16;
+       if( remCount > 0 )
+       {
+               txtStr[ 0 ] = (uint8_t)( remCount - 1 );
+               if( memcmp( ptr, txtStr, remCount ) != 0 ) return( false );
+               ptr += remCount;
+       }
+       check( ptr == &inTXTPtr[ inTXTLen ] );
+       return( true );
+}
+
+//===========================================================================================================================
+//     DotLocalTestCmd
+//===========================================================================================================================
+
+#define kDotLocalTestPreparationTimeLimitSecs          5
+#define kDotLocalTestSubTestDurationSecs                       5
+
+// Constants for SRV record query subtest.
+
+#define kDotLocalTestSRV_Priority              1
+#define kDotLocalTestSRV_Weight                        0
+#define kDotLocalTestSRV_Port                  80
+#define kDotLocalTestSRV_TargetName            ( (const uint8_t *) "\x03" "www" "\x07" "example" "\x03" "com" )
+#define kDotLocalTestSRV_TargetStr             "www.example.com."
+#define kDotLocalTestSRV_ResultStr             "1 0 80 " kDotLocalTestSRV_TargetStr
+
+typedef enum
+{
+       kDotLocalTestState_Unset                                = 0,
+       kDotLocalTestState_Preparing                    = 1,
+       kDotLocalTestState_GAIMDNSOnly                  = 2,
+       kDotLocalTestState_GAIDNSOnly                   = 3,
+       kDotLocalTestState_GAIBoth                              = 4,
+       kDotLocalTestState_GAINeither                   = 5,
+       kDotLocalTestState_GAINoSuchRecord              = 6,
+       kDotLocalTestState_QuerySRV                             = 7,
+       kDotLocalTestState_Done                                 = 8
+       
+}      DotLocalTestState;
+
+typedef struct
+{
+       const char *                    testDesc;                       // Description of the current subtest.
+       char *                                  queryName;                      // Query name for GetAddrInfo or QueryRecord operation.
+       dispatch_source_t               timer;                          // Timer used for limiting the time for each subtest.
+       NanoTime64                              startTime;                      // Timestamp of when the subtest started.
+       NanoTime64                              endTime;                        // Timestamp of when the subtest ended.
+       CFMutableArrayRef               correctResults;         // Operation results that were expected.
+       CFMutableArrayRef               duplicateResults;       // Operation results that were expected, but were already received.
+       CFMutableArrayRef               unexpectedResults;      // Operation results that were unexpected.
+       OSStatus                                error;                          // Subtest's error code.
+       uint32_t                                addrDNSv4;                      // If hasDNSv4 is true, the expected DNS IPv4 address for queryName.
+       uint32_t                                addrMDNSv4;                     // If hasMDNSv4 is true, the expected MDNS IPv4 address for queryName.
+       uint8_t                                 addrDNSv6[ 16 ];        // If hasDNSv6 is true, the expected DNS IPv6 address for queryName.
+       uint8_t                                 addrMDNSv6[ 16 ];       // If hasMDNSv6 is true, the expected MDNS IPv6 address for queryName.
+       Boolean                                 hasDNSv4;                       // True if queryName has a DNS IPv4 address.
+       Boolean                                 hasDNSv6;                       // True if queryName has a DNS IPv6 address.
+       Boolean                                 hasMDNSv4;                      // True if queryName has an MDNS IPv4 address.
+       Boolean                                 hasMDNSv6;                      // True if queryName has an MDNS IPv6 address.
+       Boolean                                 needDNSv4;                      // True if operation is expecting, but hasn't received a DNS IPv4 result.
+       Boolean                                 needDNSv6;                      // True if operation is expecting, but hasn't received a DNS IPv6 result.
+       Boolean                                 needMDNSv4;                     // True if operation is expecting, but hasn't received an MDNS IPv4 result.
+       Boolean                                 needMDNSv6;                     // True if operation is expecting, but hasn't received an MDNS IPv6 result.
+       Boolean                                 needSRV;                        // True if operation is expecting, but hasn't received an SRV result.
+       
+}      DotLocalSubtest;
+
+typedef struct
+{
+       dispatch_source_t               timer;                          // Timer used for limiting the time for each state/subtest.
+       DotLocalSubtest *               subtest;                        // Current subtest's state.
+       DNSServiceRef                   connection;                     // Shared connection for DNS-SD operations.
+       DNSServiceRef                   op;                                     // Reference for the current DNS-SD operation.
+       DNSServiceRef                   op2;                            // Reference for mdnsreplier probe query used during preparing state.
+       DNSRecordRef                    localSOARef;            // Reference returned by DNSServiceRegisterRecord() for local. SOA record.
+       char *                                  replierCmd;                     // Command used to invoke the mdnsreplier.
+       char *                                  serverCmd;                      // Command used to invoke the test DNS server.
+       CFMutableArrayRef               reportsGAI;                     // Reports for subtests that use DNSServiceGetAddrInfo.
+       CFMutableArrayRef               reportsQuerySRV;        // Reports for subtests that use DNSServiceQueryRecord for SRV records.
+       NanoTime64                              startTime;                      // Timestamp for when the test started.
+       NanoTime64                              endTime;                        // Timestamp for when the test ended.
+       DotLocalTestState               state;                          // The test's current state.
+       pid_t                                   replierPID;                     // PID of spawned mdnsreplier.
+       pid_t                                   serverPID;                      // PID of spawned test DNS server.
+       uint32_t                                ifIndex;                        // Interface index used for mdnsreplier.
+       char *                                  outputFilePath;         // File to write test results to. If NULL, then write to stdout.
+       OutputFormatType                outputFormat;           // Format of test results output.
+       Boolean                                 appendNewline;          // True if a newline character should be appended to JSON output.
+       Boolean                                 registeredSOA;          // True if the dummy local. SOA record was successfully registered.
+       Boolean                                 serverIsReady;          // True if response was received for test DNS server probe query.
+       Boolean                                 replierIsReady;         // True if response was received for mdnsreplier probe query.
+       Boolean                                 testFailed;                     // True if at least one subtest failed.
+       char                                    labelStr[ 20 + 1 ];     // Unique label string used for for making the query names used by subtests.
+                                                                                               // The format of this string is "dotlocal-test-<six random chars>".
+}      DotLocalTestContext;
+
+static void    _DotLocalTestStateMachine( DotLocalTestContext *inContext );
+static void DNSSD_API
+       _DotLocalTestProbeQueryRecordCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void DNSSD_API
+       _DotLocalTestRegisterRecordCallback(
+               DNSServiceRef           inSDRef,
+               DNSRecordRef            inRecordRef,
+               DNSServiceFlags         inFlags,
+               DNSServiceErrorType     inError,
+               void *                          inContext );
+static void    _DotLocalTestTimerHandler( void *inContext );
+static void DNSSD_API
+       _DotLocalTestGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void DNSSD_API
+       _DotLocalTestQueryRecordCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+
+static void    DotLocalTestCmd( void )
+{
+       OSStatus                                        err;
+       DotLocalTestContext *           context;
+       uint8_t *                                       rdataPtr;
+       size_t                                          rdataLen;
+       DNSServiceFlags                         flags;
+       char                                            queryName[ 64 ];
+       char                                            randBuf[ 6 + 1 ];       // Large enough for four and six character random strings below.
+       
+       context = (DotLocalTestContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       context->startTime      = NanoTimeGetCurrent();
+       context->endTime        = kNanoTime_Invalid;
+       
+       context->state = kDotLocalTestState_Preparing;
+       
+       if( gDotLocalTest_Interface )
+       {
+               err = InterfaceIndexFromArgString( gDotLocalTest_Interface, &context->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       
+       if( gDotLocalTest_OutputFilePath )
+       {
+               context->outputFilePath = strdup( gDotLocalTest_OutputFilePath );
+               require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+       }
+       
+       context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gDotLocalTest_OutputFormat, &err,
+               kOutputFormatStr_JSON,          kOutputFormatType_JSON,
+               kOutputFormatStr_XML,           kOutputFormatType_XML,
+               kOutputFormatStr_Binary,        kOutputFormatType_Binary,
+               NULL );
+       require_noerr_quiet( err, exit );
+       
+       context->appendNewline = gDotLocalTest_OutputAppendNewline ? true : false;
+       
+       context->reportsGAI = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       require_action( context->reportsGAI, exit, err = kNoMemoryErr );
+       
+       context->reportsQuerySRV = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       require_action( context->reportsQuerySRV, exit, err = kNoMemoryErr );
+       
+       SNPrintF( context->labelStr, sizeof( context->labelStr ), "dotlocal-test-%s",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 6, randBuf ) );
+       
+       // Spawn an mdnsreplier.
+       
+       err = ASPrintF( &context->replierCmd,
+               "dnssdutil mdnsreplier --follow %lld --interface %u --hostname %s --tag %s --maxInstanceCount 2 --countA 1"
+               " --countAAAA 1",
+               (int64_t) getpid(), context->ifIndex, context->labelStr,
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 4, randBuf ) );
+       require_action_quiet( context->replierCmd, exit, err = kUnknownErr );
+       
+       err = SpawnCommand( &context->replierPID, "%s", context->replierCmd );
+       require_noerr( err, exit );
+       
+       // Spawn a test DNS server
+       
+       err = ASPrintF( &context->serverCmd,
+               "dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --domain %s.local.",
+               (int64_t) getpid(), context->labelStr );
+       require_action_quiet( context->serverCmd, exit, err = kUnknownErr );
+       
+       err = SpawnCommand( &context->serverPID, "%s", context->serverCmd );
+       require_noerr( err, exit );
+       
+       // Create a shared DNS-SD connection.
+       
+       err = DNSServiceCreateConnection( &context->connection );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( context->connection, dispatch_get_main_queue() );
+       require_noerr( err, exit );
+       
+       // Create probe query for DNS server, i.e., query for any name that has an A record.
+       
+       SNPrintF( queryName, sizeof( queryName ), "tag-dotlocal-test-probe.ipv4.%s.local.", context->labelStr );
+       
+       flags = kDNSServiceFlagsShareConnection;
+#if( TARGET_OS_WATCH )
+       flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+       
+       context->op = context->connection;
+       err = DNSServiceQueryRecord( &context->op, flags, kDNSServiceInterfaceIndexAny, queryName, kDNSServiceType_A,
+               kDNSServiceClass_IN, _DotLocalTestProbeQueryRecordCallback, context );
+       require_noerr( err, exit );
+       
+       // Create probe query for mdnsreplier's "about" TXT record.
+       
+       SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->labelStr );
+       
+       flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsForceMulticast;
+#if( TARGET_OS_WATCH )
+       flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+       
+       context->op2 = context->connection;
+       err = DNSServiceQueryRecord( &context->op2, flags, context->ifIndex, queryName, kDNSServiceType_TXT, kDNSServiceClass_IN,
+               _DotLocalTestProbeQueryRecordCallback, context );
+       require_noerr( err, exit );
+       
+       // Register a dummy local. SOA record.
+       
+       err = CreateSOARecordData( kRootLabel, kRootLabel, 1976040101, 1 * kSecondsPerDay, 2 * kSecondsPerHour,
+               1000 * kSecondsPerHour, 2 * kSecondsPerDay, &rdataPtr, &rdataLen );
+       require_noerr( err, exit );
+       
+       err = DNSServiceRegisterRecord( context->connection, &context->localSOARef, kDNSServiceFlagsUnique,
+               kDNSServiceInterfaceIndexLocalOnly, "local.", kDNSServiceType_SOA, kDNSServiceClass_IN, 1,
+               rdataPtr, 1 * kSecondsPerHour, _DotLocalTestRegisterRecordCallback, context );
+       require_noerr( err, exit );
+       
+       // Start timer for probe responses and SOA record registration.
+       
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDotLocalTestPreparationTimeLimitSecs ),
+               INT64_C_safe( kDotLocalTestPreparationTimeLimitSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(),
+               _DotLocalTestTimerHandler, context, &context->timer );
+       require_noerr( err, exit );
+       dispatch_resume( context->timer );
+       
+       dispatch_main();
+       
+exit:
+       if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestStateMachine
+//===========================================================================================================================
+
+static OSStatus        _DotLocalSubtestCreate( DotLocalSubtest **outSubtest );
+static void            _DotLocalSubtestFree( DotLocalSubtest *inSubtest );
+static OSStatus        _DotLocalTestStartSubtest( DotLocalTestContext *inContext );
+static OSStatus        _DotLocalTestFinalizeSubtest( DotLocalTestContext *inContext );
+static void            _DotLocalTestFinalizeAndExit( DotLocalTestContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void    _DotLocalTestStateMachine( DotLocalTestContext *inContext )
+{
+       OSStatus                                err;
+       DotLocalTestState               nextState;
+       
+       DNSServiceForget( &inContext->op );
+       DNSServiceForget( &inContext->op2 );
+       dispatch_source_forget( &inContext->timer );
+       
+       switch( inContext->state )
+       {
+               case kDotLocalTestState_Preparing:                      nextState = kDotLocalTestState_GAIMDNSOnly;             break;
+               case kDotLocalTestState_GAIMDNSOnly:            nextState = kDotLocalTestState_GAIDNSOnly;              break;
+               case kDotLocalTestState_GAIDNSOnly:                     nextState = kDotLocalTestState_GAIBoth;                 break;
+               case kDotLocalTestState_GAIBoth:                        nextState = kDotLocalTestState_GAINeither;              break;
+               case kDotLocalTestState_GAINeither:                     nextState = kDotLocalTestState_GAINoSuchRecord; break;
+               case kDotLocalTestState_GAINoSuchRecord:        nextState = kDotLocalTestState_QuerySRV;                break;
+               case kDotLocalTestState_QuerySRV:                       nextState = kDotLocalTestState_Done;                    break;
+               default:                                                                        err = kStateErr;                                                                goto exit;
+       }
+       
+       if( inContext->state == kDotLocalTestState_Preparing )
+       {
+               if( !inContext->registeredSOA || !inContext->serverIsReady || !inContext->replierIsReady )
+               {
+                       FPrintF( stderr, "Preparation timed out: Registered SOA? %s. Server ready? %s. mdnsreplier ready? %s.\n",
+                               YesNoStr( inContext->registeredSOA ),
+                               YesNoStr( inContext->serverIsReady ),
+                               YesNoStr( inContext->replierIsReady ) );
+                       err = kNotPreparedErr;
+                       goto exit;
+               }
+       }
+       else
+       {
+               err = _DotLocalTestFinalizeSubtest( inContext );
+               require_noerr( err, exit );
+       }
+       
+       inContext->state = nextState;
+       if( inContext->state == kDotLocalTestState_Done ) _DotLocalTestFinalizeAndExit( inContext );
+       err = _DotLocalTestStartSubtest( inContext );
+       
+exit:
+       if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     _DotLocalSubtestCreate
+//===========================================================================================================================
+
+static OSStatus        _DotLocalSubtestCreate( DotLocalSubtest **outSubtest )
+{
+       OSStatus                                err;
+       DotLocalSubtest *               obj;
+       
+       obj = (DotLocalSubtest *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->correctResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       require_action( obj->correctResults, exit, err = kNoMemoryErr );
+       
+       obj->duplicateResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       require_action( obj->duplicateResults, exit, err = kNoMemoryErr );
+       
+       obj->unexpectedResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+       require_action( obj->unexpectedResults, exit, err = kNoMemoryErr );
+       
+       *outSubtest = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) _DotLocalSubtestFree( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _DotLocalSubtestFree
+//===========================================================================================================================
+
+static void    _DotLocalSubtestFree( DotLocalSubtest *inSubtest )
+{
+       ForgetMem( &inSubtest->queryName );
+       ForgetCF( &inSubtest->correctResults );
+       ForgetCF( &inSubtest->duplicateResults );
+       ForgetCF( &inSubtest->unexpectedResults );
+       free( inSubtest );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestStartSubtest
+//===========================================================================================================================
+
+static OSStatus        _DotLocalTestStartSubtest( DotLocalTestContext *inContext )
+{
+       OSStatus                                err;
+       DotLocalSubtest *               subtest = NULL;
+       DNSServiceRef                   op              = NULL;
+       DNSServiceFlags                 flags;
+       
+       err = _DotLocalSubtestCreate( &subtest );
+       require_noerr( err, exit );
+       
+       if( inContext->state == kDotLocalTestState_GAIMDNSOnly )
+       {
+               ASPrintF( &subtest->queryName, "%s-2.local.", inContext->labelStr );
+               require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+               
+               subtest->hasMDNSv4 = subtest->needMDNSv4 = true;
+               subtest->hasMDNSv6 = subtest->needMDNSv6 = true;
+               
+               subtest->addrMDNSv4 = htonl( 0x00000201 );                                      // 0.0.2.1
+               memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 );      // 2001:db8:2::2:1
+               subtest->addrMDNSv6[ 13 ] = 2;
+               subtest->addrMDNSv6[ 15 ] = 1;
+               
+               subtest->testDesc = kDotLocalTestSubtestDesc_GAIMDNSOnly;
+       }
+       
+       else if( inContext->state == kDotLocalTestState_GAIDNSOnly )
+       {
+               ASPrintF( &subtest->queryName, "tag-dns-only.%s.local.", inContext->labelStr );
+               require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+               
+               subtest->hasDNSv4 = subtest->needDNSv4 = true;
+               subtest->hasDNSv6 = subtest->needDNSv6 = true;
+               
+               subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );         // 203.0.113.1
+               memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );         // 2001:db8:1::1
+               subtest->addrDNSv6[ 15 ] = 1;
+               
+               subtest->testDesc = kDotLocalTestSubtestDesc_GAIDNSOnly;
+       }
+       
+       else if( inContext->state == kDotLocalTestState_GAIBoth )
+       {
+               ASPrintF( &subtest->queryName, "%s.local.", inContext->labelStr );
+               require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+               
+               subtest->hasDNSv4       = subtest->needDNSv4    = true;
+               subtest->hasDNSv6       = subtest->needDNSv6    = true;
+               subtest->hasMDNSv4      = subtest->needMDNSv4   = true;
+               subtest->hasMDNSv6      = subtest->needMDNSv6   = true;
+               
+               subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 );         // 203.0.113.1
+               memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 );         // 2001:db8:1::1
+               subtest->addrDNSv6[ 15 ] = 1;
+               
+               subtest->addrMDNSv4 = htonl( 0x00000101 );                                      // 0.0.1.1
+               memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 );      // 2001:db8:2::1:1
+               subtest->addrMDNSv6[ 13 ] = 1;
+               subtest->addrMDNSv6[ 15 ] = 1;
+               
+               subtest->testDesc = kDotLocalTestSubtestDesc_GAIBoth;
+       }
+       
+       else if( inContext->state == kDotLocalTestState_GAINeither )
+       {
+               ASPrintF( &subtest->queryName, "doesnotexit-%s.local.", inContext->labelStr );
+               require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+               
+               subtest->testDesc = kDotLocalTestSubtestDesc_GAINeither;
+       }
+       
+       else if( inContext->state == kDotLocalTestState_GAINoSuchRecord )
+       {
+               ASPrintF( &subtest->queryName, "doesnotexit-dns.%s.local.", inContext->labelStr );
+               require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+               
+               subtest->hasDNSv4 = subtest->needDNSv4 = true;
+               subtest->hasDNSv6 = subtest->needDNSv6 = true;
+               subtest->testDesc = kDotLocalTestSubtestDesc_GAINoSuchRecord;
+       }
+       
+       else if( inContext->state == kDotLocalTestState_QuerySRV )
+       {
+               ASPrintF( &subtest->queryName, "_http._tcp.srv-%u-%u-%u.%s%s.local.",
+                       kDotLocalTestSRV_Priority, kDotLocalTestSRV_Weight, kDotLocalTestSRV_Port, kDotLocalTestSRV_TargetStr,
+                       inContext->labelStr );
+               require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+               
+               subtest->needSRV        = true;
+               subtest->testDesc       = kDotLocalTestSubtestDesc_QuerySRV;
+       }
+       
+       else
+       {
+               err = kStateErr;
+               goto exit;
+       }
+       
+       // Start new operation.
+       
+       flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates;
+#if( TARGET_OS_WATCH )
+       flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+       
+       subtest->startTime      = NanoTimeGetCurrent();
+       subtest->endTime        = kNanoTime_Invalid;
+       
+       if( inContext->state == kDotLocalTestState_QuerySRV )
+       {
+               op = inContext->connection;
+               err = DNSServiceQueryRecord( &op, flags, kDNSServiceInterfaceIndexAny, subtest->queryName,
+                       kDNSServiceType_SRV, kDNSServiceClass_IN, _DotLocalTestQueryRecordCallback, inContext );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               op = inContext->connection;
+               err = DNSServiceGetAddrInfo( &op, flags, kDNSServiceInterfaceIndexAny,
+                       kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, subtest->queryName, _DotLocalTestGAICallback, inContext );
+               require_noerr( err, exit );
+       }
+       
+       // Start timer.
+       
+       check( !inContext->timer );
+       err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDotLocalTestSubTestDurationSecs ),
+               INT64_C_safe( kDotLocalTestSubTestDurationSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(),
+               _DotLocalTestTimerHandler, inContext, &inContext->timer );
+       require_noerr( err, exit );
+       dispatch_resume( inContext->timer );
+       
+       check( !inContext->op );
+       inContext->op = op;
+       op = NULL;
+       
+       check( !inContext->subtest );
+       inContext->subtest = subtest;
+       subtest = NULL;
+       
+exit:
+       if( subtest )   _DotLocalSubtestFree( subtest );
+       if( op )                DNSServiceRefDeallocate( op );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestFinalizeSubtest
+//===========================================================================================================================
+
+#define kDotLocalTestReportKey_StartTime                               CFSTR( "startTime" )            // String.
+#define kDotLocalTestReportKey_EndTime                                 CFSTR( "endTime" )                      // String.
+#define kDotLocalTestReportKey_Success                                 CFSTR( "success" )                      // Boolean.
+#define kDotLocalTestReportKey_MDNSReplierCmd                  CFSTR( "replierCmd" )           // String.
+#define kDotLocalTestReportKey_DNSServerCmd                            CFSTR( "serverCmd" )            // String.
+#define kDotLocalTestReportKey_GetAddrInfoTests                        CFSTR( "testsGAI" )                     // Array of Dictionaries.
+#define kDotLocalTestReportKey_QuerySRVTests                   CFSTR( "testsQuerySRV" )        // Array of Dictionaries.
+#define kDotLocalTestReportKey_Description                             CFSTR( "description" )          // String.
+#define kDotLocalTestReportKey_QueryName                               CFSTR( "queryName" )            // String.
+#define kDotLocalTestReportKey_Error                                   CFSTR( "error" )                        // Integer.
+#define kDotLocalTestReportKey_Results                                 CFSTR( "results" )                      // Dictionary of Arrays.
+#define kDotLocalTestReportKey_CorrectResults                  CFSTR( "correct" )                      // Array of Strings
+#define kDotLocalTestReportKey_DuplicateResults                        CFSTR( "duplicates" )           // Array of Strings.
+#define kDotLocalTestReportKey_UnexpectedResults               CFSTR( "unexpected" )           // Array of Strings.
+#define kDotLocalTestReportKey_MissingResults                  CFSTR( "missing" )                      // Array of Strings.
+
+static OSStatus        _DotLocalTestFinalizeSubtest( DotLocalTestContext *inContext )
+{
+       OSStatus                                        err;
+       DotLocalSubtest *                       subtest;
+       CFMutableDictionaryRef          reportDict;
+       CFMutableDictionaryRef          resultsDict;
+       CFMutableArrayRef                       missingResults, reportArray;
+       char                                            startTimeStr[ 32 ];
+       char                                            endTimeStr[ 32 ];
+       
+       subtest = inContext->subtest;
+       inContext->subtest = NULL;
+       
+       subtest->endTime = NanoTimeGetCurrent();
+       _NanoTime64ToDateString( subtest->startTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToDateString( subtest->endTime, endTimeStr, sizeof( endTimeStr ) );
+       
+       reportDict = NULL;
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &reportDict,
+               "{"
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // endTime
+                       "%kO=%s"        // queryName
+                       "%kO=%s"        // description
+                       "%kO={%@}"      // results
+               "}",
+               kDotLocalTestReportKey_StartTime,       startTimeStr,
+               kDotLocalTestReportKey_EndTime,         endTimeStr,
+               kDotLocalTestReportKey_QueryName,       subtest->queryName,
+               kDotLocalTestReportKey_Description,     subtest->testDesc,
+               kDotLocalTestReportKey_Results,         &resultsDict );
+       require_noerr( err, exit );
+       
+       missingResults = NULL;
+       switch( inContext->state )
+       {
+               case kDotLocalTestState_GAIMDNSOnly:
+               case kDotLocalTestState_GAIDNSOnly:
+               case kDotLocalTestState_GAIBoth:
+               case kDotLocalTestState_GAINeither:
+                       if( subtest->needDNSv4 || subtest->needDNSv6 || subtest->needMDNSv4 || subtest->needMDNSv6 )
+                       {
+                               err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+                                       "["
+                                               "%.4a"  // Expected DNS IPv4 address
+                                               "%.16a" // Expected DNS IPv6 address
+                                               "%.4a"  // Expected MDNS IPv4 address
+                                               "%.16a" // Expected MDNS IPv6 address
+                                       "]",
+                                       subtest->needDNSv4  ? &subtest->addrDNSv4  : NULL,
+                                       subtest->needDNSv6  ?  subtest->addrDNSv6  : NULL,
+                                       subtest->needMDNSv4 ? &subtest->addrMDNSv4 : NULL,
+                                       subtest->needMDNSv6 ?  subtest->addrMDNSv6 : NULL );
+                               require_noerr( err, exit );
+                       }
+                       break;
+               
+               case kDotLocalTestState_QuerySRV:
+                       if( subtest->needSRV )
+                       {
+                               err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+                                       "["
+                                               "%s"    // Expected SRV record data as a string.
+                                       "]",
+                                       kDotLocalTestSRV_ResultStr );
+                               require_noerr( err, exit );
+                       }
+                       break;
+               
+               case kDotLocalTestState_GAINoSuchRecord:
+                       if( subtest->needDNSv4 || subtest->needDNSv6 )
+                       {
+                               err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+                                       "["
+                                               "%s" // No Such Record (A)
+                                               "%s" // No Such Record (AAAA)
+                                       "]",
+                                       subtest->needDNSv4 ? kNoSuchRecordAStr    : NULL,
+                                       subtest->needDNSv6 ? kNoSuchRecordAAAAStr : NULL );
+                               require_noerr( err, exit );
+                       }
+                       break;
+               
+               default:
+                       err = kStateErr;
+                       goto exit;
+       }
+       
+       CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_CorrectResults, subtest->correctResults );
+       
+       if( missingResults )
+       {
+               CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_MissingResults, missingResults );
+               ForgetCF( &missingResults );
+               if( !subtest->error ) subtest->error = kNotFoundErr;
+       }
+       
+       if( CFArrayGetCount( subtest->unexpectedResults ) > 0 )
+       {
+               CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_UnexpectedResults, subtest->unexpectedResults );
+               if( !subtest->error ) subtest->error = kUnexpectedErr;
+       }
+       
+       if( CFArrayGetCount( subtest->duplicateResults ) > 0 )
+       {
+               CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_DuplicateResults, subtest->duplicateResults );
+               if( !subtest->error ) subtest->error = kDuplicateErr;
+       }
+       
+       if( subtest->error ) inContext->testFailed = true;
+       err = CFDictionarySetInt64( reportDict, kDotLocalTestReportKey_Error, subtest->error );
+       require_noerr( err, exit );
+       
+       reportArray = ( inContext->state == kDotLocalTestState_QuerySRV ) ? inContext->reportsQuerySRV : inContext->reportsGAI;
+       CFArrayAppendValue( reportArray, reportDict );
+       
+exit:
+       _DotLocalSubtestFree( subtest );
+       CFReleaseNullSafe( reportDict );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestFinalizeAndExit
+//===========================================================================================================================
+
+static void    _DotLocalTestFinalizeAndExit( DotLocalTestContext *inContext )
+{
+       OSStatus                                err;
+       CFPropertyListRef               plist;
+       char                                    startTimeStr[ 32 ];
+       char                                    endTimeStr[ 32 ];
+       
+       check( !inContext->subtest );
+       inContext->endTime = NanoTimeGetCurrent();
+       
+       if( inContext->replierPID != -1 )
+       {
+               kill( inContext->replierPID, SIGTERM );
+               inContext->replierPID = -1;
+       }
+       if( inContext->serverPID != -1 )
+       {
+               kill( inContext->serverPID, SIGTERM );
+               inContext->serverPID = -1;
+       }
+       err = DNSServiceRemoveRecord( inContext->connection, inContext->localSOARef, 0 );
+       require_noerr( err, exit );
+       
+       _NanoTime64ToDateString( inContext->startTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToDateString( inContext->endTime, endTimeStr, sizeof( endTimeStr ) );
+       
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // endTime
+                       "%kO=%O"        // testsGAI
+                       "%kO=%O"        // testsQuerySRV
+                       "%kO=%b"        // success
+                       "%kO=%s"        // replierCmd
+                       "%kO=%s"        // serverCmd
+               "}",
+               kDotLocalTestReportKey_StartTime,                       startTimeStr,
+               kDotLocalTestReportKey_EndTime,                         endTimeStr,
+               kDotLocalTestReportKey_GetAddrInfoTests,        inContext->reportsGAI,
+               kDotLocalTestReportKey_QuerySRVTests,           inContext->reportsQuerySRV,
+               kDotLocalTestReportKey_Success,                         inContext->testFailed ? false : true,
+               kDotLocalTestReportKey_MDNSReplierCmd,          inContext->replierCmd,
+               kDotLocalTestReportKey_DNSServerCmd,            inContext->serverCmd );
+       require_noerr( err, exit );
+       
+       ForgetCF( &inContext->reportsGAI );
+       ForgetCF( &inContext->reportsQuerySRV );
+       
+       err = OutputPropertyList( plist, inContext->outputFormat, inContext->appendNewline, inContext->outputFilePath );
+       CFRelease( plist );
+       require_noerr( err, exit );
+       
+       exit( inContext->testFailed ? 2 : 0 );
+       
+exit:
+       ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestProbeQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _DotLocalTestProbeQueryRecordCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       DotLocalTestContext * const             context = (DotLocalTestContext *) inContext;
+       
+       Unused( inInterfaceIndex );
+       Unused( inFullName );
+       Unused( inType );
+       Unused( inClass );
+       Unused( inRDataLen );
+       Unused( inRDataPtr );
+       Unused( inTTL );
+       
+       check( context->state == kDotLocalTestState_Preparing );
+       
+       require_quiet( ( inFlags & kDNSServiceFlagsAdd ) && !inError, exit );
+       
+       if( inSDRef == context->op )
+       {
+               DNSServiceForget( &context->op );
+               context->serverIsReady = true;
+       }
+       else if( inSDRef == context->op2 )
+       {
+               DNSServiceForget( &context->op2 );
+               context->replierIsReady = true;
+       }
+       
+       if( context->registeredSOA && context->serverIsReady && context->replierIsReady )
+       {
+               _DotLocalTestStateMachine( context );
+       }
+       
+exit:
+       return;
+}
+
+//===========================================================================================================================
+//     _DotLocalTestRegisterRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _DotLocalTestRegisterRecordCallback(
+               DNSServiceRef           inSDRef,
+               DNSRecordRef            inRecordRef,
+               DNSServiceFlags         inFlags,
+               DNSServiceErrorType     inError,
+               void *                          inContext )
+{
+       DotLocalTestContext * const             context = (DotLocalTestContext *) inContext;
+       
+       Unused( inSDRef );
+       Unused( inRecordRef );
+       Unused( inFlags );
+       
+       if( inError ) ErrQuit( 1, "error: local. SOA record registration failed: %#m\n", inError );
+       
+       if( !context->registeredSOA )
+       {
+               context->registeredSOA = true;
+               if( context->serverIsReady && context->replierIsReady ) _DotLocalTestStateMachine( context );
+       }
+}
+
+//===========================================================================================================================
+//     _DotLocalTestTimerHandler
+//===========================================================================================================================
+
+static void    _DotLocalTestTimerHandler( void *inContext )
+{
+       _DotLocalTestStateMachine( (DotLocalTestContext *) inContext );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _DotLocalTestGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       OSStatus                                                err;
+       DotLocalTestContext * const             context = (DotLocalTestContext *) inContext;
+       DotLocalSubtest * const                 subtest = context->subtest;
+       const sockaddr_ip * const               sip             = (const sockaddr_ip *) inSockAddr;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inHostname );
+       Unused( inTTL );
+       
+       require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+       require_action_quiet( ( sip->sa.sa_family == AF_INET ) || ( sip->sa.sa_family == AF_INET6 ), exit, err = kTypeErr );
+       
+       if( context->state == kDotLocalTestState_GAINoSuchRecord )
+       {
+               if( inError == kDNSServiceErr_NoSuchRecord )
+               {
+                       CFMutableArrayRef               array = NULL;   
+                       const char *                    noSuchRecordStr;
+                       
+                       if( sip->sa.sa_family == AF_INET )
+                       {
+                               array = subtest->needDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+                               subtest->needDNSv4 = false;
+                               
+                               noSuchRecordStr = kNoSuchRecordAStr;
+                       }
+                       else
+                       {
+                               array = subtest->needDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+                               subtest->needDNSv6 = false;
+                               
+                               noSuchRecordStr = kNoSuchRecordAAAAStr;
+                       }
+                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%s", noSuchRecordStr );
+                       require_noerr( err, fatal );
+               }
+               else if( !inError )
+               {
+                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpectedResults, "%##a", sip );
+                       require_noerr( err, fatal );
+               }
+               else
+               {
+                       err = inError;
+                       goto exit;
+               }
+       }
+       else
+       {
+               if( !inError )
+               {
+                       CFMutableArrayRef               array = NULL;   
+                       
+                       if( sip->sa.sa_family == AF_INET )
+                       {
+                               const uint32_t          addrV4 = sip->v4.sin_addr.s_addr;
+                               
+                               if( subtest->hasDNSv4 && ( addrV4 == subtest->addrDNSv4 ) )
+                               {
+                                       array = subtest->needDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+                                       subtest->needDNSv4 = false;
+                               }
+                               else if( subtest->hasMDNSv4 && ( addrV4 == subtest->addrMDNSv4 ) )
+                               {
+                                       array = subtest->needMDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+                                       subtest->needMDNSv4 = false;
+                               }
+                       }
+                       else
+                       {
+                               const uint8_t * const           addrV6 = sip->v6.sin6_addr.s6_addr;
+                               
+                               if( subtest->hasDNSv6 && ( memcmp( addrV6, subtest->addrDNSv6, 16 ) == 0 ) )
+                               {
+                                       array = subtest->needDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+                                       subtest->needDNSv6 = false;
+                               }
+                               else if( subtest->hasMDNSv6 && ( memcmp( addrV6, subtest->addrMDNSv6, 16 ) == 0 ) )
+                               {
+                                       array = subtest->needMDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+                                       subtest->needMDNSv6 = false;
+                               }
+                       }
+                       if( !array ) array = subtest->unexpectedResults;
+                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%##a", sip );
+                       require_noerr( err, fatal );
+               }
+               else if( inError == kDNSServiceErr_NoSuchRecord )
+               {
+                       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpectedResults, "%s",
+                               ( sip->sa.sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr );
+                       require_noerr( err, fatal );
+               }
+               else
+               {
+                       err = inError;
+                       goto exit;
+               }
+       }
+       
+exit:
+       if( err )
+       {
+               subtest->error = err;
+               _DotLocalTestStateMachine( context );
+       }
+       return;
+       
+fatal:
+       ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     _DotLocalTestQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _DotLocalTestQueryRecordCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       OSStatus                                                                err;
+       DotLocalTestContext * const                             context = (DotLocalTestContext *) inContext;
+       DotLocalSubtest * const                                 subtest = context->subtest;
+       const SRVRecordDataFixedFields *                fields;
+       const uint8_t *                                                 target;
+       const uint8_t *                                                 ptr;
+       const uint8_t *                                                 end;
+       char *                                                                  rdataStr;
+       unsigned int                                                    priority, weight, port;
+       CFMutableArrayRef                                               array;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inFullName );
+       Unused( inTTL );
+       
+       check( context->state == kDotLocalTestState_QuerySRV );
+       
+       err = inError;
+       require_noerr_quiet( err, exit );
+       require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+       require_action_quiet( ( inType == kDNSServiceType_SRV ) && ( inClass == kDNSServiceClass_IN ), exit, err = kTypeErr );
+       require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kSizeErr );
+       
+       fields  = (const SRVRecordDataFixedFields *) inRDataPtr;
+       SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port );
+       target  = (const uint8_t *) &fields[ 1 ];
+       end             = ( (const uint8_t *) inRDataPtr ) + inRDataLen;
+       for( ptr = target; ( ptr < end ) && ( *ptr != 0 ); ptr += ( 1 + *ptr ) ) {}
+       
+       if( ( priority == kDotLocalTestSRV_Priority ) &&
+               ( weight   == kDotLocalTestSRV_Weight )   &&
+               ( port     == kDotLocalTestSRV_Port )     &&
+               ( ptr < end ) && DomainNameEqual( target, kDotLocalTestSRV_TargetName ) )
+       {
+               array = subtest->needSRV ? subtest->correctResults : subtest->duplicateResults;
+               subtest->needSRV = false;
+       }
+       else
+       {
+               array = subtest->unexpectedResults;
+       }
+       
+       rdataStr = NULL;
+       DNSRecordDataToString( inRDataPtr, inRDataLen, kDNSServiceType_SRV, NULL, 0, &rdataStr );
+       if( !rdataStr )
+       {
+               ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, inRDataLen );
+               require_action( rdataStr, fatal, err = kNoMemoryErr );
+       }
+       
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%s", rdataStr );
+       free( rdataStr );
+       require_noerr( err, fatal );
+       
+exit:
+       if( err )
+       {
+               subtest->error = err;
+               _DotLocalTestStateMachine( context );
+       }
+       return;
+       
+fatal:
+       ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     ProbeConflictTestCmd
+//===========================================================================================================================
+
+#define kProbeConflictTestService_DefaultName          "name"
+#define kProbeConflictTestService_Port                         60000
+
+#define kProbeConflictTestTXTPtr               "\x13" "PROBE-CONFLICT-TEST"
+#define kProbeConflictTestTXTLen               sizeof_string( kProbeConflictTestTXTPtr )
+
+typedef struct
+{
+       const char *            description;
+       const char *            program;
+       Boolean                         expectsRename;
+       
+}      ProbeConflictTestCase;
+
+#define kPCTProgPreWait                        "wait 1000;"    // Wait 1 second before sending gratuitous response.
+#define kPCTProgPostWait               "wait 8000;"    // Wait 8 seconds after sending gratuitous response.
+                                                                                               // This allows ~2.75 seconds for probing and ~5 seconds for a rename.
+
+static const ProbeConflictTestCase             kProbeConflictTestCases[] =
+{
+       // No conflicts
+       
+       { "No probe conflicts.",                       kPCTProgPreWait "probes n-n-n;"       "send;" kPCTProgPostWait, false },
+       
+       // One multicast probe conflict
+       
+       { "One multicast probe conflict (1).",         kPCTProgPreWait "probes m;"           "send;" kPCTProgPostWait, false },
+       { "One multicast probe conflict (2).",         kPCTProgPreWait "probes n-m;"         "send;" kPCTProgPostWait, false },
+       { "One multicast probe conflict (3).",         kPCTProgPreWait "probes n-n-m;"       "send;" kPCTProgPostWait, false },
+       
+       // One unicast probe conflict
+       
+       { "One unicast probe conflict (1).",           kPCTProgPreWait "probes u;"           "send;" kPCTProgPostWait, true },
+       { "One unicast probe conflict (2).",           kPCTProgPreWait "probes n-u;"         "send;" kPCTProgPostWait, true },
+       { "One unicast probe conflict (3).",           kPCTProgPreWait "probes n-n-u;"       "send;" kPCTProgPostWait, true },
+       
+       // One multicast and one unicast probe conflict
+       
+       { "Multicast and unicast probe conflict (1).", kPCTProgPreWait "probes m-u;"         "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (2).", kPCTProgPreWait "probes m-n-u;"       "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (3).", kPCTProgPreWait "probes m-n-n-u;"     "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (4).", kPCTProgPreWait "probes n-m-u;"       "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (5).", kPCTProgPreWait "probes n-m-n-u;"     "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (6).", kPCTProgPreWait "probes n-m-n-n-u;"   "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (7).", kPCTProgPreWait "probes n-n-m-u;"     "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (8).", kPCTProgPreWait "probes n-n-m-n-u;"   "send;" kPCTProgPostWait, true },
+       { "Multicast and unicast probe conflict (9).", kPCTProgPreWait "probes n-n-m-n-n-u;" "send;" kPCTProgPostWait, true },
+       
+       // Two multicast probe conflicts
+       
+       { "Two multicast probe conflicts (1).",        kPCTProgPreWait "probes m-m;"         "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (2).",        kPCTProgPreWait "probes m-n-m;"       "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (3).",        kPCTProgPreWait "probes m-n-n-m;"     "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (4).",        kPCTProgPreWait "probes n-m-m;"       "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (5).",        kPCTProgPreWait "probes n-m-n-m-n;"   "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (6).",        kPCTProgPreWait "probes n-m-n-n-m;"   "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (7).",        kPCTProgPreWait "probes n-n-m-m;"     "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (8).",        kPCTProgPreWait "probes n-n-m-n-m;"   "send;" kPCTProgPostWait, true },
+       { "Two multicast probe conflicts (9).",        kPCTProgPreWait "probes n-n-m-n-n-m;" "send;" kPCTProgPostWait, true },
+};
+
+#define kProbeConflictTestCaseCount            countof( kProbeConflictTestCases )
+
+typedef struct
+{
+       DNSServiceRef                   registration;   // Test service registration.
+       NanoTime64                              testStartTime;  // Test's start time.
+       NanoTime64                              startTime;              // Current test case's start time.
+       MDNSColliderRef                 collider;               // mDNS collider object.
+       CFMutableArrayRef               results;                // Array of test case results.
+       char *                                  serviceName;    // Test service's instance name as a string. (malloced)
+       char *                                  serviceType;    // Test service's service type as a string. (malloced)
+       uint8_t *                               recordName;             // FQDN of collider's record (same as test service's SRV+TXT records). (malloced)
+       unsigned int                    testCaseIndex;  // Index of the current test case.
+       uint32_t                                ifIndex;                // Index of the interface that the collider is to operate on.
+       char *                                  outputFilePath; // File to write test results to. If NULL, then write to stdout. (malloced)
+       OutputFormatType                outputFormat;   // Format of test report output.
+       Boolean                                 appendNewline;  // True if a newline character should be appended to JSON output.
+       Boolean                                 registered;             // True if the test service instance is currently registered.
+       Boolean                                 testFailed;             // True if at least one test case failed.
+       
+}      ProbeConflictTestContext;
+
+static void DNSSD_API
+       _ProbeConflictTestRegisterCallback(
+               DNSServiceRef           inSDRef,
+               DNSServiceFlags         inFlags,
+               DNSServiceErrorType     inError,
+               const char *            inName,
+               const char *            inType,
+               const char *            inDomain,
+               void *                          inContext );
+static void            _ProbeConflictTestColliderStopHandler( void *inContext, OSStatus inError );
+static OSStatus        _ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext );
+static OSStatus        _ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed );
+static void            _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void    ProbeConflictTestCmd( void )
+{
+       OSStatus                                                err;
+       ProbeConflictTestContext *              context;
+       const char *                                    serviceName;
+       char                                                    tag[ 6 + 1 ];
+       
+       context = (ProbeConflictTestContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       if( gProbeConflictTest_Interface )
+       {
+               err = InterfaceIndexFromArgString( gProbeConflictTest_Interface, &context->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       else
+       {
+               err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+               require_noerr_quiet( err, exit );
+       }
+       
+       if( gProbeConflictTest_OutputFilePath )
+       {
+               context->outputFilePath = strdup( gProbeConflictTest_OutputFilePath );
+               require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+       }
+       
+       context->appendNewline  = gProbeConflictTest_OutputAppendNewline ? true : false;
+       context->outputFormat   = (OutputFormatType) CLIArgToValue( "format", gProbeConflictTest_OutputFormat, &err,
+               kOutputFormatStr_JSON,          kOutputFormatType_JSON,
+               kOutputFormatStr_XML,           kOutputFormatType_XML,
+               kOutputFormatStr_Binary,        kOutputFormatType_Binary,
+               NULL );
+       require_noerr_quiet( err, exit );
+       
+       context->results = CFArrayCreateMutable( NULL, kProbeConflictTestCaseCount, &kCFTypeArrayCallBacks );
+       require_action( context->results, exit, err = kNoMemoryErr );
+       
+       serviceName = gProbeConflictTest_UseComputerName ? NULL : kProbeConflictTestService_DefaultName;
+       
+       ASPrintF( &context->serviceType, "_pctest-%s._udp",
+               _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+       require_action( context->serviceType, exit, err = kNoMemoryErr );
+       
+       context->testStartTime = NanoTimeGetCurrent();
+       err = DNSServiceRegister( &context->registration, 0, context->ifIndex, serviceName, context->serviceType, "local.",
+               NULL, htons( kProbeConflictTestService_Port ), 0, NULL, _ProbeConflictTestRegisterCallback, context );
+       require_noerr( err, exit );
+       
+       err = DNSServiceSetDispatchQueue( context->registration, dispatch_get_main_queue() );
+       require_noerr( err, exit );
+       
+       dispatch_main();
+       
+exit:
+       exit( 1 );
+}
+
+//===========================================================================================================================
+//     _ProbeConflictTestRegisterCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _ProbeConflictTestRegisterCallback(
+               DNSServiceRef           inSDRef,
+               DNSServiceFlags         inFlags,
+               DNSServiceErrorType     inError,
+               const char *            inName,
+               const char *            inType,
+               const char *            inDomain,
+               void *                          inContext )
+{
+       OSStatus                                                                err;
+       ProbeConflictTestContext * const                context = (ProbeConflictTestContext *) inContext;
+       
+       Unused( inSDRef );
+       Unused( inType );
+       Unused( inDomain );
+       
+       err = inError;
+       require_noerr( err, exit );
+       
+       if( !context->registered )
+       {
+               if( inFlags & kDNSServiceFlagsAdd )
+               {
+                       uint8_t *                       ptr;
+                       size_t                          recordNameLen;
+                       unsigned int            len;
+                       uint8_t                         name[ kDomainNameLengthMax ];
+                       
+                       context->registered = true;
+                       
+                       FreeNullSafe( context->serviceName );
+                       context->serviceName = strdup( inName );
+                       require_action( context->serviceName, exit, err = kNoMemoryErr );
+                       
+                       err = DomainNameFromString( name, context->serviceName, NULL );
+                       require_noerr( err, exit );
+                       
+                       err = DomainNameAppendString( name, context->serviceType, NULL );
+                       require_noerr( err, exit );
+                       
+                       err = DomainNameAppendString( name, "local", NULL );
+                       require_noerr( err, exit );
+                       
+                       ForgetMem( &context->recordName );
+                       err = DomainNameDup( name, &context->recordName, &recordNameLen );
+                       require_noerr( err, exit );
+                       require_fatal( recordNameLen > 0, "Record name length is zero." );      // Prevents dubious static analyzer warning.
+                       
+                       // Make the first label all caps so that it's easier to spot in system logs.
+                       
+                       ptr = context->recordName;
+                       for( len = *ptr++; len > 0; --len, ++ptr ) *ptr = (uint8_t) toupper_safe( *ptr );
+                       
+                       err = _ProbeConflictTestStartNextTest( context );
+                       require_noerr( err, exit );
+               }
+       }
+       else
+       {
+               if( !( inFlags & kDNSServiceFlagsAdd ) )
+               {
+                       context->registered = false;
+                       err = _ProbeConflictTestStopCurrentTest( context, true );
+                       require_noerr( err, exit );
+               }
+       }
+       err = kNoErr;
+       
+exit:
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     _ProbeConflictTestColliderStopHandler
+//===========================================================================================================================
+
+static void    _ProbeConflictTestColliderStopHandler( void *inContext, OSStatus inError )
+{
+       OSStatus                                                                err;
+       ProbeConflictTestContext * const                context = (ProbeConflictTestContext *) inContext;
+       
+       err = inError;
+       require_noerr_quiet( err, exit );
+       
+       ForgetCF( &context->collider );
+       
+       err = _ProbeConflictTestStopCurrentTest( context, false );
+       require_noerr( err, exit );
+       
+       err = _ProbeConflictTestStartNextTest( context );
+       require_noerr( err, exit );
+       
+exit:
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     _ProbeConflictTestStartNextTest
+//===========================================================================================================================
+
+static OSStatus        _ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext )
+{
+       OSStatus                                                        err;
+       const ProbeConflictTestCase *           testCase;
+       
+       check( !inContext->collider );
+       
+       if( inContext->testCaseIndex < kProbeConflictTestCaseCount )
+       {
+               testCase = &kProbeConflictTestCases[ inContext->testCaseIndex ];
+       }
+       else
+       {
+               _ProbeConflictTestFinalizeAndExit( inContext );
+       }
+       
+       err = MDNSColliderCreate( dispatch_get_main_queue(), &inContext->collider );
+       require_noerr( err, exit );
+       
+       err = MDNSColliderSetProgram( inContext->collider, testCase->program );
+       require_noerr( err, exit );
+       
+       err = MDNSColliderSetRecord( inContext->collider, inContext->recordName, kDNSServiceType_TXT,
+               kProbeConflictTestTXTPtr, kProbeConflictTestTXTLen );
+       require_noerr( err, exit );
+       
+       MDNSColliderSetProtocols( inContext->collider, kMDNSColliderProtocol_IPv4 );
+       MDNSColliderSetInterfaceIndex( inContext->collider, inContext->ifIndex );
+       MDNSColliderSetStopHandler( inContext->collider, _ProbeConflictTestColliderStopHandler, inContext );
+       
+       inContext->startTime = NanoTimeGetCurrent();
+       err = MDNSColliderStart( inContext->collider );
+       require_noerr( err, exit );
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     _ProbeConflictTestStopCurrentTest
+//===========================================================================================================================
+
+#define kProbeConflictTestCaseResultKey_Description                    CFSTR( "description" )
+#define kProbeConflictTestCaseResultKey_StartTime                      CFSTR( "startTime" )
+#define kProbeConflictTestCaseResultKey_EndTime                                CFSTR( "endTime" )
+#define kProbeConflictTestCaseResultKey_ExpectedRename         CFSTR( "expectedRename" )
+#define kProbeConflictTestCaseResultKey_ServiceName                    CFSTR( "serviceName" )
+#define kProbeConflictTestCaseResultKey_Passed                         CFSTR( "passed" )
+
+static OSStatus        _ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed )
+{
+       OSStatus                                                        err;
+       const ProbeConflictTestCase *           testCase;
+       NanoTime64                                                      endTime;
+       Boolean                                                         passed;
+       char                                                            startTimeStr[ 32 ];
+       char                                                            endTimeStr[ 32 ];
+       
+       endTime = NanoTimeGetCurrent();
+       
+       if( inContext->collider )
+       {
+               MDNSColliderSetStopHandler( inContext->collider, NULL, NULL );
+               MDNSColliderStop( inContext->collider );
+               CFRelease( inContext->collider );
+               inContext->collider = NULL;
+       }
+       
+       testCase = &kProbeConflictTestCases[ inContext->testCaseIndex ];
+       passed = ( ( testCase->expectsRename && inRenamed ) || ( !testCase->expectsRename && !inRenamed ) ) ? true : false;
+       if( !passed ) inContext->testFailed = true;
+       
+       _NanoTime64ToDateString( inContext->startTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToDateString( endTime, endTimeStr, sizeof( endTimeStr ) );
+       
+       err = CFPropertyListAppendFormatted( kCFAllocatorDefault, inContext->results,
+               "{"
+                       "%kO=%s"        // description
+                       "%kO=%b"        // expectedRename
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // endTime
+                       "%kO=%s"        // serviceName
+                       "%kO=%b"        // passed
+               "}",
+               kProbeConflictTestCaseResultKey_Description,    testCase->description,
+               kProbeConflictTestCaseResultKey_ExpectedRename, testCase->expectsRename,
+               kProbeConflictTestCaseResultKey_StartTime,              startTimeStr,
+               kProbeConflictTestCaseResultKey_EndTime,                endTimeStr,
+               kProbeConflictTestCaseResultKey_ServiceName,    inContext->serviceName,
+               kProbeConflictTestCaseResultKey_Passed,                 passed );
+       require_noerr( err, exit );
+       
+       ++inContext->testCaseIndex;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     _ProbeConflictTestFinalizeAndExit
+//===========================================================================================================================
+
+#define kProbeConflictTestReportKey_StartTime          CFSTR( "startTime" )
+#define kProbeConflictTestReportKey_EndTime                    CFSTR( "endTime" )
+#define kProbeConflictTestReportKey_ServiceType                CFSTR( "serviceType" )
+#define kProbeConflictTestReportKey_Results                    CFSTR( "results" )
+#define kProbeConflictTestReportKey_Passed                     CFSTR( "passed" )
+
+static void    _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext )
+{
+       OSStatus                                err;
+       CFPropertyListRef               plist;
+       NanoTime64                              endTime;
+       char                                    startTimeStr[ 32 ];
+       char                                    endTimeStr[ 32 ];
+       
+       endTime = NanoTimeGetCurrent();
+       
+       check( !inContext->collider );
+       
+       _NanoTime64ToDateString( inContext->testStartTime, startTimeStr, sizeof( startTimeStr ) );
+       _NanoTime64ToDateString( endTime, endTimeStr, sizeof( endTimeStr ) );
+       
+       err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+               "{"
+                       "%kO=%s"        // startTime
+                       "%kO=%s"        // endTime
+                       "%kO=%s"        // serviceType
+                       "%kO=%O"        // results
+                       "%kO=%b"        // passed
+               "}",
+               kProbeConflictTestReportKey_StartTime,          startTimeStr,
+               kProbeConflictTestReportKey_EndTime,            endTimeStr,
+               kProbeConflictTestReportKey_ServiceType,        inContext->serviceType,
+               kProbeConflictTestReportKey_Results,            inContext->results,
+               kProbeConflictTestReportKey_Passed,                     inContext->testFailed ? false : true );
+       require_noerr( err, exit );
+       ForgetCF( &inContext->results );
+       
+       err = OutputPropertyList( plist, inContext->outputFormat, inContext->appendNewline, inContext->outputFilePath );
+       CFRelease( plist );
+       require_noerr( err, exit );
+       
+       exit( inContext->testFailed ? 2 : 0 );
+       
+exit:
+       ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//     SSDPDiscoverCmd
+//===========================================================================================================================
+
+#define kSSDPPort              1900
+
+typedef struct
+{
+       HTTPHeader                              header;                 // HTTP header object for sending and receiving.
+       dispatch_source_t               readSourceV4;   // Read dispatch source for IPv4 socket.
+       dispatch_source_t               readSourceV6;   // Read dispatch source for IPv6 socket.
+       int                                             receiveSecs;    // After send, the amount of time to spend receiving.
+       uint32_t                                ifindex;                // Index of the interface over which to send the query.
+       Boolean                                 useIPv4;                // True if the query should be sent via IPv4 multicast.
+       Boolean                                 useIPv6;                // True if the query should be sent via IPv6 multicast.
+       
+}      SSDPDiscoverContext;
+
+static void            SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext );
+static void            SSDPDiscoverReadHandler( void *inContext );
+static int             SocketToPortNumber( SocketRef inSock );
+static OSStatus        WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST );
+
+static void    SSDPDiscoverCmd( void )
+{
+       OSStatus                                        err;
+       struct timeval                          now;
+       SSDPDiscoverContext *           context;
+       dispatch_source_t                       signalSource    = NULL;
+       SocketRef                                       sockV4                  = kInvalidSocketRef;
+       SocketRef                                       sockV6                  = kInvalidSocketRef;
+       ssize_t                                         n;
+       int                                                     sendCount;
+       
+       // Set up SIGINT handler.
+       
+       signal( SIGINT, SIG_IGN );
+       err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+       require_noerr( err, exit );
+       dispatch_resume( signalSource );
+       
+       // Check command parameters.
+       
+       if( gSSDPDiscover_ReceiveSecs < -1 )
+       {
+               FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
+               err = kParamErr;
+               goto exit;
+       }
+       
+       // Create context.
+       
+       context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
+       
+       context->receiveSecs    = gSSDPDiscover_ReceiveSecs;
+       context->useIPv4                = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
+       context->useIPv6                = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
+       
+       err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
+       require_noerr_quiet( err, exit );
+       
+       // Set up IPv4 socket.
+       
+       if( context->useIPv4 )
+       {
+               int port;
+               err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
+               require_noerr( err, exit );
+               
+               err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
+               require_noerr( err, exit );
+               
+               err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+               err = map_socket_noerr_errno( sockV4, err );
                require_noerr( err, exit );
        }
        
        // Set up IPv6 socket.
        
-       if( context->useIPv6 )
+       if( context->useIPv6 )
+       {
+               err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+               require_noerr( err, exit );
+               
+               err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+               require_noerr( err, exit );
+               
+               err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+               err = map_socket_noerr_errno( sockV6, err );
+               require_noerr( err, exit );
+       }
+       
+       // Print prologue.
+       
+       SSDPDiscoverPrintPrologue( context );
+       
+       // Send mDNS query message.
+       
+       sendCount = 0;
+       if( IsValidSocket( sockV4 ) )
+       {
+               struct sockaddr_in              mcastAddr4;
+               
+               memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
+               SIN_LEN_SET( &mcastAddr4 );
+               mcastAddr4.sin_family           = AF_INET;
+               mcastAddr4.sin_port                     = htons( kSSDPPort );
+               mcastAddr4.sin_addr.s_addr      = htonl( 0xEFFFFFFA );  // 239.255.255.250
+               
+               err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
+               require_noerr( err, exit );
+               
+               n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
+                       (socklen_t) sizeof( mcastAddr4 ) );
+               err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
+               if( err )
+               {
+                       FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+                       ForgetSocket( &sockV4 );
+               }
+               else
+               {
+                       if( gSSDPDiscover_Verbose )
+                       {
+                               gettimeofday( &now, NULL );
+                               FPrintF( stdout, "---\n" );
+                               FPrintF( stdout, "Send time:    %{du:time}\n",  &now );
+                               FPrintF( stdout, "Source Port:  %d\n",                  SocketToPortNumber( sockV4 ) );
+                               FPrintF( stdout, "Destination:  %##a\n",                &mcastAddr4 );
+                               FPrintF( stdout, "Message size: %zu\n",                 context->header.len );
+                               FPrintF( stdout, "HTTP header:\n%1{text}",              context->header.buf, context->header.len );
+                       }
+                       ++sendCount;
+               }
+       }
+       
+       if( IsValidSocket( sockV6 ) )
+       {
+               struct sockaddr_in6             mcastAddr6;
+               
+               memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
+               SIN6_LEN_SET( &mcastAddr6 );
+               mcastAddr6.sin6_family                          = AF_INET6;
+               mcastAddr6.sin6_port                            = htons( kSSDPPort );
+               mcastAddr6.sin6_addr.s6_addr[  0 ]      = 0xFF; // SSDP IPv6 link-local multicast address FF02::C
+               mcastAddr6.sin6_addr.s6_addr[  1 ]      = 0x02;
+               mcastAddr6.sin6_addr.s6_addr[ 15 ]      = 0x0C;
+               
+               err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
+               require_noerr( err, exit );
+               
+               n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
+                       (socklen_t) sizeof( mcastAddr6 ) );
+               err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
+               if( err )
+               {
+                       FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+                       ForgetSocket( &sockV6 );
+               }
+               else
+               {
+                       if( gSSDPDiscover_Verbose )
+                       {
+                               gettimeofday( &now, NULL );
+                               FPrintF( stdout, "---\n" );
+                               FPrintF( stdout, "Send time:    %{du:time}\n",  &now );
+                               FPrintF( stdout, "Source Port:  %d\n",                  SocketToPortNumber( sockV6 ) );
+                               FPrintF( stdout, "Destination:  %##a\n",                &mcastAddr6 );
+                               FPrintF( stdout, "Message size: %zu\n",                 context->header.len );
+                               FPrintF( stdout, "HTTP header:\n%1{text}",              context->header.buf, context->header.len );
+                       }
+                       ++sendCount;
+               }
+       }
+       require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+       
+       // If there's no wait period after the send, then exit.
+       
+       if( context->receiveSecs == 0 ) goto exit;
+       
+       // Create dispatch read sources for socket(s).
+       
+       if( IsValidSocket( sockV4 ) )
+       {
+               SocketContext *         sockCtx;
+               
+               err = SocketContextCreate( sockV4, context, &sockCtx );
+               require_noerr( err, exit );
+               sockV4 = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
+                       &context->readSourceV4 );
+               if( err ) ForgetSocketContext( &sockCtx );
+               require_noerr( err, exit );
+               
+               dispatch_resume( context->readSourceV4 );
+       }
+       
+       if( IsValidSocket( sockV6 ) )
+       {
+               SocketContext *         sockCtx;
+               
+               err = SocketContextCreate( sockV6, context, &sockCtx );
+               require_noerr( err, exit );
+               sockV6 = kInvalidSocketRef;
+               
+               err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
+                       &context->readSourceV6 );
+               if( err ) ForgetSocketContext( &sockCtx );
+               require_noerr( err, exit );
+               
+               dispatch_resume( context->readSourceV6 );
+       }
+       
+       if( context->receiveSecs > 0 )
+       {
+               dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+                       Exit );
+       }
+       dispatch_main();
+       
+exit:
+       ForgetSocket( &sockV4 );
+       ForgetSocket( &sockV6 );
+       dispatch_source_forget( &signalSource );
+       if( err ) exit( 1 );
+}
+
+static int     SocketToPortNumber( SocketRef inSock )
+{
+       OSStatus                err;
+       sockaddr_ip             sip;
+       socklen_t               len;
+       
+       len = (socklen_t) sizeof( sip );
+       err = getsockname( inSock, &sip.sa, &len );
+       err = map_socket_noerr_errno( inSock, err );
+       check_noerr( err );
+       return( err ? -1 : SockAddrGetPort( &sip ) );
+}
+
+static OSStatus        WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+{
+       OSStatus                err;
+       
+       err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
+       require_noerr( err, exit );
+       
+       err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
+       require_noerr( err, exit );
+       
+       err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
+       require_noerr( err, exit );
+       
+       err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
+       require_noerr( err, exit );
+       
+       err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
+       require_noerr( err, exit );
+       
+       err = HTTPHeader_Commit( inHeader );
+       require_noerr( err, exit );
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     SSDPDiscoverPrintPrologue
+//===========================================================================================================================
+
+static void    SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+{
+       const int                               receiveSecs = inContext->receiveSecs;
+       const char *                    ifName;
+       char                                    ifNameBuf[ IF_NAMESIZE + 1 ];
+       NetTransportType                ifType;
+       
+       ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+       
+       ifType = kNetTransportType_Undefined;
+       if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+       
+       FPrintF( stdout, "Interface:        %s/%d/%s\n",
+               ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
+       FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
+               inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+       FPrintF( stdout, "Receive duration: " );
+       if( receiveSecs >= 0 )  FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
+       else                                    FPrintF( stdout, "∞\n" );
+       FPrintF( stdout, "Start time:       %{du:time}\n", NULL );
+}
+
+//===========================================================================================================================
+//     SSDPDiscoverReadHandler
+//===========================================================================================================================
+
+static void    SSDPDiscoverReadHandler( void *inContext )
+{
+       OSStatus                                                err;
+       struct timeval                                  now;
+       SocketContext * const                   sockCtx = (SocketContext *) inContext;
+       SSDPDiscoverContext * const             context = (SSDPDiscoverContext *) sockCtx->userContext;
+       HTTPHeader * const                              header  = &context->header;
+       sockaddr_ip                                             fromAddr;
+       size_t                                                  msgLen;
+       
+       gettimeofday( &now, NULL );
+       
+       err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
+               NULL, NULL, NULL, NULL );
+       require_noerr( err, exit );
+       
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
+       FPrintF( stdout, "Source:       %##a\n",                &fromAddr );
+       FPrintF( stdout, "Message size: %zu\n",                 msgLen );
+       header->len = msgLen;
+       if( HTTPHeader_Validate( header ) )
+       {
+               FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
+               if( header->extraDataLen > 0 )
+               {
+                       FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
+               }
+       }
+       else
+       {
+               FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
+               goto exit;
+       }
+       
+exit:
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     HTTPHeader_Validate
+//
+//     Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
+//     This assumes the "buf" and "len" fields are set. The other fields are set by this function.
+//
+//     Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+//===========================================================================================================================
+
+Boolean        HTTPHeader_Validate( HTTPHeader *inHeader )
+{
+       const char *            src;
+       const char *            end;
+       
+       // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+       
+       require( inHeader->len < sizeof( inHeader->buf ), exit );
+       src = inHeader->buf;
+       end = src + inHeader->len;
+       if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
+       {
+               src += 4;
+       }
+       else
+       {
+               // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
+               // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+               
+               for( ;; )
+               {
+                       while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
+                       if( src >= end ) goto exit;
+                       ++src;
+                       if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
+                       {
+                               src += 2;
+                               break;
+                       }
+                       else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
+                       {
+                               src += 1;
+                               break;
+                       }
+               }
+       }
+       inHeader->extraDataPtr  = src;
+       inHeader->extraDataLen  = (size_t)( end - src );
+       inHeader->len                   = (size_t)( src - inHeader->buf );
+       return( true );
+       
+exit:
+       return( false );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//     ResQueryCmd
+//===========================================================================================================================
+
+// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
+SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
+       int,
+       ( const char *dname, int class, int type, u_char *answer, int anslen ),
+       ( dname, class, type, answer, anslen ) );
+
+// res_query() from libinfo
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
+SOFT_LINK_FUNCTION_EX( info, res_query,
+       int,
+       ( const char *dname, int class, int type, u_char *answer, int anslen ),
+       ( dname, class, type, answer, anslen ) );
+
+typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+
+static void    ResQueryCmd( void )
+{
+       OSStatus                err;
+       res_query_f             res_query_ptr;
+       int                             n;
+       uint16_t                type, class;
+       uint8_t                 answer[ 1024 ];
+       
+       // Get pointer to one of the res_query() functions.
+       
+       if( gResQuery_UseLibInfo )
+       {
+               if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+               {
+                       FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
+                       err = kNotFoundErr;
+                       goto exit;
+               }
+               res_query_ptr = soft_res_query;
+       }
+       else
+       {
+               if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+               {
+                       FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
+                       err = kNotFoundErr;
+                       goto exit;
+               }
+               res_query_ptr = soft_res_9_query;
+       }
+       
+       // Get record type.
+       
+       err = RecordTypeFromArgString( gResQuery_Type, &type );
+       require_noerr( err, exit );
+       
+       // Get record class.
+       
+       if( gResQuery_Class )
+       {
+               err = RecordClassFromArgString( gResQuery_Class, &class );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               class = kDNSServiceClass_IN;
+       }
+       
+       // Print prologue.
+       
+       FPrintF( stdout, "Name:       %s\n",                    gResQuery_Name );
+       FPrintF( stdout, "Type:       %s (%u)\n",               RecordTypeToString( type ), type );
+       FPrintF( stdout, "Class:      %s (%u)\n",               ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
+       FPrintF( stdout, "---\n" );
+       
+       // Call res_query().
+       
+       n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
+       if( n < 0 )
+       {
+               FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+               err = kUnknownErr;
+               goto exit;
+       }
+       
+       // Print result.
+       
+       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+       
+exit:
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     ResolvDNSQueryCmd
+//===========================================================================================================================
+
+// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
+// avoid including the header file.
+
+typedef void *         dns_handle_t;
+
+SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
+SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
+SOFT_LINK_FUNCTION_EX( resolv, dns_query,
+       int32_t, (
+               dns_handle_t            dns,
+               const char *            name,
+               uint32_t                        dnsclass,
+               uint32_t                        dnstype,
+               char *                          buf,
+               uint32_t                        len,
+               struct sockaddr *       from,
+               uint32_t *                      fromlen ),
+       ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+
+static void    ResolvDNSQueryCmd( void )
+{
+       OSStatus                        err;
+       int                                     n;
+       dns_handle_t            dns = NULL;
+       uint16_t                        type, class;
+       sockaddr_ip                     from;
+       uint32_t                        fromLen;
+       uint8_t                         answer[ 1024 ];
+       
+       // Make sure that the required symbols are available.
+       
+       if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
+       {
+               FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
+               err = kNotFoundErr;
+               goto exit;
+       }
+       
+       if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
+       {
+               FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
+               err = kNotFoundErr;
+               goto exit;
+       }
+       
+       if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
+       {
+               FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
+               err = kNotFoundErr;
+               goto exit;
+       }
+       
+       // Get record type.
+       
+       err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
+       require_noerr( err, exit );
+       
+       // Get record class.
+       
+       if( gResolvDNSQuery_Class )
+       {
+               err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+               require_noerr( err, exit );
+       }
+       else
+       {
+               class = kDNSServiceClass_IN;
+       }
+       
+       // Get dns handle.
+       
+       dns = soft_dns_open( gResolvDNSQuery_Path );
+       if( !dns )
+       {
+               FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
+               err = kUnknownErr;
+               goto exit;
+       }
+       
+       // Print prologue.
+       
+       FPrintF( stdout, "Name:       %s\n",                    gResolvDNSQuery_Name );
+       FPrintF( stdout, "Type:       %s (%u)\n",               RecordTypeToString( type ), type );
+       FPrintF( stdout, "Class:      %s (%u)\n",               ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+       FPrintF( stdout, "Path:       %s\n",                    gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
+       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
+       FPrintF( stdout, "---\n" );
+       
+       // Call dns_query().
+       
+       memset( &from, 0, sizeof( from ) );
+       fromLen = (uint32_t) sizeof( from );
+       n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
+               &fromLen );
+       if( n < 0 )
+       {
+               FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+               err = kUnknownErr;
+               goto exit;
+       }
+       
+       // Print result.
+       
+       FPrintF( stdout, "From:         %##a\n", &from );
+       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+       
+exit:
+       if( dns ) soft_dns_free( dns );
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     CFHostCmd
+//===========================================================================================================================
+
+static void
+       _CFHostResolveCallback(
+               CFHostRef                               inHost,
+               CFHostInfoType                  inInfoType,
+               const CFStreamError *   inError,
+               void *                                  inInfo );
+
+static void    CFHostCmd( void )
+{
+       OSStatus                                err;
+       CFStringRef                             name;
+       Boolean                                 success;
+       CFHostRef                               host = NULL;
+       CFHostClientContext             context;
+       CFStreamError                   streamErr;
+       
+       name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 );
+       require_action( name, exit, err = kUnknownErr );
+       
+       host = CFHostCreateWithName( kCFAllocatorDefault, name );
+       ForgetCF( &name );
+       require_action( host, exit, err = kUnknownErr );
+       
+       memset( &context, 0, sizeof( context ) );
+       success = CFHostSetClient( host, _CFHostResolveCallback, &context );
+       require_action( success, exit, err = kUnknownErr );
+       
+       CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+       
+       // Print prologue.
+       
+       FPrintF( stdout, "Hostname:   %s\n",                    gCFHost_Name );
+       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
+       FPrintF( stdout, "---\n" );
+       
+       success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
+       require_action( success, exit, err = kUnknownErr );
+       err = kNoErr;
+       
+       CFRunLoopRun();
+       
+exit:
+       CFReleaseNullSafe( host );
+       if( err ) exit( 1 );
+}
+
+static void    _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+{
+       OSStatus                        err;
+       struct timeval          now;
+       
+       gettimeofday( &now, NULL );
+       
+       Unused( inInfoType );
+       Unused( inInfo );
+       
+       if( inError && ( inError->domain != 0 ) && ( inError->error ) )
+       {
+               err = inError->error;
+               if( inError->domain == kCFStreamErrorDomainNetDB )
+               {
+                       FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+               }
+               else
+               {
+                       FPrintF( stderr, "Error %#m\n", err );
+               }
+       }
+       else
+       {
+               CFArrayRef                                      addresses;
+               CFIndex                                         count, i;
+               CFDataRef                                       addrData;
+               const struct sockaddr *         sockAddr;
+               Boolean                                         wasResolved = false;
+               
+               addresses = CFHostGetAddressing( inHost, &wasResolved );
+               check( wasResolved );
+               
+               if( addresses )
+               {
+                       count = CFArrayGetCount( addresses );
+                       for( i = 0; i < count; ++i )
+                       {
+                               addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
+                               require_noerr( err, exit );
+                               
+                               sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
+                               FPrintF( stdout, "%##a\n", sockAddr );
+                       }
+               }
+               err = kNoErr;
+       }
+       
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:   %{du:time}\n", &now );
+       
+       if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+       
+exit:
+       exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+//     DNSConfigAddCmd
+//
+//     Note: Based on ajn's supplemental test tool.
+//===========================================================================================================================
+
+static void    DNSConfigAddCmd( void )
+{
+       OSStatus                                        err;
+       CFMutableDictionaryRef          dict    = NULL;
+       CFMutableArrayRef                       array   = NULL;
+       size_t                                          i;
+       SCDynamicStoreRef                       store   = NULL;
+       CFStringRef                                     key             = NULL;
+       Boolean                                         success;
+       
+       // Create dictionary.
+       
+       dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+       require_action( dict, exit, err = kNoMemoryErr );
+       
+       // Add DNS server IP addresses.
+       
+       array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
+       require_action( array, exit, err = kNoMemoryErr );
+       
+       for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+       {
+               CFStringRef             addrStr;
+               
+               addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
+               require_action( addrStr, exit, err = kUnknownErr );
+               
+               CFArrayAppendValue( array, addrStr );
+               CFRelease( addrStr );
+       }
+       
+       CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
+       ForgetCF( &array );
+       
+       // Add domains, if any.
+       
+       array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
+       require_action( array, exit, err = kNoMemoryErr );
+       
+       if( gDNSConfigAdd_DomainCount > 0 )
+       {
+               for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+               {
+                       CFStringRef             domainStr;
+                       
+                       domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
+                       require_action( domainStr, exit, err = kUnknownErr );
+                       
+                       CFArrayAppendValue( array, domainStr );
+                       CFRelease( domainStr );
+               }
+       }
+       else
+       {
+               // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
+               
+               CFArrayAppendValue( array, CFSTR( "" ) );
+       }
+       
+       CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
+       ForgetCF( &array );
+       
+       // Add interface, if any.
+       
+       if( gDNSConfigAdd_Interface )
+       {
+               err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
+               require_noerr( err, exit );
+               
+               CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+       }
+       
+       // Set dictionary in dynamic store.
+       
+       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+       err = map_scerror( store );
+       require_noerr( err, exit );
+       
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
+       require_action( key, exit, err = kUnknownErr );
+       
+       success = SCDynamicStoreSetValue( store, key, dict );
+       require_action( success, exit, err = kUnknownErr );
+       
+exit:
+       CFReleaseNullSafe( dict );
+       CFReleaseNullSafe( array );
+       CFReleaseNullSafe( store );
+       CFReleaseNullSafe( key );
+       gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+//     DNSConfigRemoveCmd
+//===========================================================================================================================
+
+static void    DNSConfigRemoveCmd( void )
+{
+       OSStatus                                err;
+       SCDynamicStoreRef               store   = NULL;
+       CFStringRef                             key             = NULL;
+       Boolean                                 success;
+       
+       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+       err = map_scerror( store );
+       require_noerr( err, exit );
+       
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
+       require_action( key, exit, err = kUnknownErr );
+       
+       success = SCDynamicStoreRemoveValue( store, key );
+       require_action( success, exit, err = kUnknownErr );
+       
+exit:
+       CFReleaseNullSafe( store );
+       CFReleaseNullSafe( key );
+       gExitCode = err ? 1 : 0;
+}
+#endif // TARGET_OS_DARWIN
+
+//===========================================================================================================================
+//     DaemonVersionCmd
+//===========================================================================================================================
+
+static void    DaemonVersionCmd( void )
+{
+       OSStatus                err;
+       uint32_t                size, version;
+       char                    strBuf[ 16 ];
+       
+       size = (uint32_t) sizeof( version );
+       err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
+       require_noerr( err, exit );
+       
+       FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+       
+exit:
+       if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+//     Exit
+//===========================================================================================================================
+
+static void    Exit( void *inContext )
+{
+       const char * const              reason = (const char *) inContext;
+       
+       FPrintF( stdout, "---\n" );
+       FPrintF( stdout, "End time:   %{du:time}\n", NULL );
+       if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
+       exit( gExitCode );
+}
+
+//===========================================================================================================================
+//     PrintFTimestampHandler
+//===========================================================================================================================
+
+static int
+       PrintFTimestampHandler(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext )
+{
+       struct timeval                          now;
+       const struct timeval *          tv;
+       struct tm *                                     localTime;
+       size_t                                          len;
+       int                                                     n;
+       char                                            dateTimeStr[ 32 ];
+       
+       Unused( inUserContext );
+       
+       tv = va_arg( inArgs->args, const struct timeval * );
+       require_action_quiet( !inFormat->suppress, exit, n = 0 );
+       
+       if( !tv )
+       {
+               gettimeofday( &now, NULL );
+               tv = &now;
+       }
+       localTime = localtime( &tv->tv_sec );
+       len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime );
+       if( len == 0 ) dateTimeStr[ 0 ] = '\0';
+       
+       n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
+       
+exit:
+       return( n );
+}
+
+//===========================================================================================================================
+//     PrintFDNSMessageHandler
+//===========================================================================================================================
+
+static int
+       PrintFDNSMessageHandler(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext )
+{
+       OSStatus                        err;
+       const void *            msgPtr;
+       size_t                          msgLen;
+       char *                          text;
+       int                                     n;
+       Boolean                         isMDNS;
+       Boolean                         printRawRData;
+       
+       Unused( inUserContext );
+       
+       msgPtr = va_arg( inArgs->args, const void * );
+       msgLen = va_arg( inArgs->args, size_t );
+       require_action_quiet( !inFormat->suppress, exit, n = 0 );
+       
+       isMDNS = ( inFormat->altForm > 0 ) ? true : false;
+       if(      inFormat->precision == 0 ) printRawRData = false;
+       else if( inFormat->precision == 1 ) printRawRData = true;
+       else
+       {
+               n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" );
+               goto exit;
+       }
+       
+       err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
+       if( !err )
+       {
+               n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
+               free( text );
+       }
+       else
+       {
+               n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+       }
+       
+exit:
+       return( n );
+}
+
+//===========================================================================================================================
+//     PrintFAddRmvFlagsHandler
+//===========================================================================================================================
+
+static int
+       PrintFAddRmvFlagsHandler(
+               PrintFContext * inContext,
+               PrintFFormat *  inFormat,
+               PrintFVAList *  inArgs,
+               void *                  inUserContext )
+{
+       DNSServiceFlags         flags;
+       int                                     n;
+       
+       Unused( inUserContext );
+       
+       flags = va_arg( inArgs->args, DNSServiceFlags );
+       require_action_quiet( !inFormat->suppress, exit, n = 0 );
+       
+       n = PrintFCore( inContext, "%08X %s%c%c", flags,
+               ( flags & kDNSServiceFlagsAdd )           ? "Add" : "Rmv",
+               ( flags & kDNSServiceFlagsMoreComing )    ? '+'   : ' ',
+               ( flags & kDNSServiceFlagsExpiredAnswer ) ? '!'   : ' ' );
+       
+exit:
+       return( n );
+}
+
+//===========================================================================================================================
+//     GetDNSSDFlagsFromOpts
+//===========================================================================================================================
+
+static DNSServiceFlags GetDNSSDFlagsFromOpts( void )
+{
+       DNSServiceFlags         flags;
+       
+       flags = (DNSServiceFlags) gDNSSDFlags;
+       if( flags & kDNSServiceFlagsShareConnection )
+       {
+               FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n",
+                       kDNSServiceFlagsShareConnection );
+       }
+       
+       if( gDNSSDFlag_AllowExpiredAnswers )    flags |= kDNSServiceFlagsAllowExpiredAnswers;
+       if( gDNSSDFlag_BrowseDomains )                  flags |= kDNSServiceFlagsBrowseDomains;
+       if( gDNSSDFlag_DenyCellular )                   flags |= kDNSServiceFlagsDenyCellular;
+       if( gDNSSDFlag_DenyExpensive )                  flags |= kDNSServiceFlagsDenyExpensive;
+       if( gDNSSDFlag_ForceMulticast )                 flags |= kDNSServiceFlagsForceMulticast;
+       if( gDNSSDFlag_IncludeAWDL )                    flags |= kDNSServiceFlagsIncludeAWDL;
+       if( gDNSSDFlag_NoAutoRename )                   flags |= kDNSServiceFlagsNoAutoRename;
+       if( gDNSSDFlag_PathEvaluationDone )             flags |= kDNSServiceFlagsPathEvaluationDone;
+       if( gDNSSDFlag_RegistrationDomains )    flags |= kDNSServiceFlagsRegistrationDomains;
+       if( gDNSSDFlag_ReturnIntermediates )    flags |= kDNSServiceFlagsReturnIntermediates;
+       if( gDNSSDFlag_Shared )                                 flags |= kDNSServiceFlagsShared;
+       if( gDNSSDFlag_SuppressUnusable )               flags |= kDNSServiceFlagsSuppressUnusable;
+       if( gDNSSDFlag_Timeout )                                flags |= kDNSServiceFlagsTimeout;
+       if( gDNSSDFlag_UnicastResponse )                flags |= kDNSServiceFlagsUnicastResponse;
+       if( gDNSSDFlag_Unique )                                 flags |= kDNSServiceFlagsUnique;
+       if( gDNSSDFlag_WakeOnResolve )                  flags |= kDNSServiceFlagsWakeOnResolve;
+       
+       return( flags );
+}
+
+//===========================================================================================================================
+//     CreateConnectionFromArgString
+//===========================================================================================================================
+
+static OSStatus
+       CreateConnectionFromArgString(
+               const char *                    inString,
+               dispatch_queue_t                inQueue,
+               DNSServiceRef *                 outSDRef,
+               ConnectionDesc *                outDesc )
+{
+       OSStatus                        err;
+       DNSServiceRef           sdRef = NULL;
+       ConnectionType          type;
+       int32_t                         pid = -1;       // Initializing because the analyzer claims pid may be used uninitialized.
+       uint8_t                         uuid[ 16 ];
+       
+       if( strcasecmp( inString, kConnectionArg_Normal ) == 0 )
+       {
+               err = DNSServiceCreateConnection( &sdRef );
+               require_noerr( err, exit );
+               type = kConnectionType_Normal;
+       }
+       else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 )
+       {
+               const char * const              pidStr = inString + sizeof_string( kConnectionArgPrefix_PID );
+               
+               err = StringToInt32( pidStr, &pid );
+               if( err )
+               {
+                       FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr );
+                       err = kParamErr;
+                       goto exit;
+               }
+               
+               memset( uuid, 0, sizeof( uuid ) );
+               err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid );
+               if( err )
+               {
+                       FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid );
+                       goto exit;
+               }
+               type = kConnectionType_DelegatePID;
+       }
+       else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 )
+       {
+               const char * const              uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID );
+               
+               check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) );
+               
+               err = StringToUUID( uuidStr, kSizeCString, false, uuid );
+               if( err )
+               {
+                       FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr );
+                       err = kParamErr;
+                       goto exit;
+               }
+               
+               err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid );
+               if( err )
+               {
+                       FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid );
+                       goto exit;
+               }
+               type = kConnectionType_DelegateUUID;
+       }
+       else
+       {
+               FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString );
+               err = kParamErr;
+               goto exit;
+       }
+       
+       err = DNSServiceSetDispatchQueue( sdRef, inQueue );
+       require_noerr( err, exit );
+       
+       *outSDRef = sdRef;
+       if( outDesc )
+       {
+               outDesc->type = type;
+               if(      type == kConnectionType_DelegatePID )  outDesc->delegate.pid = pid;
+               else if( type == kConnectionType_DelegateUUID ) memcpy( outDesc->delegate.uuid, uuid, 16 );
+       }
+       sdRef = NULL;
+       
+exit:
+       if( sdRef ) DNSServiceRefDeallocate( sdRef );
+       return( err );
+}
+
+//===========================================================================================================================
+//     InterfaceIndexFromArgString
+//===========================================================================================================================
+
+static OSStatus        InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
+{
+       OSStatus                err;
+       uint32_t                ifIndex;
+       
+       if( inString )
+       {
+               ifIndex = if_nametoindex( inString );
+               if( ifIndex == 0 )
+               {
+                       err = StringToUInt32( inString, &ifIndex );
+                       if( err )
+                       {
+                               FPrintF( stderr, "error: Invalid interface value: %s\n", inString );
+                               err = kParamErr;
+                               goto exit;
+                       }
+               }
+       }
+       else
+       {
+               ifIndex = 0;
+       }
+       
+       *outIndex = ifIndex;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     RecordDataFromArgString
+//===========================================================================================================================
+
+static OSStatus        RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+{
+       OSStatus                err;
+       uint8_t *               dataPtr = NULL;
+       size_t                  dataLen;
+       
+       if( 0 ) {}
+       
+       // Domain name
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_Domain );
+               
+               err = StringToDomainName( str, &dataPtr, &dataLen );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // File path
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+       {
+               const char * const              path = inString + sizeof_string( kRDataArgPrefix_File );
+               
+               err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+               require_noerr( err, exit );
+               require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+       }
+       
+       // Hexadecimal string
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_HexString );
+               
+               err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+               require_noerr( err, exit );
+               require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+       }
+       
+       // IPv4 address string
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+               
+               err = StringToARecordData( str, &dataPtr, &dataLen );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // IPv6 address string
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+               
+               err = StringToAAAARecordData( str, &dataPtr, &dataLen );
+               require_noerr_quiet( err, exit );
+       }
+       
+       // SRV record
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_SRV );
+               
+               err = CreateSRVRecordDataFromString( str, &dataPtr, &dataLen );
+               require_noerr( err, exit );
+       }
+       
+       // String with escaped hex and octal bytes
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_String );
+               const char * const              end = str + strlen( str );
+               size_t                                  copiedLen;
+               size_t                                  totalLen;
+               Boolean                                 success;
+               
+               if( str < end )
+               {
+                       success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
+                       require_action( success, exit, err = kParamErr );
+                       require_action( totalLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+                       
+                       dataLen = totalLen;
+                       dataPtr = (uint8_t *) malloc( dataLen );
+                       require_action( dataPtr, exit, err = kNoMemoryErr );
+                       
+                       success = ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL );
+                       require_action( success, exit, err = kParamErr );
+                       check( copiedLen == dataLen );
+               }
+               else
+               {
+                       dataPtr = NULL;
+                       dataLen = 0;
+               }
+       }
+       
+       // TXT record
+       
+       else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
+       {
+               const char * const              str = inString + sizeof_string( kRDataArgPrefix_TXT );
+               
+               err = CreateTXTRecordDataFromString( str, ',', &dataPtr, &dataLen );
+               require_noerr( err, exit );
+       }
+       
+       // Unrecognized format
+       
+       else
+       {
+               FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
+               err = kParamErr;
+               goto exit;
+       }
+       
+       err = kNoErr;
+       *outDataLen = dataLen;
+       *outDataPtr = dataPtr;
+       dataPtr = NULL;
+       
+exit:
+       FreeNullSafe( dataPtr );
+       return( err );
+}
+
+//===========================================================================================================================
+//     RecordTypeFromArgString
+//===========================================================================================================================
+
+typedef struct
+{
+       uint16_t                        value;  // Record type's numeric value.
+       const char *            name;   // Record type's name as a string (e.g., "A", "PTR", "SRV").
+       
+}      RecordType;
+
+static const RecordType                kRecordTypes[] =
+{
+       // Common types.
+       
+       { kDNSServiceType_A,                    "A" },
+       { kDNSServiceType_AAAA,                 "AAAA" },
+       { kDNSServiceType_PTR,                  "PTR" },
+       { kDNSServiceType_SRV,                  "SRV" },
+       { kDNSServiceType_TXT,                  "TXT" },
+       { kDNSServiceType_CNAME,                "CNAME" },
+       { kDNSServiceType_SOA,                  "SOA" },
+       { kDNSServiceType_NSEC,                 "NSEC" },
+       { kDNSServiceType_NS,                   "NS" },
+       { kDNSServiceType_MX,                   "MX" },
+       { kDNSServiceType_ANY,                  "ANY" },
+       { kDNSServiceType_OPT,                  "OPT" },
+       
+       // Less common types.
+       
+       { kDNSServiceType_MD,                   "MD" },
+       { kDNSServiceType_NS,                   "NS" },
+       { kDNSServiceType_MD,                   "MD" },
+       { kDNSServiceType_MF,                   "MF" },
+       { kDNSServiceType_MB,                   "MB" },
+       { kDNSServiceType_MG,                   "MG" },
+       { kDNSServiceType_MR,                   "MR" },
+       { kDNSServiceType_NULL,                 "NULL" },
+       { kDNSServiceType_WKS,                  "WKS" },
+       { kDNSServiceType_HINFO,                "HINFO" },
+       { kDNSServiceType_MINFO,                "MINFO" },
+       { kDNSServiceType_RP,                   "RP" },
+       { kDNSServiceType_AFSDB,                "AFSDB" },
+       { kDNSServiceType_X25,                  "X25" },
+       { kDNSServiceType_ISDN,                 "ISDN" },
+       { kDNSServiceType_RT,                   "RT" },
+       { kDNSServiceType_NSAP,                 "NSAP" },
+       { kDNSServiceType_NSAP_PTR,             "NSAP_PTR" },
+       { kDNSServiceType_SIG,                  "SIG" },
+       { kDNSServiceType_KEY,                  "KEY" },
+       { kDNSServiceType_PX,                   "PX" },
+       { kDNSServiceType_GPOS,                 "GPOS" },
+       { kDNSServiceType_LOC,                  "LOC" },
+       { kDNSServiceType_NXT,                  "NXT" },
+       { kDNSServiceType_EID,                  "EID" },
+       { kDNSServiceType_NIMLOC,               "NIMLOC" },
+       { kDNSServiceType_ATMA,                 "ATMA" },
+       { kDNSServiceType_NAPTR,                "NAPTR" },
+       { kDNSServiceType_KX,                   "KX" },
+       { kDNSServiceType_CERT,                 "CERT" },
+       { kDNSServiceType_A6,                   "A6" },
+       { kDNSServiceType_DNAME,                "DNAME" },
+       { kDNSServiceType_SINK,                 "SINK" },
+       { kDNSServiceType_APL,                  "APL" },
+       { kDNSServiceType_DS,                   "DS" },
+       { kDNSServiceType_SSHFP,                "SSHFP" },
+       { kDNSServiceType_IPSECKEY,             "IPSECKEY" },
+       { kDNSServiceType_RRSIG,                "RRSIG" },
+       { kDNSServiceType_DNSKEY,               "DNSKEY" },
+       { kDNSServiceType_DHCID,                "DHCID" },
+       { kDNSServiceType_NSEC3,                "NSEC3" },
+       { kDNSServiceType_NSEC3PARAM,   "NSEC3PARAM" },
+       { kDNSServiceType_HIP,                  "HIP" },
+       { kDNSServiceType_SPF,                  "SPF" },
+       { kDNSServiceType_UINFO,                "UINFO" },
+       { kDNSServiceType_UID,                  "UID" },
+       { kDNSServiceType_GID,                  "GID" },
+       { kDNSServiceType_UNSPEC,               "UNSPEC" },
+       { kDNSServiceType_TKEY,                 "TKEY" },
+       { kDNSServiceType_TSIG,                 "TSIG" },
+       { kDNSServiceType_IXFR,                 "IXFR" },
+       { kDNSServiceType_AXFR,                 "AXFR" },
+       { kDNSServiceType_MAILB,                "MAILB" },
+       { kDNSServiceType_MAILA,                "MAILA" }
+};
+
+static OSStatus        RecordTypeFromArgString( const char *inString, uint16_t *outValue )
+{
+       OSStatus                                                err;
+       int32_t                                                 i32;
+       const RecordType *                              type;
+       const RecordType * const                end = kRecordTypes + countof( kRecordTypes );
+       
+       for( type = kRecordTypes; type < end; ++type )
+       {
+               if( strcasecmp( type->name, inString ) == 0 )
+               {
+                       *outValue = type->value;
+                       return( kNoErr );
+               }
+       }
+       
+       err = StringToInt32( inString, &i32 );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+       
+       *outValue = (uint16_t) i32;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     RecordClassFromArgString
+//===========================================================================================================================
+
+static OSStatus        RecordClassFromArgString( const char *inString, uint16_t *outValue )
+{
+       OSStatus                err;
+       int32_t                 i32;
+       
+       if( strcasecmp( inString, "IN" ) == 0 )
+       {
+               *outValue = kDNSServiceClass_IN;
+               err = kNoErr;
+               goto exit;
+       }
+       
+       err = StringToInt32( inString, &i32 );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+       
+       *outValue = (uint16_t) i32;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     InterfaceIndexToName
+//===========================================================================================================================
+
+static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
+{
+       switch( inIfIndex )
+       {
+               case kDNSServiceInterfaceIndexAny:
+                       strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
+                       break;
+               
+               case kDNSServiceInterfaceIndexLocalOnly:
+                       strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
+                       break;
+               
+               case kDNSServiceInterfaceIndexUnicast:
+                       strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
+                       break;
+               
+               case kDNSServiceInterfaceIndexP2P:
+                       strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
+                       break;
+               
+       #if( defined( kDNSServiceInterfaceIndexBLE ) )
+               case kDNSServiceInterfaceIndexBLE:
+                       strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
+                       break;
+       #endif
+               
+               default:
+               {
+                       const char *            name;
+                       
+                       name = if_indextoname( inIfIndex, inNameBuf );
+                       if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
+                       break;
+               }
+       }
+       
+       return( inNameBuf );
+}
+
+//===========================================================================================================================
+//     RecordTypeToString
+//===========================================================================================================================
+
+static const char *    RecordTypeToString( unsigned int inValue )
+{
+       const RecordType *                              type;
+       const RecordType * const                end = kRecordTypes + countof( kRecordTypes );
+       
+       for( type = kRecordTypes; type < end; ++type )
+       {
+               if( type->value == inValue ) return( type->name );
+       }
+       return( "???" );
+}
+
+//===========================================================================================================================
+//     DNSMessageExtractDomainName
+//===========================================================================================================================
+
+static OSStatus
+       DNSMessageExtractDomainName(
+               const uint8_t *         inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t *         inNamePtr,
+               uint8_t                         inBuf[ kDomainNameLengthMax ],
+               const uint8_t **        outNextPtr )
+{
+       OSStatus                                        err;
+       const uint8_t *                         label;
+       uint8_t                                         labelLen;
+       const uint8_t *                         nextLabel;
+       const uint8_t * const           msgEnd  = inMsgPtr + inMsgLen;
+       uint8_t *                                       dst             = inBuf;
+       const uint8_t * const           dstLim  = inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL;
+       const uint8_t *                         nameEnd = NULL;
+       
+       require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
+       
+       for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+       {
+               if( labelLen <= kDomainLabelLengthMax )
+               {
+                       nextLabel = label + 1 + labelLen;
+                       require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
+                       if( dst )
+                       {
+                               require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr );
+                               memcpy( dst, label, 1 + labelLen );
+                               dst += ( 1 + labelLen );
+                       }
+               }
+               else if( IsCompressionByte( labelLen ) )
+               {
+                       uint16_t                offset;
+                       
+                       require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr );
+                       if( !nameEnd )
+                       {
+                               nameEnd = label + 2;
+                               if( !dst ) break;
+                       }
+                       offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] );
+                       nextLabel = inMsgPtr + offset;
+                       require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
+                       require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr );
+               }
+               else
+               {
+                       dlogassert( "Unhandled label length 0x%02X\n", labelLen );
+                       err = kMalformedErr;
+                       goto exit;
+               }
+       }
+       
+       if( dst ) *dst = 0;
+       if( !nameEnd ) nameEnd = label + 1;
+       
+       if( outNextPtr ) *outNextPtr = nameEnd;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSMessageExtractDomainNameString
+//===========================================================================================================================
+
+static OSStatus
+       DNSMessageExtractDomainNameString(
+               const void *            inMsgPtr,
+               size_t                          inMsgLen,
+               const void *            inNamePtr,
+               char                            inBuf[ kDNSServiceMaxDomainName ],
+               const uint8_t **        outNextPtr )
+{
+       OSStatus                        err;
+       const uint8_t *         nextPtr;
+       uint8_t                         domainName[ kDomainNameLengthMax ];
+       
+       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr );
+       require_noerr( err, exit );
+       
+       err = DomainNameToString( domainName, NULL, inBuf, NULL );
+       require_noerr( err, exit );
+       
+       if( outNextPtr ) *outNextPtr = nextPtr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSMessageExtractQuestion
+//===========================================================================================================================
+
+static OSStatus
+       DNSMessageExtractQuestion(
+               const uint8_t *         inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t *         inPtr,
+               uint8_t                         inNameBuf[ kDomainNameLengthMax ],
+               uint16_t *                      outType,
+               uint16_t *                      outClass,
+               const uint8_t **        outPtr )
+{
+       OSStatus                                                        err;
+       const uint8_t * const                           msgEnd = &inMsgPtr[ inMsgLen ];
+       const uint8_t *                                         ptr;
+       const DNSQuestionFixedFields *          fields;
+       
+       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( (size_t)( msgEnd - ptr ) >= sizeof( DNSQuestionFixedFields ), exit, err = kUnderrunErr );
+       
+       fields = (const DNSQuestionFixedFields *) ptr;
+       if( outType )  *outType  = DNSQuestionFixedFieldsGetType( fields );
+       if( outClass ) *outClass = DNSQuestionFixedFieldsGetClass( fields );
+       if( outPtr )   *outPtr   = (const uint8_t *) &fields[ 1 ];
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSMessageExtractRecord
+//===========================================================================================================================
+
+typedef struct
+{
+       uint8_t         type[ 2 ];
+       uint8_t         class[ 2 ];
+       uint8_t         ttl[ 4 ];
+       uint8_t         rdLength[ 2 ];
+       uint8_t         rdata[ 1 ];
+       
+}      DNSRecordFields;
+
+check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
+
+static OSStatus
+       DNSMessageExtractRecord(
+               const uint8_t *         inMsgPtr,
+               size_t                          inMsgLen,
+               const uint8_t *         inPtr,
+               uint8_t                         inNameBuf[ kDomainNameLengthMax ],
+               uint16_t *                      outType,
+               uint16_t *                      outClass,
+               uint32_t *                      outTTL,
+               const uint8_t **        outRDataPtr,
+               size_t *                        outRDataLen,
+               const uint8_t **        outPtr )
+{
+       OSStatus                                        err;
+       const uint8_t * const           msgEnd = inMsgPtr + inMsgLen;
+       const uint8_t *                         ptr;
+       const DNSRecordFields *         record;
+       size_t                                          rdLength;
+       
+       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
+       require_noerr_quiet( err, exit );
+       require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr );
+       
+       record = (DNSRecordFields *) ptr;
+       rdLength = ReadBig16( record->rdLength );
+       require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
+       
+       if( outType )           *outType                = ReadBig16( record->type );
+       if( outClass )          *outClass               = ReadBig16( record->class );
+       if( outTTL )            *outTTL                 = ReadBig32( record->ttl );
+       if( outRDataPtr )       *outRDataPtr    = record->rdata;
+       if( outRDataLen )       *outRDataLen    = rdLength;
+       if( outPtr )            *outPtr                 = record->rdata + rdLength;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSMessageGetAnswerSection
+//===========================================================================================================================
+
+static OSStatus        DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
+{
+       OSStatus                                err;
+       unsigned int                    questionCount, i;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+       
+       hdr = (DNSHeader *) inMsgPtr;
+       questionCount = DNSHeaderGetQuestionCount( hdr );
+       
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       for( i = 0; i < questionCount; ++i )
+       {
+               err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, NULL, NULL, NULL, &ptr );
+               require_noerr( err, exit );
+       }
+       
+       if( outPtr ) *outPtr = ptr;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSRecordDataToString
+//===========================================================================================================================
+
+static OSStatus
+       DNSRecordDataToString(
+               const void *    inRDataPtr,
+               size_t                  inRDataLen,
+               unsigned int    inRDataType,
+               const void *    inMsgPtr,
+               size_t                  inMsgLen,
+               char **                 outString )
+{
+       OSStatus                                        err;
+       const uint8_t * const           rdataPtr = (uint8_t *) inRDataPtr;
+       const uint8_t * const           rdataEnd = rdataPtr + inRDataLen;
+       char *                                          rdataStr;
+       const uint8_t *                         ptr;
+       int                                                     n;
+       char                                            domainNameStr[ kDNSServiceMaxDomainName ];
+       
+       rdataStr = NULL;
+       if( inRDataType == kDNSServiceType_A )
+       {
+               require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
+               
+               ASPrintF( &rdataStr, "%.4a", rdataPtr );
+               require_action( rdataStr, exit, err = kNoMemoryErr );
+       }
+       else if( inRDataType == kDNSServiceType_AAAA )
+       {
+               require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+               
+               ASPrintF( &rdataStr, "%.16a", rdataPtr );
+               require_action( rdataStr, exit, err = kNoMemoryErr );
+       }
+       else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
+                       ( inRDataType == kDNSServiceType_NS ) )
+       {
+               if( inMsgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               
+               rdataStr = strdup( domainNameStr );
+               require_action( rdataStr, exit, err = kNoMemoryErr );
+       }
+       else if( inRDataType == kDNSServiceType_SRV )
+       {
+               const SRVRecordDataFixedFields *                fields;
+               const uint8_t *                                                 target;
+               unsigned int                                                    priority, weight, port;
+               
+               require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr );
+               
+               fields = (const SRVRecordDataFixedFields *) rdataPtr;
+               SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port );
+               target = (const uint8_t *) &fields[ 1 ];
+               
+               if( inMsgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               
+               ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
+               require_action( rdataStr, exit, err = kNoMemoryErr );
+       }
+       else if( inRDataType == kDNSServiceType_TXT )
+       {
+               require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
+               
+               if( inRDataLen == 1 )
+               {
+                       ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
+                       require_action( rdataStr, exit, err = kNoMemoryErr );
+               }
+               else
+               {
+                       ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
+                       require_action( rdataStr, exit, err = kNoMemoryErr );
+               }
+       }
+       else if( inRDataType == kDNSServiceType_SOA )
+       {
+               uint32_t                serial, refresh, retry, expire, minimum;
+               
+               if( inMsgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+                       require_noerr( err, exit );
+                       
+                       require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+                       
+                       rdataStr = strdup( domainNameStr );
+                       require_action( rdataStr, exit, err = kNoMemoryErr );
+                       
+                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
+                       require_noerr( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+                       require_noerr( err, exit );
+                       
+                       rdataStr = strdup( domainNameStr );
+                       require_action( rdataStr, exit, err = kNoMemoryErr );
+                       
+                       err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+                       require_noerr( err, exit );
+               }
+               
+               require_action_quiet( ( rdataEnd - ptr ) == sizeof( SOARecordDataFixedFields ), exit, err = kMalformedErr );
+               
+               SOARecordDataFixedFieldsGet( (const SOARecordDataFixedFields *) ptr, &serial, &refresh, &retry, &expire, &minimum );
+               
+               n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
+               require_action( n > 0, exit, err = kUnknownErr );
+       }
+       else if( inRDataType == kDNSServiceType_NSEC )
+       {
+               unsigned int            windowBlock, bitmapLen, i, recordType;
+               const uint8_t *         bitmapPtr;
+               
+               if( inMsgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+                       require_noerr( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+                       require_noerr( err, exit );
+               }
+               
+               require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+               
+               rdataStr = strdup( domainNameStr );
+               require_action( rdataStr, exit, err = kNoMemoryErr );
+               
+               for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
+               {
+                       require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
+                       
+                       windowBlock     =  ptr[ 0 ];
+                       bitmapLen       =  ptr[ 1 ];
+                       bitmapPtr       = &ptr[ 2 ];
+                       
+                       require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
+                       require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
+                       
+                       for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
+                       {
+                               if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
+                               {
+                                       recordType = ( windowBlock * 256 ) + i;
+                                       n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
+                                       require_action( n > 0, exit, err = kUnknownErr );
+                               }
+                       }
+               }
+       }
+       else if( inRDataType == kDNSServiceType_MX )
+       {
+               uint16_t                        preference;
+               const uint8_t *         exchange;
+               
+               require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
+               
+               preference      = ReadBig16( rdataPtr );
+               exchange        = &rdataPtr[ 2 ];
+               
+               if( inMsgPtr )
+               {
+                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               else
+               {
+                       err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
+                       require_noerr( err, exit );
+               }
+               
+               n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
+               require_action( n > 0, exit, err = kUnknownErr );
+       }
+       else
+       {
+               err = kNotHandledErr;
+               goto exit;
+       }
+       
+       check( rdataStr );
+       *outString = rdataStr;
+       rdataStr = NULL;
+       err = kNoErr;
+       
+exit:
+       FreeNullSafe( rdataStr );
+       return( err );
+}
+
+//===========================================================================================================================
+//     DomainNameAppendString
+//===========================================================================================================================
+
+static OSStatus
+       DomainNameAppendString(
+               uint8_t                 inDomainName[ kDomainNameLengthMax ],
+               const char *    inString,
+               uint8_t **              outEndPtr )
+{
+       OSStatus                                        err;
+       const char *                            src;
+       uint8_t *                                       root;
+       const uint8_t * const           nameLim = inDomainName + kDomainNameLengthMax;
+       
+       for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
+       require_action_quiet( root < nameLim, exit, err = kMalformedErr );
+       
+       // If the string is a single dot, denoting the root domain, then there are no non-empty labels.
+       
+       src = inString;
+       if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src;
+       while( *src )
+       {
+               uint8_t * const                         label           = root;
+               const uint8_t * const           labelLim        = Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
+               uint8_t *                                       dst;
+               int                                                     c;
+               size_t                                          labelLen;
+               
+               dst = &label[ 1 ];
+               while( *src && ( ( c = *src++ ) != '.' ) )
+               {
+                       if( c == '\\' )
+                       {
+                               require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+                               c = *src++;
+                               if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
+                               {
+                                       const int               decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
+                                       
+                                       if( decimal <= 255 )
+                                       {
+                                               c = decimal;
+                                               src += 2;
+                                       }
+                               }
+                       }
+                       require_action_quiet( dst < labelLim, exit, err = kOverrunErr );
+                       *dst++ = (uint8_t) c;
+               }
+               
+               labelLen = (size_t)( dst - &label[ 1 ] );
+               require_action_quiet( labelLen > 0, exit, err = kMalformedErr );
+               
+               label[ 0 ] = (uint8_t) labelLen;
+               root = dst;
+               *root = 0;
+       }
+       
+       if( outEndPtr ) *outEndPtr = root + 1;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DomainNameEqual
+//===========================================================================================================================
+
+static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
+{
+       const uint8_t *         p1 = inName1;
+       const uint8_t *         p2 = inName2;
+       unsigned int            len;
+       
+       for( ;; )
+       {
+               if( ( len = *p1++ ) != *p2++ ) return( false );
+               if( len == 0 ) break;
+               for( ; len > 0; ++p1, ++p2, --len )
+               {
+                       if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false );
+               }
+       }
+       return( true );
+}
+
+//===========================================================================================================================
+//     DomainNameLength
+//===========================================================================================================================
+
+static size_t  DomainNameLength( const uint8_t * const inName )
+{
+       const uint8_t *         ptr;
+       
+       for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {}
+       return( (size_t)( ptr - inName ) + 1 );
+}
+
+//===========================================================================================================================
+//     DomainNameDupEx
+//===========================================================================================================================
+
+static OSStatus        DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen )
+{
+       OSStatus                        err;
+       uint8_t *                       namePtr;
+       const size_t            nameLen = DomainNameLength( inName );
+       
+       if( inLower )
+       {
+               const uint8_t *         src;
+               uint8_t *                       dst;
+               unsigned int            len;
+               
+               namePtr = (uint8_t *) malloc( nameLen );
+               require_action( namePtr, exit, err = kNoMemoryErr );
+               
+               src = inName;
+               dst = namePtr;
+               while( ( len = *src ) != 0 )
+               {
+                       *dst++ = *src++;
+                       while( len-- )
+                       {
+                               *dst++ = (uint8_t) tolower_safe( *src );
+                               ++src;
+                       }
+               }
+               *dst = 0;
+       }
+       else
+       {
+               namePtr = (uint8_t *) memdup( inName, nameLen );
+               require_action( namePtr, exit, err = kNoMemoryErr );
+       }
+       
+       *outNamePtr = namePtr;
+       if( outNameLen ) *outNameLen = nameLen;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DomainNameFromString
+//===========================================================================================================================
+
+static OSStatus
+       DomainNameFromString(
+               uint8_t                 inDomainName[ kDomainNameLengthMax ],
+               const char *    inString,
+               uint8_t **              outEndPtr )
+{
+       inDomainName[ 0 ] = 0;
+       return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+}
+
+//===========================================================================================================================
+//     DomainNameToString
+//===========================================================================================================================
+
+static OSStatus
+       DomainNameToString(
+               const uint8_t *         inDomainName,
+               const uint8_t *         inEnd,
+               char                            inBuf[ kDNSServiceMaxDomainName ],
+               const uint8_t **        outNextPtr )
+{
+       OSStatus                        err;
+       const uint8_t *         label;
+       uint8_t                         labelLen;
+       const uint8_t *         nextLabel;
+       char *                          dst;
+       const uint8_t *         src;
+       
+       require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr );
+       
+       // Convert each label up until the root label, i.e., the zero-length label.
+       
+       dst = inBuf;
+       for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+       {
+               require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr );
+               
+               nextLabel = &label[ 1 ] + labelLen;
+               require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr );
+               require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr );
+               
+               for( src = &label[ 1 ]; src < nextLabel; ++src )
+               {
+                       if( isprint_safe( *src ) )
+                       {
+                               if( ( *src == '.' ) || ( *src == '\\' ) ||  ( *src == ' ' ) ) *dst++ = '\\';
+                               *dst++ = (char) *src;
+                       }
+                       else
+                       {
+                               *dst++ = '\\';
+                               *dst++ = '0' + (   *src / 100 );
+                               *dst++ = '0' + ( ( *src /  10 ) % 10 );
+                               *dst++ = '0' + (   *src         % 10 );
+                       }
+               }
+               *dst++ = '.';
+       }
+       
+       // At this point, label points to the root label.
+       // If the root label was the only label, then write a dot for it.
+       
+       if( label == inDomainName ) *dst++ = '.';
+       *dst = '\0';
+       if( outNextPtr ) *outNextPtr = label + 1;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSMessageToText
+//===========================================================================================================================
+
+#define DNSFlagsOpCodeToString( X ) (                                  \
+       ( (X) == kDNSOpCode_Query )                     ? "Query"       :       \
+       ( (X) == kDNSOpCode_InverseQuery )      ? "IQuery"      :       \
+       ( (X) == kDNSOpCode_Status )            ? "Status"      :       \
+       ( (X) == kDNSOpCode_Notify )            ? "Notify"      :       \
+       ( (X) == kDNSOpCode_Update )            ? "Update"      :       \
+                                                                                 "Unassigned" )
+
+#define DNSFlagsRCodeToString( X ) (                                           \
+       ( (X) == kDNSRCode_NoError )            ? "NoError"             :       \
+       ( (X) == kDNSRCode_FormatError )        ? "FormErr"             :       \
+       ( (X) == kDNSRCode_ServerFailure )      ? "ServFail"    :       \
+       ( (X) == kDNSRCode_NXDomain )           ? "NXDomain"    :       \
+       ( (X) == kDNSRCode_NotImplemented )     ? "NotImp"              :       \
+       ( (X) == kDNSRCode_Refused )            ? "Refused"             :       \
+                                                                                 "???" )
+
+static OSStatus
+       DNSMessageToText(
+               const uint8_t * inMsgPtr,
+               size_t                  inMsgLen,
+               const Boolean   inMDNS,
+               const Boolean   inPrintRaw,
+               char **                 outText )
+{
+       OSStatus                                err;
+       DataBuffer                              dataBuf;
+       size_t                                  len;
+       const DNSHeader *               hdr;
+       const uint8_t *                 ptr;
+       unsigned int                    id, flags, opcode, rcode;
+       unsigned int                    questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
+       uint8_t                                 name[ kDomainNameLengthMax ];
+       char                                    nameStr[ kDNSServiceMaxDomainName ];
+       
+       DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
+       #define _Append( ... )          do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
+       
+       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+       
+       hdr                             = (DNSHeader *) inMsgPtr;
+       id                              = DNSHeaderGetID( hdr );
+       flags                   = DNSHeaderGetFlags( hdr );
+       questionCount   = DNSHeaderGetQuestionCount( hdr );
+       answerCount             = DNSHeaderGetAnswerCount( hdr );
+       authorityCount  = DNSHeaderGetAuthorityCount( hdr );
+       additionalCount = DNSHeaderGetAdditionalCount( hdr );
+       opcode                  = DNSFlagsGetOpCode( flags );
+       rcode                   = DNSFlagsGetRCode( flags );
+       
+       _Append( "ID:               0x%04X (%u)\n", id, id );
+       _Append( "Flags:            0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
+               flags,
+               ( flags & kDNSHeaderFlag_Response )                             ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
+               ( flags & kDNSHeaderFlag_AuthAnswer )                   ? ' ' : '!',
+               ( flags & kDNSHeaderFlag_Truncation )                   ? ' ' : '!',
+               ( flags & kDNSHeaderFlag_RecursionDesired )             ? ' ' : '!',
+               ( flags & kDNSHeaderFlag_RecursionAvailable )   ? ' ' : '!',
+               !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData )               ? " AD" : "!AD",
+               !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled )    ? " CD" : "!CD",
+               DNSFlagsRCodeToString( rcode ) );
+       _Append( "Question count:   %u\n", questionCount );
+       _Append( "Answer count:     %u\n", answerCount );
+       _Append( "Authority count:  %u\n", authorityCount );
+       _Append( "Additional count: %u\n", additionalCount );
+       
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       for( i = 0; i < questionCount; ++i )
        {
-               err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+               uint16_t                qtype, qclass;
+               Boolean                 isQU;
+               
+               err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, name, &qtype, &qclass, &ptr );
                require_noerr( err, exit );
                
-               err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+               err = DomainNameToString( name, NULL, nameStr, NULL );
                require_noerr( err, exit );
                
-               err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
-               err = map_socket_noerr_errno( sockV6, err );
+               isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
+               if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
+               
+               if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
+               
+               _Append( "%-30s %2s %?2s%?2u %-5s\n",
+                       nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
+                       ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
+       }
+       
+       totalRRCount = answerCount + authorityCount + additionalCount;
+       for( i = 0; i < totalRRCount; ++i )
+       {
+               uint16_t                        type;
+               uint16_t                        class;
+               uint32_t                        ttl;
+               const uint8_t *         rdataPtr;
+               size_t                          rdataLen;
+               char *                          rdataStr;
+               Boolean                         cacheFlush;
+               
+               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
+               require_noerr( err, exit );
+               
+               err = DomainNameToString( name, NULL, nameStr, NULL );
                require_noerr( err, exit );
+               
+               cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
+               if( inMDNS ) class &= ~kRRClassCacheFlushBit;
+               
+               rdataStr = NULL;
+               if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
+               if( !rdataStr )
+               {
+                       ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX );
+                       require_action( rdataStr, exit, err = kNoMemoryErr );
+               }
+               
+               if(      answerCount     && ( i ==   0                              ) ) _Append( "\nANSWER SECTION\n" );
+               else if( authorityCount  && ( i ==   answerCount                    ) ) _Append( "\nAUTHORITY SECTION\n" );
+               else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
+               
+               _Append( "%-42s %6u %2s %?2s%?2u %-5s %s\n",
+                       nameStr, ttl, cacheFlush ? "CF" : "",
+                       ( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
+                       RecordTypeToString( type ), rdataStr );
+               free( rdataStr );
        }
+       _Append( "\n" );
+       
+       err = DataBuffer_Append( &dataBuf, "", 1 );
+       require_noerr( err, exit );
+       
+       err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
+       require_noerr( err, exit );
+       
+exit:
+       DataBuffer_Free( &dataBuf );
+       return( err );
+}
+
+//===========================================================================================================================
+//     WriteDNSQueryMessage
+//===========================================================================================================================
+
+static OSStatus
+       WriteDNSQueryMessage(
+               uint8_t                 inMsg[ kDNSQueryMessageMaxLen ],
+               uint16_t                inMsgID,
+               uint16_t                inFlags,
+               const char *    inQName,
+               uint16_t                inQType,
+               uint16_t                inQClass,
+               size_t *                outMsgLen )
+{
+       OSStatus                                err;
+       DNSHeader * const               hdr = (DNSHeader *) inMsg;
+       uint8_t *                               ptr;
+       size_t                                  msgLen;
+       
+       memset( hdr, 0, sizeof( *hdr ) );
+       DNSHeaderSetID( hdr, inMsgID );
+       DNSHeaderSetFlags( hdr, inFlags );
+       DNSHeaderSetQuestionCount( hdr, 1 );
+       
+       ptr = (uint8_t *)( hdr + 1 );
+       err = DomainNameFromString( ptr, inQName, &ptr );
+       require_noerr_quiet( err, exit );
+       
+       DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
+       ptr += 4;
+       
+       msgLen = (size_t)( ptr - inMsg );
+       check( msgLen <= kDNSQueryMessageMaxLen );
+       
+       if( outMsgLen ) *outMsgLen = msgLen;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DispatchSignalSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+       DispatchSignalSourceCreate(
+               int                                     inSignal,
+               DispatchHandler         inEventHandler,
+               void *                          inContext,
+               dispatch_source_t *     outSource )
+{
+       OSStatus                                err;
+       dispatch_source_t               source;
+       
+       source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
+       require_action( source, exit, err = kUnknownErr );
+       
+       dispatch_set_context( source, inContext );
+       dispatch_source_set_event_handler_f( source, inEventHandler );
+       
+       *outSource = source;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DispatchSocketSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+       DispatchSocketSourceCreate(
+               SocketRef                               inSock,
+               dispatch_source_type_t  inType,
+               dispatch_queue_t                inQueue,
+               DispatchHandler                 inEventHandler,
+               DispatchHandler                 inCancelHandler,
+               void *                                  inContext,
+               dispatch_source_t *             outSource )
+{
+       OSStatus                                err;
+       dispatch_source_t               source;
+       
+       source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
+       require_action( source, exit, err = kUnknownErr );
+       
+       dispatch_set_context( source, inContext );
+       dispatch_source_set_event_handler_f( source, inEventHandler );
+       dispatch_source_set_cancel_handler_f( source, inCancelHandler );
+       
+       *outSource = source;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DispatchTimerCreate
+//===========================================================================================================================
+
+static OSStatus
+       DispatchTimerCreate(
+               dispatch_time_t         inStart,
+               uint64_t                        inIntervalNs,
+               uint64_t                        inLeewayNs,
+               dispatch_queue_t        inQueue,
+               DispatchHandler         inEventHandler,
+               DispatchHandler         inCancelHandler,
+               void *                          inContext,
+               dispatch_source_t *     outTimer )
+{
+       OSStatus                                err;
+       dispatch_source_t               timer;
+       
+       timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
+       require_action( timer, exit, err = kUnknownErr );
+       
+       dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
+       dispatch_set_context( timer, inContext );
+       dispatch_source_set_event_handler_f( timer, inEventHandler );
+       dispatch_source_set_cancel_handler_f( timer, inCancelHandler );
+       
+       *outTimer = timer;
+       err = kNoErr;
+       
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     DispatchProcessMonitorCreate
+//===========================================================================================================================
+
+static OSStatus
+       DispatchProcessMonitorCreate(
+               pid_t                           inPID,
+               unsigned long           inFlags,
+               dispatch_queue_t        inQueue,
+               DispatchHandler         inEventHandler,
+               DispatchHandler         inCancelHandler,
+               void *                          inContext,
+               dispatch_source_t *     outMonitor )
+{
+       OSStatus                                err;
+       dispatch_source_t               monitor;
        
-       // Print prologue.
+       monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
+               inQueue ? inQueue : dispatch_get_main_queue() );
+       require_action( monitor, exit, err = kUnknownErr );
        
-       SSDPDiscoverPrintPrologue( context );
+       dispatch_set_context( monitor, inContext );
+       dispatch_source_set_event_handler_f( monitor, inEventHandler );
+       dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
        
-       // Send mDNS query message.
+       *outMonitor = monitor;
+       err = kNoErr;
        
-       sendCount = 0;
-       if( IsValidSocket( sockV4 ) )
-       {
-               struct sockaddr_in              mcastAddr4;
-               
-               memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
-               SIN_LEN_SET( &mcastAddr4 );
-               mcastAddr4.sin_family           = AF_INET;
-               mcastAddr4.sin_port                     = htons( kSSDPPort );
-               mcastAddr4.sin_addr.s_addr      = htonl( 0xEFFFFFFA );  // 239.255.255.250
-               
-               err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
-               require_noerr( err, exit );
-               
-               n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
-                       (socklen_t) sizeof( mcastAddr4 ) );
-               err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
-               if( err )
-               {
-                       FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
-                       ForgetSocket( &sockV4 );
-               }
-               else
-               {
-                       if( gSSDPDiscover_Verbose )
-                       {
-                               gettimeofday( &now, NULL );
-                               FPrintF( stdout, "---\n" );
-                               FPrintF( stdout, "Send time:    %{du:time}\n",  &now );
-                               FPrintF( stdout, "Source Port:  %d\n",                  SocketToPortNumber( sockV4 ) );
-                               FPrintF( stdout, "Destination:  %##a\n",                &mcastAddr4 );
-                               FPrintF( stdout, "Message size: %zu\n",                 context->header.len );
-                               FPrintF( stdout, "HTTP header:\n%1{text}",              context->header.buf, context->header.len );
-                       }
-                       ++sendCount;
-               }
-       }
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     ServiceTypeDescription
+//===========================================================================================================================
+
+typedef struct
+{
+       const char *            name;                   // Name of the service type in two-label "_service._proto" format.
+       const char *            description;    // Description of the service type.
        
-       if( IsValidSocket( sockV6 ) )
+}      ServiceType;
+
+// A Non-comprehensive table of DNS-SD service types
+
+static const ServiceType               kServiceTypes[] =
+{
+       { "_acp-sync._tcp",                     "AirPort Base Station Sync" },
+       { "_adisk._tcp",                        "Automatic Disk Discovery" },
+       { "_afpovertcp._tcp",           "Apple File Sharing" },
+       { "_airdrop._tcp",                      "AirDrop" },
+       { "_airplay._tcp",                      "AirPlay" },
+       { "_airport._tcp",                      "AirPort Base Station" },
+       { "_daap._tcp",                         "Digital Audio Access Protocol (iTunes)" },
+       { "_eppc._tcp",                         "Remote AppleEvents" },
+       { "_ftp._tcp",                          "File Transfer Protocol" },
+       { "_home-sharing._tcp",         "Home Sharing" },
+       { "_homekit._tcp",                      "HomeKit" },
+       { "_http._tcp",                         "World Wide Web HTML-over-HTTP" },
+       { "_https._tcp",                        "HTTP over SSL/TLS" },
+       { "_ipp._tcp",                          "Internet Printing Protocol" },
+       { "_ldap._tcp",                         "Lightweight Directory Access Protocol" },
+       { "_mediaremotetv._tcp",        "Media Remote" },
+       { "_net-assistant._tcp",        "Apple Remote Desktop" },
+       { "_od-master._tcp",            "OpenDirectory Master" },
+       { "_nfs._tcp",                          "Network File System" },
+       { "_presence._tcp",                     "Peer-to-peer messaging / Link-Local Messaging" },
+       { "_pdl-datastream._tcp",       "Printer Page Description Language Data Stream" },
+       { "_raop._tcp",                         "Remote Audio Output Protocol" },
+       { "_rfb._tcp",                          "Remote Frame Buffer" },
+       { "_scanner._tcp",                      "Bonjour Scanning" },
+       { "_smb._tcp",                          "Server Message Block over TCP/IP" },
+       { "_sftp-ssh._tcp",                     "Secure File Transfer Protocol over SSH" },
+       { "_sleep-proxy._udp",          "Sleep Proxy Server" },
+       { "_ssh._tcp",                          "SSH Remote Login Protocol" },
+       { "_teleport._tcp",                     "teleport" },
+       { "_tftp._tcp",                         "Trivial File Transfer Protocol" },
+       { "_workstation._tcp",          "Workgroup Manager" },
+       { "_webdav._tcp",                       "World Wide Web Distributed Authoring and Versioning (WebDAV)" },
+       { "_webdavs._tcp",                      "WebDAV over SSL/TLS" }
+};
+
+static const char *    ServiceTypeDescription( const char *inName )
+{
+       const ServiceType *                             serviceType;
+       const ServiceType * const               end = kServiceTypes + countof( kServiceTypes );
+       
+       for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
        {
-               struct sockaddr_in6             mcastAddr6;
-               
-               memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
-               SIN6_LEN_SET( &mcastAddr6 );
-               mcastAddr6.sin6_family                          = AF_INET6;
-               mcastAddr6.sin6_port                            = htons( kSSDPPort );
-               mcastAddr6.sin6_addr.s6_addr[  0 ]      = 0xFF; // SSDP IPv6 link-local multicast address FF02::C
-               mcastAddr6.sin6_addr.s6_addr[  1 ]      = 0x02;
-               mcastAddr6.sin6_addr.s6_addr[ 15 ]      = 0x0C;
-               
-               err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
-               require_noerr( err, exit );
-               
-               n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
-                       (socklen_t) sizeof( mcastAddr6 ) );
-               err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
-               if( err )
-               {
-                       FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
-                       ForgetSocket( &sockV6 );
-               }
-               else
+               if( ( stricmp_prefix( inName, serviceType->name ) == 0 ) )
                {
-                       if( gSSDPDiscover_Verbose )
+                       const size_t            len = strlen( serviceType->name );
+                       
+                       if( ( inName[ len ] == '\0' ) || ( strcmp( &inName[ len ], "." ) == 0 ) )
                        {
-                               gettimeofday( &now, NULL );
-                               FPrintF( stdout, "---\n" );
-                               FPrintF( stdout, "Send time:    %{du:time}\n",  &now );
-                               FPrintF( stdout, "Source Port:  %d\n",                  SocketToPortNumber( sockV6 ) );
-                               FPrintF( stdout, "Destination:  %##a\n",                &mcastAddr6 );
-                               FPrintF( stdout, "Message size: %zu\n",                 context->header.len );
-                               FPrintF( stdout, "HTTP header:\n%1{text}",              context->header.buf, context->header.len );
+                               return( serviceType->description );
                        }
-                       ++sendCount;
                }
        }
-       require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+       return( NULL );
+}
+
+//===========================================================================================================================
+//     SocketContextCreate
+//===========================================================================================================================
+
+static OSStatus        SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+{
+       OSStatus                        err;
+       SocketContext *         context;
        
-       // If there's no wait period after the send, then exit.
+       context = (SocketContext *) calloc( 1, sizeof( *context ) );
+       require_action( context, exit, err = kNoMemoryErr );
        
-       if( context->receiveSecs == 0 ) goto exit;
+       context->refCount               = 1;
+       context->sock                   = inSock;
+       context->userContext    = inUserContext;
        
-       // Create dispatch read sources for socket(s).
+       *outContext = context;
+       err = kNoErr;
        
-       if( IsValidSocket( sockV4 ) )
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     SocketContextRetain
+//===========================================================================================================================
+
+static SocketContext * SocketContextRetain( SocketContext *inContext )
+{
+       ++inContext->refCount;
+       return( inContext );
+}
+
+//===========================================================================================================================
+//     SocketContextRelease
+//===========================================================================================================================
+
+static void    SocketContextRelease( SocketContext *inContext )
+{
+       if( --inContext->refCount == 0 )
        {
-               SocketContext *         sockCtx;
-               
-               err = SocketContextCreate( sockV4, context, &sockCtx );
-               require_noerr( err, exit );
-               sockV4 = kInvalidSocketRef;
-               
-               err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
-                       &context->readSourceV4 );
-               if( err ) ForgetSocketContext( &sockCtx );
-               require_noerr( err, exit );
-               
-               dispatch_resume( context->readSourceV4 );
+               ForgetSocket( &inContext->sock );
+               free( inContext );
        }
+}
+
+//===========================================================================================================================
+//     SocketContextCancelHandler
+//===========================================================================================================================
+
+static void    SocketContextCancelHandler( void *inContext )
+{
+       SocketContextRelease( (SocketContext *) inContext );
+}
+
+//===========================================================================================================================
+//     StringToInt32
+//===========================================================================================================================
+
+static OSStatus        StringToInt32( const char *inString, int32_t *outValue )
+{
+       OSStatus                err;
+       long                    value;
+       char *                  endPtr;
        
-       if( IsValidSocket( sockV6 ) )
-       {
-               SocketContext *         sockCtx;
-               
-               err = SocketContextCreate( sockV6, context, &sockCtx );
-               require_noerr( err, exit );
-               sockV6 = kInvalidSocketRef;
-               
-               err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
-                       &context->readSourceV6 );
-               if( err ) ForgetSocketContext( &sockCtx );
-               require_noerr( err, exit );
-               
-               dispatch_resume( context->readSourceV6 );
-       }
+       value = strtol( inString, &endPtr, 0 );
+       require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+       require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr );
        
-       if( context->receiveSecs > 0 )
-       {
-               dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
-                       Exit );
-       }
-       dispatch_main();
+       *outValue = (int32_t) value;
+       err = kNoErr;
        
-exit:
-       ForgetSocket( &sockV4 );
-       ForgetSocket( &sockV6 );
-       dispatch_source_forget( &signalSource );
-       if( err ) exit( 1 );
+exit:
+       return( err );
 }
 
-static int     SocketToPortNumber( SocketRef inSock )
+//===========================================================================================================================
+//     StringToUInt32
+//===========================================================================================================================
+
+static OSStatus        StringToUInt32( const char *inString, uint32_t *outValue )
 {
        OSStatus                err;
-       sockaddr_ip             sip;
-       socklen_t               len;
+       uint32_t                value;
+       char *                  endPtr;
        
-       len = (socklen_t) sizeof( sip );
-       err = getsockname( inSock, &sip.sa, &len );
-       err = map_socket_noerr_errno( inSock, err );
-       check_noerr( err );
-       return( err ? -1 : SockAddrGetPort( &sip ) );
+       value = (uint32_t) strtol( inString, &endPtr, 0 );
+       require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+       
+       *outValue = value;
+       err = kNoErr;
+       
+exit:
+       return( err );
 }
 
-static OSStatus        WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+//     StringToPID
+//===========================================================================================================================
+
+static OSStatus        StringToPID( const char *inString, pid_t *outPID )
 {
        OSStatus                err;
+       long long               value;
+       char *                  endPtr;
        
-       err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
-       require_noerr( err, exit );
+       set_errno_compat( 0 );
+       value = strtoll( inString, &endPtr, 0 );
+       err = errno_compat();
+       require_noerr_quiet( err, exit );
+       require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kMalformedErr );
+       require_action_quiet( value == (pid_t) value, exit, err = kRangeErr );
        
-       err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
-       require_noerr( err, exit );
+       *outPID = (pid_t) value;
+       err = kNoErr;
        
-       err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
-       require_noerr( err, exit );
+exit:
+       return( err );
+}
+#endif
+
+//===========================================================================================================================
+//     StringToARecordData
+//===========================================================================================================================
+
+static OSStatus        StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+       OSStatus                        err;
+       uint32_t *                      addrPtr;
+       const size_t            addrLen = sizeof( *addrPtr );
+       const char *            end;
        
-       err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
-       require_noerr( err, exit );
+       addrPtr = (uint32_t *) malloc( addrLen );
+       require_action( addrPtr, exit, err = kNoMemoryErr );
        
-       err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
-       require_noerr( err, exit );
+       err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
+               NULL, NULL, NULL, &end );
+       if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+       require_noerr_quiet( err, exit );
        
-       err = HTTPHeader_Commit( inHeader );
-       require_noerr( err, exit );
+       *addrPtr = HostToBig32( *addrPtr );
+       
+       *outPtr = (uint8_t *) addrPtr;
+       addrPtr = NULL;
+       *outLen = addrLen;
        
 exit:
+       FreeNullSafe( addrPtr );
        return( err );
 }
 
 //===========================================================================================================================
-//     SSDPDiscoverPrintPrologue
+//     StringToAAAARecordData
 //===========================================================================================================================
 
-static void    SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+static OSStatus        StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
 {
-       const int                               receiveSecs = inContext->receiveSecs;
-       const char *                    ifName;
-       char                                    ifNameBuf[ IF_NAMESIZE + 1 ];
-       NetTransportType                ifType;
+       OSStatus                        err;
+       uint8_t *                       addrPtr;
+       const size_t            addrLen = 16;
+       const char *            end;
        
-       ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+       addrPtr = (uint8_t *) malloc( addrLen );
+       require_action( addrPtr, exit, err = kNoMemoryErr );
        
-       ifType = kNetTransportType_Undefined;
-       if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+       err = StringToIPv6Address( inString,
+               kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+               addrPtr, NULL, NULL, NULL, &end );
+       if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+       require_noerr_quiet( err, exit );
        
-       FPrintF( stdout, "Interface:        %s/%d/%s\n",
-               ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
-       FPrintF( stdout, "IP protocols:     %?s%?s%?s\n",
-               inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
-       FPrintF( stdout, "Receive duration: " );
-       if( receiveSecs >= 0 )  FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
-       else                                    FPrintF( stdout, "∞\n" );
-       FPrintF( stdout, "Start time:       %{du:time}\n", NULL );
+       *outPtr = addrPtr;
+       addrPtr = NULL;
+       *outLen = addrLen;
+       
+exit:
+       FreeNullSafe( addrPtr );
+       return( err );
 }
 
 //===========================================================================================================================
-//     SSDPDiscoverReadHandler
+//     StringToDomainName
 //===========================================================================================================================
 
-static void    SSDPDiscoverReadHandler( void *inContext )
+static OSStatus        StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
 {
-       OSStatus                                                err;
-       struct timeval                                  now;
-       SocketContext * const                   sockCtx = (SocketContext *) inContext;
-       SSDPDiscoverContext * const             context = (SSDPDiscoverContext *) sockCtx->userContext;
-       HTTPHeader * const                              header  = &context->header;
-       sockaddr_ip                                             fromAddr;
-       size_t                                                  msgLen;
+       OSStatus                err;
+       uint8_t *               namePtr;
+       size_t                  nameLen;
+       uint8_t *               end;
+       uint8_t                 nameBuf[ kDomainNameLengthMax ];
        
-       gettimeofday( &now, NULL );
+       err = DomainNameFromString( nameBuf, inString, &end );
+       require_noerr_quiet( err, exit );
        
-       err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
-               NULL, NULL, NULL, NULL );
-       require_noerr( err, exit );
+       nameLen = (size_t)( end - nameBuf );
+       namePtr = memdup( nameBuf, nameLen );
+       require_action( namePtr, exit, err = kNoMemoryErr );
        
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "Receive time: %{du:time}\n",  &now );
-       FPrintF( stdout, "Source:       %##a\n",                &fromAddr );
-       FPrintF( stdout, "Message size: %zu\n",                 msgLen );
-       header->len = msgLen;
-       if( HTTPHeader_Validate( header ) )
-       {
-               FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
-               if( header->extraDataLen > 0 )
-               {
-                       FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
-               }
-       }
-       else
-       {
-               FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
-               goto exit;
-       }
+       *outPtr = namePtr;
+       namePtr = NULL;
+       if( outLen ) *outLen = nameLen;
        
 exit:
-       if( err ) exit( 1 );
+       return( err );
 }
 
+#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//     HTTPHeader_Validate
-//
-//     Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
-//     This assumes the "buf" and "len" fields are set. The other fields are set by this function.
-//
-//     Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+//     GetDefaultDNSServer
 //===========================================================================================================================
 
-Boolean        HTTPHeader_Validate( HTTPHeader *inHeader )
+static OSStatus        GetDefaultDNSServer( sockaddr_ip *outAddr )
 {
-       const char *            src;
-       const char *            end;
+       OSStatus                                err;
+       dns_config_t *                  config;
+       struct sockaddr *               addr;
+       int32_t                                 i;
        
-       // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+       config = dns_configuration_copy();
+       require_action( config, exit, err = kUnknownErr );
        
-       require( inHeader->len < sizeof( inHeader->buf ), exit );
-       src = inHeader->buf;
-       end = src + inHeader->len;
-       if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
-       {
-               src += 4;
-       }
-       else
+       addr = NULL;
+       for( i = 0; i < config->n_resolver; ++i )
        {
-               // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
-               // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+               const dns_resolver_t * const            resolver = config->resolver[ i ];
                
-               for( ;; )
+               if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
                {
-                       while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
-                       if( src >= end ) goto exit;
-                       ++src;
-                       if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
-                       {
-                               src += 2;
-                               break;
-                       }
-                       else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
-                       {
-                               src += 1;
-                               break;
-                       }
+                       addr = resolver->nameserver[ 0 ];
+                       break;
                }
-       }
-       inHeader->extraDataPtr  = src;
-       inHeader->extraDataLen  = (size_t)( end - src );
-       inHeader->len                   = (size_t)( src - inHeader->buf );
-       return( true );
+       }
+       require_action_quiet( addr, exit, err = kNotFoundErr );
+       
+       SockAddrCopy( addr, outAddr );
+       err = kNoErr;
        
 exit:
-       return( false );
+       if( config ) dns_configuration_free( config );
+       return( err );
 }
+#endif
 
-#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//     ResQueryCmd
+//     GetMDNSMulticastAddrV4
 //===========================================================================================================================
 
-// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+static void    _MDNSMulticastAddrV4Init( void *inContext );
 
-SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
-SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
-       int,
-       ( const char *dname, int class, int type, u_char *answer, int anslen ),
-       ( dname, class, type, answer, anslen ) );
+static const struct sockaddr * GetMDNSMulticastAddrV4( void )
+{
+       static struct sockaddr_in               sMDNSMulticastAddrV4;
+       static dispatch_once_t                  sMDNSMulticastAddrV4InitOnce = 0;
+       
+       dispatch_once_f( &sMDNSMulticastAddrV4InitOnce, &sMDNSMulticastAddrV4, _MDNSMulticastAddrV4Init);
+       return( (const struct sockaddr *) &sMDNSMulticastAddrV4 );
+}
 
-// res_query() from libinfo
+static void    _MDNSMulticastAddrV4Init( void *inContext )
+{
+       struct sockaddr_in * const              addr = (struct sockaddr_in *) inContext;
+       
+       memset( addr, 0, sizeof( *addr ) );
+       SIN_LEN_SET( addr );
+       addr->sin_family                = AF_INET;
+       addr->sin_port                  = htons( kMDNSPort );
+       addr->sin_addr.s_addr   = htonl( 0xE00000FB );  // The mDNS IPv4 multicast address is 224.0.0.251
+}
 
-SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
-SOFT_LINK_FUNCTION_EX( info, res_query,
-       int,
-       ( const char *dname, int class, int type, u_char *answer, int anslen ),
-       ( dname, class, type, answer, anslen ) );
+//===========================================================================================================================
+//     GetMDNSMulticastAddrV6
+//===========================================================================================================================
 
-typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+static void    _MDNSMulticastAddrV6Init( void *inContext );
 
-static void    ResQueryCmd( void )
+static const struct sockaddr * GetMDNSMulticastAddrV6( void )
 {
-       OSStatus                err;
-       res_query_f             res_query_ptr;
-       int                             n;
-       uint16_t                type, class;
-       uint8_t                 answer[ 1024 ];
+       static struct sockaddr_in6              sMDNSMulticastAddrV6;
+       static dispatch_once_t                  sMDNSMulticastAddrV6InitOnce = 0;
+       
+       dispatch_once_f( &sMDNSMulticastAddrV6InitOnce, &sMDNSMulticastAddrV6, _MDNSMulticastAddrV6Init);
+       return( (const struct sockaddr *) &sMDNSMulticastAddrV6 );
+}
+
+static void    _MDNSMulticastAddrV6Init( void *inContext )
+{
+       struct sockaddr_in6 * const             addr = (struct sockaddr_in6 *) inContext;
        
-       // Get pointer to one of the res_query() functions.
+       memset( addr, 0, sizeof( *addr ) );
+       SIN6_LEN_SET( addr );
+       addr->sin6_family       = AF_INET6;
+       addr->sin6_port         = htons( kMDNSPort );
+       addr->sin6_addr.s6_addr[  0 ] = 0xFF;   // The mDNS IPv6 multicast address is FF02::FB.
+       addr->sin6_addr.s6_addr[  1 ] = 0x02;
+       addr->sin6_addr.s6_addr[ 15 ] = 0xFB;
+}
+
+//===========================================================================================================================
+//     GetAnyMDNSInterface
+//===========================================================================================================================
+
+static OSStatus        GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex )
+{
+       OSStatus                                        err;
+       struct ifaddrs *                        ifaList;
+       const struct ifaddrs *          ifa;
+       const struct ifaddrs *          ifa2;
+       const char *                            ifname          = NULL;
+       const unsigned int                      checkFlags      = IFF_UP | IFF_MULTICAST | IFF_LOOPBACK | IFF_POINTOPOINT;
+       const unsigned int                      wantFlags       = IFF_UP | IFF_MULTICAST;
+       int                                                     wantFamily;
+       NetTransportType                        type;
+       
+       ifaList = NULL;
+       err = getifaddrs( &ifaList );
+       err = map_global_noerr_errno( err );
+       require_noerr( err, exit );
        
-       if( gResQuery_UseLibInfo )
+       for( ifa = ifaList; ifa; ifa = ifa->ifa_next )
        {
-               if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+               if( ( ifa->ifa_flags & checkFlags ) != wantFlags )      continue;
+               if( !ifa->ifa_addr || !ifa->ifa_name )                          continue;
+               if( ( ifa->ifa_addr->sa_family != AF_INET ) &&
+                       ( ifa->ifa_addr->sa_family != AF_INET6 ) )              continue;
+               
+               err = SocketGetInterfaceInfo( kInvalidSocketRef, ifa->ifa_name, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &type );
+               check_noerr( err );
+               if( err || ( type == kNetTransportType_AWDL ) )         continue;
+               
+               if( !ifname ) ifname = ifa->ifa_name;
+               wantFamily = ( ifa->ifa_addr->sa_family == AF_INET ) ? AF_INET6 : AF_INET;
+               
+               for( ifa2 = ifa->ifa_next; ifa2; ifa2 = ifa2->ifa_next )
                {
-                       FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
-                       err = kNotFoundErr;
-                       goto exit;
+                       if( ( ifa2->ifa_flags & checkFlags ) != wantFlags )     continue;
+                       if( !ifa2->ifa_addr || !ifa2->ifa_name )                        continue;
+                       if( ifa2->ifa_addr->sa_family != wantFamily )           continue;
+                       if( strcmp( ifa2->ifa_name, ifa->ifa_name ) == 0 )      break;
                }
-               res_query_ptr = soft_res_query;
-       }
-       else
-       {
-               if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+               if( ifa2 )
                {
-                       FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
-                       err = kNotFoundErr;
-                       goto exit;
+                       ifname = ifa->ifa_name;
+                       break;
                }
-               res_query_ptr = soft_res_9_query;
        }
+       require_action_quiet( ifname, exit, err = kNotFoundErr );
        
-       // Get record type.
+       if( inNameBuf ) strlcpy( inNameBuf, ifname, IF_NAMESIZE + 1 );
+       if( outIndex )  *outIndex = if_nametoindex( ifname );
        
-       err = RecordTypeFromArgString( gResQuery_Type, &type );
-       require_noerr( err, exit );
+exit:
+       if( ifaList ) freeifaddrs( ifaList );
+       return( err );
+}
+
+//===========================================================================================================================
+//     CreateMulticastSocket
+//===========================================================================================================================
+
+static OSStatus
+       CreateMulticastSocket(
+               const struct sockaddr * inAddr,
+               int                                             inPort,
+               const char *                    inIfName,
+               uint32_t                                inIfIndex,
+               Boolean                                 inJoin,
+               int *                                   outPort,
+               SocketRef *                             outSock )
+{
+       OSStatus                err;
+       SocketRef               sock    = kInvalidSocketRef;
+       const int               family  = inAddr->sa_family;
+       int                             port;
        
-       // Get record class.
+       require_action_quiet( ( family == AF_INET ) ||( family == AF_INET6 ), exit, err = kUnsupportedErr );
        
-       if( gResQuery_Class )
+       err = ServerSocketOpen( family, SOCK_DGRAM, IPPROTO_UDP, inPort, &port, kSocketBufferSize_DontSet, &sock );
+       require_noerr_quiet( err, exit );
+       
+       err = SocketSetMulticastInterface( sock, inIfName, inIfIndex );
+       require_noerr_quiet( err, exit );
+       
+       if( family == AF_INET )
        {
-               err = RecordClassFromArgString( gResQuery_Class, &class );
-               require_noerr( err, exit );
+               err = setsockopt( sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+               err = map_socket_noerr_errno( sock, err );
+               require_noerr_quiet( err, exit );
        }
        else
        {
-               class = kDNSServiceClass_IN;
+               err = setsockopt( sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+               err = map_socket_noerr_errno( sock, err );
+               require_noerr_quiet( err, exit );
        }
        
-       // Print prologue.
+       if( inJoin )
+       {
+               err = SocketJoinMulticast( sock, inAddr, inIfName, inIfIndex );
+               require_noerr_quiet( err, exit );
+       }
        
-       FPrintF( stdout, "Name:       %s\n",                    gResQuery_Name );
-       FPrintF( stdout, "Type:       %s (%u)\n",               RecordTypeToString( type ), type );
-       FPrintF( stdout, "Class:      %s (%u)\n",               ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
-       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
-       FPrintF( stdout, "---\n" );
+       if( outPort ) *outPort = port;
+       *outSock = sock;
+       sock = kInvalidSocketRef;
        
-       // Call res_query().
+exit:
+       ForgetSocket( &sock );
+       return( err );
+}
+
+//===========================================================================================================================
+//     DecimalTextToUInt32
+//===========================================================================================================================
+
+static OSStatus        DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint32_t *outValue, const char **outPtr )
+{
+       OSStatus                        err;
+       uint64_t                        value;
+       const char *            ptr = inSrc;
        
-       n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
-       if( n < 0 )
+       require_action_quiet( ( ptr < inEnd ) && isdigit_safe( *ptr ), exit, err = kMalformedErr );
+       
+       value = (uint64_t)( *ptr++ - '0' );
+       if( value == 0 )
        {
-               FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
-               err = kUnknownErr;
-               goto exit;
+               if( ( ptr < inEnd ) && isdigit_safe( *ptr ) )
+               {
+                       err = kMalformedErr;
+                       goto exit;
+               }
+       }
+       else
+       {
+               while( ( ptr < inEnd ) && isdigit_safe( *ptr ) )
+               {
+                       value = ( value * 10 ) + (uint64_t)( *ptr++ - '0' );
+                       require_action_quiet( value <= UINT32_MAX, exit, err = kRangeErr );
+               }
        }
        
-       // Print result.
-       
-       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+       *outValue = (uint32_t) value;
+       if( outPtr ) *outPtr = ptr;
+       err = kNoErr;
        
 exit:
-       if( err ) exit( 1 );
+       return( err );
 }
 
 //===========================================================================================================================
-//     ResolvDNSQueryCmd
+//     CheckIntegerArgument
 //===========================================================================================================================
 
-// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
-// avoid including the header file.
+static OSStatus        CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax )
+{
+       if( ( inArgValue >= inMin ) && ( inArgValue <= inMax ) ) return( kNoErr );
+       
+       FPrintF( stderr, "error: Invalid %s: %d. Valid range is [%d, %d].\n", inArgName, inArgValue, inMin, inMax );
+       return( kRangeErr );
+}
 
-typedef void *         dns_handle_t;
+//===========================================================================================================================
+//     CheckDoubleArgument
+//===========================================================================================================================
 
-SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
-SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
-SOFT_LINK_FUNCTION_EX( resolv, dns_query,
-       int32_t, (
-               dns_handle_t            dns,
-               const char *            name,
-               uint32_t                        dnsclass,
-               uint32_t                        dnstype,
-               char *                          buf,
-               uint32_t                        len,
-               struct sockaddr *       from,
-               uint32_t *                      fromlen ),
-       ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+static OSStatus        CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax )
+{
+       if( ( inArgValue >= inMin ) && ( inArgValue <= inMax ) ) return( kNoErr );
+       
+       FPrintF( stderr, "error: Invalid %s: %.1f. Valid range is [%.1f, %.1f].\n", inArgName, inArgValue, inMin, inMax );
+       return( kRangeErr );
+}
 
-static void    ResolvDNSQueryCmd( void )
+//===========================================================================================================================
+//     CheckRootUser
+//===========================================================================================================================
+
+static OSStatus        CheckRootUser( void )
 {
-       OSStatus                        err;
-       int                                     n;
-       dns_handle_t            dns = NULL;
-       uint16_t                        type, class;
-       sockaddr_ip                     from;
-       uint32_t                        fromLen;
-       uint8_t                         answer[ 1024 ];
+       if( geteuid() == 0 ) return( kNoErr );
        
-       // Make sure that the required symbols are available.
+       FPrintF( stderr, "error: This command must to be run as root.\n" );
+       return( kPermissionErr );
+}
+
+//===========================================================================================================================
+//     SpawnCommand
+//
+//     Note: Based on systemf() from CoreUtils framework.
+//===========================================================================================================================
+
+extern char **         environ;
+
+static OSStatus        SpawnCommand( pid_t *outPID, const char *inFormat, ... )
+{
+       OSStatus                err;
+       va_list                 args;
+       char *                  command;
+       char *                  argv[ 4 ];
+       pid_t                   pid;
        
-       if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
-       {
-               FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
-               err = kNotFoundErr;
-               goto exit;
-       }
+       command = NULL;
+       va_start( args, inFormat );
+       VASPrintF( &command, inFormat, args );
+       va_end( args );
+       require_action( command, exit, err = kUnknownErr );
        
-       if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
-       {
-               FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
-               err = kNotFoundErr;
-               goto exit;
-       }
+       argv[ 0 ] = "/bin/sh";
+       argv[ 1 ] = "-c";
+       argv[ 2 ] = command;
+       argv[ 3 ] = NULL;
+       err = posix_spawn( &pid, argv[ 0 ], NULL, NULL, argv, environ );
+       free( command );
+       require_noerr_quiet( err, exit );
        
-       if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
-       {
-               FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
-               err = kNotFoundErr;
-               goto exit;
-       }
+       if( outPID ) *outPID = pid;
        
-       // Get record type.
+exit:
+       return( err );
+}
+
+//===========================================================================================================================
+//     OutputPropertyList
+//===========================================================================================================================
+
+static OSStatus
+       OutputPropertyList(
+               CFPropertyListRef       inPList,
+               OutputFormatType        inType,
+               Boolean                         inAppendNewline,
+               const char *            inOutputFilePath )
+{
+       OSStatus                err;
+       CFDataRef               results = NULL;
+       FILE *                  file    = NULL;
        
-       err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
-       require_noerr( err, exit );
+       // Convert plist to a specific format.
        
-       // Get record class.
+       switch( inType )
+       {
+               case kOutputFormatType_JSON:
+                       results = CFCreateJSONData( inPList, kJSONFlags_None, NULL );
+                       require_action( results, exit, err = kUnknownErr );
+                       break;
+               
+               case kOutputFormatType_XML:
+                       results = CFPropertyListCreateData( NULL, inPList, kCFPropertyListXMLFormat_v1_0, 0, NULL );
+                       require_action( results, exit, err = kUnknownErr );
+                       break;
+               
+               case kOutputFormatType_Binary:
+                       results = CFPropertyListCreateData( NULL, inPList, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
+                       require_action( results, exit, err = kUnknownErr );
+                       break;
+               
+               default:
+                       err = kTypeErr;
+                       goto exit;
+       }
        
-       if( gResolvDNSQuery_Class )
+       // Write formatted results to file or stdout.
+       
+       if( inOutputFilePath )
        {
-               err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+               file = fopen( inOutputFilePath, "wb" );
+               err = map_global_value_errno( file, file );
                require_noerr( err, exit );
        }
        else
        {
-               class = kDNSServiceClass_IN;
-       }
-       
-       // Get dns handle.
-       
-       dns = soft_dns_open( gResolvDNSQuery_Path );
-       if( !dns )
-       {
-               FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
-               err = kUnknownErr;
-               goto exit;
+               file = stdout;
        }
        
-       // Print prologue.
-       
-       FPrintF( stdout, "Name:       %s\n",                    gResolvDNSQuery_Name );
-       FPrintF( stdout, "Type:       %s (%u)\n",               RecordTypeToString( type ), type );
-       FPrintF( stdout, "Class:      %s (%u)\n",               ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
-       FPrintF( stdout, "Path:       %s\n",                    gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
-       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
-       FPrintF( stdout, "---\n" );
+       err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
+       require_noerr_quiet( err, exit );
        
-       // Call dns_query().
+       // Write a trailing newline for JSON-formatted results if requested.
        
-       memset( &from, 0, sizeof( from ) );
-       fromLen = (uint32_t) sizeof( from );
-       n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
-               &fromLen );
-       if( n < 0 )
+       if( ( inType == kOutputFormatType_JSON ) && inAppendNewline )
        {
-               FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
-               err = kUnknownErr;
-               goto exit;
+               err = WriteANSIFile( file, "\n", 1 );
+               require_noerr_quiet( err, exit );
        }
        
-       // Print result.
-       
-       FPrintF( stdout, "From:         %##a\n", &from );
-       FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
-       
 exit:
-       if( dns ) soft_dns_free( dns );
-       if( err ) exit( 1 );
+       if( file && ( file != stdout ) ) fclose( file );
+       CFReleaseNullSafe( results );
+       return( err );
+}
+
+//===========================================================================================================================
+//     DNSRecordFixedFieldsSet
+//===========================================================================================================================
+
+static void
+       DNSRecordFixedFieldsSet(
+               DNSRecordFixedFields *  inFields,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint32_t                                inTTL,
+               uint16_t                                inRDLength )
+{
+       WriteBig16( inFields->type,             inType );
+       WriteBig16( inFields->class,    inClass );
+       WriteBig32( inFields->ttl,              inTTL );
+       WriteBig16( inFields->rdlength, inRDLength );
+}
+
+//===========================================================================================================================
+//     SRVRecordDataFixedFieldsGet
+//===========================================================================================================================
+
+static void
+       SRVRecordDataFixedFieldsGet(
+               const SRVRecordDataFixedFields *        inFields,
+               unsigned int *                                          outPriority,
+               unsigned int *                                          outWeight,
+               unsigned int *                                          outPort )
+{
+       if( outPriority )       *outPriority    = ReadBig16( inFields->priority );
+       if( outWeight )         *outWeight              = ReadBig16( inFields->weight );
+       if( outPort )           *outPort                = ReadBig16( inFields->port );
 }
 
 //===========================================================================================================================
-//     CFHostCmd
+//     SRVRecordDataFixedFieldsSet
 //===========================================================================================================================
 
 static void
-       _CFHostResolveCallback(
-               CFHostRef                               inHost,
-               CFHostInfoType                  inInfoType,
-               const CFStreamError *   inError,
-               void *                                  inInfo );
+       SRVRecordDataFixedFieldsSet(
+               SRVRecordDataFixedFields *      inFields,
+               uint16_t                                        inPriority,
+               uint16_t                                        inWeight,
+               uint16_t                                        inPort )
+{
+       WriteBig16( inFields->priority, inPriority );
+       WriteBig16( inFields->weight,   inWeight );
+       WriteBig16( inFields->port,             inPort );
+}
 
-static void    CFHostCmd( void )
+//===========================================================================================================================
+//     SOARecordDataFixedFieldsGet
+//===========================================================================================================================
+
+static void
+       SOARecordDataFixedFieldsGet(
+               const SOARecordDataFixedFields *        inFields,
+               uint32_t *                                                      outSerial,
+               uint32_t *                                                      outRefresh,
+               uint32_t *                                                      outRetry,
+               uint32_t *                                                      outExpire,
+               uint32_t *                                                      outMinimum )
+{
+       if( outSerial )         *outSerial      = ReadBig32( inFields->serial );
+       if( outRefresh )        *outRefresh     = ReadBig32( inFields->refresh );
+       if( outRetry )          *outRetry       = ReadBig32( inFields->retry );
+       if( outExpire )         *outExpire      = ReadBig32( inFields->expire );
+       if( outMinimum )        *outMinimum     = ReadBig32( inFields->minimum );
+}
+
+//===========================================================================================================================
+//     SOARecordDataFixedFieldsSet
+//===========================================================================================================================
+
+static void
+       SOARecordDataFixedFieldsSet(
+               SOARecordDataFixedFields *      inFields,
+               uint32_t                                        inSerial,
+               uint32_t                                        inRefresh,
+               uint32_t                                        inRetry,
+               uint32_t                                        inExpire,
+               uint32_t                                        inMinimum )
+{
+       WriteBig32( inFields->serial,   inSerial );
+       WriteBig32( inFields->refresh,  inRefresh );
+       WriteBig32( inFields->retry,    inRetry );
+       WriteBig32( inFields->expire,   inExpire );
+       WriteBig32( inFields->minimum,  inMinimum );
+}
+
+//===========================================================================================================================
+//     CreateSRVRecordDataFromString
+//===========================================================================================================================
+
+static OSStatus        CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen )
 {
-       OSStatus                                err;
-       CFStringRef                             name;
-       Boolean                                 success;
-       CFHostRef                               host = NULL;
-       CFHostClientContext             context;
-       CFStreamError                   streamErr;
-       
-       name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 );
-       require_action( name, exit, err = kUnknownErr );
+       OSStatus                        err;
+       DataBuffer                      dataBuf;
+       const char *            ptr;
+       int                                     i;
+       uint8_t *                       end;
+       uint8_t                         target[ kDomainNameLengthMax ];
        
-       host = CFHostCreateWithName( kCFAllocatorDefault, name );
-       ForgetCF( &name );
-       require_action( host, exit, err = kUnknownErr );
+       DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax );
        
-       memset( &context, 0, sizeof( context ) );
-       success = CFHostSetClient( host, _CFHostResolveCallback, &context );
-       require_action( success, exit, err = kUnknownErr );
+       // Parse and set the priority, weight, and port values (all three are unsigned 16-bit values).
        
-       CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+       ptr = inString;
+       for( i = 0; i < 3; ++i )
+       {
+               char *          next;
+               long            value;
+               uint8_t         buf[ 2 ];
+               
+               value = strtol( ptr, &next, 0 );
+               require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr );
+               require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr );
+               ptr = next + 1;
+               
+               WriteBig16( buf, value );
+               
+               err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) );
+               require_noerr( err, exit );
+       }
        
-       // Print prologue.
+       // Set the target domain name.
        
-       FPrintF( stdout, "Hostname:   %s\n",                    gCFHost_Name );
-       FPrintF( stdout, "Start time: %{du:time}\n",    NULL );
-       FPrintF( stdout, "---\n" );
+       err = DomainNameFromString( target, ptr, &end );
+    require_noerr_quiet( err, exit );
        
-       success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
-       require_action( success, exit, err = kUnknownErr );
-       err = kNoErr;
+       err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) );
+       require_noerr( err, exit );
        
-       CFRunLoopRun();
+       err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+       require_noerr( err, exit );
        
 exit:
-       CFReleaseNullSafe( host );
-       if( err ) exit( 1 );
+       DataBuffer_Free( &dataBuf );
+       return( err );
 }
 
-static void    _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+//===========================================================================================================================
+//     CreateTXTRecordDataFromString
+//===========================================================================================================================
+
+static OSStatus        CreateTXTRecordDataFromString(const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen )
 {
        OSStatus                        err;
-       struct timeval          now;
-       
-       gettimeofday( &now, NULL );
+       DataBuffer                      dataBuf;
+       const char *            src;
+       uint8_t                         txtStr[ 256 ];  // Buffer for single TXT string: 1 length byte + up to 255 bytes of data.
        
-       Unused( inInfoType );
-       Unused( inInfo );
+       DataBuffer_Init( &dataBuf, NULL, 0, kDNSRecordDataLengthMax );
        
-       if( inError && ( inError->domain != 0 ) && ( inError->error ) )
-       {
-               err = inError->error;
-               if( inError->domain == kCFStreamErrorDomainNetDB )
-               {
-                       FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
-               }
-               else
-               {
-                       FPrintF( stderr, "Error %#m\n", err );
-               }
-       }
-       else
+       src = inString;
+       for( ;; )
        {
-               CFArrayRef                                      addresses;
-               CFIndex                                         count, i;
-               CFDataRef                                       addrData;
-               const struct sockaddr *         sockAddr;
-               Boolean                                         wasResolved = false;
-               
-               addresses = CFHostGetAddressing( inHost, &wasResolved );
-               check( wasResolved );
+               uint8_t *                                       dst = &txtStr[ 1 ];
+               const uint8_t * const           lim = &txtStr[ 256 ];
+               int                                                     c;
                
-               if( addresses )
+               while( *src && ( *src != inDelimiter ) )
                {
-                       count = CFArrayGetCount( addresses );
-                       for( i = 0; i < count; ++i )
+                       if( ( c = *src++ ) == '\\' )
                        {
-                               addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
-                               require_noerr( err, exit );
-                               
-                               sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
-                               FPrintF( stdout, "%##a\n", sockAddr );
+                               require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+                               c = *src++;
                        }
+                       require_action_quiet( dst < lim, exit, err = kOverrunErr );
+                       *dst++ = (uint8_t) c;
                }
-               err = kNoErr;
+               txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
+               err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
+               require_noerr( err, exit );
+               
+               if( *src == '\0' ) break;
+               ++src;
        }
        
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "End time:   %{du:time}\n", &now );
-       
-       if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+       err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+       require_noerr( err, exit );
        
 exit:
-       exit( err ? 1 : 0 );
+       DataBuffer_Free( &dataBuf );
+       return( err );
 }
 
 //===========================================================================================================================
-//     DNSConfigAddCmd
-//
-//     Note: Based on ajn's supplemental test tool.
+//     CreateNSECRecordData
 //===========================================================================================================================
 
-static void    DNSConfigAddCmd( void )
+DECLARE_QSORT_NUMERIC_COMPARATOR( _QSortCmpUnsigned );
+DEFINE_QSORT_NUMERIC_COMPARATOR( unsigned int, _QSortCmpUnsigned )
+
+#define kNSECBitmapMaxLength           32      // 32 bytes (256 bits). See <https://tools.ietf.org/html/rfc4034#section-4.1.2>.
+
+static OSStatus
+       CreateNSECRecordData(
+               const uint8_t * inNextDomainName,
+               uint8_t **              outPtr,
+               size_t *                outLen,
+               unsigned int    inTypeCount,
+               ... )
 {
-       OSStatus                                        err;
-       CFMutableDictionaryRef          dict    = NULL;
-       CFMutableArrayRef                       array   = NULL;
-       size_t                                          i;
-       SCDynamicStoreRef                       store   = NULL;
-       CFStringRef                                     key             = NULL;
-       Boolean                                         success;
-       
-       if( geteuid() != 0 )
-       {
-               FPrintF( stderr, "error: This command must to be run as root.\n" );
-               err = kIDErr;
-               goto exit;
-       }
+       OSStatus                        err;
+       va_list                         args;
+       DataBuffer                      rdataDB;
+       unsigned int *          array   = NULL;
+       unsigned int            i, type, maxBit, currBlock, bitmapLen;
+       uint8_t                         fields[ 2 + kNSECBitmapMaxLength ];
+       uint8_t * const         bitmap  = &fields[ 2 ];
        
-       // Create dictionary.
+       va_start( args, inTypeCount );
+       DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax );
        
-       dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
-       require_action( dict, exit, err = kNoMemoryErr );
+       // Append Next Domain Name.
        
-       // Add DNS server IP addresses.
+       err = DataBuffer_Append( &rdataDB, inNextDomainName, DomainNameLength( inNextDomainName ) );
+       require_noerr( err, exit );
        
-       array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
-       require_action( array, exit, err = kNoMemoryErr );
+       // Append Type Bit Maps.
        
-       for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+       maxBit = 0;
+       memset( bitmap, 0, kNSECBitmapMaxLength );
+       if( inTypeCount > 0 )
        {
-               CFStringRef             addrStr;
+               array = (unsigned int *) malloc( inTypeCount * sizeof_element( array ) );
+               require_action( array, exit, err = kNoMemoryErr );
                
-               addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
-               require_action( addrStr, exit, err = kUnknownErr );
+               for( i = 0; i < inTypeCount; ++i )
+               {
+                       type = va_arg( args, unsigned int );
+                       require_action_quiet( type <= UINT16_MAX, exit, err = kRangeErr );
+                       array[ i ] = type;
+               }
+               qsort( array, inTypeCount, sizeof_element( array ), _QSortCmpUnsigned );
                
-               CFArrayAppendValue( array, addrStr );
-               CFRelease( addrStr );
-       }
-       
-       CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
-       ForgetCF( &array );
-       
-       // Add domains, if any.
-       
-       array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
-       require_action( array, exit, err = kNoMemoryErr );
-       
-       if( gDNSConfigAdd_DomainCount > 0 )
-       {
-               for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+               currBlock = array[ 0 ] / 256;
+               for( i = 0; i < inTypeCount; ++i )
                {
-                       CFStringRef             domainStr;
+                       const unsigned int              block   = array[ i ] / 256;
+                       const unsigned int              bit             = array[ i ] % 256;
                        
-                       domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
-                       require_action( domainStr, exit, err = kUnknownErr );
-                       
-                       CFArrayAppendValue( array, domainStr );
-                       CFRelease( domainStr );
+                       if( block != currBlock )
+                       {
+                               bitmapLen       = BitArray_MaxBytes( maxBit + 1 );
+                               fields[ 0 ] = (uint8_t) currBlock;
+                               fields[ 1 ] = (uint8_t) bitmapLen;
+                               
+                               err = DataBuffer_Append( &rdataDB, fields, 2 + bitmapLen );
+                               require_noerr( err, exit );
+                               
+                               maxBit          = 0;
+                               currBlock       = block;
+                               memset( bitmap, 0, bitmapLen );
+                       }
+                       BitArray_SetBit( bitmap, bit );
+                       if( bit > maxBit ) maxBit = bit;
                }
        }
        else
        {
-               // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
-               
-               CFArrayAppendValue( array, CFSTR( "" ) );
-       }
-       
-       CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
-       ForgetCF( &array );
-       
-       // Add interface, if any.
-       
-       if( gDNSConfigAdd_Interface )
-       {
-               err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
-               require_noerr( err, exit );
-               
-               CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+               currBlock = 0;
        }
        
-       // Set dictionary in dynamic store.
+       bitmapLen       = BitArray_MaxBytes( maxBit + 1 );
+       fields[ 0 ] = (uint8_t) currBlock;
+       fields[ 1 ] = (uint8_t) bitmapLen;
        
-       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
-       err = map_scerror( store );
+       err = DataBuffer_Append( &rdataDB, fields, 2 + bitmapLen );
        require_noerr( err, exit );
        
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
-       require_action( key, exit, err = kUnknownErr );
-       
-       success = SCDynamicStoreSetValue( store, key, dict );
-       require_action( success, exit, err = kUnknownErr );
+       err = DataBuffer_Detach( &rdataDB, outPtr, outLen );
+       require_noerr( err, exit );
        
 exit:
-       CFReleaseNullSafe( dict );
-       CFReleaseNullSafe( array );
-       CFReleaseNullSafe( store );
-       CFReleaseNullSafe( key );
-       gExitCode = err ? 1 : 0;
+       va_end( args );
+       DataBuffer_Free( &rdataDB );
+       FreeNullSafe( array );
+       return( err );
 }
 
 //===========================================================================================================================
-//     DNSConfigRemoveCmd
+//     AppendSOARecord
 //===========================================================================================================================
 
-static void    DNSConfigRemoveCmd( void )
-{
-       OSStatus                                err;
-       SCDynamicStoreRef               store   = NULL;
-       CFStringRef                             key             = NULL;
-       Boolean                                 success;
+static OSStatus
+       _AppendSOARecordData(
+               DataBuffer *    inDB,
+               const uint8_t * inMName,
+               const uint8_t * inRName,
+               uint32_t                inSerial,
+               uint32_t                inRefresh,
+               uint32_t                inRetry,
+               uint32_t                inExpire,
+               uint32_t                inMinimumTTL,
+               size_t *                outLen );
+
+static OSStatus
+       AppendSOARecord(
+               DataBuffer *    inDB,
+               const uint8_t * inNamePtr,
+               size_t                  inNameLen,
+               uint16_t                inType,
+               uint16_t                inClass,
+               uint32_t                inTTL,
+               const uint8_t * inMName,
+               const uint8_t * inRName,
+               uint32_t                inSerial,
+               uint32_t                inRefresh,
+               uint32_t                inRetry,
+               uint32_t                inExpire,
+               uint32_t                inMinimumTTL,
+               size_t *                outLen )
+{
+       OSStatus                err;
+       size_t                  rdataLen;
+       size_t                  rdlengthOffset = 0;
+       uint8_t *               rdlengthPtr;
        
-       if( geteuid() != 0 )
+       if( inDB )
        {
-               FPrintF( stderr, "error: This command must to be run as root.\n" );
-               err = kIDErr;
-               goto exit;
+               err = _DataBuffer_AppendDNSRecord( inDB, inNamePtr, inNameLen, inType, inClass, inTTL, NULL, 0 );
+               require_noerr( err, exit );
+               
+               rdlengthOffset = DataBuffer_GetLen( inDB ) - 2;
        }
        
-       store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
-       err = map_scerror( store );
+       err = _AppendSOARecordData( inDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, &rdataLen );
        require_noerr( err, exit );
        
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
-       require_action( key, exit, err = kUnknownErr );
+       if( inDB )
+       {
+               rdlengthPtr = DataBuffer_GetPtr( inDB ) + rdlengthOffset;
+               WriteBig16( rdlengthPtr, rdataLen );
+       }
        
-       success = SCDynamicStoreRemoveValue( store, key );
-       require_action( success, exit, err = kUnknownErr );
+       if( outLen ) *outLen = inNameLen + sizeof( DNSRecordFixedFields ) + rdataLen;
+       err = kNoErr;
        
 exit:
-       CFReleaseNullSafe( store );
-       CFReleaseNullSafe( key );
-       gExitCode = err ? 1 : 0;
+       return( err );
 }
-#endif // TARGET_OS_DARWIN
-
-//===========================================================================================================================
-//     DaemonVersionCmd
-//===========================================================================================================================
 
-static void    DaemonVersionCmd( void )
+static OSStatus
+       _AppendSOARecordData(
+               DataBuffer *    inDB,
+               const uint8_t * inMName,
+               const uint8_t * inRName,
+               uint32_t                inSerial,
+               uint32_t                inRefresh,
+               uint32_t                inRetry,
+               uint32_t                inExpire,
+               uint32_t                inMinimumTTL,
+               size_t *                outLen )
 {
-       OSStatus                err;
-       uint32_t                size, version;
-       char                    strBuf[ 16 ];
-       
-       size = (uint32_t) sizeof( version );
-       err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
-       require_noerr( err, exit );
+       OSStatus                                                err;
+       SOARecordDataFixedFields                fields;
+       const size_t                                    mnameLen = DomainNameLength( inMName );
+       const size_t                                    rnameLen = DomainNameLength( inRName );
        
-       FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+       if( inDB )
+       {
+               err = DataBuffer_Append( inDB, inMName, mnameLen );
+               require_noerr( err, exit );
+               
+               err = DataBuffer_Append( inDB, inRName, rnameLen );
+               require_noerr( err, exit );
+               
+               SOARecordDataFixedFieldsSet( &fields, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL );
+               err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+               require_noerr( err, exit );
+       }
+       if( outLen ) *outLen = mnameLen + rnameLen + sizeof( fields );
+       err = kNoErr;
        
 exit:
-       if( err ) exit( 1 );
+       return( err );
 }
 
 //===========================================================================================================================
-//     Exit
+//     CreateSOARecordData
 //===========================================================================================================================
 
-static void    Exit( void *inContext )
+static OSStatus
+       CreateSOARecordData(
+               const uint8_t * inMName,
+               const uint8_t * inRName,
+               uint32_t                inSerial,
+               uint32_t                inRefresh,
+               uint32_t                inRetry,
+               uint32_t                inExpire,
+               uint32_t                inMinimumTTL,
+               uint8_t **              outPtr,
+               size_t *                outLen )
 {
-       const char * const              reason = (const char *) inContext;
+       OSStatus                err;
+       DataBuffer              rdataDB;
        
-       FPrintF( stdout, "---\n" );
-       FPrintF( stdout, "End time:   %{du:time}\n", NULL );
-       if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
-       exit( gExitCode );
+       DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax );
+       
+       err = _AppendSOARecordData( &rdataDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, NULL );
+       require_noerr( err, exit );
+       
+       err = DataBuffer_Detach( &rdataDB, outPtr, outLen );
+       require_noerr( err, exit );
+       
+exit:
+       DataBuffer_Free( &rdataDB );
+       return( err );
 }
 
 //===========================================================================================================================
-//     PrintFTimestampHandler
+//     _DataBuffer_AppendDNSQuestion
 //===========================================================================================================================
 
-static int
-       PrintFTimestampHandler(
-               PrintFContext * inContext,
-               PrintFFormat *  inFormat,
-               PrintFVAList *  inArgs,
-               void *                  inUserContext )
+static OSStatus
+       _DataBuffer_AppendDNSQuestion(
+               DataBuffer *    inDB,
+               const uint8_t * inNamePtr,
+               size_t                  inNameLen,
+               uint16_t                inType,
+               uint16_t                inClass )
 {
-       struct timeval                          now;
-       const struct timeval *          tv;
-       struct tm *                                     localTime;
-       size_t                                          len;
-       int                                                     n;
-       char                                            dateTimeStr[ 32 ];
-       
-       Unused( inUserContext );
-       
-       tv = va_arg( inArgs->args, const struct timeval * );
-       require_action_quiet( !inFormat->suppress, exit, n = 0 );
+       OSStatus                                        err;
+       DNSQuestionFixedFields          fields;
        
-       if( !tv )
-       {
-               gettimeofday( &now, NULL );
-               tv = &now;
-       }
-       localTime = localtime( &tv->tv_sec );
-       len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime );
-       if( len == 0 ) dateTimeStr[ 0 ] = '\0';
+       err = DataBuffer_Append( inDB, inNamePtr, inNameLen );
+       require_noerr( err, exit );
        
-       n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
+       DNSQuestionFixedFieldsInit( &fields, inType, inClass );
+       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+       require_noerr( err, exit );
        
 exit:
-       return( n );
+       return( err );
 }
 
 //===========================================================================================================================
-//     PrintFDNSMessageHandler
+//     _DataBuffer_AppendDNSRecord
 //===========================================================================================================================
 
-static int
-       PrintFDNSMessageHandler(
-               PrintFContext * inContext,
-               PrintFFormat *  inFormat,
-               PrintFVAList *  inArgs,
-               void *                  inUserContext )
+static OSStatus
+       _DataBuffer_AppendDNSRecord(
+               DataBuffer *    inDB,
+               const uint8_t * inNamePtr,
+               size_t                  inNameLen,
+               uint16_t                inType,
+               uint16_t                inClass,
+               uint32_t                inTTL,
+               const uint8_t * inRDataPtr,
+               size_t                  inRDataLen )
 {
-       OSStatus                        err;
-       const void *            msgPtr;
-       size_t                          msgLen;
-       char *                          text;
-       int                                     n;
-       Boolean                         isMDNS;
-       Boolean                         printRawRData;
+       OSStatus                                        err;
+       DNSRecordFixedFields            fields;
        
-       Unused( inUserContext );
+       require_action_quiet( inRDataLen < kDNSRecordDataLengthMax, exit, err = kSizeErr );
        
-       msgPtr = va_arg( inArgs->args, const void * );
-       msgLen = va_arg( inArgs->args, size_t );
-       require_action_quiet( !inFormat->suppress, exit, n = 0 );
+       err = DataBuffer_Append( inDB, inNamePtr, inNameLen );
+       require_noerr( err, exit );
        
-       isMDNS = ( inFormat->altForm > 0 ) ? true : false;
-       if(      inFormat->precision == 0 ) printRawRData = false;
-       else if( inFormat->precision == 1 ) printRawRData = true;
-       else
-       {
-               n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" );
-               goto exit;
-       }
+       DNSRecordFixedFieldsSet( &fields, inType, inClass, inTTL, (uint16_t) inRDataLen );
+       err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+       require_noerr( err, exit );
        
-       err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
-       if( !err )
-       {
-               n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
-               free( text );
-       }
-       else
+       if( inRDataPtr )
        {
-               n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+               err = DataBuffer_Append( inDB, inRDataPtr, inRDataLen );
+               require_noerr( err, exit );
        }
        
 exit:
-       return( n );
+       return( err );
 }
 
 //===========================================================================================================================
-//     GetDNSSDFlagsFromOpts
+//     _NanoTime64ToDateString
 //===========================================================================================================================
 
-static DNSServiceFlags GetDNSSDFlagsFromOpts( void )
+static char *  _NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen )
 {
-       DNSServiceFlags         flags;
-       
-       flags = (DNSServiceFlags) gDNSSDFlags;
-       if( flags & kDNSServiceFlagsShareConnection )
-       {
-               FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n",
-                       kDNSServiceFlagsShareConnection );
-       }
-       
-       if( gDNSSDFlag_BrowseDomains )                  flags |= kDNSServiceFlagsBrowseDomains;
-       if( gDNSSDFlag_DenyCellular )                   flags |= kDNSServiceFlagsDenyCellular;
-       if( gDNSSDFlag_DenyExpensive )                  flags |= kDNSServiceFlagsDenyExpensive;
-       if( gDNSSDFlag_ForceMulticast )                 flags |= kDNSServiceFlagsForceMulticast;
-       if( gDNSSDFlag_IncludeAWDL )                    flags |= kDNSServiceFlagsIncludeAWDL;
-       if( gDNSSDFlag_NoAutoRename )                   flags |= kDNSServiceFlagsNoAutoRename;
-       if( gDNSSDFlag_PathEvaluationDone )             flags |= kDNSServiceFlagsPathEvaluationDone;
-       if( gDNSSDFlag_RegistrationDomains )    flags |= kDNSServiceFlagsRegistrationDomains;
-       if( gDNSSDFlag_ReturnIntermediates )    flags |= kDNSServiceFlagsReturnIntermediates;
-       if( gDNSSDFlag_Shared )                                 flags |= kDNSServiceFlagsShared;
-       if( gDNSSDFlag_SuppressUnusable )               flags |= kDNSServiceFlagsSuppressUnusable;
-       if( gDNSSDFlag_Timeout )                                flags |= kDNSServiceFlagsTimeout;
-       if( gDNSSDFlag_UnicastResponse )                flags |= kDNSServiceFlagsUnicastResponse;
-       if( gDNSSDFlag_Unique )                                 flags |= kDNSServiceFlagsUnique;
-       if( gDNSSDFlag_WakeOnResolve )                  flags |= kDNSServiceFlagsWakeOnResolve;
+       struct  timeval         tv;
        
-       return( flags );
+       NanoTimeToTimeVal( inTime, &tv );
+       return( MakeFractionalDateString( &tv, inBuf, inMaxLen ) );
 }
 
 //===========================================================================================================================
-//     CreateConnectionFromArgString
+//     MDNSColliderCreate
 //===========================================================================================================================
 
-static OSStatus
-       CreateConnectionFromArgString(
-               const char *                    inString,
-               dispatch_queue_t                inQueue,
-               DNSServiceRef *                 outSDRef,
-               ConnectionDesc *                outDesc )
+typedef enum
 {
-       OSStatus                        err;
-       DNSServiceRef           sdRef = NULL;
-       ConnectionType          type;
-       int32_t                         pid = -1;       // Initializing because the analyzer claims pid may be used uninitialized.
-       uint8_t                         uuid[ 16 ];
-       
-       if( strcasecmp( inString, kConnectionArg_Normal ) == 0 )
-       {
-               err = DNSServiceCreateConnection( &sdRef );
-               require_noerr( err, exit );
-               type = kConnectionType_Normal;
-       }
-       else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 )
-       {
-               const char * const              pidStr = inString + sizeof_string( kConnectionArgPrefix_PID );
-               
-               err = StringToInt32( pidStr, &pid );
-               if( err )
-               {
-                       FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr );
-                       err = kParamErr;
-                       goto exit;
-               }
-               
-               memset( uuid, 0, sizeof( uuid ) );
-               err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid );
-               if( err )
-               {
-                       FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid );
-                       goto exit;
-               }
-               type = kConnectionType_DelegatePID;
-       }
-       else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 )
-       {
-               const char * const              uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID );
-               
-               check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) );
-               
-               err = StringToUUID( uuidStr, kSizeCString, false, uuid );
-               if( err )
-               {
-                       FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr );
-                       err = kParamErr;
-                       goto exit;
-               }
-               
-               err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid );
-               if( err )
-               {
-                       FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid );
-                       goto exit;
-               }
-               type = kConnectionType_DelegateUUID;
-       }
-       else
-       {
-               FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString );
-               err = kParamErr;
-               goto exit;
-       }
-       
-       err = DNSServiceSetDispatchQueue( sdRef, inQueue );
-       require_noerr( err, exit );
+       kMDNSColliderOpCode_Invalid                     = 0,
+       kMDNSColliderOpCode_Send                        = 1,
+       kMDNSColliderOpCode_Wait                        = 2,
+       kMDNSColliderOpCode_SetProbeActions     = 3,
+       kMDNSColliderOpCode_LoopPush            = 4,
+       kMDNSColliderOpCode_LoopPop                     = 5,
+       kMDNSColliderOpCode_Exit                        = 6
        
-       *outSDRef = sdRef;
-       if( outDesc )
-       {
-               outDesc->type = type;
-               if(      type == kConnectionType_DelegatePID )  outDesc->delegate.pid = pid;
-               else if( type == kConnectionType_DelegateUUID ) memcpy( outDesc->delegate.uuid, uuid, 16 );
-       }
-       sdRef = NULL;
-       
-exit:
-       if( sdRef ) DNSServiceRefDeallocate( sdRef );
-       return( err );
-}
+}      MDNSColliderOpCode;
+
+typedef struct
+{
+       MDNSColliderOpCode              opcode;
+       uint32_t                                operand;
+       
+}      MDNSCInstruction;
+
+#define kMaxLoopDepth          16
+
+struct MDNSColliderPrivate
+{
+       CFRuntimeBase                                   base;                                                   // CF object base.
+       dispatch_queue_t                                queue;                                                  // Queue for collider's events.
+       dispatch_source_t                               readSourceV4;                                   // Read dispatch source for IPv4 socket.
+       dispatch_source_t                               readSourceV6;                                   // Read dispatch source for IPv6 socket.
+       SocketRef                                               sockV4;                                                 // IPv4 UDP socket for mDNS.
+       SocketRef                                               sockV6;                                                 // IPv6 UDP socket for mDNS.
+       uint8_t *                                               target;                                                 // Record name being targeted. (malloced)
+       uint8_t *                                               responsePtr;                                    // Response message pointer. (malloced)
+       size_t                                                  responseLen;                                    // Response message length.
+       uint8_t *                                               probePtr;                                               // Probe query message pointer. (malloced)
+       size_t                                                  probeLen;                                               // Probe query message length.
+       unsigned int                                    probeCount;                                             // Count of probe queries received for collider's record.
+       uint32_t                                                probeActionMap;                                 // Bitmap of actions to take for 
+       MDNSCInstruction *                              program;                                                // Program to execute.
+       uint32_t                                                pc;                                                             // Program's program counter.
+       uint32_t                                                loopCounts[ kMaxLoopDepth ];    // Stack of loop counters.
+       uint32_t                                                loopDepth;                                              // Current loop depth.
+       dispatch_source_t                               waitTimer;                                              // Timer for program's wait commands.
+       uint32_t                                                interfaceIndex;                                 // Interface over which to send and receive mDNS msgs.
+       MDNSColliderStopHandler_f               stopHandler;                                    // User's stop handler.
+       void *                                                  stopContext;                                    // User's stop handler context.
+       MDNSColliderProtocols                   protocols;                                              // Protocols to use, i.e., IPv4, IPv6.
+       Boolean                                                 stopped;                                                // True if the collider has been stopped.
+       uint8_t                                                 msgBuf[ kMDNSMessageSizeMax ];  // mDNS message buffer.
+};
 
-//===========================================================================================================================
-//     InterfaceIndexFromArgString
-//===========================================================================================================================
+static void            _MDNSColliderStop( MDNSColliderRef inCollider, OSStatus inError );
+static void            _MDNSColliderReadHandler( void *inContext );
+static void            _MDNSColliderExecuteProgram( void *inContext );
+static OSStatus        _MDNSColliderSendResponse( MDNSColliderRef inCollider, SocketRef inSock, const struct sockaddr *inDest );
+static OSStatus        _MDNSColliderSendProbe( MDNSColliderRef inCollider, SocketRef inSock, const struct sockaddr *inDest );
 
-static OSStatus        InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
+CF_CLASS_DEFINE( MDNSCollider );
+
+ulog_define_ex( "com.apple.dnssdutil", MDNSCollider, kLogLevelInfo, kLogFlags_None, "MDNSCollider", NULL );
+#define mc_ulog( LEVEL, ... )          ulog( &log_category_from_name( MDNSCollider ), (LEVEL), __VA_ARGS__ )
+
+static OSStatus        MDNSColliderCreate( dispatch_queue_t inQueue, MDNSColliderRef *outCollider )
 {
-       OSStatus                err;
-       uint32_t                ifIndex;
+       OSStatus                        err;
+       MDNSColliderRef         obj = NULL;
        
-       if( inString )
-       {
-               ifIndex = if_nametoindex( inString );
-               if( ifIndex == 0 )
-               {
-                       err = StringToUInt32( inString, &ifIndex );
-                       if( err )
-                       {
-                               FPrintF( stderr, "Invalid interface value: %s\n", inString );
-                               err = kParamErr;
-                               goto exit;
-                       }
-               }
-       }
-       else
-       {
-               ifIndex = 0;
-       }
+       CF_OBJECT_CREATE( MDNSCollider, obj, err, exit );
        
-       *outIndex = ifIndex;
+       ReplaceDispatchQueue( &obj->queue, inQueue );
+       obj->sockV4 = kInvalidSocketRef;
+       obj->sockV6 = kInvalidSocketRef;
+       
+       *outCollider = obj;
        err = kNoErr;
        
 exit:
 }
 
 //===========================================================================================================================
-//     RecordDataFromArgString
+//     _MDNSColliderFinalize
 //===========================================================================================================================
 
-#define kRDataMaxLen           UINT16_C( 0xFFFF )
+static void    _MDNSColliderFinalize( CFTypeRef inObj )
+{
+       MDNSColliderRef const           me = (MDNSColliderRef) inObj;
+       
+       check( !me->waitTimer );
+       check( !me->readSourceV4 );
+       check( !me->readSourceV6 );
+       check( !IsValidSocket( me->sockV4 ) );
+       check( !IsValidSocket( me->sockV6 ) );
+       ForgetMem( &me->target );
+       ForgetMem( &me->responsePtr );
+       ForgetMem( &me->probePtr );
+       ForgetMem( &me->program );
+       dispatch_forget( &me->queue );
+}
+
+//===========================================================================================================================
+//     MDNSColliderStart
+//===========================================================================================================================
 
-static OSStatus        StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen );
-static OSStatus        StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen );
+static void    _MDNSColliderStart( void *inContext );
 
-static OSStatus        RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+static OSStatus        MDNSColliderStart( MDNSColliderRef me )
 {
        OSStatus                err;
-       uint8_t *               dataPtr = NULL;
-       size_t                  dataLen;
-       
-       if( 0 ) {}
        
-       // Domain name
+       require_action_quiet( me->target,         exit, err = kNotPreparedErr );
+       require_action_quiet( me->responsePtr,    exit, err = kNotPreparedErr );
+       require_action_quiet( me->probePtr,       exit, err = kNotPreparedErr );
+       require_action_quiet( me->program,        exit, err = kNotPreparedErr );
+       require_action_quiet( me->interfaceIndex, exit, err = kNotPreparedErr );
+       require_action_quiet( me->protocols,      exit, err = kNotPreparedErr );
        
-       else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_Domain );
-               
-               err = StringToDomainName( str, &dataPtr, &dataLen );
-               require_noerr_quiet( err, exit );
-       }
+       CFRetain( me );
+       dispatch_async_f( me->queue, me, _MDNSColliderStart );
+       err = kNoErr;
        
-       // File path
+exit:
+       return( err );
+}
+
+static void    _MDNSColliderStart( void *inContext )
+{
+       OSStatus                                        err;
+       MDNSColliderRef const           me              = (MDNSColliderRef) inContext;
+       SocketRef                                       sock    = kInvalidSocketRef;
+       SocketContext *                         sockCtx = NULL;
        
-       else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+       if( me->protocols & kMDNSColliderProtocol_IPv4 )
        {
-               const char * const              path = inString + sizeof_string( kRDataArgPrefix_File );
+               err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
+               require_noerr( err, exit );
                
-               err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+               err = SocketContextCreate( sock, me, &sockCtx );
                require_noerr( err, exit );
-               require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
-       }
-       
-       // Hexadecimal string
-       
-       else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_HexString );
+               sock = kInvalidSocketRef;
                
-               err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+               err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler,
+                       sockCtx, &me->readSourceV4 );
                require_noerr( err, exit );
-               require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
-       }
-       
-       // IPv4 address string
-       
-       else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+               me->sockV4 = sockCtx->sock;
+               sockCtx = NULL;
                
-               err = StringToARecordData( str, &dataPtr, &dataLen );
-               require_noerr_quiet( err, exit );
+               dispatch_resume( me->readSourceV4 );
        }
        
-       // IPv6 address string
-       
-       else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+       if( me->protocols & kMDNSColliderProtocol_IPv6 )
        {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+               err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
+               require_noerr( err, exit );
                
-               err = StringToAAAARecordData( str, &dataPtr, &dataLen );
-               require_noerr_quiet( err, exit );
-       }
-       
-       // SRV record
-       
-       else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_SRV );
+               err = SocketContextCreate( sock, me, &sockCtx );
+               require_noerr( err, exit );
+               sock = kInvalidSocketRef;
                
-               err = StringToSRVRData( str, &dataPtr, &dataLen );
+               err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler,
+                       sockCtx, &me->readSourceV6 );
                require_noerr( err, exit );
+               me->sockV6 = sockCtx->sock;
+               sockCtx = NULL;
+               
+               dispatch_resume( me->readSourceV6 );
        }
        
-       // String with escaped hex and octal bytes
+       _MDNSColliderExecuteProgram( me );
+       err = kNoErr;
        
-       else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_String );
-               const char * const              end = str + strlen( str );
-               size_t                                  copiedLen;
-               size_t                                  totalLen;
-               Boolean                                 success;
+exit:
+       ForgetSocket( &sock );
+       ForgetSocketContext( &sockCtx );
+       if( err ) _MDNSColliderStop( me, err );
+}
+
+//===========================================================================================================================
+//     MDNSColliderStop
+//===========================================================================================================================
+
+static void    _MDNSColliderUserStop( void *inContext );
+
+static void    MDNSColliderStop( MDNSColliderRef me )
+{
+       CFRetain( me );
+       dispatch_async_f( me->queue, me, _MDNSColliderUserStop );
+}
+
+static void    _MDNSColliderUserStop( void *inContext )
+{
+       MDNSColliderRef const           me = (MDNSColliderRef) inContext;
+       
+       _MDNSColliderStop( me, kCanceledErr );
+       CFRelease( me );
+}
+
+//===========================================================================================================================
+//     MDNSColliderSetProtocols
+//===========================================================================================================================
+
+static void    MDNSColliderSetProtocols( MDNSColliderRef me, MDNSColliderProtocols inProtocols )
+{
+       me->protocols = inProtocols;
+}
+
+//===========================================================================================================================
+//     MDNSColliderSetInterfaceIndex
+//===========================================================================================================================
+
+static void    MDNSColliderSetInterfaceIndex( MDNSColliderRef me, uint32_t inInterfaceIndex )
+{
+       me->interfaceIndex = inInterfaceIndex;
+}
+
+//===========================================================================================================================
+//     MDNSColliderSetProgram
+//===========================================================================================================================
+
+#define kMDNSColliderProgCmd_Done              "done"
+#define kMDNSColliderProgCmd_Loop              "loop"
+#define kMDNSColliderProgCmd_Send              "send"
+#define kMDNSColliderProgCmd_Probes            "probes"
+#define kMDNSColliderProgCmd_Wait              "wait"
+
+typedef uint32_t               MDNSColliderProbeAction;
+
+#define kMDNSColliderProbeAction_None                                  0
+#define kMDNSColliderProbeAction_Respond                               1
+#define kMDNSColliderProbeAction_RespondUnicast                        2
+#define kMDNSColliderProbeAction_RespondMulticast              3
+#define kMDNSColliderProbeAction_Probe                                 4
+#define kMDNSColliderProbeAction_MaxValue                              kMDNSColliderProbeAction_Probe
+
+#define kMDNSColliderProbeActionBits_Count                     3
+#define kMDNSColliderProbeActionBits_Mask                      ( ( 1U << kMDNSColliderProbeActionBits_Count ) - 1 )
+#define kMDNSColliderProbeActionMaxProbeCount          ( 32 / kMDNSColliderProbeActionBits_Count )
+
+check_compile_time( kMDNSColliderProbeAction_MaxValue <= kMDNSColliderProbeActionBits_Mask );
+
+static OSStatus        _MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap );
+
+static OSStatus        MDNSColliderSetProgram( MDNSColliderRef me, const char *inProgramStr )
+{
+       OSStatus                                err;
+       uint32_t                                insCount;
+       unsigned int                    loopDepth;
+       const char *                    cmd;
+       const char *                    end;
+       const char *                    next;
+       MDNSCInstruction *              program = NULL;
+       uint32_t                                loopStart[ kMaxLoopDepth ];
+       
+       insCount = 0;
+       for( cmd = inProgramStr; *cmd; cmd = next )
+       {
+               for( end = cmd; *end && ( *end != ';' ); ++end ) {}
+               require_action_quiet( end != cmd, exit, err = kMalformedErr );
+               next = ( *end == ';' ) ? ( end + 1 ) : end;
+               ++insCount;
+       }
+       
+       program = (MDNSCInstruction *) calloc( insCount + 1, sizeof( *program ) );
+       require_action( program, exit, err = kNoMemoryErr );
+       
+       insCount        = 0;
+       loopDepth       = 0;
+       for( cmd = inProgramStr; *cmd; cmd = next )
+       {
+               size_t                                                  cmdLen;
+               const char *                                    ptr;
+               const char *                                    arg;
+               size_t                                                  argLen;
+               uint32_t                                                value;
+               MDNSCInstruction * const                ins = &program[ insCount ];
                
-               if( str < end )
+               while( isspace_safe( *cmd ) ) ++cmd;
+               for( end = cmd; *end && ( *end != ';' ); ++end ) {}
+               next = ( *end == ';' ) ? ( end + 1 ) : end;
+               
+               for( ptr = cmd; ( ptr < end ) && !isspace_safe( *ptr ); ++ptr ) {}
+               cmdLen = (size_t)( ptr - cmd );
+               
+               // Done statement
+               
+               if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Done ) == 0 )
                {
-                       success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
-                       require_action( success, exit, err = kParamErr );
-                       require_action( totalLen <= kRDataMaxLen, exit, err = kSizeErr );
+                       while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+                       require_action_quiet( ptr == end, exit, err = kMalformedErr );
                        
-                       dataLen = totalLen;
-                       dataPtr = (uint8_t *) malloc( dataLen );
-                       require_action( dataPtr, exit, err = kNoMemoryErr );
+                       require_action_quiet( loopDepth > 0, exit, err = kMalformedErr );
                        
-                       success = ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL );
-                       require_action( success, exit, err = kParamErr );
-                       check( copiedLen == dataLen );
+                       ins->opcode             = kMDNSColliderOpCode_LoopPop;
+                       ins->operand    = loopStart[ --loopDepth ];
                }
-               else
+               
+               // Loop command
+               
+               else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Loop ) == 0 )
                {
-                       dataPtr = NULL;
-                       dataLen = 0;
+                       for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+                       err = DecimalTextToUInt32( arg, end, &value, &ptr );
+                       require_noerr_quiet( err, exit );
+                       require_action_quiet( value > 0, exit, err = kValueErr );
+                       
+                       while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+                       require_action_quiet( ptr == end, exit, err = kMalformedErr );
+                       
+                       ins->opcode     = kMDNSColliderOpCode_LoopPush;
+                       ins->operand    = value;
+                       
+                       require_action_quiet( loopDepth < kMaxLoopDepth, exit, err = kNoSpaceErr );
+                       loopStart[ loopDepth++ ] = insCount + 1;
                }
-       }
-       
-       // TXT record
-       
-       else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
-       {
-               const char * const              str = inString + sizeof_string( kRDataArgPrefix_TXT );
                
-               err = StringToTXTRData( str, ',', &dataPtr, &dataLen );
-               require_noerr( err, exit );
-       }
-       
-       // Unrecognized format
-       
-       else
-       {
-               FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
-               err = kParamErr;
-               goto exit;
-       }
-       
-       err = kNoErr;
-       *outDataLen = dataLen;
-       *outDataPtr = dataPtr;
-       dataPtr = NULL;
-       
-exit:
-       FreeNullSafe( dataPtr );
-       return( err );
-}
-
-static OSStatus        StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen )
-{
-       OSStatus                        err;
-       DataBuffer                      dataBuf;
-       const char *            ptr;
-       int                                     i;
-       uint8_t *                       end;
-       uint8_t                         target[ kDomainNameLengthMax ];
-       
-       DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax );
-       
-       // Parse and set the priority, weight, and port values (all three are unsigned 16-bit values).
-       
-       ptr = inString;
-       for( i = 0; i < 3; ++i )
-       {
-               char *          next;
-               long            value;
-               uint8_t         buf[ 2 ];
+               // Probes command
                
-               value = strtol( ptr, &next, 0 );
-               require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr );
-               require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr );
-               ptr = next + 1;
+               else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Probes ) == 0 )
+               {
+                       for( arg = ptr; ( arg < end ) &&  isspace_safe( *arg ); ++arg ) {}
+                       for( ptr = arg; ( ptr < end ) && !isspace_safe( *ptr ); ++ptr ) {}
+                       argLen = (size_t)( ptr - arg );
+                       if( argLen > 0 )
+                       {
+                               err = _MDNSColliderParseProbeActionString( arg, argLen, &value );
+                               require_noerr_quiet( err, exit );
+                       }
+                       else
+                       {
+                               value = 0;
+                       }
+                       
+                       while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+                       require_action_quiet( ptr == end, exit, err = kMalformedErr );
+                       
+                       ins->opcode     = kMDNSColliderOpCode_SetProbeActions;
+                       ins->operand    = value;
+               }
                
-               WriteBig16( buf, value );
+               // Send command
                
-               err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) );
-               require_noerr( err, exit );
+               else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Send ) == 0 )
+               {
+                       while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+                       require_action_quiet( ptr == end, exit, err = kMalformedErr );
+                       
+                       ins->opcode = kMDNSColliderOpCode_Send;
+               }
+               
+               // Wait command
+               
+               else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Wait ) == 0 )
+               {
+                       for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+                       err = DecimalTextToUInt32( arg, end, &value, &ptr );
+                       require_noerr_quiet( err, exit );
+                       
+                       while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+                       require_action_quiet( ptr == end, exit, err = kMalformedErr );
+                       
+                       ins->opcode             = kMDNSColliderOpCode_Wait;
+                       ins->operand    = value;
+               }
+               
+               // Unrecognized command
+               
+               else
+               {
+                       err = kCommandErr;
+                       goto exit;
+               }
+               ++insCount;
        }
+       require_action_quiet( loopDepth == 0, exit, err = kMalformedErr );
        
-       // Set the target domain name.
-       
-       err = DomainNameFromString( target, ptr, &end );
-    require_noerr_quiet( err, exit );
-       
-       err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) );
-       require_noerr( err, exit );
+       program[ insCount ].opcode = kMDNSColliderOpCode_Exit;
        
-       err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
-       require_noerr( err, exit );
+       FreeNullSafe( me->program );
+       me->program = program;
+       program = NULL;
+       err = kNoErr;
        
 exit:
-       DataBuffer_Free( &dataBuf );
+       FreeNullSafe( program );
        return( err );
 }
 
-static OSStatus        StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen )
+static OSStatus        _MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap )
 {
-       OSStatus                        err;
-       DataBuffer                      dataBuf;
-       const char *            src;
-       uint8_t                         txtStr[ 256 ];  // Buffer for single TXT string: 1 length byte + up to 255 bytes of data.
-       
-       DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen );
+       OSStatus                                err;
+       const char *                    ptr;
+       const char * const              end = &inString[ inLen ];
+       uint32_t                                bitmap;
+       int                                             index;
        
-       src = inString;
-       for( ;; )
+       bitmap  = 0;
+       index   = 0;
+       ptr             = inString;
+       while( ptr < end )
        {
-               uint8_t *                                       dst = &txtStr[ 1 ];
-               const uint8_t * const           lim = &txtStr[ 256 ];
-               int                                                     c;
+               int                                                     c, count;
+               MDNSColliderProbeAction         action;
                
-               while( *src && ( *src != inDelimiter ) )
+               c = *ptr++;
+               if( isdigit_safe( c ) )
                {
-                       if( ( c = *src++ ) == '\\' )
+                       count = 0;
+                       do
                        {
-                               require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
-                               c = *src++;
-                       }
-                       require_action_quiet( dst < lim, exit, err = kOverrunErr );
-                       *dst++ = (uint8_t) c;
+                               count = ( count * 10 ) + ( c - '0' );
+                               require_action_quiet( count <= ( kMDNSColliderProbeActionMaxProbeCount - index ), exit, err = kCountErr );
+                               require_action_quiet( ptr < end, exit, err = kUnderrunErr );
+                               c = *ptr++;
+                               
+                       }       while( isdigit_safe( c ) );
+                       require_action_quiet( count > 0, exit, err = kCountErr );
+               }
+               else
+               {
+                       require_action_quiet( index < kMDNSColliderProbeActionMaxProbeCount, exit, err = kMalformedErr );
+                       count = 1;
                }
-               txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
-               err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
-               require_noerr( err, exit );
                
-               if( *src == '\0' ) break;
-               ++src;
+               switch( c )
+               {
+                       case 'n':       action = kMDNSColliderProbeAction_None;                         break;
+                       case 'r':       action = kMDNSColliderProbeAction_Respond;                      break;
+                       case 'u':       action = kMDNSColliderProbeAction_RespondUnicast;       break;
+                       case 'm':       action = kMDNSColliderProbeAction_RespondMulticast;     break;
+                       case 'p':       action = kMDNSColliderProbeAction_Probe;                        break;
+                       default:        err = kMalformedErr;                                                            goto exit;
+               }
+               if( ptr < end )
+               {
+                       c = *ptr++;
+                       require_action_quiet( ( c == '-' ) && ( ptr < end ), exit, err = kMalformedErr );
+               }
+               while( count-- > 0 )
+               {
+                       bitmap |= ( action << ( index * kMDNSColliderProbeActionBits_Count ) );
+                       ++index;
+               }
        }
        
-       err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
-       require_noerr( err, exit );
+       *outBitmap = bitmap;
+       err = kNoErr;
        
 exit:
-       DataBuffer_Free( &dataBuf );
        return( err );
 }
 
 //===========================================================================================================================
-//     RecordTypeFromArgString
+//     MDNSColliderSetStopHandler
 //===========================================================================================================================
 
-typedef struct
+static void    MDNSColliderSetStopHandler( MDNSColliderRef me, MDNSColliderStopHandler_f inStopHandler, void *inStopContext )
 {
-       uint16_t                        value;  // Record type's numeric value.
-       const char *            name;   // Record type's name as a string (e.g., "A", "PTR", "SRV").
-       
-}      RecordType;
+       me->stopHandler = inStopHandler;
+       me->stopContext = inStopContext;
+}
 
-static const RecordType                kRecordTypes[] =
+//===========================================================================================================================
+//     MDNSColliderSetRecord
+//===========================================================================================================================
+
+#define kMDNSColliderDummyStr                  "\x16" "mdnscollider-sent-this" kLocalStr
+#define kMDNSColliderDummyName                 ( (const uint8_t *) kMDNSColliderDummyStr )
+#define kMDNSColliderDummyNameLen              sizeof( kMDNSColliderDummyStr )
+
+static OSStatus
+       MDNSColliderSetRecord(
+               MDNSColliderRef me,
+               const uint8_t * inName,
+               uint16_t                inType,
+               const void *    inRDataPtr,
+               size_t                  inRDataLen )
 {
-       // Common types.
+       OSStatus                err;
+       DataBuffer              msgDB;
+       DNSHeader               header;
+       uint8_t *               targetPtr       = NULL;
+       size_t                  targetLen;
+       uint8_t *               responsePtr     = NULL;
+       size_t                  responseLen;
+       uint8_t *               probePtr        = NULL;
+       size_t                  probeLen;
+       
+       DataBuffer_Init( &msgDB, NULL, 0, kMDNSMessageSizeMax );
+       
+       err = DomainNameDup( inName, &targetPtr, &targetLen );
+       require_noerr_quiet( err, exit );
        
-       { kDNSServiceType_A,                    "A" },
-       { kDNSServiceType_AAAA,                 "AAAA" },
-       { kDNSServiceType_PTR,                  "PTR" },
-       { kDNSServiceType_SRV,                  "SRV" },
-       { kDNSServiceType_TXT,                  "TXT" },
-       { kDNSServiceType_CNAME,                "CNAME" },
-       { kDNSServiceType_SOA,                  "SOA" },
-       { kDNSServiceType_NSEC,                 "NSEC" },
-       { kDNSServiceType_NS,                   "NS" },
-       { kDNSServiceType_MX,                   "MX" },
-       { kDNSServiceType_ANY,                  "ANY" },
-       { kDNSServiceType_OPT,                  "OPT" },
+       // Create response message.
        
-       // Less common types.
+       memset( &header, 0, sizeof( header ) );
+       DNSHeaderSetFlags( &header, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer );
+       DNSHeaderSetAnswerCount( &header, 1 );
        
-       { kDNSServiceType_MD,                   "MD" },
-       { kDNSServiceType_NS,                   "NS" },
-       { kDNSServiceType_MD,                   "MD" },
-       { kDNSServiceType_MF,                   "MF" },
-       { kDNSServiceType_MB,                   "MB" },
-       { kDNSServiceType_MG,                   "MG" },
-       { kDNSServiceType_MR,                   "MR" },
-       { kDNSServiceType_NULL,                 "NULL" },
-       { kDNSServiceType_WKS,                  "WKS" },
-       { kDNSServiceType_HINFO,                "HINFO" },
-       { kDNSServiceType_MINFO,                "MINFO" },
-       { kDNSServiceType_RP,                   "RP" },
-       { kDNSServiceType_AFSDB,                "AFSDB" },
-       { kDNSServiceType_X25,                  "X25" },
-       { kDNSServiceType_ISDN,                 "ISDN" },
-       { kDNSServiceType_RT,                   "RT" },
-       { kDNSServiceType_NSAP,                 "NSAP" },
-       { kDNSServiceType_NSAP_PTR,             "NSAP_PTR" },
-       { kDNSServiceType_SIG,                  "SIG" },
-       { kDNSServiceType_KEY,                  "KEY" },
-       { kDNSServiceType_PX,                   "PX" },
-       { kDNSServiceType_GPOS,                 "GPOS" },
-       { kDNSServiceType_LOC,                  "LOC" },
-       { kDNSServiceType_NXT,                  "NXT" },
-       { kDNSServiceType_EID,                  "EID" },
-       { kDNSServiceType_NIMLOC,               "NIMLOC" },
-       { kDNSServiceType_ATMA,                 "ATMA" },
-       { kDNSServiceType_NAPTR,                "NAPTR" },
-       { kDNSServiceType_KX,                   "KX" },
-       { kDNSServiceType_CERT,                 "CERT" },
-       { kDNSServiceType_A6,                   "A6" },
-       { kDNSServiceType_DNAME,                "DNAME" },
-       { kDNSServiceType_SINK,                 "SINK" },
-       { kDNSServiceType_APL,                  "APL" },
-       { kDNSServiceType_DS,                   "DS" },
-       { kDNSServiceType_SSHFP,                "SSHFP" },
-       { kDNSServiceType_IPSECKEY,             "IPSECKEY" },
-       { kDNSServiceType_RRSIG,                "RRSIG" },
-       { kDNSServiceType_DNSKEY,               "DNSKEY" },
-       { kDNSServiceType_DHCID,                "DHCID" },
-       { kDNSServiceType_NSEC3,                "NSEC3" },
-       { kDNSServiceType_NSEC3PARAM,   "NSEC3PARAM" },
-       { kDNSServiceType_HIP,                  "HIP" },
-       { kDNSServiceType_SPF,                  "SPF" },
-       { kDNSServiceType_UINFO,                "UINFO" },
-       { kDNSServiceType_UID,                  "UID" },
-       { kDNSServiceType_GID,                  "GID" },
-       { kDNSServiceType_UNSPEC,               "UNSPEC" },
-       { kDNSServiceType_TKEY,                 "TKEY" },
-       { kDNSServiceType_TSIG,                 "TSIG" },
-       { kDNSServiceType_IXFR,                 "IXFR" },
-       { kDNSServiceType_AXFR,                 "AXFR" },
-       { kDNSServiceType_MAILB,                "MAILB" },
-       { kDNSServiceType_MAILA,                "MAILA" }
-};
-
-static OSStatus        RecordTypeFromArgString( const char *inString, uint16_t *outValue )
-{
-       OSStatus                                                err;
-       int32_t                                                 i32;
-       const RecordType *                              type;
-       const RecordType * const                end = kRecordTypes + countof( kRecordTypes );
+       err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
+       require_noerr( err, exit );
        
-       for( type = kRecordTypes; type < end; ++type )
-       {
-               if( strcasecmp( type->name, inString ) == 0 )
-               {
-                       *outValue = type->value;
-                       return( kNoErr );
-               }
-       }
+       err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN | kRRClassCacheFlushBit,
+               1976, inRDataPtr, inRDataLen );
+       require_noerr( err, exit );
        
-       err = StringToInt32( inString, &i32 );
-       require_noerr_quiet( err, exit );
-       require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+       err = DataBuffer_Detach( &msgDB, &responsePtr, &responseLen );
+       require_noerr( err, exit );
        
-       *outValue = (uint16_t) i32;
+       // Create probe message.
+       
+       memset( &header, 0, sizeof( header ) );
+       DNSHeaderSetQuestionCount( &header, 2 );
+       DNSHeaderSetAuthorityCount( &header, 1 );
+       
+       err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
+       require_noerr( err, exit );
+       
+       err = _DataBuffer_AppendDNSQuestion( &msgDB, targetPtr, targetLen, kDNSServiceType_ANY, kDNSServiceClass_IN );
+       require_noerr( err, exit );
+       
+       err = _DataBuffer_AppendDNSQuestion( &msgDB, kMDNSColliderDummyName, kMDNSColliderDummyNameLen,
+               kDNSServiceType_NULL, kDNSServiceClass_IN );
+       require_noerr( err, exit );
+       
+       err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN,
+               1976, inRDataPtr, inRDataLen );
+       require_noerr( err, exit );
+       
+       err = DataBuffer_Detach( &msgDB, &probePtr, &probeLen );
+       require_noerr( err, exit );
+       
+       FreeNullSafe( me->target );
+       me->target = targetPtr;
+       targetPtr = NULL;
+       
+       FreeNullSafe( me->responsePtr );
+       me->responsePtr = responsePtr;
+       me->responseLen = responseLen;
+       responsePtr = NULL;
+       
+       FreeNullSafe( me->probePtr );
+       me->probePtr = probePtr;
+       me->probeLen = probeLen;
+       probePtr = NULL;
        
 exit:
+       DataBuffer_Free( &msgDB );
+       FreeNullSafe( targetPtr );
+       FreeNullSafe( responsePtr );
+       FreeNullSafe( probePtr );
        return( err );
 }
 
 //===========================================================================================================================
-//     RecordClassFromArgString
+//     _MDNSColliderStop
 //===========================================================================================================================
 
-static OSStatus        RecordClassFromArgString( const char *inString, uint16_t *outValue )
+static void    _MDNSColliderStop( MDNSColliderRef me, OSStatus inError )
 {
-       OSStatus                err;
-       int32_t                 i32;
+       dispatch_source_forget( &me->waitTimer );
+       dispatch_source_forget( &me->readSourceV4 );
+       dispatch_source_forget( &me->readSourceV6 );
+       me->sockV4 = kInvalidSocketRef;
+       me->sockV6 = kInvalidSocketRef;
        
-       if( strcasecmp( inString, "IN" ) == 0 )
+       if( !me->stopped )
        {
-               *outValue = kDNSServiceClass_IN;
-               err = kNoErr;
-               goto exit;
+               me->stopped = true;
+               if( me->stopHandler ) me->stopHandler( me->stopContext, inError );
+               CFRelease( me );
        }
-       
-       err = StringToInt32( inString, &i32 );
-       require_noerr_quiet( err, exit );
-       require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
-       
-       *outValue = (uint16_t) i32;
-       
-exit:
-       return( err );
 }
 
 //===========================================================================================================================
-//     InterfaceIndexToName
+//     _MDNSColliderReadHandler
 //===========================================================================================================================
 
-static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
+static MDNSColliderProbeAction _MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber );
+static const char *                            _MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction );
+
+static void    _MDNSColliderReadHandler( void *inContext )
 {
-       switch( inIfIndex )
+       OSStatus                                        err;
+       struct timeval                          now;
+       SocketContext * const           sockCtx = (SocketContext *) inContext;
+       MDNSColliderRef const           me              = (MDNSColliderRef) sockCtx->userContext;
+       size_t                                          msgLen;
+       sockaddr_ip                                     sender;
+       const DNSHeader *                       hdr;
+       const uint8_t *                         ptr;
+       const struct sockaddr *         dest;
+       int                                                     probeFound, probeIsQU;
+       unsigned int                            qCount, i;
+       MDNSColliderProbeAction         action;
+       
+       gettimeofday( &now, NULL );
+       
+       err = SocketRecvFrom( sockCtx->sock, me->msgBuf, sizeof( me->msgBuf ), &msgLen, &sender, sizeof( sender ),
+               NULL, NULL, NULL, NULL );
+       require_noerr( err, exit );
+       
+       require_quiet( msgLen >= kDNSHeaderLength, exit );
+       hdr = (const DNSHeader *) me->msgBuf;
+       
+       probeFound      = false;
+       probeIsQU       = false;
+       qCount = DNSHeaderGetQuestionCount( hdr );
+       ptr = (const uint8_t *) &hdr[ 1 ];
+       for( i = 0; i < qCount; ++i )
        {
-               case kDNSServiceInterfaceIndexAny:
-                       strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
-                       break;
-               
-               case kDNSServiceInterfaceIndexLocalOnly:
-                       strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
-                       break;
+               uint16_t                qtype, qclass;
+               uint8_t                 qname[ kDomainNameLengthMax ];
                
-               case kDNSServiceInterfaceIndexUnicast:
-                       strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
-                       break;
+               err = DNSMessageExtractQuestion( me->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
+               require_noerr_quiet( err, exit );
                
-               case kDNSServiceInterfaceIndexP2P:
-                       strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
+               if( ( qtype == kDNSServiceType_NULL ) && ( qclass == kDNSServiceClass_IN ) &&
+                       DomainNameEqual( qname, kMDNSColliderDummyName ) )
+               {
+                       probeFound = false;
                        break;
+               }
                
-       #if( defined( kDNSServiceInterfaceIndexBLE ) )
-               case kDNSServiceInterfaceIndexBLE:
-                       strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
-                       break;
-       #endif
+               if( qtype != kDNSServiceType_ANY ) continue;
+               if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+               if( !DomainNameEqual( qname, me->target ) ) continue;
                
-               default:
+               if( !probeFound )
                {
-                       const char *            name;
-                       
-                       name = if_indextoname( inIfIndex, inNameBuf );
-                       if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
-                       break;
+                       probeFound      = true;
+                       probeIsQU       = ( qclass & kQClassUnicastResponseBit ) ? true : false;
                }
        }
+       require_quiet( probeFound, exit );
        
-       return( inNameBuf );
+       ++me->probeCount;
+       action = _MDNSColliderGetProbeAction( me->probeActionMap, me->probeCount );
+       
+       mc_ulog( kLogLevelInfo, "Received probe from %##a at %{du:time} (action: %s):\n\n%#1{du:dnsmsg}",
+               &sender, &now, _MDNSColliderProbeActionToString( action ), me->msgBuf, msgLen );
+       
+       if( ( action == kMDNSColliderProbeAction_Respond )                      ||
+               ( action == kMDNSColliderProbeAction_RespondUnicast )   ||
+               ( action == kMDNSColliderProbeAction_RespondMulticast ) )
+       {
+               if( ( ( action == kMDNSColliderProbeAction_Respond ) && probeIsQU ) ||
+                       (   action == kMDNSColliderProbeAction_RespondUnicast ) )
+               {
+                       dest = &sender.sa;
+               }
+               else if( ( ( action == kMDNSColliderProbeAction_Respond ) && !probeIsQU ) ||
+                                (   action == kMDNSColliderProbeAction_RespondMulticast ) )
+               {
+                       dest = ( sender.sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+               }
+               
+               err = _MDNSColliderSendResponse( me, sockCtx->sock, dest );
+               require_noerr( err, exit );
+       }
+       else if( action == kMDNSColliderProbeAction_Probe )
+       {
+               dest = ( sender.sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+               
+               err = _MDNSColliderSendProbe( me, sockCtx->sock, dest );
+               require_noerr( err, exit );
+       }
+       
+exit:
+       return;
 }
 
-//===========================================================================================================================
-//     RecordTypeToString
-//===========================================================================================================================
+static MDNSColliderProbeAction _MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber )
+{
+       MDNSColliderProbeAction         action;
+       
+       if( ( inProbeNumber >= 1 ) && ( inProbeNumber <= kMDNSColliderProbeActionMaxProbeCount ) )
+       {
+               action = ( inBitmap >> ( ( inProbeNumber - 1 ) * kMDNSColliderProbeActionBits_Count ) ) &
+                       kMDNSColliderProbeActionBits_Mask;
+       }
+       else
+       {
+               action = kMDNSColliderProbeAction_None;
+       }
+       return( action );
+}
 
-static const char *    RecordTypeToString( unsigned int inValue )
+static const char *    _MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction )
 {
-       const RecordType *                              type;
-       const RecordType * const                end = kRecordTypes + countof( kRecordTypes );
-       
-       for( type = kRecordTypes; type < end; ++type )
+       switch( inAction )
        {
-               if( type->value == inValue ) return( type->name );
+               case kMDNSColliderProbeAction_None:                             return( "None" );
+               case kMDNSColliderProbeAction_Respond:                  return( "Respond" );
+               case kMDNSColliderProbeAction_RespondUnicast:   return( "Respond (unicast)" );
+               case kMDNSColliderProbeAction_RespondMulticast: return( "Respond (multicast)" );
+               case kMDNSColliderProbeAction_Probe:                    return( "Probe" );
+               default:                                                                                return( "???" );
        }
-       return( "???" );
 }
 
 //===========================================================================================================================
-//     DNSMessageExtractDomainName
+//     _MDNSColliderExecuteProgram
 //===========================================================================================================================
 
-#define IsCompressionByte( X )         ( ( ( X ) & 0xC0 ) == 0xC0 )
-
-static OSStatus
-       DNSMessageExtractDomainName(
-               const uint8_t *         inMsgPtr,
-               size_t                          inMsgLen,
-               const uint8_t *         inNamePtr,
-               uint8_t                         inBuf[ kDomainNameLengthMax ],
-               const uint8_t **        outNextPtr )
+static void    _MDNSColliderExecuteProgram( void *inContext )
 {
        OSStatus                                        err;
-       const uint8_t *                         label;
-       uint8_t                                         labelLen;
-       const uint8_t *                         nextLabel;
-       const uint8_t * const           msgEnd  = inMsgPtr + inMsgLen;
-       uint8_t *                                       dst             = inBuf;
-       const uint8_t * const           dstLim  = inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL;
-       const uint8_t *                         nameEnd = NULL;
+       MDNSColliderRef const           me = (MDNSColliderRef) inContext;
+       int                                                     stop;
        
-       require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
+       dispatch_forget( &me->waitTimer );
        
-       for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+       stop = false;
+       for( ;; )
        {
-               if( labelLen <= kDomainLabelLengthMax )
-               {
-                       nextLabel = label + 1 + labelLen;
-                       require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
-                       if( dst )
-                       {
-                               require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr );
-                               memcpy( dst, label, 1 + labelLen );
-                               dst += ( 1 + labelLen );
-                       }
-               }
-               else if( IsCompressionByte( labelLen ) )
+               const MDNSCInstruction * const          ins = &me->program[ me->pc++ ];
+               uint32_t                                                        waitMs;
+               
+               switch( ins->opcode )
                {
-                       uint16_t                offset;
+                       case kMDNSColliderOpCode_Send:
+                               if( IsValidSocket( me->sockV4 ) )
+                               {
+                                       err = _MDNSColliderSendResponse( me, me->sockV4, GetMDNSMulticastAddrV4() );
+                                       require_noerr( err, exit );
+                               }
+                               if( IsValidSocket( me->sockV6 ) )
+                               {
+                                       err = _MDNSColliderSendResponse( me, me->sockV6, GetMDNSMulticastAddrV6() );
+                                       require_noerr( err, exit );
+                               }
+                               break;
                        
-                       require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr );
-                       if( !nameEnd )
-                       {
-                               nameEnd = label + 2;
-                               if( !dst ) break;
-                       }
-                       offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] );
-                       nextLabel = inMsgPtr + offset;
-                       require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
-                       require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr );
-               }
-               else
-               {
-                       dlogassert( "Unhandled label length 0x%02X\n", labelLen );
-                       err = kMalformedErr;
-                       goto exit;
+                       case kMDNSColliderOpCode_Wait:
+                               waitMs = ins->operand;
+                               if( waitMs > 0 )
+                               {
+                                       err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( waitMs ), 1, me->queue,
+                                               _MDNSColliderExecuteProgram, me, &me->waitTimer );
+                                       require_noerr( err, exit );
+                                       dispatch_resume( me->waitTimer );
+                                       goto exit;
+                               }
+                               break;
+                       
+                       case kMDNSColliderOpCode_SetProbeActions:
+                               me->probeCount          = 0;
+                               me->probeActionMap      = ins->operand;
+                               break;
+                       
+                       case kMDNSColliderOpCode_LoopPush:
+                               check( me->loopDepth < kMaxLoopDepth );
+                               me->loopCounts[ me->loopDepth++ ] = ins->operand;
+                               break;
+                       
+                       case kMDNSColliderOpCode_LoopPop:
+                               check( me->loopDepth > 0 );
+                               if( --me->loopCounts[ me->loopDepth - 1 ] > 0 )
+                               {
+                                       me->pc = ins->operand;
+                               }
+                               else
+                               {
+                                       --me->loopDepth;
+                               }
+                               break;
+                       
+                       case kMDNSColliderOpCode_Exit:
+                               stop = true;
+                               err     = kNoErr;
+                               goto exit;
+                       
+                       default:
+                               dlogassert( "Unhandled opcode %u\n", ins->opcode );
+                               err = kCommandErr;
+                               goto exit;
                }
        }
        
-       if( dst ) *dst = 0;
-       if( !nameEnd ) nameEnd = label + 1;
-       
-       if( outNextPtr ) *outNextPtr = nameEnd;
-       err = kNoErr;
-       
 exit:
-       return( err );
+       if( err || stop ) _MDNSColliderStop( me, err );
 }
 
 //===========================================================================================================================
-//     DNSMessageExtractDomainNameString
+//     _MDNSColliderSendResponse
 //===========================================================================================================================
 
-static OSStatus
-       DNSMessageExtractDomainNameString(
-               const void *            inMsgPtr,
-               size_t                          inMsgLen,
-               const void *            inNamePtr,
-               char                            inBuf[ kDNSServiceMaxDomainName ],
-               const uint8_t **        outNextPtr )
+static OSStatus        _MDNSColliderSendResponse( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest )
 {
-       OSStatus                        err;
-       const uint8_t *         nextPtr;
-       uint8_t                         domainName[ kDomainNameLengthMax ];
-       
-       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr );
-       require_noerr( err, exit );
-       
-       err = DomainNameToString( domainName, NULL, inBuf, NULL );
-       require_noerr( err, exit );
+       OSStatus                err;
+       ssize_t                 n;
        
-       if( outNextPtr ) *outNextPtr = nextPtr;
+       n = sendto( inSock, (char *) me->responsePtr, me->responseLen, 0, inDest, SockAddrGetSize( inDest ) );
+       err = map_socket_value_errno( inSock, n == (ssize_t) me->responseLen, n );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _MDNSColliderSendProbe
+//===========================================================================================================================
+
+static OSStatus        _MDNSColliderSendProbe( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest )
+{
+       OSStatus                err;
+       ssize_t                 n;
        
-exit:
+       n = sendto( inSock, (char *) me->probePtr, me->probeLen, 0, inDest, SockAddrGetSize( inDest ) );
+       err = map_socket_value_errno( inSock, n == (ssize_t) me->probeLen, n );
        return( err );
 }
 
 //===========================================================================================================================
-//     DNSMessageExtractRecord
+//     ServiceBrowserCreate
 //===========================================================================================================================
 
+typedef struct SBDomain                                        SBDomain;
+typedef struct SBServiceType                   SBServiceType;
+typedef struct SBServiceBrowse                 SBServiceBrowse;
+typedef struct SBServiceInstance               SBServiceInstance;
+typedef struct SBIPAddress                             SBIPAddress;
+
+struct ServiceBrowserPrivate
+{
+       CFRuntimeBase                                   base;                           // CF object base.
+       dispatch_queue_t                                queue;                          // Queue for service browser's events.
+       DNSServiceRef                                   connection;                     // Shared connection for DNS-SD ops.
+       DNSServiceRef                                   domainsQuery;           // Query for recommended browsing domains.
+       char *                                                  domain;                         // If non-null, then browsing is limited to this domain.
+       StringListItem *                                serviceTypeList;        // If non-null, then browsing is limited to these service types.
+       ServiceBrowserCallback_f                userCallback;           // User's callback. Called when browsing stops.
+       void *                                                  userContext;            // User's callback context.
+       SBDomain *                                              domainList;                     // List of domains and their browse results.
+       dispatch_source_t                               stopTimer;                      // Timer to stop browsing after browseTimeSecs.
+       uint32_t                                                ifIndex;                        // If non-zero, then browsing is limited to this interface.
+       unsigned int                                    browseTimeSecs;         // Amount of time to spend browsing in seconds.
+       Boolean                                                 includeAWDL;            // True if the IncludeAWDL flag should be used for DNS-SD ops that
+                                                                                                               // use the "any" interface.
+};
+
+struct SBDomain
+{
+       SBDomain *                              next;                   // Next domain object in list.
+       ServiceBrowserRef               browser;                // Pointer to parent service browser.
+       char *                                  name;                   // Name of the domain.
+       DNSServiceRef                   servicesQuery;  // Query for services (_services._dns-sd._udp.<domain> PTR record) in domain.
+       SBServiceType *                 typeList;               // List of service types to browse for in this domain.
+};
+
+struct SBServiceType
+{
+       SBServiceType *                 next;           // Next service type object in list.
+       char *                                  name;           // Name of the service type.
+       SBServiceBrowse *               browseList;     // List of browses for this service type.
+};
+
+struct SBServiceBrowse
+{
+       SBServiceBrowse *               next;                   // Next browse object in list.
+       ServiceBrowserRef               browser;                // Pointer to parent service browser.
+       DNSServiceRef                   browse;                 // Reference to DNSServiceBrowse op.
+       SBServiceInstance *             instanceList;   // List of service instances that were discovered by this browse.
+       uint64_t                                startTicks;             // Value of UpTicks() when the browse op began.
+       uint32_t                                ifIndex;                // If non-zero, then the browse is limited to this interface.
+};
+
+struct SBServiceInstance
+{
+       SBServiceInstance *             next;                           // Next service instance object in list.
+       ServiceBrowserRef               browser;                        // Pointer to parent service browser.
+       char *                                  name;                           // Name of the service instance.
+       uint32_t                                ifIndex;                        // Index of interface over which this service instance was discovered.
+       uint64_t                                discoverTimeUs;         // Time it took to discover this service instance in microseconds.
+       DNSServiceRef                   resolve;                        // Reference to DNSServiceResolve op for this service instance.
+       uint64_t                                resolveStartTicks;      // Value of UpTicks() when the DNSServiceResolve op began.
+       uint64_t                                resolveTimeUs;          // Time it took to resolve this service instance.
+       char *                                  hostname;                       // Service instance's hostname. Result of DNSServiceResolve.
+       uint16_t                                port;                           // Service instance's port number. Result of DNSServiceResolve.
+       uint8_t *                               txtPtr;                         // Service instance's TXT record data. Result of DNSServiceResolve.
+       size_t                                  txtLen;                         // Length of service instance's TXT record data.
+       DNSServiceRef                   getAddrInfo;            // Reference to DNSServiceGetAddrInfo op for service instance's hostname.
+       uint64_t                                gaiStartTicks;          // Value of UpTicks() when the DNSServiceGetAddrInfo op began.
+       SBIPAddress *                   ipaddrList;                     // List of IP addresses that the hostname resolved to.
+};
+
+struct SBIPAddress
+{
+       SBIPAddress *           next;                   // Next IP address object in list.
+       sockaddr_ip                     sip;                    // IPv4 or IPv6 address.
+       uint64_t                        resolveTimeUs;  // Time it took to resolve this IP address in microseconds.
+};
+
 typedef struct
 {
-       uint8_t         type[ 2 ];
-       uint8_t         class[ 2 ];
-       uint8_t         ttl[ 4 ];
-       uint8_t         rdLength[ 2 ];
-       uint8_t         rdata[ 1 ];
+       SBRDomain *             domainList;     // List of domains in which services were found.
+       int32_t                 refCount;       // This object's reference count.
        
-}      DNSRecordFields;
+}      ServiceBrowserResultsPrivate;
 
-check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
+static void            _ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError );
+static OSStatus        _ServiceBrowserAddDomain( ServiceBrowserRef inBrowser, const char *inDomain );
+static OSStatus        _ServiceBrowserRemoveDomain( ServiceBrowserRef inBrowser, const char *inName );
+static void            _ServiceBrowserTimerHandler( void *inContext );
+static void DNSSD_API
+       _ServiceBrowserDomainsQueryCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void DNSSD_API
+       _ServiceBrowserServicesQueryCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static void DNSSD_API
+       _ServiceBrowserBrowseCallback(
+               DNSServiceRef           inSDRef,
+               DNSServiceFlags         inFlags,
+               uint32_t                        inInterfaceIndex,
+               DNSServiceErrorType     inError,
+               const char *            inName,
+               const char *            inRegType,
+               const char *            inDomain,
+               void *                          inContext );
+static void DNSSD_API
+       _ServiceBrowserResolveCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               const char *                    inHostname,
+               uint16_t                                inPort,
+               uint16_t                                inTXTLen,
+               const unsigned char *   inTXTPtr,
+               void *                                  inContext );
+static void DNSSD_API
+       _ServiceBrowserGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext );
+static OSStatus
+       _ServiceBrowserAddServiceType(
+               ServiceBrowserRef       inBrowser,
+               SBDomain *                      inDomain,
+               const char *            inName,
+               uint32_t                        inIfIndex );
+static OSStatus
+       _ServiceBrowserRemoveServiceType(
+               ServiceBrowserRef       inBrowser,
+               SBDomain *                      inDomain,
+               const char *            inName,
+               uint32_t                        inIfIndex );
+static OSStatus
+       _ServiceBrowserAddServiceInstance(
+               ServiceBrowserRef       inBrowser,
+               SBServiceBrowse *       inBrowse,
+               uint32_t                        inIfIndex,
+               const char *            inName,
+               const char *            inRegType,
+               const char *            inDomain,
+               uint64_t                        inDiscoverTimeUs );
+static OSStatus
+       _ServiceBrowserRemoveServiceInstance(
+               ServiceBrowserRef       inBrowser,
+               SBServiceBrowse *       inBrowse,
+               const char *            inName,
+               uint32_t                        inIfIndex );
+static OSStatus
+       _ServiceBrowserAddIPAddress(
+               ServiceBrowserRef               inBrowser,
+               SBServiceInstance *             inInstance,
+               const struct sockaddr * inSockAddr,
+               uint64_t                                inResolveTimeUs );
+static OSStatus
+       _ServiceBrowserRemoveIPAddress(
+               ServiceBrowserRef               inBrowser,
+               SBServiceInstance *             inInstance,
+               const struct sockaddr * inSockAddr );
+static OSStatus        _ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults );
+static OSStatus        _SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain );
+static void            _SBDomainFree( SBDomain *inDomain );
+static OSStatus        _SBServiceTypeCreate( const char *inName, SBServiceType **outType );
+static void            _SBServiceTypeFree( SBServiceType *inType );
+static OSStatus        _SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse );
+static void            _SBServiceBrowseFree( SBServiceBrowse *inBrowse );
+static OSStatus
+       _SBServiceInstanceCreate(
+               const char *                    inName,
+               uint32_t                                inIfIndex,
+               uint64_t                                inDiscoverTimeUs,
+               ServiceBrowserRef               inBrowser,
+               SBServiceInstance **    outInstance );
+static void            _SBServiceInstanceFree( SBServiceInstance *inInstance );
+static OSStatus
+       _SBIPAddressCreate(
+               const struct sockaddr * inSockAddr,
+               uint64_t                                inResolveTimeUs,
+               SBIPAddress **                  outIPAddress );
+static void            _SBIPAddressFree( SBIPAddress *inIPAddress );
+static void            _SBIPAddressFreeList( SBIPAddress *inList );
+static OSStatus        _SBRDomainCreate( const char *inName, SBRDomain **outDomain );
+static void            _SBRDomainFree( SBRDomain *inDomain );
+static OSStatus        _SBRServiceTypeCreate( const char *inName, SBRServiceType **outType );
+static void            _SBRServiceTypeFree( SBRServiceType *inType );
+static OSStatus
+       _SBRServiceInstanceCreate(
+               const char *                    inName,
+               uint32_t                                inInterfaceIndex,
+               const char *                    inHostname,
+               uint16_t                                inPort,
+               const uint8_t *                 inTXTPtr,
+               size_t                                  inTXTLen,
+               uint64_t                                inDiscoverTimeUs,
+               uint64_t                                inResolveTimeUs,
+               SBRServiceInstance **   outInstance );
+static void            _SBRServiceInstanceFree( SBRServiceInstance *inInstance );
+static OSStatus
+       _SBRIPAddressCreate(
+               const struct sockaddr * inSockAddr,
+               uint64_t                                inResolveTimeUs,
+               SBRIPAddress **                 outIPAddress );
+static void            _SBRIPAddressFree( SBRIPAddress *inIPAddress );
+
+#define ForgetSBIPAddressList( X )             ForgetCustom( X, _SBIPAddressFreeList )
+
+CF_CLASS_DEFINE( ServiceBrowser );
 
 static OSStatus
-       DNSMessageExtractRecord(
-               const uint8_t *         inMsgPtr,
-               size_t                          inMsgLen,
-               const uint8_t *         inPtr,
-               uint8_t                         inNameBuf[ kDomainNameLengthMax ],
-               uint16_t *                      outType,
-               uint16_t *                      outClass,
-               uint32_t *                      outTTL,
-               const uint8_t **        outRDataPtr,
-               size_t *                        outRDataLen,
-               const uint8_t **        outPtr )
+       ServiceBrowserCreate(
+               dispatch_queue_t        inQueue,
+               uint32_t                        inInterfaceIndex,
+               const char *            inDomain,
+               unsigned int            inBrowseTimeSecs,
+               Boolean                         inIncludeAWDL,
+               ServiceBrowserRef *     outBrowser )
 {
-       OSStatus                                        err;
-       const uint8_t * const           msgEnd = inMsgPtr + inMsgLen;
-       const uint8_t *                         ptr;
-       const DNSRecordFields *         record;
-       size_t                                          rdLength;
+       OSStatus                                err;
+       ServiceBrowserRef               obj;
        
-       err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
-       require_noerr_quiet( err, exit );
-       require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr );
+       CF_OBJECT_CREATE( ServiceBrowser, obj, err, exit );
        
-       record = (DNSRecordFields *) ptr;
-       rdLength = ReadBig16( record->rdLength );
-       require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
+       ReplaceDispatchQueue( &obj->queue, inQueue );
+       obj->ifIndex            = inInterfaceIndex;
+       if( inDomain )
+       {
+               obj->domain = strdup( inDomain );
+               require_action( obj->domain, exit, err = kNoMemoryErr );
+       }
+       obj->browseTimeSecs     = inBrowseTimeSecs;
+       obj->includeAWDL        = inIncludeAWDL;
        
-       if( outType )           *outType                = ReadBig16( record->type );
-       if( outClass )          *outClass               = ReadBig16( record->class );
-       if( outTTL )            *outTTL                 = ReadBig32( record->ttl );
-       if( outRDataPtr )       *outRDataPtr    = record->rdata;
-       if( outRDataLen )       *outRDataLen    = rdLength;
-       if( outPtr )            *outPtr                 = record->rdata + rdLength;
+       *outBrowser = obj;
+       obj = NULL;
+       err = kNoErr;
        
 exit:
+       CFReleaseNullSafe( obj );
        return( err );
 }
 
 //===========================================================================================================================
-//     DNSMessageGetAnswerSection
+//     _ServiceBrowserFinalize
 //===========================================================================================================================
 
-static OSStatus        DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
+static void    _ServiceBrowserFinalize( CFTypeRef inObj )
+{
+       ServiceBrowserRef const         me = (ServiceBrowserRef) inObj;
+       StringListItem *                        serviceType;
+       
+       dispatch_forget( &me->queue );
+       check( !me->connection );
+       check( !me->domainsQuery );
+       ForgetMem( &me->domain );
+       while( ( serviceType = me->serviceTypeList ) != NULL )
+       {
+               me->serviceTypeList = serviceType->next;
+               ForgetMem( &serviceType->str );
+               free( serviceType );
+       }
+       check( !me->domainList );
+       check( !me->stopTimer );
+}
+
+//===========================================================================================================================
+//     ServiceBrowserStart
+//===========================================================================================================================
+
+static void    _ServiceBrowserStart( void *inContext );
+
+static void    ServiceBrowserStart( ServiceBrowserRef me )
+{
+       CFRetain( me );
+       dispatch_async_f( me->queue, me, _ServiceBrowserStart );
+}
+
+static void    _ServiceBrowserStart( void *inContext )
 {
        OSStatus                                        err;
-       const uint8_t * const           msgEnd  = inMsgPtr + inMsgLen;
-       unsigned int                            questionCount, i;
-       const DNSHeader *                       hdr;
-       const uint8_t *                         ptr;
+       ServiceBrowserRef const         me = (ServiceBrowserRef) inContext;
        
-       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+       err = DNSServiceCreateConnection( &me->connection );
+       require_noerr( err, exit );
        
-       hdr = (DNSHeader *) inMsgPtr;
-       questionCount = DNSHeaderGetQuestionCount( hdr );
+       err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+       require_noerr( err, exit );
        
-       ptr = (uint8_t *)( hdr + 1 );
-       for( i = 0; i < questionCount; ++i )
+       if( me->domain )
+       {
+               err = _ServiceBrowserAddDomain( me, me->domain );
+               require_noerr( err, exit );
+       }
+       else
        {
-               err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr );
+               DNSServiceRef           sdRef;
+               
+               sdRef = me->connection;
+               err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
+                       "b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, _ServiceBrowserDomainsQueryCallback, me );
                require_noerr( err, exit );
-               require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr );
-               ptr += 4;
+               
+               me->domainsQuery = sdRef;
        }
        
-       if( outPtr ) *outPtr = ptr;
-       err = kNoErr;
+       err = DispatchTimerCreate( dispatch_time_seconds( me->browseTimeSecs ), DISPATCH_TIME_FOREVER,
+               100 * kNanosecondsPerMillisecond, me->queue, _ServiceBrowserTimerHandler, NULL, me, &me->stopTimer );
+       require_noerr( err, exit );
+       dispatch_resume( me->stopTimer );
        
 exit:
-       return( err );
+       if( err ) _ServiceBrowserStop( me, err );
 }
 
 //===========================================================================================================================
-//     DNSRecordDataToString
+//     ServiceBrowserAddServiceType
 //===========================================================================================================================
 
-static OSStatus
-       DNSRecordDataToString(
-               const void *    inRDataPtr,
-               size_t                  inRDataLen,
-               unsigned int    inRDataType,
-               const void *    inMsgPtr,
-               size_t                  inMsgLen,
-               char **                 outString )
+static OSStatus        ServiceBrowserAddServiceType( ServiceBrowserRef me, const char *inServiceType )
 {
-       OSStatus                                        err;
-       const uint8_t * const           rdataPtr = (uint8_t *) inRDataPtr;
-       const uint8_t * const           rdataEnd = rdataPtr + inRDataLen;
-       char *                                          rdataStr;
-       const uint8_t *                         ptr;
-       int                                                     n;
-       char                                            domainNameStr[ kDNSServiceMaxDomainName ];
+       OSStatus                                err;
+       StringListItem *                item;
+       StringListItem **               itemPtr;
+       StringListItem *                newItem = NULL;
        
-       rdataStr = NULL;
-       if( inRDataType == kDNSServiceType_A )
+       for( itemPtr = &me->serviceTypeList; ( item = *itemPtr ) != NULL; itemPtr = &item->next )
        {
-               require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
-               
-               ASPrintF( &rdataStr, "%.4a", rdataPtr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
+               if( strcmp( item->str, inServiceType ) == 0 ) break;
        }
-       else if( inRDataType == kDNSServiceType_AAAA )
+       if( !item )
        {
-               require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+               newItem = (StringListItem *) calloc( 1, sizeof( *newItem ) );
+               require_action( newItem, exit, err = kNoMemoryErr );
                
-               ASPrintF( &rdataStr, "%.16a", rdataPtr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
+               newItem->str = strdup( inServiceType );
+               require_action( newItem->str, exit, err = kNoMemoryErr );
+               
+               *itemPtr = newItem;
+               newItem = NULL;
        }
-       else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
-                       ( inRDataType == kDNSServiceType_NS ) )
+       err = kNoErr;
+       
+exit:
+       FreeNullSafe( newItem );
+       return( err );
+}
+
+//===========================================================================================================================
+//     ServiceBrowserSetCallback
+//===========================================================================================================================
+
+static void    ServiceBrowserSetCallback( ServiceBrowserRef me, ServiceBrowserCallback_f inCallback, void *inContext )
+{
+       me->userCallback        = inCallback;
+       me->userContext         = inContext;
+}
+
+//===========================================================================================================================
+//     ServiceBrowserResultsRetain
+//===========================================================================================================================
+
+static void    ServiceBrowserResultsRetain( ServiceBrowserResults *inResults )
+{
+       ServiceBrowserResultsPrivate * const            results = (ServiceBrowserResultsPrivate *) inResults;
+       
+       atomic_add_32( &results->refCount, 1 );
+}
+
+//===========================================================================================================================
+//     ServiceBrowserResultsRelease
+//===========================================================================================================================
+
+static void    ServiceBrowserResultsRelease( ServiceBrowserResults *inResults )
+{
+       ServiceBrowserResultsPrivate * const            results = (ServiceBrowserResultsPrivate *) inResults;
+       SBRDomain *                                                                     domain;
+       
+       if( atomic_add_and_fetch_32( &results->refCount, -1 ) == 0 )
        {
-               if( inMsgPtr )
+               while( ( domain = inResults->domainList ) != NULL )
                {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
-                       require_noerr( err, exit );
+                       inResults->domainList = domain->next;
+                       _SBRDomainFree( domain );
                }
-               else
+               free( inResults );
+       }
+}
+
+//===========================================================================================================================
+//     _ServiceBrowserStop
+//===========================================================================================================================
+
+static void    _ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError )
+{
+       OSStatus                                err;
+       SBDomain *                              d;
+       SBServiceType *                 t;
+       SBServiceBrowse *               b;
+       SBServiceInstance *             i;
+       
+       dispatch_source_forget( &me->stopTimer );
+       DNSServiceForget( &me->domainsQuery );
+       for( d = me->domainList; d; d = d->next )
+       {
+               DNSServiceForget( &d->servicesQuery );
+               for( t = d->typeList; t; t = t->next )
                {
-                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
-                       require_noerr( err, exit );
+                       for( b = t->browseList; b; b = b->next )
+                       {
+                               DNSServiceForget( &b->browse );
+                               for( i = b->instanceList; i; i = i->next )
+                               {
+                                       DNSServiceForget( &i->resolve );
+                                       DNSServiceForget( &i->getAddrInfo );
+                               }
+                       }
                }
-               
-               rdataStr = strdup( domainNameStr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
        }
-       else if( inRDataType == kDNSServiceType_SRV )
+       DNSServiceForget( &me->connection );
+       
+       if( me->userCallback )
        {
-               uint16_t                        priority, weight, port;
-               const uint8_t *         target;
+               ServiceBrowserResults *         results = NULL;
                
-               require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr );
+               err = _ServiceBrowserCreateResults( me, &results );
+               if( !err ) err = inError;
                
-               priority        = ReadBig16( rdataPtr );
-               weight          = ReadBig16( rdataPtr + 2 );
-               port            = ReadBig16( rdataPtr + 4 );
-               target          = rdataPtr + 6;
-               
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               
-               ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
+               me->userCallback( results, err, me->userContext );
+               me->userCallback        = NULL;
+               me->userContext         = NULL;
+               if( results ) ServiceBrowserResultsRelease( results );
        }
-       else if( inRDataType == kDNSServiceType_TXT )
+       
+       while( ( d = me->domainList ) != NULL )
        {
-               require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
-               
-               if( inRDataLen == 1 )
-               {
-                       ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
-               else
-               {
-                       ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
+               me->domainList = d->next;
+               _SBDomainFree( d );
        }
-       else if( inRDataType == kDNSServiceType_SOA )
+       CFRelease( me );
+}
+
+//===========================================================================================================================
+//     _ServiceBrowserAddDomain
+//===========================================================================================================================
+
+static OSStatus        _ServiceBrowserAddDomain( ServiceBrowserRef me, const char *inDomain )
+{
+       OSStatus                err;
+       SBDomain *              domain;
+       SBDomain **             domainPtr;
+       SBDomain *              newDomain = NULL;
+       
+       for( domainPtr = &me->domainList; ( domain = *domainPtr ) != NULL; domainPtr = &domain->next )
        {
-               uint32_t                serial, refresh, retry, expire, minimum;
+               if( strcasecmp( domain->name, inDomain ) == 0 ) break;
+       }
+       require_action_quiet( !domain, exit, err = kDuplicateErr );
+       
+       err = _SBDomainCreate( inDomain, me, &newDomain );
+       require_noerr_quiet( err, exit );
+       
+       if( me->serviceTypeList )
+       {
+               const StringListItem *          item;
                
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-                       
-                       require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
-                       
-                       rdataStr = strdup( domainNameStr );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-                       
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
-               else
+               for( item = me->serviceTypeList; item; item = item->next )
                {
-                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-                       
-                       rdataStr = strdup( domainNameStr );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-                       
-                       err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+                       err = _ServiceBrowserAddServiceType( me, newDomain, item->str, me->ifIndex );
+                       if( err == kDuplicateErr ) err = kNoErr;
                        require_noerr( err, exit );
                }
-               
-               require_action_quiet( ( ptr + 20 ) == rdataEnd, exit, err = kMalformedErr );
-               
-               serial  = ReadBig32( ptr );
-               refresh = ReadBig32( ptr +  4 );
-               retry   = ReadBig32( ptr +  8 );
-               expire  = ReadBig32( ptr + 12 );
-               minimum = ReadBig32( ptr + 16 );
-               
-               n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
-               require_action( n > 0, exit, err = kUnknownErr );
        }
-       else if( inRDataType == kDNSServiceType_NSEC )
+       else
        {
-               unsigned int            windowBlock, bitmapLen, i, recordType;
-               const uint8_t *         bitmapPtr;
+               char *                          recordName;
+               DNSServiceFlags         flags;
+               DNSServiceRef           sdRef;
                
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
-                       require_noerr( err, exit );
-               }
+               ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
+               require_action( recordName, exit, err = kNoMemoryErr );
                
-               require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+               flags = kDNSServiceFlagsShareConnection;
+               if( ( me->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
                
-               rdataStr = strdup( domainNameStr );
-               require_action( rdataStr, exit, err = kNoMemoryErr );
+               sdRef = newDomain->browser->connection;
+               err = DNSServiceQueryRecord( &sdRef, flags, me->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
+                       _ServiceBrowserServicesQueryCallback, newDomain );
+               free( recordName );
+               require_noerr( err, exit );
                
-               for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
-               {
-                       require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
-                       
-                       windowBlock     = ptr[ 0 ];
-                       bitmapLen       = ptr[ 1 ];
-                       bitmapPtr       = &ptr[ 2 ];
-                       
-                       require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
-                       require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
-                       
-                       for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
-                       {
-                               if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
-                               {
-                                       recordType = ( windowBlock * 256 ) + i;
-                                       n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
-                                       require_action( n > 0, exit, err = kUnknownErr );
-                               }
-                       }
-               }
+               newDomain->servicesQuery = sdRef;
+       }
+       
+       *domainPtr      = newDomain;
+       newDomain       = NULL;
+       err = kNoErr;
+       
+exit:
+       if( newDomain ) _SBDomainFree( newDomain );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _ServiceBrowserRemoveDomain
+//===========================================================================================================================
+
+static OSStatus        _ServiceBrowserRemoveDomain( ServiceBrowserRef me, const char *inName )
+{
+       OSStatus                err;
+       SBDomain *              domain;
+       SBDomain **             domainPtr;
+       
+       for( domainPtr = &me->domainList; ( domain = *domainPtr ) != NULL; domainPtr = &domain->next )
+       {
+               if( strcasecmp( domain->name, inName ) == 0 ) break;
        }
-       else if( inRDataType == kDNSServiceType_MX )
+       
+       if( domain )
        {
-               uint16_t                        preference;
-               const uint8_t *         exchange;
-               
-               require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
-               
-               preference      = ReadBig16( rdataPtr );
-               exchange        = &rdataPtr[ 2 ];
-               
-               if( inMsgPtr )
-               {
-                       err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               else
-               {
-                       err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
-                       require_noerr( err, exit );
-               }
-               
-               n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
-               require_action( n > 0, exit, err = kUnknownErr );
+               *domainPtr = domain->next;
+               _SBDomainFree( domain );
+               err = kNoErr;
        }
        else
        {
-               err = kNotHandledErr;
-               goto exit;
+               err = kNotFoundErr;
        }
        
-       check( rdataStr );
-       *outString = rdataStr;
-       rdataStr = NULL;
-       err = kNoErr;
+       return( err );
+}
+
+//===========================================================================================================================
+//     _ServiceBrowserTimerHandler
+//===========================================================================================================================
+
+static void    _ServiceBrowserTimerHandler( void *inContext )
+{
+       ServiceBrowserRef const         me = (ServiceBrowserRef) inContext;
+       
+       _ServiceBrowserStop( me, kNoErr );
+}
+
+//===========================================================================================================================
+//     _ServiceBrowserDomainsQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _ServiceBrowserDomainsQueryCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
+{
+       ServiceBrowserRef const         me = (ServiceBrowserRef) inContext;
+       OSStatus                                        err;
+       char                                            domainStr[ kDNSServiceMaxDomainName ];
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inFullName );
+       Unused( inType );
+       Unused( inClass );
+       Unused( inTTL );
+       
+       require_noerr( inError, exit );
+       
+       err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
+       require_noerr( err, exit );
+       
+       if( inFlags & kDNSServiceFlagsAdd )
+       {
+               err = _ServiceBrowserAddDomain( me, domainStr );
+               if( err == kDuplicateErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
+       else
+       {
+               err = _ServiceBrowserRemoveDomain( me, domainStr );
+               if( err == kNotFoundErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
        
 exit:
-       FreeNullSafe( rdataStr );
-       return( err );
+       return;
 }
 
 //===========================================================================================================================
-//     DomainNameAppendString
+//     _ServiceBrowserServicesQueryCallback
 //===========================================================================================================================
 
-static OSStatus
-       DomainNameAppendString(
-               uint8_t                 inDomainName[ kDomainNameLengthMax ],
-               const char *    inString,
-               uint8_t **              outEndPtr )
+static void DNSSD_API
+       _ServiceBrowserServicesQueryCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               uint16_t                                inType,
+               uint16_t                                inClass,
+               uint16_t                                inRDataLen,
+               const void *                    inRDataPtr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
 {
        OSStatus                                        err;
-       const char *                            src;
-       uint8_t *                                       root;
-       const uint8_t * const           nameLim = inDomainName + kDomainNameLengthMax;
+       SBDomain * const                        domain  = (SBDomain *) inContext;
+       ServiceBrowserRef const         me              = domain->browser;
+       const uint8_t *                         src;
+       const uint8_t *                         end;
+       uint8_t *                                       dst;
+       int                                                     i;
+       uint8_t                                         serviceType[ 2 * ( 1 + kDomainLabelLengthMax ) + 1 ];
+       char                                            serviceTypeStr[ kDNSServiceMaxDomainName ];
        
-       for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
-       require_action_quiet( root < nameLim, exit, err = kMalformedErr );
+       Unused( inSDRef );
+       Unused( inFullName );
+       Unused( inTTL );
+       Unused( inType );
+       Unused( inClass );
        
-       // If the string is a single dot, denoting the root domain, then there are no non-empty labels.
+       require_noerr( inError, exit );
        
-       src = inString;
-       if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src;
-       while( *src )
+       check( inType  == kDNSServiceType_PTR );
+       check( inClass == kDNSServiceClass_IN );
+       
+       // The first two labels of the domain name in the RDATA describe a service type.
+       // See <https://tools.ietf.org/html/rfc6763#section-9>.
+       
+       src = (const uint8_t *) inRDataPtr;
+       end = src + inRDataLen;
+       dst = serviceType;
+       for( i = 0; i < 2; ++i )
        {
-               uint8_t * const                         label           = root;
-               const uint8_t * const           labelLim        = Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
-               uint8_t *                                       dst;
-               int                                                     c;
-               size_t                                          labelLen;
+               size_t          labelLen;
                
-               dst = &label[ 1 ];
-               while( *src && ( ( c = *src++ ) != '.' ) )
-               {
-                       if( c == '\\' )
-                       {
-                               require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
-                               c = *src++;
-                               if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
-                               {
-                                       const int               decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
-                                       
-                                       if( decimal <= 255 )
-                                       {
-                                               c = decimal;
-                                               src += 2;
-                                       }
-                               }
-                       }
-                       require_action_quiet( dst < labelLim, exit, err = kOverrunErr );
-                       *dst++ = (uint8_t) c;
-               }
+               require_action_quiet( ( end - src ) > 0, exit, err = kUnderrunErr );
                
-               labelLen = (size_t)( dst - &label[ 1 ] );
-               require_action_quiet( labelLen > 0, exit, err = kMalformedErr );
+               labelLen = *src;
+               require_action_quiet( ( labelLen > 0 ) && ( labelLen <= kDomainLabelLengthMax ), exit, err = kMalformedErr );
+               require_action_quiet( ( (size_t)( end - src ) ) >= ( 1 + labelLen ), exit, err = kUnderrunErr );
                
-               label[ 0 ] = (uint8_t) labelLen;
-               root = dst;
-               *root = 0;
+               memcpy( dst, src, 1 + labelLen );
+               src += 1 + labelLen;
+               dst += 1 + labelLen;
        }
+       *dst = 0;
        
-       if( outEndPtr ) *outEndPtr = root + 1;
-       err = kNoErr;
+       err = DomainNameToString( serviceType, NULL, serviceTypeStr, NULL );
+       require_noerr( err, exit );
+       
+       if( inFlags & kDNSServiceFlagsAdd )
+       {
+               err = _ServiceBrowserAddServiceType( me, domain, serviceTypeStr, inInterfaceIndex );
+               if( err == kDuplicateErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
+       else
+       {
+               err = _ServiceBrowserRemoveServiceType( me, domain, serviceTypeStr, inInterfaceIndex );
+               if( err == kNotFoundErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
        
 exit:
-       return( err );
+       return;
 }
 
 //===========================================================================================================================
-//     DomainNameEqual
+//     _ServiceBrowserBrowseCallback
 //===========================================================================================================================
 
-static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
+static void DNSSD_API
+       _ServiceBrowserBrowseCallback(
+               DNSServiceRef           inSDRef,
+               DNSServiceFlags         inFlags,
+               uint32_t                        inInterfaceIndex,
+               DNSServiceErrorType     inError,
+               const char *            inName,
+               const char *            inRegType,
+               const char *            inDomain,
+               void *                          inContext )
 {
-       const uint8_t *         p1 = inName1;
-       const uint8_t *         p2 = inName2;
-       unsigned int            len;
+       OSStatus                                        err;
+       const uint64_t                          nowTicks        = UpTicks();
+       SBServiceBrowse * const         browse          = (SBServiceBrowse *) inContext;
+       ServiceBrowserRef const         me                      = (ServiceBrowserRef) browse->browser;
        
-       for( ;; )
+       Unused( inSDRef );
+       
+       require_noerr( inError, exit );
+       
+       if( inFlags & kDNSServiceFlagsAdd )
        {
-               if( ( len = *p1++ ) != *p2++ ) return( false );
-               if( len == 0 ) break;
-               for( ; len > 0; ++p1, ++p2, --len )
-               {
-                       if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false );
-               }
+               err = _ServiceBrowserAddServiceInstance( me, browse, inInterfaceIndex, inName, inRegType, inDomain,
+                       UpTicksToMicroseconds( nowTicks - browse->startTicks ) );
+               if( err == kDuplicateErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
+       else
+       {
+               err = _ServiceBrowserRemoveServiceInstance( me, browse, inName, inInterfaceIndex );
+               if( err == kNotFoundErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
+       
+exit:
+       return;
+}
+
+//===========================================================================================================================
+//     _ServiceBrowserResolveCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+       _ServiceBrowserResolveCallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inFullName,
+               const char *                    inHostname,
+               uint16_t                                inPort,
+               uint16_t                                inTXTLen,
+               const unsigned char *   inTXTPtr,
+               void *                                  inContext )
+{
+       OSStatus                                                err;
+       const uint64_t                                  nowTicks        = UpTicks();
+       SBServiceInstance * const               instance        = (SBServiceInstance *) inContext;
+       ServiceBrowserRef const                 me                      = (ServiceBrowserRef) instance->browser;
+       
+       Unused( inSDRef );
+       Unused( inFlags );
+       Unused( inInterfaceIndex );
+       Unused( inFullName );
+       
+       require_noerr( inError, exit );
+       
+       if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
+       {
+               FreeNullSafe( instance->txtPtr );
+               instance->txtPtr = memdup( inTXTPtr, inTXTLen );
+               require_action( instance->txtPtr, exit, err = kNoMemoryErr );
+               
+               instance->txtLen = inTXTLen;
+       }
+       
+       instance->port = ntohs( inPort );
+       
+       if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
+       {
+               DNSServiceRef           sdRef;
+               
+               if( !instance->hostname ) instance->resolveTimeUs = UpTicksToMicroseconds( nowTicks - instance->resolveStartTicks );
+               
+               err = ReplaceString( &instance->hostname, NULL, inHostname, kSizeCString );
+               require_noerr( err, exit );
+               
+               DNSServiceForget( &instance->getAddrInfo );
+               ForgetSBIPAddressList( &instance->ipaddrList );
+               
+               sdRef = me->connection;
+               instance->gaiStartTicks = UpTicks();
+               err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
+                       kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, _ServiceBrowserGAICallback, instance );
+               require_noerr( err, exit );
+               
+               instance->getAddrInfo = sdRef;
        }
-       return( true );
-}
-
-//===========================================================================================================================
-//     DomainNameLength
-//===========================================================================================================================
-
-static size_t  DomainNameLength( const uint8_t * const inName )
-{
-       const uint8_t *         ptr;
        
-       for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {}
-       return( (size_t)( ptr - inName ) + 1 );
+exit:
+       return;
 }
 
 //===========================================================================================================================
-//     DomainNameFromString
+//     _ServiceBrowserGAICallback
 //===========================================================================================================================
 
-static OSStatus
-       DomainNameFromString(
-               uint8_t                 inDomainName[ kDomainNameLengthMax ],
-               const char *    inString,
-               uint8_t **              outEndPtr )
+static void DNSSD_API
+       _ServiceBrowserGAICallback(
+               DNSServiceRef                   inSDRef,
+               DNSServiceFlags                 inFlags,
+               uint32_t                                inInterfaceIndex,
+               DNSServiceErrorType             inError,
+               const char *                    inHostname,
+               const struct sockaddr * inSockAddr,
+               uint32_t                                inTTL,
+               void *                                  inContext )
 {
-       inDomainName[ 0 ] = 0;
-       return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+       OSStatus                                                err;
+       const uint64_t                                  nowTicks        = UpTicks();
+       SBServiceInstance * const               instance        = (SBServiceInstance *) inContext;
+       ServiceBrowserRef const                 me                      = (ServiceBrowserRef) instance->browser;
+       
+       Unused( inSDRef );
+       Unused( inInterfaceIndex );
+       Unused( inHostname );
+       Unused( inTTL );
+       
+       require_noerr( inError, exit );
+       
+       if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+       {
+               dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+               goto exit;
+       }
+       
+       if( inFlags & kDNSServiceFlagsAdd )
+       {
+               err = _ServiceBrowserAddIPAddress( me, instance, inSockAddr,
+                       UpTicksToMicroseconds( nowTicks - instance->gaiStartTicks ) );
+               if( err == kDuplicateErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
+       else
+       {
+               err = _ServiceBrowserRemoveIPAddress( me, instance, inSockAddr );
+               if( err == kNotFoundErr ) err = kNoErr;
+               require_noerr( err, exit );
+       }
+       
+exit:
+       return;
 }
 
 //===========================================================================================================================
-//     DomainNameToString
+//     _ServiceBrowserAddServiceType
 //===========================================================================================================================
 
 static OSStatus
-       DomainNameToString(
-               const uint8_t *         inDomainName,
-               const uint8_t *         inEnd,
-               char                            inBuf[ kDNSServiceMaxDomainName ],
-               const uint8_t **        outNextPtr )
+       _ServiceBrowserAddServiceType(
+               ServiceBrowserRef       me,
+               SBDomain *                      inDomain,
+               const char *            inName,
+               uint32_t                        inIfIndex )
 {
-       OSStatus                        err;
-       const uint8_t *         label;
-       uint8_t                         labelLen;
-       const uint8_t *         nextLabel;
-       char *                          dst;
-       const uint8_t *         src;
-       
-       require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr );
-       
-       // Convert each label up until the root label, i.e., the zero-length label.
+       OSStatus                                err;
+       SBServiceType *                 type;
+       SBServiceType **                typePtr;
+       SBServiceType *                 newType         = NULL;
+       SBServiceBrowse *               browse;
+       SBServiceBrowse **              browsePtr;
+       SBServiceBrowse *               newBrowse       = NULL;
+       DNSServiceRef                   sdRef;
+       DNSServiceFlags                 flags;
        
-       dst = inBuf;
-       for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+       for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
        {
-               require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr );
-               
-               nextLabel = &label[ 1 ] + labelLen;
-               require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr );
-               require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr );
+               if( strcasecmp( type->name, inName ) == 0 ) break;
+       }
+       if( !type )
+       {
+               err = _SBServiceTypeCreate( inName, &newType );
+               require_noerr_quiet( err, exit );
                
-               for( src = &label[ 1 ]; src < nextLabel; ++src )
-               {
-                       if( isprint_safe( *src ) )
-                       {
-                               if( ( *src == '.' ) || ( *src == '\\' ) ||  ( *src == ' ' ) ) *dst++ = '\\';
-                               *dst++ = (char) *src;
-                       }
-                       else
-                       {
-                               *dst++ = '\\';
-                               *dst++ = '0' + (   *src / 100 );
-                               *dst++ = '0' + ( ( *src /  10 ) % 10 );
-                               *dst++ = '0' + (   *src         % 10 );
-                       }
-               }
-               *dst++ = '.';
+               type = newType;
        }
        
-       // At this point, label points to the root label.
-       // If the root label was the only label, then write a dot for it.
+       for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+       {
+               if( browse->ifIndex == inIfIndex ) break;
+       }
+       require_action_quiet( !browse, exit, err = kDuplicateErr );
        
-       if( label == inDomainName ) *dst++ = '.';
-       *dst = '\0';
-       if( outNextPtr ) *outNextPtr = label + 1;
-       err = kNoErr;
+       err = _SBServiceBrowseCreate( inIfIndex, me, &newBrowse );
+       require_noerr_quiet( err, exit );
+       
+       flags = kDNSServiceFlagsShareConnection;
+       if( ( newBrowse->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+       
+       sdRef = me->connection;
+       newBrowse->startTicks = UpTicks();
+       err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, _ServiceBrowserBrowseCallback,
+               newBrowse );
+       require_noerr( err, exit );
+       
+       newBrowse->browse = sdRef;
+       *browsePtr      = newBrowse;
+       newBrowse       = NULL;
+       
+       if( newType )
+       {
+               *typePtr        = newType;
+               newType         = NULL;
+       }
        
 exit:
+       if( newBrowse ) _SBServiceBrowseFree( newBrowse );
+       if( newType )   _SBServiceTypeFree( newType );
        return( err );
 }
 
 //===========================================================================================================================
-//     DNSMessageToText
+//     _ServiceBrowserRemoveServiceType
 //===========================================================================================================================
 
-#define DNSFlagsOpCodeToString( X ) (                                  \
-       ( (X) == kDNSOpCode_Query )                     ? "Query"       :       \
-       ( (X) == kDNSOpCode_InverseQuery )      ? "IQuery"      :       \
-       ( (X) == kDNSOpCode_Status )            ? "Status"      :       \
-       ( (X) == kDNSOpCode_Notify )            ? "Notify"      :       \
-       ( (X) == kDNSOpCode_Update )            ? "Update"      :       \
-                                                                                 "Unassigned" )
-
-#define DNSFlagsRCodeToString( X ) (                                           \
-       ( (X) == kDNSRCode_NoError )            ? "NoError"             :       \
-       ( (X) == kDNSRCode_FormatError )        ? "FormErr"             :       \
-       ( (X) == kDNSRCode_ServerFailure )      ? "ServFail"    :       \
-       ( (X) == kDNSRCode_NXDomain )           ? "NXDomain"    :       \
-       ( (X) == kDNSRCode_NotImplemented )     ? "NotImp"              :       \
-       ( (X) == kDNSRCode_Refused )            ? "Refused"             :       \
-                                                                                 "???" )
-
 static OSStatus
-       DNSMessageToText(
-               const uint8_t * inMsgPtr,
-               size_t                  inMsgLen,
-               const Boolean   inMDNS,
-               const Boolean   inPrintRaw,
-               char **                 outText )
+       _ServiceBrowserRemoveServiceType(
+               ServiceBrowserRef       me,
+               SBDomain *                      inDomain,
+               const char *            inName,
+               uint32_t                        inIfIndex )
 {
-       OSStatus                                        err;
-       DataBuffer                                      dataBuf;
-       size_t                                          len;
-       const DNSHeader *                       hdr;
-       const uint8_t * const           msgEnd = inMsgPtr + inMsgLen;
-       const uint8_t *                         ptr;
-       unsigned int                            id, flags, opcode, rcode;
-       unsigned int                            questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
-       char                                            nameStr[ kDNSServiceMaxDomainName ];
-       
-       DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
-       #define _Append( ... )          do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
-       
-       require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
-       
-       hdr                             = (DNSHeader *) inMsgPtr;
-       id                              = DNSHeaderGetID( hdr );
-       flags                   = DNSHeaderGetFlags( hdr );
-       questionCount   = DNSHeaderGetQuestionCount( hdr );
-       answerCount             = DNSHeaderGetAnswerCount( hdr );
-       authorityCount  = DNSHeaderGetAuthorityCount( hdr );
-       additionalCount = DNSHeaderGetAdditionalCount( hdr );
-       opcode                  = DNSFlagsGetOpCode( flags );
-       rcode                   = DNSFlagsGetRCode( flags );
-       
-       _Append( "ID:               0x%04X (%u)\n", id, id );
-       _Append( "Flags:            0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
-               flags,
-               ( flags & kDNSHeaderFlag_Response )                             ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
-               ( flags & kDNSHeaderFlag_AuthAnswer )                   ? ' ' : '!',
-               ( flags & kDNSHeaderFlag_Truncation )                   ? ' ' : '!',
-               ( flags & kDNSHeaderFlag_RecursionDesired )             ? ' ' : '!',
-               ( flags & kDNSHeaderFlag_RecursionAvailable )   ? ' ' : '!',
-               !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData )               ? " AD" : "!AD",
-               !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled )    ? " CD" : "!CD",
-               DNSFlagsRCodeToString( rcode ) );
-       _Append( "Question count:   %u\n", questionCount );
-       _Append( "Answer count:     %u\n", answerCount );
-       _Append( "Authority count:  %u\n", authorityCount );
-       _Append( "Additional count: %u\n", additionalCount );
+       OSStatus                                err;
+       SBServiceType *                 type;
+       SBServiceType **                typePtr;
+       SBServiceBrowse *               browse;
+       SBServiceBrowse **              browsePtr;
        
-       ptr = (const uint8_t *) &hdr[ 1 ];
-       for( i = 0; i < questionCount; ++i )
-       {
-               unsigned int            qtype, qclass;
-               Boolean                         isQU;
-               
-               err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr );
-               require_noerr( err, exit );
-               
-               if( ( msgEnd - ptr ) < 4 )
-               {
-                       err = kUnderrunErr;
-                       goto exit;
-               }
-               
-               qtype   = DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
-               qclass  = DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
-               ptr += 4;
-               
-               isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
-               if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
-               
-               if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
-               
-               _Append( "%s %2s %?2s%?2u %-5s\n",
-                       nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
-                       ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
-       }
+       Unused( me );
        
-       totalRRCount = answerCount + authorityCount + additionalCount;
-       for( i = 0; i < totalRRCount; ++i )
+       for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
        {
-               uint16_t                        type;
-               uint16_t                        class;
-               uint32_t                        ttl;
-               const uint8_t *         rdataPtr;
-               size_t                          rdataLen;
-               char *                          rdataStr;
-               Boolean                         cacheFlush;
-               uint8_t                         name[ kDomainNameLengthMax ];
-               
-               err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
-               require_noerr( err, exit );
-               
-               err = DomainNameToString( name, NULL, nameStr, NULL );
-               require_noerr( err, exit );
-               
-               cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
-               if( inMDNS ) class &= ~kRRClassCacheFlushBit;
-               
-               rdataStr = NULL;
-               if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
-               if( !rdataStr )
-               {
-                       ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX );
-                       require_action( rdataStr, exit, err = kNoMemoryErr );
-               }
-               
-               if(      answerCount     && ( i ==   0                              ) ) _Append( "\nANSWER SECTION\n" );
-               else if( authorityCount  && ( i ==   answerCount                    ) ) _Append( "\nAUTHORITY SECTION\n" );
-               else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
-               
-               _Append( "%-42s %6u %2s %?2s%?2u %-5s %s\n",
-                       nameStr, ttl, cacheFlush ? "CF" : "",
-                       ( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
-                       RecordTypeToString( type ), rdataStr );
-               free( rdataStr );
+               if( strcasecmp( type->name, inName ) == 0 ) break;
        }
-       _Append( "\n" );
+       require_action_quiet( type, exit, err = kNotFoundErr );
        
-       err = DataBuffer_Append( &dataBuf, "", 1 );
-       require_noerr( err, exit );
+       for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+       {
+               if( browse->ifIndex == inIfIndex ) break;
+       }
+       require_action_quiet( browse, exit, err = kNotFoundErr );
        
-       err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
-       require_noerr( err, exit );
+       *browsePtr = browse->next;
+       _SBServiceBrowseFree( browse );
+       if( !type->browseList )
+       {
+               *typePtr = type->next;
+               _SBServiceTypeFree( type );
+       }
+       err = kNoErr;
        
 exit:
-       DataBuffer_Free( &dataBuf );
        return( err );
 }
 
 //===========================================================================================================================
-//     WriteDNSQueryMessage
+//     _ServiceBrowserAddServiceInstance
 //===========================================================================================================================
 
 static OSStatus
-       WriteDNSQueryMessage(
-               uint8_t                 inMsg[ kDNSQueryMessageMaxLen ],
-               uint16_t                inMsgID,
-               uint16_t                inFlags,
-               const char *    inQName,
-               uint16_t                inQType,
-               uint16_t                inQClass,
-               size_t *                outMsgLen )
+       _ServiceBrowserAddServiceInstance(
+               ServiceBrowserRef       me,
+               SBServiceBrowse *       inBrowse,
+               uint32_t                        inIfIndex,
+               const char *            inName,
+               const char *            inRegType,
+               const char *            inDomain,
+               uint64_t                        inDiscoverTimeUs )
 {
-       OSStatus                                err;
-       DNSHeader * const               hdr = (DNSHeader *) inMsg;
-       uint8_t *                               ptr;
-       size_t                                  msgLen;
+       OSStatus                                        err;
+       DNSServiceRef                           sdRef;
+       SBServiceInstance *                     instance;
+       SBServiceInstance **            instancePtr;
+       SBServiceInstance *                     newInstance     = NULL;
        
-       memset( hdr, 0, sizeof( *hdr ) );
-       DNSHeaderSetID( hdr, inMsgID );
-       DNSHeaderSetFlags( hdr, inFlags );
-       DNSHeaderSetQuestionCount( hdr, 1 );
+       for( instancePtr = &inBrowse->instanceList; ( instance = *instancePtr ) != NULL; instancePtr = &instance->next )
+       {
+               if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+       }
+       require_action_quiet( !instance, exit, err = kDuplicateErr );
        
-       ptr = (uint8_t *)( hdr + 1 );
-       err = DomainNameFromString( ptr, inQName, &ptr );
+       err = _SBServiceInstanceCreate( inName, inIfIndex, inDiscoverTimeUs, me, &newInstance );
        require_noerr_quiet( err, exit );
        
-       DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
-       ptr += 4;
-       
-       msgLen = (size_t)( ptr - inMsg );
-       check( msgLen <= kDNSQueryMessageMaxLen );
+       sdRef = me->connection;
+       newInstance->resolveStartTicks = UpTicks();
+       err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
+               _ServiceBrowserResolveCallback, newInstance );
+       require_noerr( err, exit );
        
-       if( outMsgLen ) *outMsgLen = msgLen;
+       newInstance->resolve = sdRef;
+       *instancePtr    = newInstance;
+       newInstance             = NULL;
        
 exit:
+       if( newInstance ) _SBServiceInstanceFree( newInstance );
        return( err );
 }
 
 //===========================================================================================================================
-//     DispatchSignalSourceCreate
+//     _ServiceBrowserRemoveServiceInstance
 //===========================================================================================================================
 
 static OSStatus
-       DispatchSignalSourceCreate(
-               int                                     inSignal,
-               DispatchHandler         inEventHandler,
-               void *                          inContext,
-               dispatch_source_t *     outSource )
+       _ServiceBrowserRemoveServiceInstance(
+               ServiceBrowserRef       me,
+               SBServiceBrowse *       inBrowse,
+               const char *            inName,
+               uint32_t                        inIfIndex )
 {
-       OSStatus                                err;
-       dispatch_source_t               source;
+       OSStatus                                        err;
+       SBServiceInstance *                     instance;
+       SBServiceInstance **            ptr;
        
-       source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
-       require_action( source, exit, err = kUnknownErr );
+       Unused( me );
        
-       dispatch_set_context( source, inContext );
-       dispatch_source_set_event_handler_f( source, inEventHandler );
+       for( ptr = &inBrowse->instanceList; ( instance = *ptr ) != NULL; ptr = &instance->next )
+       {
+               if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+       }
+       require_action_quiet( instance, exit, err = kNotFoundErr );
        
-       *outSource = source;
+       *ptr = instance->next;
+       _SBServiceInstanceFree( instance );
        err = kNoErr;
        
 exit:
@@ -12599,63 +19826,72 @@ exit:
 }
 
 //===========================================================================================================================
-//     DispatchSocketSourceCreate
+//     _ServiceBrowserAddIPAddress
 //===========================================================================================================================
 
 static OSStatus
-       DispatchSocketSourceCreate(
-               SocketRef                               inSock,
-               dispatch_source_type_t  inType,
-               dispatch_queue_t                inQueue,
-               DispatchHandler                 inEventHandler,
-               DispatchHandler                 inCancelHandler,
-               void *                                  inContext,
-               dispatch_source_t *             outSource )
+       _ServiceBrowserAddIPAddress(
+               ServiceBrowserRef               me,
+               SBServiceInstance *             inInstance,
+               const struct sockaddr * inSockAddr,
+               uint64_t                                inResolveTimeUs )
 {
-       OSStatus                                err;
-       dispatch_source_t               source;
+       OSStatus                        err;
+       SBIPAddress *           ipaddr;
+       SBIPAddress **          ipaddrPtr;
+       SBIPAddress *           newIPAddr = NULL;
        
-       source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
-       require_action( source, exit, err = kUnknownErr );
+       Unused( me );
        
-       dispatch_set_context( source, inContext );
-       dispatch_source_set_event_handler_f( source, inEventHandler );
-       dispatch_source_set_cancel_handler_f( source, inCancelHandler );
+       if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+       {
+               dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+               err = kTypeErr;
+               goto exit;
+       }
        
-       *outSource = source;
+       for( ipaddrPtr = &inInstance->ipaddrList; ( ipaddr = *ipaddrPtr ) != NULL; ipaddrPtr = &ipaddr->next )
+       {
+               if( SockAddrCompareAddr( &ipaddr->sip, inSockAddr ) == 0 ) break;
+       }
+       require_action_quiet( !ipaddr, exit, err = kDuplicateErr );
+       
+       err = _SBIPAddressCreate( inSockAddr, inResolveTimeUs, &newIPAddr );
+       require_noerr_quiet( err, exit );
+       
+       *ipaddrPtr = newIPAddr;
+       newIPAddr = NULL;
        err = kNoErr;
        
 exit:
+       if( newIPAddr ) _SBIPAddressFree( newIPAddr );
        return( err );
 }
 
 //===========================================================================================================================
-//     DispatchTimerCreate
+//     _ServiceBrowserRemoveIPAddress
 //===========================================================================================================================
 
 static OSStatus
-       DispatchTimerCreate(
-               dispatch_time_t         inStart,
-               uint64_t                        inIntervalNs,
-               uint64_t                        inLeewayNs,
-               dispatch_queue_t        inQueue,
-               DispatchHandler         inEventHandler,
-               DispatchHandler         inCancelHandler,
-               void *                          inContext,
-               dispatch_source_t *     outTimer )
+       _ServiceBrowserRemoveIPAddress(
+               ServiceBrowserRef               me,
+               SBServiceInstance *             inInstance,
+               const struct sockaddr * inSockAddr )
 {
-       OSStatus                                err;
-       dispatch_source_t               timer;
+       OSStatus                        err;
+       SBIPAddress *           ipaddr;
+       SBIPAddress **          ipaddrPtr;
        
-       timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
-       require_action( timer, exit, err = kUnknownErr );
+       Unused( me );
        
-       dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
-       dispatch_set_context( timer, inContext );
-       dispatch_source_set_event_handler_f( timer, inEventHandler );
-       dispatch_source_set_cancel_handler_f( timer, inCancelHandler );
+       for( ipaddrPtr = &inInstance->ipaddrList; ( ipaddr = *ipaddrPtr ) != NULL; ipaddrPtr = &ipaddr->next )
+       {
+               if( SockAddrCompareAddr( &ipaddr->sip.sa, inSockAddr ) == 0 ) break;
+       }
+       require_action_quiet( ipaddr, exit, err = kNotFoundErr );
        
-       *outTimer = timer;
+       *ipaddrPtr = ipaddr->next;
+       _SBIPAddressFree( ipaddr );
        err = kNoErr;
        
 exit:
@@ -12663,116 +19899,186 @@ exit:
 }
 
 //===========================================================================================================================
-//     DispatchProcessMonitorCreate
+//     _ServiceBrowserCreateResults
 //===========================================================================================================================
 
-static OSStatus
-       DispatchProcessMonitorCreate(
-               pid_t                           inPID,
-               unsigned long           inFlags,
-               dispatch_queue_t        inQueue,
-               DispatchHandler         inEventHandler,
-               DispatchHandler         inCancelHandler,
-               void *                          inContext,
-               dispatch_source_t *     outMonitor )
+static OSStatus        _ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults )
 {
-       OSStatus                                err;
-       dispatch_source_t               monitor;
+       OSStatus                                                        err;
+       SBDomain *                                                      d;
+       SBServiceType *                                         t;
+       SBServiceBrowse *                                       b;
+       SBServiceInstance *                                     i;
+       SBIPAddress *                                           a;
+       ServiceBrowserResultsPrivate *          results;
+       SBRDomain **                                            domainPtr;
+       
+       results = (ServiceBrowserResultsPrivate *) calloc( 1, sizeof( *results ) );
+       require_action( results, exit, err = kNoMemoryErr );
        
-       monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
-               inQueue ? inQueue : dispatch_get_main_queue() );
-       require_action( monitor, exit, err = kUnknownErr );
+       results->refCount = 1;
        
-       dispatch_set_context( monitor, inContext );
-       dispatch_source_set_event_handler_f( monitor, inEventHandler );
-       dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
+       domainPtr = &results->domainList;
+       for( d = me->domainList; d; d = d->next )
+       {
+               SBRDomain *                             domain;
+               SBRServiceType **               typePtr;
+               
+               err = _SBRDomainCreate( d->name, &domain );
+               require_noerr_quiet( err, exit );
+               *domainPtr = domain;
+                domainPtr = &domain->next;
+               
+               typePtr = &domain->typeList;
+               for( t = d->typeList; t; t = t->next )
+               {
+                       SBRServiceType *                        type;
+                       SBRServiceInstance **           instancePtr;
+                       
+                       err = _SBRServiceTypeCreate( t->name, &type );
+                       require_noerr_quiet( err, exit );
+                       *typePtr = type;
+                        typePtr = &type->next;
+                       
+                       instancePtr = &type->instanceList;
+                       for( b = t->browseList; b; b = b->next )
+                       {
+                               for( i = b->instanceList; i; i = i->next )
+                               {
+                                       SBRServiceInstance *            instance;
+                                       SBRIPAddress **                         ipaddrPtr;
+                                       
+                                       err = _SBRServiceInstanceCreate( i->name, i->ifIndex, i->hostname, i->port, i->txtPtr, i->txtLen,
+                                               i->discoverTimeUs, i->resolveTimeUs, &instance );
+                                       require_noerr_quiet( err, exit );
+                                       *instancePtr = instance;
+                                        instancePtr = &instance->next;
+                                       
+                                       ipaddrPtr = &instance->ipaddrList;
+                                       for( a = i->ipaddrList; a; a = a->next )
+                                       {
+                                               SBRIPAddress *          ipaddr;
+                                               
+                                               err = _SBRIPAddressCreate( &a->sip.sa, a->resolveTimeUs, &ipaddr );
+                                               require_noerr_quiet( err, exit );
+                                               
+                                               *ipaddrPtr = ipaddr;
+                                                ipaddrPtr = &ipaddr->next;
+                                       }
+                               }
+                       }
+               }
+       }
        
-       *outMonitor = monitor;
+       *outResults = (ServiceBrowserResults *) results;
+       results = NULL;
        err = kNoErr;
        
 exit:
+       if( results ) ServiceBrowserResultsRelease( (ServiceBrowserResults *) results );
        return( err );
 }
 
 //===========================================================================================================================
-//     ServiceTypeDescription
+//     _SBDomainCreate
 //===========================================================================================================================
 
-typedef struct
+static OSStatus        _SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain )
 {
-       const char *            name;                   // Name of the service type in two-label "_service._proto" format.
-       const char *            description;    // Description of the service type.
+       OSStatus                err;
+       SBDomain *              obj;
        
-}      ServiceType;
+       obj = (SBDomain *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
+       
+       obj->browser = inBrowser;
+       
+       *outDomain = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) _SBDomainFree( obj );
+       return( err );
+}
 
-// A Non-comprehensive table of DNS-SD service types
+//===========================================================================================================================
+//     _SBDomainFree
+//===========================================================================================================================
 
-static const ServiceType               kServiceTypes[] =
+static void    _SBDomainFree( SBDomain *inDomain )
 {
-       { "_acp-sync._tcp",                     "AirPort Base Station Sync" },
-       { "_adisk._tcp",                        "Automatic Disk Discovery" },
-       { "_afpovertcp._tcp",           "Apple File Sharing" },
-       { "_airdrop._tcp",                      "AirDrop" },
-       { "_airplay._tcp",                      "AirPlay" },
-       { "_airport._tcp",                      "AirPort Base Station" },
-       { "_daap._tcp",                         "Digital Audio Access Protocol (iTunes)" },
-       { "_eppc._tcp",                         "Remote AppleEvents" },
-       { "_ftp._tcp",                          "File Transfer Protocol" },
-       { "_home-sharing._tcp",         "Home Sharing" },
-       { "_homekit._tcp",                      "HomeKit" },
-       { "_http._tcp",                         "World Wide Web HTML-over-HTTP" },
-       { "_https._tcp",                        "HTTP over SSL/TLS" },
-       { "_ipp._tcp",                          "Internet Printing Protocol" },
-       { "_ldap._tcp",                         "Lightweight Directory Access Protocol" },
-       { "_mediaremotetv._tcp",        "Media Remote" },
-       { "_net-assistant._tcp",        "Apple Remote Desktop" },
-       { "_od-master._tcp",            "OpenDirectory Master" },
-       { "_nfs._tcp",                          "Network File System" },
-       { "_presence._tcp",                     "Peer-to-peer messaging / Link-Local Messaging" },
-       { "_pdl-datastream._tcp",       "Printer Page Description Language Data Stream" },
-       { "_raop._tcp",                         "Remote Audio Output Protocol" },
-       { "_rfb._tcp",                          "Remote Frame Buffer" },
-       { "_scanner._tcp",                      "Bonjour Scanning" },
-       { "_smb._tcp",                          "Server Message Block over TCP/IP" },
-       { "_sftp-ssh._tcp",                     "Secure File Transfer Protocol over SSH" },
-       { "_sleep-proxy._udp",          "Sleep Proxy Server" },
-       { "_ssh._tcp",                          "SSH Remote Login Protocol" },
-       { "_teleport._tcp",                     "teleport" },
-       { "_tftp._tcp",                         "Trivial File Transfer Protocol" },
-       { "_workstation._tcp",          "Workgroup Manager" },
-       { "_webdav._tcp",                       "World Wide Web Distributed Authoring and Versioning (WebDAV)" },
-       { "_webdavs._tcp",                      "WebDAV over SSL/TLS" }
-};
+       SBServiceType *         type;
+       
+       ForgetMem( &inDomain->name );
+       DNSServiceForget( &inDomain->servicesQuery );
+       while( ( type = inDomain->typeList ) != NULL )
+       {
+               inDomain->typeList = type->next;
+               _SBServiceTypeFree( type );
+       }
+       free( inDomain );
+}
+
+//===========================================================================================================================
+//     _SBServiceTypeCreate
+//===========================================================================================================================
+
+static OSStatus        _SBServiceTypeCreate( const char *inName, SBServiceType **outType )
+{
+       OSStatus                        err;
+       SBServiceType *         obj;
+       
+       obj = (SBServiceType *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
+       
+       *outType = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) _SBServiceTypeFree( obj );
+       return( err );
+}
+
+//===========================================================================================================================
+//     _SBServiceTypeFree
+//===========================================================================================================================
 
-static const char *    ServiceTypeDescription( const char *inName )
+static void    _SBServiceTypeFree( SBServiceType *inType )
 {
-       const ServiceType *                             serviceType;
-       const ServiceType * const               end = kServiceTypes + countof( kServiceTypes );
+       SBServiceBrowse *               browse;
        
-       for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
+       ForgetMem( &inType->name );
+       while( ( browse = inType->browseList ) != NULL )
        {
-               if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description );
+               inType->browseList = browse->next;
+               _SBServiceBrowseFree( browse );
        }
-       return( NULL );
+       free( inType );
 }
 
 //===========================================================================================================================
-//     SocketContextCreate
+//     _SBServiceBrowseCreate
 //===========================================================================================================================
 
-static OSStatus        SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+static OSStatus        _SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse )
 {
-       OSStatus                        err;
-       SocketContext *         context;
-       
-       context = (SocketContext *) calloc( 1, sizeof( *context ) );
-       require_action( context, exit, err = kNoMemoryErr );
+       OSStatus                                err;
+       SBServiceBrowse *               obj;
        
-       context->refCount               = 1;
-       context->sock                   = inSock;
-       context->userContext    = inUserContext;
+       obj = (SBServiceBrowse *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       *outContext = context;
+       obj->ifIndex = inIfIndex;
+       obj->browser = inBrowser;
+       *outBrowse = obj;
        err = kNoErr;
        
 exit:
@@ -12780,52 +20086,87 @@ exit:
 }
 
 //===========================================================================================================================
-//     SocketContextRetain
+//     _SBServiceBrowseFree
 //===========================================================================================================================
 
-static SocketContext * SocketContextRetain( SocketContext *inContext )
+static void    _SBServiceBrowseFree( SBServiceBrowse *inBrowse )
 {
-       ++inContext->refCount;
-       return( inContext );
+       SBServiceInstance *             instance;
+       
+       DNSServiceForget( &inBrowse->browse );
+       while( ( instance = inBrowse->instanceList ) != NULL )
+       {
+               inBrowse->instanceList = instance->next;
+               _SBServiceInstanceFree( instance );
+       }
+       free( inBrowse );
 }
 
 //===========================================================================================================================
-//     SocketContextRelease
+//     _SBServiceInstanceCreate
 //===========================================================================================================================
 
-static void    SocketContextRelease( SocketContext *inContext )
+static OSStatus
+       _SBServiceInstanceCreate(
+               const char *                    inName,
+               uint32_t                                inIfIndex,
+               uint64_t                                inDiscoverTimeUs,
+               ServiceBrowserRef               inBrowser,
+               SBServiceInstance **    outInstance )
 {
-       if( --inContext->refCount == 0 )
-       {
-               ForgetSocket( &inContext->sock );
-               free( inContext );
-       }
+       OSStatus                                err;
+       SBServiceInstance *             obj;
+       
+       obj = (SBServiceInstance *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
+       
+       obj->ifIndex            = inIfIndex;
+       obj->discoverTimeUs     = inDiscoverTimeUs;
+       obj->browser            = inBrowser;
+       
+       *outInstance = obj;
+       obj = NULL;
+       err = kNoErr;
+       
+exit:
+       if( obj ) _SBServiceInstanceFree( obj );
+       return( err );
 }
 
 //===========================================================================================================================
-//     SocketContextCancelHandler
+//     _SBServiceInstanceFree
 //===========================================================================================================================
 
-static void    SocketContextCancelHandler( void *inContext )
+static void    _SBServiceInstanceFree( SBServiceInstance *inInstance )
 {
-       SocketContextRelease( (SocketContext *) inContext );
+       ForgetMem( &inInstance->name );
+       DNSServiceForget( &inInstance->resolve );
+       ForgetMem( &inInstance->hostname );
+       ForgetMem( &inInstance->txtPtr );
+       DNSServiceForget( &inInstance->getAddrInfo );
+       ForgetSBIPAddressList( &inInstance->ipaddrList );
+       free( inInstance );
 }
 
 //===========================================================================================================================
-//     StringToInt32
+//     _SBIPAddressCreate
 //===========================================================================================================================
 
-static OSStatus        StringToInt32( const char *inString, int32_t *outValue )
+static OSStatus        _SBIPAddressCreate( const struct sockaddr *inSockAddr, uint64_t inResolveTimeUs, SBIPAddress **outIPAddress )
 {
-       OSStatus                err;
-       long                    value;
-       char *                  endPtr;
+       OSStatus                        err;
+       SBIPAddress *           obj;
        
-       value = strtol( inString, &endPtr, 0 );
-       require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
-       require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr );
+       obj = (SBIPAddress *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       *outValue = (int32_t) value;
+       SockAddrCopy( inSockAddr, &obj->sip );
+       obj->resolveTimeUs = inResolveTimeUs;
+       
+       *outIPAddress = obj;
        err = kNoErr;
        
 exit:
@@ -12833,183 +20174,213 @@ exit:
 }
 
 //===========================================================================================================================
-//     StringToUInt32
+//     _SBIPAddressFree
 //===========================================================================================================================
 
-static OSStatus        StringToUInt32( const char *inString, uint32_t *outValue )
+static void _SBIPAddressFree( SBIPAddress *inIPAddress )
 {
-       OSStatus                err;
-       uint32_t                value;
-       char *                  endPtr;
-       
-       value = (uint32_t) strtol( inString, &endPtr, 0 );
-       require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
-       
-       *outValue = value;
-       err = kNoErr;
+       free( inIPAddress );
+}
+
+//===========================================================================================================================
+//     _SBIPAddressFreeList
+//===========================================================================================================================
+
+static void    _SBIPAddressFreeList( SBIPAddress *inList )
+{
+       SBIPAddress *           ipaddr;
        
-exit:
-       return( err );
+       while( ( ipaddr = inList ) != NULL )
+       {
+               inList = ipaddr->next;
+               _SBIPAddressFree( ipaddr );
+       }
 }
 
 //===========================================================================================================================
-//     StringToLongLong
+//     _SBRDomainCreate
 //===========================================================================================================================
 
-static OSStatus        StringToLongLong( const char *inString, long long *outValue )
+static OSStatus        _SBRDomainCreate( const char *inName, SBRDomain **outDomain )
 {
        OSStatus                err;
-       long long               value;
-       char *                  endPtr;
+       SBRDomain *             obj;
        
-       set_errno_compat( 0 );
-       value = strtol( inString, &endPtr, 0 );
-       err = errno_compat();
-       if( ( ( value == LLONG_MIN ) || ( value == LLONG_MAX ) ) && ( err == ERANGE ) ) goto exit;
-       require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+       obj = (SBRDomain *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       *outValue = value;
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
+       
+       *outDomain = obj;
+       obj = NULL;
        err = kNoErr;
        
 exit:
+       if( obj ) _SBRDomainFree( obj );
        return( err );
 }
 
 //===========================================================================================================================
-//     StringToARecordData
+//     _SBRDomainFree
 //===========================================================================================================================
 
-static OSStatus        StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+static void    _SBRDomainFree( SBRDomain *inDomain )
 {
-       OSStatus                        err;
-       uint32_t *                      addrPtr;
-       const size_t            addrLen = sizeof( *addrPtr );
-       const char *            end;
+       SBRServiceType *                type;
        
-       addrPtr = (uint32_t *) malloc( addrLen );
-       require_action( addrPtr, exit, err = kNoMemoryErr );
+       ForgetMem( &inDomain->name );
+       while( ( type = inDomain->typeList ) != NULL )
+       {
+               inDomain->typeList = type->next;
+               _SBRServiceTypeFree( type );
+       }
+       free( inDomain );
+}
+
+//===========================================================================================================================
+//     _SBRServiceTypeCreate
+//===========================================================================================================================
+
+static OSStatus        _SBRServiceTypeCreate( const char *inName, SBRServiceType **outType )
+{
+       OSStatus                                err;
+       SBRServiceType *                obj;
        
-       err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
-               NULL, NULL, NULL, &end );
-       if( !err && ( *end != '\0' ) ) err = kMalformedErr;
-       require_noerr_quiet( err, exit );
+       obj = (SBRServiceType *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       *addrPtr = HostToBig32( *addrPtr );
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
        
-       *outPtr = (uint8_t *) addrPtr;
-       addrPtr = NULL;
-       *outLen = addrLen;
+       *outType = obj;
+       obj = NULL;
+       err = kNoErr;
        
 exit:
-       FreeNullSafe( addrPtr );
+       if( obj ) _SBRServiceTypeFree( obj );
        return( err );
 }
 
 //===========================================================================================================================
-//     StringToAAAARecordData
+//     _SBRServiceTypeFree
 //===========================================================================================================================
 
-static OSStatus        StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+static void    _SBRServiceTypeFree( SBRServiceType *inType )
 {
-       OSStatus                        err;
-       uint8_t *                       addrPtr;
-       const size_t            addrLen = 16;
-       const char *            end;
-       
-       addrPtr = (uint8_t *) malloc( addrLen );
-       require_action( addrPtr, exit, err = kNoMemoryErr );
-       
-       err = StringToIPv6Address( inString,
-               kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
-               addrPtr, NULL, NULL, NULL, &end );
-       if( !err && ( *end != '\0' ) ) err = kMalformedErr;
-       require_noerr_quiet( err, exit );
-       
-       *outPtr = addrPtr;
-       addrPtr = NULL;
-       *outLen = addrLen;
+       SBRServiceInstance *            instance;
        
-exit:
-       FreeNullSafe( addrPtr );
-       return( err );
+       ForgetMem( &inType->name );
+       while( ( instance = inType->instanceList ) != NULL )
+       {
+               inType->instanceList = instance->next;
+               _SBRServiceInstanceFree( instance );
+       }
+       free( inType );
 }
 
 //===========================================================================================================================
-//     StringToDomainName
+//     _SBRServiceInstanceCreate
 //===========================================================================================================================
 
-static OSStatus        StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
+static OSStatus
+       _SBRServiceInstanceCreate(
+               const char *                    inName,
+               uint32_t                                inInterfaceIndex,
+               const char *                    inHostname,
+               uint16_t                                inPort,
+               const uint8_t *                 inTXTPtr,
+               size_t                                  inTXTLen,
+               uint64_t                                inDiscoverTimeUs,
+               uint64_t                                inResolveTimeUs,
+               SBRServiceInstance **   outInstance )
 {
-       OSStatus                err;
-       uint8_t *               namePtr;
-       size_t                  nameLen;
-       uint8_t *               end;
-       uint8_t                 nameBuf[ kDomainNameLengthMax ];
+       OSStatus                                        err;
+       SBRServiceInstance *            obj;
        
-       err = DomainNameFromString( nameBuf, inString, &end );
-       require_noerr_quiet( err, exit );
+       obj = (SBRServiceInstance *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
        
-       nameLen = (size_t)( end - nameBuf );
-       namePtr = memdup( nameBuf, nameLen );
-       require_action( namePtr, exit, err = kNoMemoryErr );
+       obj->name = strdup( inName );
+       require_action( obj->name, exit, err = kNoMemoryErr );
        
-       *outPtr = namePtr;
-       namePtr = NULL;
-       if( outLen ) *outLen = nameLen;
+       if( inHostname )
+       {
+               obj->hostname = strdup( inHostname );
+               require_action( obj->hostname, exit, err = kNoMemoryErr );
+       }
+       if( inTXTLen > 0 )
+       {
+               obj->txtPtr = (uint8_t *) memdup( inTXTPtr, inTXTLen );
+               require_action( obj->txtPtr, exit, err = kNoMemoryErr );
+               obj->txtLen = inTXTLen;
+       }
+       obj->discoverTimeUs     = inDiscoverTimeUs;
+       obj->resolveTimeUs      = inResolveTimeUs;
+       obj->ifIndex            = inInterfaceIndex;
+       obj->port                       = inPort;
+       
+       *outInstance = obj;
+       obj = NULL;
+       err = kNoErr;
        
 exit:
+       if( obj ) _SBRServiceInstanceFree( obj );
        return( err );
 }
 
-#if( TARGET_OS_DARWIN )
 //===========================================================================================================================
-//     GetDefaultDNSServer
+//     _SBRServiceInstanceFree
 //===========================================================================================================================
 
-static OSStatus        GetDefaultDNSServer( sockaddr_ip *outAddr )
+static void    _SBRServiceInstanceFree( SBRServiceInstance *inInstance )
 {
-       OSStatus                                err;
-       dns_config_t *                  config;
-       struct sockaddr *               addr;
-       int32_t                                 i;
-       
-       config = dns_configuration_copy();
-       require_action( config, exit, err = kUnknownErr );
+       SBRIPAddress *          ipaddr;
        
-       addr = NULL;
-       for( i = 0; i < config->n_resolver; ++i )
+       ForgetMem( &inInstance->name );
+       ForgetMem( &inInstance->hostname );
+       ForgetMem( &inInstance->txtPtr );
+       while( ( ipaddr = inInstance->ipaddrList ) != NULL )
        {
-               const dns_resolver_t * const            resolver = config->resolver[ i ];
-               
-               if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
-               {
-                       addr = resolver->nameserver[ 0 ];
-                       break;
-               }
-       }
-       require_action_quiet( addr, exit, err = kNotFoundErr );
+               inInstance->ipaddrList = ipaddr->next;
+               _SBRIPAddressFree( ipaddr );
+       }
+       free( inInstance );
+}
+
+//===========================================================================================================================
+//     _SBRIPAddressCreate
+//===========================================================================================================================
+
+static OSStatus
+       _SBRIPAddressCreate(
+               const struct sockaddr * inSockAddr,
+               uint64_t                                inResolveTimeUs,
+               SBRIPAddress **                 outIPAddress )
+{
+       OSStatus                        err;
+       SBRIPAddress *          obj;
        
-       SockAddrCopy( addr, outAddr );
+       obj = (SBRIPAddress *) calloc( 1, sizeof( *obj ) );
+       require_action( obj, exit, err = kNoMemoryErr );
+       
+       SockAddrCopy( inSockAddr, &obj->sip );
+       obj->resolveTimeUs = inResolveTimeUs;
+       
+       *outIPAddress = obj;
        err = kNoErr;
        
 exit:
-       if( config ) dns_configuration_free( config );
        return( err );
 }
-#endif
 
 //===========================================================================================================================
-//     GetCurrentMicroTime
+//     _SBRIPAddressFree
 //===========================================================================================================================
 
-static MicroTime64     GetCurrentMicroTime( void )
+static void    _SBRIPAddressFree( SBRIPAddress *inIPAddress )
 {
-       struct timeval          now;
-       
-       TIMEVAL_ZERO( now );
-       gettimeofday( &now, NULL );
-       
-       return( (MicroTime64) TIMEVAL_USEC64( now ) );
+       free( inIPAddress );
 }
 
 //===========================================================================================================================
@@ -13853,3 +21224,24 @@ int    memicmp( const void *inP1, const void *inP2, size_t inLen )
        return( 0 );
 }
 #endif
+
+//===========================================================================================================================
+//     FNV1
+//
+//     Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+uint32_t       FNV1( const void *inData, size_t inSize )
+{
+       const uint8_t *                         src = (const uint8_t *) inData;
+       const uint8_t * const           end = src + inSize;
+       uint32_t                                        hash;
+       
+       hash = 0x811c9dc5U;
+       while( src != end )
+       {
+               hash *= 0x01000193;
+               hash ^= *src++;
+       }
+       return( hash );
+}
index 78adbd5ec5afe38200a1658e012bf575a03c7114..9aaa08eb820fb5a8726be8803f88540181ab29bb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@
 
 include $(MAKEFILEPATH)/pb_makefiles/platform.make
 
-MVERS = "mDNSResponder-878.240.1"
+MVERS = "mDNSResponder-878.250.4"
 
 VER =
 ifneq ($(strip $(GCC_VERSION)),)
diff --git a/mDNSMacOSX/BATS/mDNSResponder.plist b/mDNSMacOSX/BATS/mDNSResponder.plist
new file mode 100644 (file)
index 0000000..66a8c49
--- /dev/null
@@ -0,0 +1,627 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>Project</key>
+       <string>mDNSResponder</string>
+       <key>RadarComponents</key>
+       <dict>
+               <key>Name</key>
+               <string>mDNSResponder</string>
+               <key>Version</key>
+               <string>all</string>
+       </dict>
+       <key>Tests</key>
+       <array>
+               <dict>
+                       <key>TestName</key>
+                       <string>GAIPerf Advanced</string>
+                       <key>Description</key>
+                       <string>Tests correctness of resolving hostnames via DNS using the GAIPerf Advanced test suite.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>600</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>gaiperf</string>
+                               <string>--suite</string>
+                               <string>advanced</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--skipPathEval</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 1-1-1</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 1-1-1 (No Additionals)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Responses from mdnsreplier contain no additional answers.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--noAdditionals</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 10-100-2</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 10-100-2 (No Additionals)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Responses from mdnsreplier contain no additonal answers.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--noAdditionals</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 100-500-5</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>5</string>
+                               <string>--countAAAA</string>
+                               <string>5</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 100-500-5 (No Additionals)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Responses from mdnsreplier contain no additonal answers.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>5</string>
+                               <string>--countAAAA</string>
+                               <string>5</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--noAdditionals</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 1-1-1 (No Cache Flush)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Cache is not flushed beforehand.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 1-1-1 (No Cache Flush, No Additionals)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>1</string>
+                               <string>--txtSize</string>
+                               <string>1</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>1</string>
+                               <string>--countAAAA</string>
+                               <string>1</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--noAdditionals</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 10-100-2 (No Cache Flush)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Cache is not flushed beforehand.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 10-100-2 (No Cache Flush, No Additionals)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>3</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--noAdditionals</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 100-500-5 (No Cache Flush)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Cache is not flushed beforehand.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>5</string>
+                               <string>--countAAAA</string>
+                               <string>5</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery 100-500-5 (No Cache Flush, No Additionals)</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>500</string>
+                               <string>--browseTime</string>
+                               <string>5</string>
+                               <string>--countA</string>
+                               <string>5</string>
+                               <string>--countAAAA</string>
+                               <string>5</string>
+                               <string>--ipv4</string>
+                               <string>--ipv6</string>
+                               <string>--noAdditionals</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery w/Packet Drops 10</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>30</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>10</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>16</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv6</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNS Discovery w/Packet Drops 100</string>
+                       <key>Description</key>
+                       <string>Tests mDNS discovery and resolution of 100 service instances with 100-byte TXT records and two pairs of A and AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>30</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>mdnsdiscovery</string>
+                               <string>--instanceCount</string>
+                               <string>100</string>
+                               <string>--txtSize</string>
+                               <string>100</string>
+                               <string>--browseTime</string>
+                               <string>18</string>
+                               <string>--countA</string>
+                               <string>2</string>
+                               <string>--countAAAA</string>
+                               <string>2</string>
+                               <string>--ipv6</string>
+                               <string>--udrop</string>
+                               <string>0.5</string>
+                               <string>--mdrop</string>
+                               <string>0.5</string>
+                               <string>--maxDropCount</string>
+                               <string>3</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--flushCache</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>DotLocal Queries</string>
+                       <key>Description</key>
+                       <string>Tests DNS and mDNS queries for domain names in the local domain.</string>
+                       <key>AsRoot</key>
+                       <false/>
+                       <key>RequiresWiFi</key>
+                       <true/>
+                       <key>Timeout</key>
+                       <integer>40</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>dotlocal</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>TCP Fallback</string>
+                       <key>Description</key>
+                       <string>Tests mDNSResponder&apos;s TCP fallback mechanism, which is triggered by UDP responses with invalid message IDs that would otherwise be acceptable.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>60</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/local/bin/dnssdutil</string>
+                               <string>test</string>
+                               <string>gaiperf</string>
+                               <string>--suite</string>
+                               <string>basic</string>
+                               <string>--format</string>
+                               <string>json</string>
+                               <string>--appendNewLine</string>
+                               <string>--skipPathEval</string>
+                               <string>--badUDPMode</string>
+                       </array>
+               </dict>
+               <dict>
+                       <key>TestName</key>
+                       <string>mDNSResponder Leaks</string>
+                       <key>Description</key>
+                       <string>Checks mDNSResponder for memory leaks.</string>
+                       <key>AsRoot</key>
+                       <true/>
+                       <key>RequiresWiFi</key>
+                       <false/>
+                       <key>Timeout</key>
+                       <integer>10</integer>
+                       <key>IgnoreOutput</key>
+                       <true/>
+                       <key>Command</key>
+                       <array>
+                               <string>/usr/bin/leaks</string>
+                               <string>mDNSResponder</string>
+                       </array>
+               </dict>
+       </array>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/dnssdutil-entitlements.plist b/mDNSMacOSX/dnssdutil-entitlements.plist
new file mode 100644 (file)
index 0000000..750f16a
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>com.apple.security.network.client</key>
+       <true/>
+       <key>com.apple.security.network.server</key>
+       <true/>
+       <key>com.apple.SystemConfiguration.SCDynamicStore-write-access</key>
+       <true/>
+</dict>
+</plist>
index 51fc67a8dd745569e15a4df5cd8914f8aa88e36c..4714293570b09ea6c98f70dbb6584f693ab01432 100644 (file)
@@ -6875,7 +6875,7 @@ typedef struct
 #include <IOKit/IOKitLib.h>
 #include <dns_util.h>
 
-mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray)
+mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray, mDNSBool TCPKAOnly, mDNSBool supportsTCPKA)
 {
     mDNS *const m = &mDNSStorage;
     const domainlabel *const tp = (trans == mDNSTransport_UDP) ? (const domainlabel *)"\x4_udp" : (const domainlabel *)"\x4_tcp";
@@ -6884,6 +6884,14 @@ mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray)
     AuthRecord *rr;
     for (rr = m->ResourceRecords; rr; rr=rr->next)
     {
+        mDNSBool isKeepAliveRecord = mDNS_KeepaliveRecord(&rr->resrec);
+        // Skip over all other records if we are registering TCP KeepAlive records only
+        // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive
+        // supportsTCPKA is set to true if both policy and interface allow TCP Keepalive
+        if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA)) {
+            continue;
+        }
+
         if (rr->resrec.rrtype == kDNSType_SRV && SameDomainLabel(ThirdLabel(rr->resrec.name)->c, tp->c))
         {
             if (!portarray)
@@ -7013,6 +7021,7 @@ mDNSlocal void GetProxyRecords(DNSMessage *const msg, uint32_t *const numbytes,
 
             // Skip over all other records if we are registering TCP KeepAlive records only
             // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive
+            // supportsTCPKA is set to true if both policy and interface allow TCP Keepalive
             if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA))
                 continue;
 
@@ -7134,8 +7143,8 @@ mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool
                     mDNSOffloadCmd cmd;
                     mDNSPlatformMemZero(&cmd, sizeof(cmd)); // When compiling 32-bit, make sure top 32 bits of 64-bit pointers get initialized to zero
                     cmd.command       = cmd_mDNSOffloadRR;
-                    cmd.numUDPPorts   = GetPortArray(mDNSTransport_UDP, mDNSNULL);
-                    cmd.numTCPPorts   = GetPortArray(mDNSTransport_TCP, mDNSNULL);
+                    cmd.numUDPPorts   = GetPortArray(mDNSTransport_UDP, mDNSNULL, TCPKAOnly, supportsTCPKA);
+                    cmd.numTCPPorts   = GetPortArray(mDNSTransport_TCP, mDNSNULL, TCPKAOnly, supportsTCPKA);
                     cmd.numRRRecords  = CountProxyRecords(&cmd.rrBufferSize, intf, TCPKAOnly, supportsTCPKA);
                     cmd.compression   = sizeof(DNSMessageHeader);
 
@@ -7151,8 +7160,8 @@ mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool
                            cmd.tcpPorts.ptr, cmd.numTCPPorts);
 
                     if (msg && cmd.rrRecords.ptr) GetProxyRecords(msg, &cmd.rrBufferSize, cmd.rrRecords.ptr, TCPKAOnly, supportsTCPKA);
-                    if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr);
-                    if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr);
+                    if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr, TCPKAOnly, supportsTCPKA);
+                    if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr, TCPKAOnly, supportsTCPKA);
 
                     char outputData[2];
                     size_t outputDataSize = sizeof(outputData);
index bbbd12ff9cfe85d26e3a50ff98540b6b0cb73e2b..8067d931b566fc7ec1aaa72c7afb97d948ea2977 100644 (file)
                BDB04224203FF18000419961 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
                BDB61845206ADB9D00AFF600 /* com.apple.mDNSResponder.plist in Copy Base Logging Profile */ = {isa = PBXBuildFile; fileRef = BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */; };
                BDBF9B941ED74B9C001498A8 /* DNS64State.h in Headers */ = {isa = PBXBuildFile; fileRef = BDBF9B931ED74B8C001498A8 /* DNS64State.h */; };
+               BDF8BB902208E2A800419B62 /* mDNSResponder.plist in Copy BATS test plist */ = {isa = PBXBuildFile; fileRef = BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */; };
                D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */ = {isa = PBXBuildFile; fileRef = F5E11B5B04A28126019798ED /* dnssd_ipc.h */; };
                D284BE580ADD80740027CCDF /* mDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBE9022EAF5A00000109 /* mDNS.c */; };
                D284BE590ADD80740027CCDF /* uDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F70587CEF6001880B3 /* uDNS.c */; };
                        name = "Copy AppleInternal Logging Profile";
                        runOnlyForDeploymentPostprocessing = 1;
                };
+               BDF8BB8A2208E09D00419B62 /* Copy BATS test plist */ = {
+                       isa = PBXCopyFilesBuildPhase;
+                       buildActionMask = 8;
+                       dstPath = /AppleInternal/CoreOS/BATS/unit_tests;
+                       dstSubfolderSpec = 0;
+                       files = (
+                               BDF8BB902208E2A800419B62 /* mDNSResponder.plist in Copy BATS test plist */,
+                       );
+                       name = "Copy BATS test plist";
+                       runOnlyForDeploymentPostprocessing = 1;
+               };
                D284BE6A0ADD80740027CCDF /* CopyFiles */ = {
                        isa = PBXCopyFilesBuildPhase;
                        buildActionMask = 8;
                BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
                BDBF9B931ED74B8C001498A8 /* DNS64State.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64State.h; sourceTree = "<group>"; };
                BDE238C11DF69D8300B9F696 /* dns_sd_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dns_sd_internal.h; path = ../mDNSShared/dns_sd_internal.h; sourceTree = "<group>"; };
+               BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = "<group>"; };
                D284BE730ADD80740027CCDF /* mDNSResponder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mDNSResponder; sourceTree = BUILT_PRODUCTS_DIR; };
                D284BEB00ADD80920027CCDF /* dns-sd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "dns-sd"; sourceTree = BUILT_PRODUCTS_DIR; };
                D284BEBE0ADD809A0027CCDF /* libjdns_sd.jnilib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libjdns_sd.jnilib; sourceTree = BUILT_PRODUCTS_DIR; };
                                BD9BA7561EAF929C00658CCF /* Frameworks */,
                                BDB61842206ADB7700AFF600 /* LoggingProfiles */,
                                BD28AE8D207B88F600F0B257 /* Scripts */,
+                               BDF8BB8E2208E26E00419B62 /* BATS */,
                        );
                        name = mDNSResponder;
                        sourceTree = "<group>";
                        path = AppleInternal;
                        sourceTree = "<group>";
                };
+               BDF8BB8E2208E26E00419B62 /* BATS */ = {
+                       isa = PBXGroup;
+                       children = (
+                               BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */,
+                       );
+                       path = BATS;
+                       sourceTree = "<group>";
+               };
                DB2CC4420662DCE500335AB3 /* Java Support */ = {
                        isa = PBXGroup;
                        children = (
                                8418673D15AB8BFF00BB7F70 /* Copy Base Logging Profile */,
                                BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */,
                                BD28AE8C207B888E00F0B257 /* Copy diagnose scripts */,
+                               BDF8BB8A2208E09D00419B62 /* Copy BATS test plist */,
                        );
                        buildRules = (
                        );
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
                                CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
                                CLANG_CXX_LIBRARY = "libc++";
-                               CLANG_ENABLE_MODULES = YES;
                                CLANG_ENABLE_OBJC_ARC = YES;
                                CLANG_WARN_BOOL_CONVERSION = YES;
                                CLANG_WARN_CONSTANT_CONVERSION = YES;
                                CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+                               CODE_SIGN_ENTITLEMENTS = "dnssdutil-entitlements.plist";
+                               CODE_SIGN_IDENTITY = "-";
                                FRAMEWORK_SEARCH_PATHS = (
                                        "$(inherited)",
                                        "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
                                CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
                                CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
                                CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+                               CODE_SIGN_ENTITLEMENTS = "dnssdutil-entitlements.plist";
+                               CODE_SIGN_IDENTITY = "-";
                                FRAMEWORK_SEARCH_PATHS = (
                                        "$(inherited)",
                                        "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
index 3495c6e6da2e616249063a57c3d78e0dd54bbbd8..f76fbbdd7452f97f06410be13ebad4916229e53d 100644 (file)
@@ -66,7 +66,7 @@
  */
 
 #ifndef _DNS_SD_H
-#define _DNS_SD_H 8804001
+#define _DNS_SD_H 8805004
 
 #ifdef  __cplusplus
 extern "C" {